/* IScrape — web UI.

   Neutrals with a sage cast. Every surface, line and ink in the ramp carries
   the same slight green; nothing is a saturated colour, and the "accent" is
   not a hue at all: it is the ink itself, inverted. Dark sage on off-white in
   light, near-white on dark sage in dark. A primary button is therefore the
   darkest (or lightest) thing on the screen rather than the most colourful,
   which is what makes it read as primary without spending colour on it.

   That leaves colour free to mean one thing: state. Running, done and failed
   are the only things allowed to be genuinely coloured, so a hue in this app
   is telling you what happened to a run.

   Which is why "done" is teal rather than green. On a sage canvas a green
   pill is the one state that stops announcing itself — the ground is already
   halfway there — and success is the state people look for hardest. Running
   (amber) and failed (red) sit far enough away to be safe.

   Because the ramp is near-neutral, links still cannot be picked out by
   colour — they carry an underline instead. Same reason the switch knob has
   its own token: on an inverted track, a fixed light knob disappears in one
   theme or the other. */

:root {
  color-scheme: light;

  --bg:        #eef0ec;   /* page canvas */
  --sidebar:   #eaece8;   /* the rail, a shade off the cards it frames */
  --surface:   #f7f8f6;   /* cards */
  --surface-2: #e5e9e4;   /* wells, table heads, hover */
  --tint:      #e5e9e4;   /* quieter card, e.g. the settings panel */

  --ink:       #1e241f;
  --ink-soft:  #515c55;
  --ink-faint: #79837c;

  --line:      #d3d9d3;
  --line-soft: #e2e6e0;

  /* Not a colour — the ink, inverted. See the note above. */
  --accent:     #262d27;
  --accent-hi:  #161b17;
  --accent-in:  #f7f8f6;
  --accent-dim: #e5e9e4;   /* chips, hovers, tinted wells */
  --ring:       rgba(30, 36, 31, .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:      #515c55;

  --knob:    #f7f8f6;   /* switch knob, off */
  --knob-on: #f7f8f6;   /* switch knob, on (track is dark here) */
  --sweep:   rgba(255, 255, 255, .45);  /* progress shimmer over a dark bar */

  /* The ground is now sage, and "done" is green. On a green-cast canvas the
     one state anyone looks for was the one hardest to see, so success moved
     to teal: still unmistakably "good", far enough round the wheel to stand
     off the surface behind it. Running and failed were never at risk. */
  --good: #0d7490;  --good-dim: #d3eef5;
  --warn: #b45309;  --warn-dim: #fdf0d5;
  --bad:  #c62828;  --bad-dim:  #fbe0e0;

  --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;
  /* Deeper than the sage the landing introduced. The steps between canvas,
     rail and card are *widened* as it gets darker, not kept proportional:
     near-black surfaces a couple of values apart stop being distinguishable
     long before the numbers say they should, and this app leans on the
     hairline to separate rather than on elevation. */
  --bg:        #141815;   /* canvas, the darkest thing here */
  --sidebar:   #191e1a;
  --surface:   #1d231f;   /* cards lift off the canvas */
  --surface-2: #262d28;   /* wells, table heads, hover */
  --tint:      #191e1a;
  --ink:       #e9ece9;
  --ink-soft:  #a3aca6;
  --ink-faint: #78827b;
  --line:      #303830;
  --line-soft: #252b27;
  --accent:     #e9ece9;
  --accent-hi:  #ffffff;
  --accent-in:  #141815;
  --accent-dim: #262d28;
  --ring:       rgba(233, 236, 233, .22);
  --focus:      #a3aca6;
  --knob:    #cdd3ce;
  --knob-on: #141815;   /* the track is near-white now, so the knob goes dark */
  --sweep:   rgba(0, 0, 0, .28);
  /* Success moves off green here for the same reason as in light: the canvas
     is sage, and a green pill on a green ground is the one state that stops
     announcing itself. */
  --good: #4dd0e1;  --good-dim: rgba(77, 208, 225, .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;
}

/* The five most recent runs, under the History tab. Indented to the nav
   label's text so they read as belonging to it rather than as three more
   sections, and quieter than the nav items so the tabs stay the structure. */
.side-recent { margin: 4px 0 2px; }
.side-recent-list { list-style: none; margin: 0; padding: 0 0 0 11px; }
.side-recent-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 6px 10px; border: 0; background: none; border-radius: var(--r-sm);
  color: var(--ink-faint); font: 400 12.5px/1.35 var(--sans);
  text-align: left; cursor: pointer;
  transition: background .12s, color .12s;
}
.side-recent-item:hover { background: var(--surface-2); color: var(--ink); }
.side-recent-item.is-active { color: var(--ink); font-weight: 600; }
.side-recent-dot {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: var(--ink-faint);
}
.side-recent-dot[data-s="done"]    { background: var(--good); }
.side-recent-dot[data-s="failed"]  { background: var(--bad); }
.side-recent-dot[data-s="running"] { background: var(--warn); }
.side-recent-dot[data-s="queued"]  { background: var(--warn); }
/* A request is a sentence, and the rail is 232px wide. One line, clipped —
   the full text is on the button's title. */
.side-recent-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}

/* The account panel. Anchored to the rail it opens from, rather than centred:
   it belongs to that button and pointing at it costs nothing. */
.me-backdrop { position: fixed; inset: 0; z-index: 60; background: transparent; }
.me-panel {
  position: fixed; z-index: 61;
  left: 12px; bottom: 12px; width: min(320px, calc(100vw - 24px));
  max-height: min(78vh, 640px); overflow-y: auto;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--shadow-pop);
  display: flex; flex-direction: column;
}
.me-head {
  padding: 14px 16px; border-bottom: 1px solid var(--line-soft);
  display: flex; flex-direction: column; gap: 3px;
}
.me-head strong {
  font-size: 13.5px; font-weight: 650;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.me-body { padding: 14px 16px; display: flex; flex-direction: column; gap: 18px; }
.me-block { display: flex; flex-direction: column; gap: 9px; }

.me-gauge { display: flex; flex-direction: column; gap: 5px; }
.me-gauge-top {
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
  font-size: 12.5px; color: var(--ink-soft);
}
.me-gauge-top .mono {
  font-family: var(--mono); font-size: 11.5px; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.me-bar {
  height: 5px; border-radius: var(--r-pill);
  background: var(--surface-2); overflow: hidden;
}
.me-bar span {
  display: block; height: 100%; width: 0;
  border-radius: var(--r-pill); background: var(--accent);
  transition: width .2s ease;
}
/* The only colour in here, and it means what colour is allowed to mean in
   this app: state. Approaching a limit, and at it. */
.me-bar span[data-level="high"] { background: var(--warn); }
.me-bar span[data-level="full"] { background: var(--bad); }

.me-stats { margin: 0; display: flex; flex-direction: column; gap: 6px; }
.me-stats > div {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
}
.me-stats dt { font-size: 12.5px; color: var(--ink-soft); }
.me-stats dd {
  margin: 0; font-family: var(--mono); font-size: 12px; color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.me-foot { padding: 12px 16px 14px; border-top: 1px solid var(--line-soft); }

.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;
}
/* The field keeps its box. There were two here: this one, and a second drawn
   inside it by Chrome's own `type="search"` styling — that inner one is what
   `appearance: none` on the input below removes. Taking this outer box away
   too left the search with no field at all, which was a misread on my part,
   not a design decision.

   It does not repaint on focus. The caret says where you are, and a field
   that changes colour the moment you click into it draws the eye to having
   been clicked rather than to what is being typed. */
.search {
  display: flex; align-items: center; gap: 9px;
  flex: 1; max-width: 420px; padding: 7px 12px;
  background: var(--surface-2);
  border-radius: var(--r-md); color: var(--ink-faint);
}
.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; }

/* .url-note styled the per-site robots.txt notice. Both are gone: the check
   is off by default, so no control changes itself and there is nothing to
   announce. */

.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); }

/* ── the conversation ────────────────────────────────────────
   One column, transcript above and composer below, filling the height it is
   given. The composer stays put while the thread scrolls behind it — a chat
   whose input drifts up the page as the history grows is a form with extra
   steps. */
.chat { display: block; position: relative; }
.chat-main {
  display: flex; flex-direction: column;
  height: calc(100vh - var(--top) - 48px); min-height: 420px;
}
.chat-log {
  flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: 18px;
  padding: 4px 4px 20px;
}

.chat-empty {
  margin: auto; text-align: center; max-width: 42ch;
  display: flex; flex-direction: column; align-items: center; gap: 12px;
}
.chat-empty h2 { margin: 4px 0 0; font-size: 21px; font-weight: 650; }
.chat-empty p { margin: 0; font-size: 14px; line-height: 1.65; color: var(--ink-soft); }

/* A message. The two sides are told apart by alignment and ground, not by a
   label — the thread only has two speakers. */
.msg { display: flex; max-width: 100%; }
.msg-you { justify-content: flex-end; }
/* The asker's bubble is a quiet tint of the surface, not the accent at full
   volume — it carries the user's own words back to them, and shouting them
   in saturated colour made the thread read as two competing brands. */
.msg-you .msg-body {
    background: color-mix(in srgb, var(--accent) 22%, var(--surface));
    color: var(--fg);
    border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line));
    border-radius: 14px 14px 4px 14px; max-width: min(70ch, 82%);
  }
.msg-bot .msg-body {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 14px 14px 14px 4px; max-width: min(76ch, 92%);
}
.msg-body { padding: 12px 15px; font-size: 14.5px; line-height: 1.6; }

/* The link sits under the request rather than inside it: it is what you are
   asking about, not part of the sentence. */
.msg-url {
  display: block; margin-top: 6px; font-family: var(--mono); font-size: 11.5px;
  opacity: .72; overflow-wrap: anywhere;
}

.msg-working { display: inline-flex; align-items: center; gap: 9px; color: var(--ink-soft); }
.msg-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--warn);
  animation: pulse 1.4s ease-in-out infinite; flex: none;
}
.msg-lead { margin: 0; }
.msg-lead strong { font-weight: 700; }
.msg-cols { margin: 10px 0 0; display: flex; flex-wrap: wrap; gap: 4px; }
.msg-bad { margin: 0; color: var(--bad); }

/* The data, inside the message. */
.msg-loading { margin: 10px 0 0; color: var(--ink-faint); font-size: 13px; }
.msg-more { margin: 8px 0 0; font-size: 12px; color: var(--ink-faint); }

/* Several rows: a compact table that scrolls sideways inside the bubble
   rather than stretching it. */
.msg-table {
  margin-top: 10px; overflow-x: auto;
  border: 1px solid var(--line); border-radius: var(--r-md);
}
.msg-table table { width: max-content; min-width: 100%; font-size: 12.5px; }
.msg-table thead th {
  position: static; padding: 8px 11px; font-size: 9.5px;
  background: var(--surface-2);
}
.msg-table tbody td { padding: 7px 11px; max-width: 260px; }
.msg-table tbody tr:last-child td { border-bottom: none; }

/* One record: fields, not a one-row table eleven columns wide. */
.msg-record { margin: 12px 0 0; gap: 12px 24px; }
.msg-record dd { font-size: 13px; }

/* The answer's attachments. A file per format, and the table itself. */
.msg-files { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 6px; }
.msg-file {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: var(--r-md);
  border: 1px solid var(--line); background: var(--surface-2);
  color: var(--ink); font: 600 12.5px var(--sans); text-decoration: none;
  cursor: pointer;
}
.msg-file:hover { border-color: var(--focus); }
.msg-file-open { background: var(--accent); color: var(--accent-in); border-color: var(--accent); }

/* Docked below the thread. */
.chat-dock {
  flex: none; display: flex; flex-direction: column; gap: 10px;
  padding-top: 12px; border-top: 1px solid var(--line);
}
.composer {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 12px 14px 10px;
}
.composer textarea {
  width: 100%; border: none; background: none; outline: none; resize: none;
  padding: 0; font: 14.5px/1.6 var(--sans); color: var(--ink);
  /* The global textarea rule sets a 108px floor, which is right for a form
     field and wrong for a chat box: it opened four lines tall and empty.
     This one starts at a line and grows with the message. */
  min-height: 0; height: auto; max-height: 220px; overflow-y: auto;
}
.composer textarea:focus { box-shadow: none; }
.composer-foot {
  display: flex; align-items: center; gap: 12px; margin-top: 10px;
}
.composer-hint { margin-left: auto; font-size: 12px; color: var(--ink-faint); }
.composer-btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 11px; border-radius: var(--r-md);
  border: 1px solid var(--line); background: none;
  color: var(--ink-soft); font: 500 12.5px var(--sans); cursor: pointer;
}
.composer-btn:hover { color: var(--ink); border-color: var(--focus); }

/* The settings drawer, over the thread rather than beside it. */
.settings-panel {
  position: absolute; right: 0; bottom: 96px; z-index: 30;
  width: min(340px, calc(100% - 8px)); max-height: 62vh; overflow-y: auto;
  border-radius: var(--r-lg); box-shadow: var(--shadow-pop);
  background: var(--bg); border: 1px solid var(--line);
}

/* ── 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 box does not change when you click into it. It used to take a mid-grey
   border and a ring, which repainted the field at the exact moment attention
   moved to it — the caret is already the answer to "where am I typing".
   Keyboard users are not stranded: a text field with a blinking caret is
   self-announcing in a way a button is not. */
.input-box input {
  flex: 1; min-width: 0; border: none; background: none; outline: none;
  font: 13.5px var(--mono); color: var(--ink);
}
/* Neither of these repaints on focus. The generic `input:focus` rule further
   down would otherwise draw a ring inside a box that has deliberately stopped
   drawing one, which is the thing being removed rather than a smaller version
   of it. */
.input-box input:focus,
.search input:focus { box-shadow: none; border-color: transparent; }

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;
}

/* A field inside a box that already draws a box must not draw its own.
   `.input-box input` and `input[type=text]` have identical specificity — a
   class plus a type against a type plus an attribute — so the generic rule
   above won purely by coming later in the file, and the URL field rendered
   as two identical rounded rectangles, one inside the other. Same for the
   search field. Stated here, after the rule it has to beat, and with the
   wrapper named so the intent survives the next edit. */
.input-box input[type=text],
.input-box input,
.search input {
  border: none; background: none; outline: none;
  padding: 0; border-radius: 0; box-shadow: none;
  /* type="search" comes with native decoration of its own in Chrome. */
  appearance: none; -webkit-appearance: none;
}
.search input::-webkit-search-decoration,
.search input::-webkit-search-cancel-button { appearance: none; }
textarea { resize: vertical; min-height: 108px; }

/* The request field sits inside a card that is already a bordered box, and
   drawing its own put a second rectangle inside the first. It blends into the
   card instead; focus still reads, through the ring below rather than a
   border, so nothing here draws an edge that was not already there. */
#request {
  border: none; background: none; padding: 0;
  border-radius: var(--r-sm);
}
#request:focus { box-shadow: none; }

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; }

/* The notices panel lived here — the "N things worth knowing" strip above a
   result. Removed on request. Warnings are still recorded on the job and
   still returned by the API; nothing renders them. */

/* ── 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 ──────────────────────────────────────────────── */
/* The results grid.

   It used to be `width: 100%`, which sounds harmless and is the whole problem:
   eleven columns forced into the viewport means every one is too narrow, so
   every cell wraps, so a row is as tall as its worst column. One long
   description made each row 130px and the table showed two records at a time.

   So: columns take the width their content needs, the container scrolls
   sideways, and a row is one line. What one line cannot show, the expanded
   record underneath shows in full — clipping is only acceptable when there is
   a way to see what was clipped. */
.table-wrap { overflow: auto; max-height: 62vh; overscroll-behavior-x: contain; }
table {
  border-collapse: separate; border-spacing: 0;
  width: max-content; min-width: 100%; font-size: 13px;
}
thead th {
  position: sticky; top: 0; z-index: 2; text-align: left;
  padding: 11px 14px; 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: 10px 14px; border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
  /* One line, clipped. The full string stays in the DOM so the filter still
     finds it and the expanded record can show it. */
  max-width: 320px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
tbody tr:last-child td { border-bottom: none; }
.data-row { cursor: pointer; }
.data-row:hover td { background: var(--surface-2); }
.data-row:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.data-row.is-open td { background: var(--surface-2); }

/* The row number stays put while the columns scroll under it, so you never
   lose which record you are looking at. */
.col-num {
  position: sticky; left: 0; z-index: 1;
  background: var(--surface); width: 1%; white-space: nowrap;
}
thead .col-num { z-index: 3; background: var(--surface-2); }
.data-row:hover .col-num, .data-row.is-open .col-num { background: var(--surface-2); }
td.num { color: var(--ink-faint); font: 11.5px var(--mono); text-align: right; }
td strong { font-weight: 600; }

/* Numbers line up under each other or they cannot be compared at a glance. */
td.col-number, th.col-number { text-align: right; font-variant-numeric: tabular-nums; }
td.col-image { width: 1%; }
.cell-empty { color: var(--ink-faint); }

/* A field holding several values is a set, not a sentence. Rendering it as
   "Action | Drama | Sci-Fi" made the widest column on the page out of the one
   carrying the least. */
.cell-chip {
  display: inline-block; margin-right: 4px; padding: 2px 8px;
  border-radius: var(--r-pill); background: var(--surface-2);
  color: var(--ink-soft); font-size: 11.5px; white-space: nowrap;
}

/* 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: 40px; max-height: 52px; border-radius: var(--r-sm);
  display: block; border: 1px solid var(--line);
}

/* The expanded record. */
.detail-row td { background: var(--surface-2); white-space: normal; max-width: none; }
.detail-row .col-num { background: var(--surface-2); }
.record {
  margin: 4px 0; display: grid; gap: 14px 28px;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  /* Without this every field stretches to the tallest in its row, so a long
     description drags its neighbours' labels halfway down the cell and the
     record reads as scattered rather than tabulated. */
  align-items: start;
}
.record > div { display: grid; gap: 3px; min-width: 0; align-content: start; }
.record dt {
  font-size: 10px; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--ink-faint);
}
.record dd {
  margin: 0; font-size: 13px; line-height: 1.55; color: var(--ink);
  overflow-wrap: anywhere;
}
.record dd a { font-size: 12px; }
img.detail-thumb {
  max-width: 120px; border-radius: var(--r-sm); 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 raw log and its "Show details" disclosure used to live here. The named
   stages above answer the question a person watching a run actually has, and
   the log restated it in storage form. */

.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; }
  /* The rail collapses to icons. `#me-label` is a `.side-link span`, so the
     account button correctly becomes its icon here too. */
  .brand-text, .nav-item span, .side-link span, .btn-block span,
  .side-recent { 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; }
}

/* ── the signed-out page ─────────────────────────────────────
   A full page rather than a dialog: this is the first thing anyone sees, and
   a lone sign-in card explains nothing about what they would be signing in
   to. It scrolls, so `place-items: center` is gone — a centred grid child
   cannot be scrolled back to once it is taller than the viewport.

   No nav bar. There is one destination and one action, and a row of links to
   nowhere would be set dressing.

   The sage this page introduced is now the whole app's palette, so the local
   --l-* names are aliases onto the real tokens rather than a second set of
   values. Two hand-maintained copies of one palette drift, and the seam shows
   at exactly the moment someone signs in. */
.gate {
  --l-bg:      var(--bg);
  --l-surface: var(--surface);
  --l-raise:   var(--surface-2);
  --l-line:    var(--line);
  --l-ink:     var(--ink);
  --l-soft:    var(--ink-soft);
  --l-faint:   var(--ink-faint);

  position: fixed; inset: 0; z-index: 100;
  overflow-y: auto; overscroll-behavior: contain;
  background: var(--l-bg); color: var(--l-ink);
}

.landing { min-height: 100%; }

.lhero { padding: 76px 20px 92px; text-align: center; }
.lbrand {
  display: inline-flex; align-items: center; gap: 9px;
  margin-bottom: 44px; color: var(--l-ink);
  font-size: 14.5px; letter-spacing: -0.01em;
}
.lbrand .brand-mark { background: var(--l-ink); color: var(--l-bg); }

/* Very large and very light. The weight is the point: at 300 a 68px line
   reads as calm rather than as shouting, which is what carries this look. */
.lhero-title {
  margin: 0 auto; max-width: 20ch; color: var(--l-ink);
  font-size: clamp(42px, 7.4vw, 78px); line-height: 1.02;
  letter-spacing: -0.03em; font-weight: 300; text-wrap: balance;
}
.lhero-soft { color: var(--l-faint); }
.lhero-sub {
  margin: 26px auto 0; max-width: 54ch;
  font-size: clamp(15px, 1.8vw, 18px); line-height: 1.75;
  color: var(--l-soft);
}

/* The signature device of the look this was drawn from: bordered chips set
   inline in running prose, so a sentence and a list are the same object. */
.lchip {
  display: inline-block; padding: 1px 9px; margin: 0 1px;
  border: 1px solid var(--l-line); border-radius: var(--r-sm);
  color: var(--l-ink); white-space: nowrap;
}
.lchips {
  margin: 26px auto 0; max-width: 60ch;
  display: flex; flex-wrap: wrap; gap: 8px; justify-content: center;
}
.lchips .lchip { margin: 0; padding: 5px 12px; font-size: 13.5px; }

/* The way in, above the fold. The page used to open on a claim with nothing
   to press until you had scrolled past three sections. */
.lhero-act {
  margin-top: 38px;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
.lbtn-lead {
  background: var(--accent); border-color: var(--accent); color: var(--accent-in);
  padding: 13px 26px;
}
.lbtn-lead:hover { background: var(--accent-hi); border-color: var(--accent-hi); }
.lhero-act-note { margin: 0; font-size: 12.5px; color: var(--l-faint); }

.lhero-alert { max-width: 560px; margin: 26px auto 0; text-align: left; }
.lhero-note {
  margin: 16px auto 0; max-width: 56ch;
  font-size: 12.5px; line-height: 1.6; color: var(--l-faint);
}
.lhero-note code {
  font-family: var(--mono); font-size: 11.5px;
  background: var(--l-raise); color: var(--l-soft);
  padding: 1px 5px; border-radius: var(--r-sm);
}

/* Soft filled buttons rather than the app's hard inverted ink. */
.lbtn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  padding: 12px 22px; border-radius: var(--r-md);
  background: var(--l-raise); border: 1px solid var(--l-line);
  color: var(--l-ink); font: 600 14px var(--sans); text-decoration: none;
  cursor: pointer; transition: background .14s, border-color .14s;
}
.lbtn:hover { background: var(--l-surface); border-color: var(--l-faint); }
.lbtn .ic { width: 17px; height: 17px; }

/* Numbered because it genuinely is a sequence. */
.lsteps {
  width: min(1060px, calc(100% - 40px));
  margin: 0 auto; padding: 24px 0 0;
  display: grid; gap: 30px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.lstep { border-top: 1px solid var(--l-line); padding-top: 18px; }
.lstep-n {
  display: inline-grid; place-items: center;
  width: 22px; height: 22px; border-radius: 50%;
  border: 1px solid var(--l-line);
  font-family: var(--mono); font-size: 11px; color: var(--l-faint);
}
.lstep h2 {
  margin: 12px 0 8px; font-size: 17px; font-weight: 600;
  letter-spacing: -0.01em; color: var(--l-ink);
}
.lstep p { margin: 0; font-size: 14px; line-height: 1.65; color: var(--l-soft); }

/* Eyebrow → headline → subhead → cards. */
.lband {
  width: min(1060px, calc(100% - 40px));
  margin: 0 auto; padding: 104px 0 0; text-align: center;
}
.leyebrow {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 13px; border-radius: var(--r-sm);
  border: 1px solid var(--l-line); background: transparent;
  font-size: 12.5px; font-weight: 500; color: var(--l-soft);
}
.leyebrow .ic { width: 14px; height: 14px; }
.lband-title {
  margin: 24px auto 0; max-width: 24ch; color: var(--l-ink);
  font-size: clamp(30px, 4.8vw, 52px); line-height: 1.08;
  letter-spacing: -0.028em; font-weight: 300; text-wrap: balance;
}
.lband-sub {
  margin: 18px auto 0; max-width: 54ch;
  font-size: 15.5px; line-height: 1.65; color: var(--l-soft);
}
.lcards {
  margin-top: 46px; display: grid; gap: 16px; text-align: left;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.lcard {
  border: 1px solid var(--l-line); border-radius: var(--r-lg);
  background: var(--l-surface); padding: 22px 22px 24px;
}
.lcard h3 {
  margin: 0 0 8px; font-size: 16px; font-weight: 600;
  letter-spacing: -0.01em; color: var(--l-ink);
}
.lcard p {
  margin: 0; font-size: 13.5px; line-height: 1.65; color: var(--l-soft);
}

/* Closing action. The only way in, and the page ends on it. */
.lend {
  width: min(1060px, calc(100% - 40px));
  margin: 0 auto; padding: 104px 0 112px; text-align: center;
}
.lend-title {
  margin: 0 auto 28px; color: var(--l-ink);
  font-size: clamp(28px, 4.4vw, 46px); line-height: 1.08;
  letter-spacing: -0.028em; font-weight: 300;
}
.lend-note {
  margin: 20px auto 0; max-width: 46ch;
  font-size: 12.5px; line-height: 1.6; color: var(--l-faint);
}

@media (max-width: 560px) {
  .lhero { padding: 54px 18px 76px; }
  .lband, .lend { padding-top: 76px; }
}
/* ── too small for this app ──────────────────────────────────
   Below the breakpoint the whole product is replaced by one screen saying so.
   Not a responsive layout: a deliberate decision that a results grid eleven
   columns wide, next to the settings that produced it, is not something to
   attempt on a phone.

   The breakpoint is 820px rather than a phone width. Portrait tablets sit at
   768–834 and are no better suited to this than a phone; the rail already
   collapses to icons at 900, which is the point where the layout starts
   apologising for itself.

   It hides `.shell` AND `.gate`, so the landing page goes too — a phone
   visitor who signed in from here would land straight on the thing this
   screen exists to prevent. */
.small-screen { display: none; }

@media (max-width: 820px) {
  .shell, .gate, .toast { display: none !important; }

  .small-screen {
    display: grid; place-items: center;
    position: fixed; inset: 0; z-index: 200;
    padding: 32px 26px; text-align: center;
    background: var(--bg); color: var(--ink);
  }
  .small-screen-inner {
    display: flex; flex-direction: column; align-items: center; gap: 14px;
    max-width: 34ch;
  }
  .small-screen .brand-mark { width: 40px; height: 40px; margin-bottom: 6px; }
  .small-screen h1 {
    margin: 0; font-size: 26px; font-weight: 600; letter-spacing: -0.02em;
  }
  .small-screen p {
    margin: 0; font-size: 14.5px; line-height: 1.65; color: var(--ink-soft);
  }
  .small-screen-note { color: var(--ink-faint) !important; font-size: 13px !important; }
  .small-screen-note strong { color: var(--ink); font-weight: 600; }
}

/* 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); }

/* ── Gemini key block in the account panel ─────────────────── */
.gk-status { margin: 4px 0 8px; font-size: 13px; opacity: .75; }
.gk-status.is-set { opacity: .95; }
.gk-row { display: flex; gap: 6px; }
.gk-input {
  flex: 1; min-width: 0; padding: 7px 10px; font-size: 13px;
  border: 1px solid var(--line, #ccc); border-radius: 8px;
  background: transparent; color: inherit;
}
.btn-sm { padding: 6px 12px; font-size: 12.5px; }
.gk-help { margin-top: 8px; font-size: 11.5px; opacity: .55; line-height: 1.45; }

/* ── per-user key nudge + prompt-bubble actions ────────────── */
.gk-nudge {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 10px 14px; margin: 0 0 8px;
  border: 1px solid var(--line, #ccc); border-radius: 12px;
  font-size: 13.5px; background: var(--panel, rgba(127,127,127,.06));
}
.gk-nudge a { font-weight: 600; }
.msg-actions {
  display: inline-flex; gap: 2px; margin-left: 10px;
  vertical-align: middle; opacity: 0; transition: opacity .15s ease;
}
.msg:hover .msg-actions, .msg-actions:focus-within { opacity: .9; }
.msg-actions button {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0; border: none; border-radius: 6px;
  background: transparent; color: inherit; cursor: pointer; opacity: .65;
}
.msg-actions button:hover { opacity: 1; background: rgba(127,127,127,.18); }
.msg-actions .ic { width: 14px; height: 14px; }
