/* ============================================================
   Lygon Dashboard - nimbus brand
   navy #1D3153, sky #ABCCED, Inter, pill buttons, sky-wash page ground.
   ============================================================ */

*, *::before, *::after { box-sizing: border-box; }

:root {
    /* ─────────────────────────────────────────────────────────────────────
       Lygon - nimbus brand, light
         navy #1D3153  primary: headings, actions, chart fills
         sky  #ABCCED  the brand light blue, used as the page ground
       Inter, headings 700, pill buttons - as on nimbus.cloud.

       The page sits on a soft wash of the brand sky rather than white, so
       white cards lift off it without needing heavy borders or shadows.
       ───────────────────────────────────────────────────────────────────── */
    --navy:        #1d3153;
    --navy-rgb:    29, 49, 83;
    --navy-hover:  #16263f;
    --navy-soft:   #2f4f80;

    --sky:         #abcced;
    --sky-rgb:     171, 204, 237;
    --sky-tint:    #e6eff9;
    --sky-wash:    #eef4fb;

    /* Aliases so the component CSS below needs no edits */
    --azure-blue:        var(--navy);
    --azure-blue-hover:  var(--navy-hover);
    --azure-blue-active: var(--navy-hover);
    --azure-blue-bg:     rgba(var(--navy-rgb), 0.07);

    /* Backgrounds */
    --bg-body:       var(--sky-wash);
    --bg-topbar:     #ffffff;
    --bg-card:       #ffffff;
    --bg-card-hover: var(--sky-wash);
    --bg-input:      #ffffff;

    /* Text - navy-tinted rather than pure grey, so type belongs to the palette */
    --text-primary:   #16243d;
    --text-secondary: #4d5f7c;
    --text-muted:     #8092ad;
    --text-link:      var(--navy);
    --text-on-brand:  #ffffff;

    /* Borders - low contrast; the background wash does the separating */
    --border:        #d5e2f0;
    --border-light:  #e6eff9;
    --border-subtle: #d5e2f0;
    --accent:        var(--navy);

    /* Status - all clear WCAG AA on white */
    --s-ok:     #12784b;
    --s-ok-bg:  rgba(18, 120, 75, 0.10);
    --s-ok-br:  rgba(18, 120, 75, 0.26);
    --s-rev:    var(--navy);
    --s-rev-bg: rgba(var(--navy-rgb), 0.08);
    --s-rev-br: rgba(var(--navy-rgb), 0.22);
    --s-warn:   #a35a06;
    --s-warn-bg:rgba(163, 90, 6, 0.10);
    --s-warn-br:rgba(163, 90, 6, 0.26);
    --s-err:    #b3261e;
    --s-err-bg: rgba(179, 38, 30, 0.10);
    --s-err-br: rgba(179, 38, 30, 0.26);

    /* Layout - taller bar, wider page: there is real estate, so use it */
    --topbar-h:     64px;
    --max-w:        1600px;

    /* Spacing */
    --sp-xs: 4px;  --sp-sm: 8px;  --sp-md: 16px;
    --sp-lg: 24px; --sp-xl: 36px; --sp-2xl: 56px;

    /* Type scale - the old UI ran at 11-14px throughout, which read as cramped
       on a 1600px screen. Base is now 16px and everything derives from it. */
    --fs-xs:  13px;
    --fs-sm:  14px;
    --fs-md:  16px;
    --fs-lg:  18px;
    --fs-xl:  22px;
    --fs-2xl: 30px;
    --fs-3xl: 40px;

    /* Radius / Shadow */
    --r-sm: 8px; --r-md: 12px; --r-lg: 16px; --r-pill: 999px;
    --shadow-sm:   0 1px 2px rgba(var(--navy-rgb), .05);
    --shadow-md:   0 8px 24px rgba(var(--navy-rgb), .10);
    --shadow-card: 0 1px 3px rgba(var(--navy-rgb), .06);

    /* Fonts - Inter, as used on nimbus.cloud */
    --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;
    --mono: 'Cascadia Code', Consolas, 'Courier New', monospace;
}

html, body {
    margin: 0; padding: 0;
    min-height: 100vh;
    font-family: var(--font);
    background: var(--bg-body);
    color: var(--text-primary);
    font-size: 14px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

a { color: var(--text-link); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ── App Shell ─────────────────────────────────────── */

.app-shell {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

/* ── Top Bar ───────────────────────────────────────── */

.top-bar {
    position: sticky; top: 0; z-index: 100;
    height: var(--topbar-h);
    background: var(--bg-topbar);
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    padding: 0 var(--sp-lg);
    gap: var(--sp-md);
    box-shadow: var(--shadow-sm);
}

.brand {
    display: flex; align-items: center;
    gap: var(--sp-sm); flex-shrink: 0;
}

/* The lockup carries the wordmark, so the top bar sets no brand type of its
   own. Height drives the size and the width follows the artwork's own ratio. */
.brand-link {
    display: inline-flex; align-items: center;
    text-decoration: none;
}
.brand-link:hover { text-decoration: none; }

.brand-icon {
    display: block; flex-shrink: 0;
    height: 34px; width: auto;
    transition: opacity .15s;
}
.brand-link:hover .brand-icon { opacity: .8; }

.brand-tagline {
    font-size: 11px; color: var(--text-muted);
    border-left: 1px solid var(--border-light);
    padding-left: var(--sp-sm); margin-left: var(--sp-xs);
}

.top-nav { display: flex; gap: var(--sp-md); flex: 1; }

.nav-link {
    font-size: 13px; color: var(--text-secondary);
    padding: 4px 8px; border-radius: var(--r-sm);
    transition: color .15s, background .15s;
}
.nav-link:hover {
    color: var(--text-primary);
    background: var(--sky-tint);
    text-decoration: none;
}

/* The current tab, styled the way this app already marks a selected tab further down the
   page: a solid navy pill. Matching the runs filter rather than inventing a second idiom
   means one thing on screen means "you are here".

   It has to differ from :hover, not just from the resting state. A tinted background would
   have been the obvious lighter touch, but hover already uses that tint, so the current tab
   would have been indistinguishable from whichever tab the pointer happened to rest on. */
.nav-link.active,
.nav-link.active:hover {
    background: var(--azure-blue);
    color: var(--text-on-brand);
    font-weight: 600;
    text-decoration: none;
}

.top-bar-actions {
    display: flex; align-items: center;
    gap: var(--sp-md); margin-left: auto;
}

.user-chip { display: flex; align-items: center; gap: var(--sp-sm); }

.user-avatar {
    width: 30px; height: 30px; border-radius: 50%;
    background: var(--azure-blue); color: #fff;
    font-size: 12px; font-weight: 600;
    display: flex; align-items: center; justify-content: center;
    flex-shrink: 0; user-select: none;
}

.user-info-block {
    display: flex; flex-direction: column; line-height: 1.2;
}
.user-name  { font-size: 13px; font-weight: 500; color: var(--text-primary); }
.user-email { font-size: 11px; color: var(--text-muted); }

.btn-signout {
    font-size: 12px; color: var(--text-secondary);
    background: transparent;
    border: 1px solid var(--border-light);
    border-radius: var(--r-sm);
    padding: 4px 10px; cursor: pointer;
    text-decoration: none; transition: all .15s;
}
.btn-signout:hover {
    color: var(--text-primary);
    border-color: var(--azure-blue);
    background: var(--azure-blue-bg);
    text-decoration: none;
}

/* Help: a question mark rather than a word. The bar is sized close to the pixel for its
   links, see the narrow screen breakpoint below, and a fourth label would tip it into
   horizontal scroll just above that width. The same size as the avatar beside it. */
.btn-help {
    width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 14px; font-weight: 600; line-height: 1; color: var(--text-secondary);
    background: transparent;
    border: 1px solid var(--border-light);
    text-decoration: none; transition: all .15s;
}
.btn-help:hover {
    color: var(--text-primary);
    border-color: var(--azure-blue);
    background: var(--azure-blue-bg);
    text-decoration: none;
}
.btn-help:focus-visible { outline: 2px solid var(--azure-blue); outline-offset: 2px; }

/* ── Top bar on narrow screens ────────────────────────────────────────────────
   The bar is a single non-wrapping row of brand, three links and the signed in user,
   which needs around 780px. Below that the links used to push the row wider than the
   viewport and take the whole page into horizontal scroll, so they fold into a panel
   under the bar instead.

   This breakpoint is the only copy of the number. wwwroot/js/nav-menu.js asks whether
   the toggle button is visible rather than measuring the window, so there is nothing
   here to keep in step with it. */
.nav-toggle { display: none; }

@media (max-width: 860px) {
    .nav-toggle {
        display: inline-flex; align-items: center; justify-content: center;
        /* 40px square: a touch target, not a 20px icon with a hit box the size of itself. */
        width: 40px; height: 40px; flex-shrink: 0;
        margin-left: var(--sp-sm);
        background: transparent; color: var(--text-secondary);
        border: 1px solid var(--border); border-radius: var(--r-sm);
        cursor: pointer; transition: background .15s, color .15s;
    }
    .nav-toggle:hover { background: var(--sky-tint); color: var(--text-primary); }
    .nav-toggle:focus-visible { outline: 2px solid var(--azure-blue); outline-offset: 2px; }

    /* The panel hangs off the bar, which is sticky and therefore already a containing
       block for it. Full bleed rather than a floating card, so it reads as the bar
       having grown rather than as something laid over the page. */
    .top-nav {
        display: none;
        position: absolute; top: 100%; left: 0; right: 0;
        flex-direction: column; align-items: stretch;
        flex: none; gap: 2px;
        padding: var(--sp-sm);
        background: var(--bg-topbar);
        border-bottom: 1px solid var(--border);
        box-shadow: var(--shadow-md);
    }
    .top-bar.nav-open .top-nav { display: flex; }

    /* Full width rows in the panel, so the whole line is the target rather than the
       width of the word in it. */
    .top-nav .nav-link { padding: 10px var(--sp-md); border-radius: var(--r-sm); }

    /* The tagline is a caption on the brand, and the first thing worth losing for room. */
    .brand-tagline { display: none; }

    /* Keep the avatar, drop the name and address. The avatar carries a title with the
       full name, and the panel is where a narrow screen goes for anything else. */
    .user-info-block { display: none; }
    .top-bar-actions { gap: var(--sp-sm); }
}

/* ── Page Body ─────────────────────────────────────── */

.page-body {
    flex: 1;
    padding: var(--sp-xl) var(--sp-lg);
    max-width: var(--max-w);
    width: 100%; margin: 0 auto;
}

/* ── Footer ────────────────────────────────────────── */

.app-footer {
    border-top: 1px solid var(--border);
    padding: var(--sp-md) var(--sp-lg);
    text-align: center;
    font-size: 11px; color: var(--text-muted);
}

/* ── Buttons ───────────────────────────────────────── */

.btn-primary {
    display: inline-flex; align-items: center; gap: var(--sp-sm);
    background: var(--azure-blue); color: #fff;
    border: none; border-radius: var(--r-pill);
    padding: 8px 18px; font-size: 13px; font-weight: 500;
    cursor: pointer; text-decoration: none;
    transition: background .15s;
}
.btn-primary:hover {
    background: var(--azure-blue-hover);
    text-decoration: none; color: #fff;
}

/* ── Generic Card ──────────────────────────────────── */

.card {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    padding: var(--sp-lg);
    box-shadow: var(--shadow-card);
}

/* ── Page Header ───────────────────────────────────── */

.page-header {
    margin-bottom: var(--sp-xl);
    display: flex; align-items: flex-start;
    justify-content: space-between; gap: var(--sp-md);
}
.page-header-text h1 {
    font-size: 22px; font-weight: 600;
    color: var(--text-primary); margin: 0 0 4px;
}
.page-header-text p { color: var(--text-secondary); font-size: 13px; margin: 0; }

.live-badge {
    display: flex; align-items: center; gap: 6px;
    font-size: 11px; color: var(--s-ok);
    background: var(--s-ok-bg);
    border: 1px solid var(--s-ok-br);
    border-radius: 100px; padding: 4px 10px;
    flex-shrink: 0; font-weight: 500;
}
.live-dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--s-ok);
    animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
    0%, 100% { opacity: 1; }
    50%       { opacity: .35; }
}

/* ── Stat Cards ────────────────────────────────────── */

.stats-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--sp-md);
    margin-bottom: var(--sp-xl);
}
@media (max-width: 900px) { .stats-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 540px) { .stats-grid { grid-template-columns: 1fr; } }

.stat-card {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    padding: var(--sp-lg) var(--sp-xl);
    box-shadow: var(--shadow-card);
    display: flex; flex-direction: column; gap: var(--sp-xs);
    transition: border-color .2s, box-shadow .2s;
}
.stat-card:hover {
    border-color: var(--azure-blue);
    box-shadow: var(--shadow-md), 0 0 0 1px var(--azure-blue);
}
.stat-label {
    font-size: 11px; font-weight: 500;
    text-transform: uppercase; letter-spacing: .8px;
    color: var(--text-secondary);
}
.stat-value {
    font-size: 28px; font-weight: 700;
    color: var(--text-primary); line-height: 1;
}
.stat-value.accent { color: var(--azure-blue); }
.stat-trend { font-size: 11px; color: var(--s-ok); margin-top: var(--sp-xs); }

/* Cost ROI row */
.stats-grid-cost { margin-top: calc(-1 * var(--sp-lg)); margin-bottom: var(--sp-xl); }
.stat-card-cost { border-color: rgba(var(--navy-rgb), 0.14); }
.stat-card-cost:hover { border-color: var(--s-ok); box-shadow: var(--shadow-md), 0 0 0 1px var(--s-ok); }
.stat-value-cost { font-size: 22px; color: var(--s-ok); }

/* ── Section ───────────────────────────────────────── */

.section { margin-bottom: var(--sp-xl); }

.section-header {
    display: flex; align-items: center;
    justify-content: space-between;
    margin-bottom: var(--sp-md);
}
.section-header h2 {
    font-size: 15px; font-weight: 600;
    color: var(--text-primary); margin: 0;
}
.section-count { font-size: 12px; color: var(--text-muted); }

/* ── Pipeline Flow ─────────────────────────────────── */

.pipeline-flow-card { padding: var(--sp-md) var(--sp-lg); overflow-x: auto; }

.pipeline-flow {
    display: flex; align-items: center;
    gap: 0; min-width: max-content; padding: var(--sp-sm) 0;
}

.flow-step { display: flex; align-items: center; flex-shrink: 0; }

.flow-node {
    display: flex; flex-direction: column; align-items: center;
    gap: 5px; padding: 10px 14px;
    background: var(--bg-input);
    border: 1px solid var(--border-light);
    border-radius: var(--r-md);
    font-size: 11px; text-align: center; min-width: 76px;
    color: var(--text-secondary);
    transition: border-color .2s;
}
/* Idle: ALL nodes join a left-to-right wave animation.
   --step-delay is set inline on each .flow-step (0s … 4.2s) and inherited here. */
.flow-node-idle {
    border-color: var(--border-light);
    background: var(--bg-input);
    color: var(--text-secondary);
    animation: idle-wave 4.9s var(--step-delay, 0s) ease-in-out infinite;
}

/* Live "currently running" step - pulsing glow + spinning icon */
.flow-node-running {
    border-color: var(--azure-blue);
    background: var(--azure-blue-bg);
    color: var(--text-primary);
    animation: pulse-node 1.6s ease-in-out infinite;
    box-shadow: 0 0 0 0 rgba(var(--navy-rgb), 0.5);
}

/* Spin the emoji inside the currently-running node */
.flow-node-running .flow-node-icon {
    display: inline-block;
    animation: spin-icon 3s linear infinite;
    transform-origin: center;
}

/* Completed step during active run */
.flow-node-done {
    border-color: var(--s-ok);
    background: var(--s-ok-bg);
    color: var(--s-ok);
}

/* Failed step */
.flow-node-failed {
    border-color: var(--s-err);
    background: var(--s-err-bg);
    color: var(--s-err);
}

/* Arrow in the already-completed section - subtle brightness pulse */
.flow-arrow-active {
    color: var(--azure-blue);
    animation: arrow-pulse 1.2s ease-in-out infinite alternate;
}

/* Arrow immediately BEFORE the currently-running step - forward bounce */
.flow-arrow-current {
    color: var(--azure-blue);
    font-size: 18px;
    animation: arrow-bounce 0.5s ease-in-out infinite alternate;
}

.flow-node-icon { font-size: 18px; }
.flow-node-label { line-height: 1.3; }
.flow-arrow { color: var(--border-light); font-size: 16px; padding: 0 6px; transition: color .3s; }

/* Live pipeline badge next to section header */
/* Now a link to the run, and carries the live stage, so it needs room to breathe. */
.pipeline-live-badge {
    display: inline-flex; align-items: center; gap: 8px;
    font-size: var(--fs-xs); font-weight: 500;
    color: var(--azure-blue);
    background: var(--azure-blue-bg);
    border: 1px solid rgba(var(--navy-rgb), .3);
    border-radius: var(--r-pill); padding: 6px 16px;
    text-decoration: none;
}
.pipeline-live-badge:hover { background: var(--sky-tint); text-decoration: none; }
.pipeline-live-badge:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }

/* Idle is the normal state, so it should read as calm rather than as a fault, and it
   is the right place to say how work is actually started. */
.pipeline-idle-note {
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

/* ── Animation keyframes ───────────────────────────────── */

/* Sequential wave: each node glows briefly then fades back; --step-delay staggers them */
@keyframes idle-wave {
    0%   { border-color: var(--border-light); background: var(--bg-input);      color: var(--text-secondary); box-shadow: none; }
    8%   { border-color: var(--azure-blue);   background: var(--azure-blue-bg); color: var(--text-primary);   box-shadow: 0 0 10px rgba(var(--navy-rgb), .45); }
    18%  { border-color: var(--border-light); background: var(--bg-input);      color: var(--text-secondary); box-shadow: none; }
    100% { border-color: var(--border-light); background: var(--bg-input);      color: var(--text-secondary); box-shadow: none; }
}

/* Pulsing glow ring for the active running node */
@keyframes pulse-node {
    0%   { box-shadow: 0 0 0 0   rgba(var(--navy-rgb), 0.55); border-color: var(--azure-blue); }
    50%  { box-shadow: 0 0 0 8px rgba(var(--navy-rgb), 0);    border-color: var(--navy-soft); }
    100% { box-shadow: 0 0 0 0   rgba(var(--navy-rgb), 0.55); border-color: var(--azure-blue); }
}

/* Emoji spin for the running node icon */
@keyframes spin-icon {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

/* Completed-section arrow opacity fade */
@keyframes arrow-pulse {
    from { opacity: 0.4; }
    to   { opacity: 1.0; }
}

/* Forward bounce on the arrow immediately before the running step */
@keyframes arrow-bounce {
    from { transform: translateX(0);   opacity: 0.65; color: var(--azure-blue); }
    to   { transform: translateX(5px); opacity: 1.0;  color: var(--navy-soft); }
}

/* ── Pipeline Table ────────────────────────────────── */

.pipeline-table-wrapper {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    overflow: hidden; box-shadow: var(--shadow-card);
}
.pipeline-table { width: 100%; border-collapse: collapse; }
.pipeline-table th {
    background: var(--bg-input);
    color: var(--text-secondary);
    font-size: 12px; font-weight: 600;
    text-transform: uppercase; letter-spacing: .6px;
    padding: 12px var(--sp-md); text-align: left;
    border-bottom: 1px solid var(--border);
}
.pipeline-table td {
    padding: 14px var(--sp-md); font-size: 14px;
    color: var(--text-primary);
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}
.pipeline-table tr:last-child td { border-bottom: none; }
.pipeline-table tbody tr:hover td { background: var(--bg-card-hover); }

.ticket-id {
    font-family: var(--mono); font-size: 13px;
    color: var(--azure-blue); font-weight: 600;
}
.ticket-link {
    text-decoration: none; color: var(--azure-blue);
    border-bottom: 1px dashed rgba(var(--navy-rgb), 0.4);
    transition: border-color 0.15s, color 0.15s;
}
.ticket-link:hover { color: var(--navy-soft); border-bottom-color: var(--navy-soft); }

/* The key and its Jira link on one line. nowrap so a narrow column never puts the icon
   on a line of its own under a key it no longer appears to belong to. */
.ticket-cell { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }

/* Recessive at rest, so a column of them reads as a column of keys rather than a column
   of icons, and full strength on the row the reader is actually looking at. The padding
   and negative margin grow the target to a comfortable size without moving the icon. */
.jira-link {
    display: inline-flex; align-items: center;
    padding: 5px; margin: -5px;
    color: var(--text-muted); border-radius: var(--r-sm);
    opacity: .7; transition: opacity .15s, color .15s, background .15s;
}
.pipeline-row:hover .jira-link { opacity: 1; }
.jira-link:hover { color: var(--azure-blue); background: var(--sky-tint); opacity: 1; text-decoration: none; }
.jira-link:focus-visible { opacity: 1; outline: 2px solid var(--azure-blue); outline-offset: 1px; }
.pipeline-row:hover td { background: var(--bg-card-hover) !important; transition: background 0.12s; }

/* Failed row - subtle red left-border + tinted background */
.pipeline-row-failed td           { background: rgba(179,38,30,0.04) !important; }
.pipeline-row-failed td:first-child { border-left: 3px solid var(--s-err); padding-left: calc(var(--sp-md) - 3px); }
.pipeline-row-failed:hover td     { background: rgba(179,38,30,0.08) !important; }

/* A button that reads as a link, for an action offered inside a sentence. A real button
   element because it acts on this page rather than going anywhere, which is what decides
   the element: an anchor with no destination is a trap for anyone using a keyboard. */
.link-button {
    background: none; border: none; padding: 0;
    font: inherit; color: var(--text-link); font-weight: 600;
    cursor: pointer; text-decoration: underline;
}
.link-button:hover { color: var(--navy-soft); }
.link-button:focus-visible { outline: 2px solid var(--azure-blue); outline-offset: 2px; border-radius: 2px; }

/* Pagination bar below the runs table */
.pagination-bar {
    display: flex; align-items: center; justify-content: center; gap: var(--sp-md);
    margin-top: var(--sp-md); padding: var(--sp-sm) 0;
}
.pagination-btn {
    background: var(--bg-card); border: 1px solid var(--border-subtle);
    color: var(--text-primary); font-size: 12px; padding: 5px 14px;
    border-radius: var(--r-sm); cursor: pointer; transition: background 0.15s, border-color 0.15s;
}
.pagination-btn:hover:not(:disabled) { background: var(--bg-card-hover); border-color: var(--accent); }
.pagination-btn:disabled { opacity: 0.35; cursor: default; }
.pagination-info { font-size: 12px; color: var(--text-muted); min-width: 90px; text-align: center; }


/* Failure alert banner - shown at page top when any pipeline run has failed */
.failure-banner {
    display: flex; align-items: center; gap: var(--sp-md);
    background: rgba(179,38,30,0.08); border: 1px solid var(--s-err-br);
    border-left: 4px solid var(--s-err);
    border-radius: var(--r-md); padding: 10px var(--sp-md);
    margin-bottom: var(--sp-lg); font-size: 13px; flex-wrap: wrap;
}
.failure-banner-icon { font-size: 1.1rem; color: var(--s-err); flex-shrink: 0; }
.failure-banner strong { color: var(--s-err); }
/* Scoped to the text span only. A bare 'span' rule also matched the icon
   wrapper, which then grew to fill the row and pushed the message right. */
.failure-banner-text  { flex: 1; min-width: 200px; color: var(--text-primary);
                        line-height: 1.55; }
.failure-banner-link  {
    font-size: 12px; color: var(--s-err); text-decoration: none;
    opacity: 0.8; transition: opacity 0.15s; white-space: nowrap;
    border-bottom: 1px dashed var(--s-err);
}
.failure-banner-link:hover { opacity: 1; }

.row-details-link {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 5px 14px;
    font-size: 13px; font-weight: 600;
    color: var(--azure-blue);
    background: rgba(var(--navy-rgb), 0.10);
    border: 1.5px solid rgba(var(--navy-rgb), 0.30);
    border-radius: 100px;
    text-decoration: none;
    transition: background 0.15s, border-color 0.15s, color 0.15s, box-shadow 0.15s;
    white-space: nowrap;
}
.row-details-link:hover {
    background: rgba(var(--navy-rgb), 0.20);
    border-color: var(--azure-blue);
    color: var(--navy-soft);
    box-shadow: 0 2px 8px rgba(var(--navy-rgb), 0.18);
    text-decoration: none;
}
/* The same button on a failed run. Soft red rather than solid, because this is still a
   "go and look" control and not a warning in its own right: the Failed badge in the status
   column is what states the outcome, and it carries an icon and a word so the meaning never
   rests on the colour alone. */
.row-details-link-failed {
    color: var(--s-err);
    background: var(--s-err-bg);
    border-color: var(--s-err-br);
}
.row-details-link-failed:hover {
    color: var(--s-err);
    background: rgba(179, 38, 30, 0.18);
    border-color: var(--s-err);
    box-shadow: 0 2px 8px rgba(179, 38, 30, 0.18);
}

.agent-name {
    display: inline-flex; align-items: center; gap: 5px;
    font-size: 12px; color: var(--text-secondary);
}
.agent-dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--azure-blue); opacity: .7;
}
.duration { font-family: var(--mono); font-size: 13px; color: var(--text-secondary); }
.time-ago { font-size: 13px; color: var(--text-muted); }

/* ── Status Badges ─────────────────────────────────── */

.status-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 5px 14px; border-radius: 100px;
    font-size: 13px; font-weight: 600;
    letter-spacing: .01em;
    white-space: nowrap;
    box-shadow: var(--shadow-card);
}
.status-completed  { color: var(--s-ok);   background: var(--s-ok-bg);   border: 1.5px solid var(--s-ok-br); }
.status-review     { color: var(--s-rev);  background: var(--s-rev-bg);  border: 1.5px solid var(--s-rev-br); }
.status-analyzing  { color: var(--s-warn); background: var(--s-warn-bg); border: 1.5px solid var(--s-warn-br); }
.status-failed     { color: var(--s-err);  background: var(--s-err-bg);  border: 1.5px solid var(--s-err-br); }
.status-watching   { color: var(--navy-soft);       background: rgba(var(--navy-rgb),0.10); border: 1.5px solid rgba(var(--navy-rgb),0.28); }
.status-terminated { color: var(--text-muted);       background: rgba(139,148,158,0.10); border: 1.5px solid rgba(139,148,158,0.25); }

/* -- Bottom Grid ------------------------------------ */

/* One column since the Power BI placeholder was removed. Kept as a grid so a second
   panel can go back beside the services list without reworking the layout. */
.bottom-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--sp-md);
    margin-bottom: var(--sp-xl);
}

/* ── Services Panel ────────────────────────────────── */

.services-panel {
    background: var(--bg-card);
    border: 1px solid var(--border);
    border-radius: var(--r-lg);
    padding: var(--sp-lg);
    box-shadow: var(--shadow-card);
}
.services-panel h3 {
    font-size: 14px; font-weight: 600;
    color: var(--text-primary); margin: 0 0 var(--sp-md);
}
.service-list { display: flex; flex-direction: column; gap: var(--sp-sm); }
.service-item {
    display: flex; align-items: center;
    justify-content: space-between;
    padding: 8px 10px;
    background: var(--bg-input);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
}
.service-name {
    display: flex; align-items: center; gap: var(--sp-sm);
    font-size: 13px; color: var(--text-secondary);
}
.service-icon { font-size: 14px; }
.service-status {
    display: flex; align-items: center; gap: 5px;
    font-size: 11px; font-weight: 500;
}
.status-ok  { color: var(--s-ok); }
.status-err { color: var(--s-err); }
.service-dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: currentColor;
}

/* ── Login Page ────────────────────────────────────── */

.login-page {
    min-height: calc(100vh - var(--topbar-h));
    display: flex; align-items: center; justify-content: center;
    padding: var(--sp-xl);
    background:
        radial-gradient(ellipse at 20% 50%, rgba(var(--navy-rgb), .08) 0%, transparent 60%),
        radial-gradient(ellipse at 80% 20%, rgba(var(--sky-rgb),.30) 0%, transparent 60%),
        var(--bg-body);
}
.login-card {
    background: var(--bg-card);
    border: 1px solid var(--border-light);
    border-radius: var(--r-lg);
    padding: var(--sp-2xl);
    width: 100%; max-width: 420px;
    box-shadow: var(--shadow-md);
    display: flex; flex-direction: column;
    align-items: center; gap: var(--sp-lg);
}
.login-logo {
    display: flex; flex-direction: column;
    align-items: center; gap: var(--sp-sm); text-align: center;
}
/* The h1 keeps the page's heading structure and the lockup is its whole
   content, so it carries no type styling of its own. */
.login-logo h1 { margin: 0; line-height: 0; }
.login-logo h1 img { display: block; height: 88px; width: auto; }
.login-logo p { font-size: 13px; color: var(--text-secondary); margin: 0; }
.login-divider { width: 100%; height: 1px; background: var(--border); }
.login-prompt {
    font-size: 13px; color: var(--text-secondary);
    text-align: center; margin: 0;
}

/* Microsoft sign-in button - follows Microsoft's branding spec */
.btn-ms-signin {
    display: flex; align-items: center; justify-content: center;
    gap: 10px; width: 100%;
    padding: 10px 20px;
    background: var(--bg-card); color: var(--text-primary);
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    font-size: 14px; font-weight: 500;
    font-family: var(--font);
    cursor: pointer; text-decoration: none;
    transition: background .15s, box-shadow .15s;
}
.btn-ms-signin:hover {
    background: var(--sky-tint);
    box-shadow: var(--shadow-md);
    text-decoration: none; color: var(--text-primary);
}

.login-security-note {
    font-size: 11px; color: var(--text-muted);
    text-align: center;
    display: flex; align-items: center; gap: 5px;
}

/* ── Not Found ─────────────────────────────────────── */

.not-found {
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: var(--sp-md); min-height: 400px; text-align: center;
}
.not-found-code {
    font-size: 80px; font-weight: 700;
    color: var(--border-light); line-height: 1;
}
.not-found h1 { font-size: 22px; color: var(--text-primary); margin: 0; }
.not-found p  { color: var(--text-secondary); margin: 0; }

/* ── Error Page ────────────────────────────────────── */

.error-page {
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: var(--sp-md); min-height: 400px; text-align: center;
}
.error-page h1 { font-size: 22px; color: var(--s-err); margin: 0; }
.error-page p  { color: var(--text-secondary); margin: 0; }
.error-details {
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: var(--r-md); padding: var(--sp-md);
    font-family: var(--mono); font-size: 12px;
    color: var(--text-secondary); max-width: 600px; width: 100%;
    text-align: left; word-break: break-all;
}

/* ============================================================
   Pipeline Runner Panel
   ============================================================ */

.pipeline-runner-card {
    display: flex; flex-direction: column; gap: var(--sp-md);
    padding: var(--sp-lg);
}

.runner-trigger-row {
    display: flex; gap: var(--sp-sm); align-items: center;
}

.runner-input {
    flex: 1; background: var(--bg-input); border: 1px solid var(--border-light);
    border-radius: var(--r-sm); color: var(--text-primary);
    padding: 8px 14px; font-size: 14px; font-family: var(--mono);
    outline: none; transition: border-color 0.15s;
}
.runner-input:focus { border-color: var(--azure-blue); }
.runner-input:disabled { opacity: 0.5; cursor: not-allowed; }

.runner-btn {
    white-space: nowrap; padding: 8px 20px;
    font-size: 14px; font-weight: 600;
}

/* Stage strip */
.pipeline-stages {
    display: flex; align-items: center; gap: 6px;
    padding: var(--sp-sm) 0;
}
.stage {
    display: flex; flex-direction: column; align-items: center;
    gap: 4px; padding: 10px 18px;
    border-radius: var(--r-sm); border: 1px solid var(--border);
    font-size: 12px; min-width: 70px; transition: all 0.2s;
}
.stage-icon   { font-size: 18px; }
.stage-label  { color: var(--text-secondary); font-size: 11px; }
.stage-active { border-color: var(--azure-blue); background: var(--azure-blue-bg);
                animation: pulse-ring 1.2s ease-in-out infinite; }
.stage-done   { border-color: var(--s-ok); background: rgba(23,195,146,.1); }
.stage-failed { border-color: var(--s-err); background: rgba(235,77,75,.1); }
.stage-idle   { opacity: 0.45; }
.stage-arrow  { color: var(--text-muted); font-size: 18px; }

/* Status bar */
.runner-status {
    display: flex; align-items: center; gap: 10px;
    font-size: 13px; padding: 8px 12px;
    border-radius: var(--r-sm); border: 1px solid var(--border);
}
.runner-status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.runner-status-running .runner-status-dot { background: var(--azure-blue); animation: blink 1s infinite; }
.runner-status-ok      .runner-status-dot { background: var(--s-ok); }
.runner-status-fail    .runner-status-dot { background: var(--s-err); }
.runner-status-running { border-color: var(--azure-blue); }
.runner-status-ok      { border-color: var(--s-ok); }
.runner-status-fail    { border-color: var(--s-err); }
.runner-duration { margin-left: auto; color: var(--text-muted); font-family: var(--mono); font-size: 12px; }
.runner-link { color: var(--text-link); text-decoration: none; font-size: 12px; }
.runner-link:hover { text-decoration: underline; }

/* Box-shadow ring pulse for the active runner stage (renamed from the
   duplicate 'pulse' that collided with the opacity keyframes above). */
@keyframes pulse-ring {
    0%, 100% { box-shadow: 0 0 0 0 rgba(var(--navy-rgb), .4); }
    50%       { box-shadow: 0 0 0 6px rgba(var(--navy-rgb), .0); }
}

@keyframes blink { 0%,100%{opacity:1} 50%{opacity:.3} }

/* Live log */
.runner-log {
    background: var(--sky-wash); border: 1px solid var(--border);
    border-radius: var(--r-sm); padding: var(--sp-sm) var(--sp-md);
    font-family: var(--mono); font-size: 12px;
    max-height: 220px; overflow-y: auto;
    display: flex; flex-direction: column; gap: 2px;
}
.log-line { display: flex; gap: 10px; padding: 1px 0; }
.log-ts   { color: var(--text-muted); flex-shrink: 0; }
.log-msg  { color: var(--text-secondary); white-space: pre-wrap; }
.log-success .log-msg { color: var(--s-ok); }
.log-error   .log-msg { color: var(--s-err); }
.log-start   .log-msg { color: var(--azure-blue); }

.runner-hint { color: var(--text-muted); font-size: 13px; margin: 0; }

/* ── Runs toolbar: filter tabs + search ────────────────────────────────── */
.runs-toolbar {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--sp-md); margin-bottom: var(--sp-md); flex-wrap: wrap;
}
.runs-filter-tabs {
    display: inline-flex; gap: 2px;
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: var(--r-md); padding: 3px;
    /* Wraps rather than running off the side. A filter strip is a set of choices, so the
       reader has to be able to see all of them: scrolling it sideways would hide the last
       tabs behind a gesture nobody knows is available. max-width is what lets it wrap at
       all, since an inline-flex box is otherwise sized by its content. */
    flex-wrap: wrap; max-width: 100%;
}
.runs-filter-tab {
    display: inline-flex; align-items: center; gap: 6px;
    background: transparent; border: none; cursor: pointer;
    color: var(--text-secondary); font-size: 12px; font-weight: 500;
    padding: 5px 12px; border-radius: var(--r-sm);
    transition: background .15s, color .15s;
}
.runs-filter-tab:hover { color: var(--text-primary); }
.runs-filter-tab.active { background: var(--azure-blue); color: #fff; }
.runs-filter-count {
    font-size: 11px; font-weight: 600;
    background: rgba(var(--navy-rgb),.07); color: inherit;
    border-radius: 100px; padding: 1px 7px; min-width: 18px; text-align: center;
}
.runs-filter-tab:not(.active) .runs-filter-count { background: var(--bg-card); color: var(--text-muted); }

.runs-search {
    position: relative; display: flex; align-items: center;
    color: var(--text-muted);
}
.runs-search svg { position: absolute; left: 10px; pointer-events: none; }
.runs-search input {
    background: var(--bg-input); border: 1px solid var(--border);
    border-radius: var(--r-md); color: var(--text-primary);
    font-size: 13px; padding: 7px 12px 7px 30px; width: 240px; max-width: 100%;
    outline: none; transition: border-color .15s;
}
.runs-search input:focus { border-color: var(--azure-blue); }
.runs-search input::placeholder { color: var(--text-muted); }

@media (max-width: 640px) {
    .runs-toolbar { flex-direction: column; align-items: stretch; }
    .runs-search input { width: 100%; }
    /* Let the runs table scroll horizontally instead of overflowing the viewport */
    .pipeline-table-wrapper { overflow-x: auto; }
    .pipeline-table { min-width: 620px; }
}

/* The top bar is sticky and 64px tall, so anything scrolled to by id lands underneath it
   unless the target reserves that height first. Applies to the failure banner's jump and
   to anyone landing on a /dashboard#recent-runs link. */
[id] { scroll-margin-top: calc(var(--topbar-h) + var(--sp-md)); }

/* ── Data viz: cost trend + outcome donut ──────────────────────────────── */
.viz-grid {
    display: grid;
    grid-template-columns: 2fr 1fr;
    gap: var(--sp-md);
    margin-bottom: var(--sp-xl);
}
@media (max-width: 800px) { .viz-grid { grid-template-columns: 1fr; } }

/* min-width: 0 is what keeps the scrolling cost strip inside its own card.
   A grid item defaults to min-width: auto, which means "never shrink below my content",
   so the month wide strip pushed the card past its 2fr track and took the whole page
   into horizontal scroll with it. Zero lets the track do the sizing and leaves the
   overflow where it belongs, on .cost-scroll. */
.viz-card { padding: var(--sp-lg); min-width: 0; }
.viz-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: var(--sp-md); gap: var(--sp-sm); }
.viz-head h3 { margin: 0; font-size: 14px; font-weight: 600; color: var(--text-primary); }
.viz-sub { font-size: 11px; color: var(--text-muted); }
.viz-empty { color: var(--text-muted); font-size: 13px; padding: var(--sp-xl) 0; text-align: center; }

/* ── Cost trend: a month of days, stacked by work type, a week at a time ──────
   Laid out in CSS rather than SVG on purpose. The requirement is that exactly
   --cost-visible-days fill the card whatever it is wide, and a flex basis does that
   for free where an SVG viewBox would need the container width measured in script.

   Work type slot colours. Validated as a set against this card's white surface for
   colour vision deficiency separation, normal vision separation and 3:1 contrast,
   in the order they stack. Worst adjacent pair is well clear of the floors, so the
   fills carry identity without needing a texture or a value printed on every bar.
   Assigned by work type and never by size, so a bar never repaints because a
   different type happened to lead that day. */
.viz-card {
    --wt-cover:   #0f7b5f;
    --wt-dev:     #6b3fa0;
    --wt-fix:     #d2601a;
    --wt-study:   #3573c4;
    --wt-explain: #b03060;
    /* Never a generated hue. Anything unlabelled is neutral and says so in the legend. */
    --wt-other:   #8092ad;
}

.cost-legend {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--sp-sm) var(--sp-md);
    margin-bottom: var(--sp-sm);
}
/* Sits at the far end of the legend row and names the gridline the bars top out against. */
.cost-scale { margin-left: auto; font-size: 11px; color: var(--text-muted); }

.cost-scroll {
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: thin;
    padding-bottom: 2px;
}
.cost-scroll:focus-visible { outline: 2px solid var(--azure-blue); outline-offset: 2px; }

.cost-days {
    display: flex;
    align-items: flex-end;
    /* One month wide. Each column is a fraction of the VISIBLE width, so the strip
       overflows its container by exactly the days that do not fit. */
    min-width: 100%;
}

.cost-day {
    flex: 0 0 calc(100% / var(--cost-visible-days, 7));
    display: flex; flex-direction: column; align-items: center;
    gap: 6px;
}

/* The plot area. A fixed height is what makes the bars comparable between days: the
   stack's own height is a percentage of it, and the percentage is the data. */
.cost-plot {
    height: 120px;
    width: 100%;
    display: flex; align-items: flex-end; justify-content: center;
    /* Baseline below, top of scale above, both solid hairlines one shade off the card.
       The columns sit flush, so the per-column borders read as two continuous rules. */
    border-bottom: 1px solid var(--border);
    border-top: 1px solid var(--border-light);
}

.cost-stack {
    width: 58%; max-width: 34px; min-height: 2px;
    display: flex; flex-direction: column-reverse;
    /* A 2px gap of the card's own surface between segments, so two fills never touch
       and read as one block. */
    gap: 2px;
    /* Rounded at the data end only. The baseline end stays square because that edge is
       the axis, and rounding it would lift the bar off its own zero. */
    border-radius: 4px 4px 0 0;
    overflow: hidden;
    transition: opacity .15s;
}
.cost-day:hover .cost-stack { opacity: .82; }

.cost-seg {
    display: block;
    flex-basis: 0; flex-shrink: 1;
    min-height: 3px;
}

/* Date over weekday, both in text tokens. The series colour lives in the bars and the
   legend dots, never in the type. */
.cost-day-label {
    display: flex; flex-direction: column; align-items: center; line-height: 1.25;
}
.cost-day-date { font-size: 11px; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.cost-day-dow  { font-size: 10px; color: var(--text-muted); }

/* Outcome donut */
.donut-wrap { display: flex; align-items: center; gap: var(--sp-lg); }
.donut { width: 120px; height: 120px; flex-shrink: 0; }
.donut-num { fill: var(--text-primary); font-size: 20px; font-weight: 700; font-family: var(--font); }
.donut-lbl { fill: var(--text-muted); font-size: 9px; text-transform: uppercase; letter-spacing: .5px; font-family: var(--font); }
.donut-legend { display: flex; flex-direction: column; gap: 8px; }
.legend-item { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text-secondary); }
.legend-item strong { color: var(--text-primary); margin-left: auto; font-variant-numeric: tabular-nums; }
.legend-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; }

/* ── Accessibility: respect reduced-motion preference ──────────────────── */
/* Users who set "reduce motion" in their OS get a calm, static UI: no idle
   wave, no spinning icons, no pulsing rings. Functional state colours remain. */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}

/* ── Inline SVG icons ─────────────────────────────────────────────────────
   Replaced the emoji set. Icons inherit currentColor, so a status colour on
   the parent applies to the glyph without extra rules. Sized in em so they
   track the text they sit beside. */
.icon {
    display: inline-block;
    vertical-align: -0.15em;
    flex-shrink: 0;
}
.icon-inline { margin-right: 6px; }

/* Flow-diagram icons sit above the label, muted until the step is active */
.flow-node .icon {
    display: block;
    margin: 0 auto 6px;
    opacity: .55;
    transition: opacity .2s;
}
.flow-node-running .icon,
.flow-node-done    .icon { opacity: 1; }

/* Health rows: icon takes the row's status colour */
.health-row .icon { opacity: .9; }

/* ══════════════════════════════════════════════════════════════════════════
   Type scale and density
   The original ran at 11-14px throughout, which reads as cramped on a wide
   screen and was the main reason the UI felt low-quality. Everything below
   raises size and breathing room; nothing changes layout structure.
   ══════════════════════════════════════════════════════════════════════════ */

html, body { font-size: var(--fs-md); line-height: 1.55; }

/* ── Headings ─────────────────────────────────────────────────────────── */
.page-header h1     { font-size: var(--fs-3xl); font-weight: 700; letter-spacing: -.02em; }
.page-header p      { font-size: var(--fs-md);  color: var(--text-secondary); }
.section-header h2  { font-size: var(--fs-xl);  font-weight: 700; letter-spacing: -.01em; }
.viz-head h3,
.detail-card-title  { font-size: var(--fs-lg);  font-weight: 600; }

/* ── Stat cards: the headline numbers should dominate ─────────────────── */
.stat-card   { padding: var(--sp-lg); border-radius: var(--r-lg); }
.stat-label  { font-size: var(--fs-sm); font-weight: 500; color: var(--text-secondary);
               letter-spacing: .01em; text-transform: none; }
.stat-value  { font-size: var(--fs-3xl); font-weight: 700; letter-spacing: -.025em; line-height: 1.1; }
.stat-trend  { font-size: var(--fs-xs); color: var(--text-muted); }
.stat-value-cost { font-size: var(--fs-2xl); }

/* ── Tables: row height is what makes a grid feel considered ──────────── */
.pipeline-table          { font-size: var(--fs-sm); }
.pipeline-table th       { font-size: var(--fs-xs); font-weight: 600; letter-spacing: .04em;
                           text-transform: uppercase; color: var(--text-muted);
                           padding: 14px var(--sp-md); }
.pipeline-table td       { padding: 16px var(--sp-md); font-size: var(--fs-sm); }
.ticket-link             { font-size: var(--fs-sm); font-weight: 600; }
.duration, .mono         { font-size: var(--fs-sm); }

/* ── Badges and chips ─────────────────────────────────────────────────── */
.status-badge { font-size: var(--fs-xs); font-weight: 600; padding: 5px 12px;
                border-radius: var(--r-pill); letter-spacing: .01em; }
.live-badge   { font-size: var(--fs-xs); font-weight: 600; padding: 6px 14px;
                border-radius: var(--r-pill); }

/* ── Cards and sections ───────────────────────────────────────────────── */
.card    { border-radius: var(--r-lg); box-shadow: var(--shadow-card); }
.section { margin-top: var(--sp-2xl); }
.section-header { margin-bottom: var(--sp-md); }

/* ── Top bar ──────────────────────────────────────────────────────────── */
.brand-tagline { font-size: var(--fs-xs); }
.nav-link      { font-size: var(--fs-sm); font-weight: 500; padding: 8px 14px;
                 border-radius: var(--r-pill); }

/* ── Pipeline flow ────────────────────────────────────────────────────── */
.flow-node       { padding: 16px 18px; border-radius: var(--r-md); min-width: 108px; }
.flow-node-label { font-size: var(--fs-xs); font-weight: 600; line-height: 1.35; }
.flow-arrow      { font-size: var(--fs-lg); color: var(--text-muted); }

/* ── Buttons: the site's pill shape, at a size that is comfortable ────── */
.btn-primary, .btn-secondary, .btn-approve, .btn-reject {
    font-size: var(--fs-sm); font-weight: 600;
    padding: 11px 22px; border-radius: var(--r-pill);
}

/* ── Detail page ──────────────────────────────────────────────────────── */
.detail-card      { border-radius: var(--r-lg); padding: var(--sp-lg); }
.detail-card-body { font-size: var(--fs-sm); line-height: 1.6; }
.kv-key           { font-size: var(--fs-sm); color: var(--text-secondary); }
.kv-val           { font-size: var(--fs-sm); font-weight: 500; }

/* ── Health rows ──────────────────────────────────────────────────────── */
.service-item  { padding: 16px var(--sp-md); }
.service-name  { font-size: var(--fs-md); font-weight: 500; }
.service-label { font-size: var(--fs-sm); }

/* ── In-flight stage ──────────────────────────────────────────────────────────
   A run takes 10 to 20 minutes, so "Running" on its own is not much of an answer.
   The current step sits under the status badge in the table, and gets a progress
   bar on the detail page. */

.stage-line {
    display: block;
    margin-top: 6px;
    font-size: var(--fs-xs);
    color: var(--text-muted);
    letter-spacing: 0.01em;
}

.stage-line::before {
    content: "";
    display: inline-block;
    width: 6px;
    height: 6px;
    margin-right: 7px;
    border-radius: 50%;
    background: var(--s-warn);
    vertical-align: middle;
    animation: stage-pulse 1.8s ease-in-out infinite;
}

@keyframes stage-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.25; }
}

/* A decided pull request: merged or abandoned. Nothing is in flight, so the dot
   holds still and drops the warning colour. */
.stage-line.settled::before {
    background: var(--text-muted);
    animation: none;
}

/* Respect a reduced-motion preference: the dot still marks the row, it just
   stops moving. */
@media (prefers-reduced-motion: reduce) {
    .stage-line::before { animation: none; }
}

.stage-panel {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 18px 20px;
    margin-bottom: 20px;
    background: var(--s-warn-bg);
    border: 1.5px solid var(--s-warn-br);
    border-radius: var(--r-md);
}

.stage-panel-text { flex: 1; min-width: 0; }

.stage-panel-label {
    font-size: var(--fs-md);
    font-weight: 600;
    color: var(--text-primary);
}

.stage-panel-sub {
    margin-top: 3px;
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

.stage-track {
    margin-top: 10px;
    height: 6px;
    background: var(--border);
    border-radius: var(--r-pill);
    overflow: hidden;
}

.stage-fill {
    height: 100%;
    background: var(--s-warn);
    border-radius: var(--r-pill);
    transition: width 0.6s ease;
}

/* ── Detail page: states that are neither pass nor fail ──────────────────────
   A run that has not reached a gate yet is not a failure. These read as neutral
   so an in-flight run never looks broken. */
.kv-pending {
    color: var(--text-muted);
    font-style: normal;
}

.work-badge {
    display: inline-block;
    padding: 3px 12px;
    border-radius: var(--r-pill);
    font-size: var(--fs-xs);
    font-weight: 600;
    text-transform: capitalize;
    color: var(--navy);
    background: var(--sky-tint);
    border: 1px solid var(--sky);
}

/* == Brand mark easter egg ==================================================
   Five clicks on the top bar lockup inside four seconds. Driven by
   wwwroot/js/easter-egg.js, which appends .egg-backdrop to the body.

   Every rule below rests in its final visible state and uses animation only
   for the flourish, so the reduced-motion block above lands users straight on
   a static, fully legible card rather than on a frame that never arrives. */

/* The block is the target, so rapid clicking must not start selecting the text
   in it: a drag-select swallows the clicks that follow. */
.brand { cursor: pointer; user-select: none; -webkit-user-select: none; }

/* Grow the lockup's hit box to a 44px touch target without moving anything: the
   padding is cancelled by an equal negative margin. content-box is required
   because the sheet sets border-box globally, which would otherwise shrink the
   34px artwork to fit the padding instead of growing the box around it. */
.brand-icon {
    box-sizing: content-box;
    padding: 8px;
    margin: -8px;
    /* The lockup is an img, and a press that turns into a drag never becomes a
       click, which quietly ate clicks part way through a sequence. */
    -webkit-user-drag: none;
}

.brand-icon-nudge { animation: brand-wobble 0.35s ease; }

@keyframes brand-wobble {
    0%   { transform: rotate(0deg)  scale(1);    }
    30%  { transform: rotate(-9deg) scale(1.12); }
    60%  { transform: rotate(9deg)  scale(1.12); }
    100% { transform: rotate(0deg)  scale(1);    }
}

.egg-backdrop {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    background: rgba(var(--navy-rgb), 0.45);
    backdrop-filter: blur(3px);
    cursor: pointer;
    animation: egg-fade 0.2s ease;
}

@keyframes egg-fade { from { opacity: 0; } to { opacity: 1; } }

.egg-card {
    background: var(--bg-card);
    border: 1px solid var(--sky);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-md);
    padding: var(--sp-xl) var(--sp-2xl);
    text-align: center;
    max-width: 420px;
    animation: egg-pop 0.4s cubic-bezier(.18, 1.3, .5, 1);
}

@keyframes egg-pop {
    from { opacity: 0; transform: scale(0.82) translateY(12px); }
    to   { opacity: 1; transform: none; }
}

.egg-mark {
    display: block;
    width: auto; height: 56px;
    margin: 0 auto var(--sp-md);
    animation: egg-bob 1.6s ease-in-out infinite;
}

@keyframes egg-bob {
    0%, 100% { transform: translateY(0)    rotate(-3deg); }
    50%      { transform: translateY(-8px) rotate(3deg);  }
}

.egg-kicker {
    margin: 0 0 var(--sp-xs);
    font-size: var(--fs-xs);
    font-weight: 600;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--text-muted);
}

.egg-title {
    margin: 0 0 var(--sp-sm);
    font-size: 30px;
    font-weight: 800;
    letter-spacing: -.02em;
    color: var(--navy);
}

.egg-line {
    margin: 0 0 var(--sp-lg);
    font-size: var(--fs-md);
    color: var(--text-secondary);
}

/* Reuses .btn-primary for its shape and colour, so the card's one control is
   the same pill button as everywhere else on the dashboard. */
.egg-close { margin-bottom: var(--sp-sm); }

.egg-hint {
    margin: 0;
    font-size: var(--fs-xs);
    color: var(--text-muted);
}

/* Confetti - absolutely positioned bits falling the height of the backdrop.
   Pointer events off, so the click that dismisses the card always lands. */
.egg-confetti {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.egg-confetti span {
    position: absolute;
    top: -16px;
    width: 9px;
    height: 14px;
    border-radius: 2px;
    opacity: 0;
    animation-name: egg-fall;
    animation-timing-function: linear;
    animation-fill-mode: forwards;
}

@keyframes egg-fall {
    0%   { opacity: 1; transform: translateY(0) rotate(0deg); }
    100% { opacity: 0; transform: translateY(102vh) rotate(540deg); }
}
