/*
 * Refinery design tokens, ported from docs/design/Refinery.dc.html per
 * IMPLEMENTATION_PLAN.md's "Design system tokens" notes (Architecture
 * Decision 1: Twig + AssetMapper, no build step — plain CSS custom
 * properties, no Sass/PostCSS).
 */

:root {
    /* Typography */
    --font-body: 'Archivo', 'Helvetica Neue', Helvetica, sans-serif;
    --font-mono: 'IBM Plex Mono', monospace;

    /* Palette */
    --color-bg: #f1ecdf;
    --color-surface: #fffcf5;
    --color-sidebar-bg: #faf6ec;
    --color-header-bg: #f6f1e4;
    --color-row-hover: #f3edde;
    --color-border: #e3dac6;
    --color-border-muted: #ece4d2;
    --color-border-strong: #cfc5ae;
    --color-ink: #211a0e;
    --color-text-secondary: #46402f;
    --color-text-muted: #6f6757;
    --color-text-faint: #8c8271;
    /* Design's own dimmed/zero-state tone (`#c1b7a0` — disabled buttons,
       "not linked" labels, pagination arrows, "no overrides") — one shade
       lighter than `--color-text-faint`, distinct enough to be worth its
       own token rather than reusing that one for this specific treatment. */
    --color-text-dim: #c1b7a0;

    /* Severity */
    --color-severity-critical: #d84a31;
    --color-severity-critical-bg: #f9e2d6;
    --color-severity-high: #f76b15;
    --color-severity-high-bg: #fdeadb;
    --color-severity-medium: #a87716;
    --color-severity-medium-bg: #f6e8c8;
    --color-severity-low: #8c8271;
    --color-severity-low-bg: #e8dfcb;

    /* Status */
    --color-status-new: #3f382a;
    --color-status-new-bg: #ebe2cf;
    --color-status-acknowledged: #54718e;
    --color-status-acknowledged-bg: #e4e7e2;
    --color-status-snoozed: #9a6e14;
    --color-status-snoozed-bg: #f6e8c8;
    --color-status-accepted-risk: #6f6757;
    --color-status-accepted-risk-bg: #efe7d5;
    --color-status-ticketed: #211a0e;
    --color-status-ticketed-bg: #ebe2cf;
    --color-status-resolved: #4d7c3a;
    --color-status-resolved-bg: #e5eccf;

    /* Radius */
    --radius-panel: 2px;
    --radius-pill: 5px;

    --sidebar-width: 216px;
    --topbar-height: 52px;
}

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    background: var(--color-bg);
    font-family: var(--font-body);
    color: var(--color-ink);
    font-variant-numeric: tabular-nums;
    -webkit-font-smoothing: antialiased;
}

a {
    color: var(--color-ink);
    text-decoration: none;
}

a:hover {
    color: var(--color-text-secondary);
    text-decoration: underline;
}

select,
input,
textarea,
button {
    font-family: inherit;
    font-variant-numeric: tabular-nums;
    color: inherit;
}

/* ---- App shell ---- */

.app-shell {
    display: flex;
    height: 100vh;
    min-width: 1024px;
    overflow: hidden;
}

.sidebar {
    width: var(--sidebar-width);
    flex: none;
    background: var(--color-sidebar-bg);
    border-right: 1px solid var(--color-border);
    display: flex;
    flex-direction: column;
}

.sidebar__brand {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 16px 16px 14px;
}

.sidebar__logo {
    width: 26px;
    height: 26px;
    border-radius: var(--radius-panel);
    background: var(--color-ink);
    display: flex;
    align-items: center;
    justify-content: center;
    flex: none;
}

.sidebar__logo-icon {
    width: 18px;
    height: 18px;
    display: block;
}

.sidebar__logo-mark {
    fill: none;
    stroke: var(--color-surface);
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.sidebar__logo-mark--faint {
    opacity: 0.65;
}

.sidebar__brand-name {
    font-family: var(--font-body);
    font-size: 15px;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--color-ink);
}

.sidebar__nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 4px 8px;
}

.sidebar__nav-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 10px;
    border-radius: var(--radius-panel);
    font-family: var(--font-mono);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    cursor: pointer;
    color: var(--color-text-muted);
    font-weight: 400;
    text-decoration: none;
}

.sidebar__nav-item:hover {
    background: rgba(0, 0, 0, 0.05);
    text-decoration: none;
    color: var(--color-text-muted);
}

.sidebar__nav-item.is-active {
    background: rgba(0, 0, 0, 0.08);
    color: var(--color-ink);
    font-weight: 600;
}

.sidebar__nav-icon {
    width: 15px;
    height: 15px;
    flex: none;
    opacity: 0.85;
}

.sidebar__nav-icon path {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.3;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.sidebar__nav-label {
    flex: 1;
}

.sidebar__nav-badge {
    background: var(--color-ink);
    color: #fff;
    font-size: 11px;
    font-weight: 600;
    padding: 1px 7px;
    border-radius: var(--radius-panel);
}

.sidebar__spacer {
    flex: 1;
}

.sidebar__footer {
    padding: 12px 16px;
    border-top: 1px solid var(--color-border);
    font-size: 11px;
    color: var(--color-text-faint);
    line-height: 1.6;
}

.app-main {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.topbar {
    height: var(--topbar-height);
    flex: none;
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 0 20px;
    position: relative;
    z-index: 30;
}

.topbar__title {
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}

.topbar__spacer {
    flex: 1;
}

.user-menu {
    position: relative;
}

.user-menu__trigger {
    display: flex;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    padding: 4px 8px;
    border-radius: var(--radius-panel);
    background: none;
    border: none;
}

.user-menu__trigger:hover {
    background: var(--color-status-accepted-risk-bg);
}

.user-menu__avatar {
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: var(--color-border);
    color: var(--color-text-secondary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 600;
}

.user-menu__name {
    font-size: 12px;
    font-weight: 500;
}

.user-menu__caret {
    color: var(--color-text-faint);
    font-size: 10px;
}

.user-menu__dropdown {
    position: absolute;
    right: 0;
    top: 40px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
    width: 210px;
    padding: 6px;
    z-index: 40;
}

.user-menu__email {
    padding: 6px 10px;
    font-size: 12px;
    color: var(--color-text-muted);
    border-bottom: 1px solid var(--color-border-muted);
    margin-bottom: 4px;
    word-break: break-all;
}

.user-menu__signout {
    display: block;
    padding: 6px 10px;
    border-radius: 6px;
    font-size: 13px;
    color: var(--color-text-muted);
    cursor: pointer;
}

.user-menu__signout:hover {
    background: var(--color-status-accepted-risk-bg);
    color: var(--color-ink);
    text-decoration: none;
}

.app-content {
    flex: 1;
    overflow-y: auto;
    min-height: 0;
    padding: 20px 24px;
}

/*
 * Findings Inbox opts out of the standard padded/max-width content area —
 * per the design, its filter bar/table/hint bar fill the full remaining
 * viewport height edge-to-edge (see docs/design/Refinery.dc.html's
 * "Findings Inbox" screen).
 */
.app-content--flush {
    padding: 0;
    max-width: none;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/*
 * Fleet Dashboard/Rules Settings both go full-width as a deliberate deviation
 * from the design (which caps them at 1320px/980px respectively) — unlike
 * Findings, they keep the standard padded layout, just without the max-width
 * ceiling, so `--flush`'s padding/flex-column reset would be wrong here.
 */
.app-content--wide {
    max-width: none;
}

/* ---- Shared surfaces ---- */

.card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    overflow: hidden;
}

.badge {
    display: inline-block;
    font-size: 10px;
    font-weight: 600;
    border-radius: 4px;
    padding: 1px 5px;
}

.pill {
    display: inline-block;
    font-size: 11px;
    font-weight: 500;
    border-radius: var(--radius-pill);
    padding: 2px 8px;
}

/* ---- Projects Registry ---- */

.registry__head,
.registry__row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 11px 16px;
    font-size: 13px;
}

.registry__head {
    padding: 9px 16px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-header-bg);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.registry__row {
    border-bottom: 1px solid var(--color-border-muted);
}

.registry__row:last-child {
    border-bottom: none;
}

.registry__row:hover {
    background: var(--color-row-hover);
}

.registry__col--name {
    flex: 1.2;
    font-weight: 600;
}

.registry__col--customer {
    flex: 1;
    color: var(--color-text-muted);
    font-size: 12px;
}

.registry__col--hoster {
    width: 130px;
    color: var(--color-text-muted);
    font-size: 12px;
}

.registry__col--version {
    width: 150px;
    display: flex;
    align-items: center;
    gap: 6px;
}

.registry__col--uptime {
    width: 80px;
    text-align: right;
    font-size: 12px;
}

.registry__col--collector {
    width: 120px;
    text-align: right;
}

/*
 * Stage 6: "Open findings"/"Activity" columns, added between PHP and
 * Uptime per the design's exact column order. Widths match the design's
 * own `width:150px`/`width:104px` for these two columns.
 */
.registry__col--findings {
    width: 150px;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.registry__severity-chip {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 12px;
    color: var(--color-text-secondary);
}

.registry__all-clear {
    color: var(--color-status-resolved);
    font-weight: 500;
    font-size: 12px;
}

.registry__col--activity {
    width: 104px;
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: 12px;
    color: var(--color-text-muted);
}

.registry__activity-badge {
    font-size: 9px;
    font-weight: 600;
    color: var(--color-ink);
    background: var(--color-status-new-bg);
    border-radius: 4px;
    padding: 1px 5px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

/*
 * Stage 6: sortable column headers, same idiom `.findings__head-link`/
 * `.findings__head-arrow` already established for the Findings Inbox
 * (`App\Registry\ProjectsRegistryFilter` is this screen's counterpart of
 * `App\Findings\FindingsFilter`'s `sort`/`dir` half).
 */
.registry__head-link {
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    user-select: none;
    display: flex;
    align-items: center;
    gap: 3px;
}

.registry__head-link:hover {
    color: var(--color-ink);
}

.registry__head-link--active {
    color: var(--color-ink);
}

.registry__head-arrow {
    font-size: 8px;
}

.registry__version-value {
    white-space: nowrap;
}

.registry__empty {
    padding: 40px 16px;
    text-align: center;
    color: var(--color-text-faint);
    font-size: 13px;
}

.registry__project-link {
    color: inherit;
    text-decoration: none;
}

.registry__project-link:hover {
    text-decoration: underline;
}

.registry__footnote {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 12px;
}

/* ---- Customers Registry (Stage 2) ---- */

.customers__intro {
    font-size: 12px;
    color: var(--color-text-muted);
    line-height: 1.6;
    background: var(--color-header-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    padding: 10px 14px;
    margin-bottom: 14px;
}

.customers__head-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 0 10px;
}

.customers__count {
    font-size: 12px;
    color: var(--color-text-faint);
}

.customers__col--name {
    flex: 1.4;
    font-weight: 600;
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

.customers__col--health {
    width: 120px;
}

.customers__col--budget {
    width: 130px;
}

.customers__col--revenue {
    width: 170px;
}

.customers__revenue-amount {
    display: block;
    font-weight: 600;
    white-space: nowrap;
}

.customers__revenue-delta {
    display: block;
    font-size: 10px;
    margin-top: 1px;
    white-space: nowrap;
}

.customers__col--lead {
    width: 110px;
    color: var(--color-text-secondary);
    font-size: 12px;
}

.customers__col--projects {
    width: 140px;
}

.customers__col--actions {
    width: 96px;
    flex: none;
    display: flex;
    align-items: center;
    gap: 6px;
    justify-content: flex-end;
}

.customers__action-btn {
    display: inline-flex;
    align-items: center;
    font-size: 11px;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    border-radius: 5px;
    padding: 0 10px;
    height: 24px;
    cursor: pointer;
    color: var(--color-text-muted);
    text-decoration: none;
}

.customers__action-btn:hover {
    border-color: #b3a98f;
    color: var(--color-ink);
}

.customers__action-btn--danger {
    padding: 0 8px;
    border-color: #f0c6b8;
    color: var(--color-severity-critical);
}

.customers__action-btn--danger:hover {
    border-color: var(--color-severity-critical);
}

.customers__delete-form {
    display: inline-flex;
}

.customers__avatar {
    width: 28px;
    height: 28px;
    flex: none;
    border-radius: 50%;
    background: var(--color-border-muted);
    color: var(--color-text-secondary);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 700;
    object-fit: contain;
}

.customers__budget-label {
    display: block;
    font-size: 11px;
    font-weight: 500;
}

.customers__budget-track {
    height: 5px;
    background: var(--color-border-muted);
    border-radius: 3px;
    margin-top: 4px;
    overflow: hidden;
}

.customers__budget-bar {
    display: block;
    height: 100%;
    background: var(--color-status-resolved);
    border-radius: 3px;
}

.customers__no-budget {
    font-size: 11px;
    color: var(--color-text-faint);
}

.customers__project-count {
    display: block;
    font-size: 12px;
}

.customers__severity-chips {
    display: flex;
    gap: 6px;
    font-size: 10px;
    flex-wrap: wrap;
    margin-top: 2px;
}

.customers__severity-chip {
    font-weight: 600;
}

.customers__all-clear {
    color: var(--color-status-resolved);
    font-weight: 500;
}

/* ---- Placeholder ("coming soon") screens ---- */

.placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 320px;
}

.placeholder__card {
    text-align: center;
    padding: 40px 48px;
}

.placeholder__eyebrow {
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-text-faint);
}

.placeholder__title {
    font-size: 20px;
    font-weight: 600;
    margin-top: 8px;
    color: var(--color-ink);
}

.placeholder__body {
    margin-top: 8px;
    font-size: 13px;
    color: var(--color-text-muted);
}

/* ---- Findings Inbox ---- */

.findings-screen {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

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

.severity-dot {
    width: 8px;
    height: 8px;
    flex: none;
    border-radius: 50%;
    display: inline-block;
}

.btn {
    font-size: 13px;
    border: 1px solid var(--color-border-strong);
    background: var(--color-surface);
    border-radius: var(--radius-panel);
    padding: 8px 14px;
    cursor: pointer;
    color: var(--color-text-secondary);
}

.btn:hover {
    border-color: #b3a98f;
}

.btn--outline {
    display: inline-block;
}

.btn--muted {
    color: var(--color-text-muted);
}

.btn--stub {
    opacity: 0.55;
    cursor: not-allowed;
}

.btn--danger {
    border-color: #f0c6b8;
    color: var(--color-severity-critical);
}

.btn--danger:hover {
    border-color: var(--color-severity-critical);
}

/* QA fix: native `disabled` buttons/inputs (e.g. the ticketed Finding
   Detail overlay's Acknowledge/Snooze/Accept-risk row) don't otherwise look
   visually different from `.btn` — same treatment as `.btn--stub` above. */
.btn:disabled,
.finding-detail__note:disabled {
    opacity: 0.55;
    cursor: not-allowed;
}

.findings__filterbar {
    flex: none;
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
    padding: 10px 20px;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.findings__select {
    font-size: 12px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-panel);
    padding: 5px 8px;
    background: var(--color-surface);
    color: var(--color-text-secondary);
    cursor: pointer;
}

.findings__new-only {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: 12px;
    color: var(--color-text-secondary);
    cursor: pointer;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-panel);
    padding: 5px 10px;
}

.findings__filterbar-spacer {
    flex: 1;
}

.findings__count {
    font-size: 12px;
    color: var(--color-text-faint);
}

.findings__bulkbar {
    flex: none;
    background: #ebe2cf;
    border-bottom: 1px solid var(--color-border-strong);
    padding: 7px 20px;
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 12px;
}

.findings__bulkbar-count {
    font-weight: 600;
    color: var(--color-text-secondary);
}

.findings__bulkbar-btn {
    font-size: 12px;
    border: 1px solid #c9bfa8;
    background: var(--color-surface);
    border-radius: 6px;
    padding: 4px 10px;
    cursor: pointer;
    color: var(--color-text-secondary);
}

.findings__bulkbar-btn:hover {
    border-color: var(--color-ink);
}

.findings__bulkbar-clear {
    color: var(--color-text-muted);
    cursor: pointer;
    margin-left: 4px;
}

.findings__bulkbar-clear:hover {
    color: var(--color-ink);
}

/*
 * QA fix: the Findings Inbox table had no column header row at all — most
 * visibly, the bare `.finding-row__occ`/`.finding-row__age` numbers had no
 * label explaining what they were. Column widths mirror the `.finding-row__*`
 * cells below so the labels line up with their values; look/feel (mono,
 * uppercase, muted-on-header-bg) matches `.registry__head`/
 * `.audit-log__col--*` elsewhere in this file.
 */
.findings__head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 20px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-header-bg);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    flex: none;
}

.findings__head-col--checkbox {
    width: 14px;
    flex: none;
}

/*
 * Stage 5: wide enough to fit the "Severity" label (the design's dot
 * column is otherwise unlabeled) while still lining up with
 * `.finding-row__severity` below, which wraps the 8px `.severity-dot` at
 * the same width rather than resizing the shared `.severity-dot` class
 * itself (used un-wrapped on several other screens).
 */
.findings__head-col--severity {
    width: 64px;
    flex: none;
}

/*
 * Stage 5: the design has no standalone "Rule" header/column — the old
 * `.findings__head-col--rule` is gone, and this "Finding" column now
 * covers both the rule-key secondary text and the title
 * (`.finding-row__finding` below), same as it did before this stage.
 */
.findings__head-col--finding {
    flex: 1;
    min-width: 0;
}

.findings__head-link {
    text-decoration: none;
    color: inherit;
    cursor: pointer;
    user-select: none;
    display: inline-flex;
    align-items: center;
    gap: 3px;
}

.findings__head-link:hover {
    color: var(--color-ink);
}

.findings__head-link--active {
    color: var(--color-ink);
}

.findings__head-arrow {
    font-size: 8px;
}

.findings__head-col--project {
    flex: none;
    max-width: 140px;
}

.findings__head-col--occ {
    width: 30px;
    flex: none;
    text-align: right;
}

.findings__head-col--age {
    width: 36px;
    flex: none;
    text-align: right;
}

.findings__head-col--status {
    flex: none;
    width: 90px;
}

.findings__head-col--actions {
    flex: none;
    width: 176px;
}

.findings__rows {
    flex: 1;
    overflow-y: auto;
    background: var(--color-surface);
}

.findings__empty {
    padding: 60px 20px;
    text-align: center;
    color: var(--color-text-faint);
    font-size: 13px;
}

.findings__empty-reset {
    margin-top: 14px;
}

.findings__pagination {
    flex: none;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 8px 20px;
    border-top: 1px solid var(--color-border);
    font-size: 12px;
    color: var(--color-text-muted);
}

.finding-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 20px;
    height: 40px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 13px;
    transition: opacity 260ms ease, transform 260ms ease, background-color 0.1s;
}

.finding-row:hover {
    background: var(--color-row-hover);
}

.finding-row--selected {
    background: var(--color-row-hover);
    box-shadow: inset 2px 0 0 var(--color-ink);
}

.finding-row--exiting {
    opacity: 0;
    transform: translateX(12px);
    pointer-events: none;
}

.finding-row__checkbox {
    width: 14px;
    height: 14px;
    flex: none;
    accent-color: var(--color-ink);
    cursor: pointer;
}

/*
 * Stage 5: wraps the shared `.severity-dot` at the same 64px width as
 * `.findings__head-col--severity` above so the dot lines up under its new
 * "Severity" header label — `.severity-dot` itself stays untouched since
 * it's also used un-wrapped on several other screens (Customers, Dashboard,
 * Rules, Finding Detail).
 */
.finding-row__severity {
    width: 64px;
    flex: none;
    display: flex;
    align-items: center;
}

/*
 * Stage 5: the rule key and title now share one Finding cell (no separate
 * "Rule" column) — `.finding-row__rule` lost its fixed 58px width since it
 * no longer has to line up with a header column of its own.
 */
.finding-row__finding {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
    min-width: 0;
}

.finding-row__rule {
    font-size: 11px;
    font-weight: 600;
    color: var(--color-text-muted);
    flex: none;
    font-family: var(--font-mono);
}

.finding-row__title {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 500;
    color: var(--color-ink);
}

.finding-row__project {
    font-size: 11px;
    color: var(--color-text-muted);
    background: var(--color-status-accepted-risk-bg);
    border-radius: 5px;
    padding: 2px 7px;
    flex: none;
    max-width: 140px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.finding-row__environment {
    font-size: 10px;
    color: var(--color-text-faint);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    flex: none;
}

.finding-row__occ {
    font-size: 11px;
    color: var(--color-text-faint);
    width: 30px;
    text-align: right;
    flex: none;
}

.finding-row__age {
    font-size: 12px;
    color: var(--color-text-faint);
    width: 36px;
    text-align: right;
    flex: none;
}

.finding-row__actions {
    display: flex;
    gap: 4px;
    flex: none;
    width: 176px;
    justify-content: flex-end;
}

.finding-row__actions form {
    display: contents;
}

.finding-row__action-btn {
    font-size: 11px;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    border-radius: 5px;
    padding: 0 8px;
    height: 22px;
    cursor: pointer;
    color: var(--color-text-muted);
    opacity: 0.8;
}

.finding-row__action-btn:hover {
    opacity: 1;
    border-color: #b3a98f;
    color: var(--color-ink);
}

a.finding-row__action-btn {
    display: inline-flex;
    align-items: center;
    text-decoration: none;
}

/*
 * Automated root-cause investigation feature (Task 12): the row's "Ticket"
 * action while `App\Enum\RootCauseStatus::Queued`/`Running` — a non-
 * interactive stand-in (a `<span>`, not a disabled `<button>`/`<a>`, since
 * there's nothing to click through to yet) rather than hiding the action
 * outright, so the row still visibly accounts for every finding's ticket
 * affordance instead of silently dropping it.
 */
.finding-row__action-btn--disabled {
    cursor: default;
    opacity: 0.55;
}

.finding-row__action-btn--disabled:hover {
    opacity: 0.55;
    border-color: var(--color-border);
    color: var(--color-text-muted);
}

/* The `Failed` investigation marker on an otherwise-normal Ticket action. */
.finding-row__action-warning {
    color: var(--color-severity-high);
}

/* ---- Finding Detail overlay ---- */

turbo-frame#finding-detail-frame:empty {
    display: none;
}

turbo-frame#finding-detail-frame:not(:empty) {
    position: fixed;
    inset: 0;
    z-index: 41;
    background: rgba(33, 26, 14, 0.18);
    display: flex;
    align-items: center;
    justify-content: center;
}

.finding-detail {
    width: 900px;
    max-width: 94vw;
    max-height: 88vh;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.2);
    border-radius: var(--radius-panel);
    display: flex;
    flex-direction: column;
}

.finding-detail__header {
    flex: none;
    padding: 16px 20px;
    border-bottom: 1px solid var(--color-border);
}

.finding-detail__header-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.finding-detail__sev-label {
    font-size: 12px;
    font-weight: 600;
}

.finding-detail__rule {
    font-family: var(--font-mono);
    font-size: 12px;
    color: var(--color-text-muted);
}

.finding-detail__id {
    font-size: 12px;
    color: var(--color-text-faint);
    /* Now a <button> (copy-link affordance), not a plain <span> — reset to
       look identical at rest, with just a hover/focus cue that it's
       clickable. */
    background: none;
    border: none;
    padding: 0;
    font-family: inherit;
    cursor: pointer;
}

.finding-detail__id:hover,
.finding-detail__id:focus-visible {
    color: var(--color-text-muted);
    text-decoration: underline;
}

.finding-detail__spacer {
    flex: 1;
}

.finding-detail__close {
    cursor: pointer;
    color: var(--color-text-faint);
    font-size: 16px;
    padding: 2px 6px;
    border-radius: 5px;
    background: none;
    border: none;
}

.finding-detail__close:hover {
    background: var(--color-status-accepted-risk-bg);
    color: var(--color-ink);
}

.finding-detail__title {
    font-size: 15px;
    font-weight: 600;
    margin-top: 8px;
    line-height: 1.4;
}

.finding-detail__meta {
    display: flex;
    gap: 14px;
    margin-top: 8px;
    font-size: 12px;
    color: var(--color-text-muted);
}

/*
 * QA fix: prominent link to the real GitLab issue once a Ticket exists for
 * this Finding (`ticket` in templates/findings/_detail.html.twig) — sits
 * between the header and the evidence/history body, same full-width flex
 * child treatment as `.finding-detail__header`/`.finding-detail__footer`.
 */
.finding-detail__ticket-banner {
    flex: none;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 20px;
    background: var(--color-status-ticketed-bg);
    border-bottom: 1px solid var(--color-border);
    font-size: 12px;
}

.finding-detail__ticket-banner-label {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--color-text-muted);
}

.finding-detail__ticket-banner-link {
    font-weight: 600;
    color: var(--color-status-acknowledged);
}

.finding-detail__ticket-banner-link:hover {
    text-decoration: underline;
}

.finding-detail__ticket-banner-state {
    color: var(--color-text-faint);
}

.finding-detail__body {
    flex: 1;
    overflow-y: auto;
    padding: 18px 22px;
    display: grid;
    grid-template-columns: 1.6fr 1fr;
    gap: 26px;
    align-items: start;
}

/*
 * QA fix: a grid item's default min-width is `auto`, not `0` - its
 * content's intrinsic minimum size (here, an unbroken long file-path
 * string inside a TW-001 finding's stack trace, e.g.
 * `.evidence-stacktrace__location`) can force this column wider than its
 * `1.6fr`/`1fr` track sizing, which horizontally overflows the whole modal
 * before any inner `overflow: hidden`/`text-overflow: ellipsis` rule ever
 * gets a chance to apply. Reset to 0 so column width is governed by the
 * grid track, not the widest unbreakable word inside it.
 */
.finding-detail__evidence,
.finding-detail__history,
.finding-detail__evidence-column {
    min-width: 0;
}

/*
 * QA fix: holds the root-cause investigation card and the raw evidence
 * card together as ONE grid item in `.finding-detail__body`'s 1.6fr/1fr
 * column split - see `templates/findings/_detail.html.twig`'s own comment
 * at this wrapper's usage for why (without it, a 3rd top-level grid child
 * got auto-placed into the narrow 1fr column meant for History, visibly
 * cramping the evidence card's stack trace).
 */
.finding-detail__evidence-column {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.finding-detail__section-label {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    margin-bottom: 8px;
}

.finding-detail__history-row {
    display: flex;
    gap: 10px;
    font-size: 12px;
    padding: 6px 0;
    border-bottom: 1px solid var(--color-border-muted);
}

.finding-detail__history-age {
    color: var(--color-text-faint);
    width: 70px;
    flex: none;
}

.finding-detail__footer {
    flex: none;
    border-top: 1px solid var(--color-border);
    padding: 12px 20px;
    display: flex;
    gap: 8px;
    align-items: center;
}

.finding-detail__readonly {
    font-size: 12px;
    color: var(--color-text-faint);
}

/* Slack reminder send failure - reuses Severity::Critical's own red
   (#d84a31), same color this screen already uses for a critical severity
   dot, rather than inventing a new error token. */
.finding-detail__readonly--error {
    color: #d84a31;
}

.finding-detail__action-form {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
}

.finding-detail__note {
    flex: 1;
    font-size: 12px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-panel);
    padding: 7px 10px;
    background: var(--color-surface);
}

/* ---- Ticket Draft Modal (Stage 3) ---- */

.ticket-draft-modal {
    position: fixed;
    inset: 0;
    z-index: 51;
    background: rgba(33, 26, 14, 0.28);
    display: flex;
    align-items: center;
    justify-content: center;
}

.ticket-draft-modal[hidden] {
    display: none;
}

.ticket-draft-modal__panel {
    width: 640px;
    max-width: 94vw;
    max-height: 88vh;
    overflow-y: auto;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.25);
    border-radius: var(--radius-panel);
    padding: 18px 20px;
}

.ticket-draft-modal__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-weight: 600;
    margin-bottom: 14px;
}

.ticket-draft-modal__close {
    cursor: pointer;
    color: var(--color-text-faint);
    font-size: 16px;
    padding: 2px 6px;
    border-radius: 5px;
    background: none;
    border: none;
}

.ticket-draft-modal__close:hover {
    background: var(--color-status-accepted-risk-bg);
    color: var(--color-ink);
}

.ticket-draft-modal__loading {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 40px 0;
    justify-content: center;
    color: var(--color-text-muted);
    font-size: 13px;
}

/*
 * QA fix: this unconditional `display: flex` outranked the browser's default
 * `[hidden] { display: none }` UA rule (author styles win cascade ties
 * against UA styles regardless of specificity), so the Stimulus controller
 * setting `loadingTarget.hidden = true` once loading finished never actually
 * hid the spinner — it kept rendering on top of/alongside the completed
 * draft or the error/retry message. Same fix already applied to the parent
 * `.ticket-draft-modal[hidden]` rule above; this just extends it to the
 * spinner specifically, since that's the element the controller toggles.
 */
.ticket-draft-modal__loading[hidden] {
    display: none;
}

.ticket-draft-modal__spinner {
    width: 16px;
    height: 16px;
    border: 2px solid var(--color-border-strong);
    border-top-color: var(--color-ink);
    border-radius: 50%;
    animation: ticket-draft-spin 0.8s linear infinite;
}

@keyframes ticket-draft-spin {
    to {
        transform: rotate(360deg);
    }
}

.ticket-draft-modal__error {
    padding: 24px 0;
    text-align: center;
    color: var(--color-text-secondary);
    font-size: 13px;
}

.ticket-draft-modal__error p {
    margin-bottom: 10px;
}

.ticket-draft-modal__field {
    display: block;
    margin-bottom: 12px;
    font-size: 12px;
    color: var(--color-text-muted);
}

.ticket-draft-modal__field span {
    display: block;
    margin-bottom: 4px;
    font-weight: 500;
}

.ticket-draft-modal__field input,
.ticket-draft-modal__field select,
.ticket-draft-modal__field textarea {
    width: 100%;
    font-size: 13px;
    font-family: inherit;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-panel);
    padding: 8px 10px;
    background: var(--color-surface);
    color: var(--color-ink);
    resize: vertical;
}

.ticket-draft-modal__estimate {
    font-size: 12px;
    color: var(--color-text-muted);
    margin-bottom: 10px;
}

.ticket-draft-modal__similar {
    font-size: 12px;
    margin-bottom: 14px;
}

.ticket-draft-modal__similar a {
    color: var(--color-ink);
}

.ticket-draft-modal__submit-error {
    font-size: 12px;
    color: #d84a31;
    margin-bottom: 10px;
}

.ticket-draft-modal__actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

/* ---- Evidence blocks ---- */

.evidence-card {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    padding: 12px;
}

.evidence-kv {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    font-size: 12px;
    padding: 5px 0;
}

.evidence-kv span {
    color: var(--color-text-faint);
}

.evidence-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    margin-top: 10px;
    font-size: 12px;
}

.evidence-ops-regression__summary {
    font-size: 13px;
    line-height: 1.5;
    margin: 0;
}

.evidence-cve__head {
    display: flex;
    align-items: center;
    gap: 8px;
}

.evidence-cve__id {
    font-family: var(--font-mono);
    font-size: 13px;
    font-weight: 600;
}

.evidence-cve__cvss {
    font-size: 11px;
    font-weight: 600;
    color: #fff;
    border-radius: 4px;
    padding: 2px 7px;
}

.evidence-cve__summary {
    font-size: 12px;
    color: var(--color-text-secondary);
    margin-top: 8px;
    line-height: 1.55;
}

.evidence-eol {
    display: flex;
    align-items: center;
    gap: 16px;
}

.evidence-eol__ring {
    width: 76px;
    height: 76px;
    border-radius: 50%;
    border-width: 5px;
    border-style: solid;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    flex: none;
}

.evidence-eol__days {
    font-size: 17px;
    font-weight: 700;
}

.evidence-eol__unit {
    font-size: 9px;
    color: var(--color-text-muted);
    text-transform: uppercase;
}

.evidence-eol__meta {
    font-size: 12px;
    line-height: 1.7;
    color: var(--color-text-secondary);
}

.evidence-eol__meta span {
    color: var(--color-text-faint);
}

.evidence-dep-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 11px;
}

.evidence-dep-table th {
    text-align: left;
    font-size: 10px;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    background: var(--color-header-bg);
    padding: 6px 8px;
    border-bottom: 1px solid var(--color-border);
}

.evidence-dep-table td {
    padding: 6px 8px;
    border-bottom: 1px solid var(--color-border-muted);
}

.evidence-dep-table__pkg {
    font-family: var(--font-mono);
}

.evidence-collector__head {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    font-weight: 600;
}

.evidence-collector__dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--color-severity-high);
}

.evidence-generic__empty {
    font-size: 12px;
    color: var(--color-text-faint);
}

/*
 * QA fix: TW-001 (Tideways exception) evidence — `App\Findings\EvidenceType`
 * previously had no `TW-` case, so this fell through to the generic
 * key/value dump, rendering nested arrays (`lastStackTrace`, `transactions`,
 * `environments`) as raw `json_encode` strings. `.evidence-stacktrace`
 * mirrors `.evidence-dep-table`'s bordered/mono treatment above, but as a
 * scrollable list rather than a table, since a stack frame is a single
 * file:line + function rather than tabular columns.
 */
.evidence-tideways__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.evidence-tideways__type {
    font-family: var(--font-mono);
    font-size: 13px;
    font-weight: 600;
}

.evidence-tideways__occurrences {
    font-size: 11px;
    color: var(--color-text-faint);
    flex: none;
}

.evidence-tideways__message {
    font-size: 12px;
    color: var(--color-text-secondary);
    margin-top: 8px;
    line-height: 1.55;
}

.evidence-tideways__stack-label {
    font-size: 10px;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    margin-top: 14px;
    margin-bottom: 6px;
}

.evidence-stacktrace {
    list-style: none;
    margin: 0;
    padding: 0;
    font-family: var(--font-mono);
    font-size: 11px;
    background: var(--color-header-bg);
    border: 1px solid var(--color-border-muted);
    border-radius: var(--radius-panel);
    max-height: 220px;
    overflow-y: auto;
}

.evidence-stacktrace__frame {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 8px;
    padding: 5px 8px;
    border-bottom: 1px solid var(--color-border-muted);
    overflow: hidden;
}

.evidence-stacktrace__frame:last-child {
    border-bottom: none;
}

.evidence-stacktrace__location {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    color: var(--color-ink);
}

.evidence-stacktrace__fn {
    color: var(--color-text-faint);
    flex: none;
}

.evidence-stacktrace__more {
    padding: 5px 8px;
    color: var(--color-text-faint);
    font-style: italic;
}

/*
 * Automated root-cause investigation feature (Task 12) — a separate
 * evidence-style card from the raw signal evidence above it
 * (`findings/evidence/_root_cause.html.twig`), same `.evidence-card`/
 * `.evidence-grid`/`.evidence-kv` shell as every other partial in this
 * section. Not in either design prototype (postdates both exports).
 */
.evidence-root-cause__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.evidence-root-cause__title {
    font-size: 13px;
    font-weight: 600;
}

.evidence-root-cause__status {
    font-size: 12px;
    color: var(--color-text-secondary);
    margin: 10px 0 0;
}

.evidence-root-cause__status--failed {
    color: var(--color-severity-high);
}

/*
 * `finding.rootCauseReport` is now real, rendered HTML (`league/commonmark`
 * via the `investigation_report_html` Twig filter, see `App\Markdown\
 * InvestigationReportRenderer`'s own docblock) — corrected after real user
 * feedback ("weird formed markdown", literal `##`/`**` syntax rendering
 * as-is under the old `<pre>` + `white-space: pre-wrap` approach this
 * replaces. Styled minimally (this app has no other rendered-markdown
 * surface to match conventions against) rather than relying on any
 * browser/user-agent default for headings/lists/code.
 *
 * `max-height`/`overflow-y: auto` mirror `.evidence-stacktrace` above —
 * same bug class (unbounded content blowing out the Finding Detail modal,
 * first hit with TW-001's stack trace) applies here too: a real
 * Claude Code-authored report can easily run several thousand characters.
 * Taller than the stack trace's 220px since this is prose, not a dense
 * frame list, so it benefits from more breathing room before it scrolls.
 */
.evidence-root-cause__report {
    font-family: inherit;
    font-size: 12px;
    line-height: 1.55;
    color: var(--color-text-secondary);
    word-break: break-word;
    margin: 10px 0 0;
    max-height: 360px;
    overflow-y: auto;
}

.evidence-root-cause__report :first-child {
    margin-top: 0;
}

.evidence-root-cause__report :last-child {
    margin-bottom: 0;
}

.evidence-root-cause__report h1,
.evidence-root-cause__report h2,
.evidence-root-cause__report h3 {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-ink);
    margin: 14px 0 6px;
}

.evidence-root-cause__report p {
    margin: 0 0 10px;
}

.evidence-root-cause__report ul,
.evidence-root-cause__report ol {
    margin: 0 0 10px;
    padding-left: 20px;
}

.evidence-root-cause__report li {
    margin-bottom: 4px;
}

.evidence-root-cause__report code {
    font-family: var(--font-mono);
    font-size: 11px;
    background: var(--color-header-bg);
    border-radius: 3px;
    padding: 1px 4px;
}

.evidence-root-cause__report pre {
    font-family: var(--font-mono);
    font-size: 11px;
    background: var(--color-header-bg);
    border: 1px solid var(--color-border-muted);
    border-radius: var(--radius-panel);
    padding: 8px;
    overflow-x: auto;
    margin: 0 0 10px;
}

.evidence-root-cause__report pre code {
    background: none;
    padding: 0;
}

.evidence-root-cause__issue-link {
    display: inline-block;
    font-size: 12px;
    margin-top: 10px;
}

/* ---- Rules Settings ---- */

/*
 * `.rules-screen` itself carries no styles — it's purely the
 * `data-controller="rule-drawer"` mount point
 * (`templates/rules/index.html.twig`). IMPLEMENTATION_PLAN.md Stage
 * 10/Decision 1 moved this screen's width to `.app-content--wide` (see that
 * class's docblock), replacing the 980px cap this selector used to carry.
 */

.rules__banner {
    background: var(--color-header-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    padding: 12px 16px;
    margin-bottom: 18px;
    display: flex;
    gap: 12px;
}

.rules__banner-icon {
    width: 16px;
    height: 16px;
    flex: none;
    margin-top: 1px;
    color: var(--color-text-muted);
    fill: none;
    stroke: currentColor;
    stroke-width: 1.3;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.rules__banner-text {
    font-size: 12px;
    line-height: 1.65;
    color: var(--color-text-secondary);
    flex: 1;
}

.rules__banner-action {
    flex: none;
    align-self: center;
    white-space: nowrap;
}

.rules__group {
    margin-bottom: 18px;
}

.rules__group-label {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.12em;
    margin-bottom: 8px;
}

.rules__row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 13px;
}

.rules__row:last-child {
    border-bottom: none;
}

.rules__row:hover {
    background: var(--color-row-hover);
}

.rules__key {
    font-family: var(--font-mono);
    font-size: 12px;
    font-weight: 600;
    color: var(--color-ink);
    width: 70px;
    flex: none;
}

.rules__info {
    flex: 1;
    min-width: 0;
}

.rules__name {
    font-weight: 500;
}

.rules__desc {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 1px;
}

.rules__severity {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 11px;
    color: var(--color-text-muted);
    width: 90px;
    flex: none;
}

.rules__overrides-link,
.rules__overrides-count {
    font-size: 11px;
    width: 96px;
    text-align: right;
    flex: none;
}

.rules__overrides-link {
    color: var(--color-ink);
    cursor: pointer;
}

.rules__overrides-link:hover {
    text-decoration: underline;
}

.rules__overrides-count {
    color: var(--color-text-faint);
}

/*
 * "0 overrides" → "no overrides" (IMPLEMENTATION_PLAN.md Stage 10 fix) gets
 * the design's own dimmed treatment instead of the link/count's normal
 * color — a project with no overrides tuned is a non-event, not something
 * worth the same visual weight as one that actually has per-project tuning.
 */
.rules__overrides-link--zero,
.rules__overrides-count--zero {
    color: var(--color-text-dim);
}

.rules__toggle-form {
    flex: none;
}

.rules__toggle,
.rules__toggle--readonly {
    width: 32px;
    height: 18px;
    border-radius: 999px;
    background: var(--color-border-strong);
    position: relative;
    display: inline-block;
    flex: none;
    border: none;
    padding: 0;
    transition: background 0.15s;
}

.rules__toggle {
    cursor: pointer;
}

.rules__toggle--readonly {
    /* QA fix: was `cursor: default; opacity: 0.7`, visually near-identical
       to the interactive `.rules__toggle` — a pm/dev viewer had no clear
       cue this control isn't clickable. Reuses this app's existing
       disabled-state convention (`.btn:disabled`/`.btn--stub` above)
       instead of inventing a new one. */
    cursor: not-allowed;
    opacity: 0.55;
}

.rules__toggle.is-on,
.rules__toggle--readonly.is-on {
    background: var(--color-ink);
}

.rules__toggle-knob {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--color-surface);
    transition: left 0.15s;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}

.rules__toggle.is-on .rules__toggle-knob,
.rules__toggle--readonly.is-on .rules__toggle-knob {
    left: 16px;
}

.rules__empty {
    padding: 40px 16px;
    text-align: center;
    color: var(--color-text-faint);
    font-size: 13px;
}

/* ---- Suggest Rule Modal (Stage 4) ---- */

.suggest-rule-modal {
    position: fixed;
    inset: 0;
    z-index: 51;
    background: rgba(33, 26, 14, 0.28);
    display: flex;
    align-items: center;
    justify-content: center;
}

.suggest-rule-modal[hidden] {
    display: none;
}

.suggest-rule-modal__hint {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 4px;
}

/*
 * QA fix (IMPLEMENTATION_PLAN.md Stage 10): this modal reuses
 * `.customer-modal__panel`'s shell (see `templates/rules/_suggest_modal
 * .html.twig`'s docblock) but the design's own Suggest Rule Modal is 480px,
 * not the Customer/Staff Modal's 460px. Field labels no longer need a
 * per-modal override — `.wizard__label`'s own default now matches the
 * design's mono/uppercase/10px treatment everywhere it's used (QA
 * polish pass: the Customer/Staff Modal's plain-label look was the actual
 * design-fidelity gap, not this modal's mono labels).
 */
.customer-modal__panel.suggest-rule-modal__panel {
    width: 480px;
}

/* ---- Rule overrides drawer ---- */

turbo-frame#rule-overrides-drawer:empty {
    display: none;
}

turbo-frame#rule-overrides-drawer:not(:empty) {
    position: fixed;
    inset: 0;
    z-index: 44;
    background: rgba(33, 26, 14, 0.18);
    display: flex;
    justify-content: flex-end;
}

.rule-drawer {
    /* QA fix (IMPLEMENTATION_PLAN.md Stage 10): was 420px/92vw — the
       design's own "Rule Details" panel is 560px/94vw, and the Stage 10
       content this drawer grew (description/data source/schedule
       cards/thresholds/recent findings, on top of the original Stage 4
       override CRUD) genuinely needs the extra width. */
    width: 560px;
    max-width: 94vw;
    height: 100%;
    background: var(--color-surface);
    box-shadow: -12px 0 40px rgba(0, 0, 0, 0.14);
    display: flex;
    flex-direction: column;
}

.rule-drawer__header {
    flex: none;
    padding: 16px 20px;
    border-bottom: 1px solid var(--color-border);
    display: flex;
    align-items: center;
    gap: 8px;
}

.rule-drawer__key {
    font-family: var(--font-mono);
    font-size: 13px;
    font-weight: 600;
    color: var(--color-ink);
}

.rule-drawer__name {
    font-size: 14px;
    font-weight: 600;
}

.rule-drawer__severity {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 11px;
    color: var(--color-text-muted);
}

.rule-drawer__enabled-badge {
    font-size: 11px;
    font-weight: 500;
    color: var(--color-text-faint);
    background: var(--color-status-accepted-risk-bg);
    border-radius: var(--radius-pill);
    padding: 2px 9px;
}

.rule-drawer__enabled-badge--on {
    color: var(--color-status-resolved);
    background: var(--color-status-resolved-bg);
}

.rule-drawer__spacer {
    flex: 1;
}

.rule-drawer__close {
    cursor: pointer;
    color: var(--color-text-faint);
    font-size: 16px;
    padding: 2px 6px;
    border-radius: 5px;
    background: none;
    border: none;
}

.rule-drawer__close:hover {
    background: var(--color-status-accepted-risk-bg);
}

.rule-drawer__body {
    flex: 1;
    overflow-y: auto;
    padding: 16px 20px;
}

.rule-drawer__overrides-label {
    margin-top: 22px;
}

.rule-drawer__description {
    font-size: 12.5px;
    line-height: 1.65;
    color: var(--color-text-secondary);
    margin: 8px 0 0;
}

.rule-drawer__meta-cards {
    display: flex;
    gap: 8px;
    margin-top: 12px;
}

.rule-drawer__meta-card {
    flex: 1;
    background: var(--color-header-bg);
    border-radius: var(--radius-panel);
    padding: 9px 12px;
}

.rule-drawer__meta-value {
    font-size: 12px;
    font-weight: 500;
    margin-top: 3px;
}

.rule-drawer__recent-row {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 8px 4px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 12px;
    color: var(--color-ink);
    text-decoration: none;
}

.rule-drawer__recent-row:hover {
    background: var(--color-row-hover);
}

.rule-drawer__recent-title {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.rule-drawer__recent-project {
    color: var(--color-text-faint);
    font-size: 11px;
    flex: none;
}

.rule-drawer__form {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 10px;
}

.rule-drawer__form select {
    flex: 1;
    min-width: 120px;
    font-size: 12px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-panel);
    padding: 6px 8px;
    background: var(--color-surface);
}

.rule-drawer__config {
    width: 100%;
    font-family: var(--font-mono);
    font-size: 11px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-panel);
    padding: 6px 8px;
    background: var(--color-surface);
    resize: vertical;
}

.rule-drawer__config-reference {
    list-style: none;
    margin: 6px 0 0;
    padding: 0;
    font-size: 11px;
    color: var(--color-text-muted);
}

.rule-drawer__config-reference li {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    padding: 2px 0;
}

.rule-drawer__config-reference-default {
    color: var(--color-text-faint);
}

.rule-drawer__override {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    padding: 10px 12px;
    margin-top: 10px;
}

.rule-drawer__override-name {
    font-size: 12px;
    font-weight: 600;
}

.rule-drawer__empty {
    font-size: 12px;
    color: var(--color-text-faint);
    margin-top: 10px;
}

/* ---- Settings (IMPLEMENTATION_PLAN.md Stage 12: width fix + 5 sections) ---- */

.settings__card {
    max-width: 980px;
    padding: 20px 24px;
}

.settings__intro {
    font-size: 12px;
    color: var(--color-text-muted);
    line-height: 1.6;
    background: var(--color-header-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    padding: 10px 14px;
    margin-bottom: 14px;
}

.settings__grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
    margin-bottom: 14px;
}

.settings__section {
    margin-bottom: 14px;
}

/*
 * QA polish fix (IMPLEMENTATION_PLAN.md Stage 12): with the default
 * `align-items: start`, a two-column row's shorter card stopped well
 * above its taller neighbor, leaving asymmetric whitespace that broke the
 * card rhythm the rest of the page establishes. Stretching both cards to
 * the row's tallest, then pinning each card's footnote to the bottom via
 * the flex column below, keeps card *heights* equal without visually
 * padding out a card's actual content.
 */
.settings__grid .settings__section {
    margin-bottom: 0;
    display: flex;
    flex-direction: column;
}


.settings__section-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--color-border);
}

.settings__section-title {
    font-size: 13px;
    font-weight: 600;
}

.settings__section-subtitle {
    font-size: 11px;
    color: var(--color-text-faint);
}

.settings__section-actions {
    margin-left: auto;
}

.settings__section-body {
    padding: 12px 16px;
}

.settings__section-footnote {
    padding: 10px 16px 12px;
    font-size: 11px;
    color: var(--color-text-faint);
    line-height: 1.5;
    margin-top: auto;
}

.settings__row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 9px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 12px;
}

.settings__row:last-of-type {
    border-bottom: none;
}

.settings__row--head {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    padding: 8px 16px;
}

.settings__col--name {
    flex: 1;
    min-width: 0;
    font-weight: 500;
}

.settings__col--detail {
    flex: 2;
    color: var(--color-text-muted);
    font-size: 12px;
}

.settings__col--severity {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 6px;
    font-weight: 500;
}

.settings__col--toggle {
    width: 110px;
    flex: none;
}

.settings__col--project {
    width: 170px;
    flex: none;
    color: var(--color-text-muted);
}

.settings__col--date {
    width: 110px;
    flex: none;
    color: var(--color-text-faint);
}

.settings__col--status {
    width: 90px;
    flex: none;
}

.settings__col--actions {
    width: 70px;
    flex: none;
    text-align: right;
}

.settings__col--retention-select {
    width: 110px;
    flex: none;
}

.settings__col--retention-desc {
    flex: 1;
    color: var(--color-text-muted);
    font-size: 12px;
}

.settings__retention-name {
    display: block;
    font-weight: 500;
    color: var(--color-ink);
    font-size: 12px;
    margin-bottom: 1px;
}

.settings__empty {
    padding: 12px 16px;
    font-size: 12px;
    color: var(--color-text-faint);
}

.settings__field-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    padding: 12px 16px;
}

.settings__field-label {
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: 500;
    color: var(--color-text-faint);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    margin-bottom: 5px;
}

.settings__select {
    width: 100%;
    font-size: 12px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-panel);
    padding: 6px 8px;
    background: var(--color-surface);
    color: var(--color-text-secondary);
    cursor: pointer;
}

.settings__col--retention-select .settings__select {
    padding: 5px 8px;
}

.settings__toggle-form {
    flex: none;
}

.settings__toggle {
    width: 32px;
    height: 18px;
    border-radius: 999px;
    background: var(--color-border-strong);
    position: relative;
    display: inline-block;
    flex: none;
    border: none;
    padding: 0;
    cursor: pointer;
    transition: background 0.15s;
}

.settings__toggle.is-on {
    background: var(--color-ink);
}

.settings__toggle-knob {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--color-surface);
    transition: left 0.15s;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}

.settings__toggle.is-on .settings__toggle-knob {
    left: 16px;
}

.settings__channel-choice {
    display: flex;
    gap: 8px;
}

.settings__channel-btn {
    font-size: 12px;
    border: 1px solid var(--color-border-strong);
    background: var(--color-surface);
    border-radius: var(--radius-panel);
    padding: 5px 12px;
    cursor: pointer;
    font-weight: 500;
    color: var(--color-text-secondary);
}

.settings__channel-btn.is-active {
    border-color: var(--color-ink);
    color: var(--color-ink);
    font-weight: 600;
}

.settings__form-actions {
    padding: 0 16px 10px;
}

.settings__section-body--tight {
    padding-top: 0;
}

/* ---- Audit Log ---- */

.audit-log__filter-field {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--color-text-secondary);
}

.audit-log__filter-label {
    color: var(--color-text-muted);
}

.audit-log__head,
.audit-log__row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 11px 16px;
    font-size: 13px;
}

.audit-log__head {
    padding: 9px 16px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-header-bg);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.audit-log__row {
    border-bottom: 1px solid var(--color-border-muted);
}

.audit-log__row:last-child {
    border-bottom: none;
}

.audit-log__row:hover {
    background: var(--color-row-hover);
}

.audit-log__col--time {
    width: 140px;
    flex: none;
    color: var(--color-text-muted);
    font-size: 12px;
    font-family: var(--font-mono);
}

.audit-log__col--actor {
    width: 140px;
    flex: none;
}

.audit-log__col--action {
    width: 160px;
    flex: none;
}

.audit-log__col--target {
    width: 220px;
    flex: none;
    display: flex;
    align-items: center;
    gap: 6px;
    overflow: hidden;
}

/*
 * QA fix: long finding titles here had no width constraint or ellipsis at
 * all, so they overflowed past this column's fixed width and visibly
 * collided mid-word with the adjacent Detail column's text. `min-width: 0`
 * is required alongside `flex: 1` for a flex child to actually shrink below
 * its content's intrinsic width instead of overflowing regardless of
 * `text-overflow`.
 */
.audit-log__entity-label {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.audit-log__col--detail {
    flex: 1;
    color: var(--color-text-muted);
    font-size: 12px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.audit-log__entity-type {
    background: var(--color-header-bg);
    color: var(--color-text-muted);
    flex: none;
}

/* ---- Audit Log: AI daily summaries card (Stage 11) ---- */

/*
 * Deliberately not `.card` (see `.card`'s own rule above): two `.card`
 * elements on this one screen would break every pre-existing functional
 * test that scopes an assertion to "the" `.card` (the audit-log table
 * itself) — this widget replicates the same background/border/radius
 * recipe under its own scoped class instead of sharing that generic one.
 */
.audit-log__summary-card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    overflow: hidden;
    margin-bottom: 12px;
}

.audit-log__summary-head {
    padding: 11px 16px;
    border-bottom: 1px solid var(--color-border);
    display: flex;
    align-items: center;
    gap: 8px;
}

.audit-log__summary-title {
    font-size: 13px;
    font-weight: 600;
}

.audit-log__summary-caption {
    font-size: 11px;
    color: var(--color-text-faint);
}

.audit-log__summary-row {
    display: flex;
    gap: 14px;
    padding: 11px 16px;
    border-bottom: 1px solid var(--color-border-muted);
}

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

.audit-log__summary-day {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-secondary);
    width: 88px;
    flex: none;
    padding-top: 1px;
}

.audit-log__summary-items {
    flex: 1;
    min-width: 0;
}

.audit-log__summary-item {
    font-size: 12.5px;
    line-height: 1.6;
    color: var(--color-text-secondary);
}

.audit-log__summary-by {
    color: var(--color-text-faint);
}

.audit-log__summary-empty {
    padding: 12px 16px;
    font-size: 12px;
    color: var(--color-text-faint);
}

/* ---- Fleet Dashboard (Stage 4) ---- */

.dashboard__sparkline {
    width: 100%;
    height: 34px;
    display: block;
    margin-bottom: 14px;
}

@keyframes tmDraw {
    from {
        stroke-dashoffset: 1;
    }
    to {
        stroke-dashoffset: 0;
    }
}

@keyframes tmDash {
    to {
        stroke-dashoffset: -16;
    }
}

.dashboard__kpis {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 12px;
}

.dashboard__kpi-card {
    padding: 14px 16px;
}

.dashboard__kpi-label {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.dashboard__kpi-value {
    font-family: var(--font-body);
    font-size: 32px;
    font-weight: 600;
    letter-spacing: -0.02em;
    margin-top: 8px;
}

.dashboard__kpi-value--critical {
    color: var(--color-severity-critical);
}

.dashboard__kpi-value-suffix {
    font-size: 14px;
    color: var(--color-text-muted);
    font-weight: 400;
}

.dashboard__kpi-delta {
    font-size: 12px;
    margin-top: 2px;
}

/* Revenue KPI card's trend chart — same server-computed-SVG-path convention
   as `.business-tab__chart*` (Architecture Decision 1), just smaller since
   it lives in one `.dashboard__kpi-card` column rather than a full tab
   panel. See `App\Dashboard\FleetRevenueChartBuilder`. */
.dashboard__kpi-chart {
    width: 100%;
    display: block;
    margin-top: 10px;
}

.dashboard__kpi-chart-area {
    fill: var(--color-ink);
    opacity: 0.06;
}

.dashboard__kpi-chart-series {
    fill: none;
    stroke: var(--color-ink);
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.dashboard__kpi-chart-point {
    fill: var(--color-ink);
    opacity: 0.85;
}

.dashboard__kpi-chart-empty {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 10px;
}

.dashboard__kpi-sev-row {
    display: flex;
    gap: 14px;
    margin-top: 10px;
}

.dashboard__kpi-sev-count {
    display: flex;
    align-items: center;
    gap: 5px;
}

.dashboard__kpi-sev-number {
    font-size: 20px;
    font-weight: 600;
}

.dashboard__kpi-sev-label {
    font-size: 11px;
    color: var(--color-text-muted);
    margin-top: 1px;
}

.dashboard__body {
    display: grid;
    grid-template-columns: 1fr 360px;
    gap: 12px;
    margin-top: 12px;
    align-items: start;
}

.dashboard__col {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.dashboard__panel-head {
    padding: 12px 16px;
    border-bottom: 1px solid var(--color-border);
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 13px;
    font-weight: 600;
}

/* IMPLEMENTATION_PLAN.md Stage 0: the design's shaded card-header band,
   same treatment as `.business-tab__panel-title--band` above. A new
   modifier rather than a change to `.dashboard__panel-head` itself —
   that base class is also used, unbanded, by `templates/dashboard/
   index.html.twig` (Fleet Dashboard), which is out of scope this pass
   and must not visually change. Grepped every current usage first: all
   four `templates/dashboard/index.html.twig` call sites stay on the bare
   base class; Project Detail's own two call sites (`_overview.html.twig`'s
   "Open findings" card, `_security.html.twig`'s findings card) are the
   ones expected to add this modifier once their own stage (1, 8) touches
   that markup — Stage 0 only builds the CSS, it doesn't apply it. */
.dashboard__panel-head--band {
    background: var(--color-header-bg);
}

.dashboard__panel-head-link {
    font-size: 12px;
    color: var(--color-ink);
    font-weight: 500;
}

.dashboard__attention-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 13px;
    cursor: pointer;
}

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

.dashboard__attention-row:hover {
    background: var(--color-row-hover);
    text-decoration: none;
}

.dashboard__attention-sev {
    font-size: 11px;
    font-weight: 600;
    width: 60px;
    flex: none;
}

.dashboard__attention-title {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 500;
    color: var(--color-ink);
}

.dashboard__attention-project {
    font-size: 11px;
    color: var(--color-text-muted);
    background: var(--color-status-accepted-risk-bg);
    border-radius: 5px;
    padding: 2px 7px;
    flex: none;
}

.dashboard__attention-age {
    font-size: 12px;
    color: var(--color-text-faint);
    width: 34px;
    text-align: right;
    flex: none;
}

.dashboard__attention-empty,
.dashboard__matrix-empty {
    padding: 24px 16px;
    text-align: center;
    color: var(--color-text-faint);
    font-size: 13px;
}

.dashboard__matrix {
    padding: 10px 16px 14px;
}

.dashboard__matrix-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 5px 0;
}

.dashboard__matrix-project {
    width: 170px;
    flex: none;
    font-size: 12px;
    font-weight: 500;
    color: var(--color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dashboard__matrix-bar {
    flex: 1;
    height: 16px;
    display: flex;
    gap: 2px;
}

.dashboard__matrix-segment {
    border-radius: 2px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    font-weight: 600;
    text-decoration: none;
}

.dashboard__matrix-segment:hover {
    opacity: 0.85;
    text-decoration: none;
}

.dashboard__matrix-total {
    width: 30px;
    flex: none;
    text-align: right;
    font-size: 11px;
    color: var(--color-text-faint);
}

.dashboard__matrix-legend {
    display: flex;
    gap: 14px;
    font-size: 11px;
    color: var(--color-text-muted);
    margin-top: 10px;
}

.dashboard__matrix-legend-item {
    display: flex;
    align-items: center;
    gap: 5px;
}

.dashboard__matrix-legend-swatch {
    width: 9px;
    height: 9px;
    border-radius: 2px;
    border: 1px solid;
    display: inline-block;
}

.dashboard__matrix-legend-note {
    margin-left: auto;
    color: var(--color-text-faint);
}

.dashboard__occasions {
    margin-top: 12px;
}

.dashboard__occasions-caption {
    font-size: 11px;
    color: var(--color-text-faint);
}

.dashboard__occasion-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 12px;
}

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

.dashboard__occasion-icon {
    width: 14px;
    height: 14px;
    flex: none;
    color: var(--color-text-faint);
}

.dashboard__occasion-icon path {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.dashboard__occasion-body {
    flex: 1;
    min-width: 0;
}

.dashboard__occasion-who {
    font-weight: 500;
    color: var(--color-ink);
}

.dashboard__occasion-what {
    font-weight: 400;
    color: var(--color-text-muted);
}

.dashboard__occasion-detail {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 1px;
}

.dashboard__occasion-when {
    font-family: 'IBM Plex Mono', monospace;
    font-size: 11px;
    font-weight: 600;
    flex: none;
}

.dashboard__occasions-empty {
    padding: 12px 16px;
    font-size: 12px;
    color: var(--color-text-faint);
}

.dashboard__occasions-footer {
    padding: 8px 16px;
    font-size: 11px;
    color: var(--color-text-faint);
}

/* "Agency hours · Toggl" sidebar card (ticket #7), `.dashboard__body`'s
   second (360px) grid column — previously unused by `templates/dashboard/
   index.html.twig`. Font/color tokens deliberately match `.code-history__*`
   (`assets/controllers/agency_hours_chart_controller.js`'s own docblock:
   reusing that panel's established visual language rather than inventing a
   new one), kept under this file's own `.dashboard__hours-*` namespace
   since every other Fleet Dashboard rule in this section already uses the
   `dashboard__` prefix. */
.dashboard__hours-caption {
    font-size: 11px;
    font-weight: 400;
    color: var(--color-text-faint);
}

.dashboard__hours-body {
    padding: 12px 16px 14px;
}

.dashboard__hours-total {
    display: flex;
    align-items: baseline;
    gap: 7px;
}

.dashboard__hours-total-value {
    font-family: var(--font-body);
    font-size: 26px;
    font-weight: 600;
    letter-spacing: -0.02em;
    color: var(--color-ink);
}

.dashboard__hours-total-label {
    font-size: 11px;
    color: var(--color-text-muted);
}

.dashboard__hours-plot {
    position: relative;
    margin-top: 8px;
}

.dashboard__hours-svg {
    display: block;
    width: 100%;
}

.dashboard__hours-axis-label {
    font-family: var(--font-mono);
    font-size: 10px;
    fill: var(--color-text-faint);
}

.dashboard__hours-empty-text {
    font-size: 12px;
    fill: var(--color-text-faint);
}

/* Bars start collapsed to the baseline and grow into place once the chart's
   geometry is known (`.is-grown`, applied a frame after `draw()` — see that
   controller's own comment on why two `requestAnimationFrame`s). Only
   defined under `no-preference` so a reduced-motion user gets the finished
   bars immediately with no transition step at all — same gating pattern
   `.landing__hero > *`/`.login__card > *` already use in this file. */
.dashboard__hours-bar {
    transform-box: fill-box;
    transform-origin: bottom;
    transform: scaleY(0);
}

@media (prefers-reduced-motion: no-preference) {
    .dashboard__hours-bar {
        transition: transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
    }

    .dashboard__hours-bar.is-grown {
        transform: scaleY(1);
    }
}

@media (prefers-reduced-motion: reduce) {
    .dashboard__hours-bar {
        transform: scaleY(1);
    }
}

.dashboard__hours-tooltip {
    position: absolute;
    z-index: 2;
    max-width: 220px;
    padding: 9px 11px;
    background: var(--color-ink);
    border-radius: var(--radius-pill);
    box-shadow: 0 4px 14px rgb(33 26 14 / 18%);
    font-size: 11px;
    line-height: 1.5;
    color: #e8dfcb;
    pointer-events: none;
}

.dashboard__hours-tooltip[hidden] {
    display: none;
}

.dashboard__hours-note {
    margin-top: 10px;
    font-size: 11px;
    color: var(--color-text-faint);
}

.dashboard__hours-empty {
    padding: 4px 0;
    font-size: 12px;
    color: var(--color-text-faint);
}

/* Analytics screen (IMPLEMENTATION_PLAN.md Stage 13 near-total rebuild) */

.analytics__filterbar {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 14px;
    flex-wrap: wrap;
}

.analytics__select {
    padding: 5px 8px;
    border: 1px solid var(--color-border-strong);
    border-radius: 2px;
    background: var(--color-surface);
    color: var(--color-text-secondary);
    font-size: 12px;
    cursor: pointer;
}

.analytics__kpis {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 12px;
    margin-bottom: 12px;
}

.analytics__kpi-card {
    padding: 12px 16px;
}

.analytics__kpi-label {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.analytics__kpi-value {
    font-size: 24px;
    font-weight: 600;
    margin-top: 6px;
}

.analytics__kpi-delta {
    font-size: 11px;
    margin-top: 2px;
}

.analytics__grid {
    display: grid;
    gap: 12px;
    align-items: start;
}

.analytics__grid--main {
    grid-template-columns: 2fr 1fr;
    margin-bottom: 12px;
}

.analytics__grid--triple {
    grid-template-columns: repeat(3, 1fr);
}

.analytics__panel {
    padding: 14px 16px;
}

.analytics__panel--wide {
    grid-column: 1 / -1;
}

.analytics__panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.analytics__panel-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-ink);
}

.analytics__panel-note {
    font-size: 11px;
    color: var(--color-text-faint);
}

.analytics__empty {
    font-size: 12px;
    color: var(--color-text-faint);
}

/* ---- weekly created-vs-closed line/area chart ---- */

.analytics__weekly-chart {
    width: 100%;
    display: block;
    margin-top: 12px;
}

.analytics__weekly-chart-grid {
    stroke: var(--color-border-muted);
    stroke-width: 1;
}

.analytics__weekly-chart-area {
    fill: var(--color-ink);
    opacity: 0.05;
}

.analytics__weekly-chart-closed {
    fill: none;
    stroke: var(--color-border-strong);
    stroke-width: 1.6;
    stroke-dasharray: 4 4;
    stroke-linejoin: round;
}

.analytics__weekly-chart-created {
    fill: none;
    stroke: var(--color-ink);
    stroke-width: 2.2;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    animation: tmDraw 1.6s ease forwards;
}

.analytics__weekly-chart-end-dot {
    fill: var(--color-ink);
}

.analytics__weekly-chart-end-pulse {
    fill: none;
    stroke: var(--color-ink);
    stroke-width: 1;
    opacity: 0.4;
    animation: tmPulse 2s infinite;
}

.analytics__weekly-chart-end-label {
    font-size: 10px;
    font-weight: 600;
    fill: var(--color-ink);
    text-anchor: middle;
    font-family: var(--font-mono);
}

.analytics__weekly-chart-x-label {
    font-size: 9px;
    fill: var(--color-text-faint);
    text-anchor: middle;
    font-family: var(--font-mono);
}

@keyframes tmPulse {
    0% {
        transform: scale(0.6);
        opacity: 0.5;
    }

    70% {
        transform: scale(1.6);
        opacity: 0;
    }

    100% {
        transform: scale(1.6);
        opacity: 0;
    }
}

.analytics__chart-legend {
    display: flex;
    gap: 16px;
    margin-top: 8px;
    font-size: 11px;
    color: var(--color-text-muted);
    align-items: center;
}

.analytics__legend-line {
    display: inline-block;
    width: 14px;
    height: 2px;
    background: var(--color-ink);
    margin-right: 5px;
}

.analytics__legend-line--closed {
    background: none;
    border-top: 2px dashed var(--color-border-strong);
}

.analytics__legend-dot {
    display: inline-block;
    width: 9px;
    height: 9px;
    border-radius: 2px;
    margin-right: 5px;
}

.analytics__legend-dot--reactive {
    background: var(--color-severity-high);
    opacity: 0.7;
}

.analytics__legend-dot--proactive {
    background: var(--color-ink);
    opacity: 0.85;
}

/* ---- findings funnel ---- */

.analytics__funnel-row {
    margin-top: 11px;
}

.analytics__funnel-row-head {
    display: flex;
    justify-content: space-between;
    font-size: 12px;
}

.analytics__funnel-label {
    color: var(--color-text-secondary);
}

.analytics__funnel-value {
    font-weight: 600;
}

.analytics__funnel-percent {
    color: var(--color-text-faint);
    font-weight: 400;
    font-size: 11px;
}

.analytics__funnel-bar-track {
    height: 9px;
    background: var(--color-border-muted);
    border-radius: 5px;
    margin-top: 4px;
    overflow: hidden;
}

.analytics__funnel-bar {
    display: block;
    height: 100%;
    background: var(--color-ink);
    border-radius: 5px;
}

.analytics__funnel-callout {
    margin-top: 14px;
    font-size: 12px;
    color: var(--color-text-muted);
    border-top: 1px solid var(--color-border-muted);
    padding-top: 10px;
}

/* ---- first-response / resolution SLA panels + 3D bar histograms ---- */

.analytics__sla-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 11px;
    font-weight: 600;
    border-radius: 5px;
    padding: 2px 9px;
}

.analytics__sla-target {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 2px;
}

.analytics__bars3d {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    height: 118px;
    margin-top: 12px;
    padding: 0 4px;
}

.analytics__bar3d {
    position: relative;
    width: 34px;
    height: 96px;
    cursor: pointer;
}

.analytics__bar3d-front {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 22px;
    height: var(--bar-height, 2px);
    background: var(--color-ink);
    transition: opacity 0.15s;
}

.analytics__bar3d-side {
    position: absolute;
    bottom: 0;
    left: 22px;
    width: 7px;
    height: var(--bar-height, 2px);
    background: var(--color-border-strong);
    transform: skewY(-40deg);
    transform-origin: bottom left;
    transition: opacity 0.15s;
}

.analytics__bar3d-top {
    position: absolute;
    left: 0;
    width: 22px;
    height: 6px;
    bottom: calc(var(--bar-height, 2px) - 3px);
    background: var(--color-text-dim);
    transform: skewX(-40deg);
    transform-origin: bottom left;
    transition: opacity 0.15s;
}

.analytics__bar3d:hover .analytics__bar3d-front,
.analytics__bar3d:hover .analytics__bar3d-side,
.analytics__bar3d:hover .analytics__bar3d-top {
    opacity: 0.7;
}

.analytics__bar3d-count {
    position: absolute;
    bottom: calc(var(--bar-height, 2px) + 8px);
    left: 0;
    width: 22px;
    text-align: center;
    font-size: 10px;
    font-weight: 600;
    color: var(--color-text-secondary);
}

.analytics__bar3d-label {
    position: absolute;
    top: 100%;
    left: -6px;
    width: 40px;
    margin-top: 4px;
    text-align: center;
    font-size: 8px;
    color: var(--color-text-faint);
    font-family: var(--font-mono);
}

.analytics__sla-footnote {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 10px;
}

/* ---- aging WIP compact list ---- */

.analytics__aging-list {
    margin-top: 4px;
}

.analytics__aging-row {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    margin-top: 10px;
    font-size: 12px;
}

.analytics__aging-label {
    color: var(--color-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.analytics__aging-age {
    font-weight: 600;
    color: var(--color-severity-high);
    flex: none;
}

.analytics__aging-footnote {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 12px;
}

/* ---- reactive vs. proactive percent-only stacked bars ---- */

.analytics__rp-row {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 9px;
    font-size: 12px;
}

.analytics__rp-name {
    width: 150px;
    flex: none;
    color: var(--color-text-secondary);
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.analytics__rp-track {
    flex: 1;
    height: 14px;
    border-radius: 4px;
    overflow: hidden;
    display: flex;
}

.analytics__rp-segment {
    height: 100%;
}

.analytics__rp-segment--reactive {
    background: var(--color-severity-high);
    opacity: 0.7;
}

.analytics__rp-segment--proactive {
    background: var(--color-ink);
    opacity: 0.85;
}

.analytics__rp-label {
    width: 110px;
    flex: none;
    text-align: right;
    color: var(--color-text-muted);
}

/* ---- Project Detail (IMPLEMENTATION_PLAN.md Stage 5 scope: Business tab) ---- */

.project-detail__header {
    margin-bottom: 16px;
}

.project-detail__title {
    font-size: 20px;
    font-weight: 600;
    color: var(--color-ink);
}

.project-detail__meta {
    margin-top: 2px;
    font-size: 12px;
    color: var(--color-text-muted);
}

.project-detail__tabs {
    display: flex;
    gap: 4px;
    border-bottom: 1px solid var(--color-border);
    margin-bottom: 16px;
}

.project-detail__tab {
    padding: 9px 14px;
    font-size: 13px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-decoration: none;
    border-bottom: 2px solid transparent;
}

.project-detail__tab.is-active {
    color: var(--color-ink);
    font-weight: 600;
    border-bottom-color: var(--color-ink);
}

.project-detail__overview-card {
    padding: 14px 16px;
    margin-bottom: 12px;
}

.project-detail__overview-row {
    display: flex;
    gap: 10px;
    padding: 6px 0;
    font-size: 13px;
    border-bottom: 1px solid var(--color-border-muted);
}

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

.project-detail__overview-label {
    width: 110px;
    flex: none;
    color: var(--color-text-muted);
}

.project-detail__overview-value {
    color: var(--color-text-secondary);
    font-weight: 500;
}

.project-detail__panel-title {
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-text-muted);
    margin-bottom: 10px;
}

/* ---- Project Detail Overview: retainer budget bar (Stage 7 gap #5) ---- */

.project-detail__retainer-summary {
    font-size: 13px;
    font-weight: 500;
    color: var(--color-ink);
    margin-bottom: 8px;
}

.project-detail__retainer-track {
    height: 10px;
    background: var(--color-border-muted);
    border-radius: 5px;
    overflow: hidden;
}

.project-detail__retainer-bar {
    display: block;
    height: 100%;
    border-radius: 5px;
}

.project-detail__retainer-bar--normal {
    background: var(--color-status-ticketed);
}

.project-detail__retainer-bar--high {
    background: var(--color-severity-high);
}

.project-detail__retainer-bar--over {
    background: var(--color-severity-critical);
}

.project-detail__retainer-note {
    font-size: 12px;
    margin-top: 6px;
}

.project-detail__retainer-note--high {
    color: var(--color-severity-high);
}

.project-detail__retainer-note--over {
    color: var(--color-severity-critical);
}

/* ---- Project Detail Overview: Health / Uptime / Contract / Platform versions / Open findings (design fidelity follow-up, `pIsOverview`) ---- */

.overview-tab__health-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 10px;
}

/* Stage 1 rebrush: bumped from 10px/16px-600 to 12px/32px-700 — the
   design's "big stat" treatment other cards on this tab already use for
   their own headline number (Uptime's percentage, Contract's hours-used). */
.overview-tab__health-dot {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    flex: none;
}

.overview-tab__health-label {
    font-size: 32px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1;
}

.overview-tab__health-counts {
    display: flex;
    gap: 26px;
    margin-top: 14px;
}

.overview-tab__health-count-num {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 32px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1;
}

.overview-tab__health-count-label {
    display: flex;
    align-items: center;
    gap: 5px;
    margin-top: 7px;
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-muted);
}

.overview-tab__health-count-label-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex: none;
}

.overview-tab__uptime-value {
    font-size: 26px;
    font-weight: 600;
    margin-top: 8px;
}

.overview-tab__uptime-value--unknown {
    font-size: 13px;
    font-weight: 400;
    color: var(--color-text-faint);
}

.overview-tab__uptime-bars {
    margin-top: 8px;
}

.overview-tab__uptime-last-ping {
    margin-top: 6px;
    font-size: 11px;
    color: var(--color-text-faint);
}

.overview-tab__contract-type {
    font-size: 14px;
    font-weight: 600;
    margin-top: 8px;
}

.overview-tab__contract-meta {
    font-size: 12px;
    color: var(--color-text-muted);
    margin-top: 8px;
}

.overview-tab__contract-toggl {
    margin-top: 10px;
}

.overview-tab__version-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 0;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 13px;
}

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

.overview-tab__version-icon {
    width: 15px;
    height: 15px;
    flex: none;
    color: var(--color-text-faint);
    fill: none;
    stroke: currentColor;
    stroke-width: 1.3;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.overview-tab__version-name {
    width: 90px;
    flex: none;
    color: var(--color-text-muted);
}

.overview-tab__version-value {
    font-weight: 600;
}

.overview-tab__finding-row {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 9px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 12px;
    cursor: pointer;
}

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

.overview-tab__finding-row:hover {
    background: var(--color-row-hover);
    text-decoration: none;
}

.overview-tab__finding-rule-key {
    font-family: var(--font-mono);
    color: var(--color-text-muted);
    font-size: 11px;
    width: 56px;
    flex: none;
}

.overview-tab__finding-title {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.overview-tab__finding-age {
    color: var(--color-text-faint);
    flex: none;
}

.overview-tab__finding-more {
    display: block;
    padding: 9px 16px;
    font-size: 12px;
    color: var(--color-text-muted);
}

/* IMPLEMENTATION_PLAN.md Stage 1: the Overview tab's real container +
   "Project health" banner + `1fr 360px` body grid. Deliberately new classes
   rather than reusing the old equal-card-grid `.overview-tab__stats`/
   `.overview-tab__row`/`.overview-tab__panel` — a different, joined-hairline
   layout was needed. Those three were kept around unused through Stage 1
   (Info still consumed them), but Stage 3's own rebrush moved Info off them
   too, and they've since been deleted outright — zero consumers left
   anywhere in this app. */
.overview-tab {
    max-width: 1180px;
}

.overview-tab__banner-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 18px;
    border-bottom: 1px solid transparent;
}

.overview-tab__banner-head-label,
.overview-tab__banner-head-stamp {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.overview-tab__banner-head-label {
    font-weight: 600;
}

.overview-tab__banner-grid {
    display: grid;
    grid-template-columns: 300px 1fr 1fr;
    align-items: stretch;
}

.overview-tab__banner-cell {
    padding: 20px 18px 22px;
}

.overview-tab__banner-cell + .overview-tab__banner-cell {
    border-left: 1px solid var(--color-border-muted);
}

.overview-tab__banner-cell--urgent {
    display: flex;
    flex-direction: column;
}

.overview-tab__health-blurb {
    margin-top: 10px;
    font-size: 12px;
    color: var(--color-text-muted);
    max-width: 240px;
}

.overview-tab__urgent-link {
    display: block;
    margin-top: 12px;
    text-decoration: none;
}

.overview-tab__urgent-meta {
    display: flex;
    align-items: center;
    gap: 8px;
}

.overview-tab__urgent-key {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-muted);
}

.overview-tab__urgent-age {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-faint);
}

.overview-tab__urgent-title {
    margin-top: 8px;
    font-size: 16px;
    font-weight: 600;
    line-height: 1.35;
    color: var(--color-ink);
}

.overview-tab__urgent-cta {
    display: block;
    margin-top: auto;
    padding-top: 14px;
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-ink);
    font-weight: 600;
}

.overview-tab__urgent-empty {
    margin-top: 12px;
}

.overview-tab__urgent-empty-title {
    font-size: 16px;
    font-weight: 600;
    color: var(--color-text-faint);
}

.overview-tab__urgent-empty-body {
    margin-top: 6px;
    font-size: 12px;
    color: var(--color-text-faint);
    max-width: 280px;
}

.overview-tab__body {
    display: grid;
    grid-template-columns: 1fr 360px;
    gap: 12px;
    margin-top: 12px;
    align-items: start;
}

.overview-tab__col {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
}

.overview-tab__panel-body {
    padding: 16px;
}

/* Finding row's severity indicator (Stage 1: 3px left-edge color bar,
   replacing the round `.severity-dot`) — a new class rather than a change
   to `.severity-dot` itself, since `_security.html.twig`'s own "Other
   security findings" card reuses the exact same `.overview-tab__finding-row`
   row markup with the round dot and is out of this stage's scope (its own
   rebrush is Stage 8). */
.overview-tab__finding-severity {
    width: 3px;
    align-self: stretch;
    flex: none;
}

.overview-tab__live-metrics {
    border: 1px dashed var(--color-border-strong);
    border-radius: var(--radius-panel);
    padding: 16px;
    background: rgba(255, 252, 245, 0.5);
}

.overview-tab__live-metrics-label {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-faint);
}

.overview-tab__live-metrics-stripe {
    margin-top: 12px;
    height: 72px;
    border-radius: var(--radius-panel);
    background: repeating-linear-gradient(135deg, var(--color-border-muted) 0 6px, var(--color-header-bg) 6px 12px);
}

.overview-tab__live-metrics-caption {
    margin-top: 10px;
    font-size: 12px;
    color: var(--color-text-faint);
}

/* ---- Project Detail Info: Running since / Contract / Customer / Project team / Linked services / Project history (design fidelity port, `docs/design/Refinery.dc.html`'s `pIsInfo` block) ---- */

/* Stage 3 rebrush: one joined bordered container for Running since/Contract/
   Customer, 1px hairline dividers via the gap-as-gridline trick (a `gap`
   equal to the container's own border color, with each cell's own
   background filling everything but that 1px seam) — replaces the old
   `.overview-tab__stats` equal-card grid (3 separate `.card`s with real
   12px gaps between them). */
.info-tab__summary {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1px;
    background: var(--color-border);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    overflow: hidden;
}

.info-tab__summary-cell {
    background: var(--color-surface);
    padding: 16px 18px 18px;
}

.info-tab__value {
    font-size: 13px;
    font-weight: 600;
    margin-top: 6px;
}

.info-tab__anniversary {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 11px;
    color: var(--color-status-snoozed);
    margin-top: 2px;
}

.info-tab__anniversary-icon {
    width: 12px;
    height: 12px;
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.info-tab__meta {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 2px;
}

.info-tab__contract-link {
    display: inline-block;
    font-size: 11px;
    font-weight: 500;
    color: var(--color-ink);
    margin-top: 4px;
}

.info-tab__contract-fallback {
    display: inline-block;
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 4px;
    border-bottom: 1px dashed var(--color-border-strong);
}

.info-tab__customer-card {
    cursor: pointer;
    display: block;
}

/* No individual border to darken any more now that this cell lives inside
   `.info-tab__summary`'s joined container (see above) — a background shift
   is the hover affordance instead, same "row you can click" pattern as
   `.dashboard__attention-row:hover`. */
.info-tab__customer-card:hover {
    background: var(--color-row-hover);
    text-decoration: none;
}

.info-tab__customer-arrow {
    color: var(--color-text-faint);
    font-weight: 400;
}

/* Stage 3 rebrush: second row becomes `360px 1fr` (History card gets the
   remaining width) — replaces the old `.overview-tab__row` equal-halves
   grid, which Overview also stopped using once its own Stage 1 rebrush
   landed (confirmed via grep before repurposing this row for Info alone). */
.info-tab__body {
    display: grid;
    grid-template-columns: 360px 1fr;
    gap: 12px;
    margin-top: 16px;
    align-items: start;
}

.info-tab__body-col {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* Stage 3 rebrush: the three dedicated card-header rows on this tab
   (`.info-tab__team-head`/`.info-tab__services-head`/`.info-tab__
   history-head`) share the same padding/border-bottom/flex shape —
   `.dashboard__panel-head`'s own shape, in fact, which is why the shared
   `.dashboard__panel-head--band` modifier (see the template) works on all
   of them without duplicating a background rule here. Factored the common
   properties out once instead of repeating them three times; each class
   below only carries what's actually different about it (`.info-tab__
   services-head` has nothing left to add — it's identical to the shared
   shape, so it exists purely as a template hook). */
.info-tab__team-head,
.info-tab__services-head,
.info-tab__history-head {
    padding: 12px 16px;
    border-bottom: 1px solid var(--color-border);
    display: flex;
    align-items: center;
}

.info-tab__team-head {
    align-items: baseline;
    justify-content: space-between;
}

.info-tab__panel-title {
    font-size: 13px;
    font-weight: 600;
}

/* GitLab member management (Info tab) - no design prototype covers this
   addition, so it reuses this tab's existing .info-tab__team-row layout;
   only the role <select>'s own inline-form wrapper is new. */
.info-tab__gitlab-member-role-form {
    display: inline-flex;
}

.info-tab__team-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 16px;
    border-bottom: 1px solid var(--color-border-muted);
}

/* Found live in production (2026-10-06): `.business-tab__empty` only ever
   carries font-size/color, no padding of its own - it's meant to sit
   inside an already-padded parent (e.g. `.info-tab__services` for the
   "Linked services" card's own empty state). The "Top contributors" card
   never got an equivalent padded wrapper around its `{% if is empty %}`
   branch, so the message rendered flush against the card's bare edges
   with zero breathing room. Matches `.info-tab__team-row`'s own
   horizontal rhythm (16px) so the empty message lines up with where a
   real row's text would start. */
.info-tab__contributors-empty {
    padding: 11px 16px;
}

.info-tab__team-avatar {
    width: 30px;
    height: 30px;
    flex: none;
    font-size: 11px;
}

.info-tab__team-identity {
    flex: 1;
    min-width: 0;
}

/* Stage 3 rebrush: 13px/600, matches the design's `Project team` row name
   line (previously 12.5px, sharing this class with the inline `· PM`
   suffix that's now been split off into `.info-tab__team-role` below). */
.info-tab__team-name {
    font-size: 13px;
    font-weight: 600;
}

/* Stage 3 rebrush: role restacked onto its own line below the name
   (mono/uppercase/10px), replacing the old inline `&middot; PM` suffix
   that lived on `.info-tab__team-name` itself. */
.info-tab__team-role {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-muted);
    margin-top: 3px;
}

/* Stage 5 (top-contributors-design.md): "Top contributors" row caption —
   shares `.info-tab__team-row`'s identity-block layout, but this line is a
   plain real-numbers sentence, not a mono/uppercase role label, so it
   matches `.info-tab__meta`'s own faint-caption scale instead of
   `.info-tab__team-role`'s above. */
.info-tab__contributor-stats {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 2px;
}

/* Stage 3 rebrush: "Linked services" is now its own `.card` (its head row,
   `.info-tab__services-head`, is declared as part of the shared head-row
   rule above `.info-tab__team-head`) rather than a sub-section stacked
   under "Project team" inside one shared card, so no more border-top here
   separating it from the team rows above. */
.info-tab__services {
    padding: 12px 16px;
}

.info-tab__services-row {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    margin-top: 6px;
}

.info-tab__services-row:first-child {
    margin-top: 0;
}

.info-tab__service-name {
    font-weight: 500;
    color: var(--color-ink);
    width: 90px;
    flex: none;
}

.info-tab__service-detail {
    color: var(--color-text-faint);
    font-size: 11px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Stage 3 rebrush note: deliberately does NOT get `.dashboard__panel-head
   --band` (unlike `.info-tab__team-head`/`.info-tab__services-head` above)
   — the design fragment sets no explicit background on this particular head
   row, letting it inherit `.info-tab__history-card`'s own distinct
   `--color-sidebar-bg` tint uniformly instead, which is the fragment's
   actual mechanism for making this one card read as visually
   uninterrupted. */
.info-tab__history-head {
    gap: 8px;
}

/* Stage 3 rebrush: distinct highlighted chrome setting "Project history"
   apart from every other card on this tab — a modifier alongside `.card`
   (source order after `.card`'s own rule below wins on the shared
   background/border properties). */
.info-tab__history-card {
    background: var(--color-sidebar-bg);
    border: 1px solid var(--color-border-strong);
}

.info-tab__history-mark {
    color: var(--color-ink);
}

.info-tab__history-sub {
    font-size: 11px;
    color: var(--color-text-faint);
}

.info-tab__history-link {
    margin-left: auto;
    font-size: 11px;
    font-weight: 500;
    color: var(--color-ink);
    /* Shared with the "GitLab members →" trigger `<button>` (QA follow-up:
       moved out of an inline card into a modal) — reset so a real button
       renders identically to the "Audit log →" `<a>` it sits next to. */
    background: none;
    border: none;
    padding: 0;
    font-family: inherit;
    cursor: pointer;
}

/*
 * One per configured GitLab repo (`gitlab_repo_members` can be a list of
 * more than one) — previously relied entirely on the shared `.card` class
 * for its background/border/radius when it rendered as its own standalone
 * card; now nested directly inside the modal panel (which already
 * supplies that chrome), so this only needs its own padding, plus a
 * divider between repos when a project has more than one.
 */
.info-tab__gitlab-members-card {
    padding-bottom: 8px;
}

.info-tab__gitlab-members-card:not(:first-child) {
    border-top: 1px solid var(--color-border);
    margin-top: 8px;
    padding-top: 8px;
}

.info-tab__history-week {
    display: flex;
    gap: 14px;
    padding: 11px 16px;
    border-bottom: 1px solid var(--color-border-muted);
}

.info-tab__history-week:last-child {
    border-bottom: none;
}

.info-tab__history-week-label {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-muted);
    width: 104px;
    flex: none;
    padding-top: 1px;
}

.info-tab__history-items {
    flex: 1;
    min-width: 0;
}

.info-tab__history-item {
    font-size: 12.5px;
    line-height: 1.6;
    color: var(--color-text-secondary);
}

/* IMPLEMENTATION_PLAN.md Stage 6: container max-width, matching the
   `.overview-tab`/`.infra-tab` precedent (Stages 1/2) rather than a new,
   uncapped-width tab. */
.business-tab {
    max-width: 1180px;
}

.business-tab__intro {
    margin-bottom: 14px;
    font-size: 12px;
    color: var(--color-text-faint);
}

/* IMPLEMENTATION_PLAN.md Stage 6: the fuller empty-state treatment (both
   no-environment and no-data-yet variants) — bold title + explanatory body
   + CTA link, `64px 24px` padding (up from the pre-Stage-6 `40px 48px`).
   Chart-interactivity-follow-up plan, Stage 4: the eyebrow/title/body/
   CTA/sparkline content itself moved into the shared `.empty-state__*`
   family (see that block's own docblock, below `.section-head__caption`)
   — this class now only carries the container-level chrome this tab's
   own standalone `.card` needs (padding, centering) plus a `17px`/`440px`
   title/body-width override the shared defaults don't match (this
   design's own literal values — see the shared block's docblock for why
   these 2 numbers stay tab-scoped rather than homogenized). */
.business-tab__empty-state {
    padding: 64px 24px;
    text-align: center;
}

.business-tab__empty-state .empty-state__title {
    font-size: 17px;
}

.business-tab__empty-state .empty-state__body {
    max-width: 440px;
}

.business-tab__empty {
    font-size: 12px;
    color: var(--color-text-faint);
}

/* IMPLEMENTATION_PLAN.md Stage 6: chart full-width, Payment methods +
   Catalog health as a `1fr 1fr` row below it — replaces the pre-Stage-6
   `.business-tab__grid` (`2fr 1fr` chart+sidebar) / `.business-tab__side`
   (the narrow stacked sidebar), both now unused and removed. */
.business-tab__row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    margin-top: 12px;
    align-items: start;
}

/* IMPLEMENTATION_PLAN.md Stage 6: the chart card gets a `#cfc5ae`
   (`--color-border-strong`) border — stronger than every other card's
   default `--color-border` — plus a head-band header, so its own padding
   moves off the card itself and onto `.business-tab__chart-body`/
   `.business-tab__panel-body` (the header band needs to reach the card's
   full edge-to-edge width, same reasoning `.dashboard__panel-head`/
   `.info-tab__team-head` already established for banded card headers). */
.business-tab__chart-card {
    padding: 0;
    border-color: var(--color-border-strong);
    overflow: hidden;
}

.business-tab__panel {
    padding: 0;
    overflow: hidden;
}

.business-tab__panel-body {
    padding: 14px 16px;
}

/* IMPLEMENTATION_PLAN.md Stage 6: shaded two-line header band ("Orders /
   hour · trailing 7 days" + "vs. same-hour baseline" as separate spans)
   with a right-aligned source tag — the design's own head-band treatment,
   Cross-cutting #1, applied to this card's head row. */
.business-tab__chart-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 18px;
    background: var(--color-header-bg);
    border-bottom: 1px solid var(--color-border);
}

.business-tab__chart-title {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-secondary);
}

.business-tab__chart-subtitle {
    font-size: 11px;
    color: var(--color-text-faint);
}

.business-tab__chart-source {
    margin-left: auto;
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-faint);
}

/* IMPLEMENTATION_PLAN.md Stage 5: the 4-tile KPI row (`Orders · 24h` /
   `Revenue · 24h` / `Customers` / `Broken cron jobs`), sitting between
   `.business-tab__chart-head` and `.business-tab__chart-body` — same
   hairline-gridline-via-background grid shape as `.security-tab__kpis`,
   confirmed against the decoded `docs/design/Refinery Project Detail.html`
   fragment's own Business-tab KPI row (18px rather than Security's 16px
   horizontal tile padding — the one real difference this fragment shows
   for this tab specifically). */
.business-tab__kpis {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 1px;
    background: var(--color-border-muted);
    border-bottom: 1px solid var(--color-border);
}

.business-tab__kpi-tile {
    background: var(--color-surface);
    padding: 14px 18px 16px;
}

.business-tab__kpi-label {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-muted);
}

.business-tab__kpi-value {
    font-size: 30px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1;
    margin-top: 9px;
}

.business-tab__kpi-sub {
    margin-top: 7px;
    font-size: 11px;
    color: var(--color-text-faint);
}

.business-tab__chart-body {
    padding: 16px 18px 6px;
}

/* Chart-interactivity-follow-up plan, Stage 1: the "Selected" readout row
   above the chart — eyebrow/value/when/vs-baseline, a flex spacer, then
   the "click the chart to inspect an hour" caption right-aligned. Values
   ported verbatim from the design fragment's inline styles (see the plan's
   own decoded markup) rather than approximated. */
.business-tab__chart-readout {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin-bottom: 8px;
}

.business-tab__chart-readout-eyebrow {
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-faint);
}

.business-tab__chart-readout-value {
    font-size: 15px;
    font-weight: 700;
    color: var(--color-ink);
}

.business-tab__chart-readout-when {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-muted);
}

.business-tab__chart-readout-vs-baseline {
    font-size: 11px;
    font-weight: 600;
}

.business-tab__chart-readout-spacer {
    flex: 1;
}

.business-tab__chart-readout-caption {
    font-size: 11px;
    color: var(--color-text-faint);
}

.business-tab__chart {
    width: 100%;
    display: block;
}

.business-tab__chart-band {
    fill: var(--color-border-muted);
    stroke: none;
}

.business-tab__chart-area {
    fill: var(--color-ink);
    opacity: 0.06;
}

.business-tab__chart-baseline {
    fill: none;
    stroke: var(--color-border-strong);
    stroke-width: 1.4;
    stroke-dasharray: 4 4;
}

.business-tab__chart-series {
    fill: none;
    stroke: var(--color-ink);
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.business-tab__chart-point {
    fill: var(--color-ink);
    opacity: 0.85;
}

.business-tab__chart-deploy-line {
    stroke: var(--color-ink);
    stroke-width: 1.2;
    opacity: 0.45;
}

.business-tab__chart-deploy-marker {
    fill: var(--color-ink);
    opacity: 0.75;
    cursor: help;
}

/* Chart-interactivity-follow-up plan, Stage 1: the dashed selection line +
   circle `chart_readout_controller.js` repositions on click, plus the
   invisible per-hour hit-targets underneath (`r=11`, well wider than the
   always-visible `.business-tab__chart-point` dot at `r=2.2`, for an easy
   click zone). Colors/opacity ported verbatim from the design fragment. */
.business-tab__chart-readout-line {
    stroke: var(--color-ink);
    stroke-width: 1;
    stroke-dasharray: 3 3;
    opacity: 0.5;
}

.business-tab__chart-readout-marker {
    fill: var(--color-surface);
    stroke: var(--color-ink);
    stroke-width: 2.5;
}

.business-tab__chart-hit {
    fill: transparent;
    cursor: crosshair;
}

/* IMPLEMENTATION_PLAN.md Stage 6: static axis chrome only — horizontal
   value gridlines + y-value labels, vertical calendar-day tick lines +
   labels (`App\ProjectDetail\OrdersChartBuilder::buildGridlines()`/
   `buildDayTicks()`). No interactivity (Decision 3) — these render
   unconditionally alongside the already-existing series/band/deploy
   marks, never in response to hover/click. */
.business-tab__chart-gridline {
    stroke: var(--color-border-muted);
    stroke-width: 1;
}

.business-tab__chart-daytick-line {
    stroke: var(--color-border-muted);
    stroke-width: 1;
}

.business-tab__chart-axis-label {
    font-family: var(--font-mono);
    font-size: 10px;
    fill: var(--color-text-dim);
}

/* Day-tick labels sit centered under their tick line; gridline y-labels
   stay left-anchored at the axis edge (the base class's default). */
.business-tab__chart-axis-label--center {
    text-anchor: middle;
}

.business-tab__chart-legend {
    display: flex;
    gap: 16px;
    font-size: 11px;
    color: var(--color-text-muted);
    margin-top: 6px;
    flex-wrap: wrap;
}

.business-tab__legend-swatch {
    display: inline-block;
    width: 12px;
    height: 8px;
    margin-right: 5px;
    border-radius: 2px;
    vertical-align: middle;
}

.business-tab__legend-swatch--series {
    background: var(--color-ink);
    height: 2px;
}

.business-tab__legend-swatch--baseline {
    border-top: 2px dashed var(--color-border-strong);
    height: 0;
}

.business-tab__legend-swatch--band {
    background: var(--color-border-muted);
}

.business-tab__legend-swatch--deploy {
    background: var(--color-ink);
    opacity: 0.75;
    width: 8px;
    height: 8px;
}

/* IMPLEMENTATION_PLAN.md Stage 6: the deploy-effect list panel — one row
   per deploy inside the chart's own trailing-7-day window, below the SVG
   (swatch + label + colored effect text + a plain-language note). See
   `App\ProjectDetail\OrdersChartBuilder::computeDeployEffect()` for how
   `effect`/`effectColor`/`note` are derived. */
.business-tab__deploy-list {
    border-top: 1px solid var(--color-border);
    background: var(--color-sidebar-bg);
}

.business-tab__deploy-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 18px;
    border-bottom: 1px solid var(--color-border-muted);
}

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

.business-tab__deploy-swatch {
    flex: none;
    width: 9px;
    height: 9px;
    border-radius: 2px;
    background: var(--color-ink);
    opacity: 0.75;
}

.business-tab__deploy-label {
    flex: none;
    width: 180px;
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 600;
    color: var(--color-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.business-tab__deploy-effect {
    flex: none;
    width: 230px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--color-text-muted);
}

.business-tab__deploy-note {
    font-size: 12px;
    color: var(--color-text-muted);
}

.business-tab__panel-title {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    margin-bottom: 4px;
}

/* IMPLEMENTATION_PLAN.md Stage 0: the design's shaded card-header band
   (`--color-header-bg` fill + `--color-border` rule) for panel titles.
   A modifier, not a change to `.business-tab__panel-title` itself —
   that base class also renders as a plain inline `<span>` next to other
   content (e.g. Performance tab's `.performance-tab__pagetype-head`),
   where a background/border-bottom/padding would double up with that
   wrapper's own chrome. Consuming stages that restructure a card to have
   a dedicated, unpadded header row (see `.info-tab__team-head` /
   `.dashboard__panel-head` for the established shape of that row) add
   this modifier alongside the base class instead. */
.business-tab__panel-title--band {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 0;
    padding: 12px 16px;
    background: var(--color-header-bg);
    border-bottom: 1px solid var(--color-border);
}

/* IMPLEMENTATION_PLAN.md Stage 5: renamed from `.business-tab__pay-*` —
   this panel now shows real Magento order-status share, not payment
   method (the real Prometheus exporter has no payment-method label at
   all). Same visual shape as before; `.business-tab__status-bar`'s fill
   color is now set inline per-row (`OrderStatusShareRow::$color`) instead
   of this rule's own flat `--color-ink`. */
.business-tab__status-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 9px;
    font-size: 12px;
}

.business-tab__status-name {
    width: 96px;
    flex: none;
    color: var(--color-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.business-tab__status-track {
    flex: 1;
    height: 8px;
    background: var(--color-border-muted);
    border-radius: 4px;
    overflow: hidden;
}

.business-tab__status-bar {
    display: block;
    height: 100%;
    border-radius: 4px;
}

.business-tab__status-pct {
    width: 40px;
    flex: none;
    text-align: right;
    color: var(--color-text-muted);
}

/* IMPLEMENTATION_PLAN.md Stage 6: the 3-col `1fr auto 1fr` "vs" layout
   (32px/700 values either side of a "vs" glyph), replacing the pre-Stage-6
   plain 2-value flex row at 17px/600. `--drifted`'s old modifier class is
   gone — the indexed value's color is now the already-computed
   `catalogHealth.indexColor` (an inline style, since it's data-driven, same
   convention `_overview.html.twig`'s health banner already uses for
   per-severity colors), not a static CSS class. */
.business-tab__catalog-values {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 14px;
    margin-top: 4px;
}

.business-tab__catalog-value {
    font-size: 32px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1;
    color: var(--color-ink);
}

.business-tab__catalog-vs {
    font-size: 16px;
    color: var(--color-text-dim);
}

.business-tab__catalog-label {
    margin-top: 8px;
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-muted);
}

/* The boxed drift-explanation callout — background/text colors are both
   inline styles driven by `catalogHealth.driftBg`/`driftColor` (the three
   honest states documented on that class: drifted/in-sync/unknown), same
   dynamic-color convention as `indexColor` above. */
.business-tab__catalog-callout {
    margin-top: 16px;
    padding: 11px 13px;
    border-radius: 2px;
    display: flex;
    gap: 10px;
    align-items: flex-start;
}

.business-tab__catalog-callout-label {
    font-size: 13px;
    font-weight: 700;
    white-space: nowrap;
}

.business-tab__catalog-callout-body {
    font-size: 12px;
    line-height: 1.45;
}

.business-tab__catalog-oos {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.1em;
}

/* ---- Project Detail: numbered section header (IMPLEMENTATION_PLAN.md
   Stage 0 shared component) ----

   Backs the `section_head()` macro in `templates/project_detail/
   _section_head.html.twig`. Two real on-prototype variants, verified
   against `docs/design/Refinery Project Detail.html` directly rather than
   assumed to be one shape:
     - Infrastructure/Code/Dependencies (`01 / STACK`, `01 / CLASS EXPLORER`,
       `03 / Composer — local path packages`): no leading dot,
       `align-items:baseline`, label at `10px`/`0.1em`.
     - SEO (`01 / CRAWL HYGIENE`, per-source ink/blue/gold accents): a
       leading `10px×10px` colored square, `align-items:center`, label
       bumped to `11px`/`0.12em` — **but the label text itself stays
       ink**, only the square follows the per-source accent. Corrected
       during Stage 9's own implementation after decoding the real SEO
       fragment directly: all three of its section labels
       (`01 / CRAWL HYGIENE`/`02 / SEARCH PERFORMANCE · ...`/
       `03 / SHOPPING FEED · ...`) carry a literal `color:#211a0e` even
       where the square is blue/gold — this docblock's own prior claim
       ("colored to match [the square]") was written speculatively in
       Stage 0, before Stage 9 had actually decoded that fragment, and was
       wrong. Zero-risk fix: SEO is the only real consumer of a non-null
       `color` argument, and it hadn't landed yet, so no other stage's
       rendered output changes.
   The base `.section-head`/`.section-head__label` rules are the first
   (dot-less) variant, since 3 of the pattern's 4 real macro consumers
   want it unchanged; `.section-head--dot` (applied by the macro whenever
   a `color` argument is passed — SEO's Stage 9 usage) switches to the
   second. Configure's own numbered 01–05 badges are a structurally
   different pattern (Stage 11 builds its own header, doesn't consume this
   macro) and aren't covered here.

   `--section-head-color` is a per-instance override hook (set via inline
   `style` from the macro when a `color` argument is given), read only by
   `.section-head__dot` — `.section-head__label` deliberately does NOT
   reference it (see the label-color correction above), so the default
   still falls back to the ink token with zero markup from callers that
   don't need an accent (Infrastructure/Code/Dependencies/Configure all
   use the default; only SEO's per-source sections are expected to pass
   one, Stage 9).

   `#d9d0ba` (the horizontal rule) is the design's own one-off divider
   tint — doesn't match any existing `--color-border-*` token, kept
   literal rather than inventing a new custom property for a single use. */

.section-head {
    --section-head-color: var(--color-ink);
    display: flex;
    align-items: baseline;
    gap: 12px;
}

.section-head--dot {
    align-items: center;
}

.section-head__dot {
    width: 10px;
    height: 10px;
    border-radius: 2px;
    background: var(--section-head-color);
    flex: none;
}

.section-head__label {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-ink);
    white-space: nowrap;
}

.section-head--dot .section-head__label {
    font-size: 11px;
    letter-spacing: 0.12em;
}

.section-head__badge {
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    padding: 2px 7px;
    border-radius: 2px;
    flex: none;
    white-space: nowrap;
}

.section-head__rule {
    flex: 1;
    height: 1px;
    background: #d9d0ba;
}

.section-head__caption {
    font-size: 11px;
    color: var(--color-text-faint);
    white-space: nowrap;
}

/* ---- Project Detail: shared empty-state content (chart-interactivity-
   follow-up plan, Stage 4) ----

   Backs the `empty_state()` macro in `templates/project_detail/
   _empty_state.html.twig`. Business (rebrush Stage 6), Security (Stage 8),
   and SEO (Stage 9) each independently built this exact eyebrow/title/
   body/[sparkline]/[CTA-link] shape under their own tab-scoped classes,
   each explicitly flagged at the time as "duplicate now, consolidate once
   proven 3x" — this is that consolidation. Defaults below match
   Security's own literal values (16px title, 430px body max-width, 8px
   body margin-top) since Security needed zero override rules; Business
   and SEO each keep one small tab-scoped override for the 2 numbers their
   own design literally differs on (`.business-tab__empty-state`/
   `.seo-tab__empty`'s own CSS blocks) — real per-tab pixel differences
   from the prototype, not something to silently homogenize away.

   Deliberately doesn't include any container-level rule (background,
   border, border-radius, padding) — that stays each tab's own
   `.business-tab__empty-state`/`.security-tab__panel--empty`/
   `.seo-tab__empty`/`.seo-tab__crawl-empty` class, since those genuinely
   differ (standalone `.card` vs. banner-chained panel vs. own
   border+background) — see the macro's own docblock for the full
   reasoning. */
.empty-state__eyebrow {
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: var(--color-text-faint);
}

.empty-state__title {
    margin-top: 12px;
    font-size: 16px;
    font-weight: 600;
    color: var(--color-text-secondary);
}

.empty-state__body {
    margin-top: 8px;
    font-size: 13px;
    color: var(--color-text-faint);
    max-width: 430px;
    margin-left: auto;
    margin-right: auto;
    line-height: 1.55;
}

.empty-state__cta {
    display: inline-block;
    margin-top: 16px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-ink);
}

.empty-state__sparkline {
    display: flex;
    gap: 2px;
    justify-content: center;
    align-items: flex-end;
    height: 56px;
    margin-top: 22px;
    opacity: 0.55;
}

.empty-state__sparkline-bar {
    width: 9px;
    height: 14px;
    background: var(--color-border-muted);
    border-radius: 1px;
}

/* ---- Project Detail: Infrastructure tab (merged grouped table) ----
   IMPLEMENTATION_PLAN.md Stage 2 rebuilt the main table from flexbox rows
   into a real CSS grid (`3px 210px 1fr 1fr` — flag strip / component name /
   Production / Staging), so header and data rows always align exactly
   without hand-matched fixed widths. `--infra-grid-cols` is set once on
   `.infra-tab__panel` and read by every grid row inside it
   (`.infra-tab__columns`/`.infra-tab__row`) rather than repeated per row;
   `.infra-tab__panel--no-staging` (applied whenever `hasStagingEnvironment`
   is false) swaps it to the 3-column variant so the Staging column
   disappears from every row at once. */

.infra-tab {
    max-width: 1180px;
}

.infra-tab__note {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-top: 16px;
    font-size: 11px;
    color: var(--color-text-faint);
}

.infra-tab__note-link {
    font-weight: 600;
    color: var(--color-ink);
}

/* Design spec (`docs/design/Refinery Project Detail.html`, decoded `isInfra`
   fragment): every section-head after the first one on the tab carries its
   own `margin:22px 0 10px` inline. The shared `.section-head` class is
   deliberately margin-less (see its own docblock) — each tab is expected to
   supply this spacing itself — but nothing here ever did for the second/
   third section-head, so "02 / CLUSTER CONFIGURATION"/"03 / SHOP
   PERFORMANCE" sat with zero gap directly under the previous card's bottom
   border (live-confirmed visual check). `.infra-tab__panel`'s own
   `margin-top: 12px` below only covers the gap *under* a section-head
   (roughly matching the design's `10px` bottom component) — it does nothing
   for the gap *above* one that follows a card. */
.infra-tab [data-testid="infra-section-cluster"],
.infra-tab [data-testid="infra-section-shop-performance"] {
    margin-top: 22px;
}

.infra-tab__panel {
    --infra-grid-cols: 3px 210px 1fr 1fr;
    margin-top: 12px;
}

.infra-tab__panel--no-staging {
    --infra-grid-cols: 3px 210px 1fr;
}

.infra-tab__head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 16px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-header-bg);
}

.infra-tab__head-title {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-secondary);
}

.infra-tab__summary-badges {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

.infra-tab__summary-badge {
    white-space: nowrap;
}

.infra-tab__columns {
    display: grid;
    grid-template-columns: var(--infra-grid-cols);
    align-items: stretch;
    border-bottom: 1px solid var(--color-border);
}

/* Intentionally styleless — the header row's first grid cell, matching the
   width of each data row's `.infra-tab__row-flag` strip (the first
   `--infra-grid-cols` column) purely so this row's remaining columns stay
   aligned with the data rows below it. No flag color to show in a header. */
.infra-tab__col-flag {
}

.infra-tab__col {
    padding: 9px 16px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-muted);
}

.infra-tab__col--name {
    padding-left: 0;
}

.infra-tab__col--production {
    font-weight: 600;
    color: var(--color-text-secondary);
}

.infra-tab__col--staging {
    font-weight: 600;
    color: var(--color-text-muted);
    border-left: 1px solid var(--color-border);
    /* `#f7f2e6` is the design's own Staging-column tint — close to but
       genuinely distinct from `--color-header-bg` (`#f6f1e4`), so kept
       literal rather than reusing that token for a near-but-not-exact
       match (same discipline as `.section-head__rule`'s `#d9d0ba`). */
    background: #f7f2e6;
}

.infra-tab__group-title {
    padding: 10px 16px 4px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-faint);
    text-transform: uppercase;
    letter-spacing: 0.12em;
}

.infra-tab__row {
    display: grid;
    grid-template-columns: var(--infra-grid-cols);
    align-items: stretch;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 12.5px;
}

.infra-tab__row-flag {
    /* `background` set inline per row from `row.flagColor`
       (`App\ProjectDetail\InfrastructureRowView`) — `transparent` for a
       healthy row with nothing to flag. */
}

.infra-tab__row-name {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 16px 10px 13px;
    color: var(--color-text-muted);
}

.infra-tab__row-icon {
    width: 15px;
    height: 15px;
    flex: none;
    color: var(--color-text-faint);
    fill: none;
    stroke: currentColor;
    stroke-width: 1.3;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.infra-tab__row-label {
    color: var(--color-text-secondary);
    font-weight: 500;
}

.infra-tab__cell {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    padding: 10px 16px;
}

.infra-tab__cell[data-environment-kind="staging"] {
    border-left: 1px solid var(--color-border);
    background: #f7f2e6;
}

.infra-tab__cell-version {
    font-weight: 600;
}

.infra-tab__cell[data-environment-kind="staging"] .infra-tab__cell-version {
    font-weight: 500;
    color: var(--color-text-secondary);
}

.infra-tab__cell-badge {
    flex: none;
}

/*
 * Infrastructure tab follow-up: "be able to click a link that sends me to
 * the EOL site" — only EOL/EOL-soon badges render as a real `<a>` (see
 * `App\Registry\EolStatus::hasEolInfoLink()`), so this only needs to undo
 * the browser's default link underline/color and align the small
 * external-link icon next to the label; every other badge stays a plain,
 * non-interactive `<span>`.
 */
.infra-tab__cell-badge--link {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    text-decoration: none;
    cursor: pointer;
}

.infra-tab__cell-badge-icon {
    width: 9px;
    height: 9px;
}

.infra-tab__cell-dash,
.infra-tab__cell-empty {
    color: var(--color-text-faint);
    font-size: 12px;
}

/* ---- Project Detail: Infrastructure tab, "Cluster configuration" card ----
   Real per-server MaxCluster data (docs/plans/2026-08-16-gitlab-maxcluster-
   and-correlated-rules-design.md §2). IMPLEMENTATION_PLAN.md Stage 2 rebuilt
   this from flex-wrap, headerless rows into a real 8-column grid
   (`1.4fr 1fr 1.7fr 78px 88px 118px 1fr 104px` — Server/Type/Hostname/CPU/
   RAM/NVMe/Networks/blank-for-Manage-link) with a real column-header row,
   matching the design prototype's own decoded fragment exactly. */

.infra-tab__cluster-config {
    margin-top: 16px;
}

.infra-tab__cluster-columns,
.infra-tab__cluster-row {
    display: grid;
    grid-template-columns: 1.4fr 1fr 1.7fr 78px 88px 118px 1fr 104px;
    align-items: center;
}

.infra-tab__cluster-columns {
    border-bottom: 1px solid var(--color-border);
    background: var(--color-header-bg);
}

.infra-tab__cluster-col {
    padding: 10px 14px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-muted);
}

.infra-tab__cluster-col--right {
    text-align: right;
}

.infra-tab__cluster-row {
    border-bottom: 1px solid var(--color-border-muted);
}

.infra-tab__cluster-name {
    padding: 12px 14px;
    font-size: 13px;
    font-weight: 600;
}

.infra-tab__cluster-detail {
    padding: 12px 14px;
    color: var(--color-text-secondary);
    font-size: 12px;
}

.infra-tab__cluster-detail--mono {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.infra-tab__cluster-detail--strong {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-ink);
}

.infra-tab__cluster-detail--right {
    text-align: right;
}

.infra-tab__cluster-link {
    padding: 12px 14px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    text-align: right;
    white-space: nowrap;
    color: var(--color-ink);
    text-decoration: none;
}

/* ---- Project Detail: Infrastructure tab, "Shop performance" cards ----
   Follow-up ("add the summary of them into the infrastructure tab") — real
   `App\Entity\MaxClusterShopPerformanceReport` data, one card per
   environment. IMPLEMENTATION_PLAN.md Stage 2 enlarged the score number
   (24px/600 -> 40px/700) and switched to a 2-col (`260px`/`1fr`) score/tips
   grid with a vertical divider, matching the design prototype's own decoded
   fragment; previously inline-styled, now real classes like the rest of
   this tab. */

.infra-tab__shop-performance {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-top: 22px;
}

.infra-tab__shop-card {
    overflow: hidden;
}

.infra-tab__shop-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 16px;
    background: var(--color-header-bg);
    border-bottom: 1px solid var(--color-border);
}

.infra-tab__shop-head-title {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-secondary);
}

.infra-tab__shop-head-meta {
    margin-left: auto;
    font-size: 11px;
    color: var(--color-text-faint);
}

.infra-tab__shop-head-link {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-ink);
    text-decoration: none;
    flex: none;
}

.infra-tab__shop-body {
    display: grid;
    grid-template-columns: 260px 1fr;
}

.infra-tab__shop-score {
    padding: 18px 16px;
    border-right: 1px solid var(--color-border-muted);
}

.infra-tab__shop-score-label {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-muted);
}

.infra-tab__shop-score-value {
    display: flex;
    align-items: baseline;
    gap: 7px;
    margin-top: 9px;
    font-size: 40px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1;
}

.infra-tab__shop-score-max {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-text-faint);
}

.infra-tab__shop-score-delta {
    margin-top: 9px;
    font-size: 12px;
    font-weight: 600;
}

.infra-tab__shop-score-badges {
    display: flex;
    gap: 8px;
    margin-top: 14px;
}

.infra-tab__shop-tips {
    display: flex;
    flex-direction: column;
}

.infra-tab__shop-tip {
    display: flex;
    gap: 10px;
    padding: 10px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 12.5px;
}

.infra-tab__shop-tip-severity {
    flex: none;
    align-self: flex-start;
    margin-top: 1px;
}

.infra-tab__shop-tip-body {
    flex: 1;
    min-width: 0;
}

.infra-tab__shop-tip-title {
    font-weight: 500;
}

.infra-tab__shop-tip-description {
    margin-top: 2px;
    font-size: 11.5px;
    color: var(--color-text-muted);
}

.infra-tab__shop-tip-link {
    font-size: 11px;
    color: var(--color-ink);
    font-weight: 500;
}

/* ---- MaxCluster server inventory (Settings > Collector fleet > View all servers) ---- */

.maxcluster-fleet__intro {
    font-size: 12px;
    color: var(--color-text-muted);
    line-height: 1.6;
    background: var(--color-header-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    padding: 10px 14px;
    margin-bottom: 14px;
}

.maxcluster-fleet__count {
    font-size: 12px;
    color: var(--color-text-muted);
    margin-bottom: 10px;
}

.maxcluster-fleet__row {
    margin-bottom: 10px;
    padding: 12px 16px;
}

.maxcluster-fleet__row-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin-bottom: 8px;
}

.maxcluster-fleet__cluster-number {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-muted);
}

.maxcluster-fleet__cluster-name {
    font-weight: 600;
    font-size: 14px;
    flex: 1;
}

.maxcluster-fleet__servers {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.maxcluster-fleet__server {
    display: flex;
    align-items: baseline;
    gap: 12px;
    font-size: 12px;
    padding: 4px 0;
    border-top: 1px solid var(--color-border-muted);
}

.maxcluster-fleet__server-name {
    font-weight: 500;
    width: 70px;
    flex: none;
}

.maxcluster-fleet__server-type {
    color: var(--color-text-muted);
    width: 130px;
    flex: none;
}

.maxcluster-fleet__server-hostname {
    color: var(--color-text-faint);
    font-family: var(--font-mono);
    font-size: 11px;
    flex: 1;
}

/* ---- Project Detail: Dependencies tab ---- */

.deps-tab {
    margin-top: 16px;
}

/* `[hidden]` must win over `display: flex` below — same specificity as a
   class selector, so without this the Stimulus controller's `hidden = true`
   (native attribute) would be silently overridden by the flex rule and the
   bar would show even with nothing selected. */
.deps-tab__selection-bar[hidden] {
    display: none;
}

/*
 * IMPLEMENTATION_PLAN.md Stage 4: color scheme inverted to match the
 * design (was a light `#ebe2cf` bar with a dark ink button — backwards
 * from the design's dark-bar/light-pill spec). `--color-ink` is the exact
 * token match for the design's literal `#211a0e`.
 */
.deps-tab__selection-bar {
    background: var(--color-ink);
    border-radius: 2px;
    padding: 10px 14px;
    display: flex;
    align-items: center;
    gap: 14px;
    font-size: 12px;
    margin-bottom: 12px;
}

/* Purely decorative (matches the design's own illustrative status dot) —
   `#f1ecdf` is the design's literal light ink-on-dark tone; close to but
   distinct enough from `--color-header-bg` (`#f6f1e4`) that it's kept
   literal rather than reusing that token for a near-but-not-exact match
   (same discipline `.section-head__rule`'s `#d9d0ba` already established). */
.deps-tab__selection-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #f1ecdf;
    flex: none;
}

.deps-tab__selection-count {
    font-weight: 700;
    font-size: 13px;
    color: #f1ecdf;
}

/* Shows the checked row(s)' correlated Finding's real rule key (e.g.
   "DEP-001") — see `_dependencies.html.twig`'s own docblock for why this
   deviates from the design's fabricated `selFindingLabel` copy. `#a89d86`
   is the design's own literal muted-on-dark tone (no existing token is
   this dark-background-specific). */
.deps-tab__selection-rule {
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: #a89d86;
}

.deps-tab__ticket-button {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    border: none;
    background: #f1ecdf;
    color: var(--color-ink);
    border-radius: 2px;
    padding: 7px 12px;
    cursor: pointer;
    text-decoration: none;
    display: inline-block;
    margin-left: auto;
}

.deps-tab__ticket-button:hover {
    background: #e3dcc5;
}

.deps-tab__clear-link {
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: #a89d86;
    cursor: pointer;
}

.deps-tab__clear-link:hover {
    color: #f1ecdf;
}

/*
 * Was scoped to `.deps-tab__table--npm` only, back when there were exactly
 * two tables (Composer, then npm) — now up to four (Composer real/dev/
 * local-path, then npm), so this is a general adjacent-sibling rule
 * instead of a manager-specific one.
 */
.deps-tab__table + .deps-tab__table {
    margin-top: 12px;
}

/*
 * IMPLEMENTATION_PLAN.md Stage 4: the numbered `section_head()` macro
 * (padding built into `.section-head`'s own parent context here — this
 * class now just adds the header-band's border/background) replaced the
 * plain `.deps-tab__table-head`/`.deps-tab__table-title`/
 * `.deps-tab__table-source` trio entirely, so those three are gone —
 * nothing references them any more.
 *
 * IMPLEMENTATION_PLAN.md Stage 4: switched from flexbox rows (hand-matched
 * per-column `width`/`flex` declarations that could silently drift between
 * the header row and data rows) to a real CSS grid, exactly the discipline
 * `.infra-tab__panel`'s own `--infra-grid-cols` already established —
 * `--deps-grid-cols` is set once per table shape and read by both
 * `.deps-tab__col-head` and `.deps-tab__row`, so they can never
 * mis-align. `34px 1fr 104px 104px 148px 116px 92px` is the design's own
 * literal column-width spec (checkbox/package/installed/latest/behind/
 * license/advisories); `.deps-tab__col-head--local`/`.deps-tab__row--local`
 * swap in the 2-column (package/installed only, no checkbox) variant the
 * local-path table's simpler row shape needs (see this template's own
 * docblock for why).
 */
.deps-tab__table {
    --deps-grid-cols: 34px 1fr 104px 104px 148px 116px 92px;
}

.deps-tab__col-head,
.deps-tab__row {
    display: grid;
    grid-template-columns: var(--deps-grid-cols);
    column-gap: 10px;
}

.deps-tab__col-head--local,
.deps-tab__row--local {
    --deps-grid-cols: 1fr 104px;
}

.deps-tab__col-head {
    align-items: center;
    padding: 8px 16px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-header-bg);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

/*
 * Design-fidelity follow-up: "Can we make the columns sortable on the
 * dependencies tab?" — `assets/controllers/sortable_table_controller.js`.
 * `::after` carries the direction arrow so it can be swapped per
 * `[data-sort-active]` without a second DOM element; a plain `↕` hints at
 * sortability before any column is actively sorted.
 */
.deps-tab__col--sortable {
    /*
     * QA fix ("the header stylings are not as in the designs for
     * tables"): without this, the global `a { color: var(--color-ink) }`
     * rule (an element selector, which beats the `.deps-tab__col-head`
     * parent's own inherited `color: var(--color-text-muted)`) made every
     * sortable header link render dark/high-emphasis instead of the muted
     * mono-uppercase look `.findings__head-link`/`.audit-log__head` both
     * already establish — confirmed live via computed styles. Same fix:
     * `color: inherit` here, `color: var(--color-ink)` only on hover/active
     * below, so the active sort column is the one real visual exception,
     * not every sortable column all the time.
     */
    color: inherit;
    text-decoration: none;
    cursor: pointer;
    user-select: none;
    display: inline-flex;
    align-items: center;
    gap: 3px;
}

.deps-tab__col--sortable:hover,
.deps-tab__col--sortable[data-sort-active] {
    color: var(--color-ink);
}

.deps-tab__col--sortable::after {
    /*
     * GitLab #16 ("inactive sort-column arrow glyph renders broken"):
     * `\21C5` (⇅, the compound up-over-down sort glyph the design prototype
     * itself uses — `docs/design/Refinery Project Detail.html`'s `isDeps`
     * fragment) isn't in IBM Plex Mono's own glyph set, so the browser
     * silently substitutes a fallback font for just this one character —
     * confirmed live, that fallback's baseline/vertical metrics don't match
     * the surrounding mono text, rendering as a small, misaligned blob next
     * to the column label. The active-state glyphs below (`\2191`/`\2193`,
     * ↑/↓) sit in IBM Plex Mono's actual glyph set and already render
     * crisp and aligned at this same 9px size — reusing that exact same
     * proven-good pair here (rather than guessing at a new fallback
     * font-family/vertical-align correction) sidesteps the font-coverage
     * gap entirely instead of papering over it.
     */
    content: '\2191\2193';
    font-size: 9px;
    opacity: 0.4;
}

.deps-tab__col--sortable[data-sort-active="asc"]::after {
    content: '\2191';
    opacity: 1;
}

.deps-tab__col--sortable[data-sort-active="desc"]::after {
    content: '\2193';
    opacity: 1;
}

.deps-tab__row {
    align-items: center;
    padding: 8px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 12px;
}

.deps-tab__row:last-child {
    border-bottom: none;
}

.deps-tab__row--selected {
    background: var(--color-row-hover);
}

.deps-tab__col--package {
    min-width: 0;
}

.deps-tab__col--installed {
    color: var(--color-text-muted);
}

.deps-tab__col--latest {
    font-weight: 500;
}

/*
 * Design-fidelity follow-up: "a column that indicates whether the
 * dependency is paid or open source" / "Advisories" column — column
 * *widths* now come entirely from `--deps-grid-cols` (see this file's own
 * Stage 4 grid-conversion note above); `.deps-tab__col--advisories` is the
 * only one of this pair that still needs its own non-layout styling.
 */
.deps-tab__col--advisories {
    font-size: 11px;
}

.deps-tab__checkbox {
    width: 13px;
    height: 13px;
    accent-color: var(--color-ink);
    cursor: pointer;
}

.deps-tab__pkg {
    font-family: var(--font-mono);
    font-size: 12px;
}

/*
 * "click every dependency and get linked to the github repo ... or to the
 * packagist site" — only a row with a real, verified public source
 * renders as a link (`App\ProjectDetail\DependencyPackageRow::linkUrl()`);
 * every other row keeps the plain `.deps-tab__pkg` styling above.
 */
.deps-tab__pkg--link {
    color: inherit;
    text-decoration: none;
    border-bottom: 1px dotted var(--color-text-faint);
}

.deps-tab__pkg--link:hover {
    border-bottom-style: solid;
}

.deps-tab__behind-badge,
.deps-tab__license-badge {
    font-size: 11px;
    font-weight: 500;
    padding: 1px 7px;
}

.deps-tab__empty {
    padding: 14px 16px;
}

/*
 * "we basically need three tables ... local path (always folded)" — plain
 * native `<details>`/`<summary>`, no JS. IMPLEMENTATION_PLAN.md Stage 4:
 * now wraps the shared `section_head()` macro (see this tab's own
 * docblock) instead of its own title/source spans, so this only needs to
 * add the disclosure affordance (cursor, hidden default marker) plus
 * spacing/border — the macro's own `.section-head` handles the internal
 * label/badge/rule/caption layout.
 */
.deps-tab__table-head--summary {
    padding: 11px 16px;
    border-bottom: 1px solid var(--color-border);
    cursor: pointer;
    list-style: none;
}

.deps-tab__table--local-path[open] .deps-tab__table-head--summary {
    border-bottom-color: var(--color-border);
}

.deps-tab__table--local-path:not([open]) .deps-tab__table-head--summary {
    border-bottom: none;
}

/*
 * IMPLEMENTATION_PLAN.md Stage 4: real query-param pagination footer,
 * shared by all four tables (`{% macro pager() %}` in
 * `_dependencies.html.twig`) — `#f6f1e4`/`#e3dac6` are the design's own
 * literal footer tones, matching `--color-header-bg`/`--color-border`
 * exactly.
 */
.deps-tab__pager {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    background: var(--color-header-bg);
    border-top: 1px solid var(--color-border);
}

.deps-tab__pager-range {
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-faint);
}

.deps-tab__pager-spacer {
    flex: 1;
}

.deps-tab__pager-nav {
    font-size: 12px;
    font-weight: 500;
    color: var(--color-text-secondary);
    text-decoration: none;
}

.deps-tab__pager-nav:hover {
    color: var(--color-ink);
}

.deps-tab__pager-pages {
    display: flex;
    gap: 4px;
}

.deps-tab__pager-page {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    padding: 0 5px;
    border-radius: 2px;
    font-size: 12px;
    color: var(--color-text-muted);
    text-decoration: none;
}

.deps-tab__pager-page:hover {
    background: var(--color-row-hover);
    color: var(--color-ink);
}

.deps-tab__pager-page--active {
    background: var(--color-ink);
    color: #fff;
    font-weight: 600;
}

/* ---- Project Detail: Tickets tab (design-fidelity row rebuild) ---- */

/* Panel is now a flush list of full-bleed rows, not a padded table wrapper. */
.tickets-tab__panel {
    padding: 0;
}

.tickets-tab__note {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-top: 16px;
    margin-bottom: 12px;
    font-size: 11px;
    color: var(--color-text-faint);
}

.tickets-tab__note-link {
    font-weight: 600;
    color: var(--color-ink);
}

.tickets-tab__empty {
    padding: 40px 48px;
    text-align: center;
    color: var(--color-text-faint);
    font-size: 13px;
}

/*
 * "A proper table with column sorting and a table search" — the search
 * input above the header row, same visual language as the design's own
 * filter `<select>`s (border/radius/background/color tokens lifted
 * verbatim from docs/design/Refinery.dc.html's Analytics filter bar).
 *
 * Design-fidelity Stage 10: gains a search icon inside the input, a
 * ticket-count label, and an on-track/over/not-tracked progress-state
 * legend — the toolbar band itself gets the design's header-band tint
 * (`--color-header-bg`/`--color-border`) instead of a plain padded strip.
 */
.tickets-tab__toolbar {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    background: var(--color-header-bg);
    border-bottom: 1px solid var(--color-border);
}

.tickets-tab__search-wrap {
    display: flex;
    align-items: center;
    gap: 8px;
    background: #fffcf5;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-panel);
    padding: 0 10px;
    width: 320px;
    height: 32px;
    flex: none;
}

.tickets-tab__search-icon {
    width: 13px;
    height: 13px;
    flex: none;
    fill: none;
    stroke: var(--color-text-faint);
    stroke-width: 1.4;
    stroke-linecap: round;
}

.tickets-tab__search-input {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    font-family: inherit;
    border: none;
    padding: 0;
    background: transparent;
    color: var(--color-ink);
    outline: none;
}

.tickets-tab__search-input::placeholder {
    color: var(--color-text-faint);
}

.tickets-tab__count-label {
    flex: none;
    white-space: nowrap;
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-faint);
}

.tickets-tab__toolbar-spacer {
    flex: 1;
}

.tickets-tab__legend {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: none;
    font-size: 11px;
    color: var(--color-text-muted);
}

.tickets-tab__legend-item {
    display: flex;
    align-items: center;
    gap: 5px;
    white-space: nowrap;
}

.tickets-tab__legend-swatch {
    display: block;
    width: 18px;
    height: 6px;
    border-radius: 3px;
}

.tickets-tab__legend-swatch--on-track {
    background: var(--color-ink);
}

.tickets-tab__legend-swatch--over {
    background: repeating-linear-gradient(135deg, var(--color-severity-critical) 0 3px, #b83a24 3px 6px);
}

.tickets-tab__legend-swatch--not-tracked {
    border: 1px dashed #b3a98f;
}

/* Column grid — shared by the header row and every data row alike (see
   `.tickets-tab__row` below), matching the design's own
   `64px/1fr/128px/64px/104px/170px/124px` id/title/reporter/age/activity/
   progress/comments grid exactly, rather than the previous ad hoc flex
   widths. */
.tickets-tab__col-head {
    display: grid;
    grid-template-columns: 64px minmax(0, 1fr) 128px 64px 104px 170px 124px;
    align-items: center;
    gap: 12px;
    padding: 8px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

/* Same affordance as `.deps-tab__col--sortable`/`.code-tab__col--sortable`
   — this tab's own token, per this codebase's per-tab BEM convention. */
.tickets-tab__col--sortable {
    cursor: pointer;
    user-select: none;
    display: inline-flex;
    align-items: center;
    gap: 3px;
}

.tickets-tab__col--sortable:hover {
    color: var(--color-ink);
}

.tickets-tab__col--sortable::after {
    content: '\21C5';
    font-size: 9px;
    opacity: 0.4;
}

.tickets-tab__col--sortable[data-sort-active="asc"]::after {
    content: '\2191';
    opacity: 1;
}

.tickets-tab__col--sortable[data-sort-active="desc"]::after {
    content: '\2193';
    opacity: 1;
}

.tickets-tab__row {
    display: grid;
    grid-template-columns: 64px minmax(0, 1fr) 128px 64px 104px 170px 124px;
    align-items: center;
    gap: 12px;
    padding: 11px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 13px;
}

/*
 * "The search doesn't really filter the table" — root cause: this rule's
 * own `display` above (originally `flex`, now `grid` per the design's own
 * column grid) is an author-stylesheet declaration, which always wins over
 * the browser's default `[hidden] { display: none }` (a user-agent-origin
 * rule — origin beats specificity in the cascade), so `row.hidden = true`
 * from `table_search_controller.js` was silently doing nothing. This
 * explicit `[hidden]` override, in the SAME (author) origin and with
 * higher specificity than the plain class alone, is what actually makes
 * hiding a row work.
 */
.tickets-tab__row[hidden] {
    display: none;
}

.tickets-tab__row:last-child {
    border-bottom: none;
}

/* Column sizing now comes entirely from `.tickets-tab__row`/`.tickets-tab
   __col-head`'s own `grid-template-columns` (design-fidelity Stage 10) —
   these per-column rules only carry column-specific text styling now, not
   width/flex (each cell's grid track already fixes its width). */
.tickets-tab__col--title {
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 8px;
}

.tickets-tab__col--reporter {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12px;
    color: var(--color-text-muted);
}

.tickets-tab__col--age {
    text-align: right;
    font-size: 12px;
    color: var(--color-text-faint);
}

/* Base fallback color only — a real row's activity cell gets its actual
   staleness color inline (`ticket.activityColor`, see `App\ProjectDetail\
   TicketActivityColors::colorFor()`'s own docblock for the 3 flat
   day-based thresholds), this only applies when there's no known
   `lastActivityAt` to grade at all (the dash state). */
.tickets-tab__col--activity {
    text-align: right;
    font-size: 12px;
    color: var(--color-text-faint);
}

.tickets-tab__estimate--none {
    color: var(--color-text-faint);
}

/*
 * Progress bar follow-up ("show in a progress bar thingy the estimated
 * time and the time already tracked on the task") — track/bar/label
 * reuse `App\ProjectDetail\RetainerBudgetView`'s own Overview-tab budget
 * bar colour tokens (`.project-detail__retainer-bar--normal/--over`'s
 * `--color-status-ticketed`/`--color-severity-critical`) rather than
 * inventing new colours for what's the same "used vs. budgeted" concept.
 */
.tickets-tab__col--progress {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* "Fix the no-estimate dash to `--color-text-dim`" — was one tier too
   dark at `--color-text-faint` (this dash means "no GitLab estimate at
   all", a genuinely different, lighter-weight absence than the muted
   secondary text `--color-text-faint` is normally used for). */
.tickets-tab__progress-none {
    color: var(--color-text-dim);
    font-size: 12px;
}

.tickets-tab__progress-track {
    flex: 1;
    height: 6px;
    background: var(--color-border-muted);
    border-radius: 3px;
    overflow: hidden;
}

/* "Add a dashed-border empty track for the not-tracked progress state"
   (was solid, visually identical to the real filled-bar track's own empty
   background) — a real estimate exists but no Toggl task matched, so the
   track itself should look distinctly "unmeasured", not just "at 0%". */
.tickets-tab__progress-track--empty {
    background: none;
    border: 1px dashed #b3a98f;
}

.tickets-tab__progress-bar {
    display: block;
    height: 100%;
    border-radius: 3px;
}

.tickets-tab__progress-bar--normal {
    background: var(--color-status-ticketed);
}

.tickets-tab__progress-bar--over {
    background: var(--color-severity-critical);
}

.tickets-tab__progress-label {
    flex: none;
    font-size: 11px;
    color: var(--color-text-muted);
    white-space: nowrap;
}

.tickets-tab__progress-label--untracked {
    color: var(--color-text-faint);
}

.tickets-tab__progress-label--over {
    color: var(--color-severity-critical);
    font-weight: 600;
}

.tickets-tab__col--comments {
    display: flex;
    justify-content: flex-end;
}

.tickets-tab__id {
    font-family: var(--font-mono);
    color: var(--color-ink);
    font-weight: 600;
}

/* "Click on the title to get linked to the gitlab ticket" — the global
   `a` reset already drops the underline/keeps ink colour by default, so
   this only needs to add back the display/truncation behavior a plain
   `<span>` used to provide. */
.tickets-tab__title {
    display: block;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 500;
}

.tickets-tab__from-finding {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-muted);
    background: #efe7d5;
    border-radius: 5px;
    padding: 2px 7px;
    flex: none;
    white-space: nowrap;
}

/*
 * Design-fidelity follow-up: "Tickets with more than 5 comments should be
 * marked yellow ... more than 10 ... red" — `.tickets-tab__comments`
 * (normal, plain muted text) vs. the `.badge` variant (warning/critical,
 * colored inline per `App\ProjectDetail\CommentSeverity` — see that
 * template's own docblock for why "normal" never gets pill treatment).
 */
.tickets-tab__comments {
    text-align: right;
    font-size: 12px;
}

.tickets-tab__comments--normal {
    color: var(--color-text-faint);
}

/* "Add a small chat-bubble SVG icon to the comments badge's elevated-
   severity state" — only the warning/critical `.badge` variant gets it
   (the plain "normal" muted-text state never had, and still doesn't get,
   pill treatment at all — see the docblock above). */
.badge.tickets-tab__comments {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    text-align: center;
}

.tickets-tab__comments-icon {
    width: 11px;
    height: 11px;
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linejoin: round;
}

/* "Add pagination to the ticket table" — hidden entirely (not just
   inert) when everything fits on one page, matching this app's own
   "don't show a useless control" convention elsewhere. */
.tickets-tab__pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    padding: 10px 16px;
    border-top: 1px solid var(--color-border-muted);
}

.tickets-tab__page-button {
    font-size: 12px;
    font-family: inherit;
    border: 1px solid #cfc5ae;
    border-radius: 2px;
    padding: 4px 10px;
    background: #fffcf5;
    color: var(--color-ink);
    cursor: pointer;
}

.tickets-tab__page-button:disabled {
    color: var(--color-text-faint);
    cursor: default;
    opacity: 0.6;
}

.tickets-tab__page-info {
    font-size: 12px;
    color: var(--color-text-muted);
}

/*
 * "Tickets created by refinery should get an extra label or a visual
 * indicator" — reuses the already-established "acknowledged"/status-blue
 * token (`App\Registry\EolStatus::UpdateAvailable`'s own color), not a new
 * hex value.
 */
.tickets-tab__refinery-badge {
    flex: none;
    white-space: nowrap;
    color: #54718e;
    background: #e4e7e2;
}

/*
 * "Add a separate component in the projects ticket tab above the ticket
 * table to highlight the high priority issues" — design-fidelity Stage 10:
 * the solid-red-banner treatment (was only a left-border accent using the
 * wrong severity token — `--color-severity-high`, materially lighter than
 * the design's actual `#d84a31`/`--color-severity-critical`). `#fcf1ea`
 * (body tint) and `#f1d3c3` (row border) are literal hex — matching the
 * design fragment exactly — since neither has an exact existing token
 * (they're lighter/more saturated than `--color-severity-critical-bg`'s
 * `#f9e2d6`, a deliberately different, more alarming tint for this one
 * card). Hidden entirely (see the template's own `{% if %}`) rather than
 * shown empty when there are no high-priority tickets open.
 */
.tickets-tab__priority-card {
    margin-top: 16px;
    margin-bottom: 12px;
    border: 1px solid var(--color-severity-critical);
    border-radius: var(--radius-panel);
    overflow: hidden;
    background: #fcf1ea;
    padding: 0;
}

.tickets-tab__priority-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 9px 16px;
    background: var(--color-severity-critical);
}

.tickets-tab__priority-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #fffcf5;
    flex: none;
}

.tickets-tab__priority-title {
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: #fffcf5;
}

.tickets-tab__priority-sub {
    flex: 1;
    text-align: right;
    font-size: 11px;
    color: #fde4d8;
}

/* Each row is one clickable link (the design fragment's own per-row
   `<a>`), split into separate id/title/label-pills/age cells instead of a
   single combined "#id · title" link plus a plain comma-joined string. */
.tickets-tab__priority-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 16px;
    border-bottom: 1px solid #f1d3c3;
    text-decoration: none;
}

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

.tickets-tab__priority-id {
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 600;
    color: #8e2f1e;
    width: 40px;
    flex: none;
}

.tickets-tab__priority-title-text {
    flex: 1;
    min-width: 0;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--color-ink);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tickets-tab__priority-pills {
    display: flex;
    gap: 5px;
    flex: none;
}

/* Real per-label pill colors ("Add real per-label pill colors") — the
   `color`/`background` themselves are resolved per-label via
   `ticket.labelPillColor()` (see `App\ProjectDetail\LabelPillPalette`'s
   own docblock for the deterministic hash/palette) and rendered inline;
   this rule only carries the shared pill chrome. */
/* Visual QA follow-up: 2 of the 6 `LabelPillPalette` background tints
   (`--color-severity-high-bg`/`--color-severity-medium-bg`) are low-
   contrast against this card's own `#fcf1ea` body tint (measured ~7-15
   and ~6-34 per RGB channel respectively) — those pills rendered as plain
   colored text with no visible box. A subtle border makes every pill's
   shape visible regardless of which palette slot a given label happens
   to hash into, without touching the palette/hash logic itself. */
.tickets-tab__priority-pill {
    font-size: 10px;
    font-weight: 600;
    border-radius: 4px;
    padding: 2px 7px;
    white-space: nowrap;
    border: 1px solid rgba(0, 0, 0, 0.08);
}

.tickets-tab__priority-age {
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 600;
    color: #8e2f1e;
    width: 34px;
    text-align: right;
    flex: none;
}

/* ---- Public Status Page (Stage 8 follow-on: GET /status/{token}) ----
 * Deliberately outside `.app-shell`/`.app-content` (no sidebar/topbar — see
 * `templates/status_page/show.html.twig`'s docblock) but reuses this file's
 * own `:root` tokens (palette/typography/radius) for visual consistency
 * with the rest of the app.
 */

.status-page {
    min-height: 100vh;
    display: flex;
    justify-content: center;
    padding: 48px 20px;
}

.status-page__container {
    width: 100%;
    max-width: 720px;
}

.status-page__header {
    margin-bottom: 28px;
}

.status-page__eyebrow {
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--color-text-faint);
}

.status-page__title {
    font-size: 24px;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--color-ink);
    margin: 6px 0 0;
}

.status-page__subtitle {
    margin: 4px 0 0;
    font-size: 13px;
    color: var(--color-text-muted);
}

.status-page__empty {
    padding: 40px 16px;
    text-align: center;
    color: var(--color-text-faint);
    font-size: 13px;
}

.status-page__list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.status-page__card {
    padding: 16px 18px;
}

.status-page__card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}

.status-page__card-title {
    display: flex;
    align-items: center;
    gap: 9px;
}

.status-page__status-dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    flex: none;
}

.status-page__status-dot--up {
    background: var(--color-status-resolved);
}

.status-page__status-dot--down {
    background: var(--color-severity-critical);
}

.status-page__status-dot--unknown {
    background: var(--color-text-faint);
}

.status-page__page-type {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-ink);
}

.status-page__card-meta {
    display: flex;
    align-items: center;
    gap: 12px;
}

.status-page__status-label {
    font-size: 12px;
    font-weight: 600;
}

.status-page__status-label--up {
    color: var(--color-status-resolved);
}

.status-page__status-label--down {
    color: var(--color-severity-critical);
}

.status-page__status-label--unknown {
    color: var(--color-text-faint);
}

.status-page__uptime-pct {
    font-size: 12px;
    color: var(--color-text-muted);
}

.status-page__history {
    display: flex;
    align-items: stretch;
    gap: 2px;
    margin-top: 14px;
    height: 28px;
}

.status-page__history-bar {
    flex: 1;
    min-width: 2px;
    border-radius: 2px;
    background: var(--color-border-muted);
    cursor: default;
}

.status-page__history-bar--up {
    background: var(--color-status-resolved);
}

.status-page__history-bar--down {
    background: var(--color-severity-critical);
}

.status-page__history-bar--partial {
    background: var(--color-severity-medium);
}

.status-page__history-bar--no-data {
    background: var(--color-border-muted);
}

.status-page__history-labels {
    display: flex;
    justify-content: space-between;
    margin-top: 4px;
    font-size: 10px;
    color: var(--color-text-faint);
}

.status-page__footer {
    margin-top: 28px;
    text-align: center;
    font-size: 11px;
    color: var(--color-text-faint);
}

/* ---- Themed error pages (403/404/500) ----
 * Symfony's default `ErrorController` renders these directly (no app
 * controller involved), so - same reasoning as `.status-page` above -
 * this deliberately stands alone rather than extending
 * `templates/layout/app.html.twig` (whose sidebar needs `active_nav`/nav
 * state this context doesn't reliably have), reusing this file's own
 * `:root` tokens for visual consistency instead.
 */

.error-page {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 48px 20px;
}

.error-page__card {
    width: 100%;
    max-width: 480px;
    text-align: center;
    padding: 40px 36px;
}

.error-page__code {
    font-family: var(--font-mono);
    font-size: 13px;
    font-weight: 500;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--color-text-faint);
}

.error-page__title {
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--color-ink);
    margin: 10px 0 0;
}

.error-page__message {
    margin: 10px 0 0;
    font-size: 13px;
    color: var(--color-text-secondary);
    line-height: 1.6;
}

.error-page__actions {
    margin-top: 24px;
}

/* ---- Projects Registry: intro banner + "New Project" entry point ---- */

.registry__intro {
    font-size: 12px;
    color: var(--color-text-muted);
    line-height: 1.6;
    background: var(--color-header-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    padding: 10px 14px;
    margin-bottom: 14px;
}

.registry__head-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 0 10px;
}

.registry__count {
    font-size: 12px;
    color: var(--color-text-faint);
}

/*
 * Stage 6: the design's dark-filled "＋ New project" CTA
 * (`background:#211a0e;color:#fff;text-transform:uppercase`) — a `.btn`
 * modifier rather than a one-off style, so the Customers Registry's own
 * "＋ New customer" button (Stage 8, same design treatment, not yet fixed)
 * can pick it up too instead of a second copy landing later.
 */
.btn--cta {
    font-size: 12px;
    font-weight: 600;
    border: none;
    background: var(--color-ink);
    color: #fff;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.btn--cta:hover {
    background: #362f21;
}

/* ---- Project Configurator wizard ---- */

.wizard {
    max-width: 760px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.wizard__card {
    padding: 24px 28px;
}

.wizard__title {
    font-size: 18px;
    font-weight: 600;
    margin: 0 0 4px;
}

.wizard__subtitle {
    font-size: 14px;
    font-weight: 600;
    margin: 20px 0 8px;
}

.wizard__intro {
    color: var(--color-text-muted);
    font-size: 13px;
    margin: 0 0 18px;
}

.wizard__fieldset {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    padding: 14px 16px 4px;
    margin: 0 0 16px;
}

.wizard__legend {
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--color-text-muted);
    padding: 0 4px;
}

.wizard__field {
    margin-bottom: 12px;
}

.wizard__label {
    display: block;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    margin-bottom: 4px;
}

.wizard__input {
    width: 100%;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-panel);
    padding: 7px 9px;
    font-size: 13px;
    background: var(--color-surface);
    color: var(--color-ink);
}

/*
 * QA fix: every <select> in the app (wizard/modal forms via
 * `select.wizard__input`, plus every other page's own select class) relied
 * entirely on native OS/browser chrome — no custom arrow, no consistent
 * height with sibling text inputs. One shared chevron for all of them
 * rather than duplicating the background-image per class.
 */
select.wizard__input,
.analytics__select,
.findings__select,
.settings__select {
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236f6757' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
    padding-right: 28px;
    cursor: pointer;
}

.wizard__input--textarea {
    min-height: 80px;
    resize: vertical;
}

.wizard__help {
    font-size: 11px;
    color: var(--color-text-faint);
    margin: 4px 0 0;
}

.wizard__errors {
    list-style: none;
    margin: 4px 0 0;
    padding: 0;
    font-size: 11px;
    color: var(--color-severity-critical);
}

.wizard__checkbox-label {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    cursor: pointer;
}

.wizard__checkbox-text {
    font-size: 13px;
    color: var(--color-text-secondary);
}

.wizard__actions {
    display: flex;
    justify-content: space-between;
    margin-top: 20px;
}

.wizard__summary {
    display: grid;
    grid-template-columns: 180px 1fr;
    gap: 6px 12px;
    font-size: 13px;
    margin: 0 0 16px;
}

.wizard__summary dt {
    color: var(--color-text-muted);
}

.wizard__summary dd {
    margin: 0;
}

.wizard__connector-list {
    list-style: none;
    padding: 0;
    margin: 0 0 16px;
    font-size: 13px;
}

.wizard__connector-list li {
    padding: 4px 0;
    border-bottom: 1px solid var(--color-border-muted);
}

.wizard__connector-list li:last-child {
    border-bottom: none;
}

.wizard__flash {
    background: var(--color-severity-high-bg);
    color: var(--color-severity-high);
    padding: 10px 14px;
    border-radius: var(--radius-panel);
    font-size: 13px;
    margin-bottom: 16px;
}

.wizard__token-warning {
    background: var(--color-severity-high-bg);
    color: var(--color-severity-high);
    padding: 10px 14px;
    border-radius: var(--radius-panel);
    font-size: 13px;
    margin-bottom: 16px;
}

.wizard__token-row {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 8px;
}

.wizard__token-value {
    flex: 1;
    font-family: var(--font-mono);
    background: var(--color-header-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    padding: 8px 10px;
    font-size: 13px;
    overflow-wrap: anywhere;
}

/* ---- New Project Modal (Stage 7) ----
   Same fixed-overlay/backdrop recipe as `.customer-modal` (reused idiom,
   not re-derived from the design's own literal `rgba(0,0,0,0.42)` scrim —
   see `templates/project_wizard/_modal_shell.html.twig`'s docblock), just
   its own class namespace since this dialog needs extra chrome (the
   step-pill header, a scrolling body separate from a pinned footer) none
   of the Customer/Staff/Suggest-Rule modals need. */

.project-wizard-modal {
    position: fixed;
    inset: 0;
    z-index: 51;
    background: rgba(33, 26, 14, 0.28);
    display: flex;
    align-items: center;
    justify-content: center;
}

.project-wizard-modal[hidden] {
    display: none;
}

.project-wizard-modal__panel {
    width: 720px;
    max-width: 94vw;
    max-height: 90vh;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.25);
    border-radius: var(--radius-panel);
    display: flex;
    flex-direction: column;
}

.project-wizard-modal__form {
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.project-wizard-modal__header {
    flex: none;
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 14px 22px;
    border-bottom: 1px solid var(--color-border);
}

.project-wizard-modal__title {
    font-size: 15px;
    font-weight: 600;
}

.project-wizard-modal__header-spacer,
.project-wizard-modal__footer-spacer {
    flex: 1;
}

.project-wizard-modal__steps {
    display: flex;
    align-items: center;
    gap: 12px;
}

.project-wizard-modal__step {
    display: flex;
    align-items: center;
    gap: 6px;
}

.project-wizard-modal__step-num {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    border: 1px solid var(--color-border-strong);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    font-weight: 600;
    flex: none;
    color: var(--color-text-faint);
}

.project-wizard-modal__step-label {
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--color-text-faint);
}

.project-wizard-modal__step--active .project-wizard-modal__step-num {
    border-color: var(--color-ink);
    background: var(--color-ink);
    color: #fff;
}

.project-wizard-modal__step--active .project-wizard-modal__step-label {
    color: var(--color-ink);
    font-weight: 600;
}

.project-wizard-modal__step--done .project-wizard-modal__step-num {
    border-color: var(--color-status-resolved);
    color: var(--color-status-resolved);
}

.project-wizard-modal__step--done .project-wizard-modal__step-label {
    color: var(--color-status-resolved);
}

.project-wizard-modal__close {
    cursor: pointer;
    color: var(--color-text-faint);
    font-size: 16px;
    padding: 2px 6px;
    border-radius: 5px;
    background: none;
    border: none;
    flex: none;
}

.project-wizard-modal__close:hover {
    background: var(--color-status-accepted-risk-bg);
    color: var(--color-ink);
}

.project-wizard-modal__body {
    flex: 1;
    overflow-y: auto;
    padding: 18px 22px;
}

/*
 * QA fix (IMPLEMENTATION_PLAN.md Stage 7 polish pass): the Basics step's
 * fields were stacked one-per-row inside bordered `<fieldset>`s — the
 * design's own Basics step is a flat 2-column grid with no section
 * dividers at all. `App\Form\ProjectBasicsType`'s field set is untouched;
 * only `templates/project_wizard/basics.html.twig`'s markup changed to
 * drop the fieldsets and wrap fields in this grid instead.
 */
.project-wizard-modal__grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4px 18px;
}

.project-wizard-modal__grid .wizard__field {
    margin-bottom: 12px;
}

.project-wizard-modal__footer {
    flex: none;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 22px;
    margin: 0;
    border-top: 1px solid var(--color-border);
}

/*
 * ---- Project Detail: Security tab ----
 * A plain flex column with a deliberately generous gap keeps this tab's
 * top-level pieces (the Cloudflare/WAF section, the "OUTSTANDING ISSUES"
 * divider, the "other security findings" card) visually distinct rather
 * than reading as one continuous panel, matching `.dashboard__col`'s own
 * "stack of unrelated panels" gap convention.
 *
 * IMPLEMENTATION_PLAN.md Stage 8 rebrush: the Cloudflare/WAF section now
 * opens with a dark `.security-tab__banner` ("EDGE · LIVE WAF TRAFFIC")
 * exactly like `.performance-tab__section-banner` (Stage 7) — same
 * `--color-ink`/`#f1ecdf`/`#a89d86` dark-on-ink palette, reused literally
 * rather than re-deriving it a third time (`.deps-tab__selection-bar`'s own
 * docblock already explains why these exact hexes are kept literal). The
 * one block beneath it (`.security-tab__panel` — either the empty-state
 * card or the joined KPI+chart+posture panel) chains under the banner the
 * same way `.performance-tab__section-panel` does (`border-top: none`,
 * squared-off top) but never needs Performance's separate `--last`
 * modifier: Security only ever has exactly one block per banner (never a
 * multi-card chain), so that one block always both opens and closes the
 * shape and gets its bottom corners back unconditionally. Kept as its own
 * scoped class (mirroring Stage 7's pattern/values, not literally
 * reusing `.performance-tab__section-panel`) for the same reason Stage 7
 * itself mirrored rather than reused `.business-tab__chart-card` — real
 * content differences (a 2-column chart+posture body vs. Performance's
 * KPI-row+chart stack) would otherwise carry unused rules either way.
 */
.security-tab {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.security-tab__section {
    display: block;
}

.security-tab__source-line {
    display: flex;
    align-items: baseline;
    margin-bottom: 8px;
    font-size: 11px;
    color: var(--color-text-faint);
}

.security-tab__banner {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 16px;
    background: var(--color-ink);
    border-radius: var(--radius-panel) var(--radius-panel) 0 0;
}

.security-tab__banner-label {
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.12em;
    color: #f1ecdf;
}

.security-tab__banner-caption {
    font-size: 11px;
    color: #a89d86;
}

/* Chains directly beneath `.security-tab__banner` — no top border/radius of
   its own (the banner above already draws that edge), bottom corners
   restored unconditionally since this is always the one and only block in
   the chain (see this section's own top docblock). */
.security-tab__panel {
    padding: 0;
    border-color: var(--color-border-strong);
    border-top: none;
    border-radius: 0 0 var(--radius-panel) var(--radius-panel);
}

/* The two "not connected"/"no data yet" empty states — the design's own
   literal `52px 24px` for a block chained directly under a banner (shorter
   than `.business-tab__empty-state`'s standalone 64px). Chart-
   interactivity-follow-up plan, Stage 4: the eyebrow/title/body/CTA
   content itself moved into the shared `.empty-state__*` family (see that
   block's own docblock, below `.section-head__caption`) — this class now
   only carries the padding/centering this tab's own banner-chained panel
   needs; its title/body sizes (16px/430px) match the shared block's own
   defaults exactly, so no override rule is needed here (Business/SEO each
   need one — see their own blocks). */
.security-tab__panel--empty {
    padding: 52px 24px;
    text-align: center;
}

/* Joined KPI+chart+posture panel (Stage 8) — a hairline `--color-border
   -muted` 4-tile KPI grid on top (same `background`-as-gridline trick
   `.performance-tab__kpis` established in Stage 7), then a `1fr 330px`
   two-column body (chart left, posture list right) below, replacing 3
   previously separate `.card`s with one bordered block. */
.security-tab__kpis {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 1px;
    background: var(--color-border-muted);
    border-bottom: 1px solid var(--color-border);
}

.security-tab__kpi-tile {
    background: var(--color-surface);
    padding: 14px 16px 16px;
}

.security-tab__kpi-label {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-muted);
}

/* IMPLEMENTATION_PLAN.md Stage 8: bumped 24px → 30px. */
.security-tab__kpi-value {
    font-size: 30px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1;
    margin-top: 9px;
}

.security-tab__kpi-sub {
    margin-top: 7px;
    font-size: 11px;
    color: var(--color-text-faint);
}

/* IMPLEMENTATION_PLAN.md Stage 8: `2fr 1fr` → `1fr 330px`. */
.security-tab__body {
    display: grid;
    grid-template-columns: 1fr 330px;
}

.security-tab__chart-col {
    padding: 14px 18px 10px;
    border-right: 1px solid var(--color-border);
}

/* Chart-interactivity-follow-up plan, Stage 2: the decoded design fragment
   folds the "Selected" readout (when/value/flag) directly into this same
   head row, right of the title — unlike Business's separate readout row
   above its chart head — via `.security-tab__chart-readout-spacer`
   (`flex:1`) rather than `justify-content: space-between` (which would
   otherwise fight the spacer for the same job once a 5th/6th child is
   added). Gap/margin bumped to the design fragment's own literal values. */
.security-tab__chart-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin-bottom: 6px;
}

/* Design-fidelity review follow-up (Stage 8): the decoded design's own
   chart-title kicker is mono/10px/uppercase/`0.1em`/muted — the same shape
   Stage 6 already established for `.business-tab__chart-title` (that
   sibling happens to use `--color-text-secondary` for its own title; this
   one's real color match is the muted `--color-text-faint`, per the
   decoded fragment). Not the plain 13px/600/ink label this card shipped
   with initially. */
.security-tab__chart-title {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-faint);
}

.security-tab__chart-caption {
    font-size: 11px;
    color: var(--color-text-faint);
}

/* Chart-interactivity-follow-up plan, Stage 2: the "Selected" readout
   fields folded into `.security-tab__chart-head` — see that class's own
   comment for why this chart's readout lives in its head row rather than
   a separate row above it (Business's own layout) — plus the flex spacer
   shared by both this row and the legend/caption row below. Values ported
   verbatim from the decoded design fragment's inline styles, same
   discipline `.business-tab__chart-readout-*` already established. */
.security-tab__chart-readout-spacer {
    flex: 1;
}

.security-tab__chart-readout-when {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-muted);
}

.security-tab__chart-readout-value {
    font-size: 13px;
    font-weight: 700;
    color: #c96a10;
}

.security-tab__chart-readout-flag {
    font-size: 11px;
    font-weight: 600;
}

/* Chart-interactivity-follow-up plan, Stage 2: `margin-top` dropped —
   `.security-tab__chart-head`'s own `margin-bottom: 6px` now supplies the
   gap above this `<svg>` directly (matching the decoded design fragment's
   literal spacing, which puts `margin-bottom` on the head row and nothing
   extra on the chart itself). */
.security-tab__chart {
    width: 100%;
    display: block;
}

/* Chart-interactivity-follow-up plan, Stage 2: now also carries the real
   "click the chart to inspect an hour" caption (a `.security-tab__chart-
   readout-spacer` + `.security-tab__chart-caption` pair added as siblings
   of the existing swatch+label), matching the decoded design fragment's
   own bottom row (legend left, caption right). `gap`/`padding` bumped to
   that fragment's own literal values (was `gap: 5px`/`margin-top: 8px`).
   The swatch+label pair moved into its own `.security-tab__chart-legend
   -label` wrapper so its original 5px swatch-to-text gap survives
   unchanged — a flat `gap: 16px` here would otherwise apply uniformly to
   *every* direct child, widening that swatch-to-text gap along with the
   new legend-to-caption one. */
.security-tab__chart-legend {
    display: flex;
    align-items: center;
    gap: 16px;
    font-size: 11px;
    color: var(--color-text-muted);
    padding: 2px 0 10px;
}

.security-tab__chart-legend-label {
    display: flex;
    align-items: center;
    gap: 5px;
}

/* Chart-interactivity-follow-up plan, Stage 2: dashed selection marker +
   invisible hit-targets — same shape/values as `.business-tab__chart-
   readout-line`/`-marker`/`-hit`, this chart's own single-series marker
   reusing the shared `chart_readout_controller.js` unchanged. */
.security-tab__chart-readout-line {
    stroke: var(--color-ink);
    stroke-width: 1;
    stroke-dasharray: 3 3;
    opacity: 0.5;
}

.security-tab__chart-readout-marker {
    fill: var(--color-surface);
    stroke: #c96a10;
    stroke-width: 2.5;
}

.security-tab__chart-hit {
    fill: transparent;
    cursor: crosshair;
}

.security-tab__chart-legend-swatch {
    width: 14px;
    height: 2px;
    background: #c96a10;
}

.security-tab__posture-col {
    background: var(--color-sidebar-bg);
}

.security-tab__posture-head {
    padding: 10px 16px;
    background: #f3ecdb;
    border-bottom: 1px solid var(--color-border);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-secondary);
}

/* One "Edge security posture" row — `#e6ddc9` is the design's own one-off
   row-divider tint (doesn't match any existing `--color-border-*` token,
   kept literal same as `.section-head__rule`'s own `#d9d0ba`). `p.bg`
   (`SecurityPostureRow::$bg`) drives the row background inline; it's
   `transparent` for every real row this stage builds (Decision 5 — no
   highlighting rule exists yet). */
.security-tab__posture-row {
    padding: 11px 16px;
    border-bottom: 1px solid #e6ddc9;
    font-size: 12.5px;
}

.security-tab__posture-row-main {
    display: flex;
    align-items: center;
    gap: 9px;
}

/* `8px` (up from the pre-Stage-8 posture dot's `7px`) — the decoded design
   fragment's own literal `width:8px;height:8px` for this dot, a small
   design-fidelity correction alongside the rest of this stage's rework,
   not an unexplained drift. */
.security-tab__posture-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    flex: none;
}

.security-tab__posture-label {
    flex: 1;
    color: var(--color-text-muted);
}

.security-tab__posture-value {
    font-weight: 700;
}

/* `SecurityPostureRow::$hint` — only ever rendered when `$hasHint` is
   `true`, which is never yet (Decision 5); markup/CSS exist so a future
   rule can populate it without another template change. `#6b4e0c` is the
   design's own literal hint-text tint. */
.security-tab__posture-hint {
    margin-top: 5px;
    padding-left: 17px;
    font-size: 11px;
    color: #6b4e0c;
}

/* Design-fidelity review follow-up (Stage 8): "Top triggered WAF rules"/
   "Blocked traffic by origin" sit outside the joined KPI+chart+posture
   panel (the decoded design never wraps them into it), but the decoded
   design still gives both of their own headers the same tan header-band
   treatment as every other banded card header on this tab (the Findings
   card's `.dashboard__panel-head--band`).
   Code-quality review follow-up: the container chrome for that band
   (`display:flex;align-items:center;justify-content:space-between;
   padding:12px 16px;background:var(--color-header-bg);border-bottom:1px
   solid var(--color-border)`) matches what `.dashboard__panel-head`/
   `--band` (Stage 0) already provides — this pair now reuses that pair of
   classes directly on the container instead of a duplicate
   `.security-tab__panel-head` container class. Only the *title text*
   genuinely differs from every other `.dashboard__panel-head` consumer in
   this app (a mono/10px/600/`--color-text-secondary` kicker, vs. the
   default 13px/600 plain label `.dashboard__panel-head` renders for
   things like the Findings card's title) — that's the one real new bit,
   kept as its own small modifier.
   Second follow-up round: `.dashboard__panel-head` isn't *quite* identical
   to the old container it replaced — it also sets `font-weight: 600`,
   which `.security-tab__panel-head-title` already overrides explicitly but
   the plain "24h" caption span didn't, so it silently inherited bold
   instead of its correct normal weight (the decoded design's own caption
   spans carry no font-weight at all, just `font-size:11px;color:#8c8271`).
   Same hazard `.dashboard__panel-head-link` already has to counteract with
   its own explicit `font-weight: 500` — `.security-tab__panel-head-caption`
   now does the same with `font-weight: 400`. */
.security-tab__panel-head-title {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-secondary);
}

.security-tab__panel-head-caption {
    font-size: 11px;
    font-weight: 400;
    color: var(--color-text-faint);
}

/*
 * Generic small "where this data comes from" / "how to configure this"
 * note box — first used on the Security tab (CVE/cert source explainer)
 * and the Configure tab's Cloudflare fieldset (token-creation
 * instructions), reusable anywhere else a short explanatory aside is
 * needed rather than inventing a new box per tab.
 */
.info-note {
    margin-top: 8px;
    padding: 9px 12px;
    font-size: 11px;
    line-height: 1.5;
    color: var(--color-text-muted);
    background: var(--color-header-bg);
    border: 1px solid var(--color-border-muted);
    border-radius: 2px;
}

.info-note a {
    font-weight: 600;
    color: var(--color-ink);
}

/*
 * ---- Project Detail: Performance tab ----
 * IMPLEMENTATION_PLAN.md Stage 7 rebrush: each half (`.performance-tab
 * __section`) now opens with a dark `.performance-tab__section-banner`
 * (same `--color-ink`/`#f1ecdf`/`#a89d86` dark-on-light-ink palette
 * `.deps-tab__selection-bar` already established for this exact literal
 * palette — see that rule's own docblock for why `#f1ecdf`/`#a89d86` are
 * kept as literal hexes rather than reusing a lighter token), and every
 * block directly under that banner chains beneath it (`.performance-tab
 * __section-panel`/`--last`, `border-top: none` plus a squared-off top,
 * only the last block in the chain getting its bottom corners back) —
 * one continuous bordered shape per half, not a gap-separated stack of
 * `.card`s. `.performance-tab__chart-card`/`.performance-tab__panel`
 * deliberately mirror(ed) `.business-tab__chart-*`/`.business-tab__panel`
 * rather than reusing them outright: the two charts differ enough (no
 * baseline/band/deploy layer here, see `App\ProjectDetail\
 * PerformanceChartBuilder`'s docblock) that a shared class would carry
 * unused Business-tab-only rules; the *values* were kept identical on
 * purpose so both tabs' cards read as one visual system.
 */
.performance-tab {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.performance-tab__section {
    display: block;
}

.performance-tab__section-banner {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 16px;
    background: var(--color-ink);
    border-radius: var(--radius-panel) var(--radius-panel) 0 0;
}

.performance-tab__section-banner-label {
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.12em;
    color: #f1ecdf;
}

.performance-tab__section-banner-caption {
    font-size: 11px;
    color: #a89d86;
}

/* Chains a block directly beneath `.performance-tab__section-banner` — no
   top border/radius of its own, since the banner above already draws that
   edge. Only the last block in a given half's chain adds
   `--section-panel--last` back on top, restoring the bottom corners so the
   whole chained shape reads as one continuous rounded panel. */
.performance-tab__section-panel {
    border-top: none;
    border-radius: 0;
}

.performance-tab__section-panel--last {
    border-radius: 0 0 var(--radius-panel) var(--radius-panel);
}

/* The two "not connected"/"no data yet" empty states are shorter than
   `.business-tab__empty-state`'s own default 64px vertical padding once
   they're chained directly under a section banner instead of floating
   alone — the design's own literal `52px 24px` for this exact placement. */
.performance-tab__section-empty {
    padding: 52px 24px;
}

/* New Stage 7 grid rows below the joined KPI+chart card — 3-col
   (Exceptions/Non-fatal/Slow queries), then 2-col `1.5fr 1fr` (Slowest
   transactions/Observations), matching the design's own literal ratios
   (the "Where backend time goes" full-width row after these needs no grid
   at all, just the `margin-top: 12px` every row here shares). */
.performance-tab__row {
    display: grid;
    gap: 12px;
    align-items: start;
    margin-top: 12px;
}

.performance-tab__row--3col {
    grid-template-columns: repeat(3, 1fr);
}

.performance-tab__row--2col {
    grid-template-columns: 1.5fr 1fr;
}

.performance-tab__chart-card,
.performance-tab__panel {
    padding: 14px 16px;
}

/* Now a joined KPI+chart block (Stage 7), always the sole/last block in
   the Tideways half's chained section — the design's own stronger
   `--color-border-strong` border on this specific panel (vs. the plain
   `--color-border` every other card here uses) marks it as the section's
   main panel. */
.performance-tab__chart-card {
    padding: 0;
    border-color: var(--color-border-strong);
}

/* The new `perfKpis` 4-tile row (`PerformanceTabView::$perfKpis`) — a
   hairline `--color-border-muted` grid (1px gaps showing through as
   dividers between tiles) capped by a `--color-border` rule, matching the
   design's own literal `background:#ece4d2` hairline treatment. */
.performance-tab__kpis {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 1px;
    background: var(--color-border-muted);
    border-bottom: 1px solid var(--color-border);
}

/* Ticket #19: every tile now spans half the 12-col `.code-history__kpis`
   grid it shares classes with (only 4 tiles here vs. Code History's 6, so
   2-per-row keeps both rows fully filled rather than leaving a gap — see
   the template's own comment at this grid's call site). `.code-history__kpi
   (--hero)` (reused verbatim, not redefined here) now supplies the actual
   flex/typography treatment; this file only keeps the one real
   Performance-specific override (span width) plus the still-shared
   `.performance-tab__kpi-label`, which already matched `.code-history__kpi-
   label` byte-for-byte before this pass. */
.performance-tab__kpis .performance-tab__kpi-tile {
    grid-column: span 6;
}

.performance-tab__kpi-label {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-muted);
}

.performance-tab__chart-body {
    position: relative;
    padding: 14px 18px 8px;
}

/* Hover/tooltip plot wrapper (Ticket #19) — `position: relative` anchor for
   `.code-history__tooltip`'s own `position: absolute`, same pattern
   `.code-history__plot`/`.code-history__contrib-body` already establish. */
.performance-tab__chart-plot {
    position: relative;
}

.performance-tab__chart-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 8px;
}

.performance-tab__chart-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-ink);
}

.performance-tab__chart-source {
    font-size: 11px;
    color: var(--color-text-faint);
}

.performance-tab__chart {
    width: 100%;
    display: block;
    margin-top: 10px;
}

.performance-tab__chart-area {
    fill: var(--color-ink);
    opacity: 0.06;
}

.performance-tab__chart-series {
    fill: none;
    stroke: var(--color-ink);
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.performance-tab__chart-point {
    fill: var(--color-ink);
    opacity: 0.85;
}

.performance-tab__chart-legend {
    display: flex;
    align-items: center;
    gap: 16px;
    font-size: 11px;
    color: var(--color-text-muted);
    margin-top: 6px;
}

/* Chart-interactivity-follow-up plan, Stage 3: the "Selected" readout row
   above the chart — eyebrow ("Selected", matching `.business-tab__chart-
   readout-eyebrow`'s own convention rather than repeating the "Backend
   response time · 24h" title already shown in `.performance-tab__chart-
   head` directly above this row — a deliberate, minor deviation from the
   decoded design fragment's own literal first span, see the template's own
   comment at this row's call site), a flex spacer, then when/p50/p95/gap.
   `-spacer` is reused both here (pushing the readout fields right) and in
   `.performance-tab__chart-legend` below (pushing the "click to inspect"
   caption right) — same shared-class approach `.security-tab__chart-
   readout-spacer` already established for its own two rows. */
.performance-tab__chart-readout {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin-bottom: 6px;
}

.performance-tab__chart-readout-spacer {
    flex: 1;
}

.performance-tab__chart-readout-eyebrow {
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-faint);
}

.performance-tab__chart-readout-when {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-muted);
}

/* p50/p95 value spans — the literal "p50 "/"p95 " label text is static
   markup (not part of the precomputed field), so `chart_readout_
   controller.js`'s blanket `textContent` overwrite on click only replaces
   the inner value span, never wiping the label prefix alongside it. Font
   size/weight/colors ported verbatim from the decoded design fragment's own
   inline styles (p50 = default ink, p95 = this tab's own `#c96a10` accent,
   matching the series/marker colors already used above). */
.performance-tab__chart-readout-p50 {
    font-size: 13px;
    font-weight: 700;
    color: var(--color-ink);
}

.performance-tab__chart-readout-p95 {
    font-size: 13px;
    font-weight: 700;
    color: #c96a10;
}

.performance-tab__chart-readout-gap {
    font-size: 11px;
    font-weight: 600;
}

/* Dashed selection line + two markers (p95/p50) + invisible per-hour
   hit-targets — same shape/values as `.business-tab__chart-readout-line`/
   `-marker`/`-hit`, this chart's own two-series markers reusing the shared
   `chart_readout_controller.js` unchanged (two `marker` targets instead of
   one). Opacity/dash values match the sibling tabs' own established 0.5/
   "3 3" rather than the decoded design fragment's own illustrative 0.45 —
   consistency with the two already-shipped tabs' identical dashed-line
   treatment, not a second, slightly-different flavor of the same thing. */
.performance-tab__chart-readout-line {
    stroke: var(--color-ink);
    stroke-width: 1;
    stroke-dasharray: 3 3;
    opacity: 0.5;
}

.performance-tab__chart-readout-marker--p95 {
    fill: var(--color-surface);
    stroke: #c96a10;
    stroke-width: 2.5;
}

.performance-tab__chart-readout-marker--p50 {
    fill: var(--color-surface);
    stroke: var(--color-ink);
    stroke-width: 2.5;
}

.performance-tab__chart-hit {
    fill: transparent;
    cursor: crosshair;
}

.performance-tab__chart-readout-caption {
    font-size: 11px;
    color: var(--color-text-faint);
}

.performance-tab__empty {
    font-size: 12px;
    color: var(--color-text-faint);
    line-height: 1.5;
}

/* Exceptions/Non-fatal errors/Slow queries card header (Stage 7 regroup):
   split eyebrow+subtitle in place of the old single-line
   `.business-tab__panel-title`, plus a `3px` left-border accent
   (`--exceptions`/`--nonfatal`/`--slowqueries`) — `--color-severity-
   critical`/`--color-severity-high`/`--color-status-acknowledged` are
   exact real hex matches for the design's own red/orange/blue, not new
   colors invented for this. */
.performance-tab__issue-card {
    border-left-width: 3px;
    border-left-style: solid;
}

.performance-tab__issue-card--exceptions {
    border-left-color: var(--color-severity-critical);
}

.performance-tab__issue-card--exceptions .performance-tab__issue-eyebrow {
    color: var(--color-severity-critical);
}

.performance-tab__issue-card--nonfatal {
    border-left-color: var(--color-severity-high);
}

.performance-tab__issue-card--nonfatal .performance-tab__issue-eyebrow {
    color: var(--color-severity-high);
}

.performance-tab__issue-card--slowqueries {
    border-left-color: var(--color-status-acknowledged);
}

.performance-tab__issue-card--slowqueries .performance-tab__issue-eyebrow {
    color: var(--color-status-acknowledged);
}

.performance-tab__issue-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 8px;
}

.performance-tab__issue-eyebrow {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.performance-tab__issue-subtitle {
    margin-top: 3px;
    font-size: 11px;
    color: var(--color-text-faint);
}

.performance-tab__issue-total {
    flex: none;
    font-size: 22px;
    font-weight: 700;
    color: var(--color-ink);
}

.performance-tab__exception-row {
    margin-top: 10px;
}

.performance-tab__exception-row-head {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    font-size: 11px;
}

.performance-tab__exception-name {
    font-family: var(--font-mono);
    color: var(--color-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.performance-tab__exception-count {
    font-weight: 600;
    flex: none;
    color: var(--color-ink);
}

.performance-tab__exception-track {
    height: 5px;
    background: var(--color-border-muted);
    border-radius: 3px;
    margin-top: 3px;
    overflow: hidden;
}

.performance-tab__exception-bar {
    display: block;
    height: 100%;
    background: var(--color-severity-high);
    border-radius: 3px;
}

/*
 * RUMVision half of the Performance tab (web-vitals KPI row + "Web vitals
 * by page type" table) — `.performance-tab__vitals-*`/`.performance-tab
 * __vital-*`/`.performance-tab__pagetype-*` deliberately mirror the
 * `.performance-tab__chart-*` values (same card padding, header layout,
 * muted-source-label treatment) rather than introducing a visually
 * distinct sub-system, even though they're their own scoped class family
 * (see this file's own "Project Detail: Performance tab" comment above for
 * why tab-scoped classes aren't shared across tabs here). The KPI grid
 * uses `repeat(auto-fit, ...)` rather than the design mock's fixed
 * `repeat(4, 1fr)` since a real `App\RumVision\RumVisionApiClient::
 * CORE_METRIC_SLUGS` catalog commonly has 5 entries, not 4 — see
 * `templates/project_detail/_performance.html.twig`'s own docblock for the
 * full reasoning.
 */
.performance-tab__vitals-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 8px;
}

.performance-tab__vitals-title {
    font-size: 13px;
    font-weight: 600;
    color: var(--color-ink);
}

.performance-tab__vitals-source {
    font-size: 11px;
    color: var(--color-text-faint);
}

.performance-tab__vitals-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 12px;
    margin-top: 12px;
}

.performance-tab__vital-card {
    padding: 14px 16px;
}

.performance-tab__vital-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
}

.performance-tab__vital-label {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    letter-spacing: 0.1em;
}

.performance-tab__vital-value {
    font-size: 26px;
    font-weight: 600;
    margin-top: 8px;
    color: var(--color-ink);
}

.performance-tab__vital-unit {
    font-size: 12px;
    color: var(--color-text-faint);
    font-weight: 400;
}

.performance-tab__vital-secondary {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 3px;
}

.performance-tab__pagetype-head {
    padding: 12px 16px;
    border-bottom: 1px solid var(--color-border);
    display: flex;
    justify-content: space-between;
}

.performance-tab__pagetype-columns {
    display: flex;
    gap: 10px;
    padding: 8px 16px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-header-bg);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.performance-tab__pagetype-col--page {
    flex: 1;
}

.performance-tab__pagetype-col--metric {
    width: 64px;
    text-align: right;
}

.performance-tab__pagetype-col--views {
    width: 56px;
    text-align: right;
}

.performance-tab__pagetype-row {
    display: flex;
    gap: 10px;
    padding: 10px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 12px;
    align-items: center;
}

.performance-tab__pagetype-name {
    flex: 1;
    font-weight: 500;
}

.performance-tab__pagetype-metric {
    width: 64px;
    text-align: right;
    font-weight: 600;
}

.performance-tab__pagetype-views {
    width: 56px;
    text-align: right;
    color: var(--color-text-faint);
    font-size: 11px;
}

/*
 * ---- Project Detail: SEO tab ----
 * IMPLEMENTATION_PLAN.md Stage 9 rebrush: restructures the tab into 3
 * numbered, per-source-accent-colored sections via the shared
 * `section_head()` macro (Stage 0) — `01 / CRAWL HYGIENE` (ink), `02 /
 * SEARCH PERFORMANCE · GOOGLE SEARCH CONSOLE` (blue,
 * `--color-status-acknowledged`, `#54718e` — reused rather than a new
 * token, exact hex match), `03 / SHOPPING FEED · GOOGLE MERCHANT CENTER`
 * (gold, `--color-status-snoozed`, `#9a6e14` — same reuse). Pre-Stage-9
 * this template was almost entirely inline-styled with zero `.seo-tab*`
 * CSS at all; this is the first real class-based pass.
 *
 * Real per-signal data independence, not a single tri-state switch per
 * section: section 01's two sub-blocks ("Generic checks" from the
 * `seo.crawl_check` signal, "Sitemap URL count" chart from
 * `sitemap_url_count` metric snapshots) are backed by genuinely different
 * data sources that can independently have/lack data (see
 * `SeoTabTest::test_sitemap_chart_renders_from_real_metric_snapshots`,
 * which populates the chart with zero crawl-check signal at all) — so
 * unlike the decoded design fragment's own single binary "not polled yet"
 * empty state replacing the whole framed panel, `.seo-tab__crawl-panel`
 * always renders and each sub-block independently shows its own empty
 * state or real content, same "never fabricate/hide real data to fit a
 * design's binary" discipline this codebase applies everywhere else.
 * Similarly, "URL Inspection" (`seo.urlInspectionRows`) has no
 * connected/not-connected split of its own (it reuses the GSC connector
 * but never checks `gscConnected`/`gscHasData` itself — see
 * `SeoTabQuery::urlInspectionRows()`'s own docblock) — kept as its own
 * always-rendered sibling block under section 02's header, positioned
 * after the GSC KPI/queries/pages content but not nested inside its
 * connected/no-data/has-data conditional, preserving the exact
 * independence the pre-Stage-9 template already had.
 *
 * **Empty states**: materializes the shared centered eyebrow/title/body/
 * CTA-link shape Stage 8 established for Security
 * (`.security-tab__empty-*`) as this tab's own `.seo-tab__empty-*` classes
 * — same shape/naming pattern, not a 4th parallel invention, still not
 * extracted into a cross-tab shared partial (values match this design's
 * own literals: 15px title vs. Security's 16px, 40px 24px padding vs.
 * Security's 52px 24px, 420px body max-width vs. 430px). Two
 * `--empty--{gsc,gmc}` modifiers set only `border-top`, per section
 * accent — there's no third `--crawl` modifier: the crawl "not polled
 * yet" state nests *inside* `.seo-tab__crawl-panel` (as the Generic-checks
 * sub-block's own content, see above) rather than replacing the whole
 * panel, so its accent-colored border-top already comes from the panel
 * itself. It reuses the `__empty-title`/`__empty-body` text classes
 * without the outer `__empty` box's own border/background (the panel
 * already supplies both) — `.seo-tab__crawl-empty` only sets
 * padding/centering.
 * GSC/GMC not-connected/no-data states authored a new title line each
 * (e.g. "No search performance data for this project") to fit the
 * 4-part shape, exactly the same move Security's own Stage 8 made when it
 * adopted this shape — every eyebrow/body string is otherwise the
 * pre-Stage-9 copy verbatim, only restructured, per this stage's own
 * "grouping/chrome only" mandate.
 *
 * `#eef0ec`/`#3d566f` (Top-queries/Top-pages blue-tinted band) and
 * `#f9e2d6`/`#eccbb8`/`#8e2f1e` (URL Inspection / Merchant "Account
 * issues" red "to fix" band — `#f9e2d6` matches `--color-severity
 * -critical-bg` exactly and `#8e2f1e` is already this codebase's own
 * established pairing for it, see `_code.html.twig`'s README-audit
 * "HYGIENE CHECK" badge; `#eccbb8`/`#eef0ec`/`#3d566f` have no existing
 * token) are kept as literal one-off hexes, same discipline
 * `.section-head__rule`'s `#d9d0ba` and `.security-tab__posture-row`'s
 * `#e6ddc9` already established for single-use design tints.
 */
.seo-tab {
    display: flex;
    flex-direction: column;
    gap: 26px;
    max-width: 1180px;
    padding-bottom: 24px;
}

.seo-tab__section {
    display: block;
}

/* Shared empty/not-connected shape (Stage 9's own materialization of
   Security's Stage 8 pattern) — see this section's own top docblock.
   Chart-interactivity-follow-up plan, Stage 4: the eyebrow/title/body/CTA
   content itself moved into the shared `.empty-state__*` family (see that
   block's own docblock, below `.section-head__caption`) — this class now
   only carries the border/background/padding this tab's own
   not-wrapped-in-`.card` empty box needs, plus the `--gsc`/`--gmc`
   per-section accent modifiers (kept as-is — see the macro's own
   docblock for why these stayed CSS modifier classes rather than becoming
   a macro color param) and a `15px`/`420px`/`6px` title/body override the
   shared defaults don't match (this design's own literal values). */
.seo-tab__empty {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    padding: 40px 24px;
    text-align: center;
}

.seo-tab__empty--gsc {
    border-top: 3px solid var(--color-status-acknowledged);
}

.seo-tab__empty--gmc {
    border-top: 3px solid var(--color-status-snoozed);
}

/* Nested variant of the crawl "not polled yet" state — content-only
   (padding/centering), no border/background of its own since it lives
   inside the always-rendered `.seo-tab__crawl-panel` (see top docblock).
   Shares the same `15px`/`420px`/`6px` title/body override as
   `.seo-tab__empty` above (same design, just no border/background of its
   own to carry) — see the override rule below. */
.seo-tab__crawl-empty {
    padding: 40px 24px;
    text-align: center;
}

.seo-tab__empty .empty-state__title,
.seo-tab__crawl-empty .empty-state__title {
    font-size: 15px;
}

.seo-tab__empty .empty-state__body,
.seo-tab__crawl-empty .empty-state__body {
    margin-top: 6px;
    max-width: 420px;
}

/* Section 01 framed panel — joins "Generic checks" + "Sitemap URL count"
   into one bordered box (pre-Stage-9: two separately-grouped cards, see
   top docblock). */
.seo-tab__crawl-panel {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-top: 3px solid var(--color-ink);
    border-radius: var(--radius-panel);
    overflow: hidden;
}

.seo-tab__crawl-block + .seo-tab__crawl-block {
    border-top: 1px solid var(--color-border);
}

/* Mono/uppercase kicker, reused for every plain tan-band header on this
   tab (Generic checks / Sitemap URL count / Merchant "Top product
   issues") — same shape `.security-tab__panel-head-title` established,
   paired with the existing shared `.dashboard__panel-head`/`--band`
   container rather than a duplicate container class (same reuse
   Security's own WAF-rules/origin headers already established). No
   sibling caption class: unlike Security's own banded headers, none of
   these three carry a right-aligned caption (this stage relocated/
   dropped the pre-Stage-9 captions that used to sit here — see this
   file's own template docblock). */
.seo-tab__block-title {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-secondary);
}

/* Ticket #22 — "Sitemap URL count" chart visual refresh, brought up to the
   Code tab's "01 / CODE HISTORY" quality bar (`.code-history__plot`/
   `.code-history__svg`/`.code-history__axis-label`/`.code-history__tooltip`
   — see those classes' own rules). Tab-scoped names rather than reusing
   those `code-history__*` classes directly: every other chart on this
   screen (`.security-tab__chart-*`, `.performance-tab__chart-*`) already
   follows "same visual language, own tab-prefixed class names" rather than
   cross-tab class reuse, and this chart follows that same convention — only
   the underlying tokens (`var(--color-text-faint)`, `var(--font-mono)`,
   etc.) are actually shared, same as those other two tabs' own inline-style
   hex equivalents. This one differs from Performance/Security in reaching
   for Code History's CSS-custom-property axis/tooltip styling specifically
   (rather than Performance/Security's own inline-hex-per-element approach)
   because the ticket explicitly named Code History as the pattern to port.
   `#c96a10` (the series/current-point/legend-swatch color) is this app's
   existing "sitemap/p95 attention" accent, already used literally by
   `SeoSitemapChartBuilder`'s pre-#22 inline styles and by
   `PerformanceChartBuilder::GAP_ELEVATED_COLOR`'s own docblock — reused
   here unchanged, not re-picked. */
.seo-tab__sitemap-plot {
    position: relative;
    padding: 4px 16px 12px;
}

.seo-tab__sitemap-svg {
    display: block;
    width: 100%;
}

.seo-tab__sitemap-gridline {
    stroke: var(--color-border-muted);
    stroke-width: 1;
}

.seo-tab__sitemap-axis-label {
    font-family: var(--font-mono);
    font-size: 10px;
    fill: var(--color-text-faint);
}

.seo-tab__sitemap-area {
    fill: #c96a10;
    opacity: 0.08;
}

.seo-tab__sitemap-series {
    fill: none;
    stroke: #c96a10;
    stroke-width: 2.2;
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    animation: tmDraw 1.6s ease forwards;
}

/* Always-visible accent on the latest plotted value — same "highlight the
   current/most-recent point" treatment `code_history_controller.js` draws
   client-side for its own primary series, done server-side here since this
   chart has no client-side redraw to keep it in sync with. */
.seo-tab__sitemap-current {
    fill: #c96a10;
}

.seo-tab__sitemap-hover-line {
    stroke: var(--color-ink);
    stroke-width: 1;
    stroke-dasharray: 3 3;
    opacity: 0.5;
}

.seo-tab__sitemap-hover-marker {
    fill: var(--color-surface);
    stroke: #c96a10;
    stroke-width: 2.5;
}

.seo-tab__sitemap-tooltip {
    position: absolute;
    z-index: 2;
    max-width: 220px;
    padding: 9px 11px;
    background: var(--color-ink);
    border-radius: var(--radius-pill);
    box-shadow: 0 4px 14px rgb(33 26 14 / 18%);
    font-size: 11px;
    line-height: 1.5;
    color: #e8dfcb;
    pointer-events: none;
}

.seo-tab__sitemap-tooltip[hidden] {
    display: none;
}

.seo-tab__sitemap-legend {
    display: flex;
    gap: 14px;
    font-size: 11px;
    color: var(--color-text-muted);
    padding: 0 16px 14px;
}

.seo-tab__sitemap-legend-label {
    display: flex;
    align-items: center;
    gap: 5px;
}

.seo-tab__sitemap-legend-swatch {
    width: 14px;
    height: 2px;
    background: #c96a10;
}

/* Section 02 — 4-tile hairline KPI grid, `background:#e3dac6`
   (`--color-border`, the *stronger* border token — the design's own
   literal choice here, not `--color-border-muted` like Security's own KPI
   grid) doubling as the 1px gridline between tiles. */
.seo-tab__gsc-kpis {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 1px;
    background: var(--color-border);
    border: 1px solid var(--color-border);
    border-top: 3px solid var(--color-status-acknowledged);
    border-radius: var(--radius-panel) var(--radius-panel) 0 0;
    overflow: hidden;
}

.seo-tab__gsc-kpi-tile {
    background: var(--color-surface);
    padding: 14px 16px 16px;
}

.seo-tab__gsc-kpi-value {
    font-size: 17px;
    font-weight: 600;
}

.seo-tab__gsc-kpi-label {
    font-size: 11px;
    color: var(--color-text-muted);
}

.seo-tab__gsc-kpi-sub {
    margin-top: 1px;
    font-size: 10px;
    color: var(--color-text-faint);
}

/* Chains directly beneath `.seo-tab__gsc-kpis` — no top border/radius of
   its own, same "chain under the block above" technique
   `.security-tab__panel` established in Stage 8. */
.seo-tab__gsc-panel {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-top: none;
    border-radius: 0 0 var(--radius-panel) var(--radius-panel);
    overflow: hidden;
}

.seo-tab__gsc-panel-body {
    display: grid;
    grid-template-columns: 1fr 1fr;
}

.seo-tab__gsc-col--queries {
    border-right: 2px solid var(--color-border);
}

/* Top-queries/Top-pages column header — this design's own blue tint,
   distinct from the plain tan `.dashboard__panel-head--band` every other
   banded header on this tab uses (see top docblock re: `#eef0ec`/
   `#3d566f`). */
.seo-tab__gsc-col-head {
    display: flex;
    gap: 10px;
    padding: 8px 16px;
    border-bottom: 1px solid var(--color-border);
    background: #eef0ec;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: #3d566f;
}

/* URL Inspection (section 02) / Merchant "Account issues" (section 03)
   "to fix" red-tinted banner header — see top docblock re: literal hexes.
   Reuses `.dashboard__panel-head` for the container's own padding/flex
   (this modifier only overrides background/border-color), same pairing
   technique Security's own banded headers already established. */
.seo-tab__tofix {
    background: var(--color-surface);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-panel);
    overflow: hidden;
}

.seo-tab__tofix-head {
    background: #f9e2d6;
    border-bottom: 1px solid #eccbb8;
}

.seo-tab__tofix-title {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: #8e2f1e;
}

.seo-tab__tofix-caption {
    font-size: 11px;
    font-weight: 400;
    color: #8e2f1e;
}

/* Section 03 — 2-column bordered box (300px stat sidebar + issue lists),
   replacing the pre-Stage-9 single stacked card. */
.seo-tab__gmc-box {
    display: grid;
    grid-template-columns: 300px 1fr;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-top: 3px solid var(--color-status-snoozed);
    border-radius: var(--radius-panel);
    overflow: hidden;
}

.seo-tab__gmc-sidebar {
    border-right: 1px solid var(--color-border);
    display: flex;
    flex-direction: column;
}

.seo-tab__gmc-stat {
    padding: 14px 16px 16px;
}

.seo-tab__gmc-stat + .seo-tab__gmc-stat {
    border-top: 1px solid var(--color-border);
}

.seo-tab__gmc-stat-value {
    font-size: 17px;
    font-weight: 600;
    color: var(--color-ink);
}

.seo-tab__gmc-stat-label {
    font-size: 11px;
    color: var(--color-text-muted);
}

/* One account/product issue row — `border-left` is `transparent` here and
   colored per-row inline from `i.color` (the same severity color already
   driving that row's status pill, see `SeoMerchantIssueRow`'s own
   docblock), the design's own "3px colored left-flag bar" rather than a
   new dedicated `$flag` field. */
.seo-tab__gmc-issue-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 16px 8px 13px;
    border-bottom: 1px solid var(--color-border-muted);
    border-left: 3px solid transparent;
    font-size: 12px;
}

/* ---- Project Detail: Configure tab ---- */

/*
 * Design-fidelity Stage 11 (final rebrush stage): container width cap the
 * design fragment specifies for this whole tab — every card below reads
 * comfortably at this width without the 6-column composer-auth-credentials
 * table or the connector fieldset's 320px side guide panel forcing
 * horizontal scroll on typical viewports.
 */
.configure__panel {
    max-width: 1080px;
}

.configure__card {
    padding: 0;
    margin: 0 0 16px;
}

/*
 * "The Forms should be collapsable, so i can find my configs faster" —
 * `.configure__card` is a native `<details>`, same plain "no JS needed"
 * pattern already established for the Dependencies tab's
 * local-path-packages disclosure (see `.deps-tab__table-head--summary`).
 * Collapsed by default as of the follow-up "Can you make the entirety of
 * the configs collapsable?" (`templates/project_configure/index.html.twig`'s
 * own docblock covers the per-card "stay open on a real validation error"
 * exception).
 *
 * Design-fidelity Stage 11: reworked from a flat title+caret into the
 * design's richer numbered header (mono badge, live one-line summary, a
 * bordered Expand/Collapse pill) — per Decision 6 this stays a real
 * `<summary>`/`<details>` pair, only re-skinned. `.configure__card-body`
 * now carries the padding `.configure__card` itself used to (the `<details>`
 * needs `padding:0` so the header/footer bands below can bleed edge-to-edge
 * inside the card's own `overflow:hidden` — same trick
 * `.tickets-tab__priority-card` already established).
 */
.configure__card-body {
    padding: 18px 20px 20px;
}

summary.configure__card-title {
    cursor: pointer;
    list-style: none;
}

/* Suppresses the default disclosure marker in engines that render
   `<summary>` as a list item — the whole `.configure__section-head` band
   is the click target now, not a small text-adjacent caret. */
summary.configure__card-title::-webkit-details-marker {
    display: none;
}

.configure__section-head {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 15px 18px;
    background: var(--color-sidebar-bg);
}

.configure__section-number {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.1em;
    color: #8c8271;
    width: 20px;
    flex: none;
}

.configure__section-text {
    flex: 1;
    min-width: 0;
}

.configure__section-title {
    font-size: 15px;
    font-weight: 700;
    color: var(--color-ink);
}

.configure__section-summary {
    font-size: 12px;
    color: #6f6757;
    margin-top: 3px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.configure__section-chips {
    display: flex;
    gap: 5px;
    flex-wrap: wrap;
    justify-content: flex-end;
    max-width: 320px;
    flex: none;
}

.configure__section-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 10.5px;
    font-weight: 600;
    border-radius: 4px;
    padding: 3px 8px;
    white-space: nowrap;
}

.configure__section-chip-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentColor;
    flex: none;
}

.configure__section-action {
    display: flex;
    align-items: center;
    gap: 7px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-panel);
    padding: 6px 11px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: #46402f;
    background: var(--color-surface);
    flex: none;
}

/* Pure-CSS Expand/Collapse label + caret flip — `<details>`'s own native
   `[open]` state drives this, no JS needed for the visual side of a
   feature that's already JS-free (the accessible `<details>` disclosure
   itself). */
.configure__section-action-open {
    display: none;
}

.configure__card[open] .configure__section-action-open {
    display: inline;
}

.configure__card[open] .configure__section-action-closed {
    display: none;
}

.configure__footer {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 18px;
    background: var(--color-sidebar-bg);
    border-top: 1px solid var(--color-border);
    margin: 18px -20px -20px;
}

.configure__footer-note {
    font-size: 11px;
    color: #8c8271;
    flex: 1;
}

/*
 * Pill-switch checkbox restyle ("Make 'Add staging environment' and every
 * connector's enable/disable a custom pill-switch graphic instead of a
 * plain checkbox (same underlying behavior, styled control)") — scoped to
 * `.configure__panel` only, deliberately not a `project_wizard/
 * _form_theme.html.twig` block override: the creation wizard's own
 * checkboxes ("Also add a staging environment", "Enable Tideways", ...)
 * share that exact same `checkbox_row` block/markup and are out of scope
 * for this rebrush pass. A real `<input type="checkbox">` underneath
 * (`appearance: none` only changes paint, not semantics/keyboard behavior)
 * — same "style the native control" approach `<details>`/`<summary>` keeps
 * per Decision 6, not a fake JS-driven toggle.
 */
.configure__panel .wizard__checkbox-label input[type='checkbox'] {
    appearance: none;
    width: 32px;
    height: 18px;
    border-radius: 9px;
    background: var(--color-border-strong);
    position: relative;
    cursor: pointer;
    margin: 0;
    flex: none;
    transition: background 0.15s ease;
}

.configure__panel .wizard__checkbox-label input[type='checkbox']::after {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: #fffcf5;
    transition: left 0.15s ease;
}

.configure__panel .wizard__checkbox-label input[type='checkbox']:checked {
    background: var(--color-status-resolved);
}

.configure__panel .wizard__checkbox-label input[type='checkbox']:checked::after {
    left: 16px;
}

/* A connector fieldset's enable switch sits inline in its header band
   (`.configure__connector-head`), not stacked in its own field row. */
.configure__connector-head .wizard__field--checkbox {
    margin: 0;
    flex: none;
}

.configure__actions {
    justify-content: flex-end;
}

/* Success flash — green circular checkmark + "Dismiss" link (design
   fragment), replacing the earlier plain colored-text-only banner. */
.configure__flash {
    display: flex;
    align-items: center;
    gap: 10px;
    background: var(--color-status-resolved-bg);
    border: 1px solid #cdd8b6;
    color: var(--color-status-resolved);
    padding: 10px 14px;
    border-radius: var(--radius-panel);
    font-size: 13px;
    margin: 0 0 16px;
}

.configure__flash-check {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--color-status-resolved);
    color: #fffcf5;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 700;
    flex: none;
}

.configure__flash-text {
    flex: 1;
    font-weight: 600;
}

.configure__flash-dismiss {
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-status-resolved);
    cursor: pointer;
}

/*
 * Token-reveal banner — the bold dark/gold "copy it now, it's never shown
 * again" alert treatment (design fragment), replacing the earlier plain
 * `.card`-styled box. Literal hex throughout (same discipline
 * `.section-head__rule`'s `#d9d0ba` already established) — this is a
 * deliberately different, one-off alert palette with no matching existing
 * token.
 */
.configure__token-reveal {
    background: #211a0e;
    border-radius: var(--radius-panel);
    overflow: hidden;
    box-shadow: 0 0 0 3px #e0a920;
    margin: 0 0 16px;
}

.configure__token-reveal-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 18px;
    background: #e0a920;
}

.configure__token-reveal-icon {
    font-size: 13px;
    font-weight: 800;
    color: #211a0e;
}

.configure__token-reveal-headline {
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: #211a0e;
}

.configure__token-reveal-body {
    padding: 16px 18px 18px;
}

.configure__token-reveal-note {
    font-size: 13px;
    color: #d9d0ba;
}

.configure__token-reveal-note b {
    color: #f1ecdf;
}

.configure__token-reveal-row {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 12px;
}

.configure__token-reveal-value {
    flex: 1;
    font-family: var(--font-mono);
    font-size: 15px;
    font-weight: 600;
    color: #f1ecdf;
    background: #3a3122;
    border: 1px solid #5a5142;
    border-radius: var(--radius-panel);
    padding: 10px 12px;
    letter-spacing: 0.02em;
    user-select: all;
    word-break: break-all;
}

.configure__token-reveal-copy {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    padding: 0 16px;
    height: 42px;
    border-radius: var(--radius-panel);
    border: none;
    cursor: pointer;
    background: #e0a920;
    color: #211a0e;
    white-space: nowrap;
}

.configure__token-reveal-dismiss {
    display: inline-block;
    margin-top: 12px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: #a89d86;
    cursor: pointer;
}

.configure__empty {
    color: var(--color-text-faint);
    font-size: 13px;
    margin: 0 0 14px;
}

.configure__token-list {
    margin: 0 0 20px;
}

.configure__token-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 0;
    border-bottom: 1px solid var(--color-border-muted);
}

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

.configure__token-label {
    font-weight: 500;
    flex: 1;
}

.configure__token-detail {
    font-size: 12px;
    color: var(--color-text-muted);
}

.configure__token-revoke-form {
    margin: 0;
}

.configure__token-create {
    border-top: 1px solid var(--color-border-muted);
    padding-top: 16px;
}

/* ---- Project Detail Configure tab: Environments side-by-side grid ---- */

.configure__env-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
    align-items: start;
}

/* ---- Project Detail Configure tab: Contract 3-column grid ---- */

.configure__contract-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 240px));
    gap: 16px;
}

.configure__contract-grid .wizard__field {
    margin: 0;
}

/* ---- Project Detail Configure tab: Connectors ---- */

.configure__connector {
    border-radius: var(--radius-panel);
    background: var(--color-surface);
    overflow: hidden;
}

.configure__connector-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 14px;
    border-bottom: 1px solid var(--color-border);
}

.configure__connector-name {
    font-size: 14px;
    font-weight: 700;
}

.configure__connector-spacer {
    flex: 1;
}

.configure__connector-body {
    padding: 14px;
}

/* Only the 4 connectors with real "where to get this" guidance
   (Cloudflare/RUMvision/GSC/Merchant) split into this two-column layout —
   Tideways/Toggl/Prometheus keep a single-column `.configure__connector-body`. */
.configure__connector-body--with-guide {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: 0;
    padding: 0;
}

.configure__connector-fields {
    padding: 14px;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
    align-content: start;
}

.configure__connector-fields .wizard__field {
    margin: 0;
}

.configure__connector-guide {
    border-left: 1px solid var(--color-border);
    background: #f3f1ea;
    padding: 13px 14px;
}

.configure__connector-guide-head {
    display: flex;
    align-items: center;
    gap: 7px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: #3d566f;
}

.configure__connector-guide-icon {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: #54718e;
    color: #fffcf5;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    flex: none;
}

.configure__connector-guide-steps {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 10px;
}

.configure__connector-guide-step {
    display: flex;
    gap: 9px;
}

.configure__connector-guide-step-number {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 700;
    color: #54718e;
    flex: none;
    margin-top: 2px;
}

.configure__connector-guide-step-text {
    font-size: 12px;
    line-height: 1.5;
    color: #3f382a;
}

/* ---- Project Detail Configure tab: Composer auth credentials grid table ---- */

.configure__composer-table {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    overflow: hidden;
}

.configure__composer-head,
.configure__composer-row {
    display: grid;
    grid-template-columns: 1.4fr 130px 1fr 1.2fr 1fr 78px;
    gap: 10px;
    align-items: center;
}

.configure__composer-head {
    padding: 8px 12px;
    background: var(--color-sidebar-bg);
    border-bottom: 1px solid var(--color-border);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: #6f6757;
}

.configure__composer-row {
    padding: 10px 12px;
    border-bottom: 1px solid var(--color-border-muted);
}

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

.configure__composer-cell {
    min-width: 0;
}

.configure__composer-cell .wizard__input {
    width: 100%;
}

.configure__composer-cell--remove {
    text-align: right;
}

.configure__composer-remove {
    font-size: 12px;
    font-weight: 600;
    color: var(--color-severity-critical);
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
}

.configure__composer-unused {
    font-size: 12px;
    color: var(--color-text-faint);
}

.configure__composer-entered {
    margin-top: 6px;
}

.configure__composer-add-row {
    padding: 10px 12px;
    background: var(--color-surface);
}

/* ---- Project Detail Configure tab: Danger zone ----
 *
 * Real bug, not just a fidelity gap: this used to set
 * `border-color: var(--color-severity-critical-bg)` — that token is the
 * *light pink tint* (`#f9e2d6`), not a red border, so the "danger" card
 * rendered with a barely-visible pink hairline instead of a bold red one.
 * Fixed to the real `--color-severity-critical` (`#d84a31`) at 2px, plus
 * an `#fcf1ea` background tint on the whole card (previously no tint at
 * all) — same literal `#fcf1ea` hex `.tickets-tab__priority-card` already
 * established (lighter/more saturated than `--color-severity-critical-bg`,
 * a deliberately different, more alarming tint than this card's regular
 * pill color). Always-visible red, not conditional on the `<details>`
 * being open — this is the one genuinely destructive action on the tab,
 * it should read as dangerous even collapsed.
 */
.configure__card--danger {
    border: 2px solid var(--color-severity-critical);
    background: #fcf1ea;
}

.configure__card--danger .configure__section-head {
    background: transparent;
}

.configure__card--danger .configure__section-title,
.configure__card--danger .configure__section-summary {
    color: #8e2f1e;
}

.configure__card--danger .configure__section-action {
    color: #8e2f1e;
    border-color: #e3b3a0;
    background: var(--color-surface);
}

.configure__danger-icon {
    width: 22px;
    height: 22px;
    border-radius: var(--radius-panel);
    background: var(--color-severity-critical);
    color: #fffcf5;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 13px;
    font-weight: 800;
    flex: none;
}

.configure__danger-warning {
    color: #8e2f1e;
    font-size: 13px;
    margin: 0 0 14px;
}

/* ---- Delete Project Modal ----
   Same fixed-overlay/backdrop recipe as `.customer-modal`/`.staff-modal`/
   `.suggest-rule-modal` (this app's established per-modal duplicated-rule
   idiom, not a shared class — see those rules' own comments), reusing
   `.customer-modal__panel`'s chrome directly since no feature-specific
   sizing/label override is needed here. */

.delete-project-modal {
    position: fixed;
    inset: 0;
    z-index: 51;
    background: rgba(33, 26, 14, 0.28);
    display: flex;
    align-items: center;
    justify-content: center;
}

.delete-project-modal[hidden] {
    display: none;
}

.delete-project-modal__warning {
    font-size: 13px;
    margin: 0 0 14px;
}

/*
 * Design-fidelity Stage 11: live match/no-match feedback text below the
 * confirm-name input — previously JS-only submit-disable with no visible
 * inline text at all. `assets/controllers/delete_project_modal_controller.js`
 * toggles `--ok` once `inputTarget.value` exactly matches `nameValue`; the
 * base (unmatched/empty) state stays a neutral, muted color deliberately,
 * not an alarming red — a blank/in-progress input isn't an *error*, just
 * "not confirmed yet."
 */
.delete-project-modal__match {
    margin-top: 7px;
    font-size: 11px;
    color: var(--color-text-faint);
}

.delete-project-modal__match--ok {
    color: var(--color-status-resolved);
    font-weight: 600;
}

/* ---- Customer Modal (Stage 2) ---- */

.customer-modal {
    position: fixed;
    inset: 0;
    z-index: 51;
    background: rgba(33, 26, 14, 0.28);
    display: flex;
    align-items: center;
    justify-content: center;
}

.customer-modal[hidden] {
    display: none;
}

.customer-modal__panel {
    width: 460px;
    max-width: 94vw;
    max-height: 88vh;
    overflow-y: auto;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.25);
    border-radius: var(--radius-panel);
    padding: 18px 20px;
}

/*
 * GitLab Members modal (Info tab QA follow-up) — same backdrop idiom as
 * `.delete-project-modal`/`.customer-modal` (see those rules' own
 * comments), reusing `.customer-modal__panel`'s chrome directly but wider:
 * member rows carry a role `<select>` + "Remove" button that need more
 * horizontal room than the default 460px panel comfortably gives.
 *
 * Deliberately declared *after* `.customer-modal__panel` above: both
 * selectors are single-class (equal specificity), so this override must
 * come later in source order to actually win the cascade — it previously
 * sat earlier in the file (before `.customer-modal__panel` existed at this
 * line), which silently lost the `width` override back down to 460px
 * despite reading `720px` right here (live-confirmed: computed width was
 * 460px, cramming the role `<select>` + "Remove" button this comment
 * itself says needs more room).
 */
.gitlab-members-modal {
    position: fixed;
    inset: 0;
    z-index: 51;
    background: rgba(33, 26, 14, 0.28);
    display: flex;
    align-items: center;
    justify-content: center;
}

.gitlab-members-modal[hidden] {
    display: none;
}

.gitlab-members-modal__panel {
    width: 720px;
    padding: 0;
}

.gitlab-members-modal__panel .customer-modal__header {
    padding: 18px 20px;
    margin-bottom: 0;
    border-bottom: 1px solid var(--color-border);
}

.customer-modal__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-weight: 600;
    margin-bottom: 14px;
}

.customer-modal__close {
    cursor: pointer;
    color: var(--color-text-faint);
    font-size: 16px;
    padding: 2px 6px;
    border-radius: 5px;
    background: none;
    border: none;
}

.customer-modal__close:hover {
    background: var(--color-status-accepted-risk-bg);
    color: var(--color-ink);
}

.customer-modal__actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    border-top: 1px solid var(--color-border);
    padding-top: 14px;
    margin-top: 6px;
}

/* ---- Customer Detail (Stage 2) ---- */

.customer-detail__top {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: 16px;
}

.customer-detail__back {
    font-size: 12px;
    color: var(--color-text-muted);
    text-decoration: none;
}

.customer-detail__back:hover {
    color: var(--color-ink);
}

.customer-detail__top-spacer {
    flex: 1;
}

.customer-detail__header {
    display: flex;
    align-items: center;
    gap: 14px;
}

.customer-detail__avatar {
    width: 52px;
    height: 52px;
    flex: none;
    border-radius: 50%;
    background: var(--color-border-muted);
    color: var(--color-text-secondary);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    font-weight: 700;
    object-fit: contain;
}

.customer-detail__header-info {
    flex: 1;
    min-width: 0;
}

.customer-detail__logo-form {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 6px;
}

.customer-detail__logo-input {
    font-size: 11px;
    max-width: 200px;
}

.customer-detail__logo-input::file-selector-button {
    font-size: 11px;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    border-radius: 5px;
    padding: 0 10px;
    height: 24px;
    cursor: pointer;
    color: var(--color-text-secondary);
    margin-right: 8px;
}

.customer-detail__logo-input::file-selector-button:hover {
    border-color: #b3a98f;
    color: var(--color-ink);
}

.customers__avatar--logo,
.customer-detail__avatar--logo {
    object-fit: cover;
}

.customer-detail__logo-submit {
    font-size: 11px;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    border-radius: 5px;
    padding: 0 10px;
    height: 24px;
    cursor: pointer;
    color: var(--color-text-muted);
}

.customer-detail__logo-submit:hover {
    border-color: #b3a98f;
    color: var(--color-ink);
}

.customer-detail__name {
    font-size: 20px;
    font-weight: 600;
    letter-spacing: -0.01em;
}

.customer-detail__contact-line {
    font-size: 12px;
    color: var(--color-text-faint);
    margin-top: 2px;
    display: flex;
    align-items: center;
    gap: 5px;
}

.customer-detail__email-copy {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}

.customer-detail__copy {
    font-size: 10px;
    border: 1px solid var(--color-border-strong);
    background: var(--color-surface);
    border-radius: var(--radius-panel);
    padding: 1px 6px;
    cursor: pointer;
    color: var(--color-text-muted);
}

.customer-detail__copy:hover {
    border-color: #b3a98f;
    color: var(--color-ink);
}

.customer-detail__stats {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 12px;
    margin-top: 16px;
}

.customer-detail__stat {
    padding: 12px 16px;
}

.customer-detail__stat-label {
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: 500;
    color: var(--color-text-faint);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.customer-detail__stat-value {
    font-size: 13px;
    font-weight: 600;
    margin-top: 6px;
}

.customer-detail__stat-value--muted {
    font-weight: 400;
    color: var(--color-text-faint);
}

.customer-detail__contract-link {
    display: inline-block;
    font-size: 11px;
    color: var(--color-ink);
    font-weight: 500;
    margin-top: 6px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
}

.customer-detail__projects {
    display: flex;
    gap: 8px;
    margin-top: 12px;
    flex-wrap: wrap;
}

.customer-detail__project-chip {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: 12px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    padding: 5px 10px;
    background: var(--color-surface);
    color: inherit;
    text-decoration: none;
}

.customer-detail__project-chip:hover {
    border-color: var(--color-ink);
}

.customer-detail__project-chip-name {
    font-weight: 500;
}

.customer-detail__project-chip-open {
    color: var(--color-text-faint);
    font-size: 11px;
}

.customer-detail__no-projects {
    font-size: 12px;
    color: var(--color-text-faint);
}

.customer-detail__panels {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    margin-top: 16px;
    align-items: start;
}

.customer-detail__panel {
    padding: 0;
}

.customer-detail__panel-header {
    padding: 12px 16px;
    border-bottom: 1px solid var(--color-border);
    font-size: 13px;
    font-weight: 600;
}

.customer-detail__history-form {
    padding: 12px 16px;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-header-bg);
}

.customer-detail__history-form-actions {
    display: flex;
    justify-content: flex-end;
}

.customer-detail__history-row {
    display: flex;
    gap: 12px;
    padding: 10px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 12px;
}

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

.customer-detail__history-date {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-faint);
    width: 56px;
    flex: none;
}

.customer-detail__history-kind {
    font-size: 10px;
    font-weight: 600;
    border-radius: 4px;
    padding: 1px 7px;
    flex: none;
    height: fit-content;
    text-transform: uppercase;
}

.customer-detail__history-kind--note {
    color: var(--color-text-muted);
    background: var(--color-status-accepted-risk-bg);
}

.customer-detail__history-kind--project {
    color: var(--color-status-acknowledged);
    background: var(--color-status-acknowledged-bg);
}

.customer-detail__history-kind--incident {
    color: var(--color-severity-high);
    background: var(--color-severity-high-bg);
}

.customer-detail__history-kind--contract {
    color: var(--color-status-resolved);
    background: var(--color-status-resolved-bg);
}

.customer-detail__history-text {
    flex: 1;
    line-height: 1.5;
    color: var(--color-text-secondary);
}

.customer-detail__contact-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 16px;
    border-bottom: 1px solid var(--color-border-muted);
}

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

.customer-detail__contact-avatar {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: var(--color-border-muted);
    color: var(--color-text-secondary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 700;
    flex: none;
}

.customer-detail__contact-info {
    flex: 1;
    min-width: 0;
}

.customer-detail__contact-name {
    font-size: 12.5px;
    font-weight: 600;
}

.customer-detail__contact-role {
    font-weight: 400;
    color: var(--color-text-muted);
}

.customer-detail__contact-note {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 1px;
}

.customer-detail__contact-email {
    font-size: 11px;
    color: var(--color-text-muted);
    flex: none;
}

/* ---- Staff Registry & Detail (Stage 3) ---- */

.staff__intro {
    font-size: 12px;
    color: var(--color-text-muted);
    line-height: 1.6;
    background: var(--color-header-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    padding: 10px 14px;
    margin-bottom: 14px;
}

.staff__head-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 0 12px;
}

.staff__count {
    font-size: 12px;
    color: var(--color-text-faint);
}

.staff__row {
    margin-bottom: 10px;
    padding: 14px 16px;
}

.staff__row-top {
    display: flex;
    align-items: center;
    gap: 12px;
}

/* `object-fit: cover` (tickets #9/#11, real GitLab avatar images) only ever
   applies to the real `<img>` variant this class now also renders
   (`templates/layout/_avatar.html.twig`) — a no-op on the plain initials
   `<span>` this rule already styled, since `object-fit` only affects
   replaced elements. Keeps a non-square real GitLab avatar cropped to fill
   this circle rather than squashed/letterboxed. */
.staff__avatar {
    width: 34px;
    height: 34px;
    flex: none;
    border-radius: 50%;
    background: var(--color-border-muted);
    color: var(--color-text-secondary);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    font-weight: 700;
    object-fit: cover;
}

.staff__row-identity {
    width: 220px;
    flex: none;
    color: inherit;
    text-decoration: none;
}

.staff__row-identity:hover .staff__row-name {
    text-decoration: underline;
}

.staff__row-name {
    display: block;
    font-size: 14px;
    font-weight: 600;
}

.staff__row-role {
    display: block;
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 1px;
}

.staff__row-workload {
    flex: 1;
    min-width: 0;
}

.staff__row-workload-labels {
    display: flex;
    justify-content: space-between;
    font-size: 11px;
    color: var(--color-text-muted);
    margin-bottom: 4px;
}

.staff__workload-track {
    height: 8px;
    background: var(--color-border-muted);
    border-radius: 4px;
    overflow: hidden;
}

.staff__workload-bar {
    display: block;
    height: 100%;
    background: var(--color-status-resolved);
    border-radius: 4px;
    transition: width 0.5s ease-out;
}

.staff__row-panels {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--color-border-muted);
}

.staff__row-panel-header {
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: 500;
    color: var(--color-text-faint);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    margin-bottom: 6px;
}

.staff__row-panel-header--split {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
}

.staff__row-panel-header-note {
    font-family: var(--font-body);
    font-size: 11px;
    font-weight: 400;
    color: var(--color-text-faint);
    text-transform: none;
    letter-spacing: normal;
}

.staff__delete-form {
    display: inline-flex;
}

.staff__no-tickets {
    font-size: 12px;
    color: var(--color-status-acknowledged);
}

/*
 * "the ability to see assigned tickets" follow-up: real GitLab-assignee
 * data (App\Entity\StaffAssignedTicket) replacing the always-empty state
 * above. Mirrors the original design prototype's own per-row shape for
 * this exact panel (id / title / project name), translated into this
 * file's established .staff__* classes rather than the prototype's raw
 * inline styles.
 */
.staff__ticket-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 0;
    font-size: 12px;
    color: inherit;
    text-decoration: none;
}

.staff__ticket-row:hover {
    opacity: 0.7;
}

.staff__ticket-row-iid {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-muted);
    flex: none;
}

.staff__ticket-row-title {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.staff__ticket-row-project {
    font-size: 11px;
    color: var(--color-text-faint);
    flex: none;
}

.staff__ticket-row-more {
    font-size: 12px;
    color: var(--color-text-faint);
    font-style: italic;
    padding: 4px 0;
}

/* ---- Staff Registry: ranked, windowed activity (Task 8,
   docs/plans/2026-10-18-staff-activity-implementation-plan.md). No design
   prototype exists for this addition (docs/design/Refinery.dc.html's own
   `isStaff` block predates it) — a documented deviation, see
   templates/staff/index.html.twig's own comment. New classes here extend
   this page's own established tokens/spacing rather than inventing a new
   visual language: `.staff__window-picker-option.is-active` mirrors
   `.project-detail__tab.is-active`'s own "plain link row, active one picked
   out via --color-ink + weight" treatment. ---- */

.staff__window-picker {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    font-size: 12px;
    color: var(--color-text-faint);
    padding: 0 0 12px;
}

.staff__window-picker-label {
    font-family: var(--font-mono);
    font-size: 9px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.staff__window-picker-option {
    color: var(--color-text-muted);
    text-decoration: none;
    padding: 3px 8px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-pill);
}

.staff__window-picker-option.is-active {
    color: var(--color-ink);
    font-weight: 600;
    background: var(--color-header-bg);
    border-color: var(--color-border-strong);
}

.staff__activity-score {
    font-weight: 600;
    color: var(--color-ink);
}

.staff__activity-summary {
    font-size: 12px;
    color: var(--color-text-secondary);
}

.staff__activity-line-stats {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 2px;
}

.staff__toggl-hours {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 4px;
}

.staff-modal {
    position: fixed;
    inset: 0;
    z-index: 51;
    background: rgba(33, 26, 14, 0.28);
    display: flex;
    align-items: center;
    justify-content: center;
}

.staff-modal[hidden] {
    display: none;
}

.staff-detail__top {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: 16px;
}

.staff-detail__back {
    font-size: 12px;
    color: var(--color-text-muted);
    text-decoration: none;
}

.staff-detail__back:hover {
    color: var(--color-ink);
}

.staff-detail__top-spacer {
    flex: 1;
}

.staff-detail__header {
    display: flex;
    align-items: center;
    gap: 14px;
}

.staff-detail__avatar {
    width: 46px;
    height: 46px;
    flex: none;
    border-radius: 50%;
    background: var(--color-border-muted);
    color: var(--color-text-secondary);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 15px;
    font-weight: 700;
}

.staff-detail__header-info {
    flex: 1;
    min-width: 0;
}

.staff-detail__name {
    font-size: 20px;
    font-weight: 600;
    letter-spacing: -0.01em;
}

.staff-detail__sub {
    font-size: 12px;
    color: var(--color-text-faint);
    margin-top: 2px;
}

.staff-detail__workload {
    width: 220px;
    flex: none;
}

.staff-detail__workload-labels {
    display: flex;
    justify-content: space-between;
    font-size: 11px;
    color: var(--color-text-muted);
    margin-bottom: 4px;
}

.staff-detail__panels {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    margin-top: 16px;
    align-items: start;
}

.staff-detail__panel-col {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.staff-detail__panel {
    padding: 0;
}

.staff-detail__panel-header {
    padding: 12px 16px;
    border-bottom: 1px solid var(--color-border);
    font-size: 13px;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.staff-detail__panel-header-note {
    font-size: 11px;
    font-weight: 400;
    color: var(--color-text-faint);
}

.staff-detail__panel-header-link {
    font-size: 11px;
    font-weight: 500;
    color: var(--color-ink);
    text-decoration: none;
}

.staff-detail__panel-header-link:hover {
    text-decoration: underline;
}

.staff-detail__panel-empty {
    padding: 12px 16px;
    font-size: 12px;
    color: var(--color-status-acknowledged);
}

/*
 * "the ability to see assigned tickets" follow-up, Staff Detail's own
 * "Assigned tickets" panel — reuses `.staff__ticket-row` (built for the
 * Staff Overview's compact version of the same row), but this panel sets
 * `padding: 0` on itself (`.staff-detail__panel` above) unlike the
 * overview's already-padded `.staff__row` card, so this one context needs
 * its own horizontal padding added back.
 */
.staff-detail__panel .staff__ticket-row {
    padding: 8px 16px;
    border-bottom: 1px solid var(--color-border-muted);
}

.staff-detail__panel .staff__ticket-row:last-child {
    border-bottom: none;
}

/*
 * Task 9 (`docs/plans/2026-10-18-staff-activity-implementation-plan.md`) —
 * per-project activity bars ("Projects · GitLab {window}" panel), Toggl
 * day-grouped entries ("Toggl trackings" panel), and the new "Recent
 * activity" feed panel (no prototype slot — see `templates/staff_detail/
 * index.html.twig`'s own docblock). Row shapes mirror `docs/design/
 * Refinery.dc.html`'s `isStaffDetail` block's `g.*`/`r.*` inline styles,
 * translated to real classes/`--color-*` tokens per this file's existing
 * convention rather than ported as inline styles.
 */
.staff-detail__project-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 12px;
}

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

.staff-detail__project-row-name {
    width: 150px;
    flex: none;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.staff-detail__project-row-track {
    flex: 1;
    height: 6px;
    background: var(--color-border-muted);
    border-radius: 3px;
    overflow: hidden;
}

.staff-detail__project-row-bar {
    display: block;
    height: 100%;
    background: var(--color-text-faint);
    border-radius: 3px;
    transition: width 0.5s ease-out;
}

.staff-detail__project-row-count {
    width: 34px;
    flex: none;
    text-align: right;
    color: var(--color-text-muted);
    font-size: 11px;
}

.staff-detail__toggl-day {
    padding: 8px 16px 2px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-faint);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.staff-detail__toggl-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 12px;
}

.staff-detail__toggl-row-project {
    width: 140px;
    flex: none;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.staff-detail__toggl-row-description {
    flex: 1;
    min-width: 0;
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.staff-detail__toggl-row-duration {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-ink);
    flex: none;
}

.staff-detail__feed {
    margin-top: 12px;
}

.staff-detail__feed-row {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 9px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 12px;
}

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

.staff-detail__feed-row-type {
    width: 100px;
    flex: none;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-faint);
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.staff-detail__feed-row-summary {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.staff-detail__feed-row-summary a {
    color: var(--color-ink);
    text-decoration: none;
}

.staff-detail__feed-row-summary a:hover {
    text-decoration: underline;
}

.staff-detail__feed-row-date {
    font-size: 11px;
    color: var(--color-text-faint);
    flex: none;
}

/*
 * ---- Project Detail: Code tab ---- (2026-08-11 design refresh,
 * `templates/project_detail/_code.html.twig` + `assets/controllers/
 * code_quality_controller.js`). Replaces the previous pass's CC-vs-MI
 * cartesian-scatter styling wholesale — every rule below is new/rewritten
 * for the packed-circle bubble chart, 5-card summary, class-inspector
 * panel, collapsible package/class table and README audit table. Colour/
 * threshold values are computed server-side (`App\ProjectDetail\
 * CodeQualityColors`) and applied via inline `style` attributes in the
 * template — same convention as `App\Registry\InfrastructureCellView`'s
 * badge colouring elsewhere in this app — so these rules only ever carry
 * layout/typography, never a data-tied colour.
 */
.code-tab {
    margin-top: 16px;
    max-width: 1050px;
}

.code-tab__header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    margin-bottom: 12px;
}

.code-tab__header-note,
.code-tab__last-run {
    font-size: 11px;
    color: var(--color-text-faint);
}

.code-tab__header-right {
    display: flex;
    align-items: baseline;
    gap: 12px;
    flex: none;
}

.code-tab__path {
    font-family: var(--font-mono);
    color: var(--color-text-secondary);
    font-weight: 500;
}

.code-tab__gitlab-link {
    font-weight: 600;
    color: var(--color-ink);
}

.code-tab__summary {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 12px;
}

.code-tab__summary-card {
    padding: 14px 16px;
}

.code-tab__summary-label {
    display: block;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.code-tab__summary-value {
    display: block;
    font-family: var(--font-body);
    font-size: 26px;
    font-weight: 600;
    margin-top: 8px;
}

.code-tab__summary-unknown {
    font-size: 13px;
    font-weight: 400;
    color: var(--color-text-faint);
}

.code-tab__summary-sub {
    display: block;
    font-size: 11px;
    color: var(--color-text-muted);
    margin-top: 4px;
}

.code-tab__summary-bar {
    display: block;
    height: 6px;
    background: var(--color-severity-low-bg);
    border-radius: 3px;
    margin-top: 8px;
    overflow: hidden;
}

.code-tab__summary-bar-fill {
    display: block;
    height: 100%;
    border-radius: 3px;
}

/*
 * IMPLEMENTATION_PLAN.md Stage 5: the bubble-chart+inspector block and the
 * package-table+pager block merge into ONE bordered container
 * (`.code-tab__explorer`, reusing `.card` for background/radius/overflow
 * but overriding its border to `--color-border-strong` — matches the
 * design's own `#fffcf5`/`#cfc5ae` literal values exactly) behind the
 * shared "01 / CLASS EXPLORER" numbered header — same `margin-top`
 * convention `.infra-tab__panel`/`.deps-tab__table` already use for the
 * gap under their own preceding `section_head()` sibling.
 */
.code-tab__explorer {
    margin-top: 12px;
    border-color: var(--color-border-strong);
}

.code-tab__chart-row {
    display: grid;
    grid-template-columns: 1fr 330px;
    align-items: stretch;
}

.code-tab__chart-head,
.code-tab__table-head {
    padding: 12px 16px;
    border-bottom: 1px solid var(--color-border);
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.code-tab__chart-title,
.code-tab__table-title {
    font-size: 13px;
    font-weight: 600;
}

.code-tab__chart-sub,
.code-tab__table-sub {
    font-size: 11px;
    color: var(--color-text-faint);
}

.code-tab__chart-body {
    padding: 10px 12px;
}

.code-tab__chart-svg {
    width: 100%;
    height: auto;
    display: block;
}

/*
 * Default (no selection yet) bubble treatment ported verbatim from the
 * design mock's own `op:sel?(...):0.92` branch — once any class has been
 * selected, `code_quality_controller.js#highlightBubbles` takes over and
 * sets stroke/stroke-width/opacity on every bubble directly (1 for the
 * selected one, 0.55 for the rest), matching the mock's `sel` branch.
 */
.code-tab__bubble {
    stroke: #fffcf5;
    stroke-width: 1;
    opacity: 0.92;
    cursor: pointer;
}

.code-tab__chart-legend {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 0 16px 12px;
    font-size: 11px;
    color: var(--color-text-muted);
}

.code-tab__legend-item {
    display: flex;
    align-items: center;
    gap: 5px;
}

.code-tab__legend-swatch {
    display: inline-block;
    width: 9px;
    height: 9px;
    border-radius: 50%;
}

.code-tab__legend-spacer {
    flex: 1;
}

/*
 * Class-inspector side panel (design's "click a bubble → class detail",
 * now also reachable by clicking a class-table row — see
 * `code_quality_controller.js#select`). 280px-wide sibling of the bubble
 * chart card, not a modal — a bubble's detail is a small, glanceable
 * amount of data, not a full page of content.
 */
.code-tab__detail-card {
    display: flex;
    flex-direction: column;
    /* Merged-card divider (Stage 5) between the two grid columns, replacing
       the visual separation the two previously-independent `.card`s used
       to get for free from their own full border + the row's `gap`. Same
       convention `.infra-tab__row`'s own Production/Staging column divider
       already uses (`border-left: 1px solid var(--color-border)`). */
    border-left: 1px solid var(--color-border);
}

.code-tab__detail-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 11px 16px;
    background: #f3ecdb;
    border-bottom: 1px solid var(--color-border);
}

.code-tab__detail-title {
    font-size: 13px;
    font-weight: 600;
}

/*
 * Verdict badge (HEALTHY/WATCH/REFACTOR) — reflects the *selected class's*
 * own MI (code-quality review fix: an earlier draft of this stage wrongly
 * derived it from the project-wide average instead), so unlike every
 * other data-tied colour in this tab (applied inline server-side), this
 * one is set inline by `assets/controllers/code_quality_controller.js`'s
 * `fillDetail()` at selection time — starts `hidden` (native `[hidden]`
 * handles the "nothing selected yet" state, no CSS needed for that part).
 * Text colour is the one fixed, literal value the design specifies
 * regardless of which verdict it is.
 */
.code-tab__detail-verdict {
    margin-left: auto;
    font-size: 10px;
    font-weight: 700;
    border-radius: 4px;
    padding: 2px 8px;
    color: #fffcf5;
}

.code-tab__detail-placeholder {
    padding: 12px 16px;
    font-size: 12px;
    color: var(--color-text-faint);
    line-height: 1.6;
}

.code-tab__detail-body {
    padding: 12px 16px;
}

.code-tab__detail-file {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-muted);
    line-height: 1.5;
    word-break: break-all;
}

.code-tab__detail-file-link {
    color: inherit;
    text-decoration: none;
    border-bottom: 1px dotted var(--color-text-faint);
}

.code-tab__detail-file-link:hover {
    border-bottom-style: solid;
}

.code-tab__detail-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
    margin-top: 12px;
}

.code-tab__detail-metric-label {
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--color-text-faint);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.code-tab__detail-metric-value {
    font-size: 17px;
    font-weight: 600;
}

.code-tab__detail-note {
    font-size: 12px;
    color: var(--color-text-secondary);
    line-height: 1.6;
    margin-top: 12px;
}

.code-tab__detail-meta {
    font-size: 11px;
    color: var(--color-text-faint);
    margin-top: 10px;
}

/*
 * IMPLEMENTATION_PLAN.md Stage 5: this block now lives inside the merged
 * `.code-tab__explorer` card (see that class's own docblock above) —
 * `border-top` is the design's own internal divider between the chart/
 * inspector block and this table block, replacing the `margin-top` gap
 * this used when it was still a separate, independent `.card`.
 */
.code-tab__packages {
    border-top: 1px solid var(--color-border-strong);
}

/* Design's own literal package-table head tone (`#f6f1e4`, matches
   `--color-header-bg` exactly) — scoped to this table only, NOT the
   shared `.code-tab__chart-head, .code-tab__table-head` rule above, since
   the README table's own head (also `.code-tab__table-head`) is
   deliberately unchanged by this stage. */
.code-tab__packages .code-tab__table-head {
    background: var(--color-header-bg);
}

.code-tab__col-head {
    display: flex;
    gap: 12px;
    padding: 8px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

/* Realigns the package table's header labels with its data rows now that
   a 3px `.code-tab__row-strip` + its content wrapper's own 16px padding
   push package-row text 3px further right than before (19px total vs. the
   plain 16px every other `.code-tab__col-head` still uses) — scoped to
   this one table only; the class drill-down's own header
   (`.code-tab__class-col-head` below) needs no equivalent change, since
   37px (its content wrapper's padding) + 3px (the strip) still totals the
   same 40px its header already used. */
.code-tab__packages > .code-tab__col-head {
    padding-left: 19px;
}

/*
 * Shared column classes reused across the top-level package rows, the
 * nested per-class drill-down rows, AND the README audit table — the
 * template applies the exact same `.code-tab__col--*` class regardless of
 * which of those three tables it is in (see `_code.html.twig`), so a
 * column's exact width/colour here is scoped to its containing table
 * (`.code-tab__packages`/`.code-tab__readme`) rather than the shared class
 * alone, matching each table's own distinct widths from the design.
 */
.code-tab__col--num {
    flex: none;
    text-align: right;
}

.code-tab__col--mi {
    font-weight: 600;
}

.code-tab__col--coverage {
    width: 170px;
    flex: none;
    display: flex;
    align-items: center;
    gap: 8px;
}

/*
 * "Add column sortings for the tables in the code tab" — same
 * `assets/controllers/sortable_table_controller.js` the Dependencies tab
 * already uses, one instance per table (the "Coverage by package" table,
 * each package's own nested per-class drill-down, and the README audit
 * table are 3+ independent instances). Identical visual language to
 * `.deps-tab__col--sortable` on purpose — same affordance, different tab.
 */
.code-tab__col--sortable {
    /* Same QA fix as `.deps-tab__col--sortable` above (identical bug, same
       copy-pasted class) — `color: inherit` so the global `a { color:
       var(--color-ink) }` rule doesn't override the muted header color. */
    color: inherit;
    text-decoration: none;
    cursor: pointer;
    user-select: none;
    display: inline-flex;
    align-items: center;
    gap: 3px;
}

.code-tab__col--sortable:hover,
.code-tab__col--sortable[data-sort-active] {
    color: var(--color-ink);
}

.code-tab__col--sortable::after {
    content: '\21C5';
    font-size: 9px;
    opacity: 0.4;
}

.code-tab__col--sortable[data-sort-active="asc"]::after {
    content: '\2191';
    opacity: 1;
}

.code-tab__col--sortable[data-sort-active="desc"]::after {
    content: '\2193';
    opacity: 1;
}

/*
 * Package/class table: Package|Class name column is 1.4, and the 4 numeric
 * columns that follow (Classes/Methods, LoC, CC, MI) are fixed 70/70/60/50
 * — the template gives all four the same `.code-tab__col--num` class, so
 * their distinct widths are addressed positionally (`nth-of-type`) rather
 * than by a modifier class.
 */
.code-tab__packages .code-tab__col--package {
    flex: 1.4;
    min-width: 0;
}

.code-tab__packages .code-tab__col--num:nth-of-type(2) {
    width: 70px;
    color: var(--color-text-muted);
}

.code-tab__packages .code-tab__col--num:nth-of-type(3) {
    width: 70px;
}

.code-tab__packages .code-tab__col--num:nth-of-type(4) {
    width: 60px;
}

.code-tab__packages .code-tab__col--num:nth-of-type(5) {
    width: 50px;
}

/*
 * Leading colour strip (Stage 5) — a plain flex child sized to the full
 * row height via the row's own `align-items: stretch` (see
 * `.code-tab__package-row`/`.code-tab__class-row` below), coloured inline
 * from the row's own already-computed `complexityColor` (no new PHP data).
 * Package rows get one now too, not just class sub-rows, so the two row
 * types stay visually consistent inside the merged card.
 */
.code-tab__row-strip {
    width: 3px;
    flex: none;
}

.code-tab__package-row {
    display: flex;
    align-items: stretch;
    border-bottom: 1px solid var(--color-border-muted);
    cursor: pointer;
    background: transparent;
}

.code-tab__package-row:hover {
    background: var(--color-row-hover);
}

/* Holds every cell that used to be direct children of `.code-tab__package-
   row` itself, before the leading strip needed to sit outside their own
   padding to reach the row's true left edge. */
.code-tab__package-row-content {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 16px;
    font-size: 13px;
    flex: 1;
    min-width: 0;
}

.code-tab__package-cell {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.code-tab__package-caret {
    display: inline-block;
    width: 8px;
    flex: none;
    font-size: 9px;
    color: var(--color-text-faint);
}

.code-tab__package-name {
    font-family: var(--font-mono);
    font-size: 12px;
    font-weight: 500;
}

.code-tab__class-group {
    background: #f9f5ea;
    border-bottom: 1px solid var(--color-border-muted);
}

.code-tab__class-col-head {
    display: flex;
    gap: 12px;
    padding: 7px 16px 7px 40px;
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--color-text-faint);
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.code-tab__class-empty {
    padding-left: 40px;
}

.code-tab__class-row {
    display: flex;
    align-items: stretch;
    border-top: 1px solid #efe7d5;
    cursor: pointer;
    background: transparent;
}

.code-tab__class-row:hover {
    background: var(--color-row-hover);
}

/* Same role as `.code-tab__package-row-content` above, one level deeper —
   37px left padding + the strip's own 3px keeps the same 40px total indent
   this row always had. */
.code-tab__class-row-content {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 16px 8px 37px;
    font-size: 12px;
    flex: 1;
    min-width: 0;
}

/* Design's own `c.bg: sel?...'#efe7d5':'transparent'` — kept and highlighted
   in sync with the matching bubble by `code_quality_controller.js#select`. */
.code-tab__class-row--selected {
    background: #efe7d5;
}

.code-tab__class-name {
    font-family: var(--font-mono);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.code-tab__coverage-track {
    flex: 1;
    height: 6px;
    background: var(--color-severity-low-bg);
    border-radius: 3px;
    overflow: hidden;
}

.code-tab__coverage-track--sm {
    height: 5px;
}

.code-tab__coverage-bar {
    display: block;
    height: 100%;
    border-radius: 3px;
}

.code-tab__coverage-pct {
    width: 34px;
    text-align: right;
    font-size: 12px;
    font-weight: 500;
}

/*
 * IMPLEMENTATION_PLAN.md Stage 5: border upgraded to `--color-border-strong`
 * (matches every other card this stage touches, `#cfc5ae`) and background
 * to `--color-sidebar-bg` (`#faf6ec`) — both the design's own literal
 * values for this card, replacing the generic `.card` defaults
 * (`--color-border`/`--color-surface`). Structurally unchanged otherwise —
 * this card stays independent, it does not join `.code-tab__explorer`.
 */
.code-tab__readme {
    margin-top: 12px;
    margin-bottom: 8px;
    background: var(--color-sidebar-bg);
    border-color: var(--color-border-strong);
}

/* README table: Package column is 1.2 here (vs. 1.4 on the package/class
   table above) and its one numeric column (Words) is 80px, not 70/70/60/50
   — both scoped to `.code-tab__readme` for the same reason noted above. */
.code-tab__readme .code-tab__col--package {
    flex: 1.2;
    min-width: 0;
    font-family: var(--font-mono);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.code-tab__readme .code-tab__col--num {
    width: 80px;
    color: var(--color-text-muted);
}

.code-tab__col--file {
    width: 80px;
    flex: none;
    font-size: 11px;
    font-weight: 500;
}

.code-tab__col--desc {
    width: 96px;
    flex: none;
}

.code-tab__col--verdict {
    flex: 1.3;
    color: var(--color-text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Design's badge sizing for the README "Description" column (5px radius,
   2px 8px padding, 11px/500) differs from this app's generic `.badge`
   (4px/1px 5px/10px/600) — scoped to this one column so no other badge
   usage in the app is affected. */
.code-tab__col--desc .badge {
    font-size: 11px;
    font-weight: 500;
    border-radius: 5px;
    padding: 2px 8px;
}

.code-tab__readme-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 9px 16px;
    border-bottom: 1px solid var(--color-border-muted);
    font-size: 12px;
}

.code-tab__readme-footnote {
    padding: 12px 16px;
    background: #f3ecdb;
    border-top: 1px solid var(--color-border);
    font-size: 11px;
    color: var(--color-text-faint);
    line-height: 1.5;
}

/*
 * IMPLEMENTATION_PLAN.md Stage 5: real query-param pagination footer for
 * both the package table and the README table — same shape as
 * `_dependencies.html.twig`'s own `.deps-tab__pager*` rules (Stage 4),
 * duplicated under this tab's own `.code-tab__` prefix rather than shared,
 * matching this codebase's established per-tab-scoped-class convention
 * (see `.code-tab__col--sortable`'s own docblock for the same discipline).
 * `--color-sidebar-bg` (`#faf6ec`) is this stage's own literal pager
 * background — Dependencies' pager uses `--color-header-bg` instead;
 * verified against the design bundle, not copied by assumption.
 */
.code-tab__pager {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    background: var(--color-sidebar-bg);
    border-top: 1px solid var(--color-border);
}

.code-tab__pager-range {
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-faint);
}

.code-tab__pager-spacer {
    flex: 1;
}

.code-tab__pager-nav {
    font-size: 12px;
    font-weight: 500;
    color: var(--color-text-secondary);
    text-decoration: none;
}

.code-tab__pager-nav:hover {
    color: var(--color-ink);
}

.code-tab__pager-pages {
    display: flex;
    gap: 4px;
}

.code-tab__pager-page {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    padding: 0 5px;
    border-radius: 2px;
    font-size: 12px;
    color: var(--color-text-muted);
    text-decoration: none;
}

.code-tab__pager-page:hover {
    background: var(--color-row-hover);
    color: var(--color-ink);
}

.code-tab__pager-page--active {
    background: var(--color-ink);
    color: #fff;
    font-weight: 600;
}

/*
 * Code history pass (docs/plans/2026-10-28-code-history-design.md) — a
 * confirmed deviation from the design prototype, so nothing below is
 * "ported": it reuses the app's own tokens and the Business tab's chart-card
 * vocabulary (shaded head band, hairline KPI grid, mono uppercase labels)
 * so "01 / CODE HISTORY", the Hotspots view and "03 / ACTIVE BRANCHES" read
 * as part of the same tab.
 */
/* Four numbered sections now stack on this tab — give each header room. */
.code-tab > .section-head {
    margin-top: 28px;
}

.code-history {
    margin-top: 12px;
    padding: 0;
    border-color: var(--color-border-strong);
}

.code-history__head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 12px 18px;
    background: var(--color-header-bg);
    border-bottom: 1px solid var(--color-border);
}

.code-history__title {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-secondary);
    flex: none;
}

.code-history__subtitle {
    font-size: 11px;
    color: var(--color-text-faint);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.code-history__subtitle b {
    font-family: var(--font-mono);
    font-weight: 500;
    color: var(--color-text-secondary);
}

.code-history__source {
    margin-left: auto;
    flex: none;
    font-family: var(--font-mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-faint);
}

/* Two hero tiles (6 columns each) over four small tiles (3 each) —
   dev-monitor's own hierarchy, drawn as this app's hairline KPI grid. */
.code-history__kpis {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 1px;
    background: var(--color-border-muted);
    border-bottom: 1px solid var(--color-border);
}

.code-history__kpi {
    grid-column: span 3;
    display: flex;
    flex-direction: column;
    gap: 7px;
    min-width: 0;
    padding: 14px 18px 16px;
    background: var(--color-surface);
}

.code-history__kpi--hero {
    grid-column: span 6;
    flex-direction: row;
    align-items: flex-end;
    gap: 16px;
}

.code-history__kpi-text {
    display: flex;
    flex-direction: column;
    gap: 7px;
    flex: none;
}

.code-history__kpi-label {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-muted);
    white-space: nowrap;
}

.code-history__kpi-value {
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.code-history__kpi-value small {
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0;
    color: var(--color-text-muted);
}

.code-history__kpi-value--hero {
    font-size: 34px;
}

.code-history__kpi-value.code-tab__summary-unknown {
    font-size: 15px;
}

.code-history__kpi-sub {
    font-size: 11px;
    color: var(--color-text-faint);
}

.code-history__kpi-sub--clip {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.code-history__delta {
    font-weight: 600;
    color: var(--color-text-muted);
}

.code-history__delta.is-up,
.code-history__added {
    color: var(--color-status-resolved);
}

.code-history__delta.is-down,
.code-history__deleted {
    color: var(--color-severity-critical);
}

.code-history__sha {
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--color-text-secondary);
    text-decoration: none;
}

a.code-history__sha:hover {
    color: var(--color-ink);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.code-history__spark {
    flex: 1;
    min-width: 60px;
    height: 44px;
}

.code-history__toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    padding: 12px 18px 4px;
}

.code-history__chips,
.code-history__ranges {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

/* Legend chip doubling as a series toggle — the swatch carries the series
   colour (inline `--chip-color`), the off state fades and strikes the
   label so a hidden series never looks merely "unselected". */
.code-history__chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 9px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    background: var(--color-surface);
    font-family: var(--font-body);
    font-size: 11px;
    color: var(--color-text-secondary);
    cursor: pointer;
}

.code-history__chip:hover {
    border-color: var(--color-border-strong);
}

.code-history__chip i {
    width: 14px;
    height: 2px;
    background: var(--chip-color);
}

.code-history__chip i.is-dashed {
    background: repeating-linear-gradient(90deg, var(--chip-color) 0 4px, transparent 4px 6px);
}

.code-history__chip i.is-bars {
    width: 10px;
    height: 10px;
    background: linear-gradient(var(--color-status-resolved) 0 60%, var(--color-severity-critical) 60%);
}

.code-history__chip i.is-square {
    width: 9px;
    height: 9px;
    border-radius: 2px;
}

.code-history__chip[aria-pressed="false"] {
    color: var(--color-text-faint);
    background: transparent;
    text-decoration: line-through;
}

.code-history__chip[aria-pressed="false"] i {
    opacity: 0.3;
}

.code-history__range,
.code-tab__view-button {
    padding: 4px 8px;
    border: 1px solid transparent;
    border-radius: var(--radius-panel);
    background: transparent;
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.08em;
    color: var(--color-text-muted);
    cursor: pointer;
}

.code-history__range:hover,
.code-tab__view-button:hover {
    background: var(--color-row-hover);
    color: var(--color-ink);
}

.code-history__range.is-active,
.code-tab__view-button.is-active {
    background: var(--color-ink);
    color: var(--color-surface);
    font-weight: 600;
}

.code-history__chip:focus-visible,
.code-history__range:focus-visible,
.code-tab__view-button:focus-visible {
    outline: 2px solid var(--color-status-acknowledged);
    outline-offset: 1px;
}

.code-history__plot {
    position: relative;
    padding: 4px 18px 12px;
}

.code-history__svg {
    display: block;
}

.code-history__axis-label {
    font-family: var(--font-mono);
    font-size: 10px;
    fill: var(--color-text-faint);
}

.code-history__empty-text {
    font-size: 12px;
    fill: var(--color-text-faint);
}

.code-history__tooltip {
    position: absolute;
    z-index: 2;
    max-width: 300px;
    padding: 9px 11px;
    background: var(--color-ink);
    border-radius: var(--radius-pill);
    box-shadow: 0 4px 14px rgb(33 26 14 / 18%);
    font-size: 11px;
    line-height: 1.5;
    color: #e8dfcb;
    pointer-events: none;
}

.code-history__tooltip[hidden] {
    display: none;
}

.code-history__tip-title {
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--color-text-dim);
}

.code-history__tooltip .code-history__sha {
    color: #fffcf5;
}

.code-history__tip-subject {
    margin: 3px 0 4px;
    font-weight: 600;
    color: #fffcf5;
}

.code-history__tip-delta {
    font-family: var(--font-mono);
}

.code-history__tip-is-up {
    color: #a9c46e;
}

.code-history__tip-is-down {
    color: #f0a48f;
}

.code-history__tip-author {
    margin-top: 3px;
    color: var(--color-text-dim);
}

.code-history__tip-row {
    display: grid;
    grid-template-columns: 9px minmax(0, 1fr) auto auto;
    align-items: center;
    gap: 7px;
    white-space: nowrap;
}

.code-history__tip-row i {
    width: 9px;
    height: 9px;
    border-radius: 2px;
}

.code-history__tip-row b {
    font-family: var(--font-mono);
    font-weight: 500;
    color: #fffcf5;
}

.code-history__tip-row span {
    color: var(--color-text-dim);
}

/* Heatmap and contributors stack full width: the heatmap is a 53-column
   strip and is unreadably small in half a row. */
.code-history__panel {
    display: flex;
    flex-direction: column;
    margin-top: 12px;
    padding: 0;
    border-color: var(--color-border-strong);
}

.code-history__panel-body {
    position: relative;
    flex: 1;
    padding: 14px 18px 8px;
}

.code-history__heatmap {
    display: block;
    width: 100%;
    max-width: 980px;
    height: auto;
}

.code-tab__chart-title {
    white-space: nowrap;
}

/* The heatmap SVG scales up via its viewBox; keep its labels at ~10px rendered. */
.code-history__heatmap .code-history__axis-label {
    font-size: 8px;
}

.code-history__cell {
    cursor: default;
}

.code-history__cell:hover {
    stroke: var(--color-ink);
    stroke-width: 1;
}

.code-history__panel-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 10px 18px 12px;
    font-size: 11px;
    color: var(--color-text-faint);
}

.code-history__chips--legend {
    justify-content: flex-start;
}

.code-history__heat-legend {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    flex: none;
}

.code-history__heat-legend i {
    width: 10px;
    height: 10px;
    border-radius: 2px;
}

.code-history--empty .business-tab__empty,
.code-branches--empty .business-tab__empty {
    padding: 16px 18px;
}

.code-history--empty .business-tab__empty code {
    font-family: var(--font-mono);
    font-size: 11px;
}

/* Class Explorer: Maintainability / Hotspots switch + hotspot scatter. */
.code-tab__view-switch {
    display: inline-flex;
    gap: 2px;
    margin-left: auto;
    flex: none;
}

.code-tab__hot-zone {
    fill: var(--color-severity-critical-bg);
    opacity: 0.7;
}

.code-tab__hot-zone-label {
    font-family: var(--font-mono);
    font-size: 10px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    fill: var(--color-severity-critical);
}

.code-tab__hot-grid {
    stroke: var(--color-border-muted);
    stroke-width: 1;
}

.code-tab__hot-axis {
    font-family: var(--font-mono);
    font-size: 10px;
    fill: var(--color-text-faint);
}

.code-tab__hot-axis--title {
    fill: var(--color-text-muted);
}

.code-tab__hotspot {
    opacity: 0.8;
}

.code-tab__legend-swatch--zone {
    border-radius: 2px;
    background: var(--color-severity-critical-bg);
    outline: 1px solid #f0c6b8;
}

/* "03 / ACTIVE BRANCHES" — one card per unmerged, recently active branch. */
.code-branches {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
    margin-top: 12px;
}

.code-branches--empty {
    display: block;
}

.code-branches__card {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px 16px;
}

.code-branches__head {
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.code-branches__name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--font-mono);
    font-size: 12px;
    font-weight: 600;
    color: var(--color-ink);
    text-decoration: none;
}

a.code-branches__name:hover {
    text-decoration: underline;
    text-underline-offset: 2px;
}

.code-branches__age {
    margin-left: auto;
    flex: none;
    font-size: 11px;
    color: var(--color-text-faint);
}

.code-branches__stats {
    display: grid;
    grid-template-columns: repeat(3, auto);
    justify-content: space-between;
    gap: 8px;
    padding: 10px 0;
    border-top: 1px solid var(--color-border-muted);
    border-bottom: 1px solid var(--color-border-muted);
}

.code-branches__stat {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.code-branches__stat-label {
    font-family: var(--font-mono);
    font-size: 9px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--color-text-faint);
}

.code-branches__stat-value {
    font-size: 15px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.code-branches__stat-value small {
    font-size: 12px;
    font-weight: 500;
    color: var(--color-text-muted);
}

.code-branches__commit {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12px;
    color: var(--color-text-secondary);
}

.code-branches__author {
    margin-top: -6px;
    font-size: 11px;
    color: var(--color-text-faint);
}

@media (max-width: 1100px) {
    .code-history__kpi,
    .code-history__kpi--hero {
        grid-column: span 6;
    }

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

/* ==========================================================================
   Landing (`/`) & login (`/login`) — the only two screens that extend
   base.html.twig directly instead of layout/app.html.twig's sidebar chrome.
   No design-prototype coverage exists for these (confirmed against
   docs/design/Refinery.dc.html before building this), so they lean on the
   same tokens/components as the rest of the app rather than inventing a
   separate visual language. Motion is intentionally restrained - one
   ambient ticker drift, a one-time staggered fade-in, a hover-only glow -
   and gated behind prefers-reduced-motion so it never fights a visitor who
   has asked for less.
   ========================================================================== */

.landing,
.login {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    overflow: hidden;
}

.landing__ticker {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    opacity: 0.16;
    mask-image: linear-gradient(to bottom, transparent, black 15%, black 85%, transparent);
    -webkit-mask-image: linear-gradient(to bottom, transparent, black 15%, black 85%, transparent);
}

.landing__ticker-scroll {
    display: flex;
    flex-direction: column;
}

.landing__ticker-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 10px 0;
    font-size: 13px;
    white-space: nowrap;
}

.landing__ticker-code {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-muted);
    min-width: 52px;
}

.landing__ticker-text {
    color: var(--color-text-secondary);
}

.landing__ticker-time {
    font-family: var(--font-mono);
    font-size: 11px;
    color: var(--color-text-faint);
}

.landing__hero {
    position: relative;
    z-index: 1;
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 18px;
}

.landing__wordmark,
.login__wordmark {
    font-family: var(--font-mono);
    font-size: 28px;
    font-weight: 600;
    letter-spacing: 0.35em;
    color: var(--color-ink);
    /* balances the trailing letter-spacing so the wordmark optically centers */
    padding-left: 0.35em;
}

.landing__tagline {
    margin: 0;
    font-size: 16px;
    color: var(--color-text-secondary);
}

.landing__cta,
.login__gitlab-btn {
    text-decoration: none;
}

.landing__cta:hover,
.login__gitlab-btn:hover {
    text-decoration: none;
}

.landing__cta {
    margin-top: 8px;
    font-family: var(--font-mono);
}

/* Login card */

.login__card {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-panel);
    padding: 48px 56px;
}

.login__wordmark {
    font-size: 20px;
    padding-left: 0.35em;
}

.login__subhead {
    margin: 0;
    font-size: 14px;
    color: var(--color-text-muted);
}

.login__gitlab-btn {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    font-weight: 500;
    padding: 11px 22px;
    transition: box-shadow 0.3s ease, transform 0.2s ease;
}

.login__gitlab-btn:hover {
    transform: translateY(-1px);
    box-shadow: 0 4px 18px rgba(33, 26, 14, 0.12);
}

@media (prefers-reduced-motion: no-preference) {
    .landing__ticker-scroll {
        animation: refinery-ticker-drift 48s linear infinite;
    }

    .landing__hero > *,
    .login__card > * {
        opacity: 0;
        animation: refinery-fade-rise 0.7s ease-out forwards;
    }

    .landing__hero > *:nth-child(1),
    .login__card > *:nth-child(1) { animation-delay: 0.05s; }

    .landing__hero > *:nth-child(2),
    .login__card > *:nth-child(2) { animation-delay: 0.15s; }

    .landing__hero > *:nth-child(3),
    .login__card > *:nth-child(3) { animation-delay: 0.25s; }
}

@keyframes refinery-ticker-drift {
    from { transform: translateY(0); }
    to { transform: translateY(-50%); }
}

@keyframes refinery-fade-rise {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: translateY(0); }
}

/*
 * Ticket #21 — flatpickr calendar for the year-less `birthdayMonthDay`
 * field (`assets/controllers/birthday_picker_controller.js`). No design
 * prototype exists for this — flatpickr is the app's first-ever JS
 * dependency, added after the design prototype was produced — so this is
 * a light re-skin onto this app's existing tokens (font, border, ink
 * colors) rather than a design-fidelity port of a specific mock.
 *
 * `.numInputWrapper` is flatpickr's own year-spinner markup (the number
 * input + its up/down arrows next to the month name). Scoped to this
 * controller's own `birthday-picker-calendar` class — set on
 * `instance.calendarContainer` in `onReady` — rather than hiding it for
 * every flatpickr instance app-wide, even though this is currently the
 * only one. `display: none` also removes it from the tab order, so it's
 * not just visually hidden but genuinely unreachable, matching the
 * controller's `parseDate`/`formatDate` override that never reads or
 * writes a year at all.
 */
.birthday-picker-calendar {
    font-family: var(--font-body);
    border-color: var(--color-border-strong);
    box-shadow: 0 4px 18px rgba(33, 26, 14, 0.12);
}

.birthday-picker-calendar .numInputWrapper {
    display: none;
}

.birthday-picker-calendar .flatpickr-current-month {
    padding: 0;
}

.birthday-picker-calendar .flatpickr-current-month .cur-month {
    font-weight: 500;
}

.birthday-picker-calendar .flatpickr-day.selected,
.birthday-picker-calendar .flatpickr-day.selected:hover {
    background: var(--color-ink);
    border-color: var(--color-ink);
}

.birthday-picker-calendar .flatpickr-day.today {
    border-color: var(--color-border-strong);
}
