.pipeline-page {
  --pipeline-ok: #5bc54a;
  --pipeline-progress: #f4b942;
  --pipeline-failed: #c5221f;
  --pipeline-unobserved: #a8a8ad;
}

.pipeline-ribbon:not([hidden]),
.pipeline-banner {
  margin-bottom: 1.5rem;
  padding: 0.8rem 1.2rem;
  border: 1px solid var(--pipeline-progress);
}

.pipeline-banner--error {
  border-color: var(--pipeline-failed);
}

.pipeline-advisories {
  margin: 1.5rem 0;
}

.pipeline-advisories p {
  margin: 0.4rem 0;
}

.pipeline-group {
  border-top: 1px solid var(--border-muted-color);
}

.pipeline-group:first-child {
  border-top: 0;
}

.pipeline-group > h3 {
  margin: 1.6rem 0 0.4rem;
  font-size: 1.5rem;
  scroll-margin-top: 2rem;
}

.pipeline-row {
  display: grid;
  grid-template-columns: 19rem minmax(0, 1fr) 16rem;
  align-items: start;
  gap: 2rem;
  padding: 1.6rem 0;
}

.pipeline-source-meta {
  overflow-wrap: anywhere;
  color: var(--muted-text);
  font-size: 1.2rem;
}

.pipeline-row-advisory {
  color: var(--pill-degraded-bg);
  font-weight: 700;
}

.pipeline-row-body {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  min-width: 0;
}

/* The field: a band per measurement, a square per run, time to the right.
   Clicking it cycles which dimension owns the rows. */

.pipeline-viz {
  flex: 1;
  min-width: 0;
}

.pipeline-viz[role="group"] {
  cursor: pointer;
}

.pipeline-viz:focus-visible {
  outline: 2px solid var(--link-color);
  outline-offset: 3px;
}

.pipeline-field {
  display: flex;
  flex-direction: column;
  gap: var(--band-gap);
  min-width: 0;
}

/* The facet-row field is one grid: a gutter column of facet labels, then a
   column per run. Every node is a direct child placed by grid area, in
   reading order, so a run keeps its nodes when the window rolls it from the
   newer lane into the older — a lane is a set of rows, not a subtree. The
   row and column templates come from the script, which knows the facet and
   run counts. */

.pipeline-field--runs {
  display: grid;
  gap: var(--band-gap) var(--clumped-run-gap);
  align-items: center;
}

/* the lead labels sit on the squares below them, the times on the squares
   above them; the tracks carry the breathing room */
.pipeline-run-head,
.pipeline-field--runs .pipeline-run-label {
  align-self: end;
}

.pipeline-band {
  display: grid;
  grid-template-columns: var(--band-gutter) minmax(0, max-content);
  align-items: center;
  gap: 0 0.6rem;
}

.pipeline-band-label {
  overflow: hidden;
  color: var(--muted-text);
  font-size: 1rem;
  line-height: 1;
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pipeline-band[data-kind="lead"] .pipeline-band-label {
  color: var(--text-color);
}

.pipeline-cells {
  display: flex;
  gap: var(--run-gap);
}

/* a clumped band needs daylight between runs, since the squares within one run
   sit a single pixel apart */
.pipeline-cells[data-clumped] {
  gap: var(--clumped-run-gap);
}

.pipeline-clump {
  display: flex;
  flex: none;
  gap: var(--clump-gap);
}

.pipeline-cell {
  position: relative;
  flex: none;
  /* the lead dimension is proportional, so whichever axis it owns is set
     per cell; the other axis stays one cell */
  width: var(--cell-w, var(--sq));
  height: var(--cell-h, var(--sq));
  border: 1px solid var(--text-color);
}

.pipeline-cell-fill {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  height: var(--fill, 0%);
}

/* where lead time owns the horizontal axis, a cell fills from its left edge
   rather than its floor, so progress reads along the same axis as the leads */
.pipeline-field[data-fill="side"] .pipeline-cell-fill {
  top: 0;
  right: auto;
  width: var(--fill, 0%);
  height: auto;
}

.pipeline-cell.g-complete .pipeline-cell-fill {
  background: var(--pipeline-ok);
}

.pipeline-cell.g-complete[data-timing="delayed"] .pipeline-cell-fill,
.pipeline-cell.g-in_flight .pipeline-cell-fill {
  background: var(--pipeline-progress);
}

.pipeline-cell.g-in_flight[data-timing="on_time"] .pipeline-cell-fill {
  background: var(--pipeline-ok);
}

.pipeline-cell.g-failed .pipeline-cell-fill {
  height: 100%;
  background: var(--pipeline-failed);
}

.pipeline-field[data-fill="side"] .pipeline-cell.g-failed .pipeline-cell-fill {
  width: 100%;
}

/* an empty outline is "expected, nothing yet"; hatching is "no evidence either
   way" — the two must not read the same, since one is a measurement and the
   other is the absence of one */
.pipeline-cell.g-pending {
  border: 1px solid var(--pipeline-unobserved);
}

.pipeline-cell.g-pending[data-timing="delayed"] {
  border-color: var(--pipeline-progress);
}

.pipeline-cell.g-unobserved {
  border: 1px dotted var(--pipeline-unobserved);
  background: repeating-linear-gradient(
    135deg,
    var(--pipeline-unobserved) 0 1px,
    transparent 1px 4px
  );
}

.pipeline-cell.g-pending .pipeline-cell-fill,
.pipeline-cell.g-unobserved .pipeline-cell-fill {
  display: none;
}

/* The facet-row field: a run per block, lead groups as columns inside it */

.pipeline-column-label {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--cell-w, var(--sq));
  height: var(--label-h);
  font-size: 0.9rem;
  line-height: var(--label-h);
  text-align: center;
}

.pipeline-band--foot {
  margin-top: 0.3rem;
}

/* one per run block, exactly as wide as the block it names */
.pipeline-run-label,
.pipeline-run-date {
  /* never shrink, or a label drifts out from under the blocks it names */
  flex: none;
  /* exactly one run block wide, so both tiers centre on the same axis */
  width: var(--run-width);
  height: var(--label-h);
  font-size: 0.9rem;
  line-height: var(--label-h);
  white-space: nowrap;
  text-align: center;
}

.pipeline-run-label {
  color: var(--muted-text);
}

.pipeline-stats {
  color: var(--muted-text);
  font-size: 1.2rem;
  text-align: right;
}

.pipeline-stats strong,
.pipeline-stats span {
  display: block;
}

.pipeline-stats strong {
  color: var(--text-color);
}

.pipeline-stats [data-timing="on_time"] {
  color: var(--pipeline-ok);
}

.pipeline-stats [data-timing="delayed"] {
  color: var(--pipeline-progress);
}

.pipeline-details-button {
  margin-top: 0.8rem;
  padding: 0;
  border: 0;
  color: var(--link-color);
  background: none;
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}

.pipeline-row-details {
  grid-column: 1 / -1;
  /* each table scrolls in its own container, so the row must not stretch the
     grid to fit one */
  min-width: 0;
}

/* The details share the row's columns, so the chart that lines up with the
   lead-group field can sit in the field's own column; everything else spans
   the row. Open details only: the attribute hides closed ones. Without
   subgrid the details stay a block and that chart simply spans the row. */
@supports (grid-template-columns: subgrid) {
  .pipeline-row-details:not([hidden]) {
    display: grid;
    grid-template-columns: subgrid;
  }

  .pipeline-row-details > * {
    grid-column: 1 / -1;
  }

  .pipeline-row-details > .pipeline-runs[data-aligned] {
    grid-column: 2;
  }
}

.pipeline-row-details .table-container + .table-container {
  margin-top: 1.2rem;
}

/* The run chart: the runs the field draws as squares, plotted by their time
   after init against the current delayed threshold — under the lead-group
   field, each under its square. Color is the summarizer's verdict, where
   a square's is the run's status: green a run judged on time, amber one
   judged delayed, drawn larger as well so the verdict does not rest on
   color alone. A run with no verdict — a product short of history, a run
   too early to judge — is drawn in the text color, not a grey that reads
   as a third kind of run. A run not yet complete is hollow, at
   the time elapsed so far. The key beneath names the marks it shows. */

.pipeline-runs {
  margin: 0 0 1.6rem;
}

/* the chart across the row draws nothing for a product whose runs all lack
   a completion time, and leaves no room for it */
.pipeline-runs:empty {
  margin: 0;
}

.pipeline-runs svg {
  display: block;
  overflow: visible;
  font-size: 1rem;
}

.pipeline-runs text {
  fill: var(--muted-text);
}

.pipeline-runs line {
  stroke: var(--border-muted-color);
}

.pipeline-runs [data-axis="y"] line {
  stroke: var(--pill-muted-border);
}

.pipeline-runs circle {
  fill: var(--text-color);
  stroke: var(--text-color);
}

/* the summarizer's verdict; a run with none stays ink under a green square */
.pipeline-runs [data-timing="on_time"] {
  fill: var(--pipeline-ok);
  stroke: var(--pipeline-ok);
}

/* amber is a light token (1.77:1 on white), so a delayed mark is heavier as
   well as larger, and the verdict does not rest on its color */
.pipeline-runs [data-timing="delayed"] {
  fill: var(--pipeline-progress);
  stroke: var(--pipeline-progress);
  stroke-width: 2;
}

.pipeline-runs circle[data-elapsed] {
  fill: var(--bg-color);
  stroke-width: 1.5;
}

/* a delayed run still arriving is only a ring, so its ring carries the
   heavier stroke too */
.pipeline-runs circle[data-elapsed][data-timing="delayed"] {
  stroke-width: 2;
}

/* the line is today's run-level reference, not a verdict: muted and dashed,
   so amber on the chart only ever marks a run judged delayed, and its label
   is readable text rather than amber on white */
.pipeline-runs [data-threshold="run"] line {
  stroke: var(--muted-text);
  stroke-dasharray: 4 3;
}

.pipeline-runs [data-threshold="run"] text {
  fill: var(--muted-text);
}

/* the key starts where the plot does, and draws each mark as the chart
   does: filled, hollow, or amber and larger */
.pipeline-runs ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem 1.6rem;
  margin: 0.6rem 0 0 var(--plot-left, 0);
  padding: 0;
  color: var(--muted-text);
  font-size: 1rem;
  list-style: none;
}

/* the key's items sit on one line, spaced by the gap rather than by the
   prose list's stacking margin. main.css's classless-list rule has the same
   specificity (0,1,3); this wins because the page stylesheet loads after it */
.pipeline-runs ul > li + li {
  margin-top: 0;
}

.pipeline-runs li[data-mark]::before {
  content: "";
  display: inline-block;
  box-sizing: border-box;
  width: 0.8rem;
  height: 0.8rem;
  margin-right: 0.5rem;
  border: 0.15rem solid var(--text-color);
  border-radius: 50%;
  background: var(--text-color);
  vertical-align: -0.05rem;
}

.pipeline-runs li[data-mark^="delayed"]::before {
  width: 1rem;
  height: 1rem;
  border-width: 0.2rem;
  border-color: var(--pipeline-progress);
  background: var(--pipeline-progress);
  vertical-align: -0.15rem;
}

.pipeline-runs li[data-mark^="on-time"]::before {
  border-color: var(--pipeline-ok);
  background: var(--pipeline-ok);
}

/* hollow, as a run not yet complete is */
.pipeline-runs li:is([data-mark="elapsed"], [data-mark$="-elapsed"])::before {
  background: none;
}


.pipeline-row-details table {
  width: 100%;
  border-collapse: collapse;
  font-size: 1.2rem;
}

/* the lead table's ten or twelve nowrap measurement columns need more room than
   the content column has; the facet table's few fit, so it is sized by its
   content */
.pipeline-row-details .table-container:first-of-type table {
  min-width: 100rem;
}

.pipeline-row-details th,
.pipeline-row-details td {
  padding: 0.4rem 1.2rem;
  border: 1px dotted var(--border-muted-color);
  text-align: right;
  white-space: nowrap;
}

.pipeline-row-details th:first-child,
.pipeline-row-details td:first-child {
  text-align: left;
}

/* "horizon" spans both header rows, so the sub-header row starts in column 2 —
   its first cell is a status column and belongs right with the rest */
.pipeline-row-details thead:has(th[rowspan]) tr + tr th:first-child {
  text-align: right;
}

/* a status wears the color its square wears on the grid: green once a run has
   landed on time, amber while it is running or running late, red when it failed.
   pending and unobserved stay uncolored, as their empty squares do. */
.pipeline-row-details [data-status="complete"],
.pipeline-row-details [data-status="in_flight"][data-timing="on_time"] {
  color: var(--pipeline-ok);
}

.pipeline-row-details [data-status="in_flight"],
.pipeline-row-details [data-timing="delayed"] {
  color: var(--pipeline-progress);
}

.pipeline-row-details [data-status="failed"] {
  color: var(--pipeline-failed);
}

/* the bar takes the row's leftover width, and the number beside it is always
   three characters wide, so every bar starts and ends in the same place */
.pipeline-facets td:last-child {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
}

.pipeline-facets progress {
  flex: 1;
  accent-color: var(--pipeline-ok);
  vertical-align: middle;
}

.pipeline-facet-pct {
  flex: none;
  font-variant-numeric: tabular-nums;
}

.pipeline-facet-num {
  display: inline-block;
  width: 3ch;
  text-align: right;
}

.pipeline-time-local .pipeline-time-utc,
body:not(.pipeline-time-local) .pipeline-time-local-only {
  display: none;
}

@media (max-width: 900px) {
  .pipeline-row {
    grid-template-columns: 1fr;
  }

  .pipeline-row-details > .pipeline-runs[data-aligned] {
    grid-column: 1;
  }

  .pipeline-stats {
    text-align: left;
  }
}
