/* The shell the dashboard fills in. Mobile first, because the glance the console exists
   for happens on a phone. Three surfaces do the layering: the canvas behind everything,
   a card for each thing that can be read on its own, and a sunken well for output that
   was captured rather than written. */

:root {
  --canvas: #f1f3f6;
  --surface: #ffffff;
  --sunken: #f5f7fa;
  --ink: #14171c;
  --quiet: #5d6673;
  --edge: #e0e4ea;
  --accent: #4f46e5;
  --good: #15774a;
  --warn: #97600a;
  --wrong: #b32b1c;

  --radius: 14px;
  --radius-sm: 9px;
  --lift: 0 1px 2px rgb(16 20 28 / 6%), 0 1px 3px rgb(16 20 28 / 4%);
  --lift-high: 0 2px 4px rgb(16 20 28 / 6%), 0 8px 24px rgb(16 20 28 / 8%);
  --measure: 68rem;
  --pad: clamp(1rem, 3vw, 1.5rem);

  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --canvas: #0d0f13;
    --surface: #171a20;
    --sunken: #12151a;
    --ink: #e6e9ef;
    --quiet: #949dab;
    --edge: #262b34;
    --accent: #8f90f8;
    --good: #4ec98a;
    --warn: #e0ab4c;
    --wrong: #f2836e;

    --lift: 0 1px 2px rgb(0 0 0 / 40%);
    --lift-high: 0 2px 6px rgb(0 0 0 / 40%), 0 12px 32px rgb(0 0 0 / 34%);
  }
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0 0 4rem;
  background: var(--canvas);
  color: var(--ink);
  font: 1rem/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--accent);
  text-decoration-color: color-mix(in srgb, currentcolor 35%, transparent);
  text-underline-offset: 2px;
}

/* Drawn outside the shape rather than in it, so a rounded control keeps its corners */
:where(a, button, input, select, textarea):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The bar across the top: where you are, where else you can go, and the way out */

.topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--edge);
}

.bar {
  display: flex;
  align-items: center;
  gap: 0.75rem 1rem;
  flex-wrap: wrap;
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0.7rem var(--pad);
}

.mark {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--ink);
  font-weight: 650;
  letter-spacing: -0.015em;
  text-decoration: none;
}

.mark .logo {
  width: 1.5rem;
  height: 1.5rem;
  padding: 0.24rem;
  border-radius: 7px;
  background: linear-gradient(140deg, var(--accent), color-mix(in srgb, var(--accent) 50%, #06b6d4));
  color: #fff;
}

nav {
  display: flex;
  gap: 0.15rem;
  margin-right: auto;
}

nav a {
  padding: 0.35rem 0.7rem;
  border-radius: 999px;
  color: var(--quiet);
  font-size: 0.9rem;
  font-weight: 550;
  text-decoration: none;
  transition: background 120ms ease, color 120ms ease;
}

nav a:hover {
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  color: var(--ink);
}

nav a.here {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
}

main {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 0 var(--pad);
}

/* h1 names the page, h2 names a card, h3 labels a block inside one */

h1 {
  font-size: clamp(1.5rem, 4vw, 1.85rem);
  font-weight: 660;
  letter-spacing: -0.022em;
  margin: 1.75rem 0 1.25rem;
}

h2 {
  font-size: 1.05rem;
  font-weight: 640;
  letter-spacing: -0.012em;
  margin: 2rem 0 0.75rem;
}

h3 {
  font-size: 0.72rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--quiet);
  margin: 1.25rem 0 0.4rem;
}

/* A card is one thing that can be read on its own. What used to be a heading over a bare
   list is a heading over a card, so the eye has an edge to stop at. */

.card {
  background: var(--surface);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
  box-shadow: var(--lift);
  padding: var(--pad);
}

.card > :first-child {
  margin-top: 0;
}

.card > :last-child {
  margin-bottom: 0;
}

/* Whether the loop is alive, said before any job is. A live dot, because a number that
   stopped moving looks the same as one that is moving slowly. */

.scheduler {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0 0 1rem;
  padding: 0.7rem 0.95rem;
  border: 1px solid var(--edge);
  border-radius: var(--radius-sm);
  background: var(--surface);
  box-shadow: var(--lift);
  color: var(--quiet);
  font-size: 0.88rem;
}

.scheduler::before {
  content: "";
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: currentcolor;
}

.scheduler.running {
  color: var(--good);
}

.scheduler.running::before {
  box-shadow: 0 0 0 0 color-mix(in srgb, currentcolor 60%, transparent);
  animation: pulse 2.4s ease-out infinite;
}

.scheduler.starting {
  color: var(--warn);
}

.scheduler.halted {
  color: var(--wrong);
  font-weight: 600;
  border-color: color-mix(in srgb, var(--wrong) 40%, var(--edge));
  background: color-mix(in srgb, var(--wrong) 7%, var(--surface));
}

@keyframes pulse {
  70% { box-shadow: 0 0 0 0.5rem transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .scheduler.running::before {
    animation: none;
  }
}

/* The headline: four counts, so the question the console is opened with is answered
   before a single job has been read */

.tally {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: 0.75rem;
  margin: 0 0 1.25rem;
  padding: 0;
  list-style: none;
}

.tile {
  background: var(--surface);
  border: 1px solid var(--edge);
  border-radius: var(--radius-sm);
  box-shadow: var(--lift);
  padding: 0.8rem 0.95rem;
}

.tile .count {
  display: block;
  font-size: 1.7rem;
  font-weight: 660;
  line-height: 1.15;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.tile .stamp {
  display: block;
  font-size: 0.95rem;
  font-weight: 600;
  line-height: 1.35;
  font-variant-numeric: tabular-nums;
}

.tile .caption {
  display: block;
  margin-top: 0.15rem;
  color: var(--quiet);
  font-size: 0.72rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* Nothing needing attention is the good news, so it is coloured as such rather than left
   as a bare nought */
.tile.wanted .count {
  color: var(--wrong);
}

.tile.wanted.clear .count {
  color: var(--good);
}

/* An automation and the pipeline its jobs form. The heading names the area and the
   service it drives; the stages beneath it run down the page on a phone and left to right
   where the measure has room for them. */

.area {
  margin-bottom: 1.75rem;
}

.area h2 {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}

.vendor {
  color: var(--quiet);
  font-size: 0.72rem;
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.pipeline {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
}

.stage {
  display: flex;
  flex-direction: column;
}

/* Generated rather than marked up: which way the arrow points is a fact about the
   layout, and turns with it. */
.stage + .stage::before {
  content: "↓";
  align-self: center;
  padding: 0.3rem 0;
  color: var(--quiet);
  font-size: 0.9rem;
}

@media (min-width: 52rem) {
  .pipeline {
    flex-direction: row;
    align-items: stretch;
  }

  .stage {
    flex: 1;
    flex-direction: row;
    align-items: center;
    min-width: 0;
  }

  .stage + .stage::before {
    content: "→";
    padding: 0 0.6rem;
  }

  .job {
    align-self: stretch;
  }
}

/* One stage: the same card the rest of the console is built from */

.job {
  position: relative;
  overflow: hidden;
  flex: 1;
  min-width: 0;
  padding: 0.9rem 1rem;
  background: var(--surface);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
  box-shadow: var(--lift);
  transition: box-shadow 140ms ease, transform 140ms ease;
}

.job:hover {
  box-shadow: var(--lift-high);
  transform: translateY(-1px);
}

/* The stripe is the band, drawn rather than bordered so it follows the rounded corner */
.job.band-0::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--wrong);
}

.job.band-2 {
  background: color-mix(in srgb, var(--surface) 65%, var(--canvas));
}

.job.band-2 .name {
  color: var(--quiet);
}

.job .head {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: 0.6rem;
  margin-bottom: 0.35rem;
}

.job .name {
  font-size: 1.02rem;
  font-weight: 620;
  letter-spacing: -0.012em;
  color: var(--ink);
  text-decoration: none;
}

/* The whole card follows the name, so a thumb has the card rather than the words */
.job .name::after {
  content: "";
  position: absolute;
  inset: 0;
}

.job .name:hover {
  color: var(--accent);
}

/* The two meta lines sit under a rule, because when it ran and when it runs next are a
   different kind of fact from what it is called */
.job .times {
  margin-top: 0.55rem;
  padding-top: 0.55rem;
  border-top: 1px solid var(--edge);
}

.job .when,
.job .due {
  display: block;
  font-variant-numeric: tabular-nums;
}

/* A badge on a card sits above the stretched link, or it stops being clickable */
.job .badge {
  position: relative;
  z-index: 1;
}

.empty {
  color: var(--quiet);
}

/* An outcome is a shape and a colour before it is a word, and the shape is what carries
   it where the colour does not arrive */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.18rem 0.6rem;
  border: 1px solid color-mix(in srgb, currentcolor 28%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, currentcolor 11%, transparent);
  font-size: 0.78rem;
  font-weight: 620;
  text-decoration: none;
  white-space: nowrap;
}

a.badge:hover {
  background: color-mix(in srgb, currentcolor 20%, transparent);
}

.badge.ok {
  color: var(--good);
}

.badge.failed,
.badge.missed {
  color: var(--wrong);
}

.badge.aborted {
  color: var(--warn);
}

.badge.skipped,
.badge.never,
.badge.disabled {
  color: var(--quiet);
}

/* Small print: a key is a machine's word for something and is set as one */

.key {
  display: inline-block;
  padding: 0.05rem 0.4rem;
  border-radius: 5px;
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  color: var(--quiet);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.78rem;
}

.when,
.due,
.label,
.empty {
  color: var(--quiet);
  font-size: 0.85rem;
}

.refused {
  color: var(--wrong);
}

.guard {
  color: var(--warn);
}

.reason code {
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}

/* A row of facts about one thing, labels in a column narrow enough to scan down */

.facts {
  display: grid;
  grid-template-columns: minmax(6.5rem, max-content) 1fr;
  gap: 0.45rem 1.25rem;
  margin: 0;
}

.facts dt {
  padding-top: 0.1rem;
  color: var(--quiet);
  font-size: 0.78rem;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.facts dd {
  margin: 0;
  overflow-wrap: anywhere;
}

/* A key that is the whole of a value is still a chip, not a bar the width of the column */
.facts dd.key {
  justify-self: start;
}

.detail dd {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
}

.knobs .own {
  background: color-mix(in srgb, var(--accent) 13%, transparent);
  color: var(--accent);
}

/* Lists that are rows rather than cards: what a job produced, what it declares, and the
   times its schedule is about to make */

.artefacts,
.secrets,
.upcoming {
  list-style: none;
  margin: 0;
  padding: 0;
}

.artefacts li,
.secrets li {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.75rem;
  align-items: center;
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--edge);
}

.artefacts li:first-child,
.secrets li:first-child {
  padding-top: 0;
}

.artefacts li:last-child,
.secrets li:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

.upcoming li {
  padding: 0.32rem 0;
  font-variant-numeric: tabular-nums;
}

.upcoming li + li {
  border-top: 1px dashed var(--edge);
}

/* Run history: a run is one row, and the attempts before the last are indented under it
   so three retries read as one run rather than as three entries. */

.runs {
  display: grid;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.runs > li {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 0.7rem;
  align-items: center;
  padding: 0.75rem 0.9rem;
  background: var(--surface);
  border: 1px solid var(--edge);
  border-radius: var(--radius-sm);
  box-shadow: var(--lift);
}

.runs .when {
  font-variant-numeric: tabular-nums;
}

.summary-line {
  flex-basis: 100%;
  color: var(--quiet);
  font-size: 0.85rem;
}

.earlier {
  flex-basis: 100%;
  list-style: none;
  margin: 0.4rem 0 0;
  padding: 0 0 0 0.9rem;
  border-left: 2px solid var(--edge);
}

.earlier li {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.7rem;
  align-items: center;
  padding: 0.3rem 0;
}

/* The one form that is a toolbar rather than a column of settings, so its label sits
   beside its control instead of taking a line of its own */
.filter {
  gap: 0.6rem;
  margin: 0 0 1rem;
  padding: 0.7rem 0.9rem;
}

.filter label {
  width: auto;
  color: var(--quiet);
  font-size: 0.85rem;
  font-weight: 650;
}

.pager {
  display: flex;
  gap: 1rem;
  align-items: center;
  margin: 1.25rem 0;
}

/* One attempt: the automation's own sentence as a banner carrying the outcome's tint, so
   the common case is answered by the colour of the top of the page. */

.summary {
  display: flex;
  align-items: baseline;
  gap: 0.55rem;
  padding: 0.8rem 1rem;
  border-radius: var(--radius-sm);
  font-size: 1rem;
  font-weight: 550;
  line-height: 1.45;
  white-space: normal;
}

.captured {
  margin: 0;
  padding: 0.85rem 1rem;
  background: var(--sunken);
  border: 1px solid var(--edge);
  border-radius: var(--radius-sm);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* A bounded capture must never read as the whole of it */
.elided {
  display: block;
  margin: 0.6rem 0;
  padding: 0.15rem 0.5rem;
  border-radius: 5px;
  background: color-mix(in srgb, var(--warn) 14%, transparent);
  color: var(--warn);
  font-weight: 650;
}

/* Forms: one control per setting, laid out from the schema rather than by hand */

form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin: 0;
}

label {
  width: 100%;
}

input,
button,
select,
textarea {
  font: inherit;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--edge);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
}

input:hover,
select:hover,
textarea:hover {
  border-color: color-mix(in srgb, var(--ink) 22%, var(--edge));
}

input:focus,
select:focus,
textarea:focus {
  border-color: var(--accent);
}

textarea {
  min-width: 16rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.88rem;
}

button {
  cursor: pointer;
  font-weight: 600;
  box-shadow: var(--lift);
  transition: background 120ms ease, border-color 120ms ease;
}

button:hover {
  background: color-mix(in srgb, var(--ink) 5%, var(--surface));
}

/* The one button on a page that starts something is the one that looks like it */
button.go {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

button.go:hover {
  background: color-mix(in srgb, var(--accent) 85%, #000);
}

button.danger {
  border-color: color-mix(in srgb, var(--wrong) 45%, var(--edge));
  background: color-mix(in srgb, var(--wrong) 10%, var(--surface));
  color: var(--wrong);
}

button.danger:hover {
  background: color-mix(in srgb, var(--wrong) 18%, var(--surface));
}

.signout button {
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  background: transparent;
  box-shadow: none;
  color: var(--quiet);
  font-size: 0.85rem;
  font-weight: 550;
}

.controls,
.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
  margin: 1rem 0;
}

.settings {
  display: block;
  margin: 0;
}

/* The save sits clear of the last setting's rule rather than tight under it */
.settings .actions {
  margin-top: 1.1rem;
}

/* One automation's secrets after another's, inside the one card */
.secrets + h3 {
  margin-top: 1.6rem;
}

.field {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.9rem;
  align-items: center;
  padding: 0.65rem 0;
  border-bottom: 1px solid var(--edge);
}

.field:first-of-type {
  padding-top: 0;
}

.field label {
  width: 12rem;
  color: var(--quiet);
  font-size: 0.82rem;
  font-weight: 650;
}

.field input[type="checkbox"] {
  width: 1.1rem;
  height: 1.1rem;
  padding: 0;
  accent-color: var(--accent);
}

.problems {
  list-style: none;
  margin: 1rem 0;
  padding: 0.8rem 1rem;
  border: 1px solid color-mix(in srgb, var(--wrong) 45%, var(--edge));
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--wrong) 8%, var(--surface));
}

/* What a page says before it says anything else: a refusal, a lockout, or the warning
   over a control that writes to the outside world */

.notice {
  margin: 0 0 1rem;
  padding: 0.75rem 1rem;
  border: 1px solid color-mix(in srgb, var(--wrong) 40%, var(--edge));
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--wrong) 8%, var(--surface));
  color: var(--wrong);
}

/* A job's own page: what it is and what can be done to it, above the detail */

.lede {
  display: grid;
  gap: 0.85rem;
}

.standing {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
}

.card .controls,
.card .actions {
  margin: 0;
}

/* The expression itself, set larger than the small print, because it is the thing on the
   page most likely to be wrong */
.cron {
  margin: 0 0 0.75rem;
}

.cron .key {
  padding: 0.28rem 0.6rem;
  font-size: 0.95rem;
  color: var(--ink);
}

/* Waiting on a run that is still going. The spinner is the only thing on the console
   that says something is happening now rather than reporting something that has. */

.waiting {
  display: flex;
  gap: 0.9rem;
  align-items: center;
}

.waiting p {
  margin: 0 0 0.35rem;
}

.waiting p:last-child {
  margin-bottom: 0;
}

.spinner {
  flex: none;
  width: 1.4rem;
  height: 1.4rem;
  border: 2.5px solid color-mix(in srgb, var(--accent) 25%, transparent);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 900ms linear infinite;
}

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

@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation-duration: 3s;
  }
}

/* Signing in is the one page with nothing else on it, so it is centred rather than sat
   at the top of a page that is otherwise empty */

.gateway {
  max-width: 23rem;
  margin: 3.5rem auto;
}

.gateway h1 {
  margin-top: 0;
}

.gateway form {
  display: grid;
  gap: 0.65rem;
}

.gateway label {
  color: var(--quiet);
  font-size: 0.82rem;
  font-weight: 650;
}
