/* ==========================================================================
   Touchpad — daylight studio

   One theme, one palette: the LeadHunt/maps design system verbatim — cream
   paper, white cards, ink text, lime as the single accent. There is no theme
   switch and no dark block; every rule below is written against these tokens,
   so the palette is the only place a colour is decided.

   Two things carry over from the old chassis because they were right:
     - the accent has TWO jobs. `--web-accent-txt` is a deep olive, because
       lime on white cannot be read; the fill stays lime.
     - "well" is a direction, not a colour. A recessed surface (input, code
       block, tab strip) goes to the paper ground, one step down from a white
       card.
   ========================================================================== */

:root,
[data-bs-theme="light"] {
  --web-black:      #F0EFE9;   /* paper — the page ground */
  --web-panel:      #FFFFFF;   /* card */
  --web-panel-2:    #FAFAF8;
  --web-panel-3:    #F0EFE9;   /* pressed / selected */
  --web-line:       #E3E1D8;
  --web-line-soft:  #EDEBE3;
  --web-line-hi:    #C9C6B8;   /* a line meant to be seen: grips, scrollbars */

  --web-orange:     #C8F31D;   /* lime — THE accent */
  --web-orange-lo:  #A5CC0E;   /* lime-dark: pressed / borders */
  --web-orange-hi:  #D8F94F;   /* hover */
  --web-lime-soft:  #E9FCA5;   /* the accent at rest, behind an avatar or icon */
  --web-amber:      #E8B33A;
  --web-red:        #C4283A;
  --web-ok:         #128257;
  --web-cyan:       #0B7FA8;
  --web-violet:     #7A3FC0;

  --web-text:       #1C1C1A;   /* ink */
  --web-text-2:     #3E3E38;
  --web-hi:         #0A0A08;   /* the strongest text there is */
  /* Muted, not invisible. These were LeadHunt's #8A8A83 / #A3A39A, which work
     on short marketing labels and fail on the paragraphs this studio actually
     has — 2.7:1 and 2.1:1 on cream, both under the 4.5:1 floor. Same greys,
     several steps down, so the hierarchy survives and the text can be read. */
  --web-dim:        #55554E;   /* muted — body-weight secondary text */
  --web-faint:      #6E6E66;   /* hints and timestamps, still readable */

  --web-well:       #F0EFE9;   /* recessed = the paper, under a white card */
  --web-well-2:     #E9E8E0;
  --web-on-accent:  #1C1C1A;   /* ink on a lime fill */

  --web-accent-rgb: 200, 243, 29;   /* the accent as channels, for washes */
  /* The accent does NOT get to be text. An olive that reads as "sort of green,
     sort of grey" was doing the job of a link colour badly; ink does it well,
     and links stay findable by weight and underline instead of by hue. Lime
     remains a fill — chips, meters, the hero — where it is unmistakable. */
  --web-accent-txt: #1C1C1A;
  --web-tint-rgb:   28, 28, 26;     /* ink as channels: every subtle lift wash */
  --web-bar:        rgba(240, 239, 233, .92);
  --web-bar-2:      rgba(240, 239, 233, .78);

  --web-busy-bg:    rgba(200, 243, 29, .35);
  --web-busy-line:  rgba(165, 204, 14, .55);
  --web-busy-txt:   #4E6608;

  --web-ok-txt:     #0F6B48;
  --web-red-txt:    #B21F31;
  --web-warn-txt:   #8A5A00;

  /* Pre-mixed accent washes — never invent a new rgba mix inline. */
  --signal-a08: var(--web-wash);
  --signal-a16: var(--web-wash-2);
  --signal-a32: rgba(var(--web-accent-rgb), 0.50);
  --signal-a60: rgba(var(--web-accent-rgb), 0.80);

  /* Shape. Cards are very round and controls are pills — that IS the design
     system; the old angular notch is gone. */
  --r-card: 1.75rem;
  --r-ctl:  999px;
  --r-well: 1rem;
  /* Surface washes are INK, not the accent. A pale lime fill behind a card,
     a row or a table cell reads as a stain on cream — the accent belongs on
     things you press (buttons, chips, badges, meters), never behind them. */
  --web-wash:   rgba(28, 28, 26, .04);
  --web-wash-2: rgba(28, 28, 26, .07);
  /* ON the lime hero the direction flips: white is the recessed surface there,
     not a no-op the way it is on cream. Tokenised so the "no raw white wash"
     rule still holds — that rule exists to catch a dark theme's lifts ported
     across, and these are the deliberate exception. */
  --web-on-lime:    rgba(255, 255, 255, .75);
  --web-on-lime-hi: rgba(255, 255, 255, .93);

  --shadow-card: 0 1px 2px rgba(28,28,26,.05), 0 8px 24px rgba(28,28,26,.06);
  --shadow-lift: 0 2px 6px rgba(28,28,26,.06), 0 14px 38px rgba(28,28,26,.10);

  --t-fast: 120ms cubic-bezier(0.2, 0, 0.2, 1);
  --t-mid:  200ms cubic-bezier(0.2, 0, 0.2, 1);
  --t-slow: 320ms cubic-bezier(0.16, 1, 0.3, 1);

  --bs-body-bg: var(--web-black);
  --bs-body-color: var(--web-text);
  --bs-border-color: var(--web-line);
  --bs-primary: var(--web-orange);
  --bs-primary-rgb: 200, 243, 29;
  --bs-link-color: var(--web-accent-txt);
  --bs-link-hover-color: #000000;
  /* Bootstrap 5.3 paints links from the *-rgb form, not the hex: set only the
     hex and every link on every classic page stays default blue. Cost an
     afternoon of "why is the theme half-applied". */
  --bs-link-color-rgb: 28, 28, 26;
  --bs-link-hover-color-rgb: 0, 0, 0;
  --bs-body-color-rgb: 28, 28, 26;
  --bs-body-bg-rgb: 240, 239, 233;
  --bs-emphasis-color-rgb: 10, 10, 8;
  --bs-secondary-bg: var(--web-well);
  --bs-tertiary-bg: var(--web-panel-2);
  --bs-secondary-color-rgb: 138, 138, 131;
  --bs-emphasis-color: var(--web-hi);
  --bs-secondary-color: var(--web-dim);
  --bs-code-color: var(--web-accent-txt);
  --bs-font-sans-serif: "Plus Jakarta Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
}

/* ---------- page chassis ----------
   Cream, with one soft lime bloom in the top-right corner. No grain, no
   scanlines, no vignette: those were the point of the black theme and dirt on
   a light one. */
body {
  background-color: var(--web-black);
  color: var(--web-text);
  letter-spacing: -.006em;
}

.mono, code, pre, .form-control.mono, .form-select.mono {
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: .8125rem;
}

/* ---------- the top bar: the mark, the nav, the account ----------

   Slim on purpose. It was removed once in favour of nav-inside-the-avatar-menu
   and going anywhere became two clicks with one hidden — the bar is for moving
   around, the avatar menu keeps the account things. */

/* One measure for the whole studio. Everything — the bar's contents and the
   page under it — sits in the same centred column, so the nav lines up with
   the first card instead of hugging the window edge. */
:root { --measure: 72rem; }

.topbar {
  position: sticky; top: 0; z-index: 60;
  background: var(--web-bar); backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--web-line-soft);
  display: flex; align-items: center; gap: .9rem;
  /* Its own cap, wider than the body's — see --measure-bar. */
  max-width: var(--measure-bar); margin: 0 auto; padding: .5rem 1rem .6rem;
  width: 100%;
}
/* The bar is centred but its blur/hairline must span the window, so the
   sticky background is drawn by a full-width wrapper behind it. */
.topbar-wrap {
  position: sticky; top: 0; z-index: 60;
  background: var(--web-bar); backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--web-line-soft);
  /* Air above the bar. On the WRAPPER, not as a margin: the wrapper is what
     sticks, and a margin would let the page scroll up into the gap. */
  padding-top: .5rem;
}
.topbar-wrap .topbar {
  position: static; background: none; backdrop-filter: none; border-bottom: 0;
}

/* Every page body sits in the same column. */
main.container-fluid, main.studio-main {
  max-width: var(--measure); margin: 0 auto; width: 100%;
}
/* The brand is not a nav item, so it is separated from them by a hairline
   rather than by a guessed gap — seven links and a wordmark in one 52rem row
   read as eight links otherwise. */
.topbar-mark {
  display: inline-flex; align-items: center; gap: .55rem; text-decoration: none;
  color: var(--web-hi); font-weight: 800; letter-spacing: -.03em; font-size: 1.05rem;
  flex: none; padding-right: 1rem; margin-right: .2rem; border-right: 1px solid var(--web-line);
}
.topbar-mark .mark { width: 28px; height: 28px; border-radius: 9px; box-shadow: 0 1px 2px rgba(28,28,26,.12); }
/* `min-width: 0` so the row can shrink before it overflows: without it a
   flex item refuses to go below its content width and the avatar is pushed
   off the right edge instead. */
.topbar-nav { display: flex; gap: .1rem; align-items: center; flex: 1 1 auto; min-width: 0; }
.topbar-nav a {
  text-decoration: none; color: var(--web-dim); font-size: .84rem; font-weight: 600;
  padding: .42rem .72rem; border-radius: 999px; border: 0;
  display: inline-flex; align-items: center; gap: .34rem; white-space: nowrap;
}
.topbar-nav a:hover { color: var(--web-text); background: var(--web-panel); }
/* Anchored to the right edge with its own gap, so it reads as the account
   corner rather than as an eighth nav item trailing the seventh. */
.topbar .usermenu { flex: none; margin-left: .5rem; }
/* The month's two bills, beside the account: one quiet pill, two figures.
   A wash of ink, never lime — a number is not something you press, and the
   link only opens the meter it is a summary of. */
.spend {
  flex: none; display: inline-flex; align-items: center; gap: .7rem;
  padding: .36rem .8rem; border-radius: 999px; background: var(--web-wash);
  text-decoration: none; color: var(--web-text); font-size: .78rem;
  transition: background var(--t-fast);
}
.spend:hover { background: var(--web-wash-2); color: var(--web-text); }
.spend-item { display: inline-flex; align-items: baseline; gap: .3rem; }
.spend-k { font-size: .66rem; font-weight: 800; letter-spacing: .05em; color: var(--web-dim); }
.spend .mono { font-weight: 700; }
@media (max-width: 940px) { .spend { order: 2; } }
@media (max-width: 560px) { .spend { display: none; } }
/* The icon is the quiet half of the pair — full strength would make seven of
   them compete with the labels they are there to help. */
.topbar-nav a .bi { font-size: .9em; opacity: .75; }
.topbar-nav a.active .bi { opacity: 1; }
/* The page you are on is a solid ink pill — the same "selected" language the
   chips and buttons use. A lime pill would compete with the accent's job. */
.topbar-nav a.active { color: var(--web-black); background: var(--web-text); }
/* Narrow: the nav drops to its own line and wraps rather than pushing the
   account off the edge and taking the whole page sideways with it. The
   breakpoint sits just under --measure-bar on purpose — between "the row
   fits" and "the row wraps" there must be no band where it neither fits nor
   wraps, which is exactly where it used to clip. */
@media (max-width: 940px) {
  .topbar { flex-wrap: wrap; gap: .5rem .8rem; padding: .55rem .9rem; }
  /* Wrapped, the brand is no longer beside the nav, so the hairline that
     separated them has nothing left to separate. */
  .topbar-mark { flex: 1 1 auto; padding-right: 0; border-right: 0; }
  .topbar-nav { order: 3; width: 100%; flex-wrap: wrap; gap: .3rem; }
  .topbar-nav a { padding: .32rem .7rem; font-size: .8rem; }
}
.usermenu { position: relative; flex: none; }
.usermenu > summary {
  list-style: none; cursor: pointer; display: block; border-radius: 50%;
  transition: box-shadow .16s ease;
}
.usermenu > summary::-webkit-details-marker { display: none; }
.usermenu > summary:hover, .usermenu[open] > summary {
  box-shadow: 0 0 0 2px rgba(var(--web-accent-rgb), .45), 0 0 20px rgba(var(--web-accent-rgb), .28);
}
.userpic {
  position: relative; display: grid; place-items: center; overflow: hidden;
  width: 34px; height: 34px; border-radius: 50%;
  border: 0; background: var(--web-lime-soft);
}
.userpic.lg { width: 42px; height: 42px; border-radius: 50%; }
.userpic img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.userpic-letter { font-size: .82rem; font-weight: 800; color: var(--web-on-accent); }

.usercard {
  position: absolute; top: calc(100% + .55rem); right: 0; width: 17.5rem;
  border: 0; border-radius: 1.25rem; overflow: hidden;
  background: var(--web-panel); box-shadow: var(--shadow-lift);
}
.usercard-head {
  display: flex; align-items: center; gap: .6rem; padding: .8rem .85rem;
  border-bottom: 1px solid var(--web-line-soft);
}
.usercard-name { display: block; font-weight: 640; color: var(--web-hi); font-size: .88rem; }
.usercard-plan { display: block; font-size: .72rem; color: var(--web-accent-txt); }

.usercard-meter { padding: .7rem .85rem; border-bottom: 1px solid var(--web-line-soft); }
.meter-line {
  display: flex; justify-content: space-between; align-items: center; gap: .5rem;
  font-size: .72rem; color: var(--web-dim); margin-bottom: .3rem;
}
.meter-line .mono, .meter-line .mock { white-space: nowrap; flex: none; }
.meter {
  height: 5px; border-radius: 999px; background: rgba(var(--web-tint-rgb),.07); overflow: hidden;
}
.meter > span {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--web-orange-lo), var(--web-orange-hi));
}
/* Says out loud that the numbers beside it are invented. */
.mock {
  font-size: .6rem; text-transform: uppercase; letter-spacing: .1em; font-weight: 640;
  color: var(--web-faint); border: 1px dashed var(--web-line); border-radius: 999px;
  padding: .05rem .4rem;
}

.usercard-nav { display: flex; flex-direction: column; padding: .3rem; }
.usercard-nav + .usercard-nav { border-top: 1px solid var(--web-line-soft); }
.usercard-nav a {
  display: flex; align-items: center; gap: .5rem; text-decoration: none;
  color: var(--web-text); font-size: .82rem; padding: .4rem .55rem; border-radius: 9px;
}
.usercard-nav a:hover { background: var(--web-wash-2); color: var(--web-hi); }
.usercard-nav a i.bi { color: var(--web-faint); font-size: .9rem; }
.usercard-nav a:hover i.bi, .usercard-nav a.active i.bi { color: var(--web-accent-txt); }
.usercard-nav a.active { color: var(--web-hi); background: var(--web-wash-2); }
.usercard-nav.muted a { color: var(--web-dim); font-size: .78rem; }

/* ---------- panels ---------- */

.card {
  background: linear-gradient(180deg, var(--web-panel), var(--web-well));
  border: 1px solid var(--web-line); border-radius: 14px;
  box-shadow: 0 10px 30px rgba(28,26,20,.10);
}
.card-header {
  background: transparent; border-bottom: 1px solid var(--web-line-soft);
  font-weight: 640; font-size: .875rem; letter-spacing: -.01em;
  color: var(--web-hi); padding: .8rem 1rem;
}
.card-header i.bi { color: var(--web-accent-txt); margin-right: .35rem; }
.card-footer { background: rgba(var(--web-tint-rgb),.015); border-top: 1px solid var(--web-line-soft); }
.list-group-item { background: transparent; border-color: var(--web-line-soft); color: var(--web-text); }
.list-group-item-action:hover { background: var(--web-wash); color: var(--web-hi); }

/* ---------- type ---------- */

.small-muted { color: var(--web-dim); font-size: .8125rem; }
th {
  font-size: .68rem; text-transform: uppercase; letter-spacing: .1em;
  color: var(--web-faint); font-weight: 640; border-color: var(--web-line-soft) !important;
}
td { vertical-align: middle; border-color: var(--web-line-soft) !important; }
.table > :not(caption) > * > * { background-color: transparent; color: var(--web-text); }
.table-hover > tbody > tr:hover > * { background-color: var(--web-wash-2); color: var(--web-hi); }
.url-cell { max-width: 340px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- controls ---------- */

.form-control, .form-select {
  background-color: var(--web-well); border-color: var(--web-line); color: var(--web-text);
  border-radius: 9px; font-size: .875rem;
}
.form-control:focus, .form-select:focus {
  background-color: var(--web-well); color: var(--web-hi); border-color: var(--web-orange);
  box-shadow: 0 0 0 3px rgba(var(--web-accent-rgb), .18);
}
.form-control::placeholder { color: var(--web-faint); }
.form-label, .field-row label, .child-block label {
  font-size: .68rem; text-transform: uppercase; letter-spacing: .1em;
  color: var(--web-faint); font-weight: 600; margin-bottom: .22rem;
}
.form-text { color: var(--web-faint); font-size: .78rem; }
.form-check-input { background-color: var(--web-well); border-color: var(--web-line); }
.form-check-input:checked { background-color: var(--web-orange); border-color: var(--web-orange); }
.form-check-label { font-size: .8rem; color: var(--web-dim); }

.btn { border-radius: 9px; font-weight: 600; font-size: .84rem; letter-spacing: -.01em; }
.btn-primary, .btn-success {
  border: 0; color: var(--web-on-accent);
  background: linear-gradient(135deg, var(--web-orange-hi), var(--web-orange) 45%, var(--web-orange-lo));
  box-shadow: 0 4px 18px rgba(var(--web-accent-rgb), .32);
}
.btn-primary:hover, .btn-success:hover {
  color: var(--web-on-accent); filter: brightness(1.08);
  box-shadow: 0 6px 26px rgba(var(--web-accent-rgb), .45);
}
.btn-outline-primary { color: var(--web-accent-txt); border-color: rgba(var(--web-accent-rgb), .45); }
.btn-outline-primary:hover { background: var(--web-wash-2); color: var(--web-hi); border-color: var(--web-orange); }
.btn-outline-secondary { color: var(--web-dim); border-color: var(--web-line); }
.btn-outline-secondary:hover { background: rgba(var(--web-tint-rgb),.06); color: var(--web-hi); border-color: var(--web-line-hi); }
.btn-outline-danger { color: var(--web-red-txt); border-color: rgba(224,36,61,.4); }
.btn-outline-danger:hover { background: rgba(224,36,61,.18); color: var(--web-hi); border-color: var(--web-red); }

/* ---------- status pills ---------- */

.status-badge {
  font-size: .68rem; font-weight: 680; padding: .22rem .55rem; border-radius: 999px;
  text-transform: lowercase; letter-spacing: .02em; border: 1px solid transparent;
  display: inline-flex; align-items: center; gap: .3rem; white-space: nowrap;
}
.status-badge::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
.status-ready, .status-done { background: rgba(46,204,113,.12); color: var(--web-ok-txt); border-color: rgba(74,222,128,.28); }
.status-failed { background: rgba(224,36,61,.13); color: var(--web-red-txt); border-color: rgba(224,36,61,.32); }
.status-queued, .status-running, .status-pending, .status-loading,
.status-analyzing, .status-generating, .status-validating, .status-verifying {
  background: var(--web-busy-bg); color: var(--web-busy-txt); border-color: var(--web-busy-line);
}
.status-running::before, .status-queued::before,
.status-loading::before, .status-analyzing::before, .status-generating::before,
.status-validating::before, .status-verifying::before { animation: pulse 1.25s ease-in-out infinite; }
@keyframes pulse { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: .25; transform: scale(.7); } }

.chip {
  display: inline-block; font-size: .72rem; padding: .16rem .5rem; border-radius: 6px;
  border: 1px solid var(--web-line); background: rgba(var(--web-tint-rgb),.03);
  color: var(--web-dim); margin: 0 .25rem .3rem 0;
}
.chip-accent {
  border-color: rgba(var(--web-accent-rgb), .38); background: var(--web-wash-2); color: var(--web-accent-txt);
}

/* ---------- code + console ---------- */

.console, .code-block {
  background: var(--web-well-2); color: var(--web-text-2); border: 1px solid var(--web-line-soft);
  padding: .9rem 1rem; margin: 0; overflow: auto; white-space: pre-wrap;
  font-family: "JetBrains Mono", ui-monospace, Menlo, monospace; font-size: .78rem; line-height: 1.55;
}
.console { max-height: 340px; border-radius: 0 0 13px 13px; }
.code-block { max-height: 560px; white-space: pre; }
.console::-webkit-scrollbar, .code-block::-webkit-scrollbar,
.table-responsive::-webkit-scrollbar { width: 9px; height: 9px; }
.console::-webkit-scrollbar-thumb, .code-block::-webkit-scrollbar-thumb,
.table-responsive::-webkit-scrollbar-thumb { background: var(--web-line-hi); border-radius: 8px; }
.console::-webkit-scrollbar-thumb:hover, .code-block::-webkit-scrollbar-thumb:hover { background: var(--web-orange-lo); }

.feedback-block {
  background: var(--web-wash);
  border-left: 2px solid var(--web-orange);
  padding: .7rem 1rem; font-size: .84rem; color: var(--web-warn-txt); white-space: pre-wrap;
}
.alert { border-radius: 10px; border-width: 1px; font-size: .85rem; }
.alert-danger { background: rgba(224,36,61,.12); border-color: rgba(224,36,61,.35); color: var(--web-red-txt); }
.alert-success { background: rgba(46,204,113,.10); border-color: rgba(74,222,128,.3); color: var(--web-ok-txt); }
.alert-warning { background: var(--web-wash-2); border-color: rgba(var(--web-accent-rgb), .32); color: var(--web-warn-txt); }

/* ---------- schema editor ---------- */

.field-row {
  background: linear-gradient(180deg, var(--web-panel-2), var(--web-well));
  border: 1px solid var(--web-line-soft); border-left: 2px solid rgba(var(--web-accent-rgb), .35);
  border-radius: 10px; padding: .8rem; margin-bottom: .6rem; transition: border-color .16s ease;
}
.field-row:hover { border-left-color: var(--web-orange); }
.child-block {
  background: var(--web-wash);
  border: 1px dashed var(--web-line-hi); border-radius: var(--r-well);
  padding: 1rem; margin-bottom: 1rem;
}
details summary { cursor: pointer; color: var(--web-accent-txt); }

/* ---------- misc ---------- */

a { text-decoration: none; }
a:hover { text-decoration: underline; }
.section-title { font-size: 1.05rem; font-weight: 700; letter-spacing: -.03em; color: var(--web-hi); }
.htmx-request .spinner-hide { display: none; }
.htmx-indicator { opacity: 0; transition: opacity .15s; }
.htmx-request .htmx-indicator, .htmx-request.htmx-indicator { opacity: 1; }
/* A long request's "I heard you" line — shown under the button while it runs.
   Keyed off the button hx-disabled-elt disables, not only the htmx-request
   class: the disable observably fires, and the note must never fail to show
   while a minute-long LLM call is in flight. */
.working-note {
  display: flex; align-items: center; gap: .5rem; margin-top: .6rem;
  font-size: .78rem; color: var(--web-accent-txt);
}
form:has(> button:disabled) .working-note,
form:has(button:disabled) .working-note { opacity: 1; }

/* The edit page's folded-away side doors (paste / describe-to-AI). */
.rebuild > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: inline-flex; align-items: center; gap: .45rem;
  font-size: .82rem; color: var(--web-dim); padding: .45rem .7rem;
  border: 1px dashed var(--web-line); border-radius: 10px; transition: .15s ease;
}
.rebuild > summary::-webkit-details-marker { display: none; }
.rebuild > summary:hover { color: var(--web-accent-txt); border-color: rgba(var(--web-accent-rgb), .45); }
.rebuild[open] > summary { color: var(--web-hi); border-style: solid; border-color: rgba(var(--web-accent-rgb), .4); }
.rebuild > summary i.bi { color: var(--web-accent-txt); }

/* Back-office blueprint cards — sketches of controls that do not exist yet. */
.blueprint { border-style: dashed; opacity: .92; }
.blueprint i.bi { color: var(--web-accent-txt); }
.blueprint-title { font-weight: 640; color: var(--web-hi); font-size: .875rem; }

/* ---------- the calendar: schedules on a month ---------- */

.cal-title { font-weight: 700; color: var(--web-hi); letter-spacing: -.02em; min-width: 9rem; text-align: center; }
.cal-grid {
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
  border-top: 1px solid var(--web-line-soft);
}
.cal-head {
  padding: .45rem .6rem; font-size: .64rem; text-transform: uppercase;
  letter-spacing: .1em; color: var(--web-faint); font-weight: 640;
  border-bottom: 1px solid var(--web-line-soft);
}
.cal-day {
  min-height: 4.6rem; padding: .4rem .45rem .5rem;
  border-bottom: 1px solid var(--web-line-soft); border-right: 1px solid var(--web-line-soft);
  display: flex; flex-direction: column; gap: .25rem; min-width: 0;
}
.cal-day:nth-child(7n) { border-right: 0; }
.cal-day.out { background: rgba(28,26,20,.05); }
.cal-day.out .cal-num { color: var(--web-faint); opacity: .55; }
.cal-day.today { background: var(--web-wash); box-shadow: inset 0 0 0 1px rgba(var(--web-accent-rgb), .35); }
.cal-day.today .cal-num {
  color: var(--web-hi); background: var(--web-orange); border-radius: 999px;
  width: 1.35rem; height: 1.35rem; display: grid; place-items: center;
}
.cal-num { font-size: .74rem; color: var(--web-dim); font-variant-numeric: tabular-nums; flex: none; }
.cal-chip {
  display: flex; align-items: center; gap: .3rem; min-width: 0;
  font-size: .68rem; text-decoration: none; color: var(--web-text);
  border: 1px solid var(--web-line); border-radius: 7px; padding: .14rem .3rem;
  background: rgba(var(--web-tint-rgb),.03); transition: .13s ease;
}
.cal-chip:hover { border-color: rgba(var(--web-accent-rgb), .5); background: var(--web-wash-2); color: var(--web-hi); }
/* Yesterday's projected runs — already happened (or would have); kept faint
   so the eye lands on what is still coming. */
.cal-chip.past { opacity: .45; }
.cal-chip-host { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-chip-time { margin-left: auto; color: var(--web-faint); font-variant-numeric: tabular-nums; flex: none; }
.site-logo.sm { width: 1.05rem; height: 1.05rem; border-radius: 4px; flex: none; }
.site-logo.sm .site-letter { font-size: .55rem; }

@media (max-width: 900px) {
  .cal-day { min-height: 4.2rem; }
  .cal-chip-host { display: none; }   /* the logo and the time still say it */
}
.working-note .spinner-border { --bs-spinner-border-width: 2px; flex: none; }

/* ==========================================================================
   Agent pipeline graph
   ========================================================================== */

.agent-graph {
  display: flex; align-items: stretch; gap: 0; flex-wrap: wrap;
  padding: 1rem 1.1rem; position: relative;
}
.ag-node {
  display: flex; align-items: center; gap: .6rem; min-width: 0; flex: 1 1 150px;
  padding: .55rem .7rem; border-radius: 11px;
  border: 1px solid var(--web-line-soft); background: rgba(var(--web-tint-rgb),.018);
  transition: border-color .2s ease, background .2s ease, box-shadow .2s ease;
}
.ag-dot {
  position: relative; flex: none; width: 34px; height: 34px; border-radius: 10px;
  display: grid; place-items: center; font-size: .95rem;
  border: 1px solid var(--web-line); background: var(--web-well); color: var(--web-faint);
}
.ag-label { font-size: .8rem; font-weight: 650; color: var(--web-dim); letter-spacing: -.01em; }
.ag-blurb {
  font-size: .68rem; color: var(--web-faint); line-height: 1.25;
  overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}

.ag-done .ag-dot { color: var(--web-ok-txt); border-color: rgba(74,222,128,.4); background: rgba(46,204,113,.1); }
.ag-done .ag-label { color: var(--web-text-2); }

.ag-active {
  border-color: rgba(var(--web-accent-rgb), .5); background: var(--web-wash);
  box-shadow: 0 0 22px rgba(var(--web-accent-rgb), .18);
}
.ag-active .ag-dot { color: var(--web-accent-txt); border-color: var(--web-orange); background: var(--web-wash-2); }
.ag-active .ag-label { color: var(--web-hi); }

.ag-failed { border-color: rgba(224,36,61,.45); background: rgba(224,36,61,.08); }
.ag-failed .ag-dot { color: var(--web-red-txt); border-color: rgba(224,36,61,.5); background: rgba(224,36,61,.14); }
.ag-failed .ag-label { color: var(--web-red-txt); }

.ag-ping {
  position: absolute; inset: -4px; border-radius: 13px;
  border: 1px solid var(--web-orange); animation: agping 1.5s ease-out infinite;
}
@keyframes agping {
  0%   { transform: scale(.85); opacity: .9; }
  100% { transform: scale(1.25); opacity: 0; }
}

.ag-edge { flex: 0 0 26px; display: grid; place-items: center; }
.ag-edge span { display: block; width: 100%; height: 2px; border-radius: 2px; }
.ag-edge-dim span { background: repeating-linear-gradient(90deg, var(--web-line) 0 4px, transparent 4px 8px); }
.ag-edge-lit span {
  background: linear-gradient(90deg, rgba(74,222,128,.25), var(--web-orange));
  box-shadow: 0 0 10px rgba(var(--web-accent-rgb), .5);
}

.agent-graph-single .ag-node { flex: 0 1 260px; }

/* --- status bar above the graph: attempts + ETA --- */
.ag-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  flex-wrap: wrap; padding: .6rem 1.1rem .1rem;
}
.ag-attempts {
  display: inline-flex; align-items: center; gap: .45rem;
  font-size: .74rem; letter-spacing: .04em; color: var(--web-dim);
  border: 1px solid var(--web-line); border-radius: 999px; padding: .3rem .75rem;
  background: rgba(var(--web-tint-rgb),.02);
}
.ag-attempts b { color: var(--web-hi); font-weight: 700; }
.ag-attempts-hot {
  color: var(--web-accent-txt); border-color: rgba(var(--web-accent-rgb), .5);
  background: var(--web-wash-2); box-shadow: 0 0 16px rgba(var(--web-accent-rgb), .18);
}
.ag-attempts-hot b { color: var(--web-accent-txt); }
.ag-attempts-hot i { animation: agspin 2.4s linear infinite; }
@keyframes agspin { to { transform: rotate(360deg); } }
.ag-retry-tag {
  font-size: .64rem; text-transform: uppercase; letter-spacing: .1em;
  padding-left: .5rem; margin-left: .2rem; border-left: 1px solid rgba(var(--web-accent-rgb), .35);
}

.ag-eta {
  display: inline-flex; align-items: center; gap: .4rem;
  font-size: .74rem; color: var(--web-dim);
}
.ag-eta i { color: var(--web-accent-txt); }
.ag-eta-value {
  font-weight: 700; color: var(--web-hi); font-family: "JetBrains Mono", monospace; font-size: .8rem;
}
.ag-eta-src {
  font-size: .62rem; text-transform: uppercase; letter-spacing: .1em;
  color: var(--web-faint); border: 1px solid var(--web-line-soft);
  border-radius: 5px; padding: .1rem .35rem; margin-left: .3rem;
}

/* --- per-node timing --- */
.ag-time {
  font-size: .66rem; font-weight: 600; font-family: "JetBrains Mono", monospace;
  color: var(--web-faint); margin-left: .35rem; white-space: nowrap;
}
.ag-time-done { color: var(--web-ok-txt); }
.ag-time-failed { color: var(--web-red-txt); }
.ag-time-active { color: var(--web-accent-txt); }
.ag-progress {
  height: 3px; border-radius: 3px; background: rgba(var(--web-tint-rgb),.07);
  margin-top: .3rem; overflow: hidden;
}
.ag-progress span {
  display: block; height: 100%; border-radius: 3px;
  background: linear-gradient(90deg, var(--web-orange-lo), var(--web-orange-hi));
  transition: width .6s ease;
}

/* ==========================================================================
   View switcher + auto-generated item UI
   ========================================================================== */

.viewtabs { display: inline-flex; gap: .2rem; padding: .2rem; border: 1px solid var(--web-line); border-radius: 10px; background: var(--web-well); }
.viewtab {
  font-size: .78rem; font-weight: 600; padding: .3rem .7rem; border-radius: 8px;
  color: var(--web-dim); text-decoration: none; border: 1px solid transparent;
}
.viewtab:hover { color: var(--web-hi); text-decoration: none; background: rgba(var(--web-tint-rgb),.05); }
.viewtab.active {
  color: var(--web-on-accent); background: linear-gradient(135deg, var(--web-orange-hi), var(--web-orange));
  box-shadow: 0 2px 12px rgba(var(--web-accent-rgb), .3);
}

.json-view {
  background: var(--web-well-2); border: 1px solid var(--web-line-soft); border-radius: 0 0 13px 13px;
  padding: 1rem; margin: 0; max-height: 640px; overflow: auto;
  font-family: "JetBrains Mono", ui-monospace, monospace; font-size: .78rem; line-height: 1.5;
  color: var(--web-text-2); white-space: pre;
}
.json-key { color: var(--web-accent-txt); }
.json-str { color: var(--web-ok-txt); }
.json-num { color: var(--web-cyan); }
.json-bool, .json-null { color: var(--web-violet); }

.autoform-row { display: flex; gap: .5rem; align-items: flex-start; margin-bottom: .4rem; }
.autoform-row .form-control { font-size: .82rem; }
.badge-req { font-size: .6rem; color: var(--web-accent-txt); letter-spacing: .06em; }

/* ---------- browser screenshots: a fixed stage, like a video ----------
   The whole point is that nothing moves. The stage has a fixed height and the
   image is contained inside it, so a tall full-page recipe and a short viewport
   listing occupy exactly the same box — a new frame replaces the picture in
   place instead of growing the card and pushing the page down.              */

.shot-stream { padding: 1rem; }

.shot-stage {
  position: relative;
  height: 340px;                 /* fixed: the frame changes, the box does not */
  border: 1px solid var(--web-line); border-radius: 10px; overflow: hidden;
  background: var(--web-well-2);
  display: flex; align-items: center; justify-content: center;
}
.shot-stage > a {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%; min-width: 0; min-height: 0;
}
/* Fixed box, natural image. It is scaled DOWN to fit when it is too big and
   left alone when it already fits — never stretched to fill, which turned a
   full-page capture into a tall thin sliver. */
.shot-stage img {
  display: block;
  max-width: 100%; max-height: 100%;
  width: auto; height: auto;
  object-fit: contain;
  border-radius: 4px;
}
.shot-empty { border-style: dashed; }
.shot-empty p { margin: 0; }

.shot-bar {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; align-items: center; gap: .6rem;
  padding: .35rem .6rem;
  background: linear-gradient(0deg, rgba(7,7,10,.94), rgba(7,7,10,.55) 70%, transparent);
  font-size: .7rem; font-family: "JetBrains Mono", monospace; color: var(--web-dim);
}
.shot-counter {
  color: var(--web-on-accent); font-weight: 700; padding: .1rem .45rem; border-radius: 5px;
  background: linear-gradient(135deg, var(--web-orange-hi), var(--web-orange));
  white-space: nowrap;
}
.shot-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shot-gif { color: var(--web-accent-txt); text-decoration: none; white-space: nowrap; }
.shot-gif:hover { color: var(--web-hi); text-decoration: none; }

.shot-kept { border-color: rgba(74,222,128,.35); }
.shot-rejected, .shot-failed { border-color: rgba(224,36,61,.45); }
.shot-kept .shot-name { color: var(--web-ok-txt); }
.shot-rejected .shot-name, .shot-failed .shot-name { color: var(--web-red-txt); }
.shot-playing img { max-width: 100%; max-height: 100%; width: auto; height: auto; }

/* Filmstrip: fixed height and horizontally scrollable, so adding frames never
   changes the card's height either. */
.shot-strip {
  display: flex; gap: .35rem; margin-top: .5rem;
  height: 46px; overflow-x: auto; overflow-y: hidden;
}
.shot-strip a {
  flex: 0 0 auto; width: 58px; height: 40px; border-radius: 5px; overflow: hidden;
  border: 1px solid var(--web-line); opacity: .5; transition: .15s ease;
}
.shot-strip a:hover { opacity: 1; border-color: var(--web-orange); }
.shot-strip a.is-current { opacity: 1; border-color: var(--web-orange); box-shadow: 0 0 8px rgba(var(--web-accent-rgb), .35); }
.shot-strip img { width: 100%; height: 100%; object-fit: cover; object-position: top center; }

/* ---------- what the scraper is doing on the site ---------- */
.steps-panel { padding: .8rem 1rem; }
.steps-head { display: flex; align-items: center; gap: .6rem; margin-bottom: .55rem; }
.steps-progress {
  flex: 1 1 auto; height: 4px; border-radius: 3px; background: rgba(var(--web-tint-rgb),.07); overflow: hidden;
}
.steps-progress > span {
  display: block; height: 100%; border-radius: 3px; transition: width .5s ease;
  background: linear-gradient(90deg, var(--web-orange-lo), var(--web-orange-hi));
}
.steps-meta { font-size: .68rem; font-family: "JetBrains Mono", monospace; color: var(--web-faint); white-space: nowrap; }

.steps { list-style: none; margin: 0; padding: 0; }
.step { display: flex; gap: .55rem; align-items: flex-start; padding: .22rem 0; }
.step-mark { flex: none; width: 18px; height: 18px; display: grid; place-items: center; font-size: .7rem; margin-top: .1rem; }
.step-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--web-line); display: block; }
.step-spin {
  width: 11px; height: 11px; border-radius: 50%; display: block;
  border: 2px solid rgba(var(--web-accent-rgb), .25); border-top-color: var(--web-orange);
  animation: stepspin .8s linear infinite;
}
@keyframes stepspin { to { transform: rotate(360deg); } }
.step-body { min-width: 0; display: flex; flex-direction: column; }
/* A flex/grid child's default min-width is auto — "as wide as my content" —
   so one unbroken 300-character URL pushed the whole monitor card past the
   panel edge instead of being clipped. Every link in the chain has to opt out. */
.monitor-card, .monitor-step, .monitor-step > span, .steps-panel { min-width: 0; }
.monitor-card { overflow: hidden; }
.monitor-step { display: flex; gap: .45rem; align-items: flex-start; }
.step-label { font-size: .8rem; color: var(--web-dim); line-height: 1.3; }
.step-detail {
  font-size: .68rem; color: var(--web-faint); font-family: "JetBrains Mono", monospace;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.step-done .step-mark { color: var(--web-ok-txt); }
.step-done .step-label { color: var(--web-text-2); }
.step-active .step-label { color: var(--web-hi); font-weight: 600; }
.step-failed .step-mark { color: var(--web-red-txt); }
.step-failed .step-label { color: var(--web-red-txt); }
.steps-note { margin: .5rem 0 0; font-size: .72rem; color: var(--web-faint); font-family: "JetBrains Mono", monospace; }

/* ---------- run monitor ---------- */
.monitor-card {
  border: 1px solid var(--web-line-soft); border-left: 2px solid var(--web-line);
  border-radius: 10px; padding: .7rem .85rem; background: rgba(var(--web-tint-rgb),.015); height: 100%;
}
.monitor-card.is-running { border-left-color: var(--web-orange); background: var(--web-wash); }
.monitor-step { display: flex; gap: .5rem; align-items: flex-start; font-size: .78rem; color: var(--web-dim); }
.monitor-step strong { color: var(--web-hi); font-weight: 600; }

/* ---------- smooth frame changes ----------
   The stage only re-renders when a genuinely new frame exists (the server
   answers 204 otherwise), so a fade here reads as the picture changing rather
   than the panel blinking. */
.shot-stage img { animation: shotfade .32s ease-out; }
@keyframes shotfade { from { opacity: .35; } to { opacity: 1; } }
.shot-strip a { transition: opacity .2s ease, border-color .2s ease; }

/* Step rows move often; keep their motion continuous rather than snapping. */
.steps-progress > span { transition: width .45s cubic-bezier(.4,0,.2,1); }
.step-label, .step-detail { transition: color .25s ease; }

/* htmx swaps: settle new content in instead of popping it. */
.steps-panel .steps, .steps-panel .steps-head { animation: stepsettle .25s ease-out; }
@keyframes stepsettle { from { opacity: .55; } to { opacity: 1; } }

/* A poll that changes nothing must not flash the request indicator. */
.shot-stream.htmx-request, .steps-panel.htmx-request { opacity: 1; }

/* ---------- bulk option on the build form ---------- */
.bulk-block {
  border: 1px dashed var(--web-line); border-radius: 9px; padding: .5rem .75rem;
  background: rgba(var(--web-tint-rgb),.015);
}
.bulk-block summary {
  cursor: pointer; font-size: .78rem; color: var(--web-accent-txt); list-style: none;
}
.bulk-block summary::-webkit-details-marker { display: none; }
.bulk-block summary::before { content: "＋ "; font-weight: 700; }
.bulk-block[open] summary::before { content: "− "; }

/* ==========================================================================
   Property editor — Notion-ish: one line per property, detail on demand
   ========================================================================== */

.prop-list { margin-bottom: .35rem; }

.prop {
  border-bottom: 1px solid var(--web-line-soft);
  padding: .3rem 0; transition: background .15s ease;
}
.prop:hover { background: rgba(var(--web-tint-rgb),.02); }
.prop:last-of-type { border-bottom: 0; }

.prop-main { display: flex; align-items: center; gap: .45rem; }
.prop-grip { color: var(--web-line-hi); cursor: grab; font-size: .9rem; line-height: 1; }
.prop:hover .prop-grip { color: var(--web-faint); }
.prop-icon {
  width: 22px; height: 22px; flex: none; display: grid; place-items: center;
  border-radius: 5px; background: rgba(var(--web-tint-rgb),.04); color: var(--web-accent-txt);
  font-size: .78rem;
}

.prop-name {
  flex: 1 1 auto; min-width: 6rem;
  background: transparent; border: 1px solid transparent; border-radius: 6px;
  color: var(--web-hi); font-size: .84rem; padding: .25rem .4rem;
}
.prop-name:focus {
  outline: none; background: var(--web-well);
  border-color: var(--web-orange); box-shadow: 0 0 0 3px rgba(var(--web-accent-rgb), .15);
}
.prop-name::placeholder { color: var(--web-faint); }

.prop-type {
  flex: none; width: 6.5rem;
  background: rgba(var(--web-tint-rgb),.04); border: 1px solid var(--web-line-soft);
  color: var(--web-dim); border-radius: 6px; font-size: .72rem;
  padding: .2rem .3rem; font-family: "JetBrains Mono", monospace;
}
.prop-type:focus { outline: none; border-color: var(--web-orange); color: var(--web-hi); }

.prop-flag {
  flex: none; display: inline-flex; align-items: center; gap: .25rem;
  font-size: .66rem; text-transform: uppercase; letter-spacing: .08em;
  color: var(--web-faint); cursor: pointer; user-select: none;
}
.prop-flag input { accent-color: var(--web-orange); }

.prop-actions { flex: none; opacity: 0; transition: opacity .15s ease; }
.prop:hover .prop-actions, .prop:focus-within .prop-actions { opacity: 1; }
.prop-btn {
  background: transparent; border: 0; color: var(--web-faint);
  padding: .15rem .35rem; border-radius: 5px; font-size: .8rem;
}
.prop-btn:hover { color: var(--web-red-txt); background: rgba(224,36,61,.12); }

.prop-more { margin: .1rem 0 .2rem 3.1rem; }
.prop-more summary {
  cursor: pointer; list-style: none; font-size: .68rem; color: var(--web-faint);
  padding: .1rem 0;
}
.prop-more summary::-webkit-details-marker { display: none; }
.prop-more summary::before { content: "⋯ "; letter-spacing: .1em; }
.prop-more[open] summary::before { content: "⌄ "; }
.prop-more summary:hover { color: var(--web-accent-txt); }

.prop-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: .5rem; padding: .5rem 0 .3rem;
}
.prop-grid label {
  display: block; font-size: .64rem; text-transform: uppercase; letter-spacing: .08em;
  color: var(--web-faint); margin-bottom: .15rem;
}
.prop-wide { grid-column: 1 / -1; }

.prop-add {
  background: transparent; border: 0; color: var(--web-faint);
  font-size: .78rem; padding: .35rem .2rem; border-radius: 6px; width: 100%;
  text-align: left; transition: color .15s ease, background .15s ease;
}
.prop-add:hover { color: var(--web-accent-txt); background: var(--web-wash); }

/* ==========================================================================
   Conversation layout
   Home reads as Touchpad talking to you and attaching widgets to what it says.
   One column, one measure — the old three-panel dashboard put the thing you
   came to do (paste a URL) beside four things you did not.
   ========================================================================== */

.narrow { max-width: 880px; margin: 0 auto; }
.narrow.wide { max-width: 1080px; }

.page-head { margin: .5rem 0 1.4rem; }
.page-head h1 {
  font-size: 1.5rem; font-weight: 760; letter-spacing: -.035em; color: var(--web-hi); margin: 0;
}
.page-head p { color: var(--web-dim); font-size: .875rem; margin: .25rem 0 0; }

.talk { padding-top: .5rem; }
.turn { display: grid; grid-template-columns: 38px 1fr; gap: .85rem; margin-bottom: 1.6rem; }
.turn .avatar {
  width: 38px; height: 38px; display: grid; place-items: center; border-radius: 11px;
  border: 1px solid rgba(var(--web-accent-rgb), .42);
  background: var(--web-lime-soft);
  box-shadow: 0 0 16px rgba(var(--web-accent-rgb), .18);
}
.turn .avatar svg { width: 21px; height: 21px; }
.turn .avatar.quiet {
  border-color: var(--web-line); background: rgba(var(--web-tint-rgb),.03); box-shadow: none;
}
.turn .avatar.quiet i { color: var(--web-faint); font-size: .95rem; }
.says { min-width: 0; padding-top: .15rem; }

.lede {
  font-size: 1.06rem; font-weight: 560; color: var(--web-hi); letter-spacing: -.02em;
  line-height: 1.45; margin: 0 0 .2rem;
}
.lede-sm {
  font-size: .68rem; text-transform: uppercase; letter-spacing: .12em;
  color: var(--web-faint); font-weight: 640; margin: .3rem 0 .55rem;
}
.seemore {
  display: inline-flex; align-items: center; gap: .35rem; margin-top: .7rem;
  font-size: .8rem; color: var(--web-dim); text-decoration: none;
}
.seemore:hover { color: var(--web-accent-txt); }

/* ---------- home: build on the left, where things stand on the right ----------

   Two panels, not a dashboard: the left one is the only place you do anything,
   and the right one answers one question at a time. The stack of mini-widgets
   that used to sit above the composer is gone — it pushed the thing you came
   to do below the fold to report things you had not asked about. */

.home-split {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 26rem);
  gap: 1.6rem; align-items: start; max-width: 1220px; margin: 0 auto;
}
.home-left { min-width: 0; padding-top: .5rem; }
.home-right { min-width: 0; position: sticky; top: 1rem; }

.panel {
  border: 1px solid var(--web-line); border-radius: 14px; overflow: hidden;
  background: linear-gradient(180deg, var(--web-panel), var(--web-well));
}
.panel-head {
  display: flex; align-items: center; gap: .4rem; padding: .5rem .55rem;
  border-bottom: 1px solid var(--web-line-soft); background: rgba(var(--web-tint-rgb),.015);
}
.switches { display: flex; gap: .2rem; flex-wrap: wrap; flex: 1 1 auto; min-width: 0; }
.switch {
  display: inline-flex; align-items: center; gap: .25rem; cursor: pointer;
  font-size: .72rem; font-weight: 560; color: var(--web-dim); white-space: nowrap;
  padding: .26rem .48rem; border-radius: 999px; border: 1px solid var(--web-line);
  background: rgba(var(--web-tint-rgb),.025); transition: .15s ease;
}
.switch i.bi { font-size: .74rem; }
.switch:hover { color: var(--web-text); border-color: var(--web-faint); }
.switch.on {
  color: var(--web-hi); border-color: rgba(var(--web-accent-rgb), .45);
  background: var(--web-wash);
}
.switch-count { font-size: .66rem; color: var(--web-faint); font-variant-numeric: tabular-nums; }
.switch.on .switch-count { color: var(--web-accent-txt); }
.icon-btn {
  display: inline-flex; align-items: center; gap: .3rem;
  border: 1px solid var(--web-line); background: rgba(var(--web-tint-rgb),.025); cursor: pointer;
  color: var(--web-dim); border-radius: 999px; padding: .2rem .55rem; font-size: .72rem;
  transition: .15s ease; flex: none;
}
.icon-btn:hover { color: var(--web-accent-txt); border-color: rgba(var(--web-accent-rgb), .45); }
.panel-note {
  padding: .45rem .8rem; font-size: .74rem; color: var(--web-accent-txt);
  background: var(--web-wash); border-bottom: 1px solid var(--web-line-soft);
}
.panel-foot {
  display: flex; align-items: center; justify-content: space-between; gap: .6rem;
  padding: .35rem .8rem .5rem; border-top: 1px solid var(--web-line-soft);
  font-size: .72rem;
}
.panel-foot .seemore { margin-top: 0; }

/* One row per site. The logo is the site's own favicon, cached in the
   workspace — see core/favicons.py for why it is never hot-linked. */
.site-row {
  display: grid; align-items: center; gap: .6rem;
  grid-template-columns: 1.9rem minmax(0, 1fr) auto auto;
  padding: .5rem .8rem; text-decoration: none; color: var(--web-text);
  border-bottom: 1px solid var(--web-line-soft); transition: background .13s ease;
}
.site-row:last-child { border-bottom: 0; }
.site-row:hover { background: var(--web-wash); }
.site-logo {
  width: 1.9rem; height: 1.9rem; border-radius: 8px; display: grid; place-items: center;
  border: 1px solid var(--web-line); background: rgba(var(--web-tint-rgb),.04); overflow: hidden;
}
/* Fill the tile: half these icons are wordmarks, and a wordmark shrunk to
   16px is a grey smudge. `contain` keeps square marks square. */
.site-logo img { width: 100%; height: 100%; object-fit: contain; padding: 1px; }
.site-letter { font-size: .78rem; font-weight: 700; color: var(--web-faint); }
.site-main { min-width: 0; }
.site-name {
  display: block; font-size: .84rem; color: var(--web-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.site-row:hover .site-name { color: var(--web-hi); }
.site-sub {
  display: block; font-size: .7rem; color: var(--web-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.site-metric {
  font-size: .72rem; color: var(--web-dim); text-align: right;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.site-state { text-align: right; }
.site-when {
  display: block; font-size: .66rem; color: var(--web-faint);
  font-variant-numeric: tabular-nums; margin-top: .15rem;
}

@media (max-width: 980px) {
  .home-split { grid-template-columns: 1fr; }
  .home-right { position: static; }
}

/* ---------- the composer ---------- */

.composer {
  border: 1px solid var(--web-line); border-radius: 16px; margin: 0 0 1.6rem;
  background: linear-gradient(180deg, var(--web-panel-2), var(--web-well));
  box-shadow: 0 14px 38px rgba(28,26,20,.12); overflow: hidden;
  transition: border-color .16s ease, box-shadow .16s ease;
}
.composer:focus-within {
  border-color: rgba(var(--web-accent-rgb), .5);
  box-shadow: 0 14px 38px rgba(28,26,20,.12), 0 0 0 3px rgba(var(--web-accent-rgb), .14);
}
.composer-main { display: flex; align-items: center; gap: .5rem; padding: .55rem .55rem .55rem .95rem; }
.composer-url {
  flex: 1; min-width: 0; background: transparent; border: 0; outline: none;
  color: var(--web-text); font-size: .95rem; padding: .5rem 0;
}
.composer-url::placeholder { color: var(--web-faint); }
.composer-go { white-space: nowrap; padding: .5rem 1.05rem; }

.composer-foot {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  padding: .5rem .75rem; border-top: 1px solid var(--web-line-soft);
  background: rgba(var(--web-tint-rgb),.014);
}
/* ---------- picking the model ----------

   The model is the target the whole pipeline is aimed at, so it is picked from
   cards that show what it holds rather than from a dropdown that shows a name.
   Each card carries its own pencil: whichever model you are looking at is one
   click from its editor. */

.modelpick {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(11.5rem, 1fr));
  gap: .5rem; padding: .6rem .75rem; border-top: 1px solid var(--web-line-soft);
}
.modelcard {
  position: relative; display: flex; flex-direction: column; gap: .35rem;
  padding: .55rem .6rem; border-radius: 11px; cursor: pointer; text-decoration: none;
  border: 1px solid var(--web-line); background: rgba(var(--web-tint-rgb),.022);
  transition: border-color .15s ease, background .15s ease, transform .15s ease;
}
.modelcard input { position: absolute; opacity: 0; pointer-events: none; }
.modelcard:hover { border-color: var(--web-faint); transform: translateY(-1px); }
/* `.on` is the server's answer; `:has(:checked)` is the one you just clicked —
   nothing re-renders on a pick, so the card has to light up on its own. */
.modelcard.on, .modelcard:has(input:checked) {
  border-color: rgba(var(--web-accent-rgb), .5); background: var(--web-wash);
  box-shadow: 0 0 18px rgba(var(--web-accent-rgb), .12);
}
.modelcard:not(:has(input:checked)) { border-color: var(--web-line); }
.modelcard:focus-within { outline: 2px solid var(--web-orange); outline-offset: 2px; }
.modelcard-top { display: flex; align-items: center; justify-content: space-between; gap: .4rem; }
.modelcard-title {
  font-size: .84rem; font-weight: 640; color: var(--web-hi); letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.modelcard-edit {
  flex: none; color: var(--web-faint); font-size: .78rem; text-decoration: none;
  border-radius: 7px; padding: 0 .2rem;
}
.modelcard-edit:hover { color: var(--web-accent-txt); }
.modelcard-fields { display: flex; flex-wrap: wrap; gap: .22rem; }
.pip {
  font-size: .62rem; color: var(--web-dim); background: rgba(var(--web-tint-rgb),.05);
  border-radius: 999px; padding: .05rem .38rem;
  font-family: "JetBrains Mono", ui-monospace, monospace;
}
.pip-child { color: var(--web-cyan); background: rgba(69,208,255,.08); }
.modelcard-foot { font-size: .66rem; color: var(--web-faint); }
.modelcard-new { justify-content: center; border-style: dashed; }
.modelcard-new:hover { border-color: rgba(var(--web-accent-rgb), .45); }
.composer-more { margin-left: auto; }
.composer-more > summary {
  list-style: none; cursor: pointer; color: var(--web-faint); font-size: .78rem;
  padding: .3rem .5rem; border-radius: 8px; user-select: none;
}
.composer-more > summary::-webkit-details-marker { display: none; }
.composer-more > summary:hover { color: var(--web-accent-txt); background: var(--web-wash); }
.composer-extra {
  flex-basis: 100%; padding: .85rem .1rem .3rem; border-top: 1px solid var(--web-line-soft);
  margin-top: .5rem;
}
.composer-more[open] { flex-basis: 100%; margin-left: 0; }

/* ==========================================================================
   Activity feed — builds and scrapes in one list
   ========================================================================== */

.filterbar {
  border: 1px solid var(--web-line); border-radius: 13px; padding: .6rem .85rem;
  background: linear-gradient(180deg, var(--web-panel), var(--web-well)); margin-bottom: 1rem;
}
.filter-line {
  display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; padding: .22rem 0;
}
.filter-label {
  font-size: .64rem; text-transform: uppercase; letter-spacing: .11em;
  color: var(--web-faint); font-weight: 640; width: 3.4rem; flex: none;
}
.chip-toggle {
  position: relative; font-size: .78rem; color: var(--web-dim); cursor: pointer;
  padding: .26rem .6rem; border-radius: 999px; border: 1px solid var(--web-line);
  background: rgba(var(--web-tint-rgb),.025); display: inline-flex; align-items: center; gap: .35rem;
  transition: .15s ease; user-select: none;
}
/* Visually hidden, not gone: opacity:0 + pointer-events:none took these out of
   the accessibility tree and off the tab order, leaving the filters mouse-only. */
.chip-toggle input {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}
.chip-toggle:focus-within {
  outline: 2px solid var(--web-orange); outline-offset: 2px; color: var(--web-hi);
}
.chip-toggle:hover { color: var(--web-text); border-color: var(--web-faint); }
/* `.on` is what the server rendered; `:has(:checked)` is what you just clicked.
   Only the rows are swapped on a filter change, so without the second selector
   the chip you pressed would stay dark while the feed underneath it updated. */
.chip-toggle.on,
.chip-toggle:has(input:checked) {
  color: var(--web-hi); border-color: rgba(var(--web-accent-rgb), .45);
  background: var(--web-wash);
}
.chip-toggle:not(:has(input:checked)) {
  color: var(--web-dim); border-color: var(--web-line);
  background: rgba(var(--web-tint-rgb),.025);
}
.chip-count { font-size: .68rem; color: var(--web-faint); font-variant-numeric: tabular-nums; }
.chip-toggle.on .chip-count,
.chip-toggle:has(input:checked) .chip-count { color: var(--web-accent-txt); }

.feed { border: 1px solid var(--web-line); border-radius: 13px; overflow: hidden; }
.feed-row {
  display: grid; align-items: center; gap: .75rem;
  grid-template-columns: 5.6rem minmax(0, 1fr) 4.4rem 5.6rem 2.4rem;
  padding: .6rem .85rem; text-decoration: none; color: var(--web-text);
  border-bottom: 1px solid var(--web-line-soft); border-left: 2px solid transparent;
  transition: background .13s ease;
}
.feed-row:last-child { border-bottom: 0; }
.feed-row:hover { background: var(--web-wash); }
.feed-running { border-left-color: var(--web-orange); }
.feed-failed  { border-left-color: rgba(224,36,61,.7); }

.feed-kind {
  font-size: .68rem; text-transform: uppercase; letter-spacing: .08em;
  color: var(--web-faint); font-weight: 640; display: inline-flex; align-items: center; gap: .3rem;
}
.feed-kind i.bi { font-size: .8rem; }
.kind-build i.bi { color: var(--web-accent-txt); }
.kind-scrape i.bi { color: var(--web-cyan); }

.feed-main { min-width: 0; }
.feed-url {
  display: block; font-size: .85rem; color: var(--web-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.feed-row:hover .feed-url { color: var(--web-hi); }
.feed-sub {
  display: block; font-size: .72rem; color: var(--web-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.feed-metric {
  font-size: .9rem; font-weight: 640; text-align: right; color: var(--web-text);
  font-variant-numeric: tabular-nums;
}
.feed-metric-unit {
  display: block; font-size: .6rem; text-transform: uppercase; letter-spacing: .08em;
  color: var(--web-faint); font-weight: 500;
}
.feed-when {
  font-size: .72rem; color: var(--web-faint); text-align: right;
  font-variant-numeric: tabular-nums;
}
.feed-empty { padding: 1.6rem; text-align: center; color: var(--web-faint); font-size: .84rem; }
.feed-empty i.bi { display: block; font-size: 1.3rem; margin-bottom: .35rem; opacity: .6; }
.feed-more {
  padding: .5rem .85rem; font-size: .72rem; color: var(--web-faint);
  background: rgba(var(--web-tint-rgb),.015); border-top: 1px solid var(--web-line-soft);
}

/* A finished build nobody has judged yet — neither green nor red. */
.status-built {
  background: rgba(255,176,32,.12); color: var(--web-warn-txt); border-color: rgba(255,176,32,.3);
}

@media (max-width: 720px) {
  .feed-row { grid-template-columns: minmax(0, 1fr) 4.6rem 2.4rem; row-gap: .3rem; }
  .feed-kind, .feed-metric { display: none; }
  /* the word fits, the labels do not — the bar keeps its icons */
  .topbar { gap: .6rem; padding: .5rem .8rem; }
  .topbar-nav a span { display: none; }
  .composer { margin-left: 0; }
  .turn { grid-template-columns: 1fr; }
  .turn .avatar { display: none; }
}

/* ==========================================================================
   Shape layer — the signature details of the daylight system, applied over
   everything above. Appended last on purpose: the cascade is the migration
   plan. Tokens live in :root at the top.

   Three moves carry the whole look, and they are the ones to keep:
     - cards are VERY round (--r-card) and lift with a soft shadow instead of
       a hairline. On cream, a border reads as a seam; a shadow reads as paper.
     - controls are PILLS (--r-ctl). The old chassis cut a notch out of the
       primary button's corner; a pill is the opposite instinct and it is the
       one this system uses everywhere.
     - the primary action is INK, not the accent. Lime is a highlight — it
       fills chips, meters and the selected state; a solid ink pill is what you
       press. Using lime for both makes nothing stand out.
   ========================================================================== */

body { font-size: 15px; font-weight: 400; letter-spacing: 0; line-height: 1.5; }
h1, h2, h3, .section-title, .card-header, .btn {
  font-family: "Plus Jakarta Sans", system-ui, sans-serif; font-weight: 700;
}
h1, h2, h3 { letter-spacing: -.02em; }
.section-title { font-weight: 700; letter-spacing: .01em; }

::selection { background: var(--web-orange); color: var(--web-on-accent); }

/* Panels: round, white, floating. No corner bracket — that belonged to the
   HUD, and there is no HUD here. */
.card {
  position: relative; border-radius: var(--r-card);
  border-color: transparent; box-shadow: var(--shadow-card);
}
.card:hover { box-shadow: var(--shadow-lift); }
.card-header, .card-footer { background: transparent; }
.card-header { border-bottom: 1px solid var(--web-line-soft); }

/* Every control is a pill. */
.btn, .form-control, .form-select, .input-group > * { border-radius: var(--r-ctl); }
.form-control, .form-select { background-color: var(--web-well); border-color: var(--web-line); padding: .5rem 1rem; }
textarea.form-control { border-radius: var(--r-well); }
/* Sizes. `.btn` above sets one padding for every button, which silently beat
   Bootstrap's `.btn-sm` — four small buttons in a card footer then squeezed
   until their labels wrapped, and a pill whose label wraps is a circle. Each
   size gets its own padding, and a label never wraps. */
.btn { padding: .45rem 1.1rem; white-space: nowrap; }
.btn-sm { padding: .3rem .85rem; font-size: .78rem; }
.btn-lg { padding: .6rem 1.6rem; font-size: .95rem; }
/* A row of actions wraps onto a second line rather than crushing its buttons. */
.card-footer.d-flex, .d-flex.gap-1, .d-flex.gap-2 { flex-wrap: wrap; }

/* The primary action is a solid ink pill; lime is reserved for highlight. */
.btn-primary, .btn-success {
  background: var(--web-text); color: var(--web-black); border: 0; box-shadow: none;
  transition: background var(--t-fast), opacity var(--t-fast);
}
.btn-primary:hover, .btn-success:hover {
  background: #000; color: var(--web-black); filter: none; box-shadow: none;
}
.btn-outline-primary { color: var(--web-text); background: var(--web-panel); border-color: var(--web-line); font-weight: 700; }
.btn-outline-primary:hover { background: var(--web-orange); color: var(--web-on-accent); border-color: var(--web-orange); }
.btn:active { filter: brightness(.94); }

/* Hover/pressed ride the surface ramp with the fixed motion tokens. */
.topbar-nav a, .usercard-nav a, .list-group-item-action, .chip, .btn {
  transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}
.topbar-nav a:active { background: var(--web-panel-3); }

/* Chips are pills too, and the accent one is a real lime fill. */
.chip { border-radius: 999px; padding: .2rem .65rem; font-weight: 600; }
.chip-accent {
  background: var(--web-orange); border-color: var(--web-orange); color: var(--web-on-accent);
}

/* Wells — code, consoles, tab strips — sit on the paper under a white card. */
.console, .code-block { border-radius: var(--r-well); background: var(--web-well); }
.console { border-radius: 0 0 var(--r-card) var(--r-card); }

/* Status colours by meaning: green only for alive, red only for wrong,
   everything in-flight is the accent. */
.status-ready, .status-done { background: rgba(18,130,87,.12); color: var(--web-ok); border-color: rgba(18,130,87,.28); }
.status-failed { background: rgba(196,40,58,.10); color: var(--web-red-txt); border-color: rgba(196,40,58,.30); }

/* Data is mono: numbers, ids, timestamps. Labels are already uppercase. */
td .mono, .cal-chip-time, .meter-line .mono { font-family: "JetBrains Mono", ui-monospace, Menlo, monospace; }

/* ---------- dashboard widgets ---------- */

/* The stat strip: a row of small readouts above a working page. */
.stat-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: .75rem; margin-bottom: 1.25rem; }
.stat-tile {
  position: relative; border: 0; border-radius: var(--r-card);
  background: var(--web-panel); box-shadow: var(--shadow-card);
  padding: .85rem 1.1rem;
}
.stat-tile .stat-label {
  display: flex; align-items: center; gap: .4rem;
  font-size: .66rem; text-transform: uppercase; letter-spacing: .1em;
  color: var(--web-dim); font-weight: 600;
}
.stat-tile .stat-label i.bi { color: var(--web-accent-txt); font-size: .8rem; }
.stat-tile .stat-value {
  /* The page's own face, not the monospace: a figure in code font beside
     Plus Jakarta labels read as pasted in. Tabular numerals keep columns
     of figures aligned without it. */
  font-family: inherit; font-weight: 800; letter-spacing: -.02em; font-variant-numeric: tabular-nums;
  font-size: 1.35rem; color: var(--web-hi); line-height: 1.25;
}
.stat-tile .stat-note { font-size: .7rem; color: var(--web-faint); }

/* A card that keeps its contents folded until asked — the code editor, the
   raw JSON, anything that would otherwise shout over the dashboard. The grid
   trick animates height without measuring it. */
details.reveal { border: 0; }
details.reveal > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: .5rem;
  padding: .8rem 1rem; font-weight: 640; font-size: .875rem; color: var(--web-hi);
  font-family: "Plus Jakarta Sans", system-ui, sans-serif;
}
details.reveal > summary::-webkit-details-marker { display: none; }
details.reveal > summary i.bi:first-child { color: var(--web-accent-txt); }
details.reveal > summary .reveal-hint {
  margin-left: auto; color: var(--web-faint); font-size: .72rem; font-weight: 500;
  display: inline-flex; align-items: center; gap: .35rem;
  transition: color var(--t-fast);
}
details.reveal > summary:hover .reveal-hint { color: var(--web-accent-txt); }
details.reveal > summary .reveal-hint i.bi { transition: transform var(--t-mid); }
details.reveal[open] > summary .reveal-hint i.bi { transform: rotate(180deg); }
details.reveal > .reveal-body { overflow: hidden; }
details.reveal[open] > .reveal-body { animation: reveal-open 320ms cubic-bezier(0.16, 1, 0.3, 1); }
@keyframes reveal-open {
  from { max-height: 0; opacity: 0; transform: translateY(-6px); }
  to   { max-height: 100vh; opacity: 1; transform: none; }
}
details.reveal[open] > summary { border-bottom: 1px solid var(--web-line-soft); }

/* ==========================================================================
   Page furniture — the shapes the working pages are built from.

   These replace Bootstrap tables on the list pages. A table gives every row
   eight columns whether or not it has eight facts, so most cells rendered "—";
   a card row carries the same information as one heading, one line of context
   and the buttons worth pressing, and it survives a narrow window.
   ========================================================================== */

.page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: 1.25rem;
}
.page-title { font-size: 2rem; font-weight: 800; letter-spacing: -.03em; margin: 0; }
.page-sub { color: var(--web-dim); font-size: .9rem; margin: .25rem 0 0; max-width: 44rem; }

/* ---------- one model, its scrapers under it ---------- */

.model-block { margin-bottom: 1.75rem; }
.model-head {
  display: flex; align-items: center; gap: .75rem;
  padding: 0 .35rem .6rem; 
}
.model-tick { display: flex; align-items: center; margin: 0; }
.model-name { flex: 1 1 auto; min-width: 0; font-size: 1.05rem; font-weight: 700; color: var(--web-hi); }
.model-name a { color: var(--web-hi); text-decoration: none; }
.model-name a:hover { color: var(--web-accent-txt); }
.model-meta { font-size: .78rem; font-weight: 500; color: var(--web-dim); margin-top: .1rem; }
.model-edit {
  flex: none; width: 32px; height: 32px; display: grid; place-items: center;
  border-radius: 999px; color: var(--web-dim); text-decoration: none;
}
.model-edit:hover { background: var(--web-panel); color: var(--web-text); }

.srow-list { display: flex; flex-direction: column; gap: .5rem; }
.srow {
  display: flex; align-items: center; gap: .85rem;
  background: var(--web-panel); border-radius: var(--r-card);
  padding: .85rem 1.1rem; box-shadow: var(--shadow-card);
  transition: box-shadow var(--t-fast);
}
.srow:hover { box-shadow: var(--shadow-lift); }
.srow-tick { margin: 0; display: flex; align-items: center; }
.srow-icon {
  flex: none; width: 34px; height: 34px; border-radius: 10px; object-fit: contain;
  background: var(--web-well);
}
.srow-icon.letter {
  display: grid; place-items: center; background: var(--web-lime-soft);
  color: var(--web-on-accent); font-weight: 800; font-size: .9rem;
}
.srow-body { flex: 1 1 auto; min-width: 0; }
.srow-name {
  display: block; font-weight: 700; font-size: .95rem; color: var(--web-hi);
  text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.srow-name:hover { color: var(--web-accent-txt); }
.srow-url {
  font-size: .72rem; color: var(--web-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.srow-facts { font-size: .76rem; color: var(--web-dim); margin-top: .2rem; }
.srow-facts a { text-decoration: none; font-weight: 600; }
.srow-state { flex: none; }
.srow-do { flex: none; display: flex; gap: .35rem; align-items: center; }

/* The one lime action per row: "this scraper is ready, run it". */
.btn-accent {
  background: var(--web-orange); color: var(--web-on-accent);
  border: 0; font-weight: 700;
}
.btn-accent:hover { background: var(--web-orange-lo); color: var(--web-on-accent); }
/* A destructive action should not advertise itself; it goes quiet until hover. */
.btn-quiet {
  background: transparent; border: 0; color: var(--web-faint);
  width: 32px; height: 32px; padding: 0; display: grid; place-items: center;
}
.btn-quiet:hover { background: rgba(196,40,58,.10); color: var(--web-red-txt); }

/* Narrow: the row folds onto two lines rather than shrinking to nothing. The
   name stays on the FIRST line next to the logo — dropping it below the badge
   and the buttons put the answer to "which scraper is this" last. */
@media (max-width: 720px) {
  .srow { flex-wrap: wrap; row-gap: .6rem; }
  .srow-body { flex: 1 1 8rem; }
  .srow-state { order: 4; margin-left: 3.6rem; }
  .srow-do { order: 5; margin-left: auto; }
  .stat-strip { grid-template-columns: repeat(2, 1fr); }
  .page-head { gap: .75rem; }
  .page-title { font-size: 1.6rem; }
}

/* ---------- the bulk bar: present only when it applies ---------- */

.runbar {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 1.1rem; z-index: 70;
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  /* `width: max-content` on purpose. With `left: 50%` and width:auto the
     shrink-to-fit available width is viewport − 50%, so the bar sizes itself to
     HALF the window and wraps onto two lines no matter how wide the screen is.
     Cost twenty minutes of "why does it wrap at 1200px". */
  width: max-content; max-width: calc(100vw - 2rem);
  padding: .6rem .6rem .6rem 1.1rem; border-radius: 999px;
  background: var(--web-text); box-shadow: var(--shadow-lift);
  animation: runbar-in 220ms cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes runbar-in {
  from { opacity: 0; transform: translate(-50%, 10px); }
  to   { opacity: 1; transform: translate(-50%, 0); }
}
.runbar, .runbar label, .runbar span { color: var(--web-black); font-size: .78rem; }
.runbar-count { white-space: nowrap; }
.runbar-count b { font-size: .95rem; }
.runbar-field { display: flex; align-items: center; gap: .4rem; margin: 0; }
.runbar-field input { width: 3.4rem; text-align: center; }
.runbar-check {
  display: flex; align-items: center; gap: .5rem; margin: 0;
  font-size: .84rem; font-weight: 600; cursor: pointer;
}
.runopts-drawer .runbar-check { padding-top: .95rem; border-top: 1px solid var(--web-line); }
.runbar-check .form-check-input { margin: 0; flex: none; }
/* Controls on an ink bar are a lighter ink with paper text — a light fill here
   reads as a separate card sitting on the bar. */
.runbar .form-control, .runbar .form-select {
  background-color: rgba(240,239,233,.14); border-color: transparent;
  color: var(--web-black); height: 2rem; padding: .2rem .75rem; font-size: .78rem;
}
.runbar .form-select {
  width: 9.5rem; padding-right: 1.9rem;
  /* Bootstrap's chevron is a dark data-URI; on ink it disappears. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23F0EFE9' stroke-width='2'%3E%3Cpath d='m2 5 6 6 6-6'/%3E%3C/svg%3E");
}
.runbar .form-control:focus, .runbar .form-select:focus {
  background-color: rgba(240,239,233,.22); color: var(--web-black);
  box-shadow: 0 0 0 3px rgba(var(--web-accent-rgb), .35);
}
.runbar .form-check-input { background-color: rgba(240,239,233,.2); border-color: transparent; }
.runbar .form-check-input:checked { background-color: var(--web-orange); border-color: var(--web-orange); }
.runbar .btn-primary { background: var(--web-orange); color: var(--web-on-accent); }
.runbar .btn-primary:hover { background: var(--web-orange-hi); color: var(--web-on-accent); }
/* Nothing hides behind the bar. */
body.has-runbar { padding-bottom: 5rem; }

/* ---------- month navigation: one segmented pill ---------- */
.monthnav {
  display: flex; align-items: center; gap: .25rem; flex: none;
  background: var(--web-panel); border-radius: 999px; padding: .25rem;
  box-shadow: var(--shadow-card);
}
.monthnav-arrow {
  width: 34px; height: 34px; display: grid; place-items: center; border-radius: 999px;
  color: var(--web-dim); text-decoration: none; font-size: 1rem;
}
.monthnav-arrow:hover { background: var(--web-wash); color: var(--web-text); }
.monthnav-title {
  padding: 0 .6rem; font-weight: 700; color: var(--web-hi); white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.monthnav-today {
  padding: .35rem .9rem; border-radius: 999px; text-decoration: none;
  background: var(--web-text); color: var(--web-black); font-size: .8rem; font-weight: 700;
}
.monthnav-today:hover { background: #000; color: var(--web-black); }
.cal-empty-mark {
  width: 40px; height: 40px; flex: none; display: grid; place-items: center;
  border-radius: 999px; background: var(--web-lime-soft); color: var(--web-on-accent);
}
.schedrow.is-paused { opacity: .55; }
.schedrow form { margin: 0; }

/* ---------- tab strip ---------- */
.tabbar {
  display: flex; gap: .25rem; flex-wrap: wrap; margin-bottom: 1.25rem;
  background: var(--web-panel); border-radius: 999px; padding: .3rem;
  box-shadow: var(--shadow-card); width: fit-content; max-width: 100%;
}
.tabbtn {
  border: 0; background: none; border-radius: 999px; cursor: pointer;
  padding: .45rem 1rem; font-size: .84rem; font-weight: 600; color: var(--web-dim);
  font-family: inherit; white-space: nowrap;
  transition: background var(--t-fast), color var(--t-fast);
}
.tabbtn:hover { background: var(--web-wash); color: var(--web-text); }
.tabbtn.on { background: var(--web-text); color: var(--web-black); }

/* ---------- the code editor ---------- */
/* CodeMirror is loaded from a CDN, same no-build-step bargain as Bootstrap and
   htmx. It replaces a bare <textarea>: line numbers, a real Tab key, bracket
   matching and Python highlighting. The textarea stays in the DOM and is the
   thing that POSTs — CodeMirror writes back into it on submit — so the page
   still works if the CDN is blocked. */
.CodeMirror {
  height: auto; min-height: 26rem; max-height: 70vh;
  font-family: "JetBrains Mono", ui-monospace, Menlo, monospace;
  font-size: .8rem; line-height: 1.6;
  background: var(--web-well); color: var(--web-text);
  border-top: 1px solid var(--web-line-soft);
}
.CodeMirror-gutters { background: var(--web-well-2); border-right: 1px solid var(--web-line-soft); }
.CodeMirror-linenumber { color: var(--web-faint); }
.CodeMirror-cursor { border-left: 2px solid var(--web-text); }
.CodeMirror-selected { background: var(--web-wash-2) !important; }
.CodeMirror-focused .CodeMirror-selected { background: rgba(var(--web-accent-rgb), .35) !important; }
.CodeMirror-matchingbracket { color: var(--web-accent-txt) !important; font-weight: 700; }
/* Syntax colours are the palette's own hues, not a stock editor theme — five
   more literals here would be five more colours the palette cannot move. */
.cm-s-default .cm-keyword  { color: var(--web-violet); }
.cm-s-default .cm-string   { color: var(--web-ok-txt); }
.cm-s-default .cm-comment  { color: var(--web-faint); font-style: italic; }
.cm-s-default .cm-def,
.cm-s-default .cm-variable-2,
.cm-s-default .cm-builtin  { color: var(--web-cyan); }
.cm-s-default .cm-number   { color: var(--web-warn-txt); }
.editor-bar {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  padding: .6rem 1rem; border-top: 1px solid var(--web-line-soft);
}
.editor-hint { flex: 1 1 12rem; }

/* ---------- the sign-in mark ---------- */
.signin-mark {
  display: flex; flex-direction: column; align-items: center; gap: .6rem;
  margin: 3.5rem 0 1.5rem;
}
.signin-mark .mark { width: 64px; height: 64px; border-radius: 18px; }
/* "or" between the password form and Google's button — a hairline in ink,
   the word sitting on the card's own white. */
.signin-or { display: flex; align-items: center; gap: .75rem; margin: 1.1rem 0; color: var(--web-muted-2); font-size: .8rem; }
.signin-or::before, .signin-or::after { content: ""; flex: 1; height: 1px; background: rgba(var(--web-tint-rgb), .12); }
.google-signin { display: flex; flex-direction: column; align-items: center; min-height: 44px; }

/* Buying tokens. The card inputs are PayPal's hosted frames (they style their
   own insides — PayPalButton.jsx); these boxes only lay them out as one pill
   per field, name and number full width, expiry and CVC side by side. */
.paybox { margin-top: 1.5rem; }
.paybox-label { font-weight: 700; font-size: .95rem; margin-bottom: .85rem; display: flex; gap: .45rem; align-items: center; }
.paybox-card { display: grid; grid-template-columns: 1fr 1fr; gap: .1rem .6rem; }
.paybox-wide { grid-column: 1 / -1; }
.paybox-field { min-height: 54px; }
.paybox-card .btn { margin-top: .4rem; padding-top: .7rem; padding-bottom: .7rem; }
.paybox-loading { color: var(--web-muted-2); font-size: .9rem; padding: .9rem 0; }
.paybox-note { color: var(--web-muted-2); font-size: .8rem; margin: .7rem 0 0; display: flex; gap: .4rem; align-items: baseline; }
.signin-mark span {
  font-weight: 800; font-size: 1.35rem; letter-spacing: -.03em; color: var(--web-hi);
}

/* The demo credentials, at the foot of the sign-in card. TEMPORARY — see the
   block in login.html. Washed in ink, not lime: the accent belongs on the thing
   you press, and here that is the outline button inside it. `user-select: all`
   so one click takes the whole credential rather than half a word. */

/* ---------- teams ----------
   LeadHunt draws each team as a recessed panel inside the card, with its
   members as pills. Same shapes here, in this palette: the wash is ink, never
   the accent, so a team does not read as a stain on the cream. */
.teamcard {
  background: var(--web-wash); border-radius: var(--r-card);
  padding: 1rem 1.1rem; margin-bottom: .6rem;
}
.teamcard-head { display: flex; align-items: center; gap: .5rem; }
.teamcard-name { font-weight: 800; letter-spacing: -.02em; }
.teamcard-members { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: .6rem; }
.teammate {
  display: inline-flex; align-items: center; gap: .35rem;
  background: var(--web-panel); border-radius: var(--r-ctl);
  padding: .2rem .6rem .2rem .2rem; font-size: .76rem; font-weight: 700;
}
.teammate-x {
  border: 0; background: none; color: var(--web-faint); cursor: pointer;
  font-size: .95rem; line-height: 1; padding: 0 .1rem;
}
.teammate-x:hover { color: var(--web-red-txt); }
.teamcard-do { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .8rem; }
.teammate-role, .teammate-role-flat {
  border: 0; background: var(--web-wash); border-radius: var(--r-ctl);
  font: inherit; font-size: .68rem; font-weight: 700; color: var(--web-dim);
  padding: .05rem .45rem; text-transform: uppercase; letter-spacing: .04em;
}
.teammate-role { cursor: pointer; }
.teammate-role:hover { background: var(--web-wash-2); color: var(--web-text); }

/* The permission matrix. A real grid rather than a list of rows: the question
   is "what does this role NOT have", and a column of ticks answers it at a
   glance where sentences do not. */
.teamperms { margin-top: .9rem; }
.teamperms > summary {
  cursor: pointer; font-size: .78rem; font-weight: 700; color: var(--web-dim);
}
.teamperms > summary:hover { color: var(--web-text); }
.permrole {
  background: var(--web-panel); border-radius: var(--r-card);
  padding: .8rem .9rem; margin-top: .6rem;
}
.permrole-head {
  display: flex; align-items: center; gap: .5rem; margin-bottom: .55rem;
}
.permrole-name {
  font-weight: 800; text-transform: uppercase; letter-spacing: .06em;
  font-size: .7rem; flex: 1 1 auto;
}
.permgrid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: .25rem .9rem;
}
.permbox {
  display: flex; align-items: center; gap: .45rem;
  font-size: .78rem; color: var(--web-text); cursor: pointer; margin: 0;
}
.permbox.is-locked { cursor: default; color: var(--web-faint); }
.userpic.sm { width: 20px; height: 20px; font-size: .62rem; }

/* ---------- picking several scrapers by their logos ----------
   A <select multiple> shows one option at a time and asks you to read domains;
   you recognise a site by its favicon first. Each card is a label wrapping a
   checkbox, so the whole tile is the hit area and it needs no JS. */
.pickgrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: .5rem; margin-top: .6rem;
}
.pickcard { margin: 0; cursor: pointer; }
.pickcard input { position: absolute; opacity: 0; pointer-events: none; }
.pickcard-face {
  display: flex; align-items: center; gap: .6rem;
  background: var(--web-wash); border-radius: var(--r-card);
  padding: .6rem .75rem; transition: background var(--t-fast);
}
.pickcard:hover .pickcard-face { background: var(--web-wash-2); }
.pickcard input:checked ~ .pickcard-face { background: var(--web-text); }
.pickcard input:focus-visible ~ .pickcard-face { outline: 2px solid var(--web-orange); }
.pickcard-icon {
  width: 26px; height: 26px; border-radius: 8px; flex: none; object-fit: contain;
  background: var(--web-panel);
}
.pickcard-icon.letter {
  display: grid; place-items: center; font-weight: 800; font-size: .8rem;
  color: var(--web-dim);
}
.pickcard-body { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; }
.pickcard-name {
  font-weight: 700; font-size: .82rem; color: var(--web-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pickcard-sub {
  font-size: .68rem; color: var(--web-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pickcard input:checked ~ .pickcard-face .pickcard-name { color: var(--web-black); }
.pickcard input:checked ~ .pickcard-face .pickcard-sub { color: var(--web-line-hi); }
/* The tick is the only thing that moves: a picked card is ink, and the check
   confirms it rather than being the sole signal. */
.pickcard-tick {
  flex: none; width: 18px; height: 18px; border-radius: 999px;
  display: grid; place-items: center; font-size: .7rem;
  background: transparent; color: transparent;
}
.pickcard input:checked ~ .pickcard-face .pickcard-tick {
  background: var(--web-orange); color: var(--web-on-accent);
}
/* Its start page only matters once it is coming along. */
.pickcard-url { display: none; margin-top: .3rem; font-size: .7rem; }
.pickcard input:checked ~ .pickcard-url { display: block; }

/* Several logos where a row normally has one. */
/* The schedule form's running estimate: one line, a calculator glyph, the
   figures in bold — the same voice as the run form's AWS row. */
.estimate { display: flex; gap: .6rem; align-items: flex-start; padding: .7rem .9rem; border-radius: .9rem;
  background: var(--web-wash); font-size: .84rem; line-height: 1.5; }
.estimate .bi { margin-top: .15rem; opacity: .7; }
.pill-stat.danger { background: rgba(224,36,61,.13); color: var(--web-red-txt); }
button.pill-stat { cursor: pointer; font: inherit; }
.pill-form { gap: .4rem; }
.pill-form .seg-num.on { width: 4.5rem; opacity: 1; padding: .25rem .5rem; background: var(--web-well); border-radius: 999px; }
/* Page entry: a short rise and fade, the same on every route, so moving
   between pages reads as one app changing view rather than a reload. Off
   for anyone who asked for less motion. */
@keyframes page-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
/* `backwards`, not `both`: a fill that outlives the animation keeps a
   transform on <main>, and a transformed ancestor becomes the containing
   block for every `position: fixed` inside it — the bulk bar sat at the foot
   of a hundred-row page instead of the window's, out of sight after a tick. */
main.page-in { animation: page-in .22s ease-out backwards; }
.topbar-nav a { transition: background var(--t-fast), color var(--t-fast); }
@media (prefers-reduced-motion: reduce) { main.page-in { animation: none; } }
/* The statistics head on the activity page: bars in the palette's two
   voices — ink for the models' bill, lime for AWS — and a legend to match. */
.bars { width: 100%; height: auto; display: block; }
.bar-ai { fill: var(--web-text); }
.bar-aws { fill: var(--web-orange); }
.bar-lbl { font-size: 10px; fill: var(--web-dim); font-family: inherit; }
.legend { display: inline-block; width: .7rem; height: .7rem; border-radius: 3px; vertical-align: -.05em; }
.legend.ai { background: var(--web-text); }
.legend.aws { background: var(--web-orange); }
/* A record row (a webhook, a login): the scraper row's shape with the form
   folded underneath. The row itself is a column so the editor can sit
   inside the same card, under a hairline, rather than as a second card. */
.srow-record { flex-direction: column; align-items: stretch; gap: 0; }
.srow-record-main { display: flex; align-items: center; gap: 1rem; }
.srow-record-edit { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--web-line-soft); }
.srow-record.is-editing { box-shadow: var(--shadow-lift); }
.srow-icon.letter.local { background: var(--web-well); color: var(--web-dim); font-size: 1.05rem; }
.srow-facts .bi { font-size: .85em; opacity: .7; margin-right: .1rem; }
.record-form { display: flex; flex-direction: column; gap: .8rem; }
.record-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .6rem .8rem; }
.record-grid > label { display: flex; flex-direction: column; gap: .3rem; margin: 0; min-width: 0; }
.record-grid .span-2 { grid-column: span 2; }
.record-grid .span-3 { grid-column: span 3; }
.record-grid .lbl-mini, .record-form .lbl-mini { display: block; font-size: .68rem; font-weight: 800;
  letter-spacing: .05em; text-transform: uppercase; color: var(--web-dim); }
.record-form .lbl-mini .mono { text-transform: none; letter-spacing: 0; }
.record-form > .btn { align-self: flex-start; }
@media (max-width: 640px) {
  .record-grid { grid-template-columns: 1fr; }
  .record-grid .span-2, .record-grid .span-3 { grid-column: span 1; }
  .srow-record-main { flex-wrap: wrap; }
}
.record-fold > summary { cursor: pointer; font-size: .82rem; color: var(--web-dim); font-weight: 600;
  list-style: none; display: flex; align-items: center; gap: .4rem; }
.record-fold > summary::-webkit-details-marker { display: none; }
.record-fold > summary::before { content: "▸"; font-size: .7rem; transition: transform var(--t-fast); }
.record-fold[open] > summary::before { transform: rotate(90deg); }
.record-fold > summary:hover { color: var(--web-text); }
/* A model's card: what you have of it, then what it is. */
.mcard-head { display: flex; align-items: center; gap: .7rem; margin-bottom: .6rem; }
.mcard-mark { display: grid; place-items: center; flex: none; width: 2.4rem; height: 2.4rem;
  border-radius: .8rem; background: var(--web-lime-soft); color: var(--web-on-accent); font-size: 1.1rem; }
.mcard-title { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.mcard-title h3 { font-size: 1.15rem; margin: 0; letter-spacing: -.02em; }
.mcard-desc { font-size: .88rem; color: var(--web-dim); margin: 0 0 .8rem; line-height: 1.45; }
.mcard-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .5rem; margin-bottom: .7rem; }
.mcard-stat { display: flex; flex-direction: column; padding: .55rem .7rem; border-radius: .9rem;
  background: var(--web-wash); text-decoration: none; color: var(--web-text); min-width: 0; }
.mcard-stat b { font-size: 1.15rem; letter-spacing: -.02em; line-height: 1.1; }
.mcard-stat span { font-size: .7rem; color: var(--web-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
a.mcard-stat:hover { background: var(--web-wash-2); color: var(--web-text); }
.mcard-sites { display: flex; align-items: center; gap: .55rem; margin-bottom: .7rem; font-size: .78rem; }
.srow-icon.sm { width: 26px; height: 26px; border-radius: 8px; font-size: .7rem; }
.mcard-fields > summary { cursor: pointer; font-size: .8rem; color: var(--web-dim); font-weight: 600; list-style: none; }
.mcard-fields > summary::-webkit-details-marker { display: none; }
.mcard-fields > summary::before { content: "▸ "; font-size: .7rem; }
.mcard-fields[open] > summary::before { content: "▾ "; }
.srow-icons { display: inline-flex; align-items: center; flex: none; }
.srow-icons .srow-icon + .srow-icon { margin-left: -8px; }

.section-title {
  font-size: .78rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .09em; color: var(--web-faint); margin: 1.6rem .25rem .7rem;
}

/* ---------- the transport button, and the small run panel ----------
   A square with a play triangle, which becomes a square with a stop glyph once
   there is a run to stop. One shape, two states, the way every player works —
   and it does not need a label to say "Run scraper" on a page whose entire
   subject is this scraper. */
/* The whole control is the button. It was a square button with a plain <span>
   label beside it, so only the 46px square did anything and "Run it" — where
   anyone would actually aim — was dead. */
/* The card's head: what this does on the left, the button that does it on the
   right. The button used to sit at the bottom-left under the options, so it
   moved down the card every time the fold opened and was the last thing found
   on a card whose entire purpose it is. */
.runcard-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem 1.5rem; flex-wrap: wrap; margin-bottom: 1.15rem;
}
.runcard-heading { min-width: 0; }
.runcard-head .hero-title { margin: 0; }
.runcard-sub {
  margin: .3rem 0 0; font-size: .88rem; color: var(--web-on-accent); opacity: .72;
}
.card .runcard-sub { color: var(--web-dim); opacity: 1; }

/* One control: the square and its label share a pill, so the whole thing is
   the hit area and reads as a single object rather than an icon with a caption
   parked beside it. */
.runstart {
  display: inline-flex; align-items: center; gap: .65rem; flex: none;
  padding: .3rem 1.15rem .3rem .3rem;
  border: 0; background: var(--web-on-lime); cursor: pointer;
  border-radius: var(--r-ctl); font: inherit; color: inherit;
  transition: background var(--t-fast);
}
.card .runstart { background: var(--web-well); }
.runstart:hover { background: var(--web-on-lime-hi); }
.card .runstart:hover { background: var(--web-well-2); }
.runstart:hover .runsquare { background: var(--web-hi); }
.runstart:active .runsquare { transform: scale(.94); }
.runstart:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--signal-a32); }
.runstart-row {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-top: 1rem;
}
/* The options, folded: four decisions in front of the thing you came to do is
   three too many, and the segmented controls clipped their last label. The
   fold is its own full-width block under the field — it used to share a flex
   row with the button, which is what squeezed the segments into a gutter. */
.runopts-fold { margin-top: 1rem; min-width: 0; }
.runopts-fold > summary {
  display: flex; align-items: center; gap: .5rem; cursor: pointer;
  font-size: .78rem; font-weight: 700; color: var(--web-text);
  padding: .5rem .9rem; border-radius: var(--r-ctl);
  background: var(--web-on-lime); list-style: none;
  transition: background var(--t-fast);
}
.card .runopts-fold > summary { background: var(--web-well); }
.runopts-fold > summary::-webkit-details-marker { display: none; }
.runopts-fold > summary:hover { background: var(--web-on-lime-hi); }
.card .runopts-fold > summary:hover { background: var(--web-well-2); }
.runopts-summary { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The caret is the affordance — a line of settings with no handle reads as a
   status line, not as something you can open. */
.runopts-caret {
  margin-left: auto; flex: none; font-size: .7rem; opacity: .55;
  transition: transform var(--t-fast);
}
.runopts-fold[open] > summary .runopts-caret { transform: rotate(180deg); }
.runopts-drawer {
  margin-top: .55rem; padding: 1.1rem 1.15rem;
  background: var(--web-on-lime); border-radius: var(--r-card);
  display: grid; gap: 1.05rem;
}
.card .runopts-drawer { background: var(--web-well); }

.runsquare {
  width: 46px; height: 46px; flex: none; border: 0; cursor: pointer;
  /* Square, softened — not a pill. The pill is every OTHER control here, so
     the one thing that starts work reads as a different kind of thing. */
  border-radius: 14px;
  display: grid; place-items: center; font-size: 1.25rem;
  background: var(--web-text); color: var(--web-black);
  transition: background var(--t-fast), transform var(--t-fast);
}
.runsquare:hover { background: var(--web-hi); }
.runsquare:active { transform: scale(.94); }
.runsquare.is-quiet { background: var(--web-wash-2); color: var(--web-text); }
.runsquare.is-quiet:hover { background: var(--web-text); color: var(--web-black); }
/* Running: still ink, so the control does not change weight mid-run — only the
   glyph changes, which is what tells you which way pressing it goes. */
.runsquare.is-stop { background: var(--web-text); color: var(--web-black); }
.runsquare.is-stop:hover { background: var(--web-red); color: var(--web-panel); }
.runsquare.is-done { background: var(--web-lime-soft); color: var(--web-on-accent); cursor: default; }
.runsquare.is-done.is-bad { background: var(--web-wash-2); color: var(--web-red-txt); }
.runstart-hint { font-size: .9rem; font-weight: 800; color: var(--web-dim); }
.hero .runstart-hint { color: var(--web-text); }

.runpanel {
  background: var(--web-panel); border-radius: var(--r-card);
  box-shadow: var(--shadow-card); border-color: transparent;
  padding: .9rem 1rem; margin-bottom: 1rem;
}
.runpanel-top { display: flex; align-items: center; gap: .8rem; }
.runpanel-body { flex: 1 1 auto; min-width: 0; }
.runpanel-line { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.runpanel-title {
  font-weight: 700; font-size: .9rem; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.runpanel-meta { font-size: .7rem; color: var(--web-faint); margin-top: .1rem; }
.runpanel-meter { margin-top: .45rem; }
/* The log is the only narration this panel has, so it is open — folding it
   would leave an empty box. Fixed height and scrolled to the bottom, so a
   chatty run does not push the page around as it goes. */
.runpanel-log {
  margin: .8rem 0 0; padding: .6rem .75rem; max-height: 11rem; overflow: auto;
  background: var(--web-wash); border-radius: 14px;
  font-size: .72rem; line-height: 1.55; color: var(--web-text-2);
  white-space: pre-wrap; word-break: break-word;
}

/* ---------- the browser-stream player ----------
   A grid of thumbnails is a contact sheet: you see every frame and follow
   none. A run is a sequence, so it gets a player — one frame, a timeline you
   can drag or scroll, and a play button. */
.shot-player { padding: .75rem .9rem 1rem; }
.shot-transport {
  display: flex; align-items: center; gap: .7rem; margin-top: .6rem;
}
.shot-play {
  width: 34px; height: 34px; flex: none; border: 0; border-radius: 11px;
  display: grid; place-items: center; cursor: pointer; font-size: 1rem;
  background: var(--web-text); color: var(--web-black);
  transition: background var(--t-fast);
}
.shot-play:hover { background: var(--web-hi); }
.shot-play:disabled { background: var(--web-wash-2); color: var(--web-faint); cursor: default; }

/* The timeline. Styled by hand in both engines because a range input inherits
   almost nothing — left alone it is a blue OS slider on a cream page. */
.shot-timeline {
  flex: 1 1 auto; appearance: none; height: 4px; border-radius: 999px;
  background: var(--web-wash-2); cursor: pointer; margin: 0;
}
.shot-timeline::-webkit-slider-thumb {
  appearance: none; width: 14px; height: 14px; border-radius: 999px;
  background: var(--web-text); border: 2px solid var(--web-panel);
  box-shadow: var(--shadow-card); cursor: grab;
}
.shot-timeline::-moz-range-thumb {
  width: 14px; height: 14px; border-radius: 999px; border: 2px solid var(--web-panel);
  background: var(--web-text); cursor: grab;
}
.shot-timeline:active::-webkit-slider-thumb { cursor: grabbing; }
.shot-count { flex: none; font-size: .72rem; color: var(--web-dim); }
.shot-live { color: var(--web-accent-txt); font-weight: 700; }

.shot-caption {
  display: flex; align-items: center; gap: .5rem; margin-top: .5rem;
  font-size: .74rem; color: var(--web-dim); min-height: 1.4rem;
}
/* The verdict on that page, said in a word rather than only as a border. */
.shot-tone {
  text-transform: uppercase; letter-spacing: .07em; font-weight: 800;
  font-size: .62rem; padding: .1rem .45rem; border-radius: 999px;
  background: var(--web-wash); color: var(--web-faint);
}
.shot-tone-kept { background: var(--web-lime-soft); color: var(--web-on-accent); }
.shot-tone-rejected, .shot-tone-failed { color: var(--web-red-txt); }
.shot-caption .shot-name {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.shot-follow {
  margin-left: auto; flex: none; border: 0; background: none; cursor: pointer;
  font: inherit; font-weight: 700; color: var(--web-text);
  text-decoration: underline; text-underline-offset: 2px;
}

/* ---------- placeholders for what has not arrived ----------
   A page that answers with the word "Loading…" and nothing else reads as a
   stall even when it is fast. Each page draws its own shape straight away and
   the server-dependent parts are these — so nothing moves when the data lands.

   Ink, not the accent: a shimmering lime bar behind every card would be the
   loudest thing on the page, and the accent belongs on what you press. */
.sk {
  display: inline-block; border-radius: 8px; vertical-align: middle;
  background: linear-gradient(
    90deg,
    var(--web-wash) 0%,
    var(--web-wash-2) 40%,
    var(--web-wash) 80%);
  background-size: 300% 100%;
  animation: sk-sweep 1.6s ease-in-out infinite;
}
.sk-text { height: .85em; border-radius: 999px; }
.sk-lg { height: 1.05em; }
.sk-icon { width: 34px; height: 34px; border-radius: 10px; flex: none; }
.sk-stack { display: flex; flex-direction: column; gap: .5rem; align-items: flex-start; }
.sk-row { pointer-events: none; }

/* The generated module, editable in place. Monospace, tall, and it scrolls
   inside itself — a 500-line scraper must not push the page around. */
.code-box {
  font-size: .74rem; line-height: 1.5; white-space: pre; overflow-wrap: normal;
  overflow-x: auto; tab-size: 4;
}

@keyframes sk-sweep {
  from { background-position: 120% 0; }
  to   { background-position: -20% 0; }
}

/* Content that IS on screen but is being refreshed. Softer than replacing it
   with placeholders — the numbers you were reading stay readable while the
   next set arrives, they just stop looking authoritative. */
.is-stale { filter: blur(1.4px); opacity: .55; transition: filter .2s, opacity .2s; }

/* A reader who asked for less movement gets none: the sweep is decoration and
   the shape carries the meaning on its own. */
@media (prefers-reduced-motion: reduce) {
  .sk { animation: none; }
  .is-stale { filter: none; }
}

.pending-mark {
  width: 56px; height: 56px; margin: 0 auto 1rem; display: grid; place-items: center;
  border-radius: 999px; background: var(--web-lime-soft); color: var(--web-on-accent);
  font-size: 1.4rem;
}

/* ==========================================================================
   The LeadHunt shapes — the accent as a SURFACE, not just a control.

   Everything in this studio is white-on-cream, which is calm and completely
   flat: nothing says "this is the thing you came here to do". LeadHunt solves
   it with one saturated lime card per page holding the primary action, and
   that is what `.hero` is. One per page, or it stops meaning anything.
   ========================================================================== */

.hero {
  background: var(--web-orange);
  border-radius: var(--r-card); padding: 1.4rem 1.5rem;
  box-shadow: var(--shadow-card); border: 0;
}
.hero-title {
  font-size: 1.35rem; font-weight: 800; letter-spacing: -.03em;
  color: var(--web-on-accent); margin: 0 0 1rem;
}
/* On lime, "white" is the recessed surface and ink is the action. */
.hero .form-control, .hero .form-select, .hero .composer-url {
  background: var(--web-on-lime); border-color: transparent; color: var(--web-text);
}
.hero .form-control:focus, .hero .composer-url:focus {
  background: #fff; box-shadow: 0 0 0 3px rgba(28,28,26,.18);
}
.hero .form-control::placeholder, .hero .composer-url::placeholder { color: var(--web-dim); }
.hero .btn-primary { background: var(--web-text); color: var(--web-black); }
.hero .btn-primary:hover { background: #000; color: var(--web-black); }
.hero label, .hero .form-check-label, .hero .form-label, .hero .small-muted {
  color: var(--web-on-accent);
}
.hero .small-muted { opacity: .72; }
/* Cards inside the hero (the model picker) are white pills on lime. */
.hero .modelcard { background: var(--web-on-lime-hi); border-color: transparent; }
.hero .modelcard:hover { background: var(--web-on-lime-hi); }
.hero .modelcard.on {
  background: #fff; box-shadow: 0 0 0 2px var(--web-text);
}
.hero .chip { background: var(--web-on-lime); border-color: transparent; color: var(--web-text); }
.hero details > summary { color: var(--web-on-accent); }

/* The accent as one word in a heading — LeadHunt's "Lead<b>Hunt</b>". Deep
   olive, not lime: this is text. */
.accent-word { color: var(--web-accent-txt); }

/* A row of small pill readouts, the way LeadHunt stacks streak / level / XP
   under its title. Replaces four boxed tiles with something you read in one
   glance. */
.pillrow { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1.25rem; }
.pill-stat {
  display: inline-flex; align-items: center; gap: .45rem;
  background: var(--web-panel); border-radius: 999px; padding: .45rem 1rem;
  box-shadow: var(--shadow-card); font-size: .82rem; font-weight: 600;
  color: var(--web-text); text-decoration: none;
}
.pill-stat b { font-weight: 800; font-size: .95rem; }
.pill-stat .lbl-mini { color: var(--web-dim); font-weight: 500; font-size: .76rem; }
.pill-stat i.bi { color: var(--web-accent-txt); }
.pill-stat.dark { background: var(--web-text); color: var(--web-black); }
.pill-stat.dark .lbl-mini { color: var(--web-black); opacity: .65; }
.pill-stat.dark i.bi { color: var(--web-orange); }
.pill-stat.lime { background: var(--web-orange); color: var(--web-on-accent); }
.pill-stat.lime i.bi { color: var(--web-on-accent); }
a.pill-stat:hover { box-shadow: var(--shadow-lift); }

.lbl-waiting {
  font-size: .68rem; text-transform: uppercase; letter-spacing: .1em;
  color: var(--web-dim); font-weight: 700; margin-bottom: .5rem;
}
/* A pending account is the one thing on this page that wants acting on. */
.waiting-row { background: var(--web-lime-soft); }
.waiting-row form { margin: 0; }

/* ==========================================================================
   LeadHunt proportions.

   The studio was a 72rem two-column dashboard with 13px type and 8px gaps —
   dense, efficient, and nothing like the thing it is supposed to look like.
   LeadHunt is one narrow column (max-w-3xl), big type, and a lot of air; the
   calm comes from the PROPORTIONS, not from the palette. Everything below is
   that, applied over the layout rather than by rewriting each page.
   ========================================================================== */

:root {
  --measure: 58rem;         /* one column, and the bar is the same one */
  /* The TOP BAR only. Measured: the brand, seven links and the avatar need
     877px and the reading measure is 832, so the nav was clipping "Activity"
     off its own right edge — silently, because a flex item with min-width:0
     shrinks rather than complaining. Capping the bar was never about the
     alignment for its own sake; it was to stop the nav hugging the window
     edge on a wide screen, and this still does that. An eighth nav item has
     to revisit this number: check .topbar-nav scrollWidth vs clientWidth. */
  --measure-bar: 58rem;
  --r-card: 2rem;           /* rounded-4xl */
  --gap: .85rem;
}

main.container-fluid, main.studio-main { padding: 1.5rem 1rem 6rem; }

/* Type. LeadHunt's body is 15-16px and its headings are genuinely big. */
body { font-size: 15.5px; line-height: 1.55; }
.page-head { margin-bottom: 1.5rem; }
.page-title { font-size: 2.25rem; line-height: 1.1; }
.page-sub { font-size: .95rem; margin-top: .4rem; }

/* Cards breathe. */
.card { padding: 0; margin-bottom: var(--gap); }
.card > .card-body { padding: 1.35rem 1.5rem; }
.card > .card-header {
  padding: 1.15rem 1.5rem .35rem; border-bottom: 0;
  font-size: 1.05rem; font-weight: 700; letter-spacing: -.02em;
}
.card > .card-header + .card-body { padding-top: .6rem; }

/* A section is a bold line and then the cards — no boxes around groups. */
.section-title { font-size: 1.2rem; font-weight: 700; letter-spacing: -.02em; }

/* ---------- home is one column, not two ----------
   The right-hand panel was a sidebar of small rows competing with the thing
   you came to do. Stacked, each gets the full width and its own moment. */
.home-split { display: block; max-width: none; }
.home-left, .home-right { position: static; padding-top: 0; }
.home-right { margin-top: 2rem; }

/* The hero, at LeadHunt's scale. */
.hero { padding: 1.75rem; margin-bottom: var(--gap); }
.hero-title { font-size: 1.5rem; margin-bottom: 1.15rem; }
.composer-main {
  padding: 0; gap: .6rem; margin-bottom: 1rem;
}
.hero .composer-url {
  background: var(--web-on-lime-hi); border-radius: 999px;
  padding: .85rem 1.25rem; font-size: .95rem; flex: 1;
}
.hero .composer-go { padding: .85rem 1.6rem; font-size: .95rem; }

/* Two model cards per row, each with room to breathe. */
.modelpick {
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: .6rem; padding: 0; border-top: 0;
}
.modelcard { padding: 1rem 1.1rem; border-radius: 1.25rem; gap: .5rem; }
.modelcard.on, .modelcard:has(input:checked) {
  box-shadow: 0 0 0 2px var(--web-text); background: #fff;
}
.composer-foot {
  padding: .9rem 0 0; margin-top: 1rem;
  border-top: 1px solid rgba(28,28,26,.12); background: none;
}

/* ---------- lists: LeadHunt's campaign row ----------
   White, very round, one bold line and one muted line, actions on the right. */
.panel { border: 0; border-radius: var(--r-card); background: var(--web-panel);
         box-shadow: var(--shadow-card); overflow: visible; }
.panel-head { padding: 1.15rem 1.35rem .8rem; }
.srow { padding: 1.1rem 1.35rem; border-radius: var(--r-card); gap: 1rem; }
.srow-list { gap: .6rem; }
.srow-name { font-size: 1rem; }
.srow-icon { width: 40px; height: 40px; border-radius: 12px; }
.model-head { padding: 0 .5rem .75rem; }
.model-name { font-size: 1.2rem; }
.stat-strip { gap: var(--gap); margin-bottom: 1.5rem; }
.pillrow { gap: .6rem; margin-bottom: 1.5rem; }
.pill-stat { padding: .6rem 1.15rem; font-size: .86rem; }

/* Sign-in and sign-up sit lower and wider, like LeadHunt's centred cards. */
.signin-mark { margin: 4rem 0 1.75rem; }
.signin-mark .mark { width: 72px; height: 72px; border-radius: 22px; }
.signin-mark span { font-size: 1.6rem; }

@media (max-width: 640px) {
  .page-title { font-size: 1.75rem; }
  main.container-fluid, main.studio-main { padding: 1rem .85rem 5rem; }
  .hero { padding: 1.25rem; }
  .card > .card-body { padding: 1.1rem 1.15rem; }
  .composer-main { flex-wrap: wrap; }
  .hero .composer-go { width: 100%; }
}

/* ---------- the model picker, as LeadHunt picks business types ---------- */
.hero-label {
  display: block; font-size: .82rem; font-weight: 700; color: var(--web-on-accent);
  margin-bottom: .55rem;
}
.chiprow { display: flex; flex-wrap: wrap; gap: .45rem; }
.modelchip {
  position: relative; display: inline-flex; align-items: center; gap: .35rem;
  padding: .5rem 1rem; border-radius: 999px; cursor: pointer; margin: 0;
  background: var(--web-on-lime); border: 0; color: var(--web-text);
  font-size: .84rem; font-weight: 700; text-decoration: none;
  transition: background var(--t-fast), color var(--t-fast);
}
.modelchip input { position: absolute; opacity: 0; pointer-events: none; }
.modelchip:hover { background: var(--web-on-lime-hi); }
/* `:has(:checked)` is the whole story for a radio group: nothing re-renders on
   a pick, so the chip lights up on its own and — the part that matters — stops
   being lit when the pick moves on. `.on` stays for the chip groups that are
   NOT radios (the schema editor's source picker, toggled in JS); putting it on
   a radio chip pins it lit forever. */
.modelchip.on, .modelchip:has(input:checked) {
  background: var(--web-text); color: var(--web-black);
}
.modelchip-edit { color: inherit; opacity: .45; font-size: .78rem; text-decoration: none; }
.modelchip-edit:hover { opacity: 1; color: inherit; }
.modelchip-new { background: transparent; box-shadow: inset 0 0 0 1.5px rgba(28,28,26,.28); }
.modelchip-new:hover { background: var(--web-on-lime); box-shadow: none; }
/* A provider with no key: on screen so the option is known to exist, unpressable
   so nothing is queued that fails at the first call. The chip says which
   variable to set. */
.modelchip.is-off { opacity: .55; cursor: not-allowed; }
.modelchip.is-off:hover { background: var(--web-on-lime); }
/* Under the tier chips: what the pressed one means, one line. */
.tier-note { margin: .45rem 0 0; font-size: .8rem; color: var(--web-on-accent); min-height: 1.2em;
  display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
.tier-note .mono { font-weight: 700; }
/* The full catalog: a list, not a wall of chips. One group per provider,
   the ones with a key first; two columns — name, about what one scraper
   costs. Recessed white on the hero like everything else you pick there;
   the chosen row goes ink. */
.mlist { background: var(--web-on-lime); border-radius: 1.1rem; padding: .35rem .5rem .5rem; font-size: .8rem; }
.mlist-head, .mlist-row { display: grid; grid-template-columns: 1fr minmax(12rem, auto); gap: .5rem; align-items: center; }
.mlist-head { padding: .3rem .7rem .2rem; font-size: .66rem; font-weight: 800; letter-spacing: .05em;
  text-transform: uppercase; color: var(--web-dim); }
.mlist-head span:not(:first-child), .mlist-row span:not(:first-child) { text-align: right; }
.mlist-note { margin: .1rem .7rem .3rem; font-size: .74rem; color: var(--web-dim); line-height: 1.4; }
.mlist-fam { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap;
  padding: .6rem .7rem .25rem; font-size: .78rem; color: var(--web-text); font-weight: 800; }
.mlist-count { font-size: .68rem; font-weight: 600; color: var(--web-dim); }
.mlist-off { color: var(--web-dim); font-weight: 600; font-size: .7rem; }
.mlist-group.is-off .mlist-row { opacity: .5; cursor: not-allowed; }
.mlist-group.is-off .mlist-row:hover { background: transparent; }
.mlist-row { position: relative; margin: 0; padding: .32rem .7rem; border-radius: 999px; cursor: pointer; }
.mlist-row input { position: absolute; opacity: 0; pointer-events: none; }
.mlist-row:hover { background: var(--web-on-lime-hi); }
.mlist-row.on, .mlist-row:has(input:checked) { background: var(--web-text); color: var(--web-black); }
.mlist-name { display: inline-flex; align-items: center; gap: .45rem; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.mlist-tag { font-family: var(--font-sans, inherit); font-size: .64rem; font-weight: 800; letter-spacing: .04em;
  text-transform: uppercase; opacity: .7; }
/* A provider's mark: an ink tile, the glyph in the recessed white — the
   same pair every pressed chip uses, so the marks belong to the palette
   rather than bringing four brand colours onto the lime. */
.pmark {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 2rem; height: 2rem; border-radius: .6rem; background: var(--web-text);
  color: var(--web-black); vertical-align: -.5em; font-size: .8rem; font-weight: 800;
}
.pmark svg { width: 1.3rem; height: 1.3rem; }
.mlist-fam .pmark { width: 1.75rem; height: 1.75rem; border-radius: .5rem; }
.mlist-fam .pmark svg { width: 1.15rem; height: 1.15rem; }
.pmark.sm { width: 1.15rem; height: 1.15rem; border-radius: .35rem; vertical-align: -.25em; }
.pmark.sm svg { width: .8rem; height: .8rem; }
.link-btn { background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
  text-decoration: underline; text-underline-offset: .15em; }
.hero .link-btn { color: var(--web-on-accent); }

/* ---------- lists are cards with gaps, not rows in a box ----------
   LeadHunt stacks its campaigns as separate white cards with air between them
   (`space-y-3`), which is why the list reads as calm rather than as a table.
   Applied to the panel and the activity feed rather than restructuring their
   markup: the box becomes transparent and each row becomes the card. */

.panel { background: transparent; box-shadow: none; border-radius: 0; }
.panel-head { padding: 0 .25rem 1rem; }
.panel-body { display: flex; flex-direction: column; gap: .55rem; }

.panel-body .feed-row, .feed-list .feed-row {
  background: var(--web-panel); border-radius: 1.5rem; border-bottom: 0;
  box-shadow: var(--shadow-card); padding: 1rem 1.25rem;
  transition: box-shadow var(--t-fast);
}
.panel-body .feed-row:hover, .feed-list .feed-row:hover {
  background: var(--web-panel); box-shadow: var(--shadow-lift);
}
.feed-list { display: flex; flex-direction: column; gap: .55rem; }

/* The view switch reads as a segmented pill, like the tab strips elsewhere. */
.switches {
  display: inline-flex; flex-wrap: wrap; gap: .25rem; background: var(--web-panel);
  border-radius: 999px; padding: .3rem; box-shadow: var(--shadow-card);
}
.switch {
  border: 0; background: none; border-radius: 999px; cursor: pointer;
  padding: .45rem .95rem; font-size: .82rem; font-weight: 600; color: var(--web-dim);
  font-family: inherit; display: inline-flex; align-items: center; gap: .35rem;
  white-space: nowrap;
}
.switch:hover { background: var(--web-wash); color: var(--web-text); }
.switch.on { background: var(--web-text); color: var(--web-black); }
.switch.on .switch-count { background: var(--web-orange); color: var(--web-on-accent); }
.switch-count {
  background: var(--web-well); border-radius: 999px; padding: 0 .4rem;
  font-size: .7rem; font-weight: 700;
}

/* ---------- one way in at a time ---------- */
.source-pane { margin-top: 1.25rem; }
.source-pane .form-control {
  background: var(--web-on-lime-hi); border-color: transparent;
  border-radius: 1rem; padding: .8rem 1rem;
}
.source-pane .form-control:focus { background: #fff; box-shadow: 0 0 0 3px rgba(28,28,26,.18); }
.hero-note {
  font-size: .8rem; color: var(--web-on-accent); opacity: .72;
  margin: .55rem 0 .9rem;
}
.hero-note .mono, .hero-note em { opacity: 1; }
/* The editor sits on the lime like the textarea it replaces. */
.source-pane .CodeMirror {
  border-radius: 1rem; border: 0; background: var(--web-on-lime-hi);
  min-height: 16rem; padding: .5rem 0;
}
.source-pane .CodeMirror-gutters { background: transparent; border-right: 0; }
#source-chips .modelchip { font-size: .86rem; padding: .55rem 1.1rem; }

/* ---------- the scheduling sheet ----------
   Native <dialog>: backdrop, Esc and focus trapping without a line of JS. */
.sheet {
  border: 0; border-radius: var(--r-card); padding: 1.75rem;
  width: min(30rem, calc(100vw - 2rem)); max-height: 88vh; overflow: auto;
  background: var(--web-panel); color: var(--web-text);
  box-shadow: var(--shadow-lift);
}
.sheet::backdrop { background: rgba(28,28,26,.45); backdrop-filter: blur(3px); }
.sheet-x { position: absolute; top: 1rem; right: 1rem; margin: 0; }
.sheet-x button {
  border: 0; background: var(--web-well); border-radius: 999px;
  width: 32px; height: 32px; cursor: pointer; color: var(--web-dim); font-size: .9rem;
}
.sheet-x button:hover { background: var(--web-panel-3); color: var(--web-text); }
.sheet-title { font-size: 1.35rem; font-weight: 800; letter-spacing: -.02em; margin: 0; }
.sheet-when { font-size: .85rem; color: var(--web-dim); margin: .3rem 0 1.25rem; }
.sheet-form .form-label { margin-top: .35rem; }
.sheet-form .btn { width: 100%; margin-top: .5rem; }

/* A day you can act on has to look like it. */
.cal-day[role="button"] { cursor: pointer; transition: background var(--t-fast); }
.cal-day[role="button"]:hover { background: var(--web-lime-soft); }
.cal-day[role="button"]:focus-visible {
  outline: 2px solid var(--web-text); outline-offset: -2px;
}

/* With links no longer coloured, weight plus a soft underline is what marks
   them. Chrome elements (nav, buttons, chips, rows) opt out — they are already
   obviously clickable and an underline there is noise. */
a { font-weight: 600; text-underline-offset: .18em; text-decoration-thickness: 1px; }
.card-body a:not(.btn):not(.chip):not(.pill-stat):not(.seemore):not(.srow-name):not(.viewtab):not(.menu-item),
.srow-facts a, p a:not(.btn), td a:not(.btn) { text-decoration: underline; }
.card-body a:not(.btn):hover, .srow-facts a:hover { color: var(--web-hi); }
.topbar a, .btn, .chip, .pill-stat, .modelchip, .srow-name, .monthnav a,
.usercard-nav a, .seemore, .tab, .switch { text-decoration: none; }

/* Small text was being asked to be quiet AND legible; give it size back. */
.small-muted, .form-text, .hint { font-size: .82rem; color: var(--web-dim); }
.srow-url, .srow-facts, .mine-facts { font-size: .8rem; }
.stat-tile .stat-note, .page-sub { color: var(--web-dim); }

.schema-name { display: block; font-size: 1.1rem; font-weight: 700; letter-spacing: -.02em; }
.schema-key {
  display: block; font-size: .76rem; font-weight: 500; color: var(--web-dim);
  margin-top: .1rem;
}
/* Every model card the same height, so the row of buttons lines up. */
.row.g-3 > [class*="col-"] > .card.h-100 { height: 100%; }

/* ---------- a numbered flow ----------
   Install instructions are a sequence, so they are drawn as one: a number, a
   heading, and only the thing you do at that point. Prose in two columns made
   the reader work out the order for themselves. */
.steps-flow { list-style: none; margin: 0 0 var(--gap); padding: 0;
              display: flex; flex-direction: column; gap: .55rem; }
.step-card {
  display: flex; gap: 1.1rem; align-items: flex-start;
  background: var(--web-panel); border-radius: var(--r-card);
  padding: 1.35rem 1.5rem; box-shadow: var(--shadow-card);
}
.step-n {
  flex: none; width: 34px; height: 34px; display: grid; place-items: center;
  border-radius: 999px; background: var(--web-text); color: var(--web-black);
  font-weight: 800; font-size: .95rem;
}
.step-card.done .step-n { background: var(--web-orange); color: var(--web-on-accent); }
.step-body { min-width: 0; flex: 1 1 auto; }
.step-title { font-size: 1.05rem; font-weight: 700; letter-spacing: -.02em; margin: .2rem 0 .4rem; }
.step-text { color: var(--web-dim); font-size: .9rem; margin: 0 0 .8rem; }
.step-card .btn { margin-top: .2rem; }
.token-row { max-width: 30rem; }

/* ---------- the model editor ---------- */
.schema-form { padding-bottom: 5rem; }   /* clear of the sticky save bar */
.fieldgrid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: .85rem;
}
.fg { display: flex; flex-direction: column; margin: 0; min-width: 0; }
.fg-wide { grid-column: 1 / -1; }
.fg .form-control { width: 100%; }
/* Labels are the quiet half of a pair; they were competing with their values. */
.fieldgrid .form-label { margin-bottom: .3rem; }

.drawer-inline { margin-top: 1.1rem; }
.drawer-inline > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: .45rem;
  font-size: .86rem; font-weight: 700; color: var(--web-dim);
  padding: .6rem 0; border-top: 1px solid var(--web-line-soft);
}
.drawer-inline > summary::-webkit-details-marker { display: none; }
.drawer-inline > summary:hover { color: var(--web-text); }
.drawer-inline > summary .chev { transition: transform .15s ease; }
.drawer-inline[open] > summary .chev { transform: rotate(90deg); }

.savebar {
  position: sticky; bottom: 1rem; z-index: 40;
  display: flex; align-items: center; gap: 1rem; margin-top: var(--gap);
  background: var(--web-text); border-radius: 999px;
  padding: .55rem .55rem .55rem 1.35rem; box-shadow: var(--shadow-lift);
}
.savebar-what {
  flex: 1 1 auto; min-width: 0; color: var(--web-black); font-weight: 700;
  font-size: .88rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.savebar .btn-primary { background: var(--web-orange); color: var(--web-on-accent); }
.savebar .btn-primary:hover { background: var(--web-orange-hi); color: var(--web-on-accent); }

/* The code drawer runs the full width now, so it stops scrolling sideways. */
.reveal .code-block, .reveal .console { border-radius: 0 0 var(--r-card) var(--r-card); }

button.pill-stat { border: 0; cursor: pointer; font-family: inherit; }
.pill-danger { color: var(--web-red-txt); }
.pill-danger:hover { background: rgba(196,40,58,.10); }
.pill-danger i.bi { color: var(--web-red-txt); }
/* The rebuild drawer is a side door, so it reads as one. */
.rebuild > summary {
  list-style: none; cursor: pointer; font-size: .88rem; font-weight: 700;
  color: var(--web-dim); padding: .8rem 1.1rem; border-radius: 999px;
  border: 1px dashed var(--web-line-hi); display: inline-flex; gap: .5rem;
  align-items: center;
}
.rebuild > summary::-webkit-details-marker { display: none; }
.rebuild > summary:hover { color: var(--web-text); border-color: var(--web-text); }

/* ==========================================================================
   Mobile: the nav goes to the thumb.

   Five links beside the mark do not fit on a phone; wrapping them pushed the
   actual page below the fold. Below 760px the top bar keeps the mark and the
   account, and the same five links render as a bottom tab bar.
   ========================================================================== */
.tabbar-mobile { display: none; }

@media (max-width: 760px) {
  .topbar-nav { display: none; }
  .topbar { padding: .6rem .9rem; gap: .6rem; }
  .topbar-mark { flex: 1 1 auto; }

  .tabbar-mobile {
    display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 80;
    background: var(--web-bar); backdrop-filter: blur(12px);
    border-top: 1px solid var(--web-line-soft);
    padding: .4rem .3rem calc(.4rem + env(safe-area-inset-bottom));
  }
  .tabbar-mobile a {
    flex: 1 1 0; min-width: 0; display: flex; flex-direction: column;
    align-items: center; gap: .15rem; padding: .35rem .1rem;
    text-decoration: none; color: var(--web-dim); font-size: .66rem;
    font-weight: 600; border-radius: 1rem;
  }
  .tabbar-mobile a i.bi { font-size: 1.15rem; line-height: 1; }
  .tabbar-mobile a span {
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .tabbar-mobile a.active { color: var(--web-text); background: var(--web-lime-soft); }

  /* Clear of the bar, and of the sticky action bars that sit above it. */
  main.container-fluid, main.studio-main { padding-bottom: 6.5rem; }
  .runbar { bottom: 5.2rem; }
  .savebar { bottom: 5.2rem; }

  /* Touch targets. 30px pills are fine with a mouse and a lottery with a thumb. */
  .btn { padding: .6rem 1.15rem; }
  .btn-sm { padding: .45rem 1rem; }
  .modelchip, .tab, .switch, .tabbtn { padding: .55rem 1rem; }
  .form-control, .form-select { padding: .65rem 1rem; font-size: 16px; }
  /* 16px is not a style choice: iOS Safari zooms the page on focus below it. */
}

/* ---------- run controls on the lime hero ---------- */
.hero-row { margin-top: 1rem; }
.hero-row .form-label, .hero .runbar-check { color: var(--web-on-accent); }
.hero-row .row, .hero-row .col-6, .hero-row .col-md-6 { --bs-gutter-x: .75rem; }
.hero-check { margin: 1rem 0; font-size: .85rem; }
.hero .btn.big { width: 100%; }
.hero .form-text, .hero .small-muted { color: var(--web-on-accent); opacity: .72; }

/* ---------- the capacity line ---------- */
.capline {
  font-size: .82rem; color: var(--web-dim); line-height: 1.45;
  background: var(--web-well); border-radius: 1rem; padding: .7rem .9rem;
  margin-bottom: 1.1rem;
}
.capline b { color: var(--web-text); font-weight: 700; }
.capline-src { opacity: .7; }
/* Over budget is the one thing here worth interrupting for. */
.capline-bad { background: rgba(196,40,58,.09); color: var(--web-red-txt); }
.capline-bad b { color: var(--web-red-txt); }

/* ---------- the log, folded away ---------- */
/* A run page leads with what happened, not with agent chatter. The summary row
   carries the last timestamp and the last line, so the fold costs no
   information at a glance; opening it is for when something went wrong. */
.log-well { border-top: 1px solid var(--web-line-soft); }
.log-well > summary {
  display: flex; align-items: center; gap: .6rem; cursor: pointer;
  padding: .7rem 1rem; font-size: .82rem; color: var(--web-dim);
  list-style: none; user-select: none;
}
.log-well > summary::-webkit-details-marker { display: none; }
.log-well > summary::before {
  content: "›"; display: inline-block; font-size: 1.1rem; line-height: 1;
  color: var(--web-faint); transition: transform .15s ease;
}
.log-well[open] > summary::before { transform: rotate(90deg); }
.log-well[open] > summary { border-bottom: 1px solid var(--web-line-soft); }
.log-well-title { font-weight: 600; color: var(--web-text); white-space: nowrap; }
/* The captured line is the wide, squeezable part — it truncates, the rest does not. */
.log-well-note {
  flex: 1 1 auto; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
.log-well > .console { border-radius: 0; max-height: 320px; }

/* ---------- "this is updating" dot ---------- */
.live-dot {
  display: inline-block; width: .5rem; height: .5rem; border-radius: 999px;
  background: var(--web-accent); animation: pulse 1.6s ease-in-out infinite;
}

/* ---------- the rebuild hero ---------- */
/* Lime is the colour of a thing that works. A scraper that is failing gets the
   same shape — one obvious action, one field — on the recessed paper surface
   with a red rule, so the page never celebrates a broken scraper. */
.hero-fix {
  background: var(--web-panel);
  border-radius: var(--r-card); padding: 1.4rem 1.5rem;
  box-shadow: var(--shadow-card);
  border: 0; border-top: 3px solid var(--web-red);
}
.hero-fix .hero-title { color: var(--web-text); }
.hero-fix .form-control, .hero-fix .form-select {
  background: var(--web-well); border-color: var(--web-line-soft); color: var(--web-text);
}
.hero-fix .btn-primary { background: var(--web-text); color: var(--web-black); }
.hero-fix .btn-primary:hover { background: #000; }
.hero-fix label, .hero-fix .form-label { color: var(--web-dim); }
/* Running a scraper that is known broken is allowed, just not the headline. */
.hero-fix details > summary { color: var(--web-dim); cursor: pointer; font-size: .85rem; }

/* ---------- field coverage: what came back, per field ---------- */
/* The quality score says whether the build worked. These bars say what is
   missing, which is the part you can act on — one row per field, filled by how
   many sample items carried a value. */
.cov-legend { display: flex; gap: 1.1rem; font-size: .78rem; color: var(--web-dim); }
.cov-legend b { color: var(--web-text); font-weight: 700; }
.cov-row {
  display: grid; grid-template-columns: minmax(6rem, 12rem) 1fr auto;
  gap: .75rem; align-items: center; padding: .28rem 0;
}
.cov-name { font-size: .78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cov-req { color: var(--web-red-txt); }
.cov-track {
  height: .5rem; border-radius: 999px; background: var(--web-well);
  overflow: hidden; min-width: 3rem;
}
.cov-fill { display: block; height: 100%; border-radius: 999px; background: var(--web-accent); }
.cov-num { font-size: .72rem; color: var(--web-faint); }
/* A field that never landed is the one worth looking at, so it is the one that
   gets a colour; a partial fill stays accent, because it did work sometimes. */
.cov-empty .cov-fill { background: var(--web-red); }
.cov-empty .cov-track { background: rgba(224,36,61,.12); }
.cov-empty .cov-name { color: var(--web-red-txt); }

/* ---------- the agent pipeline, evenly ---------- */
/* It was a flex row of nodes with 26px connector cells between them, so at any
   width that did not fit all five the row broke wherever it ran out — leaving a
   node on its own line behind a connector pointing at nothing. A grid gives
   every node the same width and wraps into tidy rows; the connectors go, since
   the pipeline reads left-to-right from the states themselves. */
.agent-graph {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  gap: .5rem; align-items: stretch;
}
.agent-graph .ag-edge { display: none; }
.agent-graph .ag-node { min-width: 0; }
/* One node (a scrape) should not stretch across the whole card. */
.agent-graph-single { grid-template-columns: minmax(0, 18rem); }

/* ---------- view tabs, at a size worth aiming at ---------- */
/* These carry the whole build page now (Overview / Items / Plan / Screens /
   Code), so they are navigation, not a filter widget tucked in a corner —
   .78rem chips read as decoration next to the cards they switch between. */
.viewtabs { gap: .25rem; padding: .28rem; border-radius: 999px; }
.viewtab {
  font-size: .92rem; padding: .5rem 1.1rem; border-radius: 999px;
  display: inline-flex; align-items: center; gap: .45rem;
}
.viewtab .mono { font-size: .74rem; opacity: .75; }
/* The selected tab is ink — the same "you are here" the rest of the app uses.
   Lime is a highlight; as a tab background it competed with the page. */
.viewtab.active {
  background: var(--web-text); color: var(--web-black); box-shadow: none;
}
.viewtab.active .mono { opacity: .8; }
@media (max-width: 640px) {
  .viewtabs { display: flex; width: 100%; overflow-x: auto; }
  .viewtab { white-space: nowrap; }
}

/* ---------- log colouring ---------- */
/* Not syntax highlighting so much as pushing the furniture back: every line
   carries a timestamp and a logger name that are identical down the whole
   column, so they go quiet and the message is what the eye lands on. Levels
   use the palette's existing status hues — no fourth colour invented here. */
.console .lg-t { color: var(--web-faint); }
.console .lg-n { color: var(--web-dim); }
.console .lg-m { color: var(--web-text-2); }
.console .lg-l { font-weight: 700; color: var(--web-faint); }
.console .lg-warning, .console .lg-warn { color: var(--web-text); }
.console .lg-error, .console .lg-critical { color: var(--web-red-txt); }
/* the parts of a message worth scanning for */
.console .lg-arrow { color: var(--web-accent-txt); font-weight: 700; }
.console .lg-k { color: var(--web-faint); }
.console .lg-v { color: var(--web-text); }
.console .lg-url { color: var(--web-dim); text-decoration: underline; text-underline-offset: 2px; }
.console .lg-num { color: var(--web-text); font-weight: 600; }

/* ---------- a new frame arrives ---------- */
/* The stage used to cut to the new picture the instant it swapped, which at one
   frame every three seconds reads as a flicker rather than progress. htmx marks
   the settling window; the frame fades up over it. The box never moves, so this
   is the only thing on the card that changes. */
.shot-stage img { animation: shot-in .26s ease-out; }
.shot-strip a:last-child { animation: shot-in .32s ease-out; }
@keyframes shot-in { from { opacity: 0; transform: scale(.985); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .shot-stage img, .shot-strip a:last-child { animation: none; }
}
.shot-stage > a, .shot-strip a { cursor: zoom-in; }

/* ---------- the frame viewer ---------- */
/* Frames open here rather than in a new tab: a run is a sequence, and reading it
   should not cost the page you were on. */
.shotbox {
  position: fixed; inset: 0; z-index: 60; display: none;
  background: rgba(28, 28, 26, .82);
  align-items: center; justify-content: center; padding: 2rem 1rem;
}
.shotbox.on { display: flex; animation: shot-in .18s ease-out; }
.shotbox-inner {
  position: relative; max-width: min(1200px, 96vw); max-height: 92vh;
  display: flex; flex-direction: column; align-items: center; gap: .75rem;
}
.shotbox img {
  max-width: 100%; max-height: 78vh; width: auto; height: auto;
  border-radius: var(--r-well); background: var(--web-panel);
  box-shadow: 0 24px 70px rgba(0,0,0,.45);
}
.shotbox-close {
  position: absolute; top: -2.4rem; right: 0; width: 2.1rem; height: 2.1rem;
  border: 0; border-radius: 999px; cursor: pointer; font-size: 1.3rem; line-height: 1;
  background: var(--web-panel); color: var(--web-text);
}
.shotbox-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 2.6rem; height: 2.6rem; border: 0; border-radius: 999px; cursor: pointer;
  background: var(--web-panel); color: var(--web-text); font-size: 1.5rem; line-height: 1;
  opacity: .9;
}
.shotbox-nav:hover { opacity: 1; }
.shotbox-prev { left: -1.3rem; }
.shotbox-next { right: -1.3rem; }
.shotbox-bar {
  display: flex; align-items: center; gap: .8rem; max-width: 100%;
  background: var(--web-panel); border-radius: 999px; padding: .45rem .9rem;
  font-size: .82rem; color: var(--web-dim);
}
.shotbox-play {
  border: 0; background: var(--web-text); color: var(--web-black);
  border-radius: 999px; padding: .25rem .8rem; font-size: .78rem; font-weight: 700;
  cursor: pointer; white-space: nowrap;
}
.shotbox-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.shotbox-count { color: var(--web-faint); white-space: nowrap; }
@media (max-width: 640px) {
  .shotbox-prev { left: .2rem; } .shotbox-next { right: .2rem; }
}
/* the key, shown only when two models share a title */
.modelchip-key { font-size: .68rem; opacity: .6; margin-left: .2rem; }

/* The same pick-one-of-these chip, on a WHITE card instead of the lime hero.
   `.modelchip` fills with `--web-on-lime`, which is a recessed white that only
   reads as a surface against lime — on a card it disappears. This is the ink
   wash instead, and the chosen one goes solid ink, so "which is picked" reads
   the same way in both places. A submit button, not a radio: the pick posts on
   click, so there is no `:checked` to hang the lit state on. */
.chip-pick {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .45rem .95rem; border-radius: var(--r-ctl); border: 0; cursor: pointer;
  background: var(--web-wash); color: var(--web-text);
  font-size: .82rem; font-weight: 700; font-family: inherit;
  transition: background var(--t-fast), color var(--t-fast);
}
.chip-pick:hover { background: var(--web-wash-2); }
.chip-pick.on { background: var(--web-text); color: var(--web-black); }
.chip-pick.on:hover { background: var(--web-hi); }
.chip-pick .small-muted { color: inherit; opacity: .6; }

/* ---------- attempts, as cards ---------- */
/* A table gave the judge's paragraph a two-word-wide column next to three
   numbers with nothing in them. Each attempt is a card: the numbers on one
   line, the reasoning underneath at full width where it can be read. */
.attempts { display: flex; flex-direction: column; gap: .75rem; }
.attempt { border-left: 3px solid var(--web-line); }
.attempt-ok { border-left-color: var(--web-ok-txt); }
.attempt-no { border-left-color: var(--web-red); }
.attempt-head { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; }
.attempt-n { font-weight: 700; color: var(--web-text); }
.attempt-score { display: inline-flex; align-items: center; gap: .5rem; }

/* ---------- which step are we on ---------- */
/* The states were a tinted border and a coloured dot, which at a glance made
   five near-identical cards. They are filled now: the step in flight is the
   accent (the system's colour for anything in progress), finished steps are
   green, and a failed one is red — so "where is it?" is answered from across
   the room rather than by reading five labels. */
.ag-node { transition: background .2s ease, border-color .2s ease, box-shadow .2s ease; }

.ag-active {
  background: var(--web-orange); border-color: var(--web-orange);
  box-shadow: 0 6px 24px rgba(var(--web-accent-rgb), .45);
}
.ag-active .ag-label, .ag-active .ag-blurb,
.ag-active .ag-time, .ag-active .ag-time-est, .ag-active .ag-time-over {
  color: var(--web-on-accent);
}
.ag-active .ag-blurb { opacity: .8; }
.ag-active .ag-dot {
  background: var(--web-panel); border-color: var(--web-panel); color: var(--web-on-accent);
}
.ag-active .ag-ping { border-color: var(--web-on-accent); }
.ag-active .ag-progress { background: rgba(var(--web-tint-rgb), .22); }
.ag-active .ag-progress > span { background: var(--web-on-accent); }

.ag-done { background: var(--web-ok); border-color: var(--web-ok); }
.ag-done .ag-label, .ag-done .ag-blurb, .ag-done .ag-time { color: var(--web-panel); }
.ag-done .ag-blurb { opacity: .78; }
.ag-done .ag-dot {
  background: var(--web-panel); border-color: var(--web-panel); color: var(--web-ok);
}

.ag-failed { background: var(--web-red); border-color: var(--web-red); }
.ag-failed .ag-label, .ag-failed .ag-blurb, .ag-failed .ag-time { color: var(--web-panel); }
.ag-failed .ag-blurb { opacity: .8; }
.ag-failed .ag-dot {
  background: var(--web-panel); border-color: var(--web-panel); color: var(--web-red);
}

/* ---------- destructive, and looking it ---------- */
/* Delete was .btn-quiet — a grey glyph that read as decoration next to a lime
   Run. It carries its own colour now, filling on hover so the last confirmation
   before the dialog is unambiguous. */
.btn-danger-soft {
  color: var(--web-red-txt); background: transparent;
  box-shadow: inset 0 0 0 1px rgba(224,36,61,.32);
}
.btn-danger-soft:hover {
  color: var(--web-panel); background: var(--web-red); box-shadow: none;
}

/* ---------- failures, folded ---------- */
.broken-fold { margin-top: .5rem; }
.broken-fold > summary {
  display: flex; align-items: center; gap: .55rem; cursor: pointer;
  padding: .6rem .9rem; border-radius: 999px; width: max-content;
  font-size: .84rem; color: var(--web-dim); list-style: none;
}
.broken-fold > summary::-webkit-details-marker { display: none; }
.broken-fold > summary::after {
  content: "›"; font-size: 1.1rem; line-height: 1; color: var(--web-faint);
  transition: transform .15s ease;
}
.broken-fold[open] > summary::after { transform: rotate(90deg); }
.broken-fold > summary:hover { background: var(--web-wash); }
.broken-count {
  font-weight: 700; color: var(--web-red-txt);
  background: rgba(224,36,61,.12); border-radius: 999px; padding: .1rem .6rem;
}
.broken-fold .srow-list { margin-top: .4rem; }

/* a compact view switcher, for rows rather than page headers */
.viewtabs-sm { padding: .18rem; }
.viewtabs-sm .viewtab { font-size: .76rem; padding: .3rem .7rem; gap: .3rem; }

/* ---- back link ---------------------------------------------------------
   A page reached from one place should offer the way back to it. Small,
   above the title, out of the way of the thing you came to read. */
.backlink {
  display: inline-flex; align-items: center; gap: .35rem;
  font-size: .82rem; font-weight: 600; color: var(--web-dim);
  text-decoration: none; margin-bottom: .3rem;
}
.backlink:hover { color: var(--web-text); }

/* ---- the fold-out menu -------------------------------------------------
   <details> as a dropdown: no plugin, no init, keyboard-reachable. The panel
   is absolutely placed so it never widens the row it sits in. */
.menu { position: relative; display: inline-block; }
.menu > summary { list-style: none; cursor: pointer; }
.menu > summary::-webkit-details-marker { display: none; }
.menu-caret { font-size: .68rem; opacity: .7; margin-left: .1rem; }
.menu[open] .menu-caret { transform: rotate(180deg); display: inline-block; }
.menu-pop {
  position: absolute; z-index: 40; top: calc(100% + .4rem); left: 0;
  min-width: 15rem; padding: .35rem;
  background: var(--web-panel); border-radius: 1rem;
  box-shadow: 0 1px 2px rgba(28,28,26,.05), 0 12px 32px rgba(28,28,26,.14);
}
.menu-end .menu-pop { left: auto; right: 0; }
.menu-item {
  display: flex; align-items: center; gap: .6rem;
  padding: .5rem .65rem; border-radius: .75rem;
  color: var(--web-text); text-decoration: none; font-size: .85rem;
}
.menu-item:hover { background: var(--web-wash); color: var(--web-text); }
.menu-item i { font-size: 1rem; color: var(--web-accent-txt); }
.menu-item b { display: block; font-weight: 700; }
.menu-item small { display: block; color: var(--web-dim); font-size: .74rem; }

/* A one-line confirmation after a redirect — said once, then gone on reload. */
.flash-note {
  display: flex; align-items: center; gap: .5rem;
  margin: 0 0 .9rem; padding: .7rem 1rem;
  background: var(--web-panel); border-radius: 999px;
  font-size: .86rem; color: var(--web-text);
  box-shadow: 0 1px 2px rgba(28,28,26,.05), 0 8px 24px rgba(28,28,26,.06);
}
.flash-note i { color: var(--web-red-txt); }

/* ---- the head of a run --------------------------------------------------
   Which scraper, against what, and what you can do about it — on one line at
   full width, wrapping to two on a phone. Not a card: it is the page's title,
   and boxing a title makes it compete with the panels underneath. */
.runhead {
  display: flex; align-items: center; gap: .9rem;
  flex-wrap: wrap; margin-bottom: .9rem;
}
.runhead-icon {
  flex: none; width: 42px; height: 42px; border-radius: 12px;
  object-fit: contain; background: var(--web-panel); padding: 5px;
  box-shadow: var(--shadow-card);
}
.runhead-icon.letter {
  display: grid; place-items: center; padding: 0;
  background: var(--web-lime-soft); font-weight: 800; color: var(--web-text);
}
.runhead-body { flex: 1 1 16rem; min-width: 0; }
.runhead-title {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  font-size: 1.35rem; font-weight: 800; letter-spacing: -.02em;
}
.runhead-sub {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  font-size: .82rem; color: var(--web-dim); margin-top: .15rem;
}
.runhead-sub .mono { font-size: .76rem; }
.runhead-do { flex: none; display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; }
/* The readout under the head reads as figures, so the numbers lead. */
.runstats b { font-size: 1.02rem; }

/* A table wider than its card scrolls sideways; the cut edge alone reads as
   broken markup, so the last inch fades to say "there is more this way". */
.table-responsive { position: relative; }
.card > .table-responsive::after {
  content: ""; position: sticky; top: 0; right: 0; float: right;
  width: 2.5rem; height: 100%; pointer-events: none;
  background: linear-gradient(90deg, transparent, var(--web-panel));
}

/* There is no `wide` any more: every page is the bar's width, and a wide
   table scrolls inside its card. */
/* A link cell holds an address, not a paragraph: it needs less room than the
   text columns beside it. */
.link-cell { max-width: 230px; }

/* ---- a pill that is also a meter ---------------------------------------
   The readout said "50%" beside a bar that said the same thing. This is the
   bar: the lime fills the pill to the figure it is showing. Ink text reads on
   both halves, which is why the fill can run under it. */
.pill-meter {
  position: relative; overflow: hidden;
  background: var(--web-well); color: var(--web-text);
}
.pill-meter::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: var(--fill, 0%);
  background: linear-gradient(90deg, var(--web-orange-lo), var(--web-orange));
  transition: width .6s cubic-bezier(.2,.7,.3,1);
}
.pill-meter > * { position: relative; }
.pill-meter .lbl-mini { color: var(--web-text); opacity: .62; }

/* The run's Progress card carries the readout now — the pills say the same
   thing as the thin bar above the steps did, with room to say it. */
.run-progress .steps-head { display: none; }
.run-progress .runstats { margin-bottom: .9rem; }
/* The two cards at the top of a run are one row and should read as one row:
   equal height, whatever the steps and the screenshots do inside them. */
.run-progress { display: flex; flex-direction: column; }
.run-progress .card-body { flex: 1 1 auto; }

/* ---- run options: two choices, pressed rather than typed -----------------
   A segmented pill per question. The chosen segment is ink, matching every
   other "this one is selected" in the studio; lime here would compete with
   the hero it usually sits on. */
/* One question per ROW — the label on the left, the answer on the right — not
   two columns side by side. `flex: 1 1 15rem` used to hand each group half of
   whatever the card had, which at this measure is ~196px, and a segmented
   control whose labels are `white-space: nowrap` does not shrink: it
   OVERFLOWS. That is what clipped "End the run there" off the right edge.
   Two columns cannot be made to work here either — the card is capped at one
   readable measure, so half of it is never enough for three labels, and the
   longer group wrapped to two lines beside a neighbour that did not. A row
   gives every control the full width and lines the answers up with each
   other, which is what makes a settings block look deliberate. */
.runopts { display: grid; gap: .85rem; margin: 0; }
.runopt {
  display: grid; align-items: center; gap: .3rem .9rem;
  grid-template-columns: minmax(0, 12.5rem) minmax(0, 1fr);
}
/* Narrow: the label goes back above its control rather than squeezing it. */
@media (max-width: 40rem) { .runopt { grid-template-columns: 1fr; } }
.runopt-lbl {
  font-size: .68rem; font-weight: 700; letter-spacing: .09em;
  text-transform: uppercase; color: var(--web-text); opacity: .62;
  line-height: 1.35;
}
.seg {
  display: inline-flex; align-items: center; gap: .2rem;
  padding: .22rem; border-radius: var(--r-ctl); background: var(--web-panel);
  max-width: 100%;
  /* Wraps. The belt to the grid's braces: at any width narrow enough that
     the labels still will not fit on one line, they go to a second line
     instead of off the edge. */
  flex-wrap: wrap; row-gap: .2rem;
}
/* The radios themselves are the state; only their labels are visible. */
.seg input[type="radio"] { position: absolute; opacity: 0; pointer-events: none; }
.seg label {
  padding: .34rem .85rem; border-radius: 999px; cursor: pointer;
  font-size: .82rem; font-weight: 700; color: var(--web-dim);
  white-space: nowrap; margin: 0; user-select: none;
}
.seg label:hover { color: var(--web-text); background: var(--web-wash); }
.seg input[type="radio"]:checked + label { background: var(--web-text); color: var(--web-black); }
.seg input[type="radio"]:focus-visible + label { box-shadow: 0 0 0 3px var(--signal-a32); }

/* The count appears only when a count is being asked for. */
.seg-num {
  width: 0; padding: 0; border: 0; margin: 0; opacity: 0;
  font: inherit; font-weight: 700; font-size: .82rem; text-align: center;
  background: transparent; color: var(--web-text);
  transition: width .18s ease, opacity .18s ease, padding .18s ease;
}
.seg:has(.seg-some:checked) .seg-num {
  width: 4.2rem; opacity: 1; padding: .3rem .5rem;
  background: var(--web-well); border-radius: 999px;
}
.seg-num:focus { outline: none; box-shadow: 0 0 0 3px var(--signal-a32); }
/* Chrome's spinner arrows push the value off-centre in a 4rem pill. */
.seg-num::-webkit-outer-spin-button, .seg-num::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}
.seg-num { -moz-appearance: textfield; appearance: textfield; }

/* Set off from the controls it explains, rather than run on underneath them
   as a third paragraph of lime-on-lime text. */
.runopts-note {
  font-size: .8rem; line-height: 1.5; color: var(--web-text); opacity: .68;
  margin: 0; padding-top: 1rem; border-top: 1px solid var(--web-line);
}

/* Progress bars — the token spend meters, and anything else that grows.

   Bootstrap 5.3 sets `--bs-progress-bar-bg` to a literal blue on `.progress`
   itself, so pointing `--bs-primary` at the accent (line 102) never reaches
   it. Exactly the `--bs-link-color-rgb` trap again: the semantic variable is
   not the one the component reads. Name the component's own variable. */
.progress {
  --bs-progress-bg: var(--web-well);
  --bs-progress-bar-bg: var(--web-orange);
  --bs-progress-bar-color: var(--web-on-accent);
  --bs-progress-border-radius: var(--r-ctl);
  border-radius: var(--r-ctl);
}

/* "This site already has a scraper" — the two answers, side by side.

   Deliberately not a modal: the question is about the thing you just pressed
   and it appears exactly where the job card would have. A dialog would darken
   the page and demand an answer before you could look at the scraper it is
   asking about — and the module name in it is a link you may well want. */
.conflict .conflict-choices {
  display: grid; gap: .75rem; grid-template-columns: 1fr 1fr;
}
@media (max-width: 32rem) {
  .conflict .conflict-choices { grid-template-columns: 1fr; }
}
.conflict .favicon { border-radius: 6px; object-fit: contain; }


/* ---------- a build in flight, for a person: step, clock, ladder ---------- */

.bprog { display: flex; flex-direction: column; gap: .5rem; }
.bprog-head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; flex-wrap: wrap; }
.bprog-step { font-weight: 700; font-size: 1rem; color: var(--web-hi); display: inline-flex; align-items: center; gap: .45rem; }
.bprog-time { font-size: .9rem; color: var(--web-text); }
.bprog-bar { height: 8px; border-radius: 999px; background: var(--web-wash-2); overflow: hidden; }
.bprog-bar span { display: block; height: 100%; border-radius: 999px; background: var(--web-text); transition: width .6s ease; }
.bprog-live .bprog-bar span { background: var(--web-orange); }
.bprog-failed .bprog-bar span { background: var(--web-red-txt); }
.bprog-ladder {
  list-style: none; margin: .25rem 0 0; padding: 0;
  display: flex; gap: .35rem .9rem; flex-wrap: wrap; font-size: .8rem; color: var(--web-faint);
}
.bprog-ladder li { display: inline-flex; align-items: center; gap: .3rem; }
.bprog-ladder li.is-done { color: var(--web-dim); }
.bprog-ladder li.is-now { color: var(--web-hi); font-weight: 700; }
.bprog-ladder li.is-failed { color: var(--web-red-txt); font-weight: 700; }
.bprog-ladder li.is-now .bi { color: var(--web-accent-txt); }
.bprog-compact .bprog-step { font-size: .9rem; }

/* The job card: picture on the left, the first rows on the right. */
.jobcard-live { display: grid; grid-template-columns: 3fr 2fr; border-top: 1px solid var(--web-line-soft); }
.jobcard-stage { min-width: 0; }
.jobcard-data { padding: 1rem 1.25rem; border-left: 1px solid var(--web-line-soft); min-width: 0; }
.jobcard-data-head { font-weight: 700; font-size: .9rem; margin-bottom: .6rem; display: flex; align-items: center; gap: .4rem; }
.jobcard-data-head .bi { color: var(--web-accent-txt); }
.jobcard-item { display: flex; flex-direction: column; gap: .1rem; padding: .5rem 0; border-top: 1px solid var(--web-line-soft); min-width: 0; }
.jobcard-item:first-of-type { border-top: 0; padding-top: 0; }
.jobcard-item b { font-size: .88rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jobcard-item .small-muted { font-size: .76rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jobcard-more { border-top: 1px solid var(--web-line-soft); }
@media (max-width: 760px) {
  .jobcard-live { grid-template-columns: 1fr; }
  .jobcard-data { border-left: 0; border-top: 1px solid var(--web-line-soft); }
}

/* Rows inside a card that is already a card — the "Running now" list —
   take less room than the page's own rows, or a list of three fills a
   screen. */
.srow.srow-slim { padding: .6rem .95rem; border-radius: 1rem; gap: .75rem; box-shadow: none; background: var(--web-well); }
.srow.srow-slim:hover { box-shadow: var(--shadow-card); }
.srow.srow-slim .srow-name { font-size: .92rem; }
.srow-list .srow-slim + .srow-slim { margin-top: .35rem; }

/* A chip that is a filter: pressed = ink. */
.pill-stat.is-on { background: var(--web-text); color: var(--web-black); }
.pill-stat.is-on i.bi { color: inherit; }
button.pill-stat { border: 0; font: inherit; cursor: pointer; }

.costbook th { font-size: .68rem; text-transform: uppercase; letter-spacing: .08em; color: var(--web-dim); font-weight: 600; }


/* ---------- the top bar: one white island on the paper ---------- */

/* The wrapper still sticks and still spans the window, but it no longer
   draws a bar of its own — a fade of paper so the page does not show
   through under the island as it scrolls. */
.topbar-wrap {
  background: linear-gradient(var(--web-well) 72%, rgba(240,239,233,0));
  backdrop-filter: none; border-bottom: 0; padding: .7rem .75rem .9rem;
}
.topbar-wrap .topbar {
  background: var(--web-panel); box-shadow: var(--shadow-card);
  border-radius: 999px; padding: .42rem .55rem .42rem .9rem; gap: .7rem;
}
.topbar-mark { border-right: 0; padding-right: .35rem; margin-right: 0; }
/* The links sit in one recessed well, the page you are on lifted out of it
   in ink — the same segmented control the run options use. */
.topbar-nav { background: var(--web-well); border-radius: 999px; padding: .2rem; gap: .1rem; flex: 0 1 auto; }
.topbar-nav a { padding: .4rem .78rem; font-size: .83rem; }
.topbar-nav a:hover { background: var(--web-panel); }
.topbar-nav a.active { box-shadow: 0 1px 2px rgba(28,28,26,.18); }
.spend { margin-left: auto; background: var(--web-well); }
.topbar .usermenu { margin-left: 0; }
@media (max-width: 940px) {
  .topbar-wrap .topbar { border-radius: 1.6rem; padding: .55rem .7rem .6rem .9rem; }
  .topbar-nav { width: 100%; justify-content: center; flex-wrap: wrap; }
}

/* ---------- the build stepper: one question at a time, on lime ---------- */

.stepper-backdrop {
  position: fixed; inset: 0; z-index: 80; background: rgba(28,28,26,.42);
  display: grid; place-items: start center; padding: 6vh 1rem; overflow: auto;
  animation: fade-in .16s ease;
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
.stepper { width: min(44rem, 100%); animation: rise-in .22s ease; }
@keyframes rise-in { from { transform: translateY(10px); opacity: 0; } to { transform: none; opacity: 1; } }
.stepper-head { display: flex; align-items: center; gap: .8rem; margin-bottom: .9rem; }
.stepper-dots { list-style: none; margin: 0; padding: 0; display: flex; gap: .4rem; }
.stepper-dots li { width: 2rem; height: 6px; border-radius: 999px; background: rgba(28,28,26,.18); transition: background var(--t-fast); }
.stepper-dots li.is-done { background: rgba(28,28,26,.55); cursor: pointer; }
.stepper-dots li.is-now { background: var(--web-text); }
.stepper-url { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .78rem; color: var(--web-on-accent); }
.stepper-x { border: 0; background: rgba(28,28,26,.08); color: var(--web-on-accent); width: 30px; height: 30px; border-radius: 999px; display: grid; place-items: center; }
.stepper-x:hover { background: rgba(28,28,26,.16); }
.stepper-hint { color: var(--web-on-accent); font-size: .88rem; margin: 0 0 .9rem; }
.stepper-hint b { font-weight: 700; }
.stepper-foot { display: flex; align-items: center; gap: .6rem; margin-top: 1.25rem; padding-top: 1rem; border-top: 1px solid rgba(28,28,26,.14); }
.stepper .btn-outline-secondary { border-color: rgba(28,28,26,.3); color: var(--web-on-accent); }
.stepper .btn-outline-secondary:hover { background: rgba(28,28,26,.08); color: var(--web-on-accent); }
.stepper-fold > summary { cursor: pointer; font-weight: 600; color: var(--web-on-accent); font-size: .9rem; }
.stepper-summary { display: flex; flex-direction: column; gap: .5rem; }
.stepper-row { display: grid; grid-template-columns: 7rem 1fr; gap: .8rem; font-size: .92rem; align-items: baseline; }
.stepper-row > span { font-size: .72rem; text-transform: uppercase; letter-spacing: .08em; font-weight: 700; color: var(--web-on-accent); }
.stepper-row b { font-weight: 600; min-width: 0; overflow-wrap: anywhere; }
.stepper-row .small-muted { color: var(--web-on-accent); }
/* What the next press will do, in one line under the URL box. */
.composer-summary { font-size: .84rem; color: var(--web-on-accent); }
.composer-summary b { font-weight: 700; }
.linkish { border: 0; background: none; padding: 0; font: inherit; color: inherit; font-weight: 700; text-decoration: underline; cursor: pointer; }

a.composer-summary { text-decoration: none; font-weight: 700; color: var(--web-on-accent); }
a.composer-summary:hover { text-decoration: underline; color: var(--web-on-accent); }
/* The tutorial: a numbered step, one line, and its clip in a window. */
.tut { display: flex; flex-direction: column; gap: 2.25rem; }
.tut-step { min-width: 0; }
.tut-title { display: flex; align-items: center; gap: .65rem; font-size: 1.25rem; font-weight: 800; letter-spacing: -.02em; margin: 0 0 .4rem; }
.tut-n { flex: none; width: 32px; height: 32px; border-radius: 999px; background: var(--web-orange); color: var(--web-on-accent); display: grid; place-items: center; font-size: .95rem; font-weight: 800; }
.tut-body { font-size: .95rem; color: var(--web-text-2); margin: 0 0 .85rem; max-width: 46rem; }
.tut-video { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--r-card);
  background: var(--web-well); box-shadow: var(--shadow-card); }

.composer-dot { color: var(--web-on-accent); opacity: .6; }

/* The spider on its silk, hanging from the mark. `--drop` is set by the
   scroll handler; nothing here moves on its own. */
.topbar-mark { position: relative; }
.spider {
  position: absolute; left: 14px; top: 100%; width: 0; pointer-events: none;
  --drop: 0px; color: var(--web-hi); opacity: 0; transition: opacity .25s ease;
}
.spider.is-out { opacity: 1; }
.spider-silk {
  position: absolute; left: 0; top: -2px; width: 1px; height: calc(var(--drop) + 12px);
  background: linear-gradient(rgba(28,28,26,.5), rgba(28,28,26,.25));
}
.spider-body {
  position: absolute; left: -11px; top: calc(var(--drop) + 2px);
  transform-origin: 50% 0; animation: spider-sway 2.6s ease-in-out infinite;
  filter: drop-shadow(0 1px 1px rgba(28,28,26,.2));
}
@keyframes spider-sway { 0%, 100% { transform: rotate(-4deg); } 50% { transform: rotate(4deg); } }
@media (prefers-reduced-motion: reduce) { .spider { display: none; } }

.mark-figure { transition: opacity .2s ease; transform-origin: 50% 30%; }
.topbar-mark.spider-out .mark-figure { opacity: 0; }

.spider { transition: opacity .12s ease; }


/* ---------- one width, pixel for pixel ----------
   The island and the page share --measure, and the same side padding, so
   the island's outer edges ARE the content's edges on every page. */
.topbar-wrap { padding-left: .75rem; padding-right: .75rem; }
.topbar-wrap .topbar { max-width: calc(var(--measure) - 1.5rem); }
main.container-fluid, main.studio-main { max-width: var(--measure); padding-left: .75rem; padding-right: .75rem; }
@media (max-width: 940px) {
  .topbar-wrap { padding-left: .6rem; padding-right: .6rem; }
  .topbar-wrap .topbar { max-width: calc(var(--measure) - 1.2rem); }
  main.container-fluid, main.studio-main { padding-left: .6rem; padding-right: .6rem; }
}

/* The page sits to the right of the mark's column, so the spider has the
   gutter under the mark to itself instead of dropping over the hero. The
   island keeps the full width; only the content steps in. On a narrow
   screen there is no room for a gutter and the spider takes its chances. */
@media (min-width: 941px) {
  main.container-fluid, main.studio-main { padding-left: 4.4rem; }
}

/* A pressed nav link went white-on-white for a beat: the colour and the
   background both transitioned when `active` landed, and half-way the text
   was already pale on a still-pale pill — under the browser's blue ring.
   The background eases; the colour switches; the ring is ink, keyboard only. */
.topbar-nav a { transition: background var(--t-fast); }
.topbar-nav a:active { background: var(--web-well); color: var(--web-hi); }
.topbar-nav a:focus { outline: none; box-shadow: none; }
.topbar-nav a:focus-visible { outline: 2px solid var(--web-text); outline-offset: 2px; }
.topbar-nav a.active:active { background: var(--web-text); color: var(--web-black); }


/* ---------- a scraper row's facts, as chips ---------- */
.facts { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .4rem; }
.fact {
  display: inline-flex; align-items: center; gap: .3rem; padding: .18rem .6rem;
  border-radius: 999px; background: var(--web-well); color: var(--web-text);
  font-size: .74rem; font-weight: 600; text-decoration: none !important; white-space: nowrap;
}
.fact .bi { font-size: .85em; color: var(--web-accent-txt); }
a.fact:hover { background: var(--web-wash-2); color: var(--web-hi); }
.fact-quiet { color: var(--web-dim); font-weight: 500; }
.fact-quiet .bi { color: var(--web-faint); }
.fact-warn { background: rgba(224,36,61,.08); color: var(--web-red-txt); }
.fact-warn .bi { color: var(--web-red-txt); }
.srow .srow-url { display: inline; font-size: .74rem; }
.srow-broken .srow-icon { filter: grayscale(1); opacity: .7; }
.srow-do .btn-quiet { opacity: .55; }
.srow:hover .srow-do .btn-quiet { opacity: 1; }
.setup-row .card-body { font-size: .9rem; }

/* No spider on a phone: the bar is a thumb bar there and the silk would
   drop over the page you are reading with nowhere to go. */
@media (max-width: 940px) { .spider { display: none; } }

/* The export menu (`.menu-list`, on the results and data pages) had no
   rule of its own: it opened flush against the button, with the items at
   full size. It is the same pop-over as `.menu-pop`, with air above it,
   hanging from the button's right edge. */
.menu-list {
  position: absolute; z-index: 40; top: calc(100% + .55rem); right: 0; left: auto;
  min-width: 14rem; padding: .35rem;
  background: var(--web-panel); border-radius: 1rem;
  box-shadow: 0 1px 2px rgba(28,28,26,.05), 0 12px 32px rgba(28,28,26,.14);
}
.menu-list .menu-item { padding: .45rem .7rem; }
.menu-list .menu-item b { font-size: .88rem; }
.menu-list .menu-item .small-muted { font-size: .76rem; }


/* The schedule's two decisions on one grid, labels level with each other. */
.sched-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem 1rem; align-items: start; }
.sched-grid .form-label { display: block; margin-bottom: .4rem; }
.sched-grid .seg { width: 100%; }
@media (max-width: 640px) { .sched-grid { grid-template-columns: 1fr; } }
/* A disabled primary is a quiet ink, never Bootstrap's blue. */
.btn-primary:disabled, .btn-primary.disabled { background: var(--web-text); border-color: var(--web-text); color: var(--web-black); opacity: .35; }


/* ---------- statistics: tiles and a calm chart ---------- */
.stat-strip-6 { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.stat-tile.dark { background: var(--web-text); }
.stat-tile.dark .stat-label, .stat-tile.dark .stat-note { color: var(--web-black); opacity: .72; }
.stat-tile.dark .stat-label i.bi { color: var(--web-orange); }
.stat-tile.dark .stat-value { color: var(--web-black); }
.stat-tile.good .stat-value { color: var(--web-accent-txt); }
.stat-tile.warn .stat-value { color: var(--web-red-txt); }
.bar-grid { stroke: rgba(28,28,26,.07); stroke-width: 1; }
.bar-base { stroke: rgba(28,28,26,.18); stroke-width: 1; }
.dayrow { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 2px; height: 22px; align-items: end; margin-top: .35rem; }
.daycell { display: block; height: max(2px, var(--v)); border-radius: 2px; background: var(--web-wash-2); }
.daycell:hover { background: var(--web-dim); }
.legend.items { background: var(--web-wash-2); }

/* What a model can do, as marks beside its name. */
.caps { display: inline-flex; gap: .3rem; margin-left: .45rem; vertical-align: middle; }
.caps .bi { font-size: .8em; color: var(--web-accent-txt); opacity: .85; }
.mlist-note .bi { color: var(--web-accent-txt); }

/* The bills' shape while they load. */
.spend-sk { pointer-events: none; }
.spend-sk .sk { vertical-align: middle; }
.usercard-nav a .bi { width: 1.1rem; color: var(--web-accent-txt); }
.usercard-nav a.is-here { background: var(--web-wash); font-weight: 700; }

/* Each bill in a bubble of its own inside the pill, with air between the
   label, the "at least" mark and the number. */
.spend { padding: .22rem .3rem; gap: .3rem; background: var(--web-well); }
.spend-item { padding: .22rem .65rem; border-radius: 999px; background: var(--web-panel); gap: .4rem; box-shadow: 0 1px 2px rgba(28,28,26,.06); }
.spend-ge { color: var(--web-dim); font-weight: 600; }
.spend:hover .spend-item { background: var(--web-panel); }
.spend-sk .spend-item { min-width: 5.5rem; }


/* ---------- the dashboard: one face, tabular figures ---------- */
.dash .stat-tile .stat-value {
  font-family: inherit; font-weight: 800; font-size: 1.5rem; letter-spacing: -.03em;
  font-variant-numeric: tabular-nums; line-height: 1.25;
}
.dash .stat-tile { padding: .95rem 1.15rem; }
.dash .stat-tile .stat-note { font-size: .74rem; color: var(--web-dim); margin-top: .15rem; }
.dash .stat-tile.dark .stat-note { color: var(--web-black); opacity: .72; }
a.stat-tile { text-decoration: none; color: inherit; transition: box-shadow var(--t-fast); }
a.stat-tile:hover { box-shadow: var(--shadow-lift); color: inherit; }
.dash .stat-strip-6 { grid-template-columns: repeat(6, 1fr); }
@media (max-width: 1000px) { .dash .stat-strip-6 { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 560px) { .dash .stat-strip-6 { grid-template-columns: repeat(2, 1fr); } }
.dash .card-header { font-size: .95rem; }
.dash-row { margin-bottom: .7rem; }
.dash-row:last-child { margin-bottom: 0; }
.dash-row-line { display: flex; justify-content: space-between; align-items: baseline; gap: .75rem; font-size: .86rem; margin-bottom: .25rem; }
.dash-row-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-row-label a { text-decoration: none; font-weight: 600; }
.dash-row-value { flex: none; font-weight: 700; font-variant-numeric: tabular-nums; }
.dash .meter { height: 6px; }
.dash .meter.good > span { background: var(--web-orange); }
.dash-sub { font-size: .68rem; text-transform: uppercase; letter-spacing: .08em; color: var(--web-dim); font-weight: 700; margin: .9rem 0 .35rem; }
.stack { display: flex; height: 8px; border-radius: 999px; overflow: hidden; background: var(--web-wash-2); gap: 1px; }
.stack > span { display: block; height: 100%; }
.stack-0 { background: var(--web-text); } .stack-1 { background: var(--web-orange); }
.stack-2 { background: var(--web-dim); } .stack-3 { background: var(--web-lime-soft); } .stack-4 { background: var(--web-faint); }
.stack-legend { display: flex; flex-wrap: wrap; gap: .3rem .8rem; margin-top: .4rem; font-size: .74rem; color: var(--web-dim); }
.stack-dot { display: inline-block; width: .55rem; height: .55rem; border-radius: 999px; margin-right: .3rem; vertical-align: -.02em; }
.dash-table th { font-size: .66rem; text-transform: uppercase; letter-spacing: .08em; color: var(--web-dim); font-weight: 700; white-space: nowrap; }
.dash-table td { font-size: .86rem; font-variant-numeric: tabular-nums; }
.dash .bars { height: auto; }
.dash .bar-lbl { font-size: 9px; fill: var(--web-faint); }


/* ---------- the full model list, at a size a person reads ---------- */
.mlist { font-size: .9rem; padding: .5rem .6rem .6rem; border-radius: 1.25rem; }
.mlist-head { font-size: .7rem; padding: .45rem .85rem .3rem; }
.mlist-note { font-size: .82rem; margin: .2rem .85rem .5rem; }
.mlist-fam { padding: .55rem .85rem .3rem; font-size: .95rem; }
.mlist-fam .pmark { width: 30px; height: 30px; }
.mlist-count { font-size: .76rem; }
.mlist-row { padding: .48rem .85rem; }
.mlist-name { display: inline-flex; align-items: center; flex-wrap: wrap; gap: .25rem .5rem; font-size: .92rem; }
.mlist-row > span:last-child { font-size: .84rem; color: var(--web-dim); }
.mlist-row.on > span:last-child { color: inherit; }
/* What a model can do, as chips: an icon and a word, never a bare glyph. */
.caps { display: inline-flex; flex-wrap: wrap; gap: .28rem; margin-left: .2rem; vertical-align: middle; }
.cap {
  display: inline-flex; align-items: center; gap: .28rem; padding: .12rem .5rem .12rem .4rem;
  border-radius: 999px; background: rgba(28,28,26,.07); color: var(--web-text);
  font-family: "Plus Jakarta Sans", system-ui, sans-serif; font-size: .7rem; font-weight: 700; letter-spacing: 0;
  line-height: 1.4; white-space: nowrap;
}
.cap .bi { font-size: .82em; color: var(--web-accent-txt); }
.mlist-row.on .cap { background: var(--web-black); color: var(--web-text); }
.mlist-row.on .cap .bi { color: var(--web-orange); }
.tier-note .cap { background: rgba(28,28,26,.1); }

/* The legend of marks, as the badges they appear as. */
.caps-legend { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; margin: 0 .85rem .6rem; font-size: .8rem; }
.caps-legend-lbl { font-size: .68rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--web-dim); margin-right: .2rem; }
.caps-legend .cap { font-size: .74rem; padding: .18rem .6rem .18rem .48rem; cursor: help; }
.cap { cursor: help; }

/* The run's story, one sentence a line. */
.plainlog { margin: 0; padding: .6rem 0 0 1.4rem; font-size: .86rem; line-height: 1.55; color: var(--web-text); }
.plainlog li { padding: .1rem 0; }
.plainlog li::marker { color: var(--web-faint); font-size: .8em; }

.jobcard-data-head { justify-content: space-between; }
.jobcard-cost .bi { color: var(--web-accent-txt); }


/* ---------- the scrapers page's filter row: buttons, any number pressed ---------- */
.filterrow { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin: 0 0 1.1rem; }
.fbtn {
  display: inline-flex; align-items: center; gap: .4rem; padding: .45rem .9rem; border-radius: 999px;
  border: 0; background: var(--web-panel); color: var(--web-text); font: inherit; font-size: .86rem; font-weight: 600;
  box-shadow: var(--shadow-card); cursor: pointer; transition: background var(--t-fast), color var(--t-fast);
}
.fbtn .bi { color: var(--web-accent-txt); }
.fbtn:hover { background: var(--web-wash); }
.fbtn.is-on { background: var(--web-text); color: var(--web-black); }
.fbtn.is-on .bi, .fbtn.is-on .fbtn-n { color: inherit; opacity: .8; }
.fbtn-n { font-size: .74rem; font-weight: 700; color: var(--web-dim); padding: .05rem .4rem; border-radius: 999px; background: var(--web-well); }
.fbtn.is-on .fbtn-n { background: rgba(28,28,26,.35); }
.fbtn-clear { background: transparent; box-shadow: none; text-decoration: underline; color: var(--web-dim); }
.fbtn-warn .bi { color: var(--web-red-txt); }
.fbtn-warn.is-on { background: var(--web-red-txt); }
.filterrow-divider { width: 1px; height: 1.6rem; background: var(--web-line); margin: 0 .35rem; }

/* The run card's shape before the server answers. */
.jobcard-sk .shot-stage.shot-empty { display: grid; place-items: center; min-height: 220px; }
.jobcard-sk .bprog-bar span { animation: sk-creep 1.6s ease-in-out infinite alternate; }
@keyframes sk-creep { from { width: 2%; } to { width: 9%; } }
@media (prefers-reduced-motion: reduce) { .jobcard-sk .bprog-bar span { animation: none; } }


.pill-stat.pill-more { background: transparent; box-shadow: none; color: var(--web-dim); font-weight: 600; }
.pill-stat.pill-more:hover { background: var(--web-wash); }
.pill-stat .lbl-mini { margin-left: .1rem; }


/* ---------- schedule plans: one press sets the lot ---------- */
.plangrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: .6rem; }
.plancard {
  display: flex; flex-direction: column; align-items: flex-start; gap: .3rem; text-align: left;
  padding: .85rem 1rem; border: 0; border-radius: 1.25rem; background: var(--web-panel);
  box-shadow: var(--shadow-card); font: inherit; color: var(--web-text); cursor: pointer;
  transition: box-shadow var(--t-fast), background var(--t-fast);
}
.plancard:hover { box-shadow: var(--shadow-lift); }
.plancard.is-on { background: var(--web-text); color: var(--web-black); }
.plancard-head { font-weight: 800; font-size: .95rem; display: inline-flex; align-items: center; gap: .4rem; }
.plancard-head .bi { color: var(--web-accent-txt); }
.plancard.is-on .plancard-head .bi { color: var(--web-orange); }
.plancard-blurb { font-size: .78rem; color: var(--web-dim); line-height: 1.4; }
.plancard.is-on .plancard-blurb, .plancard.is-on .small-muted { color: var(--web-black); opacity: .75; }
.plancard-price { font-size: .8rem; margin-top: .2rem; font-variant-numeric: tabular-nums; }
.plancard-custom { background: var(--web-well); box-shadow: none; }
.sched-grid { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }


/* ---------- the statistics bento: ring · area · month on ink ---------- */
.bento { display: grid; grid-template-columns: 1fr 2fr 1.2fr; gap: var(--gap); align-items: stretch; }
@media (max-width: 900px) { .bento { grid-template-columns: 1fr 1fr; } .bento-area { grid-column: 1 / -1; } }
@media (max-width: 560px) { .bento { grid-template-columns: 1fr; } }
.bento-big { font-size: 1.9rem; font-weight: 800; letter-spacing: -.04em; line-height: 1.1; margin: .35rem 0 .45rem; font-variant-numeric: tabular-nums; }
.bento-big-sub { font-size: .9rem; font-weight: 600; letter-spacing: 0; opacity: .7; margin-left: .3rem; }
.bento-ring .card-body { display: flex; flex-direction: column; align-items: center; justify-content: center; }
.lbl-pill { font-size: .74rem; padding: .35rem .8rem; }
.bento-dark { background: var(--web-text); color: var(--web-black); }
.bento-dark .stat-label { color: var(--web-black); opacity: .7; }
.bento-dark .stat-label .bi { color: var(--web-orange); }
.bento-line { font-size: .84rem; opacity: .8; }
.bento-kpis { display: grid; grid-template-columns: 1fr 1fr; gap: .35rem .8rem; margin-top: .8rem; font-size: .78rem; opacity: .85; }
.bento-kpis b { font-size: 1rem; font-weight: 800; margin-right: .25rem; font-variant-numeric: tabular-nums; }
.btn-lime { background: var(--web-orange); color: var(--web-on-accent); border: 0; font-weight: 700; }
.btn-lime:hover { background: var(--web-orange-lo); color: var(--web-on-accent); }
/* the ring */
.ring { width: 128px; height: 128px; }
.ring-track { fill: none; stroke: var(--web-wash-2); stroke-width: 10; }
.ring-fill { fill: none; stroke: var(--web-orange); stroke-width: 10; stroke-linecap: round; transition: stroke-dasharray .6s ease; }
.ring.warn .ring-fill { stroke: var(--web-red-txt); }
.ring-num { font-size: 22px; font-weight: 800; fill: var(--web-hi); letter-spacing: -.04em; }
.ring-sub { font-size: 9px; fill: var(--web-dim); text-transform: uppercase; letter-spacing: .08em; }
/* the area */
.area { position: relative; }
.area-svg { width: 100%; height: auto; display: block; overflow: visible; }
.area-line { fill: none; stroke-width: 2; stroke-linejoin: round; }
.area-line-aws { stroke: var(--web-orange-lo); }
.area-line-ai { stroke: var(--web-text); }
.area-pin { stroke: var(--web-text); stroke-width: 1; stroke-dasharray: 2 3; }
.area-dot { fill: var(--web-panel); stroke: var(--web-text); stroke-width: 2.5; }
.area-lbl { font-size: 9px; fill: var(--web-faint); font-family: inherit; }
.area-lbl.is-on { fill: var(--web-hi); font-weight: 700; }
.area-badge {
  position: absolute; top: -.35rem; transform: translateX(-50%); white-space: nowrap;
  background: var(--web-text); color: var(--web-black); font-size: .74rem; font-weight: 800;
  padding: .22rem .6rem; border-radius: 999px; pointer-events: none; font-variant-numeric: tabular-nums;
}
.area-badge-sub { font-weight: 500; opacity: .75; }
/* the latest list */
.latest { display: flex; flex-direction: column; }
.latest-row { display: flex; align-items: center; gap: .8rem; padding: .6rem 1.25rem; border-top: 1px solid var(--web-line-soft); text-decoration: none; color: inherit; }
.latest-row:first-child { border-top: 0; }
.latest-row:hover { background: var(--web-wash); color: inherit; }
.latest-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.latest-body b { font-size: .9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.latest-body .small-muted { font-size: .74rem; }
.latest-num { flex: none; font-size: .84rem; font-weight: 700; min-width: 4.5rem; text-align: right; }
.kchip { flex: none; font-size: .7rem; font-weight: 700; padding: .2rem .6rem; border-radius: 999px; background: var(--web-well); color: var(--web-dim); }
.kchip-build { background: var(--web-lime-soft); color: var(--web-on-accent); }
.kchip-scrape { background: var(--web-wash-2); color: var(--web-text); }
.kchip-failed { background: rgba(224,36,61,.1); color: var(--web-red-txt); }


/* ---------- home widgets: one glance, one link each ---------- */
.widgets { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--gap); margin-top: 1.25rem; }
@media (min-width: 900px) { .widgets { grid-template-columns: repeat(4, 1fr); } .widget-wide { grid-column: span 2; } }
.widget {
  display: block; padding: 1rem 1.15rem; border-radius: var(--r-card); background: var(--web-panel);
  box-shadow: var(--shadow-card); color: inherit; text-decoration: none; min-width: 0;
  transition: box-shadow var(--t-fast);
}
a.widget:hover { box-shadow: var(--shadow-lift); color: inherit; }
/* `color: inherit` on hover beat the dark tile's paper text — a.widget:hover
   outranks .widget-dark — so the month's figure vanished ink-on-ink under
   the pointer. The dark tile keeps its colour whatever the pointer does. */
a.widget-dark, a.widget-dark:hover { color: var(--web-black); text-decoration: none; }
.widget-label { font-size: .68rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--web-dim); display: flex; align-items: center; gap: .4rem; }
.widget-label .bi { color: var(--web-accent-txt); }
.widget-big { font-size: 1.6rem; font-weight: 800; letter-spacing: -.04em; line-height: 1.15; margin: .3rem 0 .15rem; font-variant-numeric: tabular-nums; }
.widget-big-sub { font-size: .82rem; font-weight: 600; letter-spacing: 0; color: var(--web-dim); }
.widget-sub { font-size: .78rem; color: var(--web-dim); }
.widget-bar { height: 6px; border-radius: 999px; background: var(--web-wash-2); overflow: hidden; margin: .45rem 0 .35rem; }
.widget-bar span { display: block; height: 100%; background: var(--web-orange); border-radius: 999px; }
.widget-warn .widget-bar span { background: var(--web-red-txt); }
.widget-dark { background: var(--web-text); color: var(--web-black); }
.widget-dark .widget-label, .widget-dark .widget-sub, .widget-dark .widget-big-sub { color: var(--web-black); opacity: .72; }
.widget-dark .widget-label .bi { color: var(--web-orange); }
.widget-dark .widget-bar { background: rgba(28,28,26,.35); }
.spark { width: 100%; height: 36px; margin-top: .4rem; display: block; }
.spark-fill { fill: var(--web-lime-soft); }
.spark-line { fill: none; stroke: var(--web-accent-txt); stroke-width: 1.6; }
.spark-dot { fill: var(--web-text); }
.widget-list { display: flex; flex-direction: column; margin-top: .45rem; }
.widget-row { display: flex; align-items: center; gap: .6rem; padding: .4rem 0; border-top: 1px solid var(--web-line-soft); text-decoration: none; color: inherit; min-width: 0; }
.widget-row:first-child { border-top: 0; }
.widget-row:hover { color: inherit; }
.widget-row-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.widget-row-body b { font-size: .84rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.widget-row-body .small-muted { font-size: .72rem; }

/* A window over the page: the activity log. */
.modal-card { width: min(60rem, 100%); max-height: 88vh; display: flex; flex-direction: column; }
.modal-body-scroll { overflow: auto; }
.modal-card .filterbar { border-radius: 0; box-shadow: none; margin: 0; }


/* ---------- the schedule as a checkout: choices left, the cart right ---------- */
.sched-layout { display: grid; grid-template-columns: 1fr 19rem; gap: var(--gap); align-items: start; }
@media (max-width: 900px) { .sched-layout { grid-template-columns: 1fr; } }
.cart { position: sticky; top: 5.2rem; }
.cart-line { display: flex; justify-content: space-between; align-items: baseline; gap: .75rem; font-size: .88rem; padding: .28rem 0; }
.cart-line > span { color: var(--web-dim); }
.cart-line b { text-align: right; font-variant-numeric: tabular-nums; }
.cart-total { font-size: 1.05rem; }
.cart-total b { font-size: 1.35rem; font-weight: 800; letter-spacing: -.03em; }
.cart-note { font-size: .74rem; color: var(--web-dim); margin: -.1rem 0 .3rem; }
.cart hr { margin: .6rem 0; border-color: var(--web-line-soft); opacity: 1; }

/* ---- The schedule page: tiles, the 24-hour timeline, a card per schedule,
   and the stepper that sets one up. Ink washes, lime only on what you press. */
.sch-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: .9rem; }
@media (max-width: 720px) { .sch-tiles { grid-template-columns: 1fr; } }
.sch-tiles .stat-tile { display: flex; flex-direction: column; }
.sch-tiles .widget-bar { margin: .45rem 0 .3rem; }
.sch-cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: .9rem; margin-top: .75rem; }
@media (max-width: 860px) { .sch-cards { grid-template-columns: 1fr; } }
.sch-card { margin: 0; }
.sch-card.is-paused { opacity: .72; }
.sch-card-head { display: flex; align-items: center; gap: .75rem; min-width: 0; }
.sch-card-title { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; line-height: 1.25; }
.sch-card-title b { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sch-card-title .small-muted { font-size: .8rem; }
.sch-card-lines { display: flex; flex-direction: column; gap: .3rem; margin-top: .85rem; padding-top: .75rem; border-top: 1px solid var(--web-wash-2); }
.sch-card-lines .cart-line { font-size: .88rem; }
.sch-card-lines .cart-line b { text-align: right; font-weight: 600; }
.sch-month { color: var(--web-accent-txt); }
.sch-card-actions { display: flex; align-items: center; gap: .4rem; margin-top: .9rem; flex-wrap: wrap; }
.fact-quiet { opacity: .7; }

.tl { position: relative; padding: 1.4rem 0 .5rem; }
.tl-axis { position: relative; height: 1.1rem; border-top: 1px solid var(--web-wash-2); margin: 0 .6rem; }
.tl-tick { position: absolute; top: -1px; width: 1px; height: 6px; background: var(--web-wash-2); }
.tl-tick.is-major { height: 10px; background: var(--web-muted-2); }
.tl-lbl { position: absolute; top: 12px; left: 0; transform: translateX(-50%); font-size: .68rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--web-muted); white-space: nowrap; }
.tl-tick:first-child .tl-lbl { transform: none; }
.tl-tick:last-child .tl-lbl { transform: translateX(-100%); }
.tl-lane { position: relative; height: 3.4rem; margin: .5rem .6rem 0; }
.tl-dot { position: absolute; transform: translateX(-50%); transition: transform var(--t-fast); cursor: default; }
.tl-dot:hover { transform: translateX(-50%) scale(1.18); z-index: 2; }
.tl-dot .srow-icon { box-shadow: var(--shadow-card); }
.tl-list { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .4rem; }
.tl-list .fact { display: inline-flex; align-items: center; gap: .4rem; }

.stepper-wide { width: min(50rem, 100%); }
.stepper-price { font-size: .82rem; color: var(--web-on-accent); font-weight: 600; }
.stepper-total { font-size: 1.05rem; padding-top: .4rem; border-top: 1px solid rgba(28,28,26,.14); }
.stepper .filterrow { margin-bottom: .6rem; }
.stepper .plangrid { margin: 0; }
.stepper .sched-grid { margin-top: .25rem; }
.stepper .form-label { font-size: .78rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--web-on-accent); }
.pickgrid-modal { max-height: 20rem; overflow: auto; padding-right: .25rem; }
.stepper .form-control, .stepper .form-select { background: var(--web-on-lime); border-color: transparent; }
.sched-grid .sched-wide { grid-column: 1 / -1; }
.sched-grid .sched-wide .seg { width: auto; display: inline-flex; }

/* ---- The pipeline on a scraper's page: a rail of stage nodes, the panel
   for the one pressed — note, frame, code. Ink washes; the pressed node is
   ink, not lime. */
.pipe-rail { display: flex; align-items: stretch; gap: 0; overflow-x: auto; padding: .1rem .1rem .6rem; }
.pipe-step { display: flex; align-items: center; flex: 1 1 0; min-width: 9.5rem; }
.pipe-node { flex: 1 1 auto; display: flex; flex-direction: column; align-items: stretch; gap: .35rem; text-align: left; border: 0;
             background: var(--web-wash); border-radius: 1.1rem; padding: .55rem; color: var(--web-text); transition: background var(--t-fast), box-shadow var(--t-fast); min-width: 0; }
.pipe-node:hover { background: var(--web-wash-2); }
.pipe-node.is-on { background: var(--web-text); color: var(--web-black); box-shadow: var(--shadow-card); }
.pipe-node.is-on .pipe-node-sub { color: inherit; opacity: .7; }
.pipe-thumb { display: grid; place-items: center; aspect-ratio: 16 / 10; border-radius: .75rem; overflow: hidden; background: var(--web-on-lime-hi); color: var(--web-muted); font-size: 1.4rem; }
.pipe-thumb img { width: 100%; height: 100%; object-fit: cover; object-position: top; display: block; }
.pipe-node.is-runtime .pipe-thumb { background: var(--web-wash-2); }
.pipe-node-title { font-weight: 700; font-size: .86rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pipe-node-sub { font-size: .72rem; color: var(--web-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pipe-arrow { flex: 0 0 auto; color: var(--web-muted); padding: 0 .3rem; font-size: 1rem; }
.pipe-panel { display: grid; grid-template-columns: 1.2fr 1fr; gap: 1rem 1.25rem; margin-top: .75rem; padding-top: 1rem; border-top: 1px solid var(--web-wash-2); }
.pipe-panel-code { grid-column: 1 / -1; }
@media (max-width: 760px) { .pipe-panel { grid-template-columns: 1fr; } .pipe-step { min-width: 8rem; } }
.pipe-h { font-size: 1.05rem; font-weight: 800; display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin: 0 0 .4rem; }
.pipe-where { font-size: .74rem; font-weight: 600; color: var(--web-muted); background: var(--web-wash); border-radius: 999px; padding: .15rem .6rem; }
.pipe-note { font-size: .95rem; line-height: 1.5; margin: 0 0 .7rem; }
.pipe-steps { margin: 0 0 .5rem; padding-left: 1.2rem; font-size: .88rem; color: var(--web-muted); }
.pipe-steps li { margin-bottom: .15rem; }
.pipe-shot { position: relative; display: block; width: 100%; border: 0; padding: 0; border-radius: 1rem; overflow: hidden; background: var(--web-wash); box-shadow: var(--shadow-card); }
.pipe-shot img { width: 100%; display: block; max-height: 18rem; object-fit: cover; object-position: top; }
.pipe-shot-cap { position: absolute; left: .6rem; bottom: .6rem; font-size: .74rem; font-weight: 600; background: var(--web-text); color: var(--web-black); border-radius: 999px; padding: .2rem .6rem; }
.pipe-shot-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .4rem; min-height: 9rem; color: var(--web-muted); font-size: .85rem; text-align: center; padding: 1rem; box-shadow: none; }
.pipe-shot-empty i { font-size: 1.6rem; }
.pipe-code-head { display: flex; justify-content: space-between; align-items: center; gap: .6rem; font-size: .82rem; font-weight: 700; margin-bottom: .4rem; }
.pipe-code { margin: 0; background: var(--web-text); color: var(--web-black); border-radius: 1rem; padding: .9rem 1rem; font-size: .78rem; line-height: 1.5; max-height: 26rem; overflow: auto; }
.pipe-line { display: block; white-space: pre; }
.pipe-ln { display: inline-block; width: 3ch; margin-right: 1.2ch; text-align: right; opacity: .45; user-select: none; }
.pipe-runtime { display: flex; gap: .6rem; align-items: flex-start; background: var(--web-wash); border-radius: 1rem; padding: .8rem 1rem; font-size: .88rem; color: var(--web-muted); }
.pipe-runtime i { font-size: 1.1rem; }
.pipe-zoom { max-width: min(96vw, 70rem); max-height: 90vh; border-radius: 1rem; box-shadow: var(--shadow-card); }
.pipe-panel > * { min-width: 0; }
.pipe-note, .pipe-h { overflow-wrap: anywhere; }
.pipe .facts .fact code { white-space: nowrap; }
.pipe-shot img { max-height: 14rem; }

.cart-price.is-stale { opacity: .55; filter: blur(.4px); transition: opacity var(--t-fast); }
.cart-repricing { color: var(--web-accent-txt); font-weight: 600; }

/* Billing on a team card: who pays, what is owed, the button that zeroes it. */
.billing { margin-top: .75rem; padding: .7rem .9rem; border-radius: 1rem; background: var(--web-wash); display: flex; flex-direction: column; gap: .45rem; }
.billing-row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; font-size: .88rem; }
.billing-k { font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--web-dim); min-width: 6rem; }
.spend-due .mono { color: var(--web-red-txt); font-weight: 700; }

/* The composer's extra listing pages, and the result row on a finished build. */
.composer-extra { margin-top: .5rem; }
.composer-x { flex: 0 0 auto; border-radius: 999px; }
.composer-add { display: inline-flex; align-items: center; gap: .4rem; margin-top: .5rem; border: 0; background: transparent; color: var(--web-on-accent); font-size: .85rem; font-weight: 600; padding: 0; }
.composer-add:hover { text-decoration: underline; }
.price-note { font-size: .82rem; color: var(--web-on-accent); margin: .6rem 0 .3rem; line-height: 1.45; }
.price-note b { font-weight: 700; }
.jobcard-result { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; font-size: .85rem; }

/* The month's bills under the name in the account menu: three small tiles,
   the way the stat tiles read elsewhere — a label, a figure — with what is
   owed in red only when there is anything owed. */
.topbar .usermenu { margin-left: auto; }
.usercard-spend { padding: .65rem .85rem .75rem; border-bottom: 1px solid var(--web-line-soft); }
.usercard-spend-head { display: flex; justify-content: space-between; align-items: baseline; font-size: .66rem; font-weight: 800; letter-spacing: .07em; text-transform: uppercase; color: var(--web-dim); margin-bottom: .45rem; }
.usercard-spend-head a { text-transform: none; letter-spacing: 0; font-weight: 700; font-size: .74rem; color: var(--web-accent-txt); text-decoration: none; display: inline-flex; align-items: center; }
.usercard-spend-head a:hover { text-decoration: underline; }
.usercard-tiles { display: grid; grid-template-columns: repeat(3, 1fr); gap: .4rem; }
.usercard-tile { display: flex; flex-direction: column; gap: .1rem; padding: .5rem .6rem; border-radius: .85rem; background: var(--web-wash); text-decoration: none; color: inherit; min-width: 0; }
.usercard-tile-k { font-size: .62rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--web-dim); display: inline-flex; align-items: center; gap: .25rem; white-space: nowrap; }
.usercard-tile-v { font-size: .95rem; font-weight: 700; color: var(--web-hi); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.usercard-tile.is-due { background: var(--web-red-soft, var(--web-wash-2)); }
.usercard-tile.is-due .usercard-tile-v, .usercard-tile.is-due .usercard-tile-k { color: var(--web-red-txt); }
.usercard-tile.is-due:hover { filter: brightness(.97); }
.usercard-tile.is-clear .usercard-tile-v { color: var(--web-dim); }

.stat-tile .widget-big-sub { font-family: inherit; font-weight: 600; letter-spacing: 0; margin-left: .3rem; }
.stat-tile .stat-value .mono { font-family: inherit; }

/* One editor for the scraper: a gutter and a textarea in the same dark box
   the stage code used to be read in — the stage's lines marked, both
   scrolled as one. Editable, downloadable, the same gate as any edit. */

/* A checkbox as a pill: off is a quiet outline, on is ink — the same
   language as every other pressed thing, and level with the inputs. */
.toggle-pill { display: inline-flex; cursor: pointer; margin: 0; width: 100%; }
.toggle-pill input { position: absolute; opacity: 0; width: 0; height: 0; }
.toggle-pill span { display: inline-flex; align-items: center; justify-content: center; gap: .35rem; width: 100%; padding: .38rem .8rem; border-radius: 999px; border: 1.5px solid var(--web-line); background: var(--web-panel); font-size: .84rem; font-weight: 700; color: var(--web-dim); transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast); white-space: nowrap; }
.toggle-pill:hover span { border-color: var(--web-text); color: var(--web-text); }
.toggle-pill input:checked + span { background: var(--web-text); border-color: var(--web-text); color: var(--web-black); }
.toggle-pill input:focus-visible + span { box-shadow: 0 0 0 3px var(--web-wash-2); }

.invite-made { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; padding: .6rem .8rem; border-radius: 1rem; background: var(--web-wash); font-size: .85rem; }
.invite-made .mono { overflow-wrap: anywhere; }

/* A card that fills the window — the data table asked for it. Above the bar,
   square corners, its own scroll; the page behind stops scrolling. */
.card.is-fullscreen { position: fixed; inset: 0; z-index: 1060; border-radius: 0; margin: 0; overflow: auto; box-shadow: none; }
.card.is-fullscreen > .card-header { position: sticky; top: 0; z-index: 2; background: var(--web-panel); }
body.has-fullscreen { overflow: hidden; }
.btn-icon { padding-left: .55rem; padding-right: .55rem; }
/* The entry animation's filled end state leaves an identity transform on
   main, and a transformed ancestor is what a fixed card positions against.
   While a card fills the window, main has no transform at all. */
body.has-fullscreen main.page-in { animation: none; transform: none; }

/* The schedule's route: a labelled block like the rows above it, not a
   label with a segmented control squeezed onto the same line. */
.sched-route { margin-top: 1rem; }
.sched-route .form-label { display: block; }
.sched-route .seg { display: inline-flex; }
/* The monthly budget, in a window of its own. */
.budget-modal { width: min(26rem, 100%); padding: 1.6rem; }
.budget-modal .sheet-when { margin-bottom: 1rem; }

/* The rebuild conversation on a failed build: what was asked, what each
   attempt found, what the builder suggests — as a thread, then the notes. */
.rb-thread { display: flex; flex-direction: column; gap: .5rem; }
.rb-msg { position: relative; padding: .7rem .9rem .7rem 1rem; border-radius: 1.1rem; background: var(--web-wash); font-size: .88rem; line-height: 1.5; }
.rb-msg.you { background: var(--web-text); color: var(--web-black); }
.rb-msg.you a { color: inherit; }
.rb-who { display: block; font-size: .66rem; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; opacity: .7; margin-bottom: .15rem; }
.rb-note { white-space: pre-wrap; opacity: .9; }
.rb-chips { display: flex; flex-wrap: wrap; gap: .4rem; }
.rb-ext { background: var(--web-wash); box-shadow: none; }

/* Between attempts a build is retrying, not failed: amber, never red. */
.status-retrying { background: rgba(232,179,58,.14); color: var(--web-warn-txt); border-color: rgba(232,179,58,.4); }
.status-retrying::before { animation: pulse 1.25s ease-in-out infinite; }
.bprog-retrying .bprog-bar span { background: var(--web-amber); }
.bprog-ladder li.is-retry { color: var(--web-warn-txt); font-weight: 700; }
.ag-retry { border-color: rgba(232,179,58,.5); background: rgba(232,179,58,.1); }
.ag-retry .ag-dot { color: var(--web-warn-txt); border-color: rgba(232,179,58,.55); background: rgba(232,179,58,.16); }
.ag-time-retry { color: var(--web-warn-txt); }
/* A built scraper the judge had notes on. */
.card.rough .card-header { color: var(--web-warn-txt); }

/* ---------- the scrapers page: an inbox of fixes over a list you search ---------- */
/* The tabs are the health verdicts; "Needs attention" goes red only when it
   holds something, so an all-clear page has no alarm colour on it at all. */
.scr-tabs { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; margin: 0 0 1rem; }
.scr-tab {
  display: inline-flex; align-items: center; gap: .5rem; border: 0; cursor: pointer;
  padding: .55rem 1rem; border-radius: 999px; font: inherit; font-size: .9rem; font-weight: 700;
  background: var(--web-panel); color: var(--web-text); box-shadow: var(--shadow-card);
}
.scr-tab:hover { box-shadow: var(--shadow-lift); }
.scr-tab.is-on { background: var(--web-text); color: var(--web-black); }
.scr-tab-n { font-size: .76rem; font-weight: 800; padding: .08rem .5rem; border-radius: 999px; background: var(--web-well); color: var(--web-dim); }
.scr-tab.is-on .scr-tab-n { background: var(--web-wash-2); color: inherit; }
.scr-tab.is-alarm .scr-tab-n { background: var(--web-red-txt); color: var(--web-black); }
.scr-live { display: inline-flex; align-items: center; gap: .4rem; margin-left: auto; font-size: .82rem; font-weight: 700; color: var(--web-busy-txt); }

.scr-tools { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin: 0 0 .75rem; }
.scr-search { position: relative; flex: 1 1 16rem; margin: 0; }
.scr-search > .bi { position: absolute; left: 1rem; top: 50%; transform: translateY(-50%); color: var(--web-faint); pointer-events: none; }
.scr-search .form-control { padding-left: 2.5rem; padding-right: 2.4rem; border-radius: 999px; background: var(--web-panel); border-color: transparent; box-shadow: var(--shadow-card); }
.scr-search .form-control:focus { box-shadow: var(--shadow-lift); border-color: var(--web-line); }
.scr-kbd { position: absolute; right: .9rem; top: 50%; transform: translateY(-50%); font: 600 .7rem/1 inherit; color: var(--web-dim); background: var(--web-well); border-radius: 6px; padding: .2rem .4rem; pointer-events: none; }
.scr-search .form-control:focus ~ .scr-kbd, .scr-search .form-control:not(:placeholder-shown) ~ .scr-kbd { display: none; }
.scr-select { width: auto; max-width: 14rem; border-radius: 999px; padding-right: 2.1rem; background-color: var(--web-panel); border-color: transparent; box-shadow: var(--shadow-card); }

.scr-context { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem 1.25rem; margin: 0 0 .6rem; padding: 0 1rem; font-size: .82rem; color: var(--web-dim); }
/* A pill you can see, not a 13px checkbox label: ticking every row is how
   a whole fleet gets run, and it was the hardest thing on the page to find. */
.scr-pickall { display: inline-flex; align-items: center; gap: .45rem; margin: 0; cursor: pointer;
  padding: .45rem .95rem; border-radius: var(--r-ctl); border: 0; background: var(--web-panel);
  box-shadow: var(--shadow-card); color: var(--web-text); font-weight: 700; font-size: .85rem;
  transition: background .15s ease, color .15s ease, transform .15s ease; }
.scr-pickall .bi { font-size: 1rem; }
.scr-pickall:hover { background: var(--web-wash-2); transform: translateY(-1px); }
.scr-pickall.is-on { background: var(--web-text); color: var(--web-black); }
.scr-pickall.is-on .bi { color: var(--web-orange); }
.scr-model { display: inline-flex; align-items: center; gap: .3rem; }
.scr-model a { font-weight: 700; color: var(--web-hi); }

.scr-building { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; margin: 0 0 1rem; }
.scr-building-lbl { display: inline-flex; align-items: center; gap: .4rem; font-size: .8rem; font-weight: 700; margin-right: .25rem; }
.fact-sub { color: var(--web-dim); font-weight: 500; }

.scr-section + .scr-section { margin-top: 1.5rem; }
.scr-section-head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; margin: 0 0 .5rem; padding: 0 1rem; font-size: .95rem; font-weight: 700; color: var(--web-hi); }
.scr-section-head .linkish { font-size: .8rem; font-weight: 600; color: var(--web-dim); }
.scr-notice { margin: 0 0 1rem; padding: .6rem 1rem; border-radius: 999px; background: var(--web-wash); font-size: .85rem; }
.scr-empty { margin: 1.5rem 0 2rem; padding: 2rem 1.35rem; border-radius: var(--r-card); background: var(--web-panel); box-shadow: var(--shadow-card); text-align: center; color: var(--web-dim); }
.scr-empty .bi { color: var(--web-ok-txt); margin-right: .3rem; }

/* Dense rows: one scraper is two short lines, not a tall card. */
.srow-list.dense { gap: .4rem; }
.srow-list.dense .srow { padding: .6rem 1rem; gap: .8rem; }
.srow-list.dense .srow-icon { width: 32px; height: 32px; border-radius: 9px; }
.srow-list.dense .facts { margin-top: .25rem; }
.srow-line { display: flex; align-items: baseline; gap: .5rem; min-width: 0; }
.srow-line .srow-name { flex: 0 1 auto; }
.srow-line .srow-url { flex: 1 1 0; min-width: 0; }
.srow-twin { flex: none; font-size: .7rem; font-weight: 600; color: var(--web-dim); background: var(--web-well); border-radius: 999px; padding: .05rem .45rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.srow-healthy, .srow-idle { }
.srow-attention .srow-tick:empty { width: 1rem; }
.srow.is-running { box-shadow: var(--shadow-lift); }
.srow-do .btn .status-badge { border: 0; padding: 0; background: none; }
/* A reason is a sentence: it wraps rather than running under the button. */
.srow-list.dense .fact-warn { white-space: normal; max-width: 100%; }
button.fact { border: 0; cursor: pointer; font-family: inherit; }
.fact-model { background: transparent; color: var(--web-dim); font-weight: 500; }
.fact-model:hover { background: var(--web-wash); color: var(--web-hi); }

.runbar .runbar-quiet { background: transparent; color: var(--web-black); opacity: .75; border: 0; }
.runbar .runbar-quiet:hover { opacity: 1; background: var(--web-wash-2); }

@media (max-width: 720px) {
  .scr-kbd { display: none; }
  .scr-live { margin-left: 0; }
  .srow-line { flex-wrap: wrap; }
  .srow-do .status-badge { display: none; }
}

/* ---------- collected items: cards you read, a panel for the whole item ---------- */
/* The selected view tab is ink, and a card header paints its icons in the
   accent-text ink too — so the active tab's icon vanished into its own pill.
   Icons in a tab take the tab's colour, whatever they sit in. */
.viewtab i.bi, .card-header .viewtab i.bi { color: inherit; margin-right: 0; }

.items-tools { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; }
.items-tools .scr-search { flex: 1 1 14rem; }
.items-tools .scr-search .form-control { box-shadow: none; background: var(--web-well); }
.items-count { font-size: .82rem; font-weight: 700; color: var(--web-dim); white-space: nowrap; margin-right: auto; }
.card.items-pane.is-cards { background: transparent; box-shadow: none; border-color: transparent; }
.card.items-pane.is-cards > .card-header { background: transparent; border: 0; padding: 0 0 1rem; }
.card.items-pane.is-cards .items-tools .scr-search .form-control { background: var(--web-panel); box-shadow: var(--shadow-card); }
.card.items-pane.is-cards > .card-footer { background: transparent; border: 0; padding: 1rem 0 0; }
.card.items-pane.is-cards.is-fullscreen { background: var(--web-black); padding: 1.25rem; }
.card.items-pane.is-cards.is-fullscreen > .card-header { background: var(--web-black); }

.icards { display: grid; grid-template-columns: repeat(auto-fill, minmax(15.5rem, 1fr)); gap: .9rem; }
.card:not(.is-cards) .icards { padding: 1rem; }
.item-card {
  display: flex; flex-direction: column; min-width: 0; cursor: pointer;
  background: var(--web-panel); border-radius: 1.35rem; overflow: hidden;
  box-shadow: var(--shadow-card); transition: box-shadow var(--t-fast), transform var(--t-fast);
}
.item-card:hover { box-shadow: var(--shadow-lift); transform: translateY(-1px); }
.item-card:focus-visible { outline: 2px solid var(--web-text); outline-offset: 2px; }
.item-thumb { position: relative; aspect-ratio: 16 / 10; background: var(--web-well); overflow: hidden; display: grid; place-items: center; }
.item-thumb > .bi { font-size: 1.6rem; color: var(--web-faint); opacity: .5; }
.item-thumb img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.item-body { padding: .9rem 1rem .4rem; flex: 1 1 auto; min-width: 0; }
.item-title {
  font-size: 1rem; font-weight: 750; line-height: 1.3; color: var(--web-hi); margin: 0 0 .35rem;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.item-blurb {
  font-size: .84rem; line-height: 1.45; color: var(--web-text-2); margin: 0 0 .55rem;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.item-tags { display: flex; flex-wrap: wrap; gap: .3rem; }
.item-tag { font-size: .72rem; font-weight: 600; padding: .15rem .55rem; border-radius: 999px; background: var(--web-well); color: var(--web-text); white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
.item-tag.strong { background: var(--web-text); color: var(--web-black); }
.item-tag-k { color: var(--web-dim); font-weight: 500; }
.item-foot { display: flex; align-items: center; justify-content: space-between; gap: .5rem; padding: .55rem .75rem .65rem 1rem; border-top: 1px solid var(--web-line-soft); min-height: 2.9rem; }
.item-meta { font-size: .74rem; color: var(--web-text-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.item-do { display: flex; gap: .25rem; flex: none; opacity: .55; transition: opacity var(--t-fast); }
.item-card:hover .item-do, .item-card:focus-within .item-do { opacity: 1; }
.item-do .btn { width: 30px; height: 30px; padding: 0; display: grid; place-items: center; }
.srow-icon.xs { width: 18px; height: 18px; border-radius: 5px; font-size: .6rem; }
.items-table tbody tr { cursor: pointer; }

.item-detail { width: min(46rem, 100%); }
.item-detail-meta { font-size: .8rem; font-weight: 600; min-width: 0; }
.item-detail-meta i.bi { margin-right: 0; }
.item-detail-img { width: 100%; max-height: 22rem; object-fit: cover; display: block; }
.item-detail-body { padding: 1.25rem 1.5rem 1.5rem; }
.item-detail-title { font-size: 1.5rem; font-weight: 800; letter-spacing: -.02em; color: var(--web-hi); margin: 0 0 .35rem; }
.item-detail-src { display: inline-flex; align-items: center; gap: .35rem; font-size: .82rem; margin-bottom: 1rem; }
.item-dl { margin: 0 0 1rem; display: grid; gap: .1rem; }
.item-dl > div { display: grid; grid-template-columns: 9rem 1fr; gap: 1rem; padding: .55rem 0; border-bottom: 1px solid var(--web-line-soft); }
.item-dl dt { font-size: .8rem; font-weight: 600; color: var(--web-dim); text-transform: capitalize; }
.item-dl dd { margin: 0; font-size: .9rem; color: var(--web-text); overflow-wrap: anywhere; white-space: pre-wrap; }
.item-child { margin-top: 1.25rem; }
.item-child h4 { font-size: .95rem; font-weight: 750; text-transform: capitalize; margin: 0 0 .5rem; }
.item-child .table { font-size: .84rem; }
@media (max-width: 640px) {
  .item-dl > div { grid-template-columns: 1fr; gap: .15rem; }
  .icards { grid-template-columns: 1fr; }
  .items-tools .btn-icon { display: none; }   /* the page IS the window on a phone */
  .items-tools > .d-flex { flex: 1 1 100%; }
  .items-count { order: 3; margin: 0; }
}

/* ---------- the data page's filters ---------- */
.site-strip {
  display: flex; gap: .35rem; overflow-x: auto; margin: 0 0 1rem; padding: .15rem .1rem .45rem;
  /* Scrolls sideways without drawing a scrollbar: the fade at the right
     edge already says there is more, and a grey bar under a row of chips
     read as a broken layout. Trackpads, wheels and touch still scroll it. */
  scrollbar-width: none; -ms-overflow-style: none; scroll-behavior: smooth;
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 2.5rem), transparent);
          mask-image: linear-gradient(90deg, #000 calc(100% - 2.5rem), transparent);
}
.site-strip::-webkit-scrollbar { display: none; }
.site-chip {
  flex: none; display: inline-flex; align-items: center; gap: .4rem; border: 0; cursor: pointer;
  padding: .35rem .75rem .35rem .45rem; border-radius: 999px; font: inherit; font-size: .82rem; font-weight: 600;
  background: var(--web-panel); color: var(--web-text); box-shadow: var(--shadow-card); white-space: nowrap;
}
.site-chip:first-child { padding-left: .8rem; }
.site-chip:hover { box-shadow: var(--shadow-lift); }
.site-chip.is-on { background: var(--web-text); color: var(--web-black); }
.site-chip-n { font-size: .72rem; font-weight: 700; color: var(--web-dim); }
.site-chip.is-on .site-chip-n { color: inherit; opacity: .7; }
.item-facts { font-size: .78rem; color: var(--web-dim); margin: 0 0 .5rem; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.item-facts > span:first-child { color: var(--web-text); font-weight: 600; }
.item-dot { color: var(--web-faint); }

/* ---------- the composer grows into the build questions ---------- */
/* Not a window over the page: the lime card itself changes shape. Its
   height eases to each step's (AutoHeight measures, CSS transitions), the
   address row and the questions cross-fade, and each step slides in from
   the side you are heading. */
.autoheight { transition: height .42s cubic-bezier(.22, .9, .24, 1); overflow: hidden; }
.composer-swap { animation: composer-swap .38s cubic-bezier(.22, .9, .24, 1) both; }
@keyframes composer-swap { from { opacity: 0; transform: translateY(8px) scale(.985); } to { opacity: 1; transform: none; } }
.stepper.stepper-inline { width: auto; animation: none; }
.stepper-pane.is-fwd { animation: pane-fwd .34s cubic-bezier(.22, .9, .24, 1) both; }
.stepper-pane.is-back { animation: pane-back .34s cubic-bezier(.22, .9, .24, 1) both; }
@keyframes pane-fwd { from { opacity: 0; transform: translateX(28px); } to { opacity: 1; transform: none; } }
@keyframes pane-back { from { opacity: 0; transform: translateX(-28px); } to { opacity: 1; transform: none; } }
.stepper-dots li { transition: background var(--t-fast), width .3s cubic-bezier(.22, .9, .24, 1); }
.stepper-dots li.is-now { width: 3rem; }
@media (prefers-reduced-motion: reduce) {
  .autoheight { transition: none; }
  .composer-swap, .stepper-pane.is-fwd, .stepper-pane.is-back { animation: none; }
}

/* ---------- the run log, as a page ---------- */
.runlog-filters { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .5rem 1rem; margin-bottom: .25rem; }
.runlog-filters .scr-tabs { margin-bottom: .6rem; }
.runlog-states { margin-bottom: .6rem; -webkit-mask-image: none; mask-image: none; }
.site-chip.is-alarm:not(.is-on) .site-chip-n { color: var(--web-red-txt); }
.card.runlog { overflow: hidden; }

/* The member's wallet in the avatar menu — an ink card, so the one number
   that decides whether they can build stands apart from the menu's rows. */
.wallet { position: relative; overflow: hidden; display: flex; flex-direction: column; gap: .35rem;
  padding: .8rem .95rem .75rem; border-radius: 1.2rem; background: var(--web-text); color: var(--web-well);
  text-decoration: none; transition: transform .18s ease, box-shadow .18s ease; }
.wallet::after { content: ""; position: absolute; right: -2.2rem; top: -2.4rem; width: 7rem; height: 7rem;
  border-radius: 50%; background: radial-gradient(circle, var(--web-orange) 0%, transparent 68%); opacity: .28;
  pointer-events: none; transition: opacity .25s ease, transform .35s ease; }
.wallet:hover { color: #fff; transform: translateY(-1px); box-shadow: 0 10px 24px -14px rgba(var(--web-tint-rgb), .7); }
.wallet:hover::after { opacity: .45; transform: scale(1.12); }
.wallet-top { display: flex; justify-content: space-between; align-items: center; font-size: .64rem; font-weight: 800;
  letter-spacing: .08em; text-transform: uppercase; color: color-mix(in srgb, var(--web-well) 62%, transparent); }
.wallet-top .bi-stars { color: var(--web-orange); }
.wallet-go { text-transform: none; letter-spacing: 0; font-size: .74rem; font-weight: 700; color: var(--web-well);
  display: inline-flex; align-items: center; }
.wallet-big { display: flex; align-items: baseline; gap: .4rem; font-size: 1.75rem; font-weight: 800;
  letter-spacing: -.02em; line-height: 1.05; font-variant-numeric: tabular-nums; }
.wallet-big small { font-size: .78rem; font-weight: 700; letter-spacing: 0; color: color-mix(in srgb, var(--web-well) 62%, transparent); }
.wallet-meter { display: block; height: .4rem; border-radius: 999px; background: color-mix(in srgb, var(--web-well) 14%, transparent); overflow: hidden; }
.wallet-meter > span { display: block; height: 100%; border-radius: inherit; background: var(--web-orange);
  animation: wallet-fill .7s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes wallet-fill { from { width: 0; } }
.wallet-foot { display: flex; justify-content: space-between; gap: .5rem; font-size: .72rem; font-weight: 600;
  color: color-mix(in srgb, var(--web-well) 70%, transparent); font-variant-numeric: tabular-nums; }
.wallet-pending { color: var(--web-orange); white-space: nowrap; }
.wallet.is-low .wallet-meter > span { background: var(--web-amber); }
.wallet.is-out { background: var(--web-red-txt); }
.wallet.is-out::after { background: radial-gradient(circle, #fff 0%, transparent 68%); opacity: .14; }
.wallet.is-out .wallet-big { font-size: 1.3rem; }
.wallet.is-out .wallet-meter > span { background: #fff; }
@media (prefers-reduced-motion: reduce) { .wallet, .wallet::after, .wallet-meter > span { transition: none; animation: none; } }

/* Send to a webhook, from the data page: the panel and the pushes it started. */
.menu-item-send { width: 100%; border: 0; background: none; text-align: left; display: flex; gap: .6rem;
  align-items: flex-start; border-top: 1px solid var(--web-line-soft); margin-top: .2rem; padding-top: .7rem; }
.push-panel { padding: 1.1rem 1.25rem; margin-bottom: 1rem; }
.push-panel-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; }
.push-panel-head b { font-size: 1.05rem; }
.push-k { font-size: .66rem; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; color: var(--web-dim);
  margin: 1rem 0 .45rem; }
.push-paces { display: grid; grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr)); gap: .5rem; }
.push-pace { display: flex; flex-direction: column; align-items: flex-start; gap: .1rem; padding: .6rem .85rem;
  border-radius: 1rem; border: 0; background: var(--web-wash); color: var(--web-text); text-align: left;
  transition: background .15s ease, transform .15s ease; }
.push-pace:hover { background: var(--web-wash-2); transform: translateY(-1px); }
.push-pace small { color: var(--web-dim); font-size: .74rem; }
.push-pace.is-on { background: var(--web-text); color: var(--web-black); }
.push-pace.is-on small { color: inherit; opacity: .7; }
.push-custom { display: flex; align-items: center; gap: .5rem; margin-top: .6rem; flex-wrap: wrap; font-size: .88rem; }
.push-custom .form-control { width: 5.5rem; }
.push-custom .form-select { width: auto; }
.push-foot { display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-wrap: wrap;
  margin-top: 1rem; padding-top: .9rem; border-top: 1px solid var(--web-line-soft); }
.pushes { display: flex; flex-direction: column; gap: .5rem; margin-bottom: 1rem; }
.push-row { display: flex; align-items: center; gap: .8rem; padding: .7rem 1rem; border-radius: 1.2rem;
  background: var(--web-panel); box-shadow: var(--shadow-card); }
.push-ico { width: 2rem; height: 2rem; flex: none; border-radius: 50%; display: grid; place-items: center;
  background: var(--web-wash); color: var(--web-text); }
.push-row.is-running .push-ico { background: var(--web-orange); color: var(--web-on-accent); animation: push-pulse 1.6s ease-in-out infinite; }
.push-row.is-failed .push-ico { color: var(--web-red-txt); }
@keyframes push-pulse { 50% { transform: scale(1.08); } }
.push-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: .25rem; }
.push-line { font-size: .88rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.push-meter { display: block; height: .3rem; border-radius: 999px; background: var(--web-wash-2); overflow: hidden; }
.push-meter > span { display: block; height: 100%; border-radius: inherit; background: var(--web-orange); transition: width .5s ease; }
.push-row.is-done .push-meter > span { background: var(--web-ok); }
.push-sub { font-size: .76rem; }
@media (prefers-reduced-motion: reduce) { .push-row.is-running .push-ico { animation: none; } }

/* ============================================================
   The landing page (/welcome) — the daylight system, laid out like
   usalo.tech: a centred headline arriving word by word, the thing working
   under it, then steps, delivery, uses, the extension, the way in.
   Everything that moves stands still under prefers-reduced-motion.
   ============================================================ */
.lp { max-width: 72rem; margin: 0 auto; padding: 0 clamp(1rem, 3vw, 2rem) 3rem; overflow-x: clip; }
.lp-bar {
  position: sticky; top: .75rem; z-index: 40; display: flex; align-items: center; gap: 1rem;
  margin: .75rem 0 0; padding: .55rem .6rem .55rem 1rem; border-radius: var(--r-ctl);
  background: var(--web-bar); -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  box-shadow: var(--shadow-card);
}
.lp-brand { display: inline-flex; align-items: center; gap: .55rem; font-weight: 800; font-size: 1.05rem;
  color: var(--web-hi); text-decoration: none; }
.lp-brand .mark { width: 30px; height: 30px; }
.lp-links { display: flex; gap: 1.4rem; margin: 0 auto; font-size: .9rem; }
.lp-links a { color: var(--web-dim); text-decoration: none; font-weight: 600; }
.lp-links a:hover { color: var(--web-hi); }
@media (max-width: 760px) { .lp-links { display: none; } .lp-bar .btn { margin-left: auto; } }

/* hero */
.lp-hero { display: grid; grid-template-columns: 1.15fr .85fr; gap: 2rem; align-items: center; padding: 4.5rem 0 3rem; }
@media (max-width: 900px) { .lp-hero { grid-template-columns: 1fr; padding-top: 3rem; } }
.lp-kicker { display: inline-flex; align-items: center; gap: .55rem; padding: .4rem .85rem; border-radius: var(--r-ctl);
  background: var(--web-panel); box-shadow: var(--shadow-card); font-size: .82rem; font-weight: 700; color: var(--web-text-2); }
.lp-live { width: .55rem; height: .55rem; border-radius: 50%; background: var(--web-ok); position: relative; }
.lp-live::after { content: ""; position: absolute; inset: -4px; border-radius: 50%; border: 2px solid var(--web-ok);
  opacity: 0; animation: lp-ping 2s ease-out infinite; }
@keyframes lp-ping { 0% { transform: scale(.4); opacity: .8; } 100% { transform: scale(1.6); opacity: 0; } }
.lp-title { font-size: clamp(2.8rem, 6.6vw, 5.2rem); line-height: .98; letter-spacing: -.045em; font-weight: 800;
  margin: 1.4rem 0 1.2rem; color: var(--web-hi); }
.lp-word { display: inline-block; margin-right: .22em; opacity: 0; transform: translateY(.45em); filter: blur(6px);
  animation: lp-word .7s cubic-bezier(.2, .8, .2, 1) forwards; animation-delay: calc(var(--i) * 90ms + 150ms); }
@keyframes lp-word { to { opacity: 1; transform: none; filter: none; } }
.lp-word-hi { position: relative; }
.lp-word-hi:last-child::after { content: ""; position: absolute; left: 0; right: .25em; bottom: .06em; height: .2em;
  background: var(--web-orange); z-index: -1; border-radius: 4px; transform: scaleX(0); transform-origin: left;
  animation: lp-underline .6s 1.2s cubic-bezier(.2, .8, .2, 1) forwards; }
@keyframes lp-underline { to { transform: scaleX(1); } }
.lp-sub { font-size: 1.15rem; line-height: 1.6; color: var(--web-text-2); max-width: 36rem; }
.lp-cta { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; margin-top: 1.6rem; }
.lp-more { margin-left: .4rem; font-weight: 700; color: var(--web-hi); text-decoration: none; }
.lp-more i { display: inline-block; animation: lp-bob 1.6s ease-in-out infinite; }
@keyframes lp-bob { 50% { transform: translateY(3px); } }

/* the web */
.lp-web { position: relative; aspect-ratio: 1; max-width: 26rem; width: 100%; justify-self: center; }
.lp-web > svg { width: 100%; height: 100%; overflow: visible; }
.lp-web::before { content: ""; position: absolute; inset: 12%; border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle, var(--web-panel) 0%, transparent 70%); opacity: .7; }
.lp-web { cursor: grab; touch-action: pan-y; }
.lp-web-threads path { fill: none; stroke-linejoin: round; stroke: var(--web-faint); stroke-opacity: .42; stroke-width: .75;
  stroke-dasharray: 1400; stroke-dashoffset: 1400; animation: lp-draw 1.8s ease forwards; }
@keyframes lp-draw { to { stroke-dashoffset: 0; } }
.lp-caught { opacity: 0; transform: translate(calc(var(--x) + 90px), calc(var(--y) - 120px));
  animation: lp-catch 7s cubic-bezier(.2, .8, .2, 1) infinite; }
.lp-web-threads .lp-spoke-line { stroke-opacity: .5; stroke-width: .85; }
.lp-hub { fill: var(--web-faint); }
.lp-dew { fill: var(--web-panel); stroke: var(--web-line-hi); stroke-width: .5; animation: lp-dew 4.2s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }
@keyframes lp-dew { 0%, 100% { opacity: .55; transform: scale(.8); } 50% { opacity: 1; transform: scale(1.15); } }
.lp-glow { pointer-events: none; }
.lp-chip { fill: var(--web-panel); stroke: var(--web-line); stroke-width: .8; filter: drop-shadow(0 2px 3px rgba(var(--web-tint-rgb), .12)); }
.lp-bug text, .lp-caught text { font: 700 9.5px/1 "JetBrains Mono", ui-monospace, monospace; fill: var(--web-hi); letter-spacing: .02em; }
.lp-catch-dot { fill: var(--web-orange); stroke: var(--web-hi); stroke-width: 1.3; }
.lp-spider { color: var(--web-hi); filter: drop-shadow(0 2px 2px rgba(var(--web-tint-rgb), .25)); }
.lp-legs { transform-box: fill-box; transform-origin: center; animation: lp-legs 1.8s ease-in-out infinite; }
@keyframes lp-legs { 50% { transform: scaleX(1.04) scaleY(.97); } }
/* Walking: the two sets of legs step against each other. */
.lp-legs.is-walking { animation: none; }
.lp-legs.is-walking .lp-leg-a, .lp-legs.is-walking .lp-leg-b { transform-box: fill-box; transform-origin: center; }
.lp-legs.is-walking .lp-leg-a { animation: lp-step .2s ease-in-out infinite alternate; }
.lp-legs.is-walking .lp-leg-b { animation: lp-step .2s ease-in-out infinite alternate-reverse; }
@keyframes lp-step { from { transform: rotate(-4deg); } to { transform: rotate(4deg); } }
@keyframes lp-catch {
  0% { opacity: 0; transform: translate(calc(var(--x) + 90px), calc(var(--y) - 120px)); }
  12% { opacity: 1; }
  24% { transform: translate(var(--x), var(--y)); }
  27% { transform: translate(var(--x), calc(var(--y) + 3px)); }
  30%, 80% { opacity: 1; transform: translate(var(--x), var(--y)); }
  92%, 100% { opacity: 0; transform: translate(var(--x), var(--y)); }
}

/* sections */
.lp-section { padding: 4.5rem 0 1rem; }
.lp-eyebrow { font-family: "JetBrains Mono", ui-monospace, monospace; font-size: .76rem; letter-spacing: .26em;
  text-transform: uppercase; color: var(--web-dim); margin-bottom: .8rem; }
.lp-h2 { font-size: clamp(2rem, 4.4vw, 3.2rem); line-height: 1.02; letter-spacing: -.04em; font-weight: 800;
  color: var(--web-hi); margin-bottom: 1rem; }
.lp-hl { background: linear-gradient(transparent 62%, var(--web-orange) 62%, var(--web-orange) 92%, transparent 92%);
  padding: 0 .08em; }
.lp-lead { font-size: 1.08rem; line-height: 1.65; color: var(--web-text-2); max-width: 40rem; }
[data-reveal] { opacity: 0; transform: translateY(28px); transition: opacity .8s ease, transform .8s cubic-bezier(.2, .8, .2, 1); }
[data-reveal].is-in { opacity: 1; transform: none; }

/* showcase */
.lp-show { margin-top: 1.8rem; }
.lp-tabs { display: inline-flex; gap: .3rem; padding: .3rem; border-radius: var(--r-ctl); background: var(--web-panel);
  box-shadow: var(--shadow-card); margin-bottom: 1rem; }
.lp-tab { border: 0; background: none; padding: .5rem 1rem; border-radius: var(--r-ctl); font-weight: 700;
  color: var(--web-dim); transition: background .2s ease, color .2s ease; }
.lp-tab.is-on { background: var(--web-text); color: var(--web-black); }
.lp-show-grid { display: grid; grid-template-columns: 1.25fr 1fr; gap: 1rem; }
@media (max-width: 860px) { .lp-show-grid { grid-template-columns: 1fr; } }
.lp-browser { background: var(--web-panel); border-radius: 1.4rem; box-shadow: var(--shadow-card); overflow: hidden; }
.lp-browser-bar { display: flex; align-items: center; gap: .8rem; padding: .7rem .9rem; border-bottom: 1px solid var(--web-line-soft); }
.lp-dots { display: inline-flex; gap: .3rem; }
.lp-dots i { width: .6rem; height: .6rem; border-radius: 50%; background: var(--web-well-2); }
.lp-url { flex: 1; padding: .3rem .8rem; border-radius: var(--r-ctl); background: var(--web-wash); font-size: .8rem;
  color: var(--web-dim); font-family: "JetBrains Mono", ui-monospace, monospace; }
.lp-page { position: relative; display: grid; grid-template-columns: 1fr 1fr; gap: .7rem; padding: 1rem; }
.lp-card { position: relative; display: flex; gap: .7rem; align-items: center; padding: .7rem; border-radius: 1rem;
  background: var(--web-panel-2); border: 2px solid transparent; transition: border-color .25s ease, transform .25s ease, background .25s ease; }
.lp-card.is-scan { border-color: var(--web-orange); transform: scale(1.025); background: var(--web-panel); box-shadow: var(--shadow-card); }
.lp-card.is-done { background: var(--web-panel); }
.lp-thumb { flex: none; width: 2.6rem; height: 2.6rem; border-radius: .7rem; display: grid; place-items: center;
  font-size: 1.35rem; line-height: 1; background: linear-gradient(135deg, var(--web-panel), var(--web-well-2));
  box-shadow: inset 0 0 0 1px var(--web-line-soft); transition: transform .3s ease; }
.lp-card.is-scan .lp-thumb { transform: scale(1.08) rotate(-4deg); background: var(--web-lime-soft); }
.lp-card-text { min-width: 0; display: flex; flex-direction: column; }
.lp-card-text b { font-size: .88rem; color: var(--web-hi); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lp-card-text small { color: var(--web-dim); font-size: .76rem; }
.lp-card-ok { position: absolute; right: .55rem; top: .45rem; color: var(--web-ok-txt); font-size: 1rem; animation: lp-pop .3s ease both; }
.lp-scan-tag { position: absolute; left: .6rem; top: -.75rem; padding: .12rem .5rem; border-radius: var(--r-ctl);
  background: var(--web-orange); color: var(--web-on-accent); font: 700 .66rem/1.4 "JetBrains Mono", ui-monospace, monospace;
  animation: lp-pop .25s ease both; white-space: nowrap; }
@keyframes lp-pop { from { transform: scale(.6); opacity: 0; } to { transform: none; opacity: 1; } }
.lp-reading { position: absolute; left: 0; right: 0; top: 0; height: 3px; background: var(--web-wash-2); }
.lp-reading::after { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: var(--p); background: var(--web-orange);
  transition: width .6s ease; }
.lp-page::after { content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg, transparent, rgba(var(--web-tint-rgb), .035) 50%, transparent);
  background-size: 100% 200%; animation: lp-sweep 2.8s linear infinite; }
@keyframes lp-sweep { from { background-position: 0 -100%; } to { background-position: 0 100%; } }
.lp-side { display: flex; flex-direction: column; gap: 1rem; }
.lp-ladder { list-style: none; margin: 0; padding: 1rem 1.1rem; border-radius: 1.4rem; background: var(--web-panel);
  box-shadow: var(--shadow-card); display: grid; gap: .45rem; }
.lp-ladder li { display: flex; align-items: center; gap: .65rem; font-size: .9rem; color: var(--web-faint); font-weight: 600;
  transition: color .3s ease; }
.lp-ladder-dot { width: 1.5rem; height: 1.5rem; border-radius: 50%; display: grid; place-items: center; font-size: .72rem;
  background: var(--web-wash-2); color: var(--web-dim); transition: background .3s ease, color .3s ease; }
.lp-ladder li.is-now { color: var(--web-hi); }
.lp-ladder li.is-now .lp-ladder-dot { background: var(--web-orange); color: var(--web-on-accent); animation: lp-throb 1s ease-in-out infinite; }
@keyframes lp-throb { 50% { transform: scale(1.12); } }
.lp-ladder li.is-done { color: var(--web-text); }
.lp-ladder li.is-done .lp-ladder-dot { background: var(--web-text); color: var(--web-black); }
.lp-rows { flex: 1; border-radius: 1.4rem; background: var(--web-text); color: var(--web-black); padding: 1rem 1.1rem; min-height: 11rem; }
.lp-rows-head { display: flex; justify-content: space-between; font-size: .8rem; font-weight: 700; opacity: .8; margin-bottom: .5rem; }
.lp-rows-head b { color: var(--web-orange); font-size: 1rem; }
.lp-json { margin: 0; font-size: .72rem; line-height: 1.55; white-space: pre-wrap; color: inherit; display: grid; gap: .3rem; }
.lp-json-row { display: block; animation: lp-row .45s cubic-bezier(.2, .8, .2, 1) both; }
@keyframes lp-row { from { opacity: 0; transform: translateX(-10px); } to { opacity: 1; transform: none; } }
.lp-json-wait { opacity: .55; }

/* steps */
.lp-steps { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-top: 1.8rem; }
@media (max-width: 900px) { .lp-steps { grid-template-columns: 1fr 1fr; } }
@media (max-width: 520px) { .lp-steps { grid-template-columns: 1fr; } }
.lp-step { position: relative; padding: 1.4rem; border-radius: 1.4rem; background: var(--web-panel); box-shadow: var(--shadow-card);
  opacity: 0; transform: translateY(18px); transition: opacity .6s ease var(--d), transform .6s cubic-bezier(.2, .8, .2, 1) var(--d); }
.is-in .lp-step { opacity: 1; transform: none; }
.lp-step:nth-child(1) { background: var(--web-orange); }
.lp-step-n { font: 700 .75rem/1 "JetBrains Mono", ui-monospace, monospace; color: var(--web-dim); }
.lp-step:nth-child(1) .lp-step-n { color: var(--web-on-accent); }
.lp-step-icon { display: block; font-size: 1.6rem; margin: 1.6rem 0 .8rem; color: var(--web-hi); }
.lp-step h3 { font-size: 1.15rem; font-weight: 800; letter-spacing: -.02em; color: var(--web-hi); }
.lp-step p { font-size: .9rem; color: var(--web-text-2); margin: .4rem 0 0; }
.lp-step:hover .lp-step-icon { animation: lp-wiggle .5s ease; }
@keyframes lp-wiggle { 25% { transform: rotate(-10deg); } 75% { transform: rotate(10deg); } }

/* delivery hub */
.lp-split { display: grid; grid-template-columns: 1fr 1fr; gap: 2.5rem; align-items: center; }
@media (max-width: 900px) { .lp-split { grid-template-columns: 1fr; } }
.lp-ticks { list-style: none; padding: 0; margin: 1.2rem 0 0; display: grid; gap: .5rem; }

/* Pricing: an estimator you drag, beside the three rates it is made of.
   White card + ink card — the lime is kept for the total, the one number
   that answers the question. */
.lp-price { display: grid; grid-template-columns: 1.35fr 1fr; gap: 1.25rem; margin-top: 1.75rem; }
@media (max-width: 900px) { .lp-price { grid-template-columns: 1fr; } }
.lp-calc { background: var(--web-panel); border-radius: 2.2rem; padding: 2rem 2rem 1.6rem; box-shadow: var(--shadow-card); display: flex; flex-direction: column; gap: 1.3rem; }
.lp-calc-kicker { margin: 0; font-weight: 700; color: var(--web-text-2); display: flex; gap: .5rem; align-items: center; }
.lp-calc-row { display: grid; grid-template-columns: 1fr auto; gap: .35rem 1rem; align-items: baseline; margin: 0; }
.lp-calc-q { font-weight: 600; color: var(--web-text); }
.lp-calc-v { font-weight: 800; font-size: 1.35rem; color: var(--web-text); }
.lp-calc-row input[type=range] { grid-column: 1 / -1; width: 100%; accent-color: var(--web-text); cursor: pointer; }
.lp-calc-total { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap;
  background: var(--web-orange); color: var(--web-on-accent); border-radius: 1.6rem; padding: 1.1rem 1.4rem; margin-top: .3rem; }
.lp-calc-sum { display: flex; flex-direction: column; line-height: 1; }
.lp-calc-sum small { font-weight: 700; font-size: .78rem; text-transform: uppercase; letter-spacing: .08em; opacity: .75; margin-bottom: .35rem; }
.lp-calc-sum b { font-size: clamp(2.4rem, 5vw, 3.2rem); letter-spacing: -.04em; }
.lp-calc-split { display: flex; flex-direction: column; gap: .2rem; font-size: .9rem; font-weight: 600; text-align: right; opacity: .85; }
.lp-rates { background: var(--web-text); color: var(--web-black); border-radius: 2.2rem; padding: 1.6rem 1.8rem; box-shadow: var(--shadow-card);
  display: flex; flex-direction: column; justify-content: center; gap: .2rem; }
.lp-rate { display: flex; align-items: center; gap: 1.1rem; padding: .85rem 0; border-bottom: 1px solid rgba(var(--web-accent-rgb), .18); }
.lp-rate b { font-size: 2.1rem; font-weight: 800; letter-spacing: -.05em; color: var(--web-orange); min-width: 6.2rem; line-height: 1; }
.lp-rate span { font-weight: 700; line-height: 1.25; }
.lp-rate small { font-weight: 500; opacity: .65; }
.lp-rates-note { margin: .9rem 0 0; font-size: .82rem; opacity: .65; }
.lp-ticks li { display: flex; gap: .55rem; align-items: center; font-weight: 600; color: var(--web-text); }
.lp-ticks i { width: 1.4rem; height: 1.4rem; border-radius: 50%; display: grid; place-items: center; background: var(--web-orange);
  color: var(--web-on-accent); font-size: .8rem; }
.lp-hub { position: relative; aspect-ratio: 1.05; width: 100%; max-width: 30rem; justify-self: center; }
.lp-hub > svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.lp-spoke { stroke: var(--web-faint); stroke-opacity: .55; stroke-width: 1.2; stroke-dasharray: 1.2 1.2; fill: none; vector-effect: non-scaling-stroke; }
.lp-pulse { fill: var(--web-hi); }
.lp-hub-core { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 9.5rem; padding: 1rem;
  border-radius: 1.6rem; background: var(--web-text); color: var(--web-black); text-align: center; display: grid; gap: .2rem;
  box-shadow: 0 18px 40px -18px rgba(var(--web-tint-rgb), .6); }
.lp-hub-core img { width: 2.4rem; height: 2.4rem; margin: 0 auto .3rem; border-radius: .7rem; animation: lp-throb 2.4s ease-in-out infinite; }
.lp-hub-core small { opacity: .65; font-size: .72rem; }
.lp-hub-node { position: absolute; transform: translate(-50%, -50%); white-space: nowrap; padding: .45rem .8rem;
  border-radius: var(--r-ctl); background: var(--web-panel); box-shadow: var(--shadow-card); font-size: .8rem; font-weight: 700;
  color: var(--web-hi); transition: transform .25s ease; }
.lp-hub-node:hover { transform: translate(-50%, -50%) scale(1.06); }
.lp-hub-node i { color: var(--web-dim); margin-right: .2rem; }

/* chat */
.lp-split-rev > :first-child { order: 0; }
.lp-chat { padding: 1.1rem; border-radius: 1.6rem; background: var(--web-panel); box-shadow: var(--shadow-card);
  display: grid; gap: .55rem; max-width: 27rem; justify-self: center; width: 100%; }
.lp-chat-head { display: flex; align-items: center; gap: .5rem; font-weight: 800; padding-bottom: .55rem; border-bottom: 1px solid var(--web-line-soft); }
.lp-chat-head img { width: 1.5rem; height: 1.5rem; border-radius: .45rem; }
.lp-chat-head span { margin-left: auto; font-size: .75rem; color: var(--web-dim); font-weight: 700; }
.lp-msg { margin: 0; padding: .65rem .85rem; border-radius: 1.1rem; font-size: .88rem; line-height: 1.45; max-width: 88%;
  opacity: 0; transform: translateY(10px); transition: opacity .45s ease, transform .45s cubic-bezier(.2, .8, .2, 1); }
.lp-chat.is-in .lp-msg { opacity: 1; transform: none; }
.lp-msg.is-you { justify-self: end; background: var(--web-text); color: var(--web-black); border-bottom-right-radius: .35rem; }
.lp-msg.is-ai { justify-self: start; background: var(--web-wash-2); color: var(--web-text); border-bottom-left-radius: .35rem; }

/* final */
.lp-final { padding: 4.5rem 0 2rem; }
.lp-final-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
@media (max-width: 760px) { .lp-final-grid { grid-template-columns: 1fr; } }
.lp-final-grid .lp-final-card { text-align: left; padding: 2.2rem; border-radius: 2rem; display: flex; flex-direction: column;
  min-height: 21rem; transition: transform .3s cubic-bezier(.2, .8, .2, 1); }
.lp-final-grid .lp-final-card:hover { transform: translateY(-4px); }
.lp-final-grid .lp-cta { justify-content: flex-start; margin-top: auto; padding-top: 1.4rem; }
.lp-final-icon { width: 3.2rem; height: 3.2rem; border-radius: 1.1rem; display: grid; place-items: center; font-size: 1.5rem;
  background: var(--web-on-lime); color: var(--web-hi); margin-bottom: 1.4rem; }
.lp-final-ink { background: var(--web-text); color: var(--web-black); box-shadow: var(--shadow-card); }
.lp-final-ink h2, .lp-final-ink p { color: var(--web-black) !important; }
.lp-final-ink .lp-final-icon { background: var(--web-orange); color: var(--web-on-accent); }
.lp-final-link { color: var(--web-on-accent); font-weight: 700; text-decoration: none; margin-left: .3rem; }
.lp-final-link:hover { text-decoration: underline; }
.lp-btn-lime { background: var(--web-orange); color: var(--web-on-accent); border: 0; font-weight: 700; }
.lp-btn-lime:hover { background: var(--web-orange-hi); color: var(--web-on-accent); }
.lp-final-card { text-align: center; padding: 3.5rem 1.5rem; border-radius: 2.2rem; }
.lp-final-card h2 { font-size: clamp(2rem, 3.6vw, 2.8rem); font-weight: 800; letter-spacing: -.045em; color: var(--web-on-accent); }
.lp-final-card p { color: var(--web-on-accent); opacity: .8; font-size: 1.1rem; }
.lp-final-card .lp-cta { justify-content: center; }
.lp-on-lime { background: var(--web-on-lime, var(--web-panel)); }
.lp-foot { display: flex; flex-wrap: wrap; gap: 1rem; justify-content: space-between; align-items: center; padding: 1.5rem 0;
  border-top: 1px solid var(--web-line); font-size: .9rem; }
.lp-foot span:first-child { display: inline-flex; align-items: center; gap: .45rem; font-weight: 800; }
.lp-foot img { width: 1.4rem; height: 1.4rem; border-radius: .4rem; }

@media (prefers-reduced-motion: reduce) {
  .lp-word, .lp-web-threads path, .lp-dew, .lp-legs, .lp-caught,
  .lp-word-hi:last-child::after { animation: none; opacity: 1; transform: none; filter: none; stroke-dashoffset: 0; }
    .lp-caught { transform: translate(var(--x), var(--y)); }
  [data-reveal], .lp-step, .lp-use, .lp-msg { opacity: 1; transform: none; transition: none; }
  .lp-page::after, .lp-live::after, .lp-more i, .lp-hub-core img { animation: none; }
}
/* On a phone the web sits under the text, a little smaller. */
@media (max-width: 900px) { .lp-web { max-width: 21rem; } }
.usercard-note { display: block; padding: .55rem .75rem; font-size: .76rem; color: var(--web-faint); }

/* Automations — what the extension's chat kept to do again. */
.auto-badge { display: inline-flex; align-items: center; gap: .3rem; padding: .25rem .6rem; border-radius: var(--r-ctl);
  background: var(--web-wash-2); font-size: .75rem; font-weight: 700; color: var(--web-text); white-space: nowrap; }
.auto-badge.is-script { background: var(--web-lime-soft); color: var(--web-hi); }
.auto-detail { padding: .2rem .2rem .4rem; font-size: .9rem; }
.auto-steps { margin: 0; padding-left: 1.3rem; display: grid; gap: .25rem; font-size: .82rem; }
.auto-steps code { color: var(--web-text); background: none; padding: 0; }
.auto-empty { display: flex; gap: 1rem; align-items: flex-start; }
.auto-empty > i { font-size: 1.6rem; color: var(--web-hi); }

/* The web's game: fields land, wait, get caught or get away. */
.lp-web-stage { display: grid; justify-items: center; gap: .6rem; }
.lp-bug .lp-bug-body { transform-box: fill-box; transform-origin: center; }
.lp-bug.is-in .lp-bug-body { animation: lp-land .45s cubic-bezier(.2, 1.4, .4, 1) both; }
@keyframes lp-land { from { transform: scale(.2); opacity: 0; } to { transform: none; opacity: 1; } }
.lp-bug.is-caught .lp-bug-body { animation: lp-gulp .45s ease-in forwards; }
@keyframes lp-gulp { to { transform: scale(0); opacity: 0; } }
.lp-bug.is-gone .lp-bug-body { animation: lp-flee .6s ease-in forwards; }
@keyframes lp-flee { to { transform: translateY(-14px); opacity: 0; } }
.lp-plus { font: 800 12px/1 "JetBrains Mono", ui-monospace, monospace; fill: var(--web-hi); text-anchor: middle;
  animation: lp-plus .9s ease-out forwards; }
@keyframes lp-plus { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(-16px); } }
.lp-score { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: .35rem .9rem; margin: 0;
  font-size: .82rem; font-weight: 600; color: var(--web-dim); }
.lp-score span { padding: .2rem .65rem; border-radius: var(--r-ctl); background: var(--web-panel); box-shadow: var(--shadow-card); }
.lp-score b { color: var(--web-hi); font-family: "JetBrains Mono", ui-monospace, monospace; }
.lp-web { cursor: crosshair; }
@media (hover: none) { .lp-web { cursor: auto; } }
.lp-final-grid .lp-final-card .lp-cta { justify-content: flex-start; }
.lp-show-grid { align-items: start; }

/* A sample row on a build's overview opens the item it came back as. */
.jobcard-item-open { width: 100%; text-align: left; background: none; border: 0; border-top: 1px solid var(--web-line-soft); color: inherit; font: inherit; cursor: pointer; border-radius: .5rem; }
.jobcard-item-open:first-of-type { border-top: 0; }
.jobcard-item-open:hover, .jobcard-item-open:focus-visible { background: var(--web-wash); }

/* The end of a build that worked: what to do next, large enough to see. */
.build-ready { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.build-ready-text { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.build-ready-text b { font-size: 1.15rem; }
.build-ready-go { padding: .6rem 1.4rem; font-size: 1rem; font-weight: 700; }

/* The export menu's language choice: one line above the formats. */
.menu-lang { display: flex; align-items: center; gap: .6rem; padding: .45rem .75rem .6rem; border-bottom: 1px solid var(--web-line-soft); margin-bottom: .25rem; font-size: .85rem; font-weight: 600; }
.menu-lang select { width: auto; flex: 1; }

/* "Something not right?" on a working scraper sits between the data and the runs. */
.fixit { margin-bottom: 1rem; }

/* Selecting collected items on the data page: a box on each card, a bar
   that says what will happen to the ticked ones. */
.item-card { position: relative; }
.item-pick { position: absolute; top: .6rem; left: .6rem; z-index: 2; background: var(--web-panel); border-radius: .4rem; padding: .15rem .25rem; box-shadow: var(--shadow-card); cursor: pointer; }
.item-pick .form-check-input { margin: 0; cursor: pointer; }
.item-card.is-picked { outline: 2px solid var(--web-text); outline-offset: -2px; }
.items-table tr.is-picked td { background: var(--web-wash); }
.pickbar { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; padding: .6rem 1rem; border-bottom: 1px solid var(--web-line-soft); }
.pickbar b { margin-right: .25rem; }

/* The owner of a scraper as a pill that is also its picker. */
.owner-pick select { border: 0; background: transparent; font: inherit; color: inherit; padding: 0 .2rem; cursor: pointer; }

/* Deliveries: what is going out now, as rows with a bar each. */
.deliv-row { padding: .55rem 0; border-top: 1px solid var(--web-line-soft); color: inherit; text-decoration: none; }
.deliv-row:first-child { border-top: 0; padding-top: 0; }
.deliv-row .bprog-bar { margin-top: .4rem; }

/* Scrapers: whose, asked first — chips above the tabs. */
.scr-whose { display: flex; align-items: center; flex-wrap: wrap; gap: .45rem; margin: 0 0 1rem; }
.scr-whose-q { font-weight: 700; font-size: .85rem; color: var(--web-dim); margin-right: .2rem; }
.scr-whose-n { margin-left: .35rem; font-size: .75rem; color: var(--web-faint); font-variant-numeric: tabular-nums; }
.chip-pick.on .scr-whose-n { color: inherit; opacity: .7; }

/* Select all: the head of the list, its box in line with the rows' boxes. */
.scr-listhead { display: flex; align-items: center; gap: 1rem; margin: 0; padding: .35rem 1.35rem; cursor: pointer;
  font-size: .85rem; color: var(--web-dim); }
.scr-listhead .form-check-input { margin: 0; width: 1.05rem; height: 1.05rem; cursor: pointer; }
.scr-listhead b { color: var(--web-text); }
.scr-listhead.is-some { color: var(--web-text); }

/* The selection card: in the margin to the right of the list on a wide
   screen, a floating card at the bottom right on a narrower one. */
.pick-cart { position: fixed; z-index: 30; width: 19rem; top: 7rem;
  left: calc(50% + var(--measure) / 2 + 1.5rem); }
.pick-cart .seg { flex-wrap: wrap; }
.pick-cart-q { font-size: .78rem; font-weight: 700; color: var(--web-dim); margin: .7rem 0 .35rem; }
@media (max-width: 1600px) {
  .pick-cart { top: auto; left: auto; right: 1.25rem; bottom: 1.25rem; }
}
.pick-cart .btn-sm { white-space: nowrap; }
