/* scrapeai — web UI.

   Achromatic by design. Every grey is zero-chroma, and the "accent" is not a
   hue at all: it is the ink itself, inverted. Near-black on white in light,
   near-white on near-black in dark. A primary button is therefore the darkest
   (or lightest) thing on the screen rather than the bluest, which is what
   makes it read as primary without spending colour on it.

   That leaves colour free to mean exactly one thing: state. Running, done and
   failed are the only things allowed to be coloured, so when you see a hue in
   this app it is telling you what happened to a run.

   Because nothing is tinted, links cannot be identified by colour — they carry
   an underline instead. Same reason the switch knob has its own token: on an
   inverted track, a fixed white knob disappears in one theme or the other. */

:root {
  color-scheme: light;

  --bg:        #fafafa;   /* page canvas */
  --sidebar:   #fafafa;   /* the rail, a shade off the cards it frames */
  --surface:   #ffffff;   /* cards */
  --surface-2: #f4f4f5;   /* wells, table heads, hover */
  --tint:      #f4f4f5;   /* quieter card, e.g. the settings panel */

  --ink:       #09090b;
  --ink-soft:  #52525b;
  --ink-faint: #71717a;

  --line:      #e4e4e7;
  --line-soft: #efeff1;

  /* Not a colour — the ink, inverted. See the note above. */
  --accent:     #18181b;
  --accent-hi:  #000000;
  --accent-in:  #fafafa;
  --accent-dim: #f4f4f5;   /* chips, hovers, tinted wells */
  --ring:       rgba(9, 9, 11, .16);   /* focus halo */
  /* Focused fields get this, not --accent. Because the accent here IS the ink,
     a focused input was outlined in near-black — a hard slab around every box
     you clicked into. This is grey enough to sit quietly and dark enough to
     clear the 3:1 an interactive border needs. */
  --focus:      #52525b;

  --knob:    #ffffff;   /* switch knob, off */
  --knob-on: #ffffff;   /* switch knob, on (track is dark here) */
  --sweep:   rgba(255, 255, 255, .45);  /* progress shimmer over a dark bar */

  --good: #15803d;  --good-dim: #dcfce7;
  --warn: #b45309;  --warn-dim: #fef3c7;
  --bad:  #dc2626;  --bad-dim:  #fee2e2;

  --r-lg: 12px;   /* cards */
  --r-md: 8px;    /* inputs, buttons */
  --r-sm: 6px;
  --r-pill: 999px;

  /* Nearly flat. The hairline does the separating; the shadow only lifts a
     card off the canvas enough to be noticed. */
  --shadow: 0 1px 2px rgba(9, 9, 11, .05);
  --shadow-pop: 0 10px 30px rgba(9, 9, 11, .12);

  --side: 232px;
  --top: 60px;

  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  --sans: Archivo, "Inter var", system-ui, -apple-system, "Segoe UI", Roboto,
          Helvetica, Arial, sans-serif;
}

/* Dark inverts the ink and the accent with it, so the same rules keep working:
   whatever was darkest is now lightest. Surfaces sit only a few steps apart
   and lean on their borders to separate, rather than climbing to a grey light
   enough to glow. Shadows are dropped entirely — a shadow on a dark canvas is
   invisible at best and a smudge at worst. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg:        #171717;
  --sidebar:   #1c1c1c;
  --surface:   #1c1c1c;
  --surface-2: #27272a;
  --tint:      #1f1f1f;
  --ink:       #fafafa;
  --ink-soft:  #a1a1aa;
  --ink-faint: #71717a;
  --line:      #27272a;
  --line-soft: #232326;
  --accent:     #fafafa;
  --accent-hi:  #ffffff;
  --accent-in:  #18181b;
  --accent-dim: #27272a;
  --ring:       rgba(250, 250, 250, .22);
  --focus:      #a1a1aa;
  --knob:    #d4d4d8;
  --knob-on: #18181b;   /* the track is near-white now, so the knob goes dark */
  --sweep:   rgba(0, 0, 0, .28);
  --good: #4ade80;  --good-dim: rgba(74, 222, 128, .14);
  --warn: #fbbf24;  --warn-dim: rgba(251, 191, 36, .14);
  --bad:  #f87171;  --bad-dim:  rgba(248, 113, 113, .14);
  --shadow: none;
  --shadow-pop: 0 10px 30px rgba(0, 0, 0, .7);
}

* { box-sizing: border-box; }

/* Half the elements here are shown and hidden with the `hidden` attribute, and
   every author `display` rule silently beats the UA's `[hidden]`. One rule, so
   hiding something always hides it. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.sprite { display: none; }
.ic {
  width: 18px; height: 18px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}

h1, h2 { letter-spacing: -0.022em; }

/* ── shell ───────────────────────────────────────────────── */
.shell { display: grid; grid-template-columns: var(--side) 1fr; min-height: 100vh; }

.sidebar {
  display: flex; flex-direction: column; gap: 6px;
  padding: 16px 14px;
  background: var(--sidebar);
  border-right: 1px solid var(--line);
  position: sticky; top: 0; height: 100vh;
}
.side-spacer { flex: 1; }

.brand { display: flex; align-items: center; gap: 10px; padding: 4px 6px 14px; }
.brand-mark {
  display: grid; place-items: center; width: 32px; height: 32px;
  border-radius: var(--r-md); background: var(--accent); color: var(--accent-in);
}
.brand-text { display: flex; flex-direction: column; line-height: 1.2; }
.brand-text strong { font-size: 15px; font-weight: 700; letter-spacing: -0.02em; }
.brand-text small { font-size: 11px; color: var(--ink-faint); }

.nav { display: flex; flex-direction: column; gap: 2px; margin-top: 14px; }
.nav-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px; width: 100%;
  background: none; border: none; border-radius: var(--r-md);
  color: var(--ink-soft); font: 500 13.5px/1 var(--sans); cursor: pointer;
  transition: background .12s, color .12s;
}
.nav-item:hover { background: var(--surface-2); color: var(--ink); }
/* A filled accent pill would be the darkest thing on the screen in light mode
   and the lightest in dark — too loud for something that is only saying "you
   are here". Weight and full-strength ink carry it instead. */
.nav-item.is-active {
  background: var(--surface-2); color: var(--ink); font-weight: 600;
}

.last-run {
  display: flex; flex-direction: column; gap: 2px; text-align: left;
  padding: 11px 12px; margin-bottom: 8px;
  background: var(--tint); border: 1px solid var(--line);
  border-radius: var(--r-md); cursor: pointer; color: inherit;
  transition: border-color .12s;
}
.last-run:hover { border-color: var(--accent); }
.last-run-name {
  font-size: 12.5px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.last-run-meta { font-size: 11px; color: var(--ink-faint); font-family: var(--mono); }

.side-foot { border-top: 1px solid var(--line-soft); padding-top: 8px; }
.side-link {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 7px 11px; border: none; background: none; border-radius: var(--r-md);
  color: var(--ink-faint); font: 400 12.5px/1 var(--sans);
  text-decoration: none; cursor: pointer;
}
.side-link:hover { background: var(--surface-2); color: var(--ink); }
.side-link .ic { width: 15px; height: 15px; }

/* ── main column ─────────────────────────────────────────── */
.main { display: flex; flex-direction: column; min-width: 0; }

.topbar {
  display: flex; align-items: center; gap: 16px;
  height: var(--top); padding: 0 24px;
  background: var(--surface); border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 20;
}
.search {
  display: flex; align-items: center; gap: 9px;
  flex: 1; max-width: 420px; padding: 7px 12px;
  background: var(--surface-2); border: 1px solid transparent;
  border-radius: var(--r-md); color: var(--ink-faint);
  transition: border-color .12s, background .12s;
}
.search:focus-within { border-color: var(--focus); background: var(--surface); }
.search input {
  flex: 1; min-width: 0; border: none; background: none; outline: none;
  font: 13.5px var(--sans); color: var(--ink);
}
.search input::placeholder { color: var(--ink-faint); }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }

.chip-live {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 11px; border-radius: var(--r-pill);
  background: var(--warn-dim); color: var(--warn);
  font-size: 12px; font-weight: 600;
}
.chip-live .dot {
  width: 6px; height: 6px; border-radius: 50%; background: currentColor;
  animation: pulse 1.4s ease-in-out infinite;
}
@keyframes pulse { 50% { opacity: .25; } }

.scroll { padding: 24px; max-width: 1500px; width: 100%; }

/* ── page head ───────────────────────────────────────────── */
.page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 20px; margin-bottom: 20px; flex-wrap: wrap;
}
.page-head h1 { margin: 0; font-size: 27px; font-weight: 700; }
.page-sub {
  margin: 6px 0 0; max-width: 62ch;
  color: var(--ink-soft); font-size: 13.5px; line-height: 1.55;
}
.head-actions { display: flex; align-items: center; gap: 10px; }

.eyebrow {
  display: flex; align-items: center; gap: 10px; margin: 0 0 6px;
  font-size: 11.5px; color: var(--ink-faint); font-family: var(--mono);
}
.state {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--sans); font-weight: 700; font-size: 11px;
  letter-spacing: .07em; text-transform: uppercase;
  padding: 3px 9px; border-radius: var(--r-pill);
  background: var(--surface-2); color: var(--ink-faint);
}
.state .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.state[data-s="done"]    { background: var(--good-dim); color: var(--good); }
.state[data-s="failed"]  { background: var(--bad-dim);  color: var(--bad); }
.state[data-s="running"] { background: var(--warn-dim); color: var(--warn); }
.state[data-s="cancelled"] { background: var(--surface-2); color: var(--ink-faint); }
.state[data-s="running"] .dot { animation: pulse 1.4s ease-in-out infinite; }

.badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: var(--r-pill);
  font-size: 12px; font-weight: 600; white-space: nowrap;
}
.badge .ic { width: 14px; height: 14px; }
.badge-accent { background: var(--accent-dim); color: var(--accent); }

/* ── buttons ─────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 9px 16px; border-radius: var(--r-md);
  font: 600 13.5px/1 var(--sans); cursor: pointer; white-space: nowrap;
  border: 1px solid transparent; transition: .12s;
  /* Feed is an <a> wearing .btn, and a button that underlines itself looks
     like a mistake. */
  text-decoration: none;
}
.btn .ic { width: 16px; height: 16px; }
.btn-primary {
  background: var(--accent); color: var(--accent-in);
  box-shadow: var(--shadow);
}
.btn-primary:hover { background: var(--accent-hi); }
.btn-primary:disabled { opacity: .55; cursor: progress; }
.btn-ghost {
  background: var(--surface); color: var(--ink-soft); border-color: var(--line);
}
.btn-ghost:hover { border-color: var(--ink-faint); color: var(--ink); }
.btn-ghost:disabled { opacity: .5; cursor: not-allowed; }
.btn-block { width: 100%; padding: 11px; }
select.btn-ghost { appearance: none; padding-right: 30px; }

.link {
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--accent); font: 600 12.5px var(--sans);
}
.link:hover { text-decoration: underline; }

.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.chip {
  padding: 5px 11px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px solid var(--line-soft);
  color: var(--ink-soft); font: 500 12px var(--sans); cursor: pointer;
  transition: .12s;
}
.chip:hover { border-color: var(--accent); color: var(--accent); }

/* ── cards ───────────────────────────────────────────────── */
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--shadow);
  margin-bottom: 16px; overflow: hidden;
}
.card-pad { padding: 18px 20px; }
.card-title {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 4px; font-size: 15px; font-weight: 700;
}
.card-title .ic { width: 16px; height: 16px; color: var(--accent); }
.card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 15px 20px; border-bottom: 1px solid var(--line-soft);
  flex-wrap: wrap;
}
.card-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; padding: 12px 20px;
  background: var(--surface-2); border-top: 1px solid var(--line-soft);
  flex-wrap: wrap;
}
.card-tint { background: var(--tint); }
.row-between {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 8px;
}

.micro-label {
  display: block; font-size: 10.5px; font-weight: 700;
  letter-spacing: .09em; text-transform: uppercase; color: var(--ink-faint);
  margin-bottom: 8px;
}
.hint, .note { color: var(--ink-faint); font-size: 12.5px; line-height: 1.5; }
.hint { display: block; margin: 8px 0 0; }
.counter { font: 11.5px var(--mono); color: var(--ink-faint); }

/* ── layout grids ────────────────────────────────────────── */
.grid-main {
  display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 16px;
  align-items: start;
}
.col-side { position: sticky; top: calc(var(--top) + 24px); }

/* ── form controls ───────────────────────────────────────── */
.input-box {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 13px; border: 1px solid var(--line);
  border-radius: var(--r-md); background: var(--surface);
  color: var(--ink-faint); transition: border-color .12s, box-shadow .12s;
}
/* The focus halo used to be a pale blue. A pale grey would be invisible, so
   focus is a mid-grey border plus a neutral ring at real opacity — enough to
   be unmistakable without outlining the field in solid black. */
.input-box:focus-within {
  border-color: var(--focus); box-shadow: 0 0 0 3px var(--ring);
}
.input-box input {
  flex: 1; min-width: 0; border: none; background: none; outline: none;
  font: 13.5px var(--mono); color: var(--ink);
}
/* The box around it already draws the focus ring. Without this the generic
   input:focus rule draws a second one inside the first, and the field looks
   double-outlined. */
.input-box input:focus { box-shadow: none; }

textarea, input[type=text], input[type=number], select {
  width: 100%; padding: 10px 13px;
  font: 14px/1.55 var(--sans); color: var(--ink);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); outline: none;
  transition: border-color .12s, box-shadow .12s;
}
textarea { resize: vertical; min-height: 108px; }
input::placeholder, textarea::placeholder { color: var(--ink-faint); }
textarea:focus, input:focus, select:focus {
  border-color: var(--focus); box-shadow: 0 0 0 3px var(--ring);
}
select {
  appearance: none; padding-right: 32px; cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 16px center, right 11px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

/* switches — the three settings most runs actually touch */
.switch-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; padding: 11px 0; border-bottom: 1px solid var(--line-soft);
  cursor: pointer;
}
.switch-row strong { display: block; font-size: 13px; font-weight: 600; }
.switch-row small {
  display: block; font-size: 11.5px; color: var(--ink-faint);
  line-height: 1.45; margin-top: 1px;
}
.switch { position: relative; flex: none; width: 38px; height: 22px; }
.switch input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.switch i {
  position: absolute; inset: 0; border-radius: var(--r-pill);
  background: var(--line); transition: background .16s;
}
.switch i::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--knob); box-shadow: 0 1px 2px rgba(0, 0, 0, .25);
  transition: transform .16s, background .16s;
}
.switch input:checked + i { background: var(--accent); }
/* The track flips to near-white in dark mode, so the knob has to flip too or
   it vanishes into the thing it is sitting on. */
.switch input:checked + i::after { background: var(--knob-on); transform: translateX(16px); }
.switch input:focus-visible + i { box-shadow: 0 0 0 3px var(--ring); }

.disclosure {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; margin-top: 14px; padding: 8px;
  background: none; border: none; border-radius: var(--r-md);
  color: var(--ink-soft); font: 600 12.5px var(--sans); cursor: pointer;
}
.disclosure:hover { background: var(--surface-2); color: var(--ink); }
.disclosure .ic { width: 15px; height: 15px; transition: transform .16s; }
.disclosure[aria-expanded="true"] .ic { transform: rotate(180deg); }

.opts { display: grid; gap: 14px; }
.opts + .opts { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line-soft); }
.opt {
  display: grid; grid-template-columns: 1fr 110px; gap: 10px;
  align-items: center; font-size: 13px; color: var(--ink-soft);
}
.opt small { color: var(--ink-faint); }
.opt input, .opt select { padding: 7px 10px; font-size: 13px; }
.opt.check { grid-template-columns: auto 1fr; align-items: start; gap: 10px; }
.opt.check input { width: auto; margin: 2px 0 0; accent-color: var(--accent); }
.opt.check small { display: block; font-size: 11.5px; margin-top: 2px; line-height: 1.45; }

.alert {
  display: block; margin: 0 0 16px; padding: 12px 15px;
  border-radius: var(--r-md); font-size: 13px; line-height: 1.55;
  white-space: pre-wrap; word-break: break-word;
}
.alert-bad { background: var(--bad-dim); color: var(--bad); border: 1px solid transparent; }

/* Notices: things the run knew but the export cannot show — skipped pages,
   empty columns, removed duplicates. Collapsed by default because a clean run
   should not nag, open-able because a short run is worth reading. */
.notices {
  margin-bottom: 16px; padding: 0 15px;
  background: var(--warn-dim); border-radius: var(--r-md);
}
.notices summary {
  display: flex; align-items: center; gap: 8px;
  padding: 11px 0; cursor: pointer;
  font-size: 13px; font-weight: 600; color: var(--warn);
  list-style: none;
}
.notices summary::-webkit-details-marker { display: none; }
.notices summary::after { content: "▾"; margin-left: auto; font-size: 11px; }
.notices[open] summary::after { content: "▴"; }
.notices .ic { width: 15px; height: 15px; }
.notices ul {
  margin: 0; padding: 0 0 14px 26px;
  font-size: 12.5px; line-height: 1.6; color: var(--ink-soft);
}

/* ── recents ─────────────────────────────────────────────── */
.recent-list { list-style: none; margin: 0; padding: 0; max-height: 300px; overflow-y: auto; }
.recent-list .empty { color: var(--ink-faint); font-size: 12.5px; padding: 6px 0; }
.recent-item {
  display: grid; grid-template-columns: 30px 1fr auto; gap: 10px;
  align-items: center; width: 100%; text-align: left;
  padding: 8px; margin-bottom: 2px;
  background: none; border: 1px solid transparent; border-radius: var(--r-md);
  color: inherit; cursor: pointer; transition: .12s;
}
.recent-item:hover { background: var(--surface-2); }
/* Hover is already surface-2, so selection needs a second signal — an outline
   at mid-grey, rather than the full-ink border that would shout. */
.recent-item.is-active { background: var(--surface-2); border-color: var(--ink-faint); }
.recent-icon {
  display: grid; place-items: center; width: 30px; height: 30px;
  border-radius: var(--r-sm); background: var(--surface-2); color: var(--ink-faint);
}
.recent-item.is-active .recent-icon { background: var(--surface); color: var(--accent); }
.recent-icon .ic { width: 15px; height: 15px; }
.recent-body { min-width: 0; }
.recent-name {
  display: block; font-size: 12.5px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.recent-meta { display: block; font-size: 11px; color: var(--ink-faint); }

/* ── stat tiles ──────────────────────────────────────────── */
.stats {
  display: grid; gap: 14px; margin-bottom: 16px;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.stat {
  display: flex; align-items: center; gap: 13px;
  padding: 15px 17px; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--shadow);
}
.stat-icon {
  display: grid; place-items: center; width: 38px; height: 38px; flex: none;
  border-radius: 50%; background: var(--accent-dim); color: var(--accent);
}
.stat-label {
  display: block; font-size: 10px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--ink-faint);
}
.stat-value {
  display: block; font-size: 20px; font-weight: 700;
  letter-spacing: -0.02em; margin-top: 2px;
}

/* ── segmented tabs ──────────────────────────────────────── */
.seg-tabs {
  display: inline-flex; gap: 2px; padding: 3px;
  background: var(--surface-2); border-radius: var(--r-md);
}
.seg-tab {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 14px; border: none; background: none; border-radius: var(--r-sm);
  color: var(--ink-faint); font: 600 12.5px var(--sans); cursor: pointer;
  transition: .12s;
}
.seg-tab:hover { color: var(--ink); }
.seg-tab.is-active {
  background: var(--surface); color: var(--accent);
  box-shadow: var(--shadow);
}
.count {
  padding: 1px 6px; border-radius: var(--r-pill);
  background: var(--accent-dim); color: var(--accent);
  font: 10.5px var(--mono);
}
.count:empty { display: none; }

/* ── tables ──────────────────────────────────────────────── */
.table-wrap { overflow: auto; max-height: 62vh; }
table { border-collapse: collapse; width: 100%; font-size: 13px; }
thead th {
  position: sticky; top: 0; z-index: 1; text-align: left;
  padding: 11px 16px; white-space: nowrap;
  background: var(--surface-2); color: var(--ink-faint);
  font-size: 10px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  border-bottom: 1px solid var(--line);
}
tbody td {
  padding: 12px 16px; border-bottom: 1px solid var(--line-soft);
  vertical-align: top; max-width: 340px;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--surface-2); }
td.num { color: var(--ink-faint); font: 11.5px var(--mono); text-align: right; }
td strong { font-weight: 600; }
/* Nothing is tinted any more, so a link in a cell cannot be picked out of the
   data by colour. It keeps a standing underline instead. */
td a {
  color: var(--ink); font-family: var(--mono); font-size: 12px;
  text-decoration: underline; text-decoration-color: var(--line);
  text-underline-offset: 2px;
}
td a:hover { text-decoration-color: currentColor; }
td img.cell-thumb {
  max-width: 60px; max-height: 72px; border-radius: var(--r-sm);
  display: block; border: 1px solid var(--line);
}
.row-open {
  padding: 4px; border: none; background: none; border-radius: var(--r-sm);
  color: var(--ink-faint); cursor: pointer;
}
.row-open:hover { background: var(--accent-dim); color: var(--accent); }
.row-actions { display: flex; gap: 2px; justify-content: flex-end; }

.tag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: var(--r-pill);
  font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  background: var(--surface-2); color: var(--ink-faint);
}
.tag::before { content: "●"; font-size: 7px; }
.tag[data-s="done"]    { background: var(--good-dim); color: var(--good); }
.tag[data-s="failed"]  { background: var(--bad-dim);  color: var(--bad); }
.tag[data-s="running"] { background: var(--warn-dim); color: var(--warn); }
.tag[data-s="cancelled"] { background: var(--surface-2); color: var(--ink-faint); }

/* Shown in place of an image the source site refused, so a failure reads as a
   reason rather than an empty rectangle. */
.img-missing {
  display: inline-block; padding: 3px 8px; border-radius: var(--r-sm);
  border: 1px dashed var(--line); color: var(--ink-faint);
  font-size: 11px; white-space: nowrap;
}
.shot .img-missing {
  width: 100%; height: 128px; border: none; border-radius: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-2);
}

.pager { display: flex; align-items: center; gap: 4px; }
.page-btn {
  min-width: 30px; height: 30px; padding: 0 8px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-sm); color: var(--ink-soft);
  font: 600 12.5px var(--sans); cursor: pointer; transition: .12s;
}
.page-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.page-btn:disabled { opacity: .4; cursor: not-allowed; }
.page-btn.is-active {
  background: var(--accent); border-color: var(--accent); color: var(--accent-in);
}

/* ── gallery ─────────────────────────────────────────────── */
.images-bar { margin-bottom: 14px; }
.gallery {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  max-height: 58vh; overflow-y: auto;
}
.shot {
  margin: 0; border: 1px solid var(--line); border-radius: var(--r-md);
  overflow: hidden; background: var(--surface-2);
  display: flex; flex-direction: column; transition: border-color .12s;
}
.shot:hover { border-color: var(--accent); }
.shot img {
  width: 100%; height: 128px; object-fit: cover;
  background: var(--surface-2); display: block;
}
.shot figcaption {
  padding: 8px 10px; font-size: 11.5px; color: var(--ink-soft);
  background: var(--surface);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ── progress + run details ──────────────────────────────── */
.bar {
  height: 8px; margin: 16px 0 18px; border-radius: var(--r-pill);
  background: var(--surface-2); overflow: hidden;
}
.bar span {
  display: block; height: 100%; width: 0;
  border-radius: var(--r-pill); background: var(--accent);
  position: relative; overflow: hidden;
  transition: width .6s cubic-bezier(.4, 0, .2, 1);
}
/* A bar that only moves once a minute looks frozen. The sweep says "still
   working" between the stages that actually advance it. */
.bar span::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--sweep), transparent);
  animation: sweep 1.5s linear infinite;
}

.steps {
  display: flex; flex-wrap: wrap; gap: 9px 22px;
  list-style: none; margin: 0; padding: 0;
}
.steps li {
  display: flex; align-items: center; gap: 8px;
  font-size: 12.5px; color: var(--ink-faint);
}
.steps li::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--line); flex: none;
}
.steps li.is-done { color: var(--ink-soft); }
.steps li.is-done::before { background: var(--good); }
.steps li.is-now { color: var(--accent); font-weight: 600; }
.steps li.is-now::before { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }

/* The staged bar above says roughly where a run is; this is what it is
   actually doing right now, for a step that is taking a while and giving no
   other sign of life. Collapsed by default — most runs are quick enough that
   nobody needs it. */
.log-details { margin-top: 12px; }
.log-details summary {
  cursor: pointer; font-size: 12px; color: var(--ink-faint);
  user-select: none;
}
.log-details summary:hover { color: var(--ink-soft); }
.log-tail {
  margin: 8px 0 0; padding: 10px 12px; max-height: 220px; overflow-y: auto;
  background: var(--surface-2); border-radius: var(--r-md, 8px);
  font-family: var(--mono); font-size: 11.5px; line-height: 1.5;
  color: var(--ink-soft); white-space: pre-wrap; word-break: break-all;
}

.details {
  margin: 12px 0 18px; display: grid; gap: 0 32px;
  /* min() so the track can shrink below its ideal width on a phone. A hard
     230px floor overflowed the card, and .card is overflow:hidden — so the
     right-aligned values were clipped with no scrollbar to recover them. */
  grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
}
.details div {
  display: flex; justify-content: space-between; gap: 12px;
  padding: 8px 0; border-bottom: 1px solid var(--line-soft);
  font-size: 12.5px;
}
.details dt { color: var(--ink-faint); }
.details dd { margin: 0; font-weight: 600; text-align: right; word-break: break-word; }

/* ── dropdown ────────────────────────────────────────────── */
.dropdown { position: relative; }
.caret { transition: transform .16s; }
[aria-expanded="true"] .caret { transform: rotate(180deg); }
.menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
  min-width: 216px; padding: 6px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md); box-shadow: var(--shadow-pop);
}
.menu a {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  padding: 9px 11px; border-radius: var(--r-sm);
  color: var(--ink); text-decoration: none; font-size: 13px; font-weight: 600;
}
.menu a:hover { background: var(--accent-dim); color: var(--accent); }
.menu a small { color: var(--ink-faint); font-weight: 400; font-size: 11.5px; }
/* The live feed is a different kind of thing from the five downloads, so it
   sits below a rule rather than pretending to be a sixth file format. */
.menu a.menu-sub {
  margin-top: 4px; padding-top: 10px;
  border-top: 1px solid var(--line-soft); border-radius: 0 0 var(--r-sm) var(--r-sm);
  color: var(--accent);
}

/* ── empty state ─────────────────────────────────────────── */
.empty-state {
  display: flex; flex-direction: column; align-items: center;
  padding: 90px 30px; text-align: center;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--shadow);
}
.empty-art {
  display: grid; place-items: center; width: 56px; height: 56px;
  border-radius: var(--r-lg); background: var(--accent-dim); color: var(--accent);
  margin-bottom: 16px;
}
.empty-art .ic { width: 26px; height: 26px; }
.empty-state h2 { margin: 0 0 6px; font-size: 17px; font-weight: 700; }
.empty-state p { margin: 0 0 18px; max-width: 40ch; color: var(--ink-faint); font-size: 13.5px; }

/* ── responsive ──────────────────────────────────────────── */
@media (max-width: 1180px) {
  .grid-main { grid-template-columns: minmax(0, 1fr); }
  .col-side { position: static; }
}
@media (max-width: 900px) {
  :root { --side: 66px; }
  .brand-text, .nav-item span, .side-link span, .btn-block span,
  .last-run { display: none; }
  .nav-item, .side-link, .btn-block { justify-content: center; }
  .sidebar { padding: 14px 10px; }
}
@media (max-width: 700px) {
  .scroll { padding: 16px; }
  .page-head h1 { font-size: 22px; }
  .head-actions { width: 100%; }
  .topbar { padding: 0 16px; }
}

/* scrollbars, so they don't punch a bright hole in a dark panel */
* { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb {
  background: var(--line); border-radius: var(--r-pill);
  border: 3px solid transparent; background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--ink-faint); background-clip: content-box; }

.spin { display: inline-block; animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes sweep {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100%); }
}
@media (prefers-reduced-motion: reduce) {
  .spin, .dot, .bar span::after { animation: none; }
  * { transition: none !important; }
}

/* ── sign-in ─────────────────────────────────────────────── */
.gate {
  position: fixed; inset: 0; z-index: 100;
  display: grid; place-items: center; padding: 24px;
  background: var(--bg);
}
.gate-card { width: 100%; max-width: 380px; margin: 0; }
.gate-card .card-pad { padding: 28px; }
.gate-mark { width: 40px; height: 40px; margin-bottom: 16px; }
.gate-card h1 { margin: 0; font-size: 22px; font-weight: 700; }
.gate-card .page-sub { margin-bottom: 22px; }
.gate-card .field { display: block; margin-bottom: 16px; }
.gate-card input {
  width: 100%; font-family: var(--mono); font-size: 13px;
}
.gate-card .hint { margin-top: 14px; }
.gate-card code {
  font-family: var(--mono); font-size: 11.5px;
  background: var(--surface-2); padding: 1px 5px; border-radius: var(--r-sm);
}
/* Google's mark is a solid four-colour glyph, so it is the one icon in the app
   that must not inherit the stroke treatment every other icon uses. */
.gate-google .ic { fill: currentColor; stroke: none; }
.gate-google { margin-bottom: 4px; justify-content: center; }

/* Transient confirmation. Fixed rather than in flow, so appearing does not
   push the page around under the cursor. */
.toast {
  position: fixed; left: 50%; bottom: 24px; z-index: 200;
  transform: translate(-50%, 16px);
  max-width: min(90vw, 460px); padding: 12px 18px;
  background: var(--ink); color: var(--bg);
  border-radius: var(--r-md); box-shadow: var(--shadow-pop);
  font-size: 13px; font-weight: 500; text-align: center;
  opacity: 0; pointer-events: none;
  transition: opacity .18s, transform .18s;
}
.toast.is-up { opacity: 1; transform: translate(-50%, 0); }
