/* Additions to Pico. Everything not listed here is Pico's default on purpose. */

:root {
  --state-progress: #2563eb;
  --state-ok: #16a34a;
  --state-warn: #d97706;
  --state-fail: #dc2626;
}

/* An icon-only nav link. The glyph has to be larger than the body text beside
   it to read as an icon rather than as a stray character; the accessible name
   lives on the link's aria-label, since the glyph is not one. */
.icon-link {
  font-size: 1.3rem;
  line-height: 1;
}

/* A page heading and the one action that page offers, on the same line with the
   action pushed to the right. Both children have their own bottom margin zeroed
   so the row centres on the text rather than on the gap beneath it, and the row
   carries the spacing instead. Wraps to two lines on a narrow screen. */
.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  margin-bottom: var(--pico-block-spacing-vertical);
}

.page-head>hgroup,
.page-head>[role="button"] {
  margin-bottom: 0;
}

/* Buttons sit next to their explanatory text, so they should not span the
   form. Pico's default width depends on context; pin it. */
main form>button,
main form>[type="submit"] {
  width: auto;
}

/* Status badge: a coloured dot plus a label, used in the table and the
   detail header. In-progress states pulse. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  white-space: nowrap;
  font-size: 0.8rem;
  font-weight: 600;
}

.badge::before,
.dot {
  content: "";
  flex: none;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--dot, var(--pico-muted-color));
}

.dot {
  display: inline-block;
  margin-right: 0.5rem;
}

[data-state="crawling"],
[data-state="validating"] {
  --dot: var(--state-progress);
}

[data-state="review"],
[data-state="exported"] {
  --dot: var(--state-warn);
}

[data-state="validated"] {
  --dot: var(--state-ok);
}

[data-state="crawl_failed"] {
  --dot: var(--state-fail);
}

/* Warn, not fail: the crawl died and produced nothing, but a validation that
   found mismatches ran fine and handed you a list to act on. That is the same
   "needs you" as review and exported, so it gets their colour. */
[data-state="validation_failed"] {
  --dot: var(--state-warn);
}

[data-level="ok"] {
  --dot: var(--state-ok);
}

[data-level="warn"] {
  --dot: var(--state-warn);
}

[data-level="fail"] {
  --dot: var(--state-fail);
}

@media (prefers-reduced-motion: no-preference) {

  [data-state="crawling"]::before,
  [data-state="validating"]::before {
    animation: pulse 1.4s ease-in-out infinite;
  }
}

@keyframes pulse {
  50% {
    opacity: 0.25;
  }
}

/* Wizard progress. Purely an orientation aid; the state column is the truth. */
.steps {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 0 0 var(--pico-block-spacing-vertical);
  padding: 0;
  list-style: none;
  counter-reset: step;
  font-size: 0.8rem;
  color: var(--pico-muted-color);
}

.steps li {
  display: flex;
  align-items: center;
  gap: 0.45rem;
}

.steps li::before {
  counter-increment: step;
  content: counter(step);
  display: grid;
  place-items: center;
  width: 1.35rem;
  height: 1.35rem;
  border-radius: 50%;
  border: 1px solid currentColor;
  font-size: 0.7rem;
}

.steps li:not(:last-child)::after {
  content: "\203A";
  margin-left: 0.3rem;
  opacity: 0.4;
}

.steps li[data-complete] {
  color: var(--pico-color);
}

.steps li[aria-current="step"] {
  color: var(--state-progress);
  font-weight: 600;
}

.steps li[aria-current="step"]::before {
  background: var(--state-progress);
  border-color: var(--state-progress);
  color: var(--pico-card-background-color);
}

/* Done is the outcome, not a page. Every other step is somewhere you can go —
   a numbered circle offers that, and behind this one there is nothing. So it
   gets the result instead of a position. Must follow the aria-current rule:
   same specificity, so source order is what decides.

   Escaped rather than literal for the same reason as the ticks above: this file
   is full of them and one encoding accident should not silently blank a glyph. */
.steps li:last-child::before {
  content: "\1F389";
  background: none;
  border: 0;
  font-size: 1.05rem;
  /* Emoji carry their own colours and ignore `color`, so the muted state has to
     be a filter — otherwise the popper sits there at full brightness
     congratulating you on a migration that is still crawling. */
  filter: grayscale(1);
  opacity: 0.45;
}

/* `data-complete` never lands on the last step, because nothing follows it —
   arriving is the only thing that lights this up. */
.steps li:last-child[aria-current="step"]::before {
  filter: none;
  opacity: 1;
}

/* A completed step is a link back to what it recorded. It has to read as part
   of the stepper rather than as body copy, so it borrows the li's colour
   instead of Pico's link colour. */
.steps a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px dotted currentColor;
}

.steps a:hover,
.steps a:focus-visible {
  color: var(--pico-color);
  border-bottom-style: solid;
}

/* The step being read, when that is not the step the pipeline is on. Distinct
   from aria-current, which stays on the pipeline position: those are two
   different facts and collapsing them loses the one you came back to check. */
.steps li[data-viewing]::before {
  border-width: 2px;
  border-style: dashed;
}

/* Says which page you are on and how to get back. Muted: it is orientation,
   not a warning — nothing is wrong with looking at a finished step. */
.revisit {
  margin: -0.4rem 0 var(--pico-block-spacing-vertical);
  padding-left: 0.8rem;
  border-left: 2px solid var(--pico-muted-border-color);
  color: var(--pico-muted-color);
  font-size: 0.85rem;
}

/* Findings report: one row per postprocessor result, detail on demand. */
.findings {
  margin: 0;
  padding: 0;
}

/* Pico sets the marker on the li, so clearing it on the ul is not enough. */
.findings>li,
.pages li {
  list-style: none;
}

.findings>li+li {
  border-top: 1px solid var(--pico-muted-border-color);
}

.findings details {
  margin: 0;
  border: 0;
  padding: 0;
}

.findings summary {
  padding: 0.7rem 0;
}

.findings details>p {
  margin: 0 0 0.6rem 1.05rem;
  color: var(--pico-muted-color);
  font-size: 0.9rem;
}

.pages {
  margin: 0 0 0.8rem 1.05rem;
  padding: 0 0 0 0.8rem;
  border-left: 2px solid var(--pico-muted-border-color);
}

.pages li {
  margin: 0.15rem 0;
}

.pages code {
  font-size: 0.8rem;
}

/* The dashboard table is wider than a phone. Scroll the table, not the page. */
.table-wrap {
  overflow-x: auto;
}

/* One row per crawl stage: label, bar, count. A single combined bar would hide
   which stage is the slow one. */
.stage {
  display: grid;
  grid-template-columns: 7rem 1fr 5rem;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.35rem;
  font-size: 0.9rem;
}

.stage progress {
  margin: 0;
}

.stage-count {
  text-align: right;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  color: var(--pico-muted-color);
}

@media (max-width: 30rem) {
  .stage {
    grid-template-columns: 1fr auto;
  }

  .stage progress {
    grid-column: 1 / -1;
    order: 3;
  }
}

/* Outcome accent on the validation summary. */
article.success {
  border-left: 3px solid var(--state-ok);
}

article.failure {
  border-left: 3px solid var(--state-fail);
}

/* Buttons that answer the same question, on one line. The form wrapper exists
   only to carry the target, so it must not introduce a row of its own. */
.actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: var(--pico-block-spacing-vertical);
}

.actions form,
.actions button {
  margin: 0;
}

/* A finding with no detail and no pages: same rhythm as a summary row, but no
   disclosure arrow, because there is nothing to disclose. */
.finding-flat {
  margin: 0;
  padding: 0.7rem 0;
}

/* Scrape log. Fixed height so a long crawl does not push the page around, and
   its own scroll so the stream can pin to the bottom. */
.log {
  max-height: 18rem;
  overflow-y: auto;
  padding: 0.6rem 0.8rem;
  border-left: 2px solid var(--pico-muted-border-color);
  font-family: var(--pico-font-family-monospace);
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--pico-muted-color);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.log .log-end {
  margin-top: 0.4rem;
  color: var(--pico-color);
}

.logbox summary {
  padding: 0.7rem 0;
  font-size: 0.9rem;
}

/* Tick or cross beside each URL in a coverage finding. Findings that list
   something other than crawl results omit the mark entirely. */
.mark {
  display: inline-block;
  width: 1.1rem;
  font-weight: 700;
}

.mark[data-mark="ok"]::before {
  content: "\2713";
  color: var(--state-ok);
}

.mark[data-mark="fail"]::before {
  content: "\2717";
  color: var(--state-fail);
}

/* Not a verdict: these scraped fine, but nothing confirms they belong to this
   content type. A tick here would answer the wrong question. */
.mark[data-mark="unsure"]::before {
  content: "?";
  color: var(--state-warn);
}

/* A large site produces a long list; scroll it rather than the page. */
.pages {
  max-height: 20rem;
  overflow-y: auto;
}

/* A segmented control: two labels styled as buttons, driven by radios shrunk to
   a transparent pixel rather than display:none, so they keep focus and keyboard
   arrow navigation. Everything below the choices is revealed by a sibling rule,
   which is why the radios are direct children of .verdict, not of .choices. */
.verdict {
  position: relative;
  margin-bottom: 1rem;
}

.verdict>input[type="radio"] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
  /* Transparent is still clickable, and the pixel lands under the first
     button; without this it can swallow a click meant for the label. */
  pointer-events: none;
}

.choices {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* Pico's outline-button look in the contrast colour, borrowed rather than
   inherited: Pico scopes both its button rules and .contrast to real buttons,
   and a label cannot take [role=button] without losing its label semantics. */
.choices label {
  margin: 0;
  padding: var(--pico-form-element-spacing-vertical) var(--pico-form-element-spacing-horizontal);
  border: var(--pico-border-width) solid var(--pico-contrast);
  border-radius: var(--pico-border-radius);
  color: var(--pico-contrast);
  font-weight: var(--pico-font-weight);
  line-height: var(--pico-line-height);
  font-size: 1rem;
  text-align: center;
  cursor: pointer;
  user-select: none;
  transition: background-color var(--pico-transition), border-color var(--pico-transition), color var(--pico-transition);
}

/* Hover darkens the outline rather than filling it: a filled hover would be
   indistinguishable from the checked state. */
.choices label:hover {
  border-color: var(--pico-contrast-hover);
  color: var(--pico-contrast-hover);
}

#verdict-ok:checked~.choices label[for="verdict-ok"],
#verdict-redo:checked~.choices label[for="verdict-redo"] {
  background: var(--pico-contrast-background);
  border-color: var(--pico-contrast-border);
  color: var(--pico-contrast-inverse);
}

/* The radio itself is off-screen, so its focus ring has to be drawn here or
   keyboard users lose their place entirely. */
#verdict-ok:focus-visible~.choices label[for="verdict-ok"],
#verdict-redo:focus-visible~.choices label[for="verdict-redo"] {
  box-shadow: 0 0 0 var(--pico-outline-width) var(--pico-contrast-focus);
}

/* Indented so the revealed form reads as belonging to the choice above it
   rather than as a third peer of the two buttons. */
.verdict>.panel {
  display: none;
  margin-top: 1rem;
  padding-left: 1rem;
  border-left: 2px solid var(--pico-muted-border-color);
}

#verdict-ok:checked~#panel-ok,
#verdict-redo:checked~#panel-redo {
  display: block;
}

.verdict form {
  margin: 0;
}
