:root {
    /* Neutral dark palette — matches the production Material Dark theme. */
    --icon-xs: 0.75rem;
    --icon-sm: 0.8125rem;
    --icon-md: 0.875rem;
    --icon-lg: 1rem;
    --icon-xl: 1.125rem;
    --icon-2xl: 1.25rem;

    /* PLAN-003 lot 3 / D8: the one height every control outside a table has. Measured in the
       browser on the reference button ("Manage Dashboards", legacy UIButton default size, icon +
       text): 28px, so 1.75rem. Never guessed, never tuned by eye. Tables are excluded on purpose:
       a row action is governed by the grid row height instead (lot 4). Recette R-309: it follows
       OE's control size setting (--omni-control-scale), and OE's own token is set from it below. */
    --aetheus-control-height: calc(1.75rem * var(--omni-control-scale, 1));

    /* PLAN-003 lot 8 / D25: the one corner radius of the site, read in the browser on a project
       tile (the reference the user named): 4px, which is the previous component own --omni-radius. Aliasing
       the theme token instead of copying the number keeps cards, buttons and fields on the same
       corner as every legacy UI component that already uses it. Deliberately round shapes (the dev
       chip, the counter dots, the login logo, pills) are the written exceptions. */
    --aetheus-radius: var(--omni-radius);

    /* PLAN-008 lot 4 / D35 (STD-BUSY): the page background, light or dark. legacy UI swaps its whole
       theme sheet, so aliasing its background token follows the active theme without a second
       block. The busy veil fades towards this colour, never towards whatever sits behind a button. */
    --surface: var(--omni-color-surface);

    /* legacy UI Compact data rows use 0.25rem vertical cell padding around content capped at
       1.25rem, plus the table border. Keep these tokens shared so rich templates cannot make
       one row taller than the default compact row. */
    --aetheus-grid-row-content-height: 1.25rem;
    --aetheus-grid-row-height: 2.25rem;
    /* PLAN-003 lot 4 / D9: a row action is a square in the row. Recette R-345: its side is OE's
       --omni-badge-height, the size OE gives an icon-only button of a grid row and the status badges
       of the same row; this alias only feeds the cell padding and height caps below. */
    --aetheus-grid-action-size: var(--omni-badge-height);

    /* Console code-block design tokens (consumed by the console-style classes .task-output /
       .pipeline-step-output / .server-detail-code and the analysis prompt blocks; the AddAgent wizard now
       uses OE OmniCodeBlock). Recette R-400: text on the inverse surface is OE's on-inverse colour, so it
       stays readable in both themes (it was the page text colour, dark on dark in the light theme). */
    --wizard-code-bg: var(--omni-color-inverse-surface, #1e1e1e);
    --wizard-code-border: color-mix(in srgb, var(--omni-color-on-inverse) 20%, var(--omni-color-inverse-surface));
    --wizard-code-text: var(--omni-color-on-inverse, #e0e0e0);
    --wizard-code-radius: var(--aetheus-radius);
    /* State variants — applied via .is-success / .is-error modifiers (e.g. agent connected). */
    --wizard-code-bg-success: color-mix(in srgb, var(--omni-color-success, #28a745) 12%, var(--wizard-code-bg));
    --wizard-code-border-success: color-mix(in srgb, var(--omni-color-success, #28a745) 60%, var(--wizard-code-border));
    --wizard-code-bg-error: color-mix(in srgb, var(--omni-color-danger, #dc3545) 12%, var(--wizard-code-bg));
    --wizard-code-border-error: color-mix(in srgb, var(--omni-color-danger, #dc3545) 60%, var(--wizard-code-border));
}

.analysis-summary-card {
    min-height: 8rem;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 0.25rem;
    text-align: center;
}

/* Recette R-100: the summary tiles that are buttons (Open findings, New, Critical, High, Fixed) fell
   under the control-height token, 28px for 52px of icon, figure and label, which then overlapped. Two
   classes, so the tile's own height wins over the button's.
   Recette R-175: the six tiles (the static "Last analysis" one included) share one anatomy (icon,
   figure, label, one line each) and one height, 5rem, instead of 154px for a figure and 180px for
   the static tile. Critical and High keep their own icon and label, so colour is never the only cue. */
.analysis-summary-row {
    --analysis-summary-tile-height: 5rem;
}

.analysis-summary-row .analysis-summary-card {
    box-sizing: border-box;
    width: 100%;
    min-width: 0;
    height: var(--analysis-summary-tile-height);
    min-height: var(--analysis-summary-tile-height);
    gap: 0.125rem;
}

.analysis-summary-card .omni-icon {
    font-size: 1.75rem;
}

/* Recette R-178: one card fill on the page, the OE surface, and the tile border token (R-055). */
.analysis-summary-action.omni-button,
.analysis-summary-static {
    padding: 0.375rem 0.5rem;
    border: 0.0625rem solid var(--aetheus-tile-border);
    border-radius: var(--aetheus-radius);
    color: var(--omni-color-text);
}

.analysis-summary-action.omni-button {
    width: 100%;
    min-width: 0;
    background-color: var(--omni-color-surface) !important;
    transition: border-color 120ms ease, background-color 120ms ease, transform 120ms ease;
}

.analysis-summary-action.omni-button:hover {
    border-color: color-mix(in srgb, var(--omni-color-accent) 48%, transparent);
    background: color-mix(in srgb, var(--omni-color-accent) 8%, var(--omni-color-surface));
}

.analysis-summary-action.omni-button > .omni-button__content {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.125rem;
    width: 100%;
    min-width: 0;
}

.analysis-summary-action.omni-button .omni-text {
    color: inherit;
}

.analysis-summary-static {
    background: var(--omni-color-surface);
}

.analysis-summary-card .omni-text {
    margin: 0;
    line-height: 1.2;
}

/* Figure and label each hold one line; a value too long for the tile is cut, never wrapped. */
.analysis-summary-row .analysis-summary-card .omni-text {
    display: block;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.analysis-summary-row .analysis-summary-card .analysis-summary-figure {
    font-size: 1.25rem;
    font-weight: 700;
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

/* The last analysis figure is a date, not a count: a smaller size on the same 1.375rem line. */
.analysis-summary-row .analysis-summary-static .analysis-summary-figure {
    font-size: 0.9375rem;
    line-height: 1.4667;
}

.analysis-summary-row .analysis-summary-card .analysis-summary-label {
    font-size: 0.75rem;
    line-height: 1.2;
}

.analysis-workspace-tabs.omni-tabs {
    min-width: 0;
    min-height: 0;
}

.analysis-workspace-tabs > .omni-tabs__strip .omni-tabs__viewport {
    gap: 0.25rem;
    padding: 0 0.25rem;
    border-bottom: 0.0625rem solid color-mix(in srgb, var(--omni-color-text) 18%, transparent);
    background: transparent;
}

.analysis-workspace-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__tab {
    gap: 0.4rem;
    min-height: 3rem;
    padding-inline: 1rem;
    color: var(--omni-color-text-muted);
}

.analysis-workspace-tabs > .omni-tabs__strip .omni-tabs__tab[aria-selected="true"] {
    color: var(--omni-color-text);
}

.analysis-workspace-tabs > .omni-tabs__panel {
    display: none;
}

/* Recette R-172: no horizontal padding inside a tab's content, only a gap under the tab rail. */
.analysis-workspace-panel {
    min-width: 0;
    padding: 0.75rem 0 0;
    color: var(--omni-color-text);
}

.analysis-critical .omni-icon,
.analysis-critical .omni-text {
    color: var(--omni-color-danger);
}

.analysis-high .omni-icon,
.analysis-high .omni-text {
    color: var(--omni-color-warning);
}

/* Recette R-177: no 24rem floor around a 100px curve. The chart's AspectRatio (4 alone, 2 when two
   share the row) makes the drawing as wide as the card, its height following from the width; the OE
   legend has its own column right of the plot, so it never covers the curve. */
.analysis-chart-card {
    height: 100%;
    background: var(--omni-color-surface);
}

.analysis-commit {
    font-family: var(--omni-font-family-monospace, monospace);
}

.analysis-governance-form .omni-form-field {
    width: 100%;
}

.analysis-architecture-level-select {
    min-width: 12rem;
}

.analysis-table {
    min-height: 28rem;
}

/* Recette R-174 and R-178: the hero is a plain card, the OE surface and the card border token, without
   the decorative radial gradient; the card border already sets it apart. */
.analysis-grade-overview {
    margin-bottom: 1rem;
    border-color: var(--omni-card-border-color);
    background: var(--omni-color-surface);
}

.analysis-grade-primary {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-bottom: 1rem;
}

/* The letter, then one reading line: title, limiting domain, commit, date. */
.analysis-grade-headline {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 1rem;
    min-width: 0;
}

/* Recette R-173: the hero letter is a 5.5rem square. The site badge rule (.omni-badge, one badge
   height, a 0.7rem font) sits later in the sheet with the same weight and won, 88 x 22 px; three
   classes win without !important. Its colour stays the shared badge scale (GradeBadge). */
.omni-badge.grade-badge.analysis-grade-letter {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 5.5rem;
    min-width: 5.5rem;
    height: 5.5rem;
    min-height: 5.5rem;
    padding: 0;
    border-radius: var(--aetheus-radius);
    font-size: 3.5rem;
    font-weight: 800;
    line-height: 1;
}

.analysis-grade-meta,
.analysis-grade-domain-state {
    color: var(--omni-color-text-muted);
    font-size: 0.8125rem;
}

/* Recette R-176: two wide columns instead of four 235px ones. */
.analysis-grade-domains {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem;
}

.analysis-grade-domain {
    padding: 0.75rem;
    border: 0.0625rem solid var(--aetheus-tile-separator);
    border-radius: var(--aetheus-radius);
    color: var(--omni-color-text);
    background: var(--omni-color-surface);
}

.analysis-grade-domain-heading,
.analysis-grade-measure,
.analysis-grade-domain-empty {
    display: grid;
    align-items: center;
    gap: 0.5rem;
}

.analysis-grade-domain-heading {
    grid-template-columns: minmax(0, 1fr) auto;
}

/* Recette R-429: every grade badge of the overview ends on the same right edge. The domain title,
   its measure lines and the lines of the domains with nothing measured are three grids whose last
   column differed (auto, 2rem, auto), and the badge sat at the start of its cell. */
.analysis-grade-domain-heading > .grade-badge,
.analysis-grade-measure > .grade-badge,
.analysis-grade-domain-empty > .grade-badge {
    justify-self: end;
}

.analysis-grade-domain-heading strong {
    font-size: 1.5rem;
    text-align: center;
}

/* One line per measure: label (1fr), value, grade. */
.analysis-grade-measure {
    grid-template-columns: minmax(0, 1fr) auto var(--aetheus-badge-height);
    gap: 0.125rem 0.75rem;
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    border-top: 0.0625rem solid var(--aetheus-tile-separator);
    font-size: 0.8125rem;
}

.analysis-grade-measure > strong {
    text-align: center;
}

.analysis-grade-measure-label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.analysis-grade-value {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* "x to reach grade Y": a muted second line under the measure. */
.analysis-grade-measure .analysis-grade-measure-hint {
    grid-column: 1 / -1;
    color: var(--omni-color-text-muted);
    font-size: 0.75rem;
}

/* A domain with nothing measured yet: a compact muted line after the cards, not an empty card. */
.analysis-grade-domain-empty {
    grid-template-columns: minmax(0, auto) minmax(0, 1fr) auto;
    gap: 0.75rem;
    margin-top: 0.5rem;
    padding: 0.375rem 0.75rem;
    border: 0.0625rem solid var(--aetheus-tile-separator);
    border-radius: var(--aetheus-radius);
    color: var(--omni-color-text-muted);
    font-size: 0.8125rem;
}

.analysis-grade-domain-empty > .analysis-grade-domain-state {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

[data-omni-theme="dark"] .analysis-critical .omni-icon,
[data-omni-theme="dark"] .analysis-critical .omni-text {
    color: var(--omni-color-danger);
}

[data-omni-theme="dark"] .analysis-high .omni-icon,
[data-omni-theme="dark"] .analysis-high .omni-text {
    color: var(--omni-color-warning);
}

.analysis-run-gate-overview {
    border: 0.0625rem solid var(--aetheus-tile-separator);
}

.analysis-run-gate-heading,
.analysis-finding-prompt-heading {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.analysis-run-gate-heading .omni-icon {
    color: var(--omni-color-accent);
    font-size: 1.4rem;
}

.analysis-run-gate-grade {
    margin-left: auto;
}

.analysis-run-gate-metrics {
    flex: 1 1 32rem;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 0.5rem;
}

.analysis-run-gate-toolbar {
    display: flex;
    align-items: stretch;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-top: 0.75rem;
}

.analysis-run-gate-actions {
    display: flex;
    flex: 0 1 auto;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.analysis-run-findings-grid {
    min-height: 20rem;
}

.omni-data-grid.analysis-run-findings-grid tr[data-omni-row-index],
.omni-data-grid.analysis-run-findings-grid tr[data-omni-row-index] > td {
    height: auto;
    max-height: none;
}

.omni-data-grid.analysis-run-findings-grid tr[data-omni-row-index] > td {
    overflow: visible;
    padding-block: 0.5rem;
    vertical-align: top;
}

.omni-data-grid.analysis-run-findings-grid tr[data-omni-row-index] > td {
    height: auto;
    max-height: none;
    overflow: visible;
    white-space: normal;
}

.omni-data-grid.analysis-run-findings-grid tr[data-omni-row-index] > td > * {
    max-height: none;
}

/* Recette R2-027: open the finding, and revert its decision when it has one, side by side. */
.analysis-finding-row-actions {
    display: inline-flex;
    gap: 0.25rem;
}

/* Recette R2-053: a findings row is one fixed line (STD-GRID), so the title and the location are one
   line each, cut with an ellipsis, the whole text on hover. The location is cut at its start: the file
   name at the end is what tells two findings apart. */
.analysis-finding-grid-title {
    display: block;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.analysis-finding-location {
    display: block;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    direction: rtl;
    text-align: left;
    color: var(--omni-color-text);
    font-family: var(--omni-font-family-monospace, monospace);
    font-size: 0.8rem;
}

.analysis-finding-location > bdi {
    direction: ltr;
    unicode-bidi: isolate;
}

/* Recette R2-054: the findings export under the table, laid out like OE's grid export bar. */
.analysis-findings-export {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: var(--omni-space-xs, 0.25rem);
    padding-top: var(--omni-space-xs, 0.25rem);
}

.analysis-finding-location-unavailable {
    color: var(--omni-color-text-muted);
    font-size: 0.75rem;
}

.analysis-finding-prompt-heading {
    justify-content: space-between;
}

.analysis-finding-prompt-heading {
    gap: 1rem;
    margin-bottom: 1rem;
}

.analysis-finding-prompt-source,
.analysis-finding-prompt-preview {
    max-height: 34rem;
    margin: 0;
    padding: 1rem;
    overflow: auto;
    border: 0.0625rem solid var(--aetheus-tile-separator);
    border-radius: var(--aetheus-radius);
    background: var(--wizard-code-bg);
}

.analysis-finding-prompt-source {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    color: var(--wizard-code-text);
    font-family: var(--omni-font-family-monospace, monospace);
    font-size: 0.8125rem;
}

@media (max-width: 39.99rem) {
    .analysis-grade-primary {
        align-items: flex-start;
    }

    .omni-badge.grade-badge.analysis-grade-letter {
        width: 4rem;
        min-width: 4rem;
        height: 4rem;
        min-height: 4rem;
        font-size: 2.5rem;
    }

    .analysis-grade-domains {
        grid-template-columns: minmax(0, 1fr);
    }

    /* Two OmniStatTile per row, no horizontal scroll: each column keeps half the card, the label
       wraps inside its tile. */
    .analysis-run-gate-metrics {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .analysis-run-gate-actions,
    .analysis-run-gate-actions > .omni-button {
        flex: 1 1 auto;
    }

    .analysis-run-gate-actions > .omni-button {
        min-height: var(--aetheus-control-height);
    }

    .analysis-run-gate-heading {
        display: grid;
        grid-template-columns: 1.5rem minmax(0, 1fr) auto;
        align-items: center;
    }

    .analysis-run-gate-heading > .omni-icon {
        grid-column: 1;
        grid-row: 1;
    }

    .analysis-run-gate-heading > div {
        grid-column: 2;
        grid-row: 1;
        min-width: 0;
    }

    .analysis-run-gate-heading > .omni-badge {
        grid-column: 2 / -1;
        grid-row: 2;
        justify-self: start;
        margin-top: 0.5rem;
    }

    .analysis-run-gate-heading .analysis-run-gate-grade {
        grid-column: 3;
        grid-row: 1;
        margin-left: 0.5rem;
    }

    .analysis-finding-prompt-heading {
        align-items: flex-start;
        flex-direction: column;
    }
}

/* Recette R-411: the quality gate card, its YAML sample and its counters sit on OE's surfaces and take
   the page's ink. They were drawn on the inverse surface, a mid grey in OE (#555555 light, #4b4b4b
   dark) that Aetheus had taken for a dark one: page text dark on it in light mode, and the accent-subtle
   ink dark on it in dark mode. */
.quality-gate-activation-card {
    border: 0.0625rem solid color-mix(in srgb, var(--omni-color-accent) 42%, var(--omni-color-text));
    background:
        radial-gradient(circle at top left, color-mix(in srgb, var(--omni-color-accent) 18%, transparent), transparent 42%),
        var(--omni-color-surface);
    overflow: hidden;
}

.quality-gate-activation-card > .omni-card__body {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(24rem, 0.95fr);
    gap: 1.25rem;
    align-items: stretch;
}

.quality-gate-activation-copy {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    padding: 0.35rem;
}

.quality-gate-activation-icon {
    display: grid;
    place-items: center;
    width: 3rem;
    height: 3rem;
    flex: 0 0 3rem;
    border-radius: var(--aetheus-radius);
    border: 0.0625rem solid color-mix(in srgb, var(--omni-color-accent) 35%, transparent);
    color: var(--omni-color-accent-strong);
    background: color-mix(in srgb, var(--omni-color-accent) 22%, transparent);
}

.quality-gate-activation-icon .omni-icon {
    font-size: 1.65rem;
}

/* The YAML example is OE's code block (R-516); it only has to shrink inside its grid cell. */
.quality-gate-yaml-panel {
    min-width: 0;
}

.quality-gate-rules-card {
    padding: 0;
    overflow: hidden;
}

.quality-gate-stat-strip {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    border-bottom: 0.0625rem solid var(--aetheus-tile-separator);
    background: var(--omni-color-surface-muted);
}

.quality-gate-stat-strip > div {
    display: flex;
    align-items: baseline;
    gap: 0.45rem;
    padding: 0.7rem 0.9rem;
}

.quality-gate-stat-strip strong {
    color: var(--omni-color-accent-strong);
    font-size: 1.15rem;
}

.quality-gate-stat-strip span {
    color: var(--omni-color-text-muted);
    font-size: 0.78rem;
}

.quality-gate-table {
    min-height: 18rem;
    border: 0;
}

@media (max-width: 39.99rem) {
    .quality-gate-activation-card > .omni-card__body {
        grid-template-columns: minmax(0, 1fr);
    }

    .quality-gate-yaml-panel {
        width: 100%;
    }

    .quality-gate-stat-strip {
        grid-template-columns: minmax(0, 1fr);
    }

    .quality-gate-stat-strip > div + div {
        border-top: 0.0625rem solid var(--aetheus-tile-separator);
    }
}

html, body {
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
    background: var(--omni-color-surface);
    color: var(--omni-color-text);
    /* The layout fills the window, and the main landmark scrolls inside it (OmniMain Scrollable).
       Without a definite height on this chain nothing downstream has one either, and a full-height
       grid computes 0: measured 2026-09-20 on /servers, the grid box was 16px tall with its ten
       rows laid out below the clip, so the page showed a header and nothing else. */
    height: 100%;
}

#app {
    height: 100%;
}

#app > .omni-layout {
    height: 100%;
}

h1:focus {
    outline: none;
}

/* Material Dark's semantic filled badges use white text on bright green/blue/orange/red
   backgrounds, which stays below WCAG AA for normal text. A dark foreground exceeds 4.5:1
   on all four semantic fills and is inherited by badge icons as well. */
.omni-badge.omni-alert--solid:is(.omni-badge--success, .omni-badge--accent, .omni-badge--warning, .omni-badge--danger) {
    color: var(--omni-color-inverse-surface, #121212);
}

a, .btn-link {
    color: var(--omni-color-accent-strong);
}

.btn-primary {
    color: var(--omni-color-on-accent);
    background-color: var(--omni-color-accent);
    border-color: var(--omni-color-accent-strong);
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem var(--omni-color-accent);
}

.content {
    padding-top: 1.1rem;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid var(--omni-color-success);
}

.invalid {
    outline: 1px solid red;
}

.validation-message {
    color: red;
}

#blazor-error-ui {
    color-scheme: light only;
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }

.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."
    }

.app-splash {
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1.5rem;
    /* Theme-aware opaque backdrop so the chrome rendering behind is hidden until the fade-out. */
    background: var(--omni-color-surface, #121212);
    transition: opacity 0.35s ease-out;
}

/* Applied by Aetheus.hideSplash once the app is ready; fades the single boot-splash node
   out (revealing the already-painted chrome) before it removes itself from the DOM. */
.app-splash--hidden {
    opacity: 0;
    pointer-events: none;
}

.app-start-error {
    align-items: center;
    background: var(--omni-color-surface, #121212);
    color: var(--omni-color-text, #fff);
    display: flex;
    inset: 0;
    justify-content: center;
    padding: 1.5rem;
    position: fixed;
    z-index: 2000;
}
.app-start-error[hidden] { display: none; }
.app-start-error__content { max-width: 28rem; text-align: center; }
.app-start-error__content p { margin-block: 0 1rem; }
.app-start-error__content button {
    background: var(--omni-color-accent-fill);
    border: 0;
    border-radius: var(--omni-radius);
    color: var(--omni-color-on-accent-fill);
    cursor: pointer;
    font: inherit;
    padding: 0.5rem 1rem;
}
.app-start-error__content button:focus-visible { box-shadow: var(--omni-focus-ring); outline: none; }

.app-splash-logo {
    width: 6rem;
    height: 6rem;
    animation: splash-fade-in 0.4s ease-out;
}

@keyframes p-bob { 0%,100% { transform: translate(0,0); } 50% { transform: translate(4px,-4px); } }

/* Reordered vs. the old standalone svg so 0% (the paused start frame) is the neutral
   resting pose; the loop keeps the same range/character, only its phase shifts. */
@keyframes p-drift { 0%,50%,100% { transform: translate(0,0); } 25% { transform: translate(2.5px,2.5px); } 75% { transform: translate(-2.5px,-2.5px); } }

@keyframes p-roll {
    0%, 50%, 100% { transform: rotate(0deg) scaleX(1) skewY(0deg); }
    25%           { transform: rotate(3deg) scaleX(0.55) skewY(9deg); }
    75%           { transform: rotate(-3deg) scaleX(0.55) skewY(-9deg); }
}

@keyframes p-flow { to { stroke-dashoffset: -24; } }

.p-ship { animation: p-bob 3.4s ease-in-out infinite; }
.p-drift { animation: p-drift 4.4s ease-in-out infinite; }
.p-roll { animation: p-roll 5.2s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
.p-jet { stroke-dasharray: 8 16; animation: p-flow 1.1s linear infinite; }

.app-splash.is-paused .p-ship,
.app-splash.is-paused .p-drift,
.app-splash.is-paused .p-roll,
.app-splash.is-paused .p-jet { animation-play-state: paused; }

/* PLAN-008 lot 44: the two jets used to carry their delay in a style attribute, which a strict
   style-src-attr refuses. The delay is a fixed design value, so it belongs in a class. */
.p-jet-left { animation-delay: -0.35s; }
.p-jet-right { animation-delay: -0.7s; }

@media (prefers-reduced-motion: reduce) {
    .p-ship, .p-drift, .p-roll, .p-jet { animation: none; }
}

@keyframes splash-fade-in { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } }

/* Recette R-536, R2-075: the loading indicator is the package's OmniLogoLoader holding the animated plane (AetheusLoader); its
   sizes are OE's, its motion the plane's keyframes above. Centered, it takes the width of its container, the mark in the middle. */
.aetheus-loader-centered {
    display: flex;
    width: 100%;
    min-height: 5rem;
}

.dev-exception-pre {
    max-width: 60rem;
    overflow: auto;
    white-space: pre-wrap;
    word-break: break-word;
    text-align: left;
    font-size: 0.8rem;
}

code {
    color: var(--omni-color-text, #c8c8c8);
}

.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;
}

/* Metric card smooth transitions */
.metric-value {
    transition: opacity 0.3s ease;
}

.metric-bar .omni-progress__indicator {
    transition: width 0.6s ease-in-out;
}

/* Edit panel slide-in animation */
@keyframes slideDown {
    from {
        opacity: 0;
        transform: translateY(-0.5rem);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.edit-panel {
    animation: slideDown 0.25s ease-out;
    transition: box-shadow 0.2s ease;
}

.edit-panel:hover {
    box-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.15);
}

/* Clickable tag badges */
.tag-clickable {
    cursor: pointer;
    transition: opacity 0.15s ease;
}

.tag-clickable:hover {
    opacity: 0.8;
}

.tag-filter-button {
    min-height: 2.5rem;
}

/* In a grid row a tag is a label-sized chip, not a square row action: the action rules below would
   squeeze it to the 1.75rem square and cut the tag name (recette R-031). One class more than those
   rules, so it wins wherever it is declared. */
.omni-data-grid tr[data-omni-row-index] .omni-button.tag-filter-button {
    width: auto;
    max-width: none;
    min-width: 0;
    min-height: 0;
    height: var(--aetheus-grid-row-content-height);
    padding: 0 0.375rem;
}
.omni-data-grid tr[data-omni-row-index] .omni-button.tag-filter-button .omni-button__content {
    gap: 0.25rem;
}

/* Only an icon-only button is the square row action. A button that also carries a text keeps its
   width, or the 1.75rem square cut its label and it spilled over the cell beside it (recette R-079,
   service names on /servers/{id}/services). */
.omni-data-grid tr[data-omni-row-index] .omni-button:has(> .omni-button__content > span:not(.omni-icon)) {
    width: auto;
    max-width: none;
    padding-inline: 0.5rem;
}
.omni-data-grid tr[data-omni-row-index] .omni-button:has(> .omni-button__content > span:not(.omni-icon)) .omni-button__content {
    gap: 0.25rem;
}

.tag-more-details {
    position: relative;
}

.tag-more-details summary {
    cursor: pointer;
    list-style: none;
    padding: 0.4rem 0.65rem;
    border-radius: var(--omni-radius);
    background: var(--omni-color-accent-subtle);
}

.tag-more-content {
    position: absolute;
    z-index: 10;
    right: 0;
    min-width: 12rem;
    padding: 0.75rem;
    border-radius: var(--omni-radius);
    background: var(--omni-color-surface);
    box-shadow: var(--omni-shadow-lg);
}

.external-action-link {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    min-height: 2.5rem;
    padding: 0.5rem 1rem;
    border-radius: var(--omni-radius);
    background: var(--omni-color-text-muted);
    color: var(--omni-color-on-inverse);
    text-decoration: none;
}

.external-action-link:hover {
    filter: brightness(1.05);
}

/* Recette R-194: the row hover is OmniEurope's (HighlightRowOnHover, in the grid preset), which paints
   the row fill over each cell's own background. The former rule set a translucent background-color with
   !important on every cell: a frozen cell then let the scrolled text show through and the hover stacked
   on OE's own row states. Only the tint stays Aetheus's. `cursor: pointer` is added only on rows that
   actually navigate (the `.aetheus-clickable-rows` marker set by lists with a RowClick). */
/* Recette R-269: one hover everywhere, frozen column included. It applies to every grid (the raw
   OmniDataGrid ones had none, or the dashboard's own row background, which a frozen cell's opaque
   background hid) and to both themes; the fill is painted inside each cell, so a frozen cell and the
   others take the same tint. */
.omni-data-grid.omni-data-grid tbody > tr[data-omni-row-index]:hover {
    --omni-row-fill: rgba(66, 165, 245, 0.2);
}

.aetheus-clickable-rows tr[data-omni-row-index] {
    cursor: pointer;
}

/* E-row: one height for EVERY data row (2.25rem). Detail-template rows are intentionally not
   targeted: their expanded content must retain its natural height. Cell content is clipped to a
   single line (ellipsis); icons, togglers and badges keep the 1.25rem content box, and a row action
   is the badge-height square of D9 and R-345, which the cell padding leaves room for exactly. */
.omni-data-grid tr[data-omni-row-index] > td {
    box-sizing: border-box;
    height: var(--aetheus-grid-row-height);
    max-height: var(--aetheus-grid-row-height);
    padding-block: calc((var(--aetheus-grid-row-height) - var(--aetheus-grid-action-size)) / 2);
    line-height: var(--aetheus-grid-row-content-height);
    overflow: hidden;
}

/* The header row is as tall as a data row (recette R-025): at the theme's cell padding it was 29px
   over 36px rows. A table cell's height is a floor, so a wrapped header still grows. */
.omni-data-grid thead > tr > th {
    box-sizing: border-box;
    height: var(--aetheus-grid-row-height);
}

.omni-data-grid tr[data-omni-row-index] > td {
    line-height: 1rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Rich rich templates use 24px icons and controls by default. In a Compact grid they are
   constrained to the text/badge content box, without changing their natural horizontal size. */
.omni-data-grid tr[data-omni-row-index] > td > *,
.omni-data-grid tr[data-omni-row-index] .omni-data-grid__expand {
    max-height: var(--aetheus-grid-row-content-height);
}
/* Recette R-166: a cell holding buttons (a row's actions) had its buttons cut at the bottom: the
   wrapper was capped at the text height (20px) while a grid button is the action size (the badge height), inside
   a cell that clips. Such a wrapper takes the action size, centred, and the cell gives back the
   padding it needs, so the row keeps its height (R-087). */
.omni-data-grid tr[data-omni-row-index] > td > *:has(.omni-button, .omni-split-button, .omni-data-grid__icon-button) {
    max-height: var(--aetheus-grid-action-size);
    align-items: center;
}
.omni-data-grid tr[data-omni-row-index] > td:has(.omni-button, .omni-split-button, .omni-data-grid__icon-button) {
    padding-block: calc((var(--aetheus-grid-row-height) - var(--aetheus-grid-action-size) - 1px) / 2);
}
.omni-data-grid tr[data-omni-row-index] > td > .omni-button,
.omni-data-grid tr[data-omni-row-index] .omni-button,
.omni-data-grid tr[data-omni-row-index] > td > .omni-data-grid__icon-button {
    max-height: var(--omni-badge-height);
}

/* The previous grid emitted an empty responsive-title span before row togglers. Its inline baseline adds almost
   one pixel to the table row even though it has no content, so remove only that empty placeholder. */
.omni-data-grid tr[data-omni-row-index] .omni-icon,
.omni-data-grid tr[data-omni-row-index] .omni-data-grid__expand {
    min-width: var(--aetheus-grid-row-content-height);
    width: var(--aetheus-grid-row-content-height);
    min-height: var(--aetheus-grid-row-content-height);
    height: var(--aetheus-grid-row-content-height);
    font-size: var(--aetheus-grid-row-content-height);
    line-height: var(--aetheus-grid-row-content-height);
}

/* Recette R-345: the square is OE's (an icon-only row button is --omni-badge-height on each side, like
   the row's badges); this rule reads the same token, read on the button itself, so it never contradicts
   OE's size, and a labelled row button (R-079 below) keeps that height too. */
.omni-data-grid tr[data-omni-row-index] .omni-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--omni-badge-height);
    width: var(--omni-badge-height);
    max-width: var(--omni-badge-height);
    min-height: var(--omni-badge-height);
    height: var(--omni-badge-height);
    padding: 0;
    border: 1px solid color-mix(in srgb, currentColor 28%, transparent);
    border-radius: var(--aetheus-radius);
    vertical-align: middle;
    margin-inline: 0.125rem;
}

/* The row expander is a chevron, not an action button: it should not carry the same button
   outline as Cancel/Rerun. Only the border is dropped, keeping its sizing from the rule above. */
.omni-data-grid tr[data-omni-row-index] .omni-data-grid__expand {
    border: none;
    background: transparent;
}

/* Table actions are icon-only in their templates. Keep the content box visible so the SVG is not
   clipped, and remove only the normal labelled-button spacing inside compact grid cells. */
.omni-data-grid tr[data-omni-row-index] .omni-button .omni-button__content {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0;
    width: 100%;
    height: 100%;
}

/* The minimums too: the row's icon rule above gives every icon a 1.25rem floor, which kept a 20px glyph
   inside the 22px badge-height square (R-345), where OE draws it at 0.875rem like a badge's. */
.omni-data-grid tr[data-omni-row-index] .omni-button .omni-icon,
.omni-data-grid tr[data-omni-row-index] .omni-data-grid__icon-button .omni-icon {
    min-width: 0.875rem;
    min-height: 0.875rem;
    width: 0.875rem;
    height: 0.875rem;
    margin: 0;
    font-size: 0.875rem;
    line-height: 0.875rem;
}

/* E: full-height list — uniform sizing model for every list grid. The grid has NO minimum height:
   it sizes exactly to its content (its bottom sits at the last row), and only grows up to the
   remaining space, then stays there and scrolls its body internally (the pager stays pinned at the
   bottom). `min-height: 0` (not the flex default `auto`) is what lets it both collapse to content
   AND shrink to scroll. Two tiers:
   1. PRIMARY (E-flex, below): when the grid is a direct child of the body wrapper, the wrapper
      becomes a flex column that exactly fills the scroll viewport, and the grid sizes to the
      REAL remaining space — no magic number, adapts to any chrome height (filters, toolbars,
      breadcrumb) above it.
   2. NESTED (this rule): a grid that is not a direct child of the body wrapper (tabs, a server or
      project detail layout) is sized by OE, which measures what is left below it in the scrolling
      area (FillAvailableHeight, attachFill). The former `calc(100vh - 15rem)` cap guessed that
      height and left vaults and libraries short of the bottom (recette R-081); the floor stays.
   Opt-in via AetheusDataGrid FullHeight; server-side pagination remains the scale strategy. */
.aetheus-grid-fullheight {
    display: flex;
    flex-direction: column;
    min-height: var(--aetheus-grid-floor);
}

.aetheus-grid-fullheight .omni-data-grid__viewport {
    flex: 1 1 auto;
    /* Floor of 10 compact rows. Without it the scroll body is free to collapse toward nothing when
       the surrounding page is short, which is how a table ends up showing two rows above its own
       scrollbar: the reader gets a scrollbar to see data that had room to be displayed. Above the
       floor the grid still takes whatever height the window leaves it. */
    min-height: calc(10 * var(--aetheus-grid-row-height));
    overflow-y: auto;
}

/* One compact legacy UI row. Kept as a variable so the row floor above and any future height maths
   stay tied to the real row height instead of a number copied by hand. */
:root {
    --aetheus-grid-row-height: 2.25rem;

    /* Floor of a full-height grid: ten compact rows plus the sticky column header and the pager that
       sit inside the same box. E-flex gives the grid the remaining space, and "remaining" can be
       nothing when the page puts unbounded content above it (measured 2026-09-20 on /analysis: a
       project-card section 859px tall left the grid a 0px box holding 25 rows, so the table was
       invisible). The floor makes the page scroll instead of the table disappearing. */
    --aetheus-grid-floor: calc(10 * var(--aetheus-grid-row-height) + 4rem);
}

/* Opt-in infinite scroll. Virtualization is off product-wide (tables page instead), but a caller can
   still set Virtualize="true", and when it does the grid needs a bounded, scrollable body: legacy UI
   only virtualises inside one. Removing these rules while the wrapper still emits the class left that
   opt-in silently broken, which is what this restores. */
.aetheus-grid-virtualized {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.aetheus-grid-virtualized .omni-data-grid__viewport {
    flex: 1 1 auto;
    /* The floor is what makes virtualization possible at all: legacy UI fills the scroll body with rows,
       but the body only gets a height from its rows, so a body allowed to be 0 tall renders no rows and
       stays 0. Declared after the full-height rule so it also wins when both classes are present. */
    min-height: calc(10 * var(--aetheus-grid-row-height));
    max-height: calc(100vh - 18rem);
    overflow-y: auto;
}

.aetheus-grid-virtualized.aetheus-grid-fullheight .omni-data-grid__viewport {
    min-height: calc(10 * var(--aetheus-grid-row-height));
    max-height: none;
}

/* Recette R-419: a full-height grid keeps its floor when it is also virtualized. OE's fill mode sizes
   the grid box to the space it measures below it (--omni-grid-fill-height, min-block-size 0); on a
   phone, under stacked tiles, that space is close to nothing, and the min-height: 0 of the
   virtualized rule above cancelled the full-height floor, so the grid box shrank to a sliver that cut
   its own rows. The page scrolls instead, as for every other full-height grid. */
.aetheus-grid-virtualized.aetheus-grid-fullheight {
    min-height: var(--aetheus-grid-floor);
}

/* Keep every page in one stable vertical formatting context. Full-height tab panels (logs,
   YAML, editors) also use flex; applying the same baseline to their sibling pages prevents CSS
   margin-collapsing from changing when a different tab becomes active. */
.app-body-wrapper {
    display: flex;
    flex-direction: column;
}

/* The page starts right under the main area's own padding (recette R-082, titles a little higher):
   the wrapper's top padding doubled it. Two classes, so it wins over the utility it also carries. */
.app-body-wrapper.omni-u-py-md {
    padding-block-start: 0;
}

/* Recette R-392: on a phone the page keeps only the package's side margin (OmniMain's gutter); the
   wrapper's own padding doubled it. */
@media (max-width: 39.99rem) {
    .app-body-wrapper.omni-u-px-md {
        padding-inline: 0;
    }
}

/* E-flex (PRIMARY): when the page body DIRECTLY hosts a full-height grid, make the body wrapper a
   flex column that exactly fills the scroll viewport. The grid grows into the real remaining space,
   keeps the ten-row floor, and scrolls its body internally with no magic-number cap. The `> ` combinator means
   tab-nested or otherwise wrapped grids (e.g. Pipelines) don't trigger this and keep the
   max-height fallback above, so there is no regression for those. */
.app-body-wrapper:has(> .aetheus-grid-fullheight),
.app-body-wrapper:has(> .aetheus-grid-host > .aetheus-grid-fullheight) {
    display: flex;
    flex-direction: column;
    height: 100%;
}

.app-body-wrapper:has(> .aetheus-grid-fullheight) > .aetheus-grid-fullheight,
.app-body-wrapper > .aetheus-grid-host > .aetheus-grid-fullheight {
    flex: 1 1 0;
    min-height: var(--aetheus-grid-floor);
    max-height: none;
}

/* AetheusDataGrid interposes .aetheus-grid-host (the positioning context of the D11 loading plane)
   between the page and the grid, so the chain above addresses the grid through it. The host relays
   the flex context instead of absorbing it, otherwise every full-height grid silently fell back to
   the magic-number cap the moment the wrapper appeared. */
.app-body-wrapper > .aetheus-grid-host:has(> .aetheus-grid-fullheight) {
    display: flex;
    flex-direction: column;
    flex: 1 1 0;
    min-height: var(--aetheus-grid-floor);
}

/* E-bis: keep inline action buttons (e.g. "Run again") from inflating a row — compact + centered so
   every row in a grid keeps the same height regardless of whether it carries action buttons. */
.omni-data-grid tr[data-omni-row-index] .omni-button.omni-button--small,
.omni-data-grid tr[data-omni-row-index] .omni-button.omni-button--small {
    vertical-align: middle;
}

.project-onboarding-heading-icon {
    display: inline-grid;
    flex: 0 0 auto;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: var(--aetheus-radius);
    color: var(--omni-color-accent);
    background: color-mix(in srgb, var(--omni-color-accent) 15%, transparent);
}

.project-onboarding-heading-icon .omni-icon {
    width: 1.4rem;
    height: 1.4rem;
}

/* C / F-07: cap form/dialog width for readability (long single-column forms become unwieldy past
   ~45rem). Applied to edit/create form wrappers; two-column legacy UIRow forms opt out by not using it. */
.form-narrow {
    max-width: 45rem;
}

/* F-07: mark required fields with a subtle asterisk on the legacy UIFormField label. */
.omni-form-field.field-required .omni-form-field__control > label::after,
.field-required > .omni-label::after {
    content: " *";
    color: var(--omni-color-danger, #dc3545);
}

/* H: semantic threshold tint on the resource metric bars (CPU/RAM/disk). The fill turns amber as
   usage gets tight (>75%) and red near saturation (>90%); the ok state keeps the theme default. */
.metric-bar-warn .omni-progress__indicator {
    background-color: var(--omni-color-warning, #f59e0b);
}

.metric-bar-danger .omni-progress__indicator {
    background-color: var(--omni-color-danger, #dc3545);
}

/* H: recent-tasks mini-list row on the server overview. */
.task-row {
    padding: 0.25rem 0;
    border-bottom: 0.0625rem solid var(--omni-color-border, rgba(128, 128, 128, 0.15));
}

.task-row:last-child {
    border-bottom: none;
}

/* Dashboard clickable rows */
.dashboard-clickable-grid tr[data-omni-row-index] {
    cursor: pointer;
}


/* Generic table-header rule: keep compact grids usable without forcing every header's intrinsic
   width onto the whole table. The native title attribute remains available for the full label. */
.omni-data-grid .omni-data-grid__table {
    table-layout: fixed;
}

.omni-data-grid thead .omni-data-grid__header,
.omni-data-grid thead .omni-data-grid__title {
    white-space: nowrap !important;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Docker container row backgrounds */
.docker-row-running {
    background-color: rgba(40, 167, 69, 0.08) !important;
}

.docker-row-exited {
    background-color: rgba(220, 53, 69, 0.08) !important;
}

.docker-row-paused {
    background-color: rgba(255, 193, 7, 0.08) !important;
}

.docker-row-other {
    background-color: rgba(108, 117, 125, 0.05) !important;
}

/* Service log line-count selector */
.log-lines-select {
    width: 5.5rem;
}

/* Recette R-516: code, commands, configuration and logs are OE's OmniCodeBlock everywhere (a title, the
   copy button at the end of the title, then the content), on OE's own surface. Aetheus only bounds the
   height of the long ones, which scroll inside themselves; a view sets its own bound with the variable. */
.code-block-bounded .omni-code-block__pre {
    max-block-size: var(--code-block-max-height, 25rem);
    overflow-y: auto;
}
.code-block-tall { --code-block-max-height: 60vh; }
.config-yaml-block { --code-block-max-height: 30rem; }
.apache-log-output { --code-block-max-height: 32rem; }
.blame-view { --code-block-max-height: 35rem; }
.analysis-source-excerpt { --code-block-max-height: 12rem; }
/* A followed log keeps its last lines in view as they arrive. */
.apache-log-follow .omni-code-block__pre {
    scroll-behavior: smooth;
    overflow-anchor: auto;
}

/* Recette R-411 (as R-400): every console block below keeps OE's inverse surface and takes its
   on-inverse ink; the page text colour it carried was dark on that mid grey in light mode. */

/* Docker project zoom dialog */
.docker-zoom-card {
    width: min(80rem, 95vw);
    max-height: 90vh;
    overflow: auto;
}

/* Docker relations interactive graph */
.docker-graph-svg {
    width: 100%;
    height: 36rem;
    border: 1px solid var(--omni-color-border, #e0e0e0);
    border-radius: var(--aetheus-radius);
    background: var(--omni-color-surface, #fafafa);
    user-select: none;
    cursor: grab;
    display: block;
}

.docker-graph-svg:active {
    cursor: grabbing;
}

/* Docker search filter */
.docker-search {
    max-width: 14rem;
}

.docker-toolbar {
    max-width: 100%;
    min-width: 0;
    flex-wrap: wrap;
}

.docker-bulk-actions {
    box-sizing: border-box;
    flex: 1 1 100%;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    overflow-x: auto;
}

.docker-row-actions {
    max-width: 100%;
    min-width: 0;
    overflow-x: auto;
}

.docker-bulk-actions .omni-button,
.docker-row-actions .omni-button {
    flex: 0 0 auto;
    min-width: max-content;
}

/* Docker memory progress bar */
.docker-mem-bar {
    height: 0.375rem;
}

.docker-mem-bar .omni-progress__indicator {
    transition: width 0.4s ease;
}

/* Docker memory progress bar colors */
.docker-mem-green .omni-progress__indicator {
    background-color: var(--omni-color-success, #28a745) !important;
}

.docker-mem-yellow .omni-progress__indicator {
    background-color: var(--omni-color-warning, #ffc107) !important;
}

.docker-mem-red .omni-progress__indicator {
    background-color: var(--omni-color-danger, #dc3545) !important;
}

/* Docker loading skeleton */
.docker-skeleton {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: 1rem 0;
}

/* Recette R-411: a placeholder row shimmers between two page surfaces; the inverse surface and the text
   colour it used were a dark band in light mode. */
.skeleton-row {
    height: 2.5rem;
    border-radius: var(--aetheus-radius);
    background: linear-gradient(90deg,
        var(--omni-color-surface-muted) 25%,
        var(--omni-color-surface-highlight) 50%,
        var(--omni-color-surface-muted) 75%);
    background-size: 200% 100%;
    animation: shimmer 1.5s infinite;
}

@keyframes shimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* Compose editor textarea */
.docker-compose-editor {
    width: 100%;
    min-height: 20rem;
    background: var(--omni-color-inverse-surface, #121212);
    color: var(--omni-color-on-inverse, #ffffff);
    border: 1px solid var(--aetheus-tile-separator);
    border-radius: var(--aetheus-radius);
    font-family: 'Cascadia Code', 'Fira Code', monospace;
    font-size: 0.8125rem;
    line-height: 1.5;
    padding: 1rem;
    resize: vertical;
}

/* Shell terminal: the output keeps a floor so the panel does not jump while the first command runs. */
.docker-shell-output {
    --code-block-max-height: 20rem;
}
.docker-shell-output .omni-code-block__pre {
    min-block-size: 10rem;
}

.docker-shell-input {
    flex: 1;
    font-family: 'Cascadia Code', 'Fira Code', monospace;
}

/* Blinking cursor for shell input */
.docker-shell-input::placeholder {
    animation: blink-cursor 1s step-end infinite;
}

@keyframes blink-cursor {
    0%, 100% { opacity: 1; }
    50% { opacity: 0; }
}

/* Fade-in for inspect/shell/env/browse panels */
@keyframes fadeInPanel {
    from {
        opacity: 0;
        transform: translateY(0.5rem);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.docker-panel-fadein {
    animation: fadeInPanel 0.3s ease-out;
}

/* YAML syntax highlighting (minimal CSS-based) */
.yaml-editor {
    width: 100%;
    min-height: 20rem;
    background: var(--omni-color-inverse-surface, #121212);
    color: var(--omni-color-on-inverse, #ffffff);
    border: 1px solid var(--aetheus-tile-separator);
    border-radius: var(--aetheus-radius);
    font-family: 'Cascadia Code', 'Fira Code', monospace;
    font-size: 0.8125rem;
    line-height: 1.5;
    padding: 1rem;
    resize: vertical;
    tab-size: 2;
}

/* Env vars table */
.docker-env-table {
    width: 100%;
    font-family: 'Cascadia Code', 'Fira Code', monospace;
    font-size: 0.8125rem;
}

.docker-env-table th {
    text-align: left;
    padding: 0.375rem 0.75rem;
    border-bottom: 1px solid var(--aetheus-tile-separator);
    color: var(--omni-color-text-muted, #999);
}

.docker-env-table td {
    padding: 0.375rem 0.75rem;
    border-bottom: 1px solid var(--omni-color-inverse-surface, #2a2a3e);
    word-break: break-all;
}

/* File browser */
.docker-file-entry {
    cursor: pointer;
    padding: 0.25rem 0.5rem;
    border-bottom: 1px solid var(--omni-color-inverse-surface, #2a2a3e);
    font-family: 'Cascadia Code', 'Fira Code', monospace;
    font-size: 0.8125rem;
}

.docker-file-entry:hover {
    background: var(--omni-color-surface-hover);
}

/* Dockerfile editor */
.docker-build-editor {
    width: 100%;
    min-height: 15rem;
    background: var(--omni-color-inverse-surface, #121212);
    color: var(--omni-color-on-inverse, #ffffff);
    border: 1px solid var(--aetheus-tile-separator);
    border-radius: var(--aetheus-radius);
    font-family: 'Cascadia Code', 'Fira Code', monospace;
    font-size: 0.8125rem;
    line-height: 1.5;
    padding: 1rem;
    resize: vertical;
}

/* Prune summary text */
.prune-summary {
    font-size: 0.75rem;
    color: var(--omni-color-text-muted, #999);
}


/* OE 1.2.0 puts an OmniTextArea's Class on its wrapper: the editor look goes on the text area inside. */
.config-yaml-editor .omni-text-area__input {
    width: 100%;
    min-height: 15rem;
    background: var(--omni-color-inverse-surface, #121212);
    color: var(--omni-color-on-inverse, #ffffff);
    border: 1px solid var(--aetheus-tile-separator);
    border-radius: var(--aetheus-radius);
    font-family: 'Cascadia Code', 'Fira Code', monospace;
    font-size: 0.8125rem;
    line-height: 1.5;
    padding: 1rem;
    resize: vertical;
}

/* ==========================================================================
   Blade Layout — Azure-inspired sidebar navigation
   ========================================================================== */

.blade-layout {
    display: flex;
    gap: 0;
    min-height: calc(100vh - 14rem);
}

.blade-sidebar {
    width: 13rem;
    min-width: 13rem;
    border-right: 1px solid var(--aetheus-tile-separator);
    padding: 0.5rem 0;
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
}

.blade-sidebar-item {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.625rem 1rem;
    cursor: pointer;
    font-size: 0.875rem;
    color: var(--omni-color-text, #c8c8c8);
    transition: background-color 0.15s ease;
    user-select: none;
}

.blade-sidebar-item:hover {
    background-color: var(--omni-color-surface-hover);
}

.blade-sidebar-item.active {
    background-color: var(--omni-color-surface-highlight);
    color: var(--omni-color-accent, #1b6ec2);
    font-weight: 600;
}

.blade-sidebar-item .omni-icon {
    font-size: var(--icon-xl);
}

.blade-content {
    box-sizing: border-box;
    flex: 1;
    width: 100%;
    max-width: 100%;
    padding: 0.25rem 0;
    overflow-x: auto;
    min-width: 0;
}

.blade-content-hidden {
    display: none;
}

.blade-content-spacer {
    flex: 1;
}

/* Responsive: collapse sidebar on small screens */
@media (max-width: 39.99rem) {
    .blade-layout {
        flex-direction: column;
    }

    .blade-sidebar {
        width: 100%;
        min-width: 100%;
        flex-direction: row;
        overflow-x: auto;
        border-right: none;
        border-bottom: 1px solid var(--aetheus-tile-separator);
        padding: 0;
    }

    .blade-sidebar-item {
        border-bottom: 3px solid transparent;
        padding: 0.5rem 0.75rem;
        white-space: nowrap;
        font-size: 0.8125rem;
    }

    .blade-sidebar-item.active {
        border-bottom-color: var(--omni-color-accent, #1b6ec2);
    }

    .blade-content {
        padding: 0.25rem 0;
    }
}

/* ==========================================================================
   Server List — Toolbar and Grid
   ========================================================================== */

.server-list-toolbar {
    margin-bottom: 1rem;
}

.server-list-spacer {
    flex: 1;
}

.server-list-search {
    min-width: 15rem;
}

.server-list-grid {
    margin-top: 0.5rem;
}

.server-status-dot {
    display: inline-block;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    flex-shrink: 0;
}

.server-status-online {
    background-color: var(--omni-color-success, #28a745);
}

.server-status-offline {
    background-color: var(--omni-color-danger, #dc3545);
}

/* ==========================================================================
   Project Cards
   ========================================================================== */

.project-list-toolbar {
    margin-bottom: var(--aetheus-header-gap);
}

.project-list-spacer {
    flex: 1;
}

.project-list-search-wrap {
    position: relative;
    min-width: 15rem;
}

.project-list-search {
    width: 100%;
}

.project-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
    gap: 1rem;
}

/* Recette R-106: the border no longer takes the text colour (too hard, #424242 / #E0E0E0) while the
   tile hardly stood out from the page; it takes the tile border token, the tile its own fill. */
.project-card {
    cursor: pointer;
    transition: border-color 0.15s;
    border: 1px solid var(--aetheus-tile-border);
    background: var(--aetheus-tile-background);
    height: 100%;
}

.project-card:hover {
    border-color: var(--omni-color-accent);
}

.project-card-icon {
    font-size: var(--icon-2xl);
    color: var(--omni-color-accent, #1b6ec2);
}

.project-card-name {
    font-weight: 600;
    color: var(--aetheus-tile-title);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.project-card-desc {
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

.project-card-link {
    text-decoration: none;
    color: inherit;
    display: block;
    height: 100%;
}

/* Meta footer: a divider-separated row of chips that WRAPS to a second line instead of
   shrinking each item (which made captions break mid-word over their icon). */
.project-card-footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 1rem;
    margin-top: 0.25rem;
    padding-top: 0.6rem;
    border-top: 1px solid var(--aetheus-tile-separator);
}

.project-card-meta {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    flex-shrink: 0;
    white-space: nowrap;
    font-size: 0.75rem;
    line-height: 1.4;
}

.project-card-meta-icon {
    font-size: var(--icon-md);
}

/* B3KP: chips inside the project card that have their own action (repository link / git history).
   They intercept the click so the surrounding card link doesn't also fire. */
.project-card-meta-clickable {
    cursor: pointer;
    border-radius: var(--aetheus-radius);
    transition: color 0.15s ease;
}

.project-card-meta-clickable:hover {
    color: var(--omni-color-accent);
}

/* Project overview: the two info/repository cards stretch to equal height so the row stays tidy
   regardless of which card holds more fields; long repo URLs wrap instead of overflowing. */
.overview-info-card {
    height: 100%;
}

/* Recette R-450: the header row of both overview cards is one control tall, button or not, so the
   Information and Repository lines start at the same height. */
.overview-card-heading {
    min-height: var(--omni-control-height);
}

.project-overview-recent-card {
    /* Recette R-209: 100% of its column, border included (OmniCard is border-box since OE 1.4.0, R2-006). */
    width: 100%;
    min-width: 0;
}

.project-overview-recent-card .omni-card__body {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.project-overview-recent-grid {
    flex: 1 1 auto;
    min-width: 0;
}

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

@media (max-width: 39.99rem) {
    .project-card-grid {
        grid-template-columns: 1fr;
    }
}

/* Projects portfolio refresh: operational cards + high-density list. */
.project-list-controls {
    display: grid;
    grid-template-columns: minmax(15rem, 1fr) 11rem 12rem auto auto auto;
    gap: 0.5rem;
    align-items: center;
    margin-top: 0.75rem;
}

.project-list-controls .project-list-search-wrap {
    min-width: 0;
}

.project-list-status-filter,
.project-list-sort {
    width: 100%;
}

.project-favorites-filter {
    min-width: max-content;
}

.project-favorites-filter[aria-pressed="true"] {
    background-color: color-mix(in srgb, var(--omni-color-accent) 14%, transparent);
}

.project-view-switch {
    display: inline-flex;
    align-items: center;
    gap: 0.125rem;
    padding: 0.125rem;
    border: 1px solid var(--omni-color-text-muted, #2e2e2e);
    border-radius: var(--omni-radius, 4px);
}

.project-card-grid {
    grid-template-columns: repeat(auto-fill, minmax(21.5rem, 1fr));
}

.project-card {
    min-width: 0;
    transition: border-color 0.15s;
}

.project-card:focus-visible,
.project-dense-row:focus-visible {
    outline: 2px solid var(--omni-color-accent);
    outline-offset: 2px;
}

.project-card-header {
    display: flex;
    align-items: center;
    gap: 0.625rem;
}

.project-card-icon {
    flex: 0 0 auto;
}

.project-card-heading {
    min-width: 0;
    flex: 1;
}

.project-card-name {
    display: block;
    color: var(--aetheus-tile-title);
    text-decoration: none;
}

.project-activity-link:hover,
.project-dense-row a:hover {
    color: var(--omni-color-accent);
    text-decoration: underline;
}

.project-card-name:hover {
    color: var(--aetheus-tile-title);
    text-decoration: none;
}

.analysis-portfolio-projects {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
    gap: 0.75rem;
    margin-bottom: 1rem;
}

.analysis-portfolio-project-card {
    min-width: 0;
    border: 1px solid var(--aetheus-tile-separator);
}

.analysis-portfolio-project-heading,
.analysis-portfolio-project-metrics {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}

.analysis-portfolio-project-heading {
    margin-bottom: 0.75rem;
}

.analysis-portfolio-project-name {
    color: inherit;
    font-weight: 650;
    text-decoration: none;
}

.analysis-portfolio-organization {
    display: block;
    margin-top: 0.15rem !important;
}

.analysis-portfolio-project-metrics {
    flex-wrap: wrap;
    justify-content: flex-start;
    margin-bottom: 0.5rem;
    font-size: 0.8rem;
}

.analysis-portfolio-project-metrics span {
    white-space: nowrap;
    padding: 0.2rem 0.4rem;
    border: 1px solid var(--aetheus-tile-separator);
    border-radius: var(--omni-radius-sm); /* Recette R-222: square badges, never pills. */
    background: var(--omni-color-surface-muted);
}

.analysis-portfolio-grade {
    display: grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    border: 1px solid currentColor;
    border-radius: var(--omni-radius-sm); /* Recette R-280 / R-302: square, like every badge (R-222). */
    font-weight: 750;
}

.pipeline-setup-step {
    max-width: 68rem;
    margin: 0 auto;
    padding: 1rem 0;
}

.pipeline-setup-options {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: 0.75rem;
    margin-top: 1rem;
}

.pipeline-setup-option {
    display: grid;
    grid-template-columns: auto auto minmax(0, 1fr);
    align-items: start;
    gap: 0.75rem;
    padding: 0.875rem;
    border: 1px solid var(--aetheus-tile-separator);
    border-radius: var(--omni-radius, 4px);
    cursor: pointer;
}

.pipeline-setup-option:has(.omni-checkbox:checked) {
    border-color: var(--omni-color-accent);
    background: color-mix(in srgb, var(--omni-color-accent) 8%, transparent);
}

.pipeline-setup-option strong,
.pipeline-setup-option small {
    display: block;
}

.pipeline-setup-option small {
    margin-top: 0.2rem;
    color: var(--omni-color-text-muted, #aaa);
    line-height: 1.35;
}

.pipeline-setup-summary {
    display: grid;
    gap: 0.4rem;
    margin-top: 1rem;
}

.pipeline-setup-summary > div {
    display: grid;
    grid-template-columns: auto minmax(12rem, 1fr) minmax(12rem, 1fr);
    align-items: center;
    gap: 0.6rem;
    padding: 0.55rem 0.75rem;
    border-bottom: 1px solid var(--aetheus-tile-separator);
}

.pipeline-setup-summary span {
    color: var(--omni-color-text-muted, #aaa);
}

@media (max-width: 39.99rem) {
    .pipeline-setup-summary > div {
        grid-template-columns: auto minmax(0, 1fr);
    }

    .pipeline-setup-summary span {
        grid-column: 2;
    }
}

.pipeline-setup-readiness {
    display: grid;
    gap: 0.75rem;
    margin-top: 1.5rem;
}

/* Paths and environment names are the point of the row, so they wrap rather than being clipped. */
.pipeline-setup-readiness-items {
    margin: 0;
    padding-left: 1.1rem;
    display: grid;
    gap: 0.2rem;
    overflow-wrap: anywhere;
}

.project-card-status {
    margin-top: 0.25rem;
}

/* Recette R-339: the header centres the margin box, so the star is pulled in by the same amount at
   the top and the bottom: centred on the name, and the same 0.375rem taken off its height as before,
   so the header keeps its height. */
.project-card-action {
    flex: 0 0 auto;
    margin: -0.1875rem -0.375rem -0.1875rem 0;
}

/* Recette R-410 (user decision 2026-09-28): a favourite star is an icon button without background
   (Ghost), a grey outline star when the item is not a favourite, a filled blue star when it is. The
   .omni-button prefix outweighs the dark theme's ghost text colour further down. */
.omni-button:is(.project-favorite, .pipeline-favorite-button).omni-button--ghost {
    --omni-button-ink: var(--omni-color-text-muted);
    color: var(--omni-button-ink);
}

.omni-button:is(.project-favorite, .pipeline-favorite-button).omni-button--ghost.active {
    --omni-button-ink: var(--omni-color-accent-strong);
}

.project-card-desc {
    min-height: 2.6rem;
    margin: 0.75rem 0;
}

/* PLAN-003 lot 8: no frame. The block is two lines ruled exactly like the activity lines below it,
   so the tile reads as one list: label on the left, value on the right. */
.project-production {
    border-top: 1px solid var(--aetheus-tile-separator);
}
.project-production-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    min-height: 2.125rem;
    padding: 0.375rem 0;
}
.project-production-row + .project-production-row {
    border-top: 1px solid var(--aetheus-tile-separator);
}

.project-production-label,
.project-activity-label {
    color: var(--omni-color-text-muted, #a0a0a0);
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.045em;
    text-transform: uppercase;
}

.project-production-status {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.15rem 0.45rem;
    border-radius: var(--omni-radius-sm); /* Recette R-222: square badges, never pills. */
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
}

.project-production-status.online {
    color: var(--omni-color-success, #4caf50);
    background: color-mix(in srgb, var(--omni-color-success, #4caf50) 14%, transparent);
}

.project-production-status.offline {
    color: var(--omni-color-danger, #f44336);
    background: color-mix(in srgb, var(--omni-color-danger, #f44336) 14%, transparent);
}

.project-production-status.unavailable {
    color: var(--omni-color-text-muted, #a0a0a0);
    background: color-mix(in srgb, var(--omni-color-text-muted, #383838) 14%, transparent);
}

.project-production-dot {
    width: 0.4375rem;
    height: 0.4375rem;
    border-radius: 50%;
    background: currentColor;
}

.project-online-users {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    margin-left: auto;
    color: var(--omni-color-text-muted, #a0a0a0);
    font-size: 0.75rem;
    white-space: nowrap;
}

.project-online-users .omni-icon {
    font-size: var(--icon-sm);
}

.project-gate-summary {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    color: inherit;
    text-decoration: none;
}

.project-activity {
    margin-top: 0.75rem;
    border-top: 1px solid var(--aetheus-tile-separator);
    border-bottom: 1px solid var(--aetheus-tile-separator);
}

/* PLAN-005 lot 7 / D43: a two-line label starts on the baseline of its value's first line. */
.project-activity-row {
    display: grid;
    grid-template-columns: 5.25rem minmax(0, 1fr);
    align-items: first baseline;
    gap: 0.625rem;
    padding: 0.625rem 0;
}

.project-activity-row + .project-activity-row {
    border-top: 1px solid var(--aetheus-tile-separator);
}

.project-activity-content {
    min-width: 0;
}

.project-activity-link {
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    min-width: 0;
    overflow: hidden;
    color: var(--omni-color-accent-strong);
    text-decoration: none;
    font-size: 0.8125rem;
    font-weight: 600;
}

.project-activity-link .short-id {
    flex: 0 0 auto;
}

.project-activity-link span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.project-activity-run {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    min-width: 0;
}

/* PLAN-003 lot 7: a badge is never cut. At 375px the run's status badge shrank beside the run name
   ("SUCC…"); it keeps its width and the name takes the ellipsis. */
.project-activity-run > .omni-badge {
    flex: 0 0 auto;
}

.project-activity-run > a {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* D44 / D45: the commit message's first line, or the active run's step, one line with an ellipsis. */
.project-activity-line {
    display: block;
    margin-top: 0.125rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.75rem;
}

.project-activity-date,
.project-activity-empty {
    display: block;
    margin-top: 0.125rem;
    color: var(--omni-color-text-muted, #a0a0a0);
    font-size: 0.6875rem;
}

.project-card-tags {
    margin-top: 0.75rem;
}

.project-dense-list {
    border: 1px solid var(--aetheus-tile-separator);
    border-radius: var(--omni-radius, 4px);
    overflow-x: auto;
    overscroll-behavior-inline: contain;
}

.project-dense-header,
.project-dense-row {
    display: grid;
    grid-template-columns: minmax(13rem, 1.2fr) minmax(8rem, 0.65fr) 3.5rem minmax(9rem, 0.8fr) minmax(12rem, 1fr) 5.5rem;
    align-items: center;
    gap: 0.75rem;
    min-width: 58rem;
}

/* Recette R-411: the header of the grids (OmniDataGrid's header fill and muted ink) in both themes.
   The inverse surface it used is a mid grey in OE, dark in light mode, under grey text. */
.project-dense-header {
    min-height: 2.25rem;
    padding: 0 0.75rem;
    color: var(--omni-color-text-muted);
    background: color-mix(in srgb, var(--omni-color-text) 6%, var(--omni-color-surface));
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.project-dense-row {
    min-height: 3.75rem;
    padding: 0.4rem 0.75rem;
    cursor: pointer;
    background: var(--omni-color-surface, #1e1e1e);
    border-top: 1px solid var(--aetheus-tile-separator);
    transition: background 0.15s;
}

.project-dense-row:hover {
    background: color-mix(in srgb, var(--omni-color-accent) 5%, var(--omni-color-surface, #1e1e1e));
}

.project-dense-project {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    min-width: 0;
}

.project-dense-project > div {
    min-width: 0;
}

.project-dense-project a,
.project-dense-activity a {
    color: inherit;
    text-decoration: none;
    font-weight: 600;
}

.project-dense-folder {
    color: var(--omni-color-accent);
}

.project-dense-description,
.project-dense-activity > span {
    display: block;
    overflow: hidden;
    color: var(--omni-color-text-muted, #a0a0a0);
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.6875rem;
}

.project-dense-activity {
    min-width: 0;
    overflow: hidden;
    font-size: 0.75rem;
}

.project-dense-activity > div {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    min-width: 0;
}

.project-dense-activity a {
    overflow: hidden;
    color: var(--omni-color-accent-strong);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.project-dense-users {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    color: var(--omni-color-text-muted, #a0a0a0);
    font-size: 0.75rem;
}

@media (max-width: 79.99rem) {
    .project-list-controls {
        /* Beside the side menu this range leaves ~44rem: the search gives way before the
           Favorites button is squeezed under its own label. */
        grid-template-columns: minmax(10rem, 1fr) 10.5rem 11.5rem auto auto;
    }

}

@media (max-width: 63.99rem) {
    .project-list-controls {
        grid-template-columns: 1fr 1fr;
    }

    .project-list-search-wrap {
        grid-column: 1 / -1;
    }

    .project-favorites-filter {
        justify-self: stretch;
    }

}

@media (max-width: 39.99rem) {
    .project-card-grid,
    .project-list-controls {
        grid-template-columns: 1fr;
    }

    .project-list-search-wrap {
        grid-column: auto;
    }

    .project-view-switch,
    .project-favorites-filter {
        width: 100%;
        justify-content: center;
    }

    .project-view-switch > .omni-button {
        flex: 1;
    }

    .project-activity-row {
        grid-template-columns: 4.75rem minmax(0, 1fr);
    }
}

/* ==========================================================================
   Pipeline List
   ========================================================================== */

.pipeline-list-toolbar {
    margin-bottom: var(--aetheus-header-gap);
}

.pipeline-list-spacer {
    flex: 1;
}

.pipeline-list-search {
    min-width: 15rem;
}

/* Recette R-416: no top margin of its own. On Git repositories it added to the toolbar's bottom gap
   (the header gap), and on Releases to a toolbar that held only a help link: the grid stood further
   from the header than any other list. The block above owns the gap, as the Headings rule says. */
.pipeline-list-grid {
    margin-top: 0;
}

.pipeline-name-link,
.server-name-link,
.omni-data-grid tr[data-omni-row-index] a:not(.omni-button) {
    color: var(--omni-color-accent-strong, #1b6ec2);
    text-decoration: none;
    font-weight: 500;
}

.pipeline-name-link:hover,
.server-name-link:hover,
.omni-data-grid tr[data-omni-row-index] a:not(.omni-button):hover {
    text-decoration: underline;
}

/* A name cell that also carries a badge: the name gives way, the badge does not. Both were
   shrinkable, so a narrow column cut the badge through its own text and the two read as one
   overlapping string (measured 2026-09-20 on /servers: a 169px cell for 194px of content). The
   name truncates with an ellipsis instead, which is the convention the page header already uses. */
.omni-data-grid tr[data-omni-row-index] .omni-badge,
.omni-data-grid tr[data-omni-row-index] .omni-icon {
    flex: 0 0 auto;
}

.pipeline-name-link,
.server-name-link {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 2.5rem;
}

.pipeline-template-link {
    color: var(--omni-color-text-muted);
    font-weight: 600;
    text-decoration: none;
}

.pipeline-template-link:hover {
    text-decoration: underline;
}

.pipeline-template-status {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    white-space: nowrap;
}

.pipeline-hub-tabs > .omni-tabs__strip {
    margin-bottom: 0.75rem;
}

.pipeline-hub-tabs > .omni-tabs__panel {
    padding: 0;
}

/* Recette R-118 to R-122, R-215: the pipelines list is two views, Catalogue and Runs. Each view is a filter
   row, the summary strip and one grid; the grid takes the height left on the screen (OE
   FillAvailableHeight) and is the view's only scroll area, so the page itself does not scroll. */

.pipeline-catalog-panel {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    min-width: 0;
}

.pipeline-catalog-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0;
    min-width: 0;
}

.pipeline-catalog-filters .omni-checkbox-label {
    flex: 0 0 auto;
}

/* The filters sit on one row: each field takes its own width instead of OE's full-width input,
   the search box grows first (same widths as OE's row rule, this row is a plain flex div). */
.pipeline-catalog-filters > .omni-input {
    flex: 0 1 auto;
    inline-size: auto;
    min-inline-size: 11rem;
}

.pipeline-catalog-filters > .omni-text-box {
    flex: 1 1 16rem;
    /* Recette R-308: the search keeps a reading width instead of taking the row. */
    max-inline-size: 22rem;
}

/* R-122: the summary strip. A chip reads by its icon, its label and its count; its colour only
   repeats that. The active chip is also told apart without colour: a thicker border and bold text,
   on top of aria-pressed for assistive technology. */
.pipeline-status-summary {
    min-width: 0;
}

.pipeline-status-chip.omni-button {
    gap: 0.4rem;
    border: 0.0625rem solid var(--omni-color-border);
}

.pipeline-status-chip-count {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

.pipeline-status-chip-active.omni-button {
    border-width: 0.125rem;
    font-weight: 700;
}

.pipeline-status-chip-failed .omni-icon { color: var(--omni-color-danger); }
.pipeline-status-chip-running .omni-icon { color: var(--omni-color-accent); }
.pipeline-status-chip-neverrun .omni-icon { color: var(--omni-color-text-muted); }
.pipeline-status-chip-outdated .omni-icon { color: var(--omni-color-warning); }

.pipeline-run-duration {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* Recette R-120: a pipelines grid that ends its tab takes the height left on the screen (OE
   FillAvailableHeight sizes it) and its body is the one scroll area. The former rule here made the
   body overflow visible, which let the page scroll instead and stacked a scrollbar per panel. */
.pipeline-grid-fill {
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    min-height: 0;
}

.pipeline-grid-virtualized {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    min-height: 0;
}

.pipeline-project-context {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.pipeline-project-context .omni-icon {
    flex: 0 0 auto;
}

.pipeline-dependency-grid .omni-data-grid__group,
.pipeline-runs-grid .omni-data-grid__group {
    display: none;
}

.pipeline-current-section .empty-state {
    gap: 0.25rem;
    padding: 0.5rem !important;
    box-sizing: border-box;
    width: 100%;
    min-width: 0;
    overflow: hidden;
}

.pipeline-current-section .empty-state-icon {
    font-size: 1.75rem;
}

.pipeline-current-section .empty-state > * {
    max-width: 100%;
    min-width: 0;
    white-space: normal;
    overflow-wrap: anywhere;
}

.pipeline-dependency-details {
    padding: 0.75rem;
    background: var(--omni-color-surface, var(--omni-color-surface));
    border: 0.0625rem solid var(--omni-color-text-muted, #2e2e2e);
    border-radius: var(--omni-radius);
}

.pipeline-relation-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.65rem;
}

.pipeline-relation-header-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    flex: 0 0 2rem;
    color: var(--omni-color-accent);
    background: color-mix(in srgb, var(--omni-color-accent) 14%, transparent);
    border-radius: 50%;
}

.pipeline-relation-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
    gap: 0.5rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.pipeline-relation-list-execution {
    grid-template-columns: minmax(0, 1fr);
}

.pipeline-relation-item {
    min-width: 0;
}

.pipeline-relation-card {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    min-width: 0;
    padding: 0.55rem 0.65rem;
    color: var(--omni-color-text);
    background: var(--omni-color-surface);
    border: 0.0625rem solid var(--omni-color-text-muted, #2e2e2e);
    border-radius: var(--omni-radius);
    text-decoration: none;
    transition: background-color 120ms ease, border-color 120ms ease;
}

a.pipeline-relation-card:hover {
    color: var(--omni-color-accent);
    background: color-mix(in srgb, var(--omni-color-accent) 8%, var(--omni-color-surface));
    border-color: color-mix(in srgb, var(--omni-color-accent) 55%, var(--omni-color-text-muted, #2e2e2e));
}

.pipeline-relation-card-static {
    opacity: 0.8;
}

.pipeline-relation-marker {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    flex: 0 0 1.75rem;
    color: var(--omni-color-accent);
    background: color-mix(in srgb, var(--omni-color-accent) 14%, transparent);
    border-radius: 50%;
    font-size: 0.75rem;
    font-weight: 700;
}

.pipeline-relation-name {
    min-width: 0;
    overflow-wrap: anywhere;
    font-weight: 500;
}

.pipeline-relation-open {
    margin-left: auto;
    flex: 0 0 auto;
    font-size: 1rem;
    opacity: 0.65;
}

.pipeline-row-actions {
    white-space: nowrap;
}

/* legacy UI inserts the hierarchy toggler as the first column when a detail template exists. Keep it
   deliberately narrow: it only contains the expansion triangle and must not compete with names. */
.pipeline-dependency-grid colgroup col:first-child,
.pipeline-dependency-grid th:first-child,
.pipeline-dependency-grid td:first-child,
.pipeline-runs-grouped colgroup col:first-child,
.pipeline-runs-grouped th:first-child,
.pipeline-runs-grouped td:first-child {
    width: 2.25rem !important;
    min-width: 2.25rem !important;
    max-width: 2.25rem !important;
    padding-inline: 0.25rem !important;
    text-align: center;
}

/* Recette R-297: with no linked runs on the page the toggler column holds nothing, yet it took a full
   column width and read as an odd empty first column. Collapse it until a row can expand. */
.pipeline-runs-grid:not(.pipeline-runs-grouped) .omni-data-grid__col--expand,
.pipeline-runs-grid:not(.pipeline-runs-grouped) .omni-data-grid__control--expand {
    width: 0 !important;
    min-width: 0 !important;
    max-width: 0 !important;
    padding: 0 !important;
    border: 0 !important;
    overflow: hidden;
}

.pipeline-linked-runs {
    display: grid;
    gap: 0.25rem;
    padding-block: 0.375rem 0.5rem;
    background: var(--omni-color-surface-muted);
    font-size: 0.8125rem;
    overflow-x: auto;
    overscroll-behavior-inline: contain;
}

.pipeline-linked-runs-header {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    padding-inline: 0.5rem;
    color: var(--omni-color-text-muted);
}

.pipeline-runs-grid,
.pipeline-run-tree {
    font-size: 0.8125rem;
}

.pipeline-runs-grid .pipeline-run-name-column {
    text-align: left;
}

.pipeline-runs-grid th.pipeline-run-name-column .omni-data-grid__header {
    justify-content: flex-start;
}

.pipeline-run-tree {
    display: grid;
    gap: 0.125rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

.pipeline-linked-runs > .pipeline-run-tree {
    margin-inline: -0.25rem;
}

.pipeline-run-tree-nested {
    margin-left: 0;
    padding-left: 0;
}

.pipeline-run-tree-row {
    display: grid;
    grid-template-columns: 2.25rem 4.5rem minmax(10rem, 1fr) 7rem 11rem 9rem 8rem;
    align-items: center;
    gap: 0;
    min-height: 1.75rem;
    min-width: 52rem;
    padding-block: 0.125rem;
    border-radius: var(--omni-radius);
}

.pipeline-run-tree-with-project .pipeline-run-tree-row {
    grid-template-columns: 2.25rem 4.5rem minmax(10rem, 1fr) 10rem 7rem 11rem 9rem 8rem;
    min-width: 62rem;
}

/* Each compact variant hides columns the non-compact rows above carry (server, branch, commit,
   step), so it must also shrink min-width to match: leaving the 52rem/62rem non-compact minimum
   in place forced a horizontal scrollbar on the dashboard's compact + grouped-by-project tree
   even though its actual content fit in far less width. */
.pipeline-run-tree-compact .pipeline-run-tree-row {
    grid-template-columns: 2.25rem 4.5rem minmax(10rem, 1fr) 7rem 9rem;
    min-width: 33rem;
}

.pipeline-run-tree-compact.pipeline-run-tree-with-project .pipeline-run-tree-row {
    grid-template-columns: 2.25rem 4.5rem minmax(10rem, 1fr) 10rem 7rem 9rem;
    min-width: 43rem;
}

.pipeline-run-tree-compact.pipeline-run-tree-with-duration .pipeline-run-tree-row {
    grid-template-columns: 2.25rem 4.5rem minmax(10rem, 1fr) 7rem 9rem 8rem;
    min-width: 41rem;
}

.pipeline-run-tree-compact.pipeline-run-tree-with-duration.pipeline-run-tree-with-project .pipeline-run-tree-row {
    grid-template-columns: 2.25rem 4.5rem minmax(10rem, 1fr) 10rem 7rem 9rem 8rem;
    min-width: 51rem;
}

.pipeline-run-tree-row:hover {
    background: var(--omni-color-surface-hover);
}

.pipeline-run-tree-toggle.omni-button {
    width: var(--aetheus-control-height);
    min-width: var(--aetheus-control-height);
    height: var(--aetheus-control-height);
    min-height: var(--aetheus-control-height);
    padding: 0;
    justify-self: center;
}

.pipeline-run-tree-toggle .omni-icon {
    width: 1rem;
    height: 1rem;
}

.pipeline-run-tree-leaf {
    position: relative;
    width: 1.5rem;
    height: 1.5rem;
}

.pipeline-run-tree-leaf::after {
    position: absolute;
    top: 0.6875rem;
    left: 0.625rem;
    width: 0.25rem;
    height: 0.25rem;
    border-radius: 50%;
    background: var(--omni-color-text-muted);
    content: "";
    opacity: 0.55;
}

.pipeline-run-tree-name,
.pipeline-run-tree-step {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pipeline-run-tree-name {
    padding-inline: 0.5rem;
    text-align: left;
}

.pipeline-run-tree-nested > .pipeline-run-tree-node > .pipeline-run-tree-row > .pipeline-run-tree-name {
    padding-left: 1rem;
}

.pipeline-run-tree-id,
.pipeline-run-tree-project,
.pipeline-run-tree-status,
.pipeline-run-tree-step,
.pipeline-run-tree-duration,
.pipeline-run-tree-started {
    box-sizing: border-box;
    padding-inline: 0.5rem;
}

.pipeline-run-tree-status {
    min-width: 0;
    text-align: left;
}

.pipeline-run-tree-project {
    min-width: 0;
}

.pipeline-run-tree-project > span {
    overflow: hidden;
    text-overflow: ellipsis;
}

.pipeline-run-tree-step {
    color: var(--omni-color-text-muted);
}

.pipeline-run-tree-id,
.pipeline-run-tree-duration,
.pipeline-run-tree-started {
    white-space: nowrap;
}

.pipeline-run-tree-id,
.pipeline-run-tree-project,
.pipeline-run-tree-step,
.pipeline-run-tree-duration,
.pipeline-run-tree-started {
    text-align: left;
}

.pipeline-run-tree-started {
    color: var(--omni-color-text-muted);
}

@media (max-width: 39.99rem) {
    .pipeline-dependency-grid .omni-button,
    .pipeline-dependency-grid .omni-split-button__toggle,
    .pipeline-list-new-button {
        min-width: var(--aetheus-control-height);
        min-height: var(--aetheus-control-height);
    }

    .pipeline-catalog-filters {
        align-items: stretch;
        flex-wrap: wrap;
    }

    .pipeline-catalog-filters > .pipeline-list-search,
    .pipeline-catalog-filters > .omni-combo {
        flex: 1 1 14rem;
    }

    /* Recette R-308: the trigger and model filters share their line half and half. */
    .pipeline-catalog-filters > .pipeline-filter-multi {
        flex: 1 1 calc(50% - 0.25rem);
        min-width: 0;
    }

    .pipeline-dependency-grid .pipeline-actions-column {
        width: 16rem !important;
        min-width: 16rem !important;
        max-width: 16rem !important;
    }

    .pipeline-dependency-grid.omni-data-grid tr[data-omni-row-index] > td,
    .pipeline-runs-grid.omni-data-grid tr[data-omni-row-index] > td {
        height: 3.25rem;
        max-height: 3.25rem;
    }

    .pipeline-dependency-grid.omni-data-grid tr[data-omni-row-index] > td,
    .pipeline-runs-grid.omni-data-grid tr[data-omni-row-index] > td {
        max-height: 2.75rem;
    }

    .pipeline-run-tree-row,
    .pipeline-run-tree-compact .pipeline-run-tree-row {
        min-height: 2.75rem;
    }

    .pipeline-run-tree-toggle.omni-button,
    .pipeline-run-tree-leaf {
        width: var(--aetheus-control-height);
        min-width: var(--aetheus-control-height);
        height: var(--aetheus-control-height);
        min-height: var(--aetheus-control-height);
    }

}

/* Cross-link (git source / linked releases) */
.cross-link-icon {
    font-size: 1rem;
}

.cross-link-sha {
    font-family: monospace;
}

.cross-link-release {
    cursor: pointer;
}

/* Cross-link tiles (run/release/artifact/commit/branch) */
.run-tile-icon {
    font-size: 0.875rem;
    vertical-align: middle;
}
/* Recette R-339: an icon beside a label sits on the middle of the text line, not on its baseline.
   Audited 2026-09-27: no baseline-aligned container holds an icon; the other icon + label pairs are
   already centred flex rows (run timeline chips, durations) or use .run-tile-icon. */
:is(.omni-text, .run-tile-sublabel):has(> .run-tile-icon) {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}

.run-tile-link {
    text-decoration: none;
    color: inherit;
    display: block;
}

.run-tile-link .run-overview-card {
    cursor: pointer;
    transition: box-shadow 0.15s ease;
}

.run-tile-link:hover .run-overview-card {
    box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.18);
}

/* A clickable tile must LOOK clickable before it is hovered: wherever a tile carries a destination
   (an <a> wrapper or a role="button" jump-to-tab), its value text takes the link colour and
   underlines on hover, exactly like an inline link. Applies to every tile pattern on the site
   (run overview tiles, the metric tiles on the project / server overviews, and the administration tiles). The caption label
   and any badge keep their own colour - only the value reads as the link. */
.run-tile-link .run-overview-card > .omni-text,
.run-tile-link .run-overview-card .run-tile-value,
.tile-link .omni-heading:not(.omni-u-text-muted),
.tile-link .omni-heading--h5:not(.omni-u-text-muted),
.tile-link .omni-heading--h6:not(.omni-u-text-muted),
.tile-link .metric-value,
.tile-link .tile-value {
    color: var(--omni-color-accent-strong, #1b6ec2);
    font-weight: 500;
}

.run-tile-link:hover .run-overview-card > .omni-text,
.run-tile-link:hover .run-overview-card .run-tile-value,
.run-tile-link:focus-visible .run-overview-card > .omni-text,
.run-tile-link:focus-visible .run-overview-card .run-tile-value,
.tile-link:hover .omni-heading:not(.omni-u-text-muted),
.tile-link:hover .omni-heading--h5:not(.omni-u-text-muted),
.tile-link:hover .omni-heading--h6:not(.omni-u-text-muted),
.tile-link:hover .metric-value,
.tile-link:hover .tile-value,
.tile-link:focus-visible .omni-heading:not(.omni-u-text-muted),
.tile-link:focus-visible .omni-heading--h5:not(.omni-u-text-muted),
.tile-link:focus-visible .omni-heading--h6:not(.omni-u-text-muted),
.tile-link:focus-visible .metric-value,
.tile-link:focus-visible .tile-value {
    text-decoration: underline;
}

/* Grouped detail panels (artifact / release detail pages): two labelled columns of
   label -> value rows, replacing the flat undifferentiated cross-link tile wrap so the
   reader can scan "where it comes from" vs "where it goes" instead of one uniform grid. */
.detail-panel-title {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding-bottom: 0.5rem;
    margin-bottom: 0.25rem;
    border-bottom: 0.0625rem solid var(--omni-color-text-muted, #333333);
}

.detail-row {
    padding: 0.45rem 0;
}

.detail-row + .detail-row {
    border-top: 0.0625rem solid var(--omni-color-text-muted, #2e2e2e);
}

.detail-value-link {
    color: var(--omni-color-accent-strong);
    text-decoration: none;
    font-weight: 500;
}

.detail-value-link:hover {
    text-decoration: underline;
}

/* Recette R-366/R-367: release uses and build inputs wrap on a narrow screen; a package URL breaks
   anywhere rather than widening the card. */
.release-provenance-row {
    flex-wrap: wrap;
}

.release-provenance-cell {
    min-width: 0;
}

.release-provenance-id {
    overflow-wrap: anywhere;
}

.detail-filepath-box {
    background: var(--omni-color-surface-muted);
    border: 0.0625rem solid var(--omni-color-text-muted, #333333);
    border-radius: var(--aetheus-radius);
    box-sizing: border-box;
    max-width: 100%;
    min-width: 0;
    padding: 0.5rem 0.75rem;
}

.detail-filepath-box > .omni-stack,
.detail-filepath-box .text-mono {
    min-width: 0;
}

.detail-filepath-box .text-mono {
    overflow-wrap: anywhere;
    white-space: normal;
}

/* Run history dots, and the grade of the newest run beside them (PLAN-007 lot 3) */
.pipeline-run-history-cell {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.pipeline-run-history {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    min-height: 1.25rem;
    white-space: nowrap;
}

.pipeline-history-empty { margin: 0; }

.pipeline-run-dot {
    display: inline-block;
    flex: 0 0 auto;
    width: 0.75rem;
    height: 0.75rem;
    border-radius: 50%;
    transition: transform 0.1s;
}

.pipeline-run-dot:hover {
    transform: scale(1.3);
}

.pipeline-run-dot-success {
    background-color: var(--omni-color-success, #28a745);
}

.pipeline-run-dot-failed {
    background-color: var(--omni-color-danger, #dc3545);
}

.pipeline-run-dot-running {
    background-color: var(--omni-color-accent, #17a2b8);
    animation: pulse-dot 1.5s infinite;
}

.pipeline-run-dot-pending {
    background-color: var(--omni-color-text-muted, #6c757d);
}

.pipeline-run-dot-cancelled {
    /* PLAN-003 D13: amber belongs to Partial now; a cancelled run is a non-event, so it is grey. */
    background-color: var(--omni-color-text-muted, #6c757d);
}

.pipeline-run-dot-partial {
    background-color: var(--omni-color-warning, #ffc107);
}

.pipeline-run-dot-waitingforapproval {
    background-color: var(--omni-color-warning, #ffc107);
    animation: pulse-dot 1.5s infinite;
}

@keyframes pulse-dot {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
}

/* ==========================================================================
   Pipeline Detail (tabs)
   ========================================================================== */

.pipeline-detail-toolbar {
    margin-bottom: 0.5rem;
}

.pipeline-detail-tabs {
    min-height: 20rem;
}

/* The page shell is always present for an existing pipeline, whatever its active tab. This keeps
   the header, breadcrumbs and tabs in one geometry instead of changing the ancestor layout only
   when the edit form is mounted. */
.pipeline-detail-page { min-height: 0; }

/* Pipeline edit tab: compact meta row (name + description inline) + inline toolbar, and the editor
   flexes to fill the remaining viewport height (full-height chain below, scoped to .pipeline-edit-form). */
.pipeline-edit-form { display: flex; flex-direction: column; gap: 0.5rem; min-height: 0; }
.pipeline-edit-meta { display: flex; gap: 1rem; flex-wrap: wrap; align-items: flex-start; }
.pipeline-edit-meta .pipeline-edit-name { flex: 1 1 16rem; }
.pipeline-edit-meta .pipeline-edit-desc { flex: 2 1 22rem; }
.pipeline-edit-meta .omni-text-box { width: 100%; }
.pipeline-edit-toolbar { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; }

.pipeline-yaml-editor {
    width: 100%;
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-height: 0;
}
.pipeline-yaml-editor .monaco-editor-container { flex: 1 1 auto; height: auto; min-height: 12rem; }

/* Edit tab body: editor on the left, an optional read-only pipeline-definition peek on the right. */
.pipeline-edit-body {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    gap: 1rem;
}
.pipeline-edit-body > .pipeline-yaml-editor { flex: 1 1 0; min-width: 0; width: auto; }
.pipeline-edit-body > .run-yaml-pane { flex: 1 1 0; min-width: 0; }

.monaco-editor-container {
    width: 100%;
    height: calc(100vh - 20rem);
    min-height: 20rem;
    border: 0.0625rem solid var(--omni-color-border);
    border-radius: var(--omni-radius);
    overflow: hidden;
}

.monaco-editor-frame {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
}

.omni-code-editor__host > .omni-code-editor-frame {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
}

.vp-provenance-inherited {
    opacity: 0.7;
    filter: grayscale(0.65);
}

.monaco-editor .monaco-scrollable-element > .scrollbar > .slider {
    background: rgba(121, 121, 121, 0.4);
    border-radius: 0.3125rem;
}

.monaco-editor .monaco-scrollable-element > .scrollbar > .slider:hover {
    background: rgba(121, 121, 121, 0.7);
}

.monaco-status-bar {
    display: flex;
    justify-content: flex-end;
    padding: 0.25rem 0.75rem;
    font-size: 0.75rem;
    font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
    color: var(--omni-color-text-muted);
    background: var(--omni-color-surface-muted);
    border: 0.0625rem solid var(--omni-color-border);
    border-top: none;
    border-radius: 0 0 var(--omni-radius) var(--omni-radius);
}

/* ==========================================================================
   Pipeline Run Detail — Stage Flow
   ========================================================================== */

.pipeline-run-toolbar {
    margin-bottom: 1rem;
}

.stage-flow {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 1rem 0;
    overflow-x: auto;
}

.stage-box {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    padding: 0.75rem 1rem;
    border-radius: var(--aetheus-radius);
    border: 1px solid var(--aetheus-tile-separator);
    min-width: 8rem;
    background-color: var(--omni-color-surface-muted);
}

.stage-box-completed {
    border-color: var(--omni-color-success, #28a745);
}

.stage-box-running {
    border-color: var(--omni-color-accent, #17a2b8);
    box-shadow: 0 0 0.5rem rgba(23, 162, 184, 0.2);
}

.stage-box-failed {
    border-color: var(--omni-color-danger, #dc3545);
}

.stage-box-header {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    font-weight: 600;
}

.stage-box-icon {
    font-size: var(--icon-md);
}

.stage-box-name {
    font-size: 0.875rem;
}

.stage-box-info {
    display: flex;
    justify-content: space-between;
    font-size: 0.75rem;
    color: var(--omni-color-text-muted, #999);
}

.stage-flow-arrow {
    display: flex;
    align-items: center;
    flex-shrink: 0;
}

/* Expandable stage/step list */
.pipeline-run-stages {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.pipeline-run-stage {
    border: 1px solid var(--aetheus-tile-separator);
    border-radius: var(--aetheus-radius);
    overflow: hidden;
}

.pipeline-run-stage-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.625rem 0.75rem;
    cursor: pointer;
    background-color: var(--omni-color-surface-muted);
    transition: background-color 0.15s;
}

.pipeline-run-stage-header:hover {
    background-color: var(--omni-color-text, #2e2e2e);
}

.pipeline-run-stage-header.active {
    background-color: var(--omni-color-text, #2e2e2e);
}

.stage-status-icon {
    font-size: var(--icon-md);
}

.pipeline-run-steps {
    padding: 0.25rem 0;
}

.pipeline-run-step {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.375rem 0.75rem 0.375rem 2.5rem;
    border-top: 1px solid var(--omni-color-inverse-surface, #2a2a3e);
}

.pipeline-run-step-name {
    font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
    font-size: 0.8125rem;
}

.pipeline-run-matrix-header {
    cursor: pointer;
    background: var(--omni-color-surface-muted);
}

.pipeline-run-matrix-header:hover {
    background: var(--omni-color-surface-hover);
}

.matrix-toggle-icon {
    font-size: var(--icon-sm);
    flex-shrink: 0;
}

.pipeline-run-matrix-leg {
    padding-left: 4rem;
}

.step-status-icon {
    font-size: var(--icon-sm);
    flex-shrink: 0;
}

@media (max-width: 39.99rem) {
    .stage-flow {
        flex-direction: column;
        align-items: stretch;
    }

    .stage-flow-arrow {
        justify-content: center;
        transform: rotate(90deg);
    }

    .pipeline-run-step {
        padding-left: 1.5rem;
    }

    .pipeline-run-matrix-leg {
        padding-left: 2.5rem;
    }
}

/* Vertical pipeline run timeline — Azure DevOps-style stage/step visualization.
   Left rail with status dots + connectors, right side with collapsible content. */
.run-timeline { display: flex; flex-direction: column; }
.run-timeline-stage { display: flex; gap: 0; min-height: 3rem; }

.run-timeline-rail {
    display: flex; flex-direction: column; align-items: center;
    width: 2rem; flex-shrink: 0; padding-top: 0.875rem;
}

.run-timeline-dot {
    position: relative; width: 0.875rem; height: 0.875rem;
    border-radius: 50%; flex-shrink: 0; z-index: 1;
}
.run-timeline-dot-success  { background: var(--omni-color-success); }
.run-timeline-dot-failed   { background: var(--omni-color-danger); }
.run-timeline-dot-running  { background: var(--omni-color-accent); }
.run-timeline-dot-pending  { background: var(--omni-color-text-muted); }

.run-timeline-pulse {
    position: absolute; inset: -4px; border-radius: 50%;
    border: 2px solid var(--omni-color-accent); opacity: 0;
    animation: timeline-pulse 1.5s ease-out infinite;
}
@keyframes timeline-pulse {
    0%   { transform: scale(0.8); opacity: 0.8; }
    100% { transform: scale(1.6); opacity: 0; }
}

.run-timeline-connector {
    flex: 1; width: 2px; min-height: 0.5rem;
}
.run-timeline-connector-success  { background: var(--omni-color-success); }
.run-timeline-connector-failed   { background: var(--omni-color-danger); }
.run-timeline-connector-running  { background: var(--omni-color-accent); }
.run-timeline-connector-pending  { background: var(--omni-color-text-muted); }
/* S-DES-19: gradient connector at a success→failure boundary */
.run-timeline-connector-success-failed { background: linear-gradient(to bottom, var(--omni-color-success), var(--omni-color-danger)); }

.run-timeline-body { flex: 1; min-width: 0; padding-bottom: 0.25rem; }

.run-timeline-stage-header {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.5rem 0.75rem; cursor: pointer;
    border-radius: var(--aetheus-radius); transition: background-color 0.15s;
}
.run-timeline-stage-header:hover { background: var(--omni-color-surface-hover); }
.run-timeline-stage-header.active { background: var(--omni-color-surface-highlight); }
.run-timeline-toggle { font-size: var(--icon-md); color: var(--omni-color-text-muted); }

.run-timeline-steps {
    margin-left: 0.75rem; padding: 0.25rem 0;
    border-left: 1px solid var(--aetheus-tile-separator);
}

.run-timeline-step {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.375rem 0.75rem;
}
.run-timeline-step-name {
    font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
    font-size: 0.8125rem;
}
.run-timeline-step-icon {
    font-family: 'Material Symbols Outlined', 'Material Icons';
    font-size: 1.125rem; font-weight: normal; font-style: normal;
    line-height: 1; flex-shrink: 0;
}
.run-timeline-step-success { color: var(--omni-color-success); }
.run-timeline-step-running { color: var(--omni-color-accent); }
.run-timeline-step-failed  { color: var(--omni-color-danger); }
.run-timeline-step-pending { color: var(--omni-color-text-muted); }

.run-timeline-matrix-header { cursor: pointer; }
.run-timeline-matrix-header:hover { background: var(--omni-color-surface-hover); }
.run-timeline-matrix-leg { padding-left: 2rem; }

.run-timeline-step.active { background: var(--omni-color-text, #252525); border-radius: var(--aetheus-radius); }
.run-timeline-step:hover { background: var(--omni-color-surface-hover); border-radius: var(--aetheus-radius); }

.run-timeline-duration { white-space: nowrap; }

/* Pipeline step log panel — terminal style */
.step-log-panel {
    background: var(--omni-color-inverse-surface, #121212);
    color: var(--omni-color-on-inverse, #ffffff);
    font-family: 'Cascadia Mono', 'Fira Code', 'Consolas', monospace;
    font-size: 0.8125rem;
    line-height: 1.5;
    padding: 0.75rem 1rem;
    margin: 0.25rem 0 0.5rem 1.75rem;
    border-radius: var(--aetheus-radius);
    border: 1px solid var(--omni-color-text, #252525);
    max-height: 25rem;
    overflow-y: auto;
    white-space: pre-wrap;
    word-break: break-all;
}
.step-log-line {
    padding: 0.0625rem 0;
}
.step-log-line:hover {
    background: color-mix(in srgb, var(--omni-color-on-inverse) 10%, var(--omni-color-inverse-surface));
    color: var(--omni-color-on-inverse);
}
.step-log-error {
    /* S-DES-12: brighter red + faint row tint for stronger contrast on the dark terminal. */
    color: #ff8a80;
    background-color: rgba(239, 83, 80, 0.1);
}
.step-log-warning {
    color: var(--omni-color-warning, #ffa726);
}
.step-log-empty {
    color: var(--omni-color-text-muted, #a0a0a0);
    font-style: italic;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 1rem 0;
}

@media (max-width: 39.99rem) {
    .run-timeline-rail { width: 1.5rem; }
    .run-timeline-steps { margin-left: 0.25rem; }
    .run-split-layout { grid-template-columns: 1fr !important; }
    /* Single column on mobile: no full-height fill (the :has() chain is desktop-only), so the two
       stacked panels size to content. Cap the log panel at 70vh so its terminal still scrolls
       internally instead of running the whole page down. */
    .run-split-logs { min-height: 20rem; max-height: 70vh; }
}

/* --- PLAN-006 lot 12.1: the run on a time axis ---
   The list below this strip is ordered but not scaled: it says a stage ran "in parallel" because the
   graph allowed it, and says nothing about where the run's minutes actually went. These bars share
   one axis, so overlap and cost are both visible at a glance.
   Geometry is rendered as SVG attributes, while colours and typography stay here. */
.run-gantt {
    /* Recette R-162: detached from the Progress block above it. */
    margin-top: 1.25rem;
    margin-bottom: 1rem;
    font-size: 0.8125rem;
}
/* The hatch pattern's carrier: present in the layout tree (a display:none SVG drops its patterns in
   some engines) but taking no room. */
.run-gantt-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
/* Recette R-158: the axis in quarters. Four equal cells; each tick label sits on its cell's left
   edge (centred on it for the inner ones), the end label on the last cell's right edge. */
.run-gantt-scale {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    color: var(--omni-color-text-muted, #c0c6cd);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.run-gantt-tick[data-tick="25"],
.run-gantt-tick[data-tick="50"],
.run-gantt-tick[data-tick="75"] { transform: translateX(-50%); }
.run-gantt-tick[data-tick="75"] { grid-column: 4; grid-row: 1; justify-self: start; }
.run-gantt-tick[data-tick="100"] { grid-column: 4; grid-row: 1; justify-self: end; }
.run-gantt-head { margin-bottom: 0.25rem; color: var(--omni-color-text-muted, #c0c6cd); }
/* PLAN-003 lot 20 / D26: the figures beside each bar. Every column has the same fixed width in every
   row, so they line up without a table; a column no stage fills is not rendered at all. */
.run-gantt-cols {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 4rem;
    gap: 0.5rem;
    font-variant-numeric: tabular-nums;
    text-align: right;
    color: var(--omni-color-text-muted, #c0c6cd);
}
.run-gantt-cols > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.run-gantt-row {
    display: grid;
    /* Labels and figures keep fixed gutters so every track starts and ends on the same x: bars in
       different rows are only comparable if their axes line up exactly. */
    grid-template-columns: minmax(0, 10rem) 1fr auto;
    align-items: center;
    column-gap: 0.5rem;
    /* Recette R-162: a little air between rows. It comes from the row height, not from padding, so
       the track cells touch and the quarter lines run unbroken from the first row to the last. */
    min-height: 1.375rem;
}
.run-gantt-label {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    min-width: 0;
    color: var(--omni-color-text, #e8eaed);
}
.run-gantt-label-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.run-gantt-system { color: var(--omni-color-text-muted, #c0c6cd); font-style: italic; }
/* Recette R-155: the status is also a shape (tick, cross, no-entry sign, play, hourglass). */
.run-gantt-status-icon { flex-shrink: 0; width: 0.875rem; height: 0.875rem; }
.run-gantt-status-success { color: var(--omni-color-success); }
.run-gantt-status-failed { color: var(--omni-color-danger); }
.run-gantt-status-running { color: var(--omni-color-accent); }
.run-gantt-status-cancelled,
.run-gantt-status-other { color: var(--omni-color-text-muted); }
.run-gantt-track {
    align-self: stretch;
    display: flex;
    align-items: center;
    /* R-158: the quarter lines between the bars, drawn on the cell so they continue across rows. */
    background-image:
        linear-gradient(var(--aetheus-tile-separator), var(--aetheus-tile-separator)),
        linear-gradient(var(--aetheus-tile-separator), var(--aetheus-tile-separator)),
        linear-gradient(var(--aetheus-tile-separator), var(--aetheus-tile-separator));
    background-size: 0.0625rem 100%;
    background-position: 25% 0, 50% 0, 75% 0;
    background-repeat: no-repeat;
}
.run-gantt-svg { display: block; width: 100%; height: 0.75rem; }
.run-gantt-track-fill { fill: var(--omni-color-surface-highlight, #21262c); }
/* ...and across the track itself, under the bars. */
.run-gantt-gridline {
    stroke: var(--omni-color-text-muted);
    stroke-opacity: 0.35;
    stroke-width: 1;
    vector-effect: non-scaling-stroke;
}
/* Recette R-155: the wait before a stage, hatched and dimmed, thinner than the bar it leads into, so
   it differs from the run time by its pattern and not by a hue alone. */
.run-gantt-wait { opacity: 0.75; }
.run-gantt-hatch path {
    stroke: var(--omni-color-text-muted);
    stroke-width: 0.3;
    fill: none;
}
.run-gantt-bar {
    vector-effect: non-scaling-stroke;
}
.run-gantt-success { fill: var(--omni-color-success, #4caf50); }
.run-gantt-failed { fill: var(--omni-color-danger, #ef5350); }
.run-gantt-cancelled { fill: var(--omni-color-text-muted, #3a424b); }
.run-gantt-other { fill: var(--omni-color-border, #2b3138); }
.run-gantt-running { fill: var(--omni-color-accent, #29b6f6); }
/* PLAN-003 lot 20 / D26: a running stage fills up to its usual duration; past it, a stronger layer
   restarts from the left over the first, "longer than usual, still going". No fill on a finished
   stage. */
.run-gantt-progress,
.run-gantt-overrun {
    pointer-events: none;
}
.run-gantt-progress { fill: var(--omni-color-on-accent); opacity: 0.28; }
.run-gantt-overrun { fill: var(--omni-color-warning, #ffa726); opacity: 0.8; }
/* Recette R-160: a stage row unfolds into its steps. The toggle keeps the page control height (the
   one token, never a second size); a stage without steps gets an empty box of the same width so every
   status icon and name still start on the same x. */
.run-gantt-toggle.omni-button,
.run-gantt-toggle-spacer {
    flex-shrink: 0;
    width: var(--aetheus-control-height);
    min-width: var(--aetheus-control-height);
    height: var(--aetheus-control-height);
    min-height: var(--aetheus-control-height);
    padding: 0;
}
.run-gantt-toggle .omni-icon { width: 1rem; height: 1rem; }
/* Steps sit under their stage, indented past the toggle, on the same three grid columns: same label
   gutter, same track, same figure columns (only Duration is filled). */
.run-gantt-step-label {
    padding-left: calc(var(--aetheus-control-height) + 0.25rem);
    color: var(--omni-color-text-muted, #c0c6cd);
}
/* Thinner than a stage bar, so a step never reads as a stage. Its status is carried by the icon
   beside its name, exactly as on the stage rows, not by the hue alone. */
.run-gantt-step-svg { height: 0.4375rem; }
.run-gantt-step-nostart {
    color: var(--omni-color-text-muted, #c0c6cd);
    font-style: italic;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.run-gantt-untimed {
    color: var(--omni-color-text-muted, #c0c6cd);
    font-style: italic;
    margin-top: 0.375rem;
}
/* Recette R-157: cumulative execution, cumulative wait and run duration under the strip. */
.run-gantt-total {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.25rem 1.25rem;
    margin-top: 0.375rem;
    padding-top: 0.375rem;
    border-top: 0.0625rem solid var(--aetheus-tile-separator);
    font-variant-numeric: tabular-nums;
}
.run-gantt-total-item { display: inline-flex; gap: 0.375rem; white-space: nowrap; }
.run-gantt-total-label { color: var(--omni-color-text-muted, #c0c6cd); }
.run-gantt-total-value { font-weight: 600; }
/* Measured on the strip's own width, not the viewport: beside a sidebar a desktop window is as
   narrow as a phone. */
.run-gantt { container-type: inline-size; }
@container (max-width: 48rem) {
    /* The label gutter costs more than it gives on a narrow screen; the tracks matter more. */
    .run-gantt-row { grid-template-columns: minmax(0, 6rem) 1fr auto; }
    .run-gantt-cols { grid-auto-columns: 3.25rem; }
    /* Three figures or more beside the track leave it no width at all (0 px measured at 343 px
       with five): they move under it, still aligned from row to row. */
    .run-gantt-row:has(> .run-gantt-cols > :nth-child(3)) { grid-template-columns: minmax(0, 6rem) 1fr; }
    .run-gantt-row > .run-gantt-cols:has(> :nth-child(3)) { grid-column: 1 / -1; justify-content: end; }
}
/* --- Split view: timeline left + logs right --- */
.run-split-layout {
    display: grid;
    grid-template-columns: 22rem 1fr;
    gap: 1rem;
    min-height: 28rem;
}

.run-split-timeline {
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-gutter: stable;
    padding-right: 0.5rem;
}

@media (max-width: 39.99rem) {
    /* Recette R-425 (measured 2026-09-28): stacked, the timeline sizes to its content and never
       scrolls, but it stayed a scroll container with overscroll containment, so the wheel over the
       steps went nowhere instead of scrolling the page. After the rule above, so it wins. */
    .run-split-timeline { overflow-y: visible; overscroll-behavior: auto; }
}

/* Orchestration runs keep the side-by-side split but widen the left column so the run tree's
   stage label + child-pipeline chip + description fit on one line (logs stay on the right). */
.run-split-wide { grid-template-columns: 34rem 1fr; }

/* Stage wrapper — rail spans full height of header + sub-steps */
.run-tl-stage {
    display: flex;
}
.run-tl-content {
    flex: 1;
    min-width: 0;
}
.run-tl-grouped { padding-left: 1rem; }

/* Group header in logs timeline */
.run-tl-group-header {
    display: flex; align-items: center; gap: 0.375rem;
    padding: 0.375rem 0.25rem 0.125rem;
    font-weight: 600; font-size: 0.75rem;
    color: var(--omni-color-accent, #7c4dff);
    border-bottom: 1px solid var(--aetheus-tile-separator);
    margin-top: 0.375rem;
}
.run-tl-group-header:first-child { margin-top: 0; }

/* Unified timeline row — one line per step/stage */
.run-tl-row {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.25rem 0.25rem;
    border-radius: var(--aetheus-radius);
    cursor: pointer;
    font-size: 0.8125rem;
    transition: background 0.15s;
    position: relative;
}
/* Recette R-139: these rules were written for the legacy tokens, whose fallbacks (#252525, #333) were
   the design; the OE tokens of the same names are the TEXT colours, which turned the hover and the
   selection into light slabs. Back to a faint hover and a slightly stronger selection. */
.run-tl-row:hover { background: var(--omni-color-surface-hover); }
.run-tl-row.selected { background: var(--omni-color-surface-highlight); }
.run-tl-stage-header { cursor: default; }
.run-tl-sub { padding-left: 0.5rem; }
.run-tl-rail {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 0.75rem;
    flex-shrink: 0;
}
.run-tl-dot {
    width: 0.625rem;
    height: 0.625rem;
    border-radius: 50%;
    flex-shrink: 0;
    margin-top: 0.375rem;
    position: relative;
}
.run-tl-line {
    display: none;
}
.run-status-badge {
    min-width: 4.5rem;
    text-align: center;
    flex-shrink: 0;
    font-size: 0.6875rem;
    /* Recette R-139 / R-141: the small capitals the Progress list and the log header had before. */
    text-transform: uppercase;
    letter-spacing: 0.02em;
}
.run-tl-name {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1;
    min-width: 0;
}
.run-tl-bold { font-weight: 600; }
.run-tl-dur {
    color: var(--omni-color-text-muted, #a0a0a0);
    font-size: 0.6875rem;
    flex-shrink: 0;
    white-space: nowrap;
}

/* Progress timeline: durations share a fixed gutter while task labels consume only the available
   width and truncate on one line. */
.run-progress-timeline .run-tl-dur { min-width: 2.75rem; text-align: right; }
.run-progress-timeline .run-tl-name { flex: 1 1 auto; }

/* Recette R-141: the log panel is the dark terminal it was (the legacy fallbacks #121212 / #252525
   were the design). The OE tokens behind these names are a mid grey and the text colour, which is
   what turned the panel into a light grey slab. The page shade is the terminal, the tile surface
   the header. */
.run-split-logs {
    display: flex;
    flex-direction: column;
    background: var(--aetheus-page-background);
    border-radius: var(--aetheus-radius);
    border: 0.0625rem solid var(--aetheus-tile-border);
    overflow: hidden;
    min-height: 20rem;
    min-width: 0;
    container-type: inline-size;
}

/* --- Full-height fill (desktop) ---------------------------------------------------------------
   The logs panel should fill the viewport so a short log doesn't leave an empty band below it and
   a long log scrolls inside the terminal (not the page). Rather than guess a `calc(100vh - Nrem)`
   reserve (fragile magic number), turn the shared layout shell into a real flex column *only on
   this page*, scoped with :has(.run-split-layout) so every other page is untouched. The chain is
   .omni-body -> .app-body-wrapper -> .omni-tabs -> .omni-tabs__panel -> split;
   each link flexes to fill and passes min-height:0 down so the terminal is the single scroll box.
   Mobile (<=768px, single column) keeps normal document flow — see the max-width media query. */
@media (min-width: 40rem) {
    /* No rule on .omni-body any more (recette R-051): since the OE migration it holds the sidebar
       beside the main area, and turning it into a column stacked the menu above the page. The OE
       scrollable main is already a flex column of definite height, so the chain starts there. */
    .app-body-wrapper:has(.run-split-layout, .run-yaml-split, .analysis-quality-findings) {
        flex: 1 1 auto;
        min-height: 0;
        display: flex;
        flex-direction: column;
    }
    .app-body-wrapper:has(.run-split-layout, .run-yaml-split) > .omni-tabs {
        flex: 1 1 auto;
        min-height: 0;
    }
    .omni-tabs__panel:has(.run-split-layout, .run-yaml-split) {
        height: 100%;
        min-height: 0;
    }
    .run-split-layout:has(.run-split-logs) { height: 100%; min-height: 0; }
    .run-split-timeline { height: 100%; }
    .run-split-logs { height: 100%; }
    /* YAML definition tab fills the same shell; panes take full height, code scrolls internally. */
    .run-yaml-split { height: 100%; min-height: 0; }

    /* Recette R-500: no rule for the Gate tab here any more. Its card was a column of the panel's
       height, but OE's card puts its content in .omni-card__body, so the grid never was a child of
       that column: it kept its own cap (100vh - 18rem), which knows nothing of the heading and the
       tiles above it, and ended 101px under the card. The grid is now a full-height grid sized by OE
       (FillAvailableHeight), which measures what is left under it and leaves room for the card's own
       padding and border; the card takes the height of its content. */

    .app-body-wrapper:has(.analysis-quality-findings) > .blade-content {
        display: flex;
        flex: 1 1 auto;
        flex-direction: column;
        min-height: 0;
        overflow: hidden;
    }

    .analysis-quality-findings {
        display: flex;
        flex: 1 1 auto;
        flex-direction: column;
        min-height: 0;
    }

    .analysis-quality-findings > .analysis-workspace-panel {
        display: flex;
        flex: 1 1 auto;
        flex-direction: column;
        min-height: 0;
        overflow: hidden;
    }

    .analysis-quality-findings .aetheus-grid-fullheight {
        flex: 1 1 auto;
        min-height: 0;
        max-height: none;
    }

    /* Pipeline edit tab: the Monaco editor fills the viewport below the compact meta + toolbar,
       with no page scrollbar (same flex-fill shell trick, scoped to the edit form). */
    .app-body-wrapper:has(.pipeline-detail-page) {
        flex: 1 1 auto;
        min-height: 0;
        display: flex;
        flex-direction: column;
    }
    .pipeline-detail-page { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
    .pipeline-detail-page > .omni-tabs { flex: 1 1 auto; min-height: 0; }
    .omni-tabs__viewport:has(.pipeline-edit-form) { flex: 1 1 auto; min-height: 0; overflow: hidden; }
    .omni-tabs__panel:has(.pipeline-edit-form) { height: 100%; }
    .pipeline-edit-form { height: 100%; }
}

.run-logs-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    min-height: 20rem;
    opacity: 0.5;
}

.run-logs-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.625rem 1rem;
    border-bottom: 0.0625rem solid var(--aetheus-tile-separator);
    background: var(--aetheus-tile-background);
    flex-shrink: 0;
}
/* Recette R-141: status and system badges of the log header in small capitals, as before. */
.run-step-logs-heading .omni-badge { text-transform: uppercase; letter-spacing: 0.02em; }
/* The Command / Result toggles keep a frame when off, so they read as buttons and not as labels. */
.run-logs-toggle.omni-button--ghost { border-color: var(--aetheus-tile-border); }

.run-logs-search {
    flex: 1 1 12rem;
    min-width: 8rem;
    max-width: none;
}

.run-step-logs-header {
    flex-direction: column;
    align-items: stretch;
    gap: 0.5rem;
}

.run-step-logs-heading,
.run-step-logs-toolbar,
.run-step-logs-actions {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    min-width: 0;
}

.run-step-logs-title {
    flex: 1 1 auto;
    min-width: 4rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.run-step-logs-duration,
.run-step-logs-actions {
    flex-shrink: 0;
}

.run-step-logs-toolbar {
    flex-wrap: wrap;
}

.run-step-logs-actions {
    margin-left: auto;
}

@container (max-width: 32rem) {
    /* Recette R-323: only the label hides on a narrow log pane; the icon stays, so no button turns
       into an empty rectangle. */
    .run-step-logs-toolbar > .omni-button .omni-button__content > span:not(.omni-icon),
    .run-step-logs-actions .omni-button__content > span:not(.omni-icon) {
        display: none;
    }

    /* Recette R-425: a button reduced to its icon is the square of an icon-only button (OE: the
       control height, no side padding). Command, Output and Copy kept their label's padding and stood
       wider than Top, Bottom and Follow beside them. */
    .run-step-logs-toolbar > .omni-button,
    .run-step-logs-actions > .omni-button {
        inline-size: var(--omni-button-size, var(--omni-control-height));
        min-inline-size: var(--omni-button-size, var(--omni-control-height));
        padding-inline: 0;
        justify-content: center;
    }

    .run-step-logs-toolbar .run-logs-search {
        order: 2;
        flex-basis: 100%;
        width: 100%;
    }
}

/* S-FEAT-27: API reference page */
.api-ref-search { max-width: 18rem; }
.api-ref-row { display: flex; align-items: center; flex-wrap: wrap; gap: 0.75rem; min-width: 0; padding: 0.4375rem 0.875rem; }
.api-ref-row + .api-ref-row { border-top: 0.0625rem solid var(--omni-color-text, #252525); }
.api-ref-method {
    flex: 0 0 4rem; text-align: center; font-size: 0.6875rem; font-weight: 700;
    padding: 0.125rem 0.375rem; border-radius: var(--aetheus-radius); text-transform: uppercase;
}
.api-ref-method-get { background: var(--omni-color-accent); color: var(--omni-color-on-accent); }
.api-ref-method-post { background: var(--omni-color-success); color: var(--omni-color-on-success); }
.api-ref-method-put { background: var(--omni-color-warning); color: var(--omni-color-on-warning); }
.api-ref-method-patch { background: var(--omni-color-warning); color: var(--omni-color-on-warning); }
.api-ref-method-delete { background: var(--omni-color-danger); color: var(--omni-color-on-danger); }
.api-ref-path { flex: 1 1 12rem; font-family: 'Cascadia Code', 'Consolas', monospace; font-size: 0.8125rem; min-width: 0; overflow-wrap: anywhere; }
.api-ref-summary { flex: 1 1 100%; font-size: 0.8125rem; margin-left: 4.75rem; text-align: right; }

.run-failed-step { padding: 0.375rem 0; }
.run-failed-step + .run-failed-step { border-top: 0.0625rem solid var(--omni-color-text, #252525); }

/* S-UX-18: executed command banner above the log terminal */
.run-cmd {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.5rem 1rem;
    background: var(--omni-color-surface-muted);
    border-bottom: 0.0625rem solid var(--aetheus-tile-separator);
    flex-shrink: 0;
}
.run-cmd-icon { color: var(--omni-color-success, #4caf50); font-size: 1rem; flex-shrink: 0; }
.run-cmd-text {
    font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
    font-size: 0.8125rem;
    color: var(--omni-color-text, #ddd);
    white-space: pre-wrap;
    word-break: break-all;
}

/* S-UX-32 / YAML definition tab: definition left (full-height), clickable `pipeline:` refs open a
   peek pane on the right (one at a time). Panes fill the viewport; only the code scrolls. */
.run-yaml-split {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
    min-height: 28rem;
}
.run-yaml-split:has(.run-yaml-peek) { grid-template-columns: 1fr 1fr; }
/* Recette R-426: the definition pane is the log panel's twin (R-141): the page shade under the code,
   the tile surface under its header, the tile border and separator. It sat on OE's inverse surface,
   a mid grey (#555555 light, #4b4b4b dark) that Aetheus had taken for a dark one, framed in the text
   colour. */
.run-yaml-pane {
    display: flex;
    flex-direction: column;
    min-width: 0;
    background: var(--aetheus-page-background);
    border: 0.0625rem solid var(--aetheus-tile-border);
    border-radius: var(--aetheus-radius);
    overflow: hidden;
}
.run-yaml-pane .run-logs-header {
    padding: 0.5rem 0.75rem;
    border-bottom: 0.0625rem solid var(--aetheus-tile-separator);
    flex-shrink: 0;
}
.run-yaml-subtitle { font-size: 0.8125rem; }
.run-yaml-code {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    padding: 0.75rem 1rem;
    font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
    font-size: 0.8125rem;
    line-height: 1.5;
    color: var(--omni-color-text, #e6edf3);
}
/* Real trailing spacer so the last line clears the horizontal scrollbar. A plain padding-bottom is
   ignored at scroll-end for a flex-item scroll container (Chrome), so use an ::after block instead. */
.run-yaml-code::after { content: ""; display: block; height: 1.5rem; }
.run-yaml-code .yaml-line { display: block; white-space: pre; min-height: 1.2em; }
/* Wrap toggle: soft-wrap long lines instead of scrolling horizontally. Independent per pane. */
.run-yaml-code.run-yaml-wrap .yaml-line { white-space: pre-wrap; overflow-wrap: anywhere; }
.run-yaml-empty { color: var(--omni-color-text-muted, #a0a0a0); }
.yaml-pipeline-link {
    color: var(--omni-color-accent, #7c4dff);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 0.15em;
    border-radius: var(--aetheus-radius);
}
.yaml-pipeline-link:hover { color: var(--omni-color-accent, #c9b8ff); background: var(--omni-color-accent-subtle, #241a4d); }

.run-logs-terminal {
    flex: 1;
    min-height: 0; /* let the flex child shrink below its content so it scrolls itself instead of overflowing the page */
    overflow-y: auto;
    padding: 0.75rem 1rem;
    font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
    font-size: 0.8125rem;
    line-height: 1.5;
}

.run-logs-lines {
    display: flex;
    flex-direction: column;
    min-width: max-content;
}
.app-error-top-frame {
    font-size: 0.8rem;
}
.run-log-line {
    display: flex;
    gap: 0.75rem;
    padding: 0.0625rem 0;
    height: 1.3125rem;
    min-height: 1.3125rem;
    line-height: 1.1875rem;
    white-space: pre;
    word-break: normal;
}
.run-log-line:hover { background: var(--omni-color-surface-muted); }
/* S-DES-11: accent known variable names inside log lines */
.log-var { color: var(--omni-color-accent, #29b6f6); font-weight: 600; }
.run-log-num {
    color: var(--omni-color-text-muted, #555);
    min-width: 2rem;
    text-align: right;
    flex-shrink: 0;
    user-select: none;
    font-size: 0.75rem;
}

.run-tl-prev-dur {
    color: var(--omni-color-text-muted, #777);
    font-size: 0.6875rem;
    white-space: nowrap;
}

/* What a collapsed trigger row's child is executing right now. */
.run-tl-current-stage {
    display: inline-flex;
    align-items: center;
    gap: 0.1875rem;
    color: var(--omni-color-text-muted, #555);
    font-size: 0.6875rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 22rem;
}

.run-tl-current-stage .omni-icon {
    font-size: 0.75rem;
}

/* The artifact's content digest, shortened; the full value is the title. */
.run-artifact-digest {
    font-family: var(--omni-font-family-monospace, ui-monospace, monospace);
    font-size: 0.75rem;
}

/* What the launch preflight verified, kept on the run. */
.run-preflight-check {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.125rem 0;
    font-size: 0.8125rem;
    flex-wrap: wrap;
}

.run-preflight-check .omni-icon { font-size: 1rem; }
.run-preflight-ok { color: var(--omni-color-success, #2eb85c); }
.run-preflight-ko { color: var(--omni-color-danger, #e55353); }
.run-preflight-subject { color: var(--omni-color-text, #333); }
.run-preflight-detail { color: var(--omni-color-text-muted, #777); font-size: 0.75rem; }

/* System task styling — grey/muted to distinguish from user tasks */
.run-timeline-dot-system { background: var(--omni-color-text-muted); border: 1px dashed var(--omni-color-text-muted); }
.run-timeline-connector-system { background: var(--omni-color-text-muted); }
.run-tl-system { color: var(--omni-color-text-muted, #888); font-style: italic; }
.run-tl-system-row { opacity: 0.75; }
/* Selected, a system step is the one being read: the muted ink faded to 4.1:1 on the selection
   (measured 2026-09-28, dark theme), below AA once the row's opacity was counted too. */
.run-tl-system-row.selected { opacity: 1; }
.run-tl-system-row.selected .run-tl-system { color: var(--omni-color-text); }
.run-system-label { font-size: 0.625rem; }
.run-empty-icon { font-size: 3rem; opacity: 0.3; }
.step-log-empty-icon { font-size: 1.5rem; opacity: 0.5; }
.run-overview-stage { padding: 0.375rem 0.5rem; border-radius: var(--aetheus-radius); }
.run-overview-stage:hover { background: var(--omni-color-surface-hover); }
.run-overview-stage-system { border-left: 2px dashed var(--omni-color-text-muted); }
/* Run overview tiles: a uniform responsive grid so every tile is the same width/height regardless
   of its content (badge, value, link), instead of flex items that stretched unevenly per row. */
.run-overview-tiles {
    display: grid;
    /* Recette R-150: 11rem so a branch or a parent pipeline name is not cut at a normal width.
       Recette R-498: auto-fill keeps the tiles at that width on a wide screen (a few tiles no longer
       stretch across the page), and they wrap when the row is full. */
    grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
    grid-auto-rows: 1fr;
    gap: 0.75rem;
}
/* On a phone, two tiles per row still beat one long column of them. */
@media (max-width: 39.99rem) {
    .run-overview-tiles { grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); }
}
/* A grid item defaults to min-width:auto, so one unbreakable value (a release named after a full
   commit sha) widened its column past the viewport and gave the whole content pane a horizontal
   scrollbar. Pinning the minimum to 0 keeps the grid inside its container. */
.run-overview-tiles > * { min-width: 0; }
.run-overview-tiles > .run-tile-link { height: 100%; }
/* Recette R2-048: the lineage tile takes two columns when it names a run (PipelineRunLineageTile.IsWide). */
.run-overview-tiles > .run-lineage-tile--wide { grid-column: span 2; }
/* Keyboard focus ring for the tiles that act as buttons (jump-to-tab), matching the hover affordance. */
.run-tile-link[role="button"]:focus-visible { outline: 2px solid var(--omni-color-accent, #7c4dff); outline-offset: 2px; border-radius: var(--aetheus-radius); }
.run-overview-card {
    height: 100%;
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* Merged tiles (status + steps, started + duration, repository + branch + commit, release + version)
   stack "label: value" sub-rows under the tile's primary value. */
.run-tile-subrow {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    min-width: 0;
    font-size: 0.8125rem;
}
.run-tile-sublabel {
    color: var(--omni-color-text-muted, #a0a0a0);
    font-size: 0.75rem;
    white-space: nowrap;
    flex-shrink: 0;
}
.run-tile-subrow > :last-child {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Recette R-149: one behaviour for every sub-row, label on the left and value on the right (it was
   the Source tile's alone). */
.run-tile-subrow { justify-content: space-between; }
/* Recette R-145 to R-147: on the run page every tile is three fixed slots, label, value, footer. The
   value is centred in its slot and the footer sits at the bottom, so labels, values and footers line
   up from one tile to the next. A tile without a footer keeps the slot, empty. Scoped to the run
   tiles: the release and artifact pages reuse .run-overview-card with their own content. */
.run-overview-tiles .run-overview-card { display: flex; flex-direction: column; }
.run-overview-tiles .run-overview-card > .omni-card__body {
    flex: 1 1 auto;
    display: grid;
    grid-template-rows: auto 1fr auto;
    gap: 0.25rem;
    min-width: 0;
}
.run-tile-main {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    min-width: 0;
    /* R-146: one text size and weight for every value, and the height of a badge, so a date, a
       name or a percentage sits on the same line as the Status, Gate and Lint badges. */
    min-height: var(--aetheus-badge-height);
    font-size: 1rem;
    font-weight: 500;
    line-height: 1.25;
}
.run-tile-main > .omni-text {
    margin: 0;
    font-size: inherit;
    font-weight: inherit;
    line-height: inherit;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Recette R2-049: an artifact name is long (ApplicationPayload-artifacts) and wrapped over several lines
   at the value size, growing the whole row of tiles. One line at the sub-row size, full name on hover. */
.run-tile-main > .run-artifact-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.8125rem;
}
/* The Source tile puts its repository and branch sub-rows in the value slot. */
.run-tile-main-rows {
    flex-direction: column;
    align-items: stretch;
    justify-content: center;
    gap: 0.125rem;
    font-size: inherit;
    font-weight: inherit;
}
/* Recette R2-025: the candidate verdict sits under the run status, and a badge wider than a narrow
   tile wraps inside the tile instead of overflowing it. */
.run-status-badges { min-width: 0; max-width: 100%; }
.run-status-badges > .omni-badge { max-width: 100%; }
.run-status-badges > .run-candidate-verdict-badge { white-space: normal; text-align: start; }
.run-tile-foot {
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
    min-width: 0;
    /* An empty footer still reserves its line, so every tile's footer row has the same height. */
    min-height: 1.25rem;
}
.run-release-version,
.run-release-app-version { font-family: monospace; }
.run-overview-stage-grouped { margin-left: 1.5rem; }
.run-overview-stage-sub { padding-left: 1rem; opacity: 0.9; border-left: 1px solid var(--omni-color-text-muted, #444); margin-left: 0.75rem; }
.run-group-header {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.5rem 0.25rem 0.25rem;
    font-weight: 600; font-size: 0.875rem;
    color: var(--omni-color-accent, #7c4dff);
    border-bottom: 1px solid var(--aetheus-tile-separator);
    margin-top: 0.5rem;
}
.run-group-header:first-child { margin-top: 0; }
/* S-DES-20: completed/total + progress bar inside a Build/Deploy group header */
.run-group-count { font-size: 0.75rem; font-weight: 400; color: var(--omni-color-text-muted, #aaa); }
.run-group-progress { flex: 1; max-width: 12rem; height: 0.375rem; }

/* G (Phase 3): legacy UITimeline-based progress view rows */
.run-progress-timeline .omni-timeline__content { padding-bottom: 0.5rem; }
.run-tl-item-row {
    display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap;
    padding: 0.25rem 0.5rem; border-radius: var(--aetheus-radius);
}
.run-tl-item-row.clickable-cell:hover { background: var(--omni-color-surface-hover); }
.run-tl-item-sub { margin-left: 0.75rem; }
.run-tl-item-grouped { margin-left: 0.75rem; }
.run-tl-item-group { padding-left: 0; }
.run-tl-item-stage { font-weight: 600; }
.run-icon-spin { animation: pulse-dot 1.4s ease-in-out infinite; }

/* G: compact legacy UITimeline inside the Logs split (selectable step rail). LinePosition.Start gives
   the native left rail (point + connecting line stay aligned) and legacy UI itself collapses the empty
   opposite side (.omni-timeline--start .omni-timeline__content { display:none }) and left-aligns the
   content — so no custom display:none / flex override is needed here (R2HD). */
.run-logs-timeline .omni-timeline__content { text-align: left; }
.run-logs-timeline .omni-timeline__content { padding-bottom: 0.25rem; }
.run-logs-timeline .run-tl-name-grow { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.run-logs-timeline .run-tl-row {
    display: flex; align-items: center; gap: 0.5rem; flex-wrap: nowrap;
    padding: 0.25rem 0.5rem; border-radius: var(--aetheus-radius); cursor: pointer;
}
.run-logs-timeline .run-tl-row.selected { background: var(--omni-color-accent-subtle); }
.run-logs-timeline .run-tl-row:hover { background: var(--omni-color-surface-hover); }
.run-logs-timeline .run-tl-stage-header { cursor: default; }
.run-logs-timeline .run-tl-sub { margin-left: 0.75rem; }
.run-logs-timeline .run-tl-grouped { margin-left: 0.75rem; }

/* A1-A4: recursive collapsible run tree (logs-tab left panel). The top-level tree is a bordered,
   rounded card with row separators (run-timeline mockup); nested child runs stay indented + railed. */
.run-tree { display: flex; flex-direction: column; gap: 0.125rem; }
/* Recette R-139: the tile surface, its border and its row separators, as before the OE tokens took
   over the legacy names (the text colour had become the frame and the separators, the inverse
   surface the background). */
.run-tree:not(.run-tree-nested) {
    flex: 0 0 auto;
    gap: 0;
    border: 0.0625rem solid var(--aetheus-tile-border);
    border-radius: var(--aetheus-radius);
    overflow: hidden;
    background: var(--aetheus-tile-background);
}
.run-tree:not(.run-tree-nested) > .run-tl-row {
    padding: 0.625rem 0.875rem;
    border-radius: 0;
    border-bottom: 0.0625rem solid var(--aetheus-tile-separator);
}
.run-tree:not(.run-tree-nested) > .run-tl-row:last-child { border-bottom: none; }
/* Indent sub-tasks (steps within a multi-step stage) under their stage header. */
.run-tl-substep { padding-left: 2rem; }
.run-tree:not(.run-tree-nested) > .run-tl-substep { padding-left: 2.25rem; }
.run-tl-stage-header { padding-left: 0.5rem; }
.run-tree-nested {
    margin-left: 1.75rem;
    padding-left: 0.75rem;
    border-left: 0.125rem solid var(--omni-color-text-muted, #333);
}
.run-tl-trigger {
    min-width: 0;
    overflow: hidden;
    font-weight: 500;
}
.run-tl-trigger-text {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    gap: 0.375rem;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
}
/* Stage label column (CI / QA / DEPLOY) + child-pipeline chip on a trigger row */
.run-tl-stage-label {
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--omni-color-text-muted, #8b949e);
    width: 3.75rem;
    flex: 0 1 3.75rem;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.run-tl-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    font-size: 0.75rem;
    padding: 0.125rem 0.5rem;
    border-radius: var(--aetheus-radius);
    background: var(--omni-color-accent-subtle, #241a4d);
    color: var(--omni-color-accent, #c9b8ff);
    white-space: nowrap;
    flex-shrink: 0;
    text-decoration: none;
    cursor: pointer;
}
/* The chip is a link to the triggered child run. */
.run-tl-chip:hover { background: var(--omni-color-accent, #7c4dff); color: #fff; }
.run-tl-chip .run-tl-chip-icon { font-size: 0.875rem; color: inherit; }
/* These two are statements about the stage, not links: no pointer, no hover affordance. */
.run-tl-parallel,
.run-tl-skipped {
    cursor: default;
    background: var(--omni-color-border, #2b3138);
    color: var(--omni-color-text-muted, #c0c6cd);
}
.run-tl-parallel:hover,
.run-tl-skipped:hover {
    background: var(--omni-color-border, #2b3138);
    color: var(--omni-color-text-muted, #c0c6cd);
}
.run-tl-skipped { font-style: italic; }
.run-tl-desc {
    color: var(--omni-color-text-muted, #c0c6cd);
    font-weight: 400;
    white-space: nowrap;
}
.run-tl-caret {
    font-size: 1.125rem;
    flex-shrink: 0;
    color: var(--omni-color-text-muted, #a0a0a0);
}
.run-tl-chip-icon {
    font-size: 1rem;
    flex-shrink: 0;
    color: var(--omni-color-accent, #7c4dff);
}
.run-tl-open-link {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    color: var(--omni-color-text-muted, #a0a0a0);
}
.run-tl-open-link:hover { color: var(--omni-color-accent, #7c4dff); }
.run-tl-children { margin-top: 0.125rem; }
.run-tl-note {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.25rem 0.5rem;
    font-size: 0.75rem;
    color: var(--omni-color-text-muted, #a0a0a0);
}

/* S-UX-31: stage duration bars */
.run-os-icon { font-size: 1rem; flex-shrink: 0; }
.run-os-icon-linux { color: var(--omni-color-warning); }
.run-os-icon-windows { color: var(--omni-color-accent); }

/* Q2BV: lock the connecting rail (thickness + neutral colour) to one value across the four
   legacy UITimeline instances — project activity, release changelog, pipeline run progress and run
   logs — so they read as one consistent visual language regardless of theme drift or local
   overrides. Status colour stays on the points/dots, never the rail. */
.project-activity-timeline,
.run-progress-timeline,
.run-logs-timeline {
    --omni-border-width: 0.125rem;
    --omni-color-border: var(--omni-color-text-muted);
}

/* Q2BV: the release changelog now renders through legacy UITimeline (ProjectOverviewSection,
   reusing the .project-activity-timeline class), so it inherits the shared rail-lock above
   alongside project activity, run progress and run logs — all four read identically. The old
   custom .release-cl-* markup CSS was removed as dead leftover of the pre-legacy UI changelog. */

/* Clickable grid cell — replaces inline style="cursor:pointer" */
.clickable-cell { cursor: pointer; }
.clickable-cell:hover { text-decoration: underline; }

/* Layout utilities */
.flex-spacer {
    flex: 1;
}

.dialog-footer { margin-top: 0.75rem; }
/* Recette R-406: in a dialog with an intention, the shared DialogFooter (rendered at the end of the
   dialog body, not in OE's footer slot) reads as OE's footer: the same rule and padding, reaching the
   dialog's edges through the body's padding. Recette R2-029: no tint any more; since OE 1.4.0 the
   header and the footer of a dialog stay neutral, only the mark before the title carries the intention. */
.omni-dialog:is(.omni-dialog--intent-accent, .omni-dialog--intent-warning, .omni-dialog--intent-danger) > .omni-dialog__content .dialog-footer {
    border-top: var(--omni-border-width) solid var(--omni-color-border);
    margin: 0.75rem calc(-1 * (var(--omni-card-pad) + 2px)) calc(-1 * (var(--omni-card-pad) + 2px));
    padding: calc(var(--omni-card-pad) - 2px) calc(var(--omni-card-pad) + 2px);
}

.sidebar-width {
    width: 15.625rem;
    --omni-sidebar-width: 15.625rem;
}

.omni-sidebar.sidebar-width { width: auto; }
.omni-main:has(> .app-body-wrapper) { flex: 1 1 auto; }

/* The content area sits on its own shade, apart from the app bar and the sidebar, so the cards, grids
   and the login card stand out from it as they did in production (recette R-039, R-009): a light grey
   in light mode, darker than the surface in dark mode. */
/* Recette R-055 and R-106 (values given by the user, checked for >= 4.5:1 text contrast): the page a
   touch cooler in light mode, tiles and cards with a softer border than the text colour, their inner
   separators lighter still, and tile titles darker than the body text. Applied to every OE card
   through its border token, so grids, tiles and cards share one frame. */
:root {
    --aetheus-page-background: #eceef2;
    --aetheus-tile-background: #ffffff;
    --aetheus-tile-border: #c9cdd6;
    --aetheus-tile-separator: #e3e5ea;
    --aetheus-tile-title: #1f1f24;
    --omni-card-border-color: var(--aetheus-tile-border);
}
[data-omni-theme="dark"] {
    --aetheus-page-background: color-mix(in srgb, var(--omni-color-surface) 62%, #000000);
    --aetheus-tile-background: #26262b;
    --aetheus-tile-border: #4a4a52;
    --aetheus-tile-separator: #3a3a40;
    --aetheus-tile-title: #f2f2f2;
    --omni-card-border-color: var(--aetheus-tile-border);
}
.omni-main:has(> .app-body-wrapper) { background: var(--aetheus-page-background); }

/* A neutral (secondary) button in the dark theme: the theme's own neutral fill sank into the dark
   surfaces (recette R-005, R-037), so it stays a grey lighter than the tiles. Recette R-384 (user
   decision 2026-09-28): white ink on every filled button in dark mode, so the grey is the lightest one
   that keeps the package's white ink (--omni-color-on-deep) at 4.5:1 or more (4.7:1 at rest, 5.7:1
   hovered, 6.6:1 pressed), and about 3:1 at rest against the dark tiles. */
[data-omni-theme="dark"] .omni-button--secondary {
    --omni-button-fill: #6e6e74;
    --omni-button-fill-hover: #626268;
    --omni-button-fill-active: #58585e;
    --omni-button-ink: var(--omni-color-on-deep);
}

/* Auth / Login */
.login-container {
    width: min(100%, 25rem);
    min-width: 0;
    margin: clamp(1rem, 8vh, 5rem) auto;
    box-sizing: border-box;
}

.login-container > .omni-card {
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
}

/* The password eye fills the inside of the shared field border (recette R-003). */
:root { --omni-password-toggle-size: calc(var(--aetheus-control-height) - 2 * var(--omni-border-width)); }

/* Recette R-309: every password field, not only the login one, is one control height border included;
   the input inside it gives up its own height (the vault secret field measured 30px beside 28px). */
.omni-password {
    box-sizing: border-box;
    height: var(--aetheus-control-height);
    min-height: var(--aetheus-control-height);
}

.omni-password__input.omni-input { height: auto; min-height: 0; }

.login-container .omni-form-field,
.login-container .omni-text-box,
.login-container .omni-password__input {
    min-width: 0;
    max-width: 100%;
}

/* The change-password card on a phone. The login page is an OmniLoginShell (recette R-376), which sets
   its own phone margins and card padding: padding its card again squeezed the form (recette R-379). */
@media (max-width: 39.99rem) {
    .login-container:not(.omni-login-shell) {
        margin-block: 0.75rem;
    }

    .login-container:not(.omni-login-shell) > .omni-card {
        padding: 1rem;
    }
}

/* S-DES-07: login page logo */
.login-logo-img {
    width: 4.5rem;
    height: 4.5rem;
    border-radius: 1rem;
    opacity: 0.9;
}

.btn-full-width {
    width: 100%;
}

/* Dashboard status colors */
.text-success {
    color: var(--omni-color-success);
}

.text-danger {
    color: var(--omni-color-danger);
}

/* ==========================================================================
   Flex Spacer
   ========================================================================== */

.flex-spacer {
    flex: 1;
}

/* ==========================================================================
   Log Viewers (Tasks + Logs pages)
   ========================================================================== */

.task-log-viewer {
    max-height: 25rem;
    overflow-y: auto;
    background: var(--omni-color-inverse-surface, #121212);
    color: var(--omni-color-on-inverse);
    padding: 1rem;
    border-radius: var(--aetheus-radius);
    font-family: 'Cascadia Code', 'Fira Code', monospace;
    font-size: 0.85rem;
}

/* Recette R-403: the explanation and the task field sit under the page title, above the panel. */
.live-logs-watch {
    flex-wrap: wrap;
}

.live-log-panel {
    /* The description and the task row above take about 6rem more than the header alone did. */
    height: calc(100vh - 20rem);
    min-height: 12rem;
    overflow-y: auto;
    /* F20+: keep terminal aesthetic in dark mode; switch to light surface in light mode for parity. */
    background: var(--omni-color-surface, #121212);
    color: var(--omni-color-text, #e0e0e0);
    padding: 1rem;
    border: 1px solid var(--omni-color-border, transparent);
    border-radius: var(--aetheus-radius);
    font-family: 'Cascadia Code', 'Fira Code', monospace;
    font-size: 0.85rem;
    /* Smooth auto-scroll on batched LogsReceived appends. */
    scroll-behavior: smooth;
}

.log-entry {
    color: var(--omni-color-text, #e0e0e0);
    line-height: 1.4;
}

.log-timestamp {
    color: var(--omni-color-text-muted, #888);
}

.log-task-id {
    color: var(--omni-color-accent, #4fc3f7);
}

.log-level-error {
    color: var(--omni-color-danger, #d83b3b);
}

.log-level-warning {
    color: var(--omni-color-warning, #b6892b);
}

.log-level-debug {
    color: var(--omni-color-text-muted, #888);
}

.masked-value {
    background: color-mix(in srgb, var(--omni-color-danger) 15%, transparent);
    color: var(--omni-color-danger);
    padding: 0 0.125rem;
    border-radius: var(--aetheus-radius);
    font-weight: 600;
}

/* ==========================================================================
   Token Display (#33 — neutral code color)
   ========================================================================== */

.token-code {
    color: var(--omni-color-text, #c8c8c8);
    font-family: 'Cascadia Code', 'Fira Code', monospace;
    font-size: 0.85rem;
}

/* ==========================================================================
   Visual Pipeline Editor
   ========================================================================== */

.vp-canvas-outer {
    position: relative;
    overflow: hidden;
    width: 100%;
    height: calc(100vh - 24rem);
    min-height: 20rem;
    border: 0.0625rem solid var(--omni-color-border);
    border-radius: var(--omni-radius);
    background-color: var(--omni-color-surface-muted);
    background-image:
        radial-gradient(circle, var(--omni-color-border) 0.0625rem, transparent 0.0625rem);
    background-size: 1.25rem 1.25rem;
}

.vp-canvas-inner {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    transform-origin: 0 0;
}

.vp-canvas-inner-loading {
    visibility: hidden;
}

.vp-node {
    position: absolute;
    width: 13.75rem;
    border-radius: var(--aetheus-radius);
    border: 0.125rem solid var(--omni-color-text-muted, #4a4a5e);
    background-color: var(--omni-color-surface);
    box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, 0.3);
    transition: border-color 0.15s, box-shadow 0.15s;
    z-index: 1;
}

.vp-node:hover {
    border-color: var(--omni-color-text-muted, #5a5a6e);
}

.vp-node.vp-node-selected {
    border-color: var(--omni-color-accent, #1e88e5);
    box-shadow: 0 0 0.75rem rgba(30, 136, 229, 0.3);
}

.vp-node-header {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.5rem 0.75rem;
    border-bottom: 0.0625rem solid var(--aetheus-tile-separator);
    border-radius: var(--aetheus-radius) var(--aetheus-radius) 0 0;
    background-color: var(--omni-color-surface-highlight);
    font-weight: 600;
    font-size: 0.8125rem;
    user-select: none;
}

.vp-node-drag-handle {
    display: inline-flex;
    align-items: center;
    min-width: 1.25rem;
    color: var(--omni-color-text-muted, #a0a0b0);
    touch-action: none;
}

.vp-node-drag-handle:focus-visible {
    outline: 0.125rem solid var(--omni-color-accent, #1e88e5);
    outline-offset: 0.125rem;
}

.vp-node-header-title {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.vp-node-agent {
    padding: 0.25rem 0.75rem;
    font-size: 0.75rem;
    color: var(--omni-color-text-muted, #a0a0b0);
    border-bottom: 0.0625rem solid var(--aetheus-tile-separator);
}

.vp-node-steps {
    padding: 0.25rem 0;
    max-height: 10rem;
    overflow-y: auto;
}

.vp-node-step {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    padding: 0.1875rem 0.75rem;
    font-size: 0.75rem;
    font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
}

.vp-node-step-number {
    color: var(--omni-color-text-muted, #a0a0b0);
    min-width: 1rem;
    text-align: right;
}

.vp-node-step-name {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.vp-node-step-edit {
    opacity: 0;
    transition: opacity 0.15s;
    cursor: pointer;
    color: var(--omni-color-text-muted, #a0a0b0);
    font-size: var(--icon-md);
}

.vp-node-step:hover .vp-node-step-edit {
    opacity: 1;
}

.vp-node-footer {
    padding: 0.375rem 0.75rem;
    border-top: 0.0625rem solid var(--aetheus-tile-separator);
}

.vp-node-action-btn {
    cursor: pointer;
    opacity: 0.6;
    display: flex;
}

.vp-node-action-btn:hover {
    opacity: 1;
}

.vp-editor-guide { margin: 0.5rem 0; }

.inline-text {
    display: inline;
}

.vp-edge-layer {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 0;
}

/* ─── Tabs: remove horizontal padding on panels ─── */
.omni-tabs .omni-tabs__panel {
    padding-left: 0;
    padding-right: 0;
}

/* Shared URL-synchronised tabs keep one stable, single-line navigation rail, left-aligned, each tab
   sized to its own label (PLAN-003 D12). When labels no longer
   fit, the rail remains keyboard/touch scrollable without introducing a horizontal scrollbar that
   would change its height between renders. */
.url-synced-tabs,
.url-synced-tabs > .omni-tabs__strip {
    box-sizing: border-box;
    width: 100%;
    max-width: 100%;
    min-width: 0;
}

.task-log-viewer .log-entry {
    color: inherit;
}

.task-log-viewer .log-timestamp,
.task-log-viewer .log-level-debug {
    color: color-mix(in srgb, var(--omni-color-on-inverse) 78%, var(--omni-color-inverse-surface));
}

.task-log-viewer .log-level-warning {
    color: color-mix(in srgb, var(--omni-color-on-inverse) 70%, var(--omni-color-warning));
}

.task-log-viewer .log-level-error {
    color: color-mix(in srgb, var(--omni-color-on-inverse) 70%, var(--omni-color-danger));
}

.url-synced-tabs > .omni-tabs__strip .omni-tabs__viewport {
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    overscroll-behavior-inline: contain;
}

.url-synced-tabs > .omni-tabs__strip .omni-tabs__viewport::-webkit-scrollbar {
    display: none;
}

.url-synced-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__item {
    flex: 0 0 auto;
    min-width: max-content;
}

/* A tab is as wide as its label (recette R-080, R-101). The former width: 100% sized it to its item
   wrapper; OE renders that wrapper display: contents now, so every tab took the whole strip and the
   others hid behind the scroll arrows. */
/* Recette R-503: OE gives the tab row 0.25rem of padding for the focus ring and takes it back with a
   negative margin, so the row ends 0.25rem past its strip. Inside .blade-content, which scrolls
   sideways, that was a 4px overflow and a scrollbar on every page made of tabs (measured in production:
   837px of content for 833px visible; reproduced, then gone with this rule: 598 for 598). Only the end
   side is given up: the start side does not scroll, and the tabs keep their place. */
.omni-tabs > .omni-tabs__strip .omni-tabs__viewport {
    margin-inline-end: 0;
}

.omni-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__tab {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: flex-start;
    min-height: 100%;
}

.url-synced-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__tab,
.url-synced-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__title {
    white-space: nowrap !important;
}

/* Every OE data grid keeps every column available at every viewport width. Columns without an
   explicit Width would otherwise collapse to 0px when the fixed-width columns already consume the
   table width. Give only those unspecified columns a stable default, and contain any overflow in
   the OE grid body so the complete table remains reachable through horizontal scrolling. */
.omni-data-grid {
    --omni-grid-row-estimate: var(--aetheus-grid-row-height);
    width: 100%;
    max-width: 100%;
    min-width: 0;
    overflow: hidden;
}

/* OE declares a column's Width as --omni-col-width on the col, not as an inline width, so those cols
   are excluded too: this default used to override every OE width, and every column came out the same
   size whatever its Width (measured 2026-09-21 on /servers, nine columns of 172px). A MinWidth-only
   column (--omni-col-min) takes its minimum when that is larger than the default. */
.omni-data-grid .omni-data-grid__table > colgroup > col:not([style^="width:"]):not([style*=";width:"]):not([style*="; width:"]):not([style*="--omni-col-width"]) {
    width: max(10rem, var(--omni-col-min, 0px));
}

.omni-data-grid .omni-data-grid__viewport {
    overflow-x: auto;
    overscroll-behavior-inline: contain;
}

@media (max-width: 39.99rem) {
    /* Every tab rail must remain a single, stable row on phones. Long labels scroll horizontally
       instead of wrapping and changing the page geometry when the selected tab changes. */
    .omni-tabs > .omni-tabs__strip .omni-tabs__viewport,
    /* These rails opt into wrapping (or a two-column grid) on wide screens and at their own
       breakpoints; on phones the single scrolling row wins, so they are repeated here with enough
       specificity to override those declarations. */
    .url-synced-tabs.pipeline-run-tabs > .omni-tabs__strip .omni-tabs__viewport,
    .analysis-finding-tabs > .omni-tabs__strip .omni-tabs__viewport,
    .analysis-workspace-tabs > .omni-tabs__strip .omni-tabs__viewport {
        flex-wrap: nowrap;
        overflow-x: auto;
        overflow-y: hidden;
        scrollbar-width: none;
        overscroll-behavior-inline: contain;
    }

    .omni-tabs > .omni-tabs__strip .omni-tabs__viewport::-webkit-scrollbar,
    .url-synced-tabs.pipeline-run-tabs > .omni-tabs__strip .omni-tabs__viewport::-webkit-scrollbar,
    .analysis-finding-tabs > .omni-tabs__strip .omni-tabs__viewport::-webkit-scrollbar,
    .analysis-workspace-tabs > .omni-tabs__strip .omni-tabs__viewport::-webkit-scrollbar {
        display: none;
    }

    /* A tab keeps its natural label width so the rail scrolls instead of squeezing every tab. */
    .omni-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__item,
    .url-synced-tabs.pipeline-run-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__item,
    .analysis-finding-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__item,
    .analysis-workspace-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__item {
        flex: 0 0 auto;
        min-width: max-content;
    }

    .omni-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__tab,
    .omni-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__title {
        white-space: nowrap !important;
    }

    .url-synced-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__item {
        flex: 0 0 auto;
    }

    .url-synced-tabs .omni-tabs__tab button,
    .url-synced-tabs .omni-tabs__title {
        white-space: nowrap !important;
    }

    .project-ci-settings-row > .omni-column,
    .project-ci-settings-row > [class*="omni-column--"] {
        flex: 0 0 100% !important;
        max-width: 100% !important;
    }

    .project-ci-settings-row .omni-form-field {
        width: 100%;
    }

    .docker-toolbar {
        align-items: stretch;
        flex-direction: column;
    }

    .docker-toolbar .blade-content-spacer {
        display: none;
    }

    .docker-toolbar > * {
        max-width: 100%;
    }
}

/* Recette R-326 / R-342: a badge keeps its whole text on one line; the group around it wraps
   instead of the badge being squeezed or cut with an ellipsis, at every width (a dashboard tile is
   narrow on a wide screen too). */
.omni-badge {
    flex-shrink: 0;
    vertical-align: middle;
}

.dashboard-tile .omni-stack:has(> .omni-badge) {
    flex-wrap: wrap;
    justify-content: flex-start;
}

/* ─── Wizard ───
   Recette R-396 / R-397: the assistant is OE's OmniWizard (progress, steps, body and actions are the
   package's own); Aetheus styles only the content of its steps. */

/* ─── Add Agent Page ─── */
/* Recette R-398: the wizard's buttons stay at the bottom of the window whatever the height of the
   step. The page fills the scrolling main area as a flex column (as the full-height grids do), the
   wizard grows into it and OE pushes its sticky foot down; a longer step scrolls under the foot. */
.app-body-wrapper:has(> .add-agent-page),
.app-body-wrapper:has(> .wizard-dialog) {
    box-sizing: border-box;
    min-height: 100%;
}

.add-agent-page {
    display: flex;
    flex: 1 0 auto;
    flex-direction: column;
}

/* Recette R-397: once stuck over a long step, the band takes the page shade, not a white surface. */
.add-agent-page > .wizard-dialog,
.app-body-wrapper > .wizard-dialog {
    --omni-wizard-actions-background: var(--aetheus-page-background);
    display: flex;
    flex: 1 0 auto;
    flex-direction: column;
}

/* Commands and token are OE OmniCodeBlock (recette R-400); only their spacing is Aetheus'. */
.wizard-code-block,
.wizard-token-code {
    margin-bottom: 0.5rem;
}

/* S-DES-14: expandable changelog cell in the releases grid */

/* ---- Console-style output blocks (unified with the wizard code tokens) ----
   .task-output           — Tasks page: stdout/stderr from agent shells
   .pipeline-step-output  — Pipelines page: per-step log streamed via SignalR
   .server-detail-code    — Servers detail: command output / inline scripts
   Use .is-success / .is-error to tint the block once a state is known. */
.task-output,
.pipeline-step-output,
.server-detail-code {
    background: var(--wizard-code-bg);
    border: 1px solid var(--wizard-code-border);
    border-radius: var(--wizard-code-radius);
    color: var(--wizard-code-text);
    font-family: 'Cascadia Code', 'Consolas', 'Menlo', monospace;
    font-size: 0.85rem;
    line-height: 1.5;
    padding: 0.75rem 1rem;
    overflow: auto;
    white-space: pre-wrap;
    word-break: break-word;
}

.task-output.is-success,
.pipeline-step-output.is-success,
.server-detail-code.is-success {
    background: var(--wizard-code-bg-success);
    border-color: var(--wizard-code-border-success);
}

.task-output.is-error,
.pipeline-step-output.is-error,
.server-detail-code.is-error {
    background: var(--wizard-code-bg-error);
    border-color: var(--wizard-code-border-error);
}

/* Verify polling (recette R-401): the page's muted surface and its own text colour, readable in both
   themes, instead of the dark inverse panel. */
.wizard-polling {
    padding: 0.75rem 1rem;
    background: var(--omni-color-surface-muted);
    border: 0.0625rem solid var(--omni-color-border);
    border-radius: var(--aetheus-radius);
    color: var(--omni-color-text);
}

.wizard-verify-success-icon {
    color: var(--omni-color-success);
    vertical-align: middle;
}

.wizard-verify-list {
    margin: 0;
    padding-left: 1.25rem;
}

.wizard-verify-list li {
    margin-bottom: 0.5rem;
}


.vp-edge {
    fill: none;
    /* Recette R-411: the muted ink, readable on the canvas in both themes (the border grey it had was
       drawn for the dark canvas and faded to 1.5:1 on the light one). */
    stroke: var(--omni-color-text-muted);
    stroke-width: 2.5;
    transition: d 0.3s ease, stroke 0.2s ease, stroke-width 0.2s ease;
}

.vp-edge-selected {
    stroke: var(--omni-color-accent, #1e88e5);
    stroke-width: 2.5;
}

.vp-edge-arrow {
    fill: var(--omni-color-text-muted);
}

.vp-edge-arrow-selected {
    fill: var(--omni-color-accent, #1e88e5);
}

.vp-toolbar {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0;
    flex-wrap: wrap;
}

.vp-settings-panel {
    border: 0.0625rem solid var(--omni-color-border);
    border-radius: var(--omni-radius);
    padding: 0.75rem;
    margin-bottom: 0.75rem;
    background-color: var(--omni-color-surface-muted);
}

.vp-edit-panel {
    border: 0.0625rem solid var(--omni-color-border);
    border-radius: var(--omni-radius);
    padding: 0.75rem;
    margin-top: 0.75rem;
    background-color: var(--omni-color-surface-muted);
}

.vp-edit-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 0.75rem;
}

.vp-empty-canvas {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    gap: 1rem;
    color: var(--omni-color-text-muted, #a0a0b0);
}

.vp-error-banner {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem;
    border-radius: var(--omni-radius);
    background-color: var(--omni-color-danger-subtle, #3d1a1e);
    border: 0.0625rem solid var(--omni-color-danger, #dc3545);
    color: var(--omni-color-danger, #dc3545);
    margin-bottom: 0.75rem;
}

.vp-var-grid {
    display: grid;
    grid-template-columns: 1fr 1fr auto;
    gap: 0.25rem;
    align-items: center;
}

@media (max-width: 39.99rem) {
    .vp-canvas-outer {
        height: calc(100vh - 28rem);
        min-height: 15rem;
    }

    .vp-node {
        width: 11rem;
    }
}

/* ---- Equal-height tiles — uniform height per row ----
   legacy UIRow renders as flex with align-items:stretch by default, but
   legacy UIColumn itself isn't flex — so a card inside it sizes to its
   content. We force the columns of these rows to be flex containers so
   their child card can stretch to the tallest sibling's height.

   Usage:
   - `equal-tile-row` on any legacy UIRow → child legacy UICards auto-stretch
     (works for metric tiles on server overview, etc.)
   - `dashboard-tile-row` = `equal-tile-row` + 20rem min-height floor
     (dashboard widgets where short content should still leave breathing room)
*/
.equal-tile-row,
.dashboard-tile-row {
    align-items: stretch !important;
}

.equal-tile-row > .omni-column,
.equal-tile-row > [class*="omni-column--"],
.dashboard-tile-row > .omni-column,
.dashboard-tile-row > [class*="omni-column--"] {
    display: flex;
    flex-direction: column;
}

.equal-tile-row > .omni-column > .omni-card,
.equal-tile-row > [class*="omni-column--"] > .omni-card,
.dashboard-tile-row > .omni-column > .omni-card,
.dashboard-tile-row > [class*="omni-column--"] > .omni-card {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    height: auto;
}

.equal-tile-row .omni-card > .omni-card__body,
.dashboard-tile-row .omni-card > .omni-card__body {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* Recette R-097: the tiles wrap with the screen width instead of scrolling sideways in a fixed
   seven-by-two block, and keep a visible gap between them. */
.project-section-grid-scroll {
    max-width: 100%;
    padding: 0.125rem;
}

.project-section-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
    grid-auto-rows: 5.25rem;
    gap: 0.75rem;
}

.project-section-link {
    min-width: 0;
}

.project-section-tile {
    height: 100%;
    min-height: 0;
    padding: 0.625rem;
}

.project-section-tile-content {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.375rem;
    height: 100%;
    text-align: center;
}

.project-section-tile-heading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.375rem;
    min-width: 0;
}

.project-section-tile-heading > .omni-icon {
    flex: 0 0 auto;
    color: var(--omni-color-accent);
    font-size: 1.25rem;
}

.project-section-tile-heading .omni-text {
    overflow: hidden;
    font-size: 0.75rem;
    line-height: 1.15;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.project-section-tile-current {
    border-color: color-mix(in srgb, var(--omni-color-accent) 55%, var(--omni-color-text));
    background: color-mix(in srgb, var(--omni-color-accent) 9%, var(--omni-color-surface));
}

/* Server overview — Pipeline runner / Container isolation posture tiles.
   The toggle button sinks to the bottom so the two side-by-side tiles align. */
.overview-feature-card .omni-card__body > .omni-stack {
    height: 100%;
}

.overview-feature-card .omni-button {
    margin-top: auto;
    align-self: flex-start;
}

/* Heartbeat pulse: a thick ring that fills over the 30 s beat cadence and resets on
   each fresh beat. Centre counts the cadence down (30 → 0). Laid out as a tile: the
   "Heartbeat" label sits at the top like the sibling metric tiles, the ring + meta
   centre in the remaining space. */
.heartbeat-pulse-card {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    height: 100%;
}

.heartbeat-pulse-body {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    flex: 1 1 auto;
    text-align: center;
}

.heartbeat-last-updated { margin: 0; }

.heartbeat-pulse {
    position: relative;
    width: 4.5rem;
    height: 4.5rem;
    flex-shrink: 0;
}

.heartbeat-pulse-ring {
    width: 100%;
    height: 100%;
    transform: rotate(-90deg);
}

.heartbeat-pulse-track {
    fill: none;
    stroke: var(--omni-color-border, #2a2a2a);
    stroke-width: 9;
}

.heartbeat-pulse-progress {
    fill: none;
    stroke-width: 9;
    stroke-linecap: round;
}

/* Live ring: a single cadence that fills then snaps back to empty and refills.
   The dash values match the r=42 circumference (2·π·42 ≈ 263.89). The circle element is
   re-keyed on each beat in the component, which restarts this animation from empty.
   The cadence is centralised in --heartbeat-cadence (HBSY) — one source of truth shared
   by the CSS loop and the component's HeartbeatCadenceSeconds constant. The component
   sets a negative animation-delay on (re)creation so the first cycle after page load is
   phase-aligned with the real age of the last beat, not just resynced at the next beat. */
.heartbeat-pulse-progress { --heartbeat-cadence: 30s; }

@keyframes heartbeat-fill {
    from { stroke-dashoffset: 263.89; }
    to { stroke-dashoffset: 0; }
}

/* HB8P: a brief scale flash each time the ring resets (the circle is re-keyed on every
   beat, so this one-shot animation replays on each fresh beat) — materialises the pulse. */
@keyframes heartbeat-flash {
    0% { transform: scale(1); }
    35% { transform: scale(1.05); }
    100% { transform: scale(1); }
}

.heartbeat-pulse-online .heartbeat-pulse-progress {
    stroke: var(--omni-color-success, #22c55e);
    animation: heartbeat-fill var(--heartbeat-cadence, 30s) linear infinite, heartbeat-flash 0.5s ease-out;
    transform-box: fill-box;
    transform-origin: center;
}

.heartbeat-pulse-offline .heartbeat-pulse-progress {
    stroke: var(--omni-color-danger, #ef4444);
}

.heartbeat-pulse-disabled .heartbeat-pulse-progress {
    stroke: var(--omni-color-text-muted, #6b7280);
}

.heartbeat-pulse-center {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.1rem;
    text-align: center;
}

.heartbeat-pulse-duration {
    font-size: 1.1rem;
    font-weight: 600;
    line-height: 1.1;
    color: var(--omni-color-text, #e0e0e0);
}

.heartbeat-pulse-state {
    font-size: 0.55rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.heartbeat-pulse-online .heartbeat-pulse-state {
    color: var(--omni-color-success, #22c55e);
}

.heartbeat-pulse-offline .heartbeat-pulse-state {
    color: var(--omni-color-danger, #ef4444);
}

.heartbeat-pulse-disabled .heartbeat-pulse-state {
    color: var(--omni-color-text-muted, #9ca3af);
}

/* Services capability notice: the "package management disabled" message and the live heartbeat
   shown as two aligned, equal-height tiles above the services tables (the tables stay tables). */
.capability-warning-tile {
    display: flex;
    align-items: center;
}

.capability-warning-icon {
    color: var(--omni-color-warning, #f59e0b);
    flex-shrink: 0;
}

.heartbeat-tile {
    flex: 0 0 auto;
    min-width: 11rem;
}

/* Server overview — Essentials definition grid: icon + label/value pairs, two
   responsive columns collapsing to one on narrow viewports. */
.essentials-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.25rem 2rem;
}

@media (max-width: 39.99rem) {
    .essentials-grid {
        grid-template-columns: 1fr;
    }
}

/* Single-column variant for the narrower half-width overview cards
   (project Information / Repository) — same icon+label+value treatment. */
.essentials-grid--stacked {
    grid-template-columns: 1fr;
    gap: 0;
}

/* No trailing divider under the last row in the stacked variant (rows have no
   column gap there, so the last border would sit flush against the card edge). */
.essentials-grid--stacked .essential-item:last-child {
    border-bottom: none;
}

/* Recette R-339: the icon sits on the middle of its label and value, not at their top. */
.essential-item {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    padding: 0.5rem 0;
    border-bottom: 1px solid var(--omni-color-surface-highlight, #ececec);
}

.essential-icon {
    font-size: 1.1rem;
    color: var(--omni-color-text-muted, #9ca3af);
    flex-shrink: 0;
}

.essential-text {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
}

.essential-label {
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--omni-color-text-muted, #9ca3af);
}

.essential-value {
    font-size: 0.9rem;
    font-weight: 500;
    color: var(--omni-color-text, #e0e0e0);
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
    word-break: break-word;
}

/* A tile is as tall as its list, never a box that scrolls inside the scrolling page (recette R-017,
   R-015): the fixed 25rem height cut the Servers and Projects grids off at the bottom and gave the
   runs grid its own scrollbar. Tiles of a row still end level, the row stretching them to the
   tallest, and the 25rem stays a floor. */
.dashboard-tile-row > .omni-column > .dashboard-tile,
.dashboard-tile-row > [class*="omni-column--"] > .dashboard-tile {
    flex: 1 1 auto;
    min-height: 25rem;
}

.dashboard-tile .dashboard-clickable-grid {
    flex: 1 1 auto;
}

/* Recette R-065: while it loads, a tile's grid already fills the tile (the 25rem floor) and the
   loading plane is centred over it, instead of a header-high strip with the loader squeezed in. The
   grid host relays the tile's flex column down to the grid. */
.dashboard-tile .aetheus-grid-host {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
}

.dashboard-tile .aetheus-grid-host > .omni-data-grid {
    flex: 1 1 auto;
}

.dashboard-tile-title .omni-icon {
    color: var(--omni-color-accent);
}

/* The dashboard uses two equal table tiles per row on regular screens. the previous component
   responsive columns collapse them to one per row on small screens. A fixed shared
   card height keeps both rows aligned while the grids scroll internally. */

/* Server overview metrics row: 5 equal-width tiles (Heartbeat, CPU, Memory, Disk,
   Services) on one line. A 12-col grid can't split into 5, so use a CSS grid with
   equal fractions. Collapses to 2-up on tablet, 1-up on phones. align-items:stretch
   gives every tile (incl. the shorter Services card) the same height. */
.metric-tile-grid {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 1rem;
    align-items: stretch;
}

.metric-tile-grid > .omni-card,
.metric-tile-grid > .tile-link > .omni-card {
    height: 100%;
}

.storage-diagnostic-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 1rem;
}

.storage-diagnostic-grid > div {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

@media (max-width: 79.99rem) {
    .metric-tile-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .storage-diagnostic-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 39.99rem) {
    .metric-tile-grid {
        grid-template-columns: minmax(0, 1fr);
    }

    .storage-diagnostic-grid {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ---- Clickable metric tile (e.g. Services tile on server overview) ---- */
.tile-link {
    text-decoration: none;
    color: inherit;
    display: flex;
    height: 100%;
}

.tile-link .tile-clickable {
    width: 100%;
    cursor: pointer;
    transition: background-color 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
}

.tile-link:hover .tile-clickable {
    background: color-mix(in srgb, var(--omni-color-accent) 6%, var(--omni-color-surface, var(--omni-color-surface)));
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.18);
    border-color: var(--omni-color-accent);
}

/* Project Overview — Recent Activity list flush to the content edges (no L/R padding).
   legacy UI DataList applies padding via three layers: .omni-data-list, its >li children
   component padding and the >li margin-inline. Reset all three. */
.project-activity-flush .omni-data-list,
.project-activity-flush .omni-data-list {
    padding-left: 0;
    padding-right: 0;
    margin-left: 0;
    margin-right: 0;
}

.project-activity-flush .omni-data-list > li {
    padding-left: 0;
    padding-right: 0;
    margin-inline: 0;
    border-left: 0;
    border-right: 0;
}

/* The splash covers this short pre-handshake state. Keeping the not-yet-classified rail invisible
   also makes the cold-load contract robust when paint timing is unusually slow. */
.viewport-pending .omni-sidebar {
    visibility: hidden;
}

/* ==========================================================================
   Cron expression live validation
   ========================================================================== */

.cron-next-run {
    color: var(--omni-color-success, #28a745);
    font-family: 'Cascadia Code', 'Fira Code', monospace;
}

.cron-error {
    color: var(--omni-color-danger, #dc3545);
}

/* ==========================================================================
   Error Boundary Card
   ========================================================================== */

.error-card {
    max-width: 32rem;
    margin-top: 4rem;
}

.error-icon {
    font-size: 3rem;
    color: var(--omni-color-danger);
}

/* ==========================================================================
   Metric Bar / Icon Sizing
   ========================================================================== */

.metric-bar-thin {
    height: 0.375rem;
}

.icon-sm {
    font-size: var(--icon-lg);
}

/* ==========================================================================
   Skip-to-content link (F21)
   ========================================================================== */

.skip-link {
    position: absolute;
    top: -3rem;
    left: 1rem;
    z-index: 10000;
    padding: 0.5rem 1rem;
    background: var(--omni-color-accent, #1b6ec2);
    color: #fff;
    text-decoration: none;
    border-radius: 0 0 var(--aetheus-radius) var(--aetheus-radius);
    transition: top 0.2s;
}

.skip-link:focus {
    top: 0;
}

/* ==========================================================================
   Keyboard focus visibility (F-040, a11y)
   :focus-visible matches keyboard / programmatic focus only — not mouse clicks —
   so keyboard users get a clear ring without adding visual noise for pointer users.
   ========================================================================== */

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid var(--omni-color-accent, #1b6ec2);
    outline-offset: 2px;
}

/* ==========================================================================
   Theme toggle contrast fix (F18)
   ========================================================================== */

.theme-toggle-btn {
    color: #fff !important;
}

/* ==========================================================================
   Utility classes (extracted from inline styles)
   ========================================================================== */

/* Pre-formatted changelog/text block */
.text-block-pre {
    white-space: pre-wrap;
    font-size: 0.8125rem;
    margin: 0;
}

/* Mail service status indicator dot */
.mail-status-dot {
    background-color: var(--omni-color-danger);
}

.mail-status-dot.is-running {
    background-color: var(--omni-color-success);
}

.lang-toggle-wrapper {
    display: inline-flex;
    align-items: center;
    position: relative;
}

.lang-badge {
    font-size: 0.625rem;
    font-weight: 700;
    line-height: 1;
    color: #fff;
    background-color: var(--omni-color-accent);
    padding: 0.125rem 0.25rem;
    border-radius: var(--aetheus-radius);
    position: absolute;
    right: -0.125rem;
    bottom: 0.125rem;
    pointer-events: none;
}

/* ==========================================================================
   404 Not Found page
   ========================================================================== */

.not-found-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    min-height: 60vh;
    padding: 2rem;
}

.not-found-illustration {
    width: 16rem;
    max-width: 100%;
    height: auto;
    margin-bottom: 1.5rem;
}

/* ==========================================================================
   Mobile responsive fixes (F02)
   ========================================================================== */

@media (max-width: 39.99rem) {
    .omni-data-grid {
        overflow-x: auto;
    }

    .server-list-search,
    .pipeline-list-search {
        min-width: 0;
        width: 100%;
    }

    .pipeline-grid-fill {
        min-height: 0;
    }

    .pipeline-dependency-grid .pipeline-name-link,
    .pipeline-runs-grid .pipeline-name-link {
        white-space: normal;
        overflow-wrap: anywhere;
    }

    .pipeline-runs-grid .pipeline-run-tree-name {
        overflow: hidden;
        overflow-wrap: normal;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .project-list-heading > .project-list-create,
    .project-list-filters > .project-list-search-wrap,
    .project-list-filters > .omni-combo {
        flex: 1 0 100%;
        width: 100%;
        max-width: 100%;
    }

    .project-list-heading > .project-list-create {
        min-height: 2.5rem;
    }

    .project-list-search-wrap {
        min-width: 0;
    }

    .project-repo-url-input,
    .clone-url-input {
        min-width: 0;
    }

    .omni-page-header__actions > .omni-input,
    .omni-page-header__actions > .omni-text-box,
    .omni-page-header__actions > .omni-combo {
        flex: 1 1 8rem;
        min-width: 0;
        max-width: 100%;
    }

    /* The application name stays beside the logo on a phone too, in the bar and in the open drawer
       (recette R-380); only the user name gives up its room. */
    .omni-app-menu__trigger-name,
    .dev-env-chip-env:has(+ .dev-env-chip-label) {
        display: none;
    }

    .dev-env-banner {
        max-width: 35%;
    }
}

/* Help Center */
.help-search-box {
    max-width: 25rem;
    width: 100%;
}

.help-card {
    cursor: pointer;
    transition: box-shadow 0.2s ease, transform 0.15s ease;
    height: 100%;
}

.help-card:hover {
    box-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.15);
}

/* HCF4: visible keyboard-focus state on the help tiles (role="button" + tabindex="0"),
   mirroring the hover lift so keyboard users get the same affordance as the mouse. */
.help-card:focus-visible {
    outline: 0.125rem solid var(--omni-color-accent, #7c4dff);
    outline-offset: 0.125rem;
    box-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.15);
}

.help-card-icon {
    font-size: 1.5rem;
    opacity: 0.7;
}

.help-article-icon {
    font-size: 1.75rem;
    opacity: 0.7;
}

/* ==========================================================================
   Header Bar & User Menu (Astraia-style)
   ========================================================================== */

.header-bar {
    width: 100%;
    box-sizing: border-box;
    position: relative;
    height: 100%;
    /* No inline padding of its own: OmniHeader already pads the bar, and adding more pushed the burger
       and the logo well right of where the sidebar entries start (recette R-001). */
}

/* The burger sits in the sidebar's icon column (recette R-056): it cancels the app bar's start padding
   and is as wide as the rail, so its glyph is centred on the menu icons below it; the logo follows. */
.header-bar > .omni-sidebar-toggle:first-child {
    margin-inline-start: -12px;
    inline-size: calc(22px + 1.25rem);
    min-inline-size: 0;
    flex: none;
}

/* The phone drawer's own top row (recette R-042): same padding and height as the app bar it covers,
   so the cross lands exactly where the burger was. */
.sidebar-drawer-header {
    box-sizing: content-box;
    height: 2.25rem;
    padding: calc(var(--omni-item-pad-y) + 1px) 12px;
    border-block-end: var(--omni-border-width) solid var(--omni-color-border);
}

/* Recette R-391: the drawer's close cross is the package's square toggle (its hover fills a square),
   not the widened burger; shifted so its glyph stays centred where the burger's was. */
.sidebar-drawer-header > .omni-sidebar-toggle:first-child {
    inline-size: var(--omni-control-height);
    block-size: var(--omni-control-height);
    min-inline-size: var(--omni-control-height);
    margin-inline-start: calc((22px + 1.25rem - var(--omni-control-height)) / 2 - 12px);
}

.app-home-link {
    text-decoration: none;
    color: inherit;
    display: flex;
    align-items: center;
    gap: 0.375rem;
    /* Recette R-380: on a narrow phone bar crowded with indicators, the name yields (ellipsis) rather
       than pushing the header buttons out of the screen. */
    min-width: 0;
}

.app-header-icon {
    width: 1.5rem;
    height: 1.5rem;
    flex: none;
}

.app-title {
    cursor: pointer;
    margin: 0;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.header-icon {
    font-size: 1.125rem;
}

.header-action-btn {
    /* PLAN-003 lot 3: was 2rem, the only control on the page that was not the token. */
    min-height: var(--aetheus-control-height);
    min-width: var(--aetheus-control-height);
    height: var(--aetheus-control-height);
    width: var(--aetheus-control-height);
    box-sizing: border-box;
}

/* Backend connection indicator: wifi glyph coloured by SignalR link state. */
.header-conn-icon {
    font-size: 1.25rem;
    padding: 0 0.25rem;
    cursor: default;
    /* PLAN-003 lot 3: a bare glyph has no box of its own, so it never lined up with the buttons
       around it. Give it the token height and centre the glyph inside. */
    display: inline-flex;
    align-items: center;
    height: var(--aetheus-control-height);
}

.header-conn-online {
    color: var(--omni-color-success, #22c55e);
}

.header-conn-warning {
    color: var(--omni-color-warning, #f59e0b);
}

.header-conn-offline {
    color: var(--omni-color-danger, #ef4444);
}

/* The organisation picker, the application's own row of OE's application menu, fills its width. */
.user-menu-org-picker {
    width: 100%;
}

[data-omni-theme="dark"] .omni-button--ghost {
    color: var(--omni-color-text);
}

/* Dev-only top-bar banner (environment / label / branch) — never shown in prod.
   Absolutely centred on the header so it stays mid-bar regardless of the differing
   left (logo) and right (org picker + actions) widths; a single chip lands centred too.
   The container is click-through (pointer-events:none) so it never blocks header
   controls underneath; the chips re-enable pointer events for their tooltips. */
.dev-env-banner {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    max-width: 60%;
    overflow: hidden;
    pointer-events: none;
    z-index: 1;
}

.dev-env-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    max-width: 18rem;
    /* PLAN-003 lot 3: sits in the top bar next to the action buttons, so it stands at the token. */
    height: var(--aetheus-control-height);
    box-sizing: border-box;
    padding: 0.125rem 0.5rem;
    border-radius: 1rem;
    font-size: 0.75rem;
    line-height: 1.4;
    white-space: nowrap;
    pointer-events: auto;
    border: 1px solid var(--omni-color-border);
    background: var(--omni-color-surface-muted);
    color: var(--omni-color-text);
}

.dev-env-chip-icon {
    font-size: 0.875rem;
    opacity: 0.85;
}

.dev-env-chip-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dev-env-chip-env {
    border-color: var(--omni-color-accent, #3b82f6);
    background: color-mix(in srgb, var(--omni-color-accent) 14%, var(--omni-color-surface));
    color: color-mix(in srgb, var(--omni-color-accent) 40%, var(--omni-color-text));
}

.dev-env-chip-label {
    border-color: var(--omni-color-warning, #f59e0b);
    background: color-mix(in srgb, var(--omni-color-warning) 14%, var(--omni-color-surface));
    color: color-mix(in srgb, var(--omni-color-warning) 40%, var(--omni-color-text));
    font-weight: 600;
}

/* Tight headers drop the long branch chip first; the environment chip always stays. */
@media (max-width: 63.99rem) {
    .dev-env-chip-branch {
        display: none;
    }
}

@media (max-width: 39.99rem) {
    .dev-env-banner {
        max-width: 35%;
    }
}

/* Recette R-191/R-192: the header menus are OmniPopovers. Their panel carries the frame (background,
   border, shadow) and closes on a click outside, so no veil covers the page any more; the content
   brings its own padding. */
.header-popover .omni-popover__panel {
    padding: 0;
    min-inline-size: 0;
    max-inline-size: calc(100vw - 1rem);
    overflow: hidden;
    z-index: 1000;
}

.user-menu-caption {
    font-size: 0.75rem;
}

/* ==========================================================================
   Admin Tile Cards
   ========================================================================== */

/* Recette R-456: compact OE settings tiles (icon, title, description under it), as many per row as
   the width holds; the whole tile is the link. */
.admin-tile-grid {
    display: grid;
    gap: var(--omni-space-sm);
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
    margin-bottom: var(--omni-space-xl, 2rem);
}

.admin-tile-link {
    text-decoration: none;
    color: inherit;
    display: block;
    height: 100%;
}

.admin-tile-link > .omni-settings-tile {
    height: 100%;
}

.admin-tile-link:hover .omni-settings-tile__title,
.admin-tile-link:focus-visible .omni-settings-tile__title {
    text-decoration: underline;
}

.color-primary {
    color: var(--omni-color-accent);
}

.color-success {
    color: var(--omni-color-success);
}

.color-info {
    color: var(--omni-color-accent);
}

.color-warning {
    color: var(--omni-color-warning);
}

.color-danger {
    color: var(--omni-color-danger);
}

/* WCAG AA contrast for the legacy UI dropdown/select placeholder text. the previous component default placeholder color
   is the disabled token (rgba .5 on dark), which axe flags at 3.17:1 (< 4.5:1) on the #1e1e1e surface.
   The secondary token is theme-aware and AA-compliant in both the light and dark legacy UI themes. */
.omni-combo .omni-input::placeholder,
.omni-input::placeholder.omni-combo__input {
    color: var(--omni-color-text-muted);
}

/* Inherited-from badge for cross-access resources */
.inherited-badge {
    font-size: 0.7rem;
    opacity: 0.8;
}

/* Small badge variant for inline use */
.badge-xs .omni-badge {
    font-size: 0.6rem;
    padding: 0.05rem 0.35rem;
}

/* Site-wide fine badge baseline (hors tableaux/grilles), aligned with the compact 1.75rem button
   and input heights above so a badge sitting next to a button or input reads at the same scale
   instead of the previous component bulkier default. */
.omni-badge {
    font-size: 0.7rem;
    padding: 0.15rem 0.45rem;
    /* One badge height everywhere, page headers, tiles and grids alike (recette R-078): the OE badge
       height. The control token (28px) made header badges taller than the 22px grid ones, and an
       icon badge landed at 26.8px. It centres its own text instead of being sized by its font.
       Recette R-345: that height is OE's --omni-badge-height, read on the badge itself, the token
       OE also gives the icon-only buttons of a grid row, so a row's badges and actions stay equal. */
    height: var(--omni-badge-height);
    min-height: var(--omni-badge-height);
    min-width: var(--omni-badge-height);
    display: inline-flex;
    align-items: center;
    box-sizing: border-box;
}

/* Alias of OE's badge height for the rules that align with a badge (run tiles, grade letters). */
:root { --aetheus-badge-height: var(--omni-badge-height); }

/* A grid cell caps its children at the content height; a badge keeps the one badge height there
   too, which the 2.25rem row has room for. */
.omni-data-grid tr[data-omni-row-index] > td > .omni-badge,
.omni-data-grid tr[data-omni-row-index] .omni-badge {
    max-height: none;
}

/* Column chooser popup (server list) */
.column-chooser-wrap {
    position: relative;
    display: inline-block;
}
/* Legacy UICard/.omni-card tie: the popup is selected through its wrapper so it wins whatever the cascade order. */
.column-chooser-wrap > .column-chooser-popup {
    position: absolute;
    top: 2.5rem;
    right: 0;
    z-index: 20;
    min-width: 12rem;
    padding: 0.75rem;
    box-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.3);
}

/* Pipeline runner toggle pulse animation */
@keyframes runner-pulse {
    0% { box-shadow: 0 0 0 0 rgba(63, 185, 80, 0.4); }
    70% { box-shadow: 0 0 0 0.5rem rgba(63, 185, 80, 0); }
    100% { box-shadow: 0 0 0 0 rgba(63, 185, 80, 0); }
}
.pipeline-runner-icon-on {
    animation: runner-pulse 1s ease-out;
}

/* Agent update progress phase chevron */
.agent-update-phase-active {
    color: var(--omni-color-accent);
    animation: pulse-dot 1.5s ease-in-out infinite;
}
.agent-update-phase-done { color: var(--omni-color-success); }
.agent-update-phase-failed { color: var(--omni-color-danger); }

/* Focus-visible: exclude legacy UI components that already have built-in focus rings */
.omni-button:focus-visible,
.omni-text-box:focus-visible,
.omni-combo:focus-visible,
.omni-input:focus-visible {
    outline: none;
}

.color-secondary {
    color: var(--omni-color-text-muted);
}

/* Message cell: truncate to a single line; the full text (and any exception) is reachable via the
   row's "view message" action button. */
.log-message-cell {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.correlation-id-text {
    font-size: 0.8rem;
    font-family: var(--omni-font-family-monospace, monospace);
    word-break: break-all;
}

/* ==========================================================================
   Alert Badge
   ========================================================================== */

.alert-badge-dot {
    font-size: 0.7rem;
    min-width: 1.25rem;
    height: 1.25rem;
    line-height: 1.25rem;
    padding: 0 0.25rem;
    border-radius: 999rem; /* D25 exception: a counter dot is round, not a box */
    animation: pulse-dot 2s infinite;
}

/* ==========================================================================
   Button Utilities
   ========================================================================== */

.btn-fit {
    width: fit-content;
}

.settings-form {
    max-width: 30rem;
}

/* Recette R-232: the appearance settings, one section for the look and one for the language; the
   selects keep one width so the tiles line up. Recette R-448: the sections take the whole width (the
   former 48rem cap is gone) and their tiles go two per row, each half the card, stacked when a half
   no longer holds 24rem; the notification preferences follow the same grid. */
.settings-pair {
    display: grid;
    gap: var(--omni-space-sm);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, max(24rem, calc((100% - var(--omni-space-sm)) / 2))), 1fr));
}

.settings-appearance-select {
    min-width: 12rem;
}

/* Recette R-232: the density of the whole site. Comfortable is the look as shipped and carries no
   attribute; the two others move the Aetheus sizes (PLAN-003 lot 3 and lot 4) by one step each way,
   and the OmniEurope.Blazor spacing follows through data-omni-density on the same element. */
/* Recette R-309: one control height. OE sizes the controls this sheet does not name (selects, split
   buttons, pickers, pager...) from --omni-control-height; it now reads the Aetheus height, so no OE
   control stands taller than the button beside it. html:root outweighs OE's :root and
   [data-omni-density] (measured: a bare html selector lost, the bell stayed 36px wide). */
html:root,
html:root[data-omni-density] {
    --omni-control-height: var(--aetheus-control-height);
}

html[data-aetheus-density="compact"] {
    --aetheus-control-height: calc(1.5rem * var(--omni-control-scale, 1));
    --aetheus-grid-row-content-height: 1.125rem;
    --aetheus-grid-row-height: 2rem;
}

html[data-aetheus-density="spacious"] {
    --aetheus-control-height: calc(2.125rem * var(--omni-control-scale, 1));
    --aetheus-grid-row-content-height: 1.5rem;
    --aetheus-grid-row-height: 2.75rem;
}

/* ==========================================================================
   Monaco Viewer (read-only)
   ========================================================================== */

.monaco-viewer-container {
    width: 100%;
    height: 25rem;
    border: 1px solid var(--omni-color-border);
    border-radius: var(--omni-radius);
}

/* O: SVG commit lane graph (replaces the ASCII .commit-graph <pre>) */
.commit-graph-svg-wrap {
    overflow: auto;
    max-height: 36rem;
    background: var(--omni-color-surface);
    border: 1px solid var(--omni-color-border);
    border-radius: var(--omni-radius);
}
.git-svg-graph { display: flex; align-items: flex-start; min-width: fit-content; }
.git-svg-lanes { flex: 0 0 auto; }
.git-svg-rows { flex: 1 1 auto; min-width: 0; }
.git-svg-row { height: 2.125rem; display: flex; align-items: center; gap: 0.625rem; padding: 0 0.5rem; }
.git-svg-row:hover { background: var(--omni-color-surface-hover); }
.git-graph-sha { font-family: "Cascadia Code", "Fira Code", "Consolas", monospace; font-size: 0.8125rem; color: var(--omni-color-accent, #7c4dff); flex: 0 0 auto; }
.git-graph-subject { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto; min-width: 0; }
.git-graph-meta { font-size: 0.75rem; color: var(--omni-color-text-muted, #aaa); flex: 0 0 auto; white-space: nowrap; }

/* Commit-detail unified diff viewer. Reuses the blame monospace frame; lines are colour-coded
   by their leading git-diff marker (+ add, - del, @@ hunk header). */
.diff-view {
    font-family: "Cascadia Code", "Fira Code", "Consolas", monospace;
    font-size: 0.8125rem;
    line-height: 1.4;
    padding: 0.5rem 0;
    overflow-x: auto;
    max-height: 40rem;
    overflow-y: auto;
    background: var(--omni-color-surface);
    border: 1px solid var(--omni-color-border);
    border-radius: var(--omni-radius);
    margin: 0;
}
.diff-line { display: block; white-space: pre; padding: 0 0.75rem; }
.diff-line-add { background: rgba(46, 160, 67, 0.18); }
.diff-line-del { background: rgba(248, 81, 73, 0.18); }
.diff-line-hunk { background: var(--omni-color-accent-subtle); color: var(--omni-color-text-muted); }
.diff-file-stat-add { color: var(--omni-color-success); }
.diff-file-stat-del { color: var(--omni-color-danger); }

/* Commit detail: compact metadata and a focused explorer keep large patches readable without
   stacking one full diff card per file down the page. */
.git-commit-summary-content {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 1.25rem;
}

.git-commit-overview {
    min-width: 0;
}

.git-commit-message {
    margin: 0 0 1rem;
}

.git-commit-metadata {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem 1.5rem;
}

.git-commit-stats {
    display: grid;
    grid-template-columns: repeat(3, minmax(5rem, 1fr));
    align-self: stretch;
}

.git-commit-stat {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 0.75rem 1rem;
    text-align: center;
}

.git-commit-stat:not(:last-child) {
    border-inline-end: 0.0625rem solid var(--aetheus-tile-separator);
}

.git-commit-stat strong {
    font-family: "Cascadia Code", "Fira Code", "Consolas", monospace;
    font-size: 1.25rem;
    font-weight: 600;
}

.git-commit-stat span {
    margin-top: 0.25rem;
    color: var(--omni-color-text-muted);
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

.git-diff-workspace {
    display: grid;
    grid-template-columns: minmax(17rem, 20rem) minmax(0, 1fr);
    height: clamp(30rem, 64vh, 44rem);
    min-height: 0;
    overflow: hidden;
    border: 0.0625rem solid var(--aetheus-tile-separator);
    border-radius: var(--omni-radius);
    background: var(--omni-color-surface, var(--omni-color-surface));
    box-shadow: var(--omni-shadow-md);
}

.git-file-browser {
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    border-inline-end: 0.0625rem solid var(--aetheus-tile-separator);
    background: var(--omni-color-surface-muted);
}

.git-file-browser-header {
    flex: 0 0 auto;
    padding: 0.75rem;
    border-bottom: 0.0625rem solid var(--aetheus-tile-separator);
}

.git-file-browser-title {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    margin-bottom: 0.625rem;
}

.git-file-browser-title > span {
    color: var(--omni-color-text-muted);
    font-family: "Cascadia Code", "Fira Code", "Consolas", monospace;
    font-size: 0.75rem;
}

.git-file-search {
    width: 100%;
}

.git-file-status-filters {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.25rem;
    margin-top: 0.625rem;
}

.git-file-status-filter {
    color: var(--omni-color-text-muted);
}

.git-file-status-filter.is-active {
    color: var(--omni-color-accent-strong);
    background: var(--omni-color-accent-subtle);
    outline: 0.0625rem solid color-mix(in srgb, var(--omni-color-accent) 40%, transparent);
    outline-offset: -0.0625rem;
}

.git-file-listbox {
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    border: 0;
    border-radius: 0;
    background: transparent;
}

.git-file-listbox .omni-list-box,
.git-file-listbox .omni-list-box {
    height: 100%;
    max-height: none;
}

.git-file-listbox .omni-list-box option {
    min-height: 2.75rem;
    display: flex;
    align-items: center;
    padding: 0.375rem 0.625rem;
    border-radius: 0;
}

.git-file-listbox .omni-list-box option.omni-data-grid__row--selected {
    color: var(--omni-color-text);
    background: var(--omni-color-accent-subtle);
}

.git-file-option {
    width: 100%;
    min-width: 0;
    display: grid;
    grid-template-columns: 1.25rem minmax(0, 1fr) auto;
    align-items: center;
    gap: 0.5rem;
}

.git-file-status-icon {
    font-size: 1rem;
}

.git-file-option-path {
    min-width: 0;
    overflow: hidden;
    color: inherit;
    font-size: 0.75rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.git-file-option-delta {
    display: flex;
    gap: 0.375rem;
    font-family: "Cascadia Code", "Fira Code", "Consolas", monospace;
    font-size: 0.6875rem;
}

.git-file-browser-empty {
    padding: 1.5rem 0.75rem;
    color: var(--omni-color-text-muted);
    font-size: 0.8125rem;
    text-align: center;
}

.git-focused-diff {
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    background: var(--omni-color-surface);
}

.git-focused-diff-header {
    flex: 0 0 auto;
    min-height: 4.5rem;
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem 1rem;
    border-bottom: 0.0625rem solid var(--aetheus-tile-separator);
    background: var(--omni-color-surface, var(--omni-color-surface));
}

.git-focused-diff-title {
    min-width: 0;
    flex: 1 1 auto;
}

.git-focused-diff-title > code {
    display: block;
    overflow: hidden;
    color: var(--omni-color-text);
    font-size: 0.8125rem;
    font-weight: 600;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.git-focused-diff-title > div {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-top: 0.25rem;
    font-family: "Cascadia Code", "Fira Code", "Consolas", monospace;
    font-size: 0.75rem;
}

.git-focused-diff-body {
    flex: 1 1 auto;
    height: 100%;
    max-height: none;
    margin: 0;
    padding: 0.5rem 0;
    border: 0;
    border-radius: 0;
}

.git-focused-diff-body .diff-line {
    min-width: max-content;
    display: grid;
    grid-template-columns: 3.5rem 3.5rem minmax(40rem, 1fr);
    padding: 0;
}

.diff-line-number {
    padding: 0 0.625rem;
    border-inline-end: 0.0625rem solid color-mix(in srgb, var(--omni-color-text) 70%, transparent);
    color: var(--omni-color-text-muted);
    background: var(--omni-color-surface-muted);
    text-align: end;
    user-select: none;
}

.diff-line-content {
    padding: 0 0.75rem;
    white-space: pre;
}

@media (max-width: 79.99rem) {
    .git-commit-summary-content {
        grid-template-columns: minmax(0, 1fr);
    }

    .git-commit-stats {
        border-block-start: 0.0625rem solid var(--aetheus-tile-separator);
    }
}

@media (max-width: 39.99rem) {
    .git-commit-metadata {
        grid-template-columns: minmax(0, 1fr);
    }

    .git-commit-stat {
        padding: 0.75rem 0.375rem;
    }

    .git-commit-stat strong {
        font-size: 1rem;
    }

    .git-diff-workspace {
        display: block;
        height: auto;
        overflow: visible;
        box-shadow: none;
    }

    .git-file-browser {
        height: 20rem;
        border-block-end: 0.0625rem solid var(--aetheus-tile-separator);
        border-inline-end: 0;
    }

    .git-file-listbox .omni-list-box option {
        min-height: 2.75rem;
    }

    .omni-button.git-file-status-filter {
        min-height: var(--aetheus-control-height);
    }

    .git-focused-diff {
        height: 32rem;
    }

    .git-focused-diff-header {
        align-items: flex-start;
    }

    .omni-button.git-diff-toggle {
        min-width: var(--aetheus-control-height);
        min-height: var(--aetheus-control-height);
    }

    .git-diff-toggle .omni-button__content {
        display: none;
    }
}

/* F26: width utility classes (replace inline Style="width:NNNpx") */
.w-rem-6 { width: 6rem; }      /* ~96px */
.w-rem-7-5 { width: 7.5rem; }  /* 120px */
.w-rem-8-75 { width: 8.75rem; }/* 140px */
.w-rem-10 { width: 10rem; }    /* 160px */
.w-rem-11 { width: 11.25rem; } /* 180px */
.w-rem-12-5 { width: 12.5rem; }/* 200px */
.w-rem-15 { width: 15rem; }    /* 240px */
.w-rem-15-625 { width: 15.625rem; } /* 250px */
.w-rem-16 { width: 16.25rem; } /* 260px */
.w-rem-37-5 { max-width: 37.5rem; } /* 600px */
.w-rem-34-375 { max-width: 34.375rem; } /* 550px */
.minw-rem-15 { min-width: 15rem; } /* 240px */
.h-progress-thin { height: 0.375rem; } /* 6px */
.fs-13 { font-size: 0.8125rem; }

/* F26: stage node positioning via CSS custom properties */
.stage-node-positioned {
    left: var(--node-x, 0);
    top: var(--node-y, 0);
}

/* ==========================================================================
   F-25 utility classes (replace inline Style="font-size:..." etc.)
   ========================================================================== */

.icon-xxs { font-size: 0.6875rem; vertical-align: middle; }
.icon-xs-app { font-size: 0.85rem; }
.icon-sm-app { font-size: 0.875rem; }
.icon-input-sm { font-size: 0.8125rem; }
.icon-md-app { font-size: 1rem; }
.icon-3xl { font-size: 3rem; }
.icon-3xl-success { font-size: 3rem; color: var(--omni-color-success); }
.icon-3xl-secondary { font-size: 3rem; color: var(--omni-color-text-muted); }
.icon-3xl-disabled { font-size: 3rem; color: var(--omni-color-text-muted); }
.icon-warn-sm { font-size: 0.875rem; color: var(--omni-color-warning); }
.text-mono { font-family: 'Cascadia Code', 'Fira Code', monospace; }
.text-mono-sm { font-family: 'Cascadia Code', 'Fira Code', monospace; font-size: 0.8125rem; }
/* OE 1.2.0 puts an OmniTextArea's Class on its wrapper; the text area sets its own control size. */
.text-mono-sm .omni-text-area__input { font-size: 0.8125rem; }
.flex-1 { flex: 1; }
.text-num-cell { min-width: 1.5rem; text-align: right; }
.text-input-flex { font-size: 0.8125rem; flex: 1; }
.font-size-09 { font-size: 0.9rem; }

/* ==========================================================================
   F-25 utility classes (replace inline Style="font-size:..." etc.)
   ========================================================================== */

.icon-xxs { font-size: 0.6875rem; vertical-align: middle; }
.icon-xs-app { font-size: 0.85rem; }
.icon-sm-app { font-size: 0.875rem; }
.icon-input-sm { font-size: 0.8125rem; }
.icon-md-app { font-size: 1rem; }
.icon-3xl { font-size: 3rem; }
.icon-3xl-success { font-size: 3rem; color: var(--omni-color-success); }
.icon-3xl-secondary { font-size: 3rem; color: var(--omni-color-text-muted); }
.icon-3xl-disabled { font-size: 3rem; color: var(--omni-color-text-muted); }
.icon-warn-sm { font-size: 0.875rem; color: var(--omni-color-warning); }
.text-mono { font-family: 'Cascadia Code', 'Fira Code', monospace; }
.text-mono-sm { font-family: 'Cascadia Code', 'Fira Code', monospace; font-size: 0.8125rem; }
.flex-1 { flex: 1; }
.text-num-cell { min-width: 1.5rem; text-align: right; }
.text-input-flex { font-size: 0.8125rem; flex: 1; }
.font-size-09 { font-size: 0.9rem; }

/* ==========================================================================
   F-25 utility classes (replace inline Style="font-size:..." etc.)
   ========================================================================== */

.icon-xxs { font-size: 0.6875rem; vertical-align: middle; }
.icon-xs-app { font-size: 0.85rem; }
.icon-sm-app { font-size: 0.875rem; }
.icon-input-sm { font-size: 0.8125rem; }
.icon-md-app { font-size: 1rem; }
.icon-3xl { font-size: 3rem; }
.icon-3xl-success { font-size: 3rem; color: var(--omni-color-success); }
.icon-3xl-secondary { font-size: 3rem; color: var(--omni-color-text-muted); }
.icon-3xl-disabled { font-size: 3rem; color: var(--omni-color-text-muted); }
.icon-warn-sm { font-size: 0.875rem; color: var(--omni-color-warning); }
.text-mono { font-family: 'Cascadia Code', 'Fira Code', monospace; }
.text-mono-sm { font-family: 'Cascadia Code', 'Fira Code', monospace; font-size: 0.8125rem; }
.flex-1 { flex: 1; }
.text-num-cell { min-width: 1.5rem; text-align: right; }
.text-input-flex { font-size: 0.8125rem; flex: 1; }
.font-size-09 { font-size: 0.9rem; }

/* Enforce a consistent, compact baseline height on all legacy UI buttons so mixed sizes within
   the same action row never produce mismatched heights. Lowered from 2.25rem (36px) - which
   forced even small buttons up to 36px and read as too tall - to 1.75rem (28px); md buttons also
   get tighter vertical padding so their intrinsic height drops to the same 28px floor.
   (In-grid action buttons keep their own tighter override above.) */
/* WCAG 2.5.8 (AA) target-size minimum is 24x24 CSS px; a compact icon-only button with no text
   label had no floor on its width, so it could shrink below that. min-width mirrors min-height so
   the square case (icon-only) never drops under the 24px minimum. */
.omni-button {
    min-height: var(--aetheus-control-height);
    min-width: var(--aetheus-control-height);
    height: var(--aetheus-control-height);
    box-sizing: border-box;
}
.omni-button.omni-button--medium { padding-top: 0.25rem; padding-bottom: 0.25rem; }
/* Recette R-215 / R-231 / R-232: a select bar (theme mode, density, pipelines views) is a row of
   controls, so each option keeps the one control height of the site; OE draws it half a rem taller. */
.omni-select-bar__item {
    min-height: var(--aetheus-control-height);
    height: var(--aetheus-control-height);
    padding-block: 0;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}

/* Match text inputs and dropdowns to the same 1.75rem compact baseline as buttons, so a button
   next to an input (search bars, filter rows, inline forms) lines up instead of the input reading
   taller than its neighbouring buttons. Grid-embedded inputs (filter row inside a datatable) are
   already governed by the grid's own row-height rules and are excluded here. */
.omni-text-box,
.omni-input,
.omni-input input,
.omni-combo {
    /* Was a floor only, so the theme padding still rendered these at 40px: twelve pixels taller
       than the reference button standing next to them. The token is now the height itself. The
       numeric wrapper is listed too: the theme sizes it 2.5rem, and an input at the token inside a
       40px wrapper still made the field 40px (port registry form, measured 2026-09-11). */
    min-height: var(--aetheus-control-height);
    height: var(--aetheus-control-height);
    box-sizing: border-box;
}

/* A grid keeps its own geometry: the filter row inside a table is sized by the row rules, not by
   the page-level control token. */
.omni-data-grid .omni-text-box,
.omni-data-grid .omni-input,
.omni-data-grid .omni-input input,
.omni-data-grid .omni-combo {
    height: auto;
}

/* A text area grows with its content; forcing the one-line control height on it would cut the text
   off at the first line. */
textarea.omni-text-box,
.omni-text-area__input {
    height: auto;
}
/* The native dropdown (select.omni-drop-down) is listed too: it kept the theme's 7px padding inside
   the 28px token, 12px of box for a 14px line, and every filter select was drawn cut (recette R-026).
   The password field had the same 7px and the same cut (login page, 2026-09-21). */
.omni-text-box,
.omni-input input,
.omni-drop-down,
.omni-password__input {
    padding-top: 0.25rem;
    padding-bottom: 0.25rem;
}
/* The theme pads the dropdown box 7px top and bottom and its label 4px more, around a 24px line:
   at the 28px token that left 12px of a 32px label, and the selected value was drawn cut in half
   (measured 2026-09-11 on /projects and in the run dialog). The box takes no vertical padding and
   the label is one line as tall as the box's inside. */
.omni-combo {
    padding-top: 0;
    padding-bottom: 0;
}
.omni-combo .omni-combo__input,
.omni-combo .omni-input {
    padding-top: 0;
    padding-bottom: 0;
    line-height: calc(var(--aetheus-control-height) - 2px);
}
.omni-data-grid .omni-combo .omni-combo__input,
.omni-data-grid .omni-combo .omni-input {
    line-height: normal;
}

/* Split button ("Run" + "Run with options" chevron): legacy UI renders the main action and the
   menu toggle as two sibling buttons, which by default show as two separate pill-shaped buttons
   with a visible gap - reads as clumsy. Fuse them into one pill: square off the touching edge on
   each side, drop the gap, and add a subtle divider on the chevron so the two zones stay legible
   as separate targets without looking like two unrelated buttons. Matches the compact 1.75rem
   button baseline above. */
.omni-split-button {
    display: inline-flex;
    gap: 0;
}
.omni-split-button .omni-button {
    border-radius: 0;
}
.omni-split-button .omni-button:first-child {
    border-start-start-radius: var(--omni-radius, 0.25rem);
    border-end-start-radius: var(--omni-radius, 0.25rem);
}
.omni-split-button .omni-split-button__toggle {
    border-start-end-radius: var(--omni-radius, 0.25rem);
    border-end-end-radius: var(--omni-radius, 0.25rem);
    border-inline-start: 1px solid var(--omni-color-text-muted, #444);
    /* Recette R-309 / R-320: the chevron is an icon-only button, so a square of the control height. */
    /* Recette R-413: in a grid row OE sets the split button to the badge height (--omni-button-size),
       and the chevron stays a square of that height beside the other row actions. */
    inline-size: var(--omni-button-size, var(--aetheus-control-height));
    min-width: var(--omni-button-size, var(--aetheus-control-height));
    padding-inline: 0;
}

/* Badge alignment in header title rows (OmniPageHeader badges + the run toolbar): a badge
   is sometimes wrapped in an <a> (click-through) or <span> (tooltip), and that inline wrapper is
   taller than the badge, leaving it ~1px off the bare badges next to it. Make such wrappers hug the
   badge so every badge centers identically on the title baseline, wherever this pattern appears. */
.omni-page-header a:has(> .omni-badge),
.omni-page-header span:has(> .omni-badge),
.pipeline-run-toolbar a:has(> .omni-badge),
.pipeline-run-toolbar span:has(> .omni-badge),
.detail-page-header a:has(> .omni-badge),
.detail-page-header span:has(> .omni-badge) {
    display: inline-flex;
    align-items: center;
}

/* Non-:has() fallback (Firefox < 121 ignores the whole rule above, since an unsupported selector in
   a comma-separated list invalidates that list per the CSS spec pre-:has() adoption): vertical-align
   middle on the badge itself approximates the same centered look against its wrapper's text baseline
   without needing to select the wrapper at all, so it degrades gracefully in every browser. */
.omni-badge { vertical-align: middle; }

/* F-48 — Remove forced uppercase styling from legacy UI Material buttons and tab titles
   for a more modern look that respects user-supplied casing. */
.omni-button .omni-button__content,
.omni-tabs__tab .omni-tabs__title {
    text-transform: none;
}

/* Keep every button label on a single line. Without this, long French labels
   (e.g. "Contacter l'agent", "Mettre à jour l'agent") wrap to two lines and
   produce mismatched heights inside the same action row — sibling buttons with
   shorter labels then look visually crushed between the wrapped ones. */
.omni-button .omni-button__content {
    white-space: nowrap;
}

/* Item #11 — Pipeline runner opt-in card: a glanceable status icon on the overview.
   Green play when armed, muted pause when locked down. */
.pipeline-runner-icon-on  { font-size: 2rem; color: var(--omni-color-success); }
.pipeline-runner-icon-off { font-size: 2rem; color: var(--omni-color-text-muted); opacity: 0.7; }

/* Item #3 — Agent self-update progress card: live indicator on the server detail page.
   Phase-aware coloring so a successful update lands green, a failure lands red, and the
   in-progress phases stay info-blue without visually alarming the operator. */
.agent-update-progress-card { transition: opacity 0.4s ease-out; }
.agent-update-progress-card.is-fading-out { opacity: 0; }
.agent-update-phase-active  { font-size: 1.25rem; color: var(--omni-color-accent); }
.agent-update-phase-done    { font-size: 1.25rem; color: var(--omni-color-success); }
.agent-update-phase-failed  { font-size: 1.25rem; color: var(--omni-color-danger); }

.notification-log-link {
    color: inherit;
    font-weight: 600;
    text-decoration: underline;
}

/* PLAN-005 lot 2 / D32: the detail of a toast stops at four lines; the title above it is whole. */
.notification-body-text {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 4;
    line-clamp: 4;
    overflow: hidden;
    overflow-wrap: anywhere;
}
.notification-body-more.omni-button { padding-inline: 0; margin-top: 0.125rem; }
.notification-detail-text { white-space: pre-wrap; overflow-wrap: anywhere; }

/* Item #7 — top-bar task tracker widget. Sits between the help button and the user menu in the
   header, shows the number of in-flight tasks in place of its icon, opens a fixed-width popover on
   click. The backdrop covers the viewport so a click anywhere outside closes the popover. */
.task-tracker-container { position: relative; }
/* PLAN-005 lot 1 / D31: the same square as every header button (.header-action-btn sets it to the
   token); the number replaces the icon inside it and is sized so "99+" fits without widening it. */
.task-tracker-btn.omni-button { padding: 0; justify-content: center; }
.task-tracker-count {
    font-size: 0.8125rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    color: var(--omni-color-accent);
    white-space: nowrap;
}
.task-tracker-popover {
    width: 22rem; max-width: 100%; max-height: 30rem;
    display: flex; flex-direction: column;
}
.task-tracker-header {
    display: flex; align-items: center; justify-content: space-between;
    border-bottom: 1px solid var(--omni-color-border);
}
.task-tracker-list {
    flex: 1 1 auto; overflow-y: auto; min-height: 4rem;
}
.task-tracker-empty {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    padding: 2rem 1rem; gap: 0.5rem;
}
.task-tracker-empty-icon {
    font-size: 2rem; color: var(--omni-color-text-muted); opacity: 0.5;
}
.task-tracker-row {
    display: flex; align-items: center; gap: 0.75rem;
    padding: 0.5rem 1rem;
    color: inherit; text-decoration: none;
    border-bottom: 1px solid var(--omni-color-surface-highlight);
}
.task-tracker-row:last-child { border-bottom: none; }
/* Recette R-193/R-206: rows alternate like the grids, and the hover is the surface hover token in both
   themes (dark mode used the text colour as a background, light grey under grey text). */
/* Recette R-298: every row alternates (the rule named only links, so a task row that is not a link
   never took it), with a tint read from the text colour so it shows in the dark theme too. */
.task-tracker-row:nth-of-type(even) { background: color-mix(in srgb, var(--omni-color-text) 6%, transparent); }
.task-tracker-row:hover,
.task-tracker-row:nth-of-type(even):hover { background: var(--omni-color-surface-hover); }
.task-tracker-row-body { flex: 1 1 auto; min-width: 0; }
.task-tracker-row-name {
    font-size: 0.875rem; font-weight: 500;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.task-tracker-row-meta {
    font-size: 0.75rem; color: var(--omni-color-text-muted);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.task-tracker-icon-running  { color: var(--omni-color-accent); }
.task-tracker-icon-assigned { color: var(--omni-color-warning); }
.task-tracker-icon-pending  { color: var(--omni-color-text-muted); }
.task-tracker-footer {
    border-top: 1px solid var(--omni-color-border);
    text-align: center;
}
.task-tracker-footer a {
    color: var(--omni-color-accent); font-size: 0.85rem; text-decoration: none;
}
.task-tracker-footer a:hover { text-decoration: underline; }

/* S-UX-08: grouped view when count > 5 */
.task-tracker-group-header {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.4rem 1rem 0.2rem;
    background: var(--omni-color-surface-muted);
    border-bottom: 1px solid var(--omni-color-surface-highlight);
    position: sticky; top: 0; z-index: 1;
}
.task-tracker-group-icon { font-size: 1rem; color: var(--omni-color-text-muted); }
.task-tracker-group-name {
    font-size: 0.8125rem; font-weight: 600;
    flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.task-tracker-group-badge { font-size: 0.6875rem; }
.task-tracker-row-grouped { padding-left: 2rem; }

.omni-task-tracker .task-tracker-btn.omni-button { padding: 0; justify-content: center; }
.omni-task-tracker .task-tracker-btn .omni-button__content { display: flex; align-items: center; justify-content: center; gap: 0.25rem; }
/* Recette R-170: the count follows the icon in the button's own ink, and the button doubles its width
   while it has one, instead of the number replacing the icon in a fixed square. */
.omni-task-tracker .task-tracker-count { color: inherit; }
.omni-task-tracker .header-action-btn.task-tracker-btn-wide { width: calc(2 * var(--aetheus-control-height)); }
.omni-task-tracker .task-tracker-header { padding: 0.75rem 1rem; border-color: var(--omni-color-border); }
/* The count is a square badge of the common badge height (recette R-076): a narrow side padding, the
   minimum width being the height, so one digit is 22x22 and more digits only widen it. */
.omni-task-tracker .task-tracker-header .omni-badge { padding-inline: 0.25rem; }
.omni-task-tracker .task-tracker-footer { padding: 0.5rem 1rem; border-color: var(--omni-color-border); }
.omni-task-tracker .task-tracker-footer a { color: var(--omni-color-accent); }
.omni-task-tracker .task-tracker-row { border-color: var(--omni-color-border); }
/* Recette R-020: while tasks are in flight the button pulses (a ring breathing out), so the count is
   noticed without opening the list. Off for readers who ask for reduced motion. Recette R-386: the
   ring is the button's own fill, so the approvals twin pulses in its own colour, not with the tasks' blue. */
.omni-task-tracker .task-tracker-btn-busy { animation: task-tracker-pulse 1.6s ease-out infinite; }
@keyframes task-tracker-pulse {
    0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--omni-button-fill, var(--omni-color-accent)) 55%, transparent); }
    70% { box-shadow: 0 0 0 0.5rem color-mix(in srgb, var(--omni-button-fill, var(--omni-color-accent)) 0%, transparent); }
    100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--omni-button-fill, var(--omni-color-accent)) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
    .omni-task-tracker .task-tracker-btn-busy { animation: none; }
}
.omni-task-tracker .task-tracker-icon-running { color: var(--omni-color-accent); }
.omni-task-tracker .task-tracker-icon-assigned { color: var(--omni-color-warning); }
.omni-task-tracker .task-tracker-icon-pending,
.omni-task-tracker .task-tracker-empty-icon,
.omni-task-tracker .task-tracker-group-icon { color: var(--omni-color-text-muted); }
.omni-task-tracker .task-tracker-group-header { background: var(--omni-color-surface-muted); border-color: var(--omni-color-border); }
.nav-alert-item { position: relative; }
.nav-alert-item .omni-panel-menu__link { padding-right: 3.5rem; }
.nav-alert-count { position: absolute; right: 0.75rem; top: 50%; transform: translateY(-50%); pointer-events: none; }

/* Recette R-188: toasts start under the top bar (53px high) instead of covering it and the header
   popovers. */
:root { --omni-notification-top: calc(3.3125rem + 0.5rem); }

/* Recette R2-061: the catch-up when the page comes back to the foreground is a full veil over the page
   (the plane and "Synchronisation en cours…"), under the connection-lost overlay (z-index 130). It
   catches every click until the data is current again. */
.realtime-sync-indicator {
    position: fixed; inset: 0; z-index: 120;
    display: flex; align-items: center; justify-content: center; padding: 1rem;
    background: color-mix(in srgb, var(--surface) 82%, transparent);
    backdrop-filter: blur(2px);
}
.realtime-sync-card {
    display: flex; flex-direction: column; align-items: center; gap: 0.75rem;
    padding: 1.5rem 2rem; max-inline-size: min(22.5rem, 100%);
    background: var(--omni-card-background, var(--omni-color-surface));
    border: 1px solid var(--omni-card-border-color, transparent);
    border-radius: var(--aetheus-radius);
    text-align: center;
}
.realtime-sync-text { font-weight: 500; }

/* R-112 to R-116: user notifications. The bell reuses the task tracker's button and popover rules
   above; only its footer (mark all read + see all) and the followed-projects list need their own. */
.omni-task-tracker .notification-bell-footer {
    display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
}
.omni-task-tracker .notification-bell-footer:has(> .task-tracker-see-all:only-child) { justify-content: center; }
/* Recette R2-034: a user who follows no project is told why nothing arrives, above the list; the
   list then takes no room of its own when it is empty. */
.omni-task-tracker .notification-bell-no-subscription {
    padding: 1rem; border-bottom: 1px solid var(--omni-color-border);
}
.omni-task-tracker .notification-bell-no-subscription + .task-tracker-list:empty { min-height: 0; }
.notification-subscriptions-actions { display: flex; justify-content: flex-end; margin-block-end: 0.75rem; }
/* Recette R-169: the event and the subject wrap on two lines at most instead of being cut, and the
   row never scrolls sideways; the status is an icon at the end of the row. */
.omni-task-tracker .task-tracker-list { overflow-x: hidden; }
.omni-task-tracker .notification-bell-row .task-tracker-row-name,
.omni-task-tracker .notification-bell-row .task-tracker-row-meta {
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
    white-space: normal; overflow-wrap: anywhere;
}
.notification-status-icon { flex: 0 0 auto; }
/* Recette R-193: the event icon on the left is the only icon of a bell row; unread reads in bold. */
.notification-event-icon { flex: 0 0 auto; color: var(--omni-color-text-muted); }
.omni-task-tracker .notification-bell-row .task-tracker-row-name { font-weight: 400; }
.omni-task-tracker .notification-bell-row--unread .task-tracker-row-name { font-weight: 700; }
.notification-status-sent { color: var(--omni-color-success); }
.notification-status-failed { color: var(--omni-color-danger); }
.notification-status-not-configured { color: var(--omni-color-warning); }
.notification-status-pending { color: var(--omni-color-text-muted); }
.notification-subscriptions { list-style: none; margin: 0; padding: 0; }
.notification-subscription {
    display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
    padding: 0.25rem 0;
    border-bottom: 1px solid var(--omni-color-border);
}
.notification-subscription:last-child { border-bottom: none; }
/* End R-112 to R-116. */

/* Rendered markdown README inside git repository detail pages. */
.readme-rendered { font-size: 0.9375rem; line-height: 1.7; word-wrap: break-word; }
.readme-rendered h1,
.readme-rendered h2,
.readme-rendered h3,
.readme-rendered h4 { margin-top: 1.5rem; margin-bottom: 0.5rem; font-weight: 600; }
.readme-rendered h1 { font-size: 1.5rem; border-bottom: 1px solid var(--omni-color-border); padding-bottom: 0.3rem; }
.readme-rendered h2 { font-size: 1.25rem; border-bottom: 1px solid var(--omni-color-border); padding-bottom: 0.3rem; }
.readme-rendered h3 { font-size: 1.1rem; }
.readme-rendered p { margin: 0.5rem 0; }
.readme-rendered ul, .readme-rendered ol { padding-left: 1.5rem; margin: 0.5rem 0; }
.readme-rendered code {
    background: var(--omni-color-surface-highlight, #2a2a3e);
    padding: 0.125rem 0.375rem; border-radius: var(--aetheus-radius);
    font-size: 0.85em;
}
.readme-rendered pre {
    background: var(--omni-color-surface-highlight, #2a2a3e);
    padding: 1rem; border-radius: var(--aetheus-radius);
    overflow-x: auto; margin: 0.75rem 0;
}
.readme-rendered pre code { background: none; padding: 0; }
.readme-rendered table { border-collapse: collapse; width: 100%; margin: 0.75rem 0; }
.readme-rendered th, .readme-rendered td {
    border: 1px solid var(--omni-color-border);
    padding: 0.375rem 0.75rem; text-align: left;
}
.readme-rendered th { background: var(--omni-color-surface-highlight, #2a2a3e); font-weight: 600; }
.readme-rendered a { color: var(--omni-color-accent); text-decoration: none; }
.readme-rendered a:hover { text-decoration: underline; }
.readme-rendered blockquote {
    margin: 0.75rem 0; padding: 0.5rem 1rem;
    color: var(--omni-color-text-muted);
}
.readme-rendered img { max-width: 100%; height: auto; }

/* F-22 — Force dashboard cards to stack on narrow viewports.
   the previous component mobile-first grid sometimes leaves Size="3" tiles squeezed on phones; explicitly force full width. */
@media (max-width: 39.99rem) {
    .dashboard-tile-row > .omni-column,
    .dashboard-tile-row > [class*="omni-column--"] {
        flex: 0 0 100% !important;
        max-width: 100% !important;
    }
}

/* F-38 / MA-3 — Ensure all modal dialogs have a clearly visible darkened backdrop
   and that the dialog itself has proper z-index isolation. */
.omni-overlay {
    background-color: rgba(0, 0, 0, 0.72) !important;
    backdrop-filter: blur(2px);
}

.omni-dialog {
    z-index: 1050;
}

/* F-33: replaces inline indent style on Teamspeak channel rows. */
.ts-channel-root { padding-left: 0; }
.ts-channel-child { padding-left: 1.5rem; }

/* Hardening (#37): replaces ad-hoc Style="..." attributes on legacy UI components. */
.empty-state { padding: 1.5rem; }
.empty-state-icon { font-size: 3rem; color: var(--omni-color-text-muted); }
.empty-state-description { text-align: center; }
.error-state-icon { font-size: 3rem; color: var(--omni-color-danger); }
.omni-error-state-icon { font-size: 3rem; color: var(--omni-color-danger); }
.dlg-summary-pre { max-height: 12rem; overflow: auto; }
.tpl-select { max-width: 100%; min-width: 0; width: 20rem; }
.bulk-perm-select { width: 10rem; }
.role-edit-stack { max-width: 32rem; }
.role-perm-select { width: 100%; }
.icon-inherit { font-size: inherit; }

/* Administration identity area: users, organizations and roles share one visual vocabulary.
   The redesign intentionally has no metric/stat tiles; information stays attached to the entity
   header, navigation, forms and grids where it is actionable. */
.iam-page {
    min-width: 0;
}

.iam-page-header,
.iam-entity-header {
    padding: 1rem 0;
}

.iam-page-header .omni-page-header__icon,
.iam-entity-header .omni-page-header__icon {
    display: inline-grid;
    place-items: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--aetheus-radius);
    color: var(--omni-color-accent);
    background: color-mix(in srgb, var(--omni-color-accent) 14%, transparent);
}

/* Recette R-316: the section links on the left, the search shared by the three sections on the right. */
.iam-section-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--omni-space-sm);
    margin: 0 0 var(--aetheus-header-gap);
}

.iam-section-search {
    /* Room for the links first; the search takes up to 16rem of what is left, on the same line. */
    flex: 0 1 16rem;
    min-width: 8rem;
    width: auto;
}

.iam-section-nav {
    display: flex;
    align-items: center;
    gap: 0.375rem;
    width: fit-content;
    max-width: 100%;
    margin: 0;
    padding: 0.3125rem;
    overflow-x: auto;
    border: 0.0625rem solid var(--aetheus-tile-separator);
    border-radius: var(--aetheus-radius);
    border: 0.0625rem solid var(--omni-color-border);
    background: var(--omni-color-surface-muted);
    scrollbar-width: thin;
}

.iam-section-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4375rem;
    min-height: 2.375rem;
    padding: 0.5rem 0.875rem;
    border: 0.0625rem solid transparent;
    border-radius: var(--aetheus-radius);
    color: var(--omni-color-text-muted);
    font-weight: 600;
    white-space: nowrap;
    text-decoration: none;
    color: var(--omni-color-text);
    transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease;
}

.iam-section-link:hover {
    color: var(--omni-color-text);
    background: var(--omni-color-surface-hover);
}

.iam-section-link-active {
    color: var(--omni-color-text);
    border-color: color-mix(in srgb, var(--omni-color-accent) 45%, var(--omni-color-border));
    background: var(--omni-color-accent-subtle);
}

.iam-section-link .omni-icon {
    font-size: 1.1rem;
}

.app-body-wrapper:has(> .iam-list-page) {
    display: flex;
    flex-direction: column;
    height: 100%;
}

.app-body-wrapper > .iam-list-page {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    min-height: 0;
}

.iam-grid-surface {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
    border: 0.0625rem solid var(--aetheus-tile-separator);
    border-radius: var(--aetheus-radius);
    background: var(--omni-color-surface);
    box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.08);
}

.iam-grid-surface > .aetheus-grid-fullheight,
.iam-grid-surface > .aetheus-grid-host > .aetheus-grid-fullheight {
    flex: 1 1 0;
    max-height: none;
    border: 0;
    border-radius: 0;
}

/* Same relay as the body wrapper: the grid host must not absorb the surface flex context. */
.iam-grid-surface > .aetheus-grid-host:has(> .aetheus-grid-fullheight) {
    display: flex;
    flex-direction: column;
    flex: 1 1 0;
    min-height: 0;
}

/* Recette R-411: the IAM grids keep OE's header fill (the rule that painted their header cells on the
   inverse surface never matched, .omni-data-grid__header being the cell's inner block, and is gone);
   their pager sits on the muted page surface under the page's ink. */
.iam-grid-surface .omni-pager {
    border-top-color: var(--omni-color-text);
    background: var(--omni-color-surface-muted);
}

.iam-entity-avatar {
    display: inline-grid;
    flex: 0 0 auto;
    place-items: center;
    width: 1.75rem;
    height: 1.75rem;
    border: 0.0625rem solid color-mix(in srgb, var(--omni-color-accent) 28%, transparent);
    border-radius: var(--aetheus-radius);
    color: var(--omni-color-accent);
    background:
        linear-gradient(145deg,
            color-mix(in srgb, var(--omni-color-accent) 16%, transparent),
            color-mix(in srgb, var(--omni-color-text-muted) 10%, transparent));
}

.iam-entity-avatar .omni-icon {
    font-size: 1rem;
}

.iam-entity-header {
    margin-bottom: 1rem;
}

.iam-entity-header .omni-page-header__subtitle {
    margin-left: 3.125rem;
}

.iam-detail-card {
    padding: 1.125rem;
    border: 0.0625rem solid var(--aetheus-tile-separator);
    border-radius: var(--aetheus-radius);
    background: color-mix(in srgb, var(--omni-color-surface) 94%, transparent);
    box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.08);
}

.iam-detail-card .omni-data-grid {
    border-radius: var(--aetheus-radius);
}

.role-assignment-picker {
    width: 100%;
    min-width: 0;
}

.iam-role-assignment-card {
    width: 100%;
}

.iam-role-autosave-status {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.375rem;
    min-height: 1.5rem;
    margin-top: 0.625rem;
    color: var(--omni-color-text-muted);
}

.role-assignment-heading {
    margin-bottom: 0.75rem;
}

.role-assignment-selected {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.375rem;
    margin-bottom: 0.75rem;
    padding: 0.625rem;
    border-radius: var(--aetheus-radius);
    background: color-mix(in srgb, var(--omni-color-accent) 8%, transparent);
}

.role-assignment-toolbar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
}

.role-assignment-search {
    flex: 1 1 18rem;
    min-width: 12rem;
}

.role-assignment-filter {
    min-height: 2.5rem;
    padding-inline: 0.625rem;
    border: 0.0625rem solid var(--aetheus-tile-separator);
    border-radius: var(--aetheus-radius);
    background: var(--omni-color-surface-muted);
}

.role-assignment-grid {
    min-width: 0;
    overflow: hidden;
    border: 0.0625rem solid var(--aetheus-tile-separator);
    border-radius: var(--aetheus-radius);
}

.role-assignment-grid .omni-data-grid {
    border: 0;
    border-radius: 0;
}

.role-assignment-toggle {
    font-weight: 600;
    color: var(--omni-color-accent);
}

.role-assignment-description {
    display: block;
    margin-top: 0.1875rem;
    margin-left: 1.75rem;
}

.iam-security-grid {
    display: grid;
    grid-template-columns: minmax(0, 1.3fr) minmax(16rem, 0.7fr);
    gap: 1rem;
}

.iam-quality-surface {
    min-width: 0;
}

@media (max-width: 63.99rem) {
    .iam-security-grid {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 39.99rem) {
    .iam-page-header,
    .iam-entity-header {
        padding: 0.875rem 0;
    }

    .iam-page-header .omni-page-header__actions .omni-button,
    .iam-entity-header .omni-page-header__actions .omni-button {
        min-width: var(--aetheus-control-height);
        min-height: var(--aetheus-control-height);
    }

    .iam-page-header .omni-page-header__actions .omni-button .omni-button__content,
    .iam-entity-header .omni-page-header__actions .omni-button .omni-button__content {
        display: none;
    }

    .iam-entity-header .omni-page-header__subtitle {
        margin-left: 0;
    }

    .iam-section-nav {
        width: 100%;
    }

    .iam-section-link {
        flex: 1 0 auto;
        min-height: 2.75rem;
    }

    .iam-detail-card {
        padding: 0.875rem;
    }

    .iam-detail-card .omni-stack {
        max-width: 100%;
    }

    .role-assignment-toolbar {
        align-items: stretch;
    }

    .role-assignment-search,
    .role-assignment-filter {
        flex: 1 1 100%;
        min-width: 0;
    }
}

.scan-status-clean { color: var(--omni-color-success); }
.scan-status-warning { color: var(--omni-color-warning); }
.scan-status-unknown { color: var(--omni-color-text-muted); }

/* R2-002: the copyable clone URL field (OE's wrapper: input and its welded Copy button). */
.clone-url-input { width: min(100%, 48rem); min-width: 0; max-width: 48rem; }

/* Diagnostic meta row — small, secondary-colored chips of metadata under the
   "Why offline?" summary so the eye picks the summary first. */
.contact-agent-diagnostic-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    font-size: 0.8125rem;
    color: var(--omni-color-text-muted);
}

/* S-DES-04: compact OE progress indicator for token lifetime. */
.diagnostic-token-row { display: inline-flex; align-items: center; gap: 0.5rem; }
.diagnostic-token-progress { display: inline-flex; width: 4rem; }

/* Recette R-440: the 24 h availability bars of the supervision list, on the OE status tokens. */
.availability-sparkline { vertical-align: middle; }
.availability-sparkline-bar--up { fill: var(--omni-color-success); }
.availability-sparkline-bar--down { fill: var(--omni-color-danger); }

/* 30-minute heartbeat sparkline. One bar per minute slot, color-graded by
   how many beats hit that slot (~2 expected at 30 s cadence). Bars use flex so
   the strip stretches to fill the parent without arithmetic. */
.heartbeat-sparkline { display: flex; flex-direction: column; gap: 0.375rem; }
.heartbeat-sparkline-header { display: flex; justify-content: space-between; align-items: baseline; }
.heartbeat-sparkline-loading { height: 1.25rem; }
.heartbeat-sparkline-row {
    display: flex;
    gap: 2px;
    align-items: flex-end;
    height: 1.5rem;
}
.heartbeat-bucket {
    flex: 1 1 0;
    min-width: 0;
    border-radius: 2px;
}
.heartbeat-bucket-ok   { background: var(--omni-color-success); height: 100%; }
.heartbeat-bucket-low  { background: var(--omni-color-warning); height: 65%; }
.heartbeat-bucket-miss { background: var(--omni-color-border); height: 35%; }

/* Audit remediation (front-a/front-b): inline-style removals. */
.status-dot-warning { background-color: var(--omni-color-warning); }
.status-dot-danger { background-color: var(--omni-color-danger); }
.status-dot-success { background-color: var(--omni-color-success); }

/* R-517: the wrapper is a column without a gap, so the banner keeps its own space above the page. */
.version-alert-bar { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem; }
.version-alert-bar-text { flex: 1; }

/* R-510: the state of the action launched on a service, under its status badge. */
.service-action-state { font-size: 0.8125rem; line-height: 1.2; }
.service-action-state--failed a { color: var(--omni-color-danger); }
.service-action-state--succeeded a { color: var(--omni-color-success); }

.artifact-detail-label { min-width: 8rem; }

.dashboard-widget-preview { min-height: 6rem; }
.dashboard-widget-height-1 { min-height: 6rem; }
.dashboard-widget-height-2 { min-height: 12rem; }
.dashboard-widget-height-3 { min-height: 18rem; }
.dashboard-widget-height-4 { min-height: 24rem; }

.org-projects-listbox { width: 100%; height: 20rem; }
.org-edit-form { max-width: 40rem; }

.coverage-card { min-width: 10rem; }
.coverage-progress { height: 0.375rem; --omni-color-accent: var(--coverage-color); }
.coverage-progress-danger { --coverage-color: var(--omni-color-danger); }
.coverage-progress-warning { --coverage-color: var(--omni-color-warning); }
.coverage-progress-success { --coverage-color: var(--omni-color-success); }

/* K: actionable coverage tab — radial gauges, trend chart, per-file grid */
.coverage-gauge-wrap { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; }
.coverage-gauge { width: 9rem; height: 5.5rem; }
.coverage-gauge-label { text-align: center; }
.coverage-trend-chart { width: 28rem; max-width: 100%; } /* height from the chart's AspectRatio */
.coverage-file-name { font-family: "Cascadia Code", "Fira Code", "Consolas", monospace; font-size: 0.8125rem; }
.coverage-file-bar { display: flex; align-items: center; gap: 0.5rem; }
.coverage-file-bar .coverage-progress { flex: 1; }
.coverage-file-pct { font-size: 0.8125rem; min-width: 3rem; text-align: right; }
.run-logs-terminal-sm { max-height: 20rem; }
.run-logs-terminal-md { max-height: 25rem; }

.artifact-policy-filter { width: 10rem; }
.server-empty-icon { font-size: 3rem; opacity: 0.4; }
.project-repo-url-input { width: 100%; min-width: 22rem; }
.docker-graph-filter-dropdown { width: 14rem; }

.server-os-icon { font-size: 1.1rem; }
.server-os-linux { color: #f7a800; }
.server-os-windows { color: #0078d4; }
.server-os-macos { color: #6e6e73; }
.server-os-unknown { color: var(--omni-color-text-muted, #9ca3af); }

/* P: queue-time run-parameters dialog */
.run-params-dialog { display: flex; flex-direction: column; gap: 1rem; }
.run-params-field { display: flex; flex-direction: column; gap: 0.25rem; }
.run-params-input { width: 100%; }
/* PLAN-005 lot 6 / D42: a parameter label or field and its help icon on one row, the icon pushed to the right. */
.param-help-row { display: flex; align-items: center; gap: 0.5rem; }
.param-help-content { flex: 1 1 auto; min-width: 0; }
.param-help { display: inline-flex; color: var(--omni-color-text-muted); cursor: help; border-radius: 50%; }
.param-help:focus-visible { outline: 2px solid var(--omni-color-accent); outline-offset: 2px; }
.param-help .omni-icon { font-size: 1.125rem; }
/* Read by screen readers through aria-describedby, never shown: the tooltip is the visible form. */
.param-help-description { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.run-params-actions { display: flex; gap: 0.5rem; justify-content: flex-end; margin-top: 0.5rem; }

.pipeline-branch-dialog-intro {
    display: flex;
    align-items: center;
    gap: 0.875rem;
    padding: 0.875rem;
    border-radius: var(--aetheus-radius);
    border: 0.0625rem solid color-mix(in srgb, var(--omni-color-accent) 28%, transparent);
    background: color-mix(in srgb, var(--omni-color-accent) 8%, var(--omni-color-surface));
}

.pipeline-branch-dialog-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 2.5rem;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
    color: var(--omni-color-accent);
    background: var(--omni-color-accent-subtle);
}

.pipeline-branch-dialog-icon .omni-icon { font-size: 1.35rem; }
.pipeline-branch-dialog-hint { color: var(--omni-color-text-muted, #a0a0b0); }
/* PLAN-003 lot 3: this dropdown carried its own 2.75rem, the tallest control in the app. */
.pipeline-branch-dropdown { min-height: var(--aetheus-control-height); }
.pipeline-branch-option { width: 100%; min-height: 2rem; }
.pipeline-branch-name { flex: 1 1 auto; font-weight: 500; }

@media (max-width: 39.99rem) {
    .pipeline-branch-dialog-intro { align-items: flex-start; }
}

/* Archived module-finalisation plan, section 4.1: work-items kanban board */
.kanban-board { display: flex; gap: 1rem; overflow-x: auto; padding-bottom: 0.5rem; align-items: flex-start; }
.kanban-column { flex: 1 1 0; min-width: 15rem; background: var(--omni-color-surface-highlight, rgba(128,128,128,0.06)); border-radius: var(--aetheus-radius); padding: 0.5rem; display: flex; flex-direction: column; gap: 0.5rem; }
.kanban-column-header { display: flex; align-items: center; justify-content: space-between; padding: 0.25rem 0.5rem; font-weight: 600; }
.kanban-column-drop { min-height: 3rem; display: flex; flex-direction: column; gap: 0.5rem; border-radius: var(--aetheus-radius); transition: background-color 0.15s ease; }
.kanban-column-drop.kanban-drop-active { background: var(--omni-color-accent-subtle, rgba(64,128,255,0.15)); outline: 2px dashed var(--omni-color-accent, #4080ff); outline-offset: -2px; }
.kanban-card { cursor: grab; }
.kanban-card:active { cursor: grabbing; }
.kanban-card.kanban-card-dragging { opacity: 0.5; }
.kanban-card-title { font-weight: 600; }
.kanban-card-meta { display: flex; align-items: center; gap: 0.375rem; flex-wrap: wrap; margin-top: 0.375rem; }
.kanban-filter { min-width: 11rem; }

/* Connection-lost overlay (realtime hub down). Full-screen mask + centered card. */
.detail-not-found { padding: 2rem; }

/* Native radios used by OwnerTypeSelector because the previous component rendered text is not
   reliably associated with its input in Chromium and Firefox. */
.owner-type-options { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem 1rem; min-height: 2.5rem; }
.owner-type-options label { display: inline-flex; align-items: center; gap: 0.35rem; cursor: pointer; user-select: none; }
.owner-type-options input[type="radio"] { accent-color: var(--omni-color-accent); cursor: pointer; }

@media (max-width: 39.99rem) {
    /* legacy UI centres an EmptyTemplate in the complete (potentially scrollable) table width. Keep
       the message pinned to the visible grid viewport so it is readable before any horizontal scroll. */
    .omni-data-grid .empty-state {
        position: sticky;
        left: 0;
        box-sizing: border-box;
        width: calc(100vw - 4rem);
        max-width: calc(100vw - 4rem);
        padding: 1rem !important;
        overflow-wrap: anywhere;
    }

    .omni-data-grid td:has(.empty-state),
    .omni-data-grid .omni-data-grid__table td:has(.empty-state) {
        position: sticky;
        left: 0;
        width: calc(100vw - 4rem);
        max-width: calc(100vw - 4rem);
        padding: 0;
    }

    .omni-data-grid .empty-state > :not(.empty-state-icon):not(.empty-state-actions) {
        align-self: stretch;
        width: 100%;
        min-width: 0;
        max-width: 100%;
        text-align: center;
        white-space: normal !important;
        overflow-wrap: anywhere;
    }

    /* Owner scope has four native choices. Keep the labels associated with their inputs while
       giving the wrapping group enough room below the floating field legend on narrow screens. */
    .owner-type-field .owner-type-options {
        width: 100%;
        padding-top: 0.625rem;
        row-gap: 0.5rem;
    }
}
.analysis-finding-problem {
    display: flex;
    align-items: flex-start;
    gap: 0.75rem;
    padding: 1rem;
    border: 0.0625rem solid color-mix(in srgb, var(--omni-color-accent) 24%, var(--omni-color-border));
    border-radius: var(--omni-radius);
    color: var(--omni-color-text);
    background: color-mix(in srgb, var(--omni-color-accent) 7%, var(--omni-color-surface, var(--omni-color-surface)));
}

.analysis-finding-summary-card {
    color: var(--omni-color-text);
    background: var(--omni-color-surface, var(--omni-color-surface));
}

.analysis-finding-tabs > .omni-tabs__viewport {
    padding: 1rem 0 0;
}

.analysis-finding-tabs .omni-tabs__panel {
    min-width: 0;
}

.analysis-decision-card {
    overflow: hidden;
    border: 0.0625rem solid color-mix(in srgb, var(--omni-color-accent) 32%, transparent);
    color: var(--omni-color-text);
    background:
        radial-gradient(circle at top right, color-mix(in srgb, var(--omni-color-accent) 12%, transparent), transparent 38%),
        var(--omni-color-surface, var(--omni-color-surface));
}

.analysis-decision-heading {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    margin-bottom: 1.25rem;
}

.analysis-decision-icon {
    display: inline-grid;
    flex: 0 0 auto;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: var(--aetheus-radius);
    color: var(--omni-color-accent);
    background: color-mix(in srgb, var(--omni-color-accent) 16%, transparent);
}

.analysis-decision-icon .omni-icon {
    font-size: 1.4rem;
}

.analysis-decision-help {
    margin-top: 0.2rem;
    color: var(--omni-color-text-muted);
}

.analysis-decision-layout {
    display: grid;
    grid-template-columns: minmax(16rem, 0.9fr) minmax(20rem, 1.5fr);
    align-items: start;
    gap: 1.25rem;
}

.analysis-decision-outcome,
.analysis-decision-details {
    min-width: 0;
}

.analysis-decision-choices {
    display: grid;
    gap: 0.625rem;
    margin-top: 0.75rem;
}

.analysis-decision-choice {
    display: grid;
    grid-template-columns: minmax(8.5rem, 0.8fr) minmax(0, 1.2fr);
    align-items: center;
    gap: 0.75rem;
    min-height: 4rem;
    padding: 0.625rem;
    border: 0.0625rem solid var(--omni-color-border);
    border-radius: var(--aetheus-radius);
    /* Recette R-434: the OE muted surface, readable in both themes. The former fill mixed in the TEXT
       colour (55 %, 91 % when selected): grey then almost white in dark, dark grey in light. */
    background: var(--omni-color-surface-muted);
    transition: border-color 120ms ease, background 120ms ease;
}

.analysis-decision-choice.is-selected {
    border-color: var(--omni-color-accent);
    background: var(--omni-color-accent-subtle);
}

.analysis-decision-choice > .omni-button {
    justify-content: flex-start;
    width: 100%;
    min-height: var(--aetheus-control-height);
}

.analysis-decision-choice > .omni-text {
    color: var(--omni-color-text-muted);
    line-height: 1.35;
}

.analysis-decision-details {
    display: grid;
    gap: 1rem;
    padding: 1rem;
    border: 0.0625rem solid var(--omni-color-border);
    border-radius: var(--aetheus-radius);
    /* Recette R-434: the OE muted surface, not a mix of the text colour. */
    background: var(--omni-color-surface-muted);
}

.analysis-decision-details .omni-form-field,
.analysis-decision-details .omni-text-area__input,
.analysis-decision-details .omni-date-input {
    width: 100%;
}

.analysis-decision-details .omni-text-area__input {
    min-height: 8rem;
    resize: vertical;
}

.analysis-decision-field-hint {
    display: block;
    margin-top: 0.375rem !important;
    color: var(--omni-color-text-muted);
}

.analysis-decision-details .validation-message {
    display: block;
    margin-top: 0.375rem;
    color: var(--omni-color-danger);
    font-size: 0.8125rem;
}

.analysis-decision-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 0.0625rem solid color-mix(in srgb, var(--omni-color-text) 18%, transparent);
}
/* Recette R2-027: "Revert the decision" sits beside the save button, on the right. */
.analysis-decision-actions > .analysis-decision-revert { margin-inline-start: auto; }

.analysis-decision-selected {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    color: var(--omni-color-text-muted);
    font-size: 0.8125rem;
}

.analysis-decision-selected strong {
    color: var(--omni-color-text);
}

.analysis-finding-problem .omni-icon {
    flex: 0 0 auto;
    color: var(--omni-color-accent);
    font-size: 1.4rem;
}

.analysis-finding-problem p {
    margin: 0.3rem 0 0;
    line-height: 1.5;
    white-space: pre-line;
    overflow-wrap: anywhere;
}

.analysis-source-card {
    border: 0.0625rem solid var(--omni-color-border);
    color: var(--omni-color-text);
    background: var(--omni-color-surface, var(--omni-color-surface));
}

.analysis-source-heading {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
}

.analysis-source-heading > .omni-icon {
    color: var(--omni-color-accent);
    font-size: 1.25rem;
}

.analysis-source-location {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    min-width: 0;
    padding: 0.8rem;
    border: 0.0625rem solid color-mix(in srgb, var(--omni-color-accent) 24%, var(--omni-color-border));
    border-radius: var(--aetheus-radius);
    background: color-mix(in srgb, var(--omni-color-accent) 7%, var(--omni-color-surface));
}

.analysis-source-location > .omni-icon {
    flex: 0 0 auto;
    color: var(--omni-color-accent);
    font-size: 1.2rem;
}

.analysis-source-location code {
    min-width: 0;
    color: var(--omni-color-text);
    font-family: var(--omni-font-family-monospace, monospace);
    font-size: 0.875rem;
    font-weight: 600;
    overflow-wrap: anywhere;
}

/* Recette R-433: the line is an OE badge and "Open the file" an OE button at the end of the row. */
.analysis-source-location > .omni-badge {
    flex: 0 0 auto;
}

.analysis-source-location > .analysis-source-open {
    flex: 0 0 auto;
    margin-left: auto;
}

.analysis-source-location-unavailable {
    color: var(--omni-color-text-muted);
}

.analysis-source-metadata {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.6rem;
    margin-top: 0.75rem;
}

.analysis-source-metadata > div {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    min-width: 0;
    padding: 0.65rem;
    border: 0.0625rem solid var(--omni-color-border);
    border-radius: var(--aetheus-radius);
    background: var(--omni-color-surface);
}

.analysis-source-metadata > div > span {
    color: var(--omni-color-text-muted);
    font-size: 0.75rem;
}

.analysis-source-metadata strong {
    color: var(--omni-color-text);
    font-size: 0.875rem;
    overflow-wrap: anywhere;
}

.analysis-source-commit {
    font-family: var(--omni-font-family-monospace, monospace);
}


.analysis-source-links {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1rem;
    margin-top: 0.75rem;
    color: var(--omni-color-text-muted);
    font-size: 0.8125rem;
}

@media (max-width: 39.99rem) {
    /* These two rails used to fold into a two-column grid here. Every tab rail now stays a single
       scrolling row on phones (see the 767.98px block), so only the button padding is kept. */
    .analysis-finding-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__tab,
    .analysis-workspace-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__tab {
        justify-content: center;
        padding-inline: 0.5rem;
    }

    .analysis-decision-heading {
        align-items: flex-start;
    }

    .analysis-decision-layout {
        grid-template-columns: minmax(0, 1fr);
    }

    .analysis-decision-choice {
        grid-template-columns: minmax(0, 1fr);
    }

    .analysis-decision-actions {
        align-items: stretch;
        flex-direction: column;
    }

    .analysis-decision-actions > .omni-button {
        width: 100%;
    }

    .analysis-source-location {
        align-items: flex-start;
        flex-direction: column;
    }

    .analysis-source-metadata {
        grid-template-columns: minmax(0, 1fr);
    }
}

.project-overview-tabs > .omni-tabs__tab > li {
    flex: 0 0 auto;
}

.project-overview-tabs > .omni-tabs__tab > li > button {
    min-height: 2.75rem;
    padding-inline: 1rem;
}

.project-overview-tabs > .omni-tabs__viewport {
    padding-top: 0;
}

.project-readme-panel {
    max-width: 70rem;
    margin-inline: auto;
    padding: 1.25rem 1.5rem;
    color: var(--omni-color-text);
    background: color-mix(in srgb, var(--omni-color-surface) 94%, var(--omni-color-accent) 6%);
    border: 0.0625rem solid var(--omni-color-border);
    border-radius: var(--omni-radius);
}

.project-readme-content {
    color: var(--omni-color-text);
}

.project-readme-content h1,
.project-readme-content h2,
.project-readme-content h3,
.project-readme-content h4,
.project-readme-content p,
.project-readme-content li,
.project-readme-content td,
.project-readme-content th {
    color: inherit;
}

.project-readme-content code,
.project-readme-content pre,
.project-readme-content th {
    color: var(--omni-color-text);
    background: color-mix(in srgb, var(--omni-color-surface) 72%, var(--omni-color-text) 8%);
}

.project-readme-content blockquote {
    color: var(--omni-color-text-muted);
    border: 0.0625rem solid color-mix(in srgb, var(--omni-color-accent) 35%, var(--omni-color-border));
    border-radius: var(--omni-radius);
    background: color-mix(in srgb, var(--omni-color-accent) 7%, transparent);
}

.project-changelog-timeline {
    max-width: 64rem;
    margin-inline: auto;
}

.project-changelog-entry {
    min-width: min(42rem, calc(100vw - 12rem));
    padding: 0.875rem 1rem;
    color: var(--omni-color-text);
    background: color-mix(in srgb, var(--omni-color-surface) 94%, var(--omni-color-accent) 6%);
    border: 0.0625rem solid var(--omni-color-border);
    border-radius: var(--omni-radius);
    box-shadow: 0 0.125rem 0.5rem color-mix(in srgb, #000 18%, transparent);
}

.project-changelog-copy {
    color: var(--omni-color-text);
    line-height: 1.65;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

@media (max-width: 39.99rem) {
    .project-readme-panel {
        padding: 1rem;
    }

    .project-changelog-entry {
        min-width: 0;
        width: calc(100vw - 6.5rem);
    }
}

.project-onboarding {
    margin-bottom: 1.25rem;
    overflow: hidden;
    border: 0.0625rem solid color-mix(in srgb, var(--omni-color-accent) 22%, var(--omni-color-border));
    background:
        radial-gradient(circle at top right, color-mix(in srgb, var(--omni-color-accent) 10%, transparent), transparent 30%),
        var(--omni-color-surface);
}

.project-onboarding-heading {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 1rem;
}

.project-onboarding-copy {
    color: var(--omni-color-text-muted);
    margin: 0;
}

.project-onboarding-heading h5 { margin: 0; }

.git-repositories-grid-empty {
    display: none;
}

.onboarding-steps-scroll {
    overflow-x: auto;
}

.onboarding-steps {
    width: 100%;
    min-width: 34rem;
    border-collapse: collapse;
    text-align: left;
}

.onboarding-steps th,
.onboarding-steps td {
    padding: 0.625rem 0.75rem;
    vertical-align: middle;
    border-bottom: 0.0625rem solid color-mix(in srgb, var(--omni-color-text) 13%, transparent);
}

.onboarding-steps thead th {
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--omni-color-text-muted);
}

.onboarding-steps tbody tr:last-child th,
.onboarding-steps tbody tr:last-child td {
    border-bottom: 0;
}

.onboarding-step-row.is-done {
    background: color-mix(in srgb, var(--omni-color-success) 8%, transparent);
}

.onboarding-step-row.is-blocked {
    background: color-mix(in srgb, var(--omni-color-warning) 8%, transparent);
}

/* A real table cell, not a flex box (recette R-095): as a flex box the cell stopped at its own
   height, so on a taller row (step 2 wraps its description on three lines) its rule sat above the
   rules of the cells beside it. The icon and the label line up inline instead. */
.onboarding-step-title {
    font-weight: 600;
    white-space: nowrap;
}

.onboarding-step-title > .project-onboarding-icon,
.onboarding-step-title > .onboarding-step-label {
    display: inline-flex;
    align-items: center;
    vertical-align: middle;
}

.onboarding-step-title > .project-onboarding-icon {
    margin-inline-end: 0.625rem;
}

.onboarding-step-copy {
    color: var(--omni-color-text-muted);
    min-width: 12rem;
}

.onboarding-step-reason {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    margin-top: 0.25rem;
    color: var(--omni-color-warning);
}

.onboarding-step-reason .omni-icon {
    font-size: 0.875rem;
}

.onboarding-step-row[role="link"] {
    cursor: pointer;
    transition: background-color 0.15s ease;
}

.onboarding-step-row[role="link"]:hover,
.onboarding-step-row[role="link"]:focus-visible {
    background: color-mix(in srgb, var(--omni-color-accent) 8%, transparent);
    outline: none;
}

/* The action link is an icon plus a label: align both on one baseline instead of letting the
   icon sit higher than the text. */
.onboarding-step-action .onboarding-step-link {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
}

.onboarding-step-action {
    white-space: nowrap;
}

.onboarding-step-action-empty {
    color: var(--omni-color-text-muted);
}

.project-onboarding-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--aetheus-radius);
    background: var(--omni-color-accent-subtle);
    color: var(--omni-color-accent);
    flex: 0 0 auto;
}

@media (max-width: 39.99rem) {
    .project-onboarding-heading {
        align-items: flex-start;
    }

    .onboarding-steps th,
    .onboarding-steps td {
        padding: 0.5rem;
    }
}

.environment-copy-source {
    border: 1px solid color-mix(in srgb, var(--omni-color-accent) 34%, var(--omni-color-border));
    background: linear-gradient(135deg, color-mix(in srgb, var(--omni-color-accent) 9%, var(--omni-color-surface)), var(--omni-color-surface));
}

.environment-copy-source__icon {
    color: var(--omni-color-accent);
    font-size: 1.5rem;
}

.environment-copy-source__hint {
    color: var(--omni-color-text-muted);
    max-width: 34rem;
}

/* PLAN-003 D12: one tab strip everywhere. Only the selected tab carries the accent colour on its
   icon; every other tab icon reads in the text colour, whatever the page set on it. */
.omni-tabs > .omni-tabs__strip .omni-tabs__viewport .omni-tabs__tab .omni-icon {
    color: var(--omni-color-text);
}

.omni-tabs > .omni-tabs__strip .omni-tabs__tab[aria-selected="true"] .omni-icon {
    color: var(--omni-color-accent);
}

/* Status cell of a running run: the pulse dot and the current step name share one baseline. */
.pipeline-run-current-step > .run-tl-dot {
    flex: 0 0 auto;
    align-self: center;
}

/* A badge or a code chip inside a clickable cell is not prose: the hover underline reads as a
   rendering defect there, so only the plain text of the cell underlines. */
.clickable-cell:hover .omni-badge,
.clickable-cell:hover code {
    text-decoration: none;
}

/* Failed-step rows on the run overview jump to the step logs: say so with a trailing chevron, and
   keep the excerpt flush with the row instead of indented by the terminal padding. */
.run-failed-step > .clickable-cell .run-failed-open-hint {
    margin-left: auto;
    color: var(--omni-color-text-muted);
}

.run-failed-step > .run-logs-terminal {
    margin-top: 0.375rem;
    padding-inline: 0.5rem;
}

/* PLAN-003 lot 1 / recette R-395: THE page header is OE's OmniPageHeader, two lines of fixed height on
   every route, drawn straight on the page (not framed). What Aetheus keeps on top of it: the title
   size (recette R-014 / R-204, the OE H2 size; the heading level is OE's h1), the icon as tall as the
   title's capitals, and one vertical gap between the header, the subtitle, the filters and the content
   (recette R-196). */
:root { --aetheus-header-gap: 0.75rem; }

.omni-page-header {
    margin-block-end: var(--aetheus-header-gap);
}

.omni-page-header .omni-page-header__title {
    font-size: 1.875rem;
}

.omni-page-header__icon {
    --omni-icon-size: 1.5rem;
}

/* The filter row is a row. An OmniEurope input is 100% wide by default, which is right inside a
   form field and wrong here: each filter took a line of its own, so a page with three of them
   pushed its table three rows down. They take their own width instead, with a floor that keeps a
   placeholder readable, and the search box gets the slack. */
.omni-page-header__filters > .omni-input {
    width: auto;
    min-width: 11rem;
    flex: 0 1 auto;
}

.omni-page-header__filters > .omni-text-box {
    flex: 1 1 16rem;
    min-width: 12rem;
    max-width: 28rem;
}

/* On a phone the row is a column again: a filter narrower than its placeholder helps nobody. */
@media (max-width: 39.99rem) {
    .omni-page-header__filters > .omni-input,
    .omni-page-header__filters > .omni-text-box {
        width: 100%;
        min-width: 0;
        max-width: none;
        flex: 1 1 100%;
    }

    .omni-page-header .omni-page-header__title {
        font-size: 1.375rem;
    }
}

/* PLAN-003 lot 18: the single grade rendering. The quality page hero keeps its presence through
   analysis-grade-letter, which already sizes it; its colour comes from the same badge scale as every
   other grade on the site, so a letter cannot read healthy on one page and alarming on the next. */
.grade-badge {
    font-weight: 600;
    letter-spacing: 0.02em;
}

/* PLAN-003 lot 7: the letter is as tall as the text line it annotates. The theme's badge line
   (16px) plus its padding made it 20.8px beside a 16.5px "Grade" label, measured 2026-09-11; one
   line of its own font and 2px of padding bring it to 15.2px. The quality page hero keeps its size. */
.grade-badge:not(.analysis-grade-letter) {
    line-height: 1;
    padding-block: 0.125rem;
}

/* Recette R-096 / R-310: beside the other badges (Active, D) the letter is one of them, so it takes the
   one badge height and is a square, never a pill or an oval: fixed width, no side padding, the site
   radius. The quality page hero keeps its own (square) size. */
.omni-badge.grade-badge:not(.analysis-grade-letter) {
    height: var(--aetheus-badge-height);
    width: var(--aetheus-badge-height);
    min-width: var(--aetheus-badge-height);
    padding-inline: 0;
    border-radius: var(--aetheus-radius);
    justify-content: center;
    /* Recette R2-050: centred in its square on both axes, not on a padded line box. */
    align-items: center;
    padding-block: 0;
}

/* Recette R2-050: the "recent runs" cell holds a 22px grade badge in a wrapper the grid caps at the
   text height (20px): the badge overflowed it and sat lower than the dots. Same release as R-166. */
.omni-data-grid tr[data-omni-row-index] > td > .pipeline-run-history-cell {
    max-height: var(--aetheus-badge-height);
}

/* PLAN-003 lot 19: on a run page the tab rail stays put while the tab content scrolls, so switching
   tab or reading a long log never costs a scroll back up. Recette R-136: the header no longer sticks
   with it, because a sticky header needs a background of its own and no page header paints one; it
   scrolls away and the rail sticks to the top of the page's scroll container. */
.pipeline-run-tabs > .omni-tabs__tab {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--omni-color-surface);
}

/* The host every Aetheus grid sits in (AetheusDataGrid, PipelineRunsGrid, MonitoredAppsStatusGrid). It no
   longer holds a loading veil: every grid shows OE's loading bar on the table (recette R-432). */
.aetheus-grid-host {
    position: relative;
}

.grid-load-alert { margin-block-end: 0.5rem; }

/* R-260: loading and empty states stay visible, including on grids using OE directly. */
.omni-data-grid__state {
    display: table-cell;
}



/* PLAN-003 lot 7: inside a table a badge is never cut. The mobile ellipsis rule below made "3 en
   service" read as "3 en ser…" in a column that had room on the row next to it; a badge is one short
   token, so it wraps the column rather than truncating itself. */
.omni-data-grid .omni-badge,
.omni-data-grid__viewport .omni-badge {
    max-inline-size: none;
    overflow: visible;
    text-overflow: clip;
    white-space: nowrap;
}

/* PLAN-003 lot 26: a tab's content starts where the page's content starts. legacy UI pads its panel on
   both sides, so everything inside a tab sat inset from the cards above it and the page read as two
   different left margins. */
.omni-tabs__panel {
    padding-inline: 0;
}

/* PLAN-003 lot 4 / D9: a row action is filled but flat; the shadow belonged to a labelled button. */
.omni-data-grid tr[data-omni-row-index] .omni-button:not(.omni-button--ghost) {
    box-shadow: none;
}

/* PLAN-005 lot 4 / D35 - STD-BUSY, the veil. A button whose action runs keeps exactly its size and
   content; the surface-coloured ::after breathes over it. No spinner, no label swap, no Disabled for
   the busy state, no opacity on the button itself. Kit reference implementation, unchanged. */
.btn-busy { position: relative; overflow: hidden; pointer-events: none; cursor: progress; }
.btn-busy::after {
    content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
    background: var(--surface); opacity: 0; animation: btn-veil 1.3s ease-in-out infinite;
}
@keyframes btn-veil { 0%, 100% { opacity: 0; } 50% { opacity: .45; } }
@media (prefers-reduced-motion: reduce) { .btn-busy::after { animation-duration: 4s; } }

/* PLAN-007 lot 7: approvals waiting for a decision (home panel) */
.pending-approvals-list {
    margin: 0;
    padding-left: 1.25rem;
}

.pending-approvals-list li + li {
    margin-top: 0.25rem;
}

/* Library entry form: the key's validation message has a reserved line under the row, so neither
   the row nor the entries grid below moves when it appears. Recette R-313: that line IS the page's
   block gap (0.75rem), the message written small inside it, instead of a 1.25rem line plus a margin
   that left 36px between the row and the grid. */
.library-entry-validation {
    height: 0.75rem;
    font-size: 0.75rem;
    line-height: 0.75rem;
    overflow: visible;
    white-space: nowrap;
}

/* PLAN-008 lot 11 (OE-13): the widths of a dialog opened through AppDialogs. OE's dialog is 40rem
   wide at most (AppDialogWidth.Default); the content wrapper names one of the others. */
.omni-dialog:has(.aetheus-dialog--narrow) {
    max-width: 32rem;
}

.omni-dialog:has(.aetheus-dialog--wide) {
    max-width: 52rem;
}

.omni-dialog:has(.aetheus-dialog--extra-wide) {
    max-width: 60rem;
}

.omni-dialog:has(.aetheus-dialog--height-600) { height: min(37.5rem, calc(100vh - 2rem)); }
.omni-dialog:has(.aetheus-dialog--height-720) { height: min(45rem, calc(100vh - 2rem)); }
.omni-dialog:has(.aetheus-dialog--height-80vh) { height: 80vh; }
.omni-dialog:has(.aetheus-dialog--height-85vh) { height: 85vh; }
.omni-dialog:has(.aetheus-dialog--resizable) { resize: both; }
.aetheus-dialog { min-height: 0; }

.aetheus-badge-dot { display: inline-block; inline-size: 0.5rem; block-size: 0.5rem; border-radius: var(--omni-radius-circle); }

/* PLAN-008 lot 44: AetheusVirtualList. The spacers take their height from a custom property that
   Aetheus.sizeVirtualSpacer writes through the CSSOM, so no style attribute appears in the markup
   and the production policy can keep refusing style-src-attr 'unsafe-inline'. */
/* Recette R-425: no scroll anchoring inside the list, as Blazor's own <Virtualize> sets on its
   spacers. Each scroll re-renders the window: the top spacer grows and the same row elements get
   other lines, so the browser's anchoring moved scrollTop to compensate and could undo the wheel's
   step over the log lines. Hypothesis read in the code, not yet confirmed in a browser. */
.aetheus-vlist {
    display: flex;
    flex-direction: column;
    overflow-anchor: none;
}

.aetheus-vlist-spacer {
    height: var(--vlist-height, 0px);
    flex-shrink: 0;
    overflow-anchor: none;
}

/* Recette R-223, then R2-050 (2026-10-02): one colour per grade letter, the same on every page, on a
   scale the user chose: A green, B a lighter, yellower green, C yellow turning orange, D orange, E red
   turning orange, F red. Each letter keeps WCAG AA contrast (R-223): white on the dark fills (A, E, F),
   a near-black on the light ones (B, C, D), where white would not reach 4.5:1. */
:root {
    --aetheus-grade-a: #15803d;
    --aetheus-grade-a-text: #ffffff;
    --aetheus-grade-b: #84cc16;
    --aetheus-grade-b-text: #1a2e05;
    --aetheus-grade-c: #fbbf24;
    --aetheus-grade-c-text: #1f1300;
    --aetheus-grade-d: #f97316;
    --aetheus-grade-d-text: #1c0a00;
    --aetheus-grade-e: #c2410c;
    --aetheus-grade-e-text: #ffffff;
    --aetheus-grade-f: #b91c1c;
    --aetheus-grade-f-text: #ffffff;
}
.omni-badge.omni-badge--solid.grade-badge--a { background: var(--aetheus-grade-a); color: var(--aetheus-grade-a-text); }
.omni-badge.omni-badge--solid.grade-badge--b { background: var(--aetheus-grade-b); color: var(--aetheus-grade-b-text); }
.omni-badge.omni-badge--solid.grade-badge--c { background: var(--aetheus-grade-c); color: var(--aetheus-grade-c-text); }
.omni-badge.omni-badge--solid.grade-badge--d { background: var(--aetheus-grade-d); color: var(--aetheus-grade-d-text); }
.omni-badge.omni-badge--solid.grade-badge--e { background: var(--aetheus-grade-e); color: var(--aetheus-grade-e-text); }
.omni-badge.omni-badge--solid.grade-badge--f { background: var(--aetheus-grade-f); color: var(--aetheus-grade-f-text); }

/* Recette R-228: on /notifications an unread row reads in bold until it is read. */
.omni-data-grid tr.notification-row--unread > td { font-weight: 700; }

/* Recette R-300: a notification opened from its row, label and value on one line each. */
.notification-detail-facts {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: 0.375rem 1rem;
    margin: 0;
}

.notification-detail-facts dt {
    color: var(--omni-color-text-muted);
}

.notification-detail-facts dd {
    margin: 0;
    overflow-wrap: anywhere;
}

/* Recette R-292: the key and the value each carry a copy button, kept on the same line and centred. */
.library-cell {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    max-width: 100%;
    min-width: 0;
}

.library-cell > .clickable-cell {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.library-cell .library-copy-btn.omni-button {
    flex: none;
}

/* Recette R-285: an inline-edited row keeps its fields and buttons centred on the row's height. */
.aetheus-grid td {
    vertical-align: middle;
}

/* Recette R-289: the add-a-secret line, every control on one baseline; the value field takes the free
   width, the validation messages sit under the line. */
.vault-add-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--omni-space-sm, 0.5rem);
}

.vault-add-row > .vault-add-row__key {
    flex: 0 1 14rem;
    min-width: 10rem;
}

.vault-add-row > .omni-password {
    flex: 1 1 16rem;
    min-width: 12rem;
}

.vault-add-row__messages:empty {
    display: none;
}

/* Recette R-437: the value field of the update and rotate dialog, with its Generate button beside it. */
.secret-value-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--omni-space-sm, 0.5rem);
}

.secret-value-row__field {
    flex: 1 1 12rem;
    min-width: 0;
}

/* Recette R-298: alternate rows read in both themes; the dark theme's own alternate fill was too close
   to its surface to see. */
.omni-data-grid.omni-data-grid--striped tbody > .omni-data-grid__row--alternate {
    --omni-row-fill: color-mix(in srgb, var(--omni-color-text) 6%, transparent);
}

/* Recette R-272: every column header reads from the start, a numeric one included; its values may
   still align to the end. OmniDataGrid puts the column's alignment on the header too. */
.omni-data-grid th.omni-data-grid__column--align-end,
.omni-data-grid th.omni-data-grid__column--align-center {
    text-align: start;
}

.omni-data-grid th.omni-data-grid__column--align-end .omni-data-grid__header,
.omni-data-grid th.omni-data-grid__column--align-center .omni-data-grid__header {
    flex-direction: row;
    justify-content: flex-start;
}

.omni-data-grid th.omni-data-grid__column--align-end .omni-data-grid__sort-icon {
    margin-inline: 0;
}

/* Recette R-276: a shortened identifier and its copy button, on one line that never overflows. */
.short-id {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    max-width: 100%;
    min-width: 0;
    vertical-align: middle;
}

.short-id__text {
    font-family: var(--omni-font-mono, ui-monospace, monospace);
    white-space: nowrap;
}

/* Recette R-373: a short identifier is the link to its object, styled like every value link. */
a.short-id__text {
    color: var(--omni-color-accent-strong);
    text-decoration: none;
    font-weight: 500;
}

a.short-id__text:hover {
    text-decoration: underline;
}

.short-id__copy-btn.omni-button {
    width: var(--aetheus-control-height);
    min-width: var(--aetheus-control-height);
    height: var(--aetheus-control-height);
    padding: 0;
    justify-content: center;
}

/* Recette R-273 / R-296: the unmet-requirements warning; its title and dismiss button share the first
   line, and the fix button and its explanation wrap rather than overlap. */
.unmet-requirements-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--omni-space-md, 0.75rem);
}

.unmet-requirements-dismiss.omni-button {
    flex: none;
}

.unmet-requirements-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--omni-space-sm, 0.5rem) var(--omni-space-md, 0.75rem);
}

.unmet-requirements-actions > .omni-button {
    flex: none;
}

.unmet-requirements-hint {
    flex: 1 1 16rem;
    min-width: 0;
}

/* Recette R-274 (decision 2026-09-24): on a phone the page never narrows below 20rem (320px), the
   smallest common phone width, so every usual phone (360 to 430px) shows it without horizontal
   scrolling, and a narrower screen scrolls instead of crushing the layout. The mobile switch stays
   at 40rem. Custom for now; to move into OmniEurope once its current changes are merged. */
@media (max-width: 39.99rem) {
    body {
        min-width: 20rem;
    }
}

/* Recette R-328 / R-343 / R-344: Runs tab of a pipeline. The summary tiles sit close under the tabs,
   stay compact for their one or two lines, keep the label apart from its badge, and leave a clear gap
   above the runs table. */
/* Recette R-416: the tabs panel already pads its top (OE); the summary adds no margin of its own. */
.pipeline-runs-summary-card.analysis-summary-card {
    /* Recette R-416 / R-418: the tile takes its column's height (100%); OmniCard counts its padding and
       border in it since OE 1.4.0 (R2-006), so it no longer runs over the runs table. */
    min-height: 0;
    padding-block: 0.5rem;
}
/* Recette R-417: OmniCard puts the content in .omni-card__body, so the card's own gap never reached
   the label and the grade badge, which sat glued on one line. The body holds two lines: icon and
   label, then the value (a figure or the grade badge), one gap between them. */
.pipeline-runs-summary-card > .omni-card__body {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.375rem;
    min-width: 0;
}
.pipeline-runs-summary-label {
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
}
.pipeline-runs-summary-value {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: var(--aetheus-badge-height);
}
.pipeline-runs-summary-value > * {
    margin: 0;
}
.pipeline-runs-summary-card.analysis-summary-card .omni-icon {
    font-size: 1.25rem;
}
.pipeline-runs-grid {
    margin-top: 1rem;
}
/* R-495: in a dashboard tile the title row already spaces the grid, like the Servers grid beside it;
   the extra margin started the recent runs 16 px lower and made their grid 16 px shorter. */
.dashboard-tile .pipeline-runs-grid {
    margin-top: 0;
}

/* Recette R-346: the pulsing dot of a running run is centred on its step name, not offset by the
   timeline's top margin. */
.pipeline-run-current-step > .run-tl-dot {
    margin-top: 0;
}

/* Recette R-275: a slot can be declared yet render nothing (a project or server section that
   contributes no action), and then the toggle opened an empty panel. No element to fold: no toggle.
   A single element: it shows directly, a toggle for one entry is one tap too many. OmniPageHeader
   folds its badges and actions on a phone; Aetheus keeps these two exceptions. */
.omni-page-header__row:not(:has(> .omni-page-header__details > * > *)) > .omni-page-header__toggle,
.omni-page-header__row:has(> .omni-page-header__details:not(:has(> .omni-page-header__badges > *)):has(> .omni-page-header__actions > :only-child)) > .omni-page-header__toggle,
.omni-page-header__row:has(> .omni-page-header__details:not(:has(> .omni-page-header__actions > *)):has(> .omni-page-header__badges > :only-child)) > .omni-page-header__toggle {
    display: none;
}

.omni-page-header__details:not(:has(> .omni-page-header__badges > *)):has(> .omni-page-header__actions > :only-child),
.omni-page-header__details:not(:has(> .omni-page-header__actions > *)):has(> .omni-page-header__badges > :only-child) {
    display: flex;
}

/* Recette R2-035: OmniPageHeader 1.3.0 draws a loading trail segment as a block skeleton inside a
   list-item <li>, so the skeleton fell to a second line under the "/" and the fixed-height trail clipped
   it: "Servers / (empty) / Services", the slash out of line. The segment is laid out in line, as the
   other segments are. Custom Aetheus until OE ships the same rule; then this block goes. */
.omni-page-header__crumb-loading {
    align-items: center;
    display: inline-flex;
}

/* Recette R-324: the phone fold, applied at any width once the header content no longer fits beside
   the title (layout.js sets data-compact). Same rules as OmniPageHeader's phone block, keyed on the
   attribute. */
.omni-page-header__frame[data-compact] .omni-page-header__title {
    flex: 1 1 auto;
}
.omni-page-header__frame[data-compact] .omni-page-header__toggle {
    display: inline-flex;
}
.omni-page-header__frame[data-compact] .omni-page-header__details:not(.omni-page-header__details--open) {
    display: none;
}
.omni-page-header__frame[data-compact] .omni-page-header__row:has(> .omni-page-header__details--open) {
    block-size: auto;
    flex-wrap: wrap;
}
.omni-page-header__frame[data-compact]:has(.omni-page-header__details--open) {
    block-size: auto;
}
.omni-page-header__frame[data-compact] .omni-page-header__details--open {
    flex: 1 1 100%;
    flex-wrap: wrap;
    order: 1;
}
.omni-page-header__frame[data-compact] .omni-page-header__details--open .omni-page-header__actions {
    justify-content: flex-start;
    margin-inline-start: 0;
}

/* Recette R-326: in a header row a split button keeps its full label on one line too (OE does it for
   buttons and badges); the row folds (R-324) rather than squeezing them. */
.omni-page-header__row .omni-split-button {
    flex-shrink: 0;
    white-space: nowrap;
}

/* Recettes R-356 and R-472: the audience summary tiles are the OE key-figure tile, a third of the row
   from the small breakpoint; each fills its column so a row of tiles has one height. Recette R2-006:
   OmniStatTile counts its padding and border in that height since OE 1.4.0, so it no longer runs
   about 14px into the block below. */
.web-analytics-summary-tile {
    height: 100%;
}

/* Recette R-361: facts of one app error group in its detail dialog, label then value on one line. */
.app-error-detail-facts {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: 0.375rem 1rem;
    margin: 0;
}
.app-error-detail-facts dt {
    color: var(--omni-color-text-muted);
}
.app-error-detail-facts dd {
    margin: 0;
    overflow-wrap: anywhere;
}

/* Recette R-350: the telemetry heading closes the monitored applications table with a block gap. */
.monitoring-telemetry-heading {
    margin-top: 1.5rem;
    margin-bottom: 0.75rem;
}

/* Recette R-358: text cells of the app log grid stay on one line; the full text is in the title. */
.app-log-cell-text {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.app-log-attributes {
    font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
    font-size: 0.8125rem;
    color: var(--omni-color-text-muted);
}

/* Recette R-333: the page-load bar runs along the bottom edge of the top bar, over the seam between the
   bar and the content, without taking any height from either. */
.omni-header:has(.page-load-progress) {
    position: relative;
}
.page-load-progress {
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    z-index: 5;
    margin: 0;
}

/* Recette R-363: the run timeline scrolls sideways in its frame when a label needs more width than the
   panel gives, instead of cutting it with an ellipsis. The frame moves from the tree to its scroll
   wrapper, the tree takes the width of its longest row so every row (sub-steps and child runs
   included) ends on the same edge, and each row's times block stays pinned to the right edge while the
   labels scroll under it, like a frozen column. Same behaviour on a phone. */
.run-tree-scroll {
    flex: 0 0 auto;
    overflow-x: auto;
    overflow-y: hidden;
    border: 0.0625rem solid var(--aetheus-tile-border);
    border-radius: var(--aetheus-radius);
    background: var(--aetheus-tile-background);
}
.run-tree-scroll > .run-tree:not(.run-tree-nested) {
    width: max-content;
    min-width: 100%;
    border: none;
    border-radius: 0;
    overflow: visible;
}
.run-tree-scroll .run-tl-name,
.run-tree-scroll .run-tl-trigger,
.run-tree-scroll .run-tl-trigger-text,
.run-tree-scroll .run-tl-stage-label,
.run-tree-scroll .run-tl-current-stage {
    overflow: visible;
    text-overflow: clip;
}
.run-tree-scroll .run-tl-name,
.run-tree-scroll .run-tl-trigger-text { flex: 1 0 auto; }
.run-tree-scroll .run-tl-stage-label { flex: 0 0 auto; width: auto; min-width: 3.75rem; }
.run-tree-scroll .run-tl-current-stage { max-width: none; }
/* The pinned block repaints the row's own tint over the tile surface, so the labels passing under it
   are hidden and a hovered or selected row keeps one colour across its whole width. */
.run-tl-row { --run-tl-row-tint: transparent; }
.run-tl-row:hover { --run-tl-row-tint: var(--omni-color-surface-hover); }
.run-tl-row.selected { --run-tl-row-tint: var(--omni-color-surface-highlight); }
.run-tl-times {
    position: sticky;
    right: 0;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    gap: 0.375rem;
    flex-shrink: 0;
    margin-left: auto;
    padding-left: 0.5rem;
    background: linear-gradient(var(--run-tl-row-tint), var(--run-tl-row-tint)), var(--aetheus-tile-background);
}
/* Recette R-424: the pinned times keep a small margin from the frame's right edge. Pinned at right: 0,
   they sat against the edge while the tree was wider than the panel and only found the row's own
   padding at the end of the scroll. The margin is the block's padding, painted with its background,
   so the labels still pass under it; the rows give up their right padding so a row that fits keeps
   the same spacing. */
.run-tree-scroll .run-tree .run-tl-row {
    padding-right: 0;
}
.run-tree-scroll .run-tl-times {
    padding-right: 0.75rem;
}

/* Recette R-364: the overview blocks after the timeline no longer touch it, and the compatibility
   attestation is a tile of its own, label, then one row per fact with its explanation (R-374). */
.run-overview > .run-tree-scroll { margin-bottom: var(--omni-space-lg, 1rem); }
.run-attestation-card { margin-bottom: var(--omni-space-lg, 1rem); }
.run-attestation-card > .omni-card__body {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-width: 0;
}
.run-attestation-row {
    display: grid;
    grid-template-columns: minmax(9rem, max-content) minmax(0, 1fr);
    column-gap: 0.75rem;
    row-gap: 0.125rem;
    align-items: center;
    font-size: 0.8125rem;
}
.run-attestation-row > .run-attestation-value,
.run-attestation-row > .omni-badge { justify-self: start; }
.run-attestation-hint {
    grid-column: 2;
    color: var(--omni-color-text-muted, #a0a0a0);
    font-size: 0.75rem;
}
@media (max-width: 39.99rem) {
    .run-attestation-row { grid-template-columns: minmax(0, 1fr); }
    .run-attestation-hint { grid-column: 1; }
}

/* Recette R-444: Metric, Series and Period share one line, each label beside its field; the fields take
   the width they are given instead of fixed widths. A phone stacks them, one field per line. */
.app-metrics-filter {
    flex: 1 1 16rem;
    min-width: 0;
}
.app-metrics-filter-period { flex: 0 1 10rem; }
.app-metrics-filter > .omni-label { white-space: nowrap; }
.app-metrics-filter-field {
    flex: 1 1 auto;
    min-width: 0;
}
@media (max-width: 39.99rem) {
    .app-metrics-filter,
    .app-metrics-filter-period { flex-basis: 100%; }
}

/* Recette R-468: the totals of the supervision page, as many tiles per row as fit. */
.supervision-totals {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
    gap: 0.5rem;
}
.supervision-totals-note {
    display: block;
}

/* R-455, R-472: the Performance tab's overview tiles, the same OE key-figure tile as the audience
   summary (the slowest route's name is the tile's detail line, which wraps anywhere); each fills its
   column, which the row stretches, so the four share one height (border-box since OE 1.4.0, R2-006). */
.app-performance-tile {
    height: 100%;
}

/* Recette R2-030: the bar of a running service action is thick enough to be seen (OE draws 2px). */
.service-actions-in-flight .omni-progress {
    --omni-progress-thickness: 4px;
}

/* Recette R2-059: the steps to a working mail server, on the page shown before the setup. */
.mail-setup-guide-steps {
    display: grid;
    gap: 0.25rem;
    margin: 0.25rem 0 0;
    padding-left: 1.25rem;
}

/* Recette R2-071: a run warning carries commit hashes and release names with no break point; on a
   phone they ran past the alert. Long words wrap, so the whole message stays readable. */
.run-warning-line {
    overflow-wrap: anywhere;
}
