/* SERP Check Bot — shared styles, derived from design.html (locked spec). */

:root {
  --bg: #f7f7f8;
  --surface: #ffffff;
  --border: #e5e5ea;
  --border-hairline: #f0f0f2;
  --text-900: #14141b;
  --text-600: #6b6b76;
  --text-400: #9a9aa5;
  --accent: #4f46e5;
  --accent-hover: #4338ca;
  --accent-subtle: #eef0ff;
  --success-bg: #ecfdf3;
  --success-text: #15803d;
  --success-dot: #22c55e;
  --neutral-bg: #f1f1f3;
  --neutral-text: #6b6b76;
  --neutral-dot: #b0b0ba;
  --ad-tag-bg: #fef3c7;
  --ad-tag-text: #92400e;
  --organic-tag-bg: #f1f1f3;
  --organic-tag-text: #8b8b96;
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body {
  margin: 0; padding: 0; width: 100%; overflow-x: hidden;
  background: var(--bg); color: var(--text-900);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 13px;
}
ul, ol { list-style: none; margin: 0; padding: 0; }
h1, h2, h3, p { margin: 0; }
button { font-family: inherit; cursor: pointer; }
a { text-decoration: none; color: inherit; }
input, select { font-family: inherit; }

/* ---------- top nav ---------- */
.topbar {
  display: flex; align-items: center; gap: 28px;
  height: 56px; padding: 0 24px;
  background: var(--surface); border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 10;
}
.brand { display: flex; align-items: center; gap: 9px; font-size: 15px; font-weight: 700; flex-shrink: 0; }
.brand-mark {
  width: 24px; height: 24px; border-radius: 7px; background: var(--accent);
  color: #fff; font-size: 12px; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
}
.nav { display: flex; align-items: center; gap: 4px; }
.nav-item {
  font-size: 13px; font-weight: 600; color: var(--text-600);
  padding: 7px 12px; border-radius: 7px;
}
.nav-item:hover { background: #f4f4f6; color: var(--text-900); }
.nav-item.is-active { background: var(--accent-subtle); color: var(--accent); }
.topbar-spacer { flex: 1; }
.btn-export {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--accent); color: #fff; border: none; border-radius: 8px;
  padding: 8px 14px; font-size: 13px; font-weight: 600;
  flex-shrink: 0;
}
.btn-export:hover { background: var(--accent-hover); }

/* Wraps the topbar's single "Export report" button — was also home to a
   quieter secondary CSV link, dropped from the UI (the /api/export.csv
   endpoint itself is unchanged; there's just no button for it anymore). */
.export-group { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

/* Bulk "Check all" action — quieter than the accent-filled .btn-export
   (this isn't the primary topbar action) but still a real button, not a
   link: outlined by default, filled with the accent-subtle treatment while
   a run is in flight (mirrors .nav-item.is-active's bg/text pairing) so the
   in-progress state reads as active/informative rather than merely
   disabled. */
.btn-check-all {
  background: var(--surface); color: var(--text-600); border: 1px solid var(--border);
  border-radius: 8px; padding: 8px 14px; font-size: 13px; font-weight: 600;
  flex-shrink: 0;
}
.btn-check-all:hover { background: #f4f4f6; color: var(--text-900); }
.btn-check-all:disabled { cursor: default; }
.btn-check-all.is-running {
  background: var(--accent-subtle); color: var(--accent); border-color: var(--accent-subtle);
}

/* ---------- page shell ---------- */
.page { max-width: 1180px; margin: 0 auto; padding: 24px 24px 40px 24px; }

/* ---------- alert banner ---------- */
.alert-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  background: var(--accent-subtle); color: var(--accent);
  border-radius: 8px; padding: 10px 14px; margin-bottom: 14px;
  font-size: 12.5px; font-weight: 600;
}
.alert-banner-text { display: flex; align-items: center; gap: 8px; }
.alert-banner-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: 999px;
  background: var(--accent); color: #fff; font-size: 11px; font-weight: 800;
}
.alert-banner-close {
  background: none; border: none; color: var(--accent); font-size: 12px; font-weight: 700;
  padding: 4px 6px; border-radius: 6px;
}
.alert-banner-close:hover { background: rgba(79, 70, 229, 0.12); }

/* ---------- keyword tab strip ---------- */
.tab-strip {
  display: flex; gap: 2px; overflow-x: auto; overflow-y: hidden;
  border-bottom: 1px solid var(--border); margin-bottom: 20px;
  scrollbar-width: thin;
}
.tab-strip::-webkit-scrollbar { height: 4px; }
.tab-strip::-webkit-scrollbar-thumb { background: var(--border); border-radius: 999px; }
.tab-item {
  flex-shrink: 0; appearance: none; background: none; border: none;
  font-size: 12.5px; font-weight: 600; color: var(--text-600);
  padding: 9px 14px; margin-bottom: -1px; cursor: pointer;
  border-bottom: 2px solid transparent;
  max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tab-item:hover { color: var(--text-900); }
.tab-item.is-active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 700; }

/* ---------- tab strip drag-and-drop (keyword reordering) ----------
   Native HTML5 DnD, no library — see public/app.js's "keyword reordering"
   section. The dragged tab itself gets a lifted/ghost look; whichever
   neighboring tab the pointer is currently over shows a thin accent
   insertion line on the side the drop would land — same inset-box-shadow
   idiom `.kw-table tr.is-selected` already uses elsewhere on this page. */
.tab-item.is-dragging { opacity: 0.4; }
.tab-item.drop-before { box-shadow: inset 3px 0 0 var(--accent); }
.tab-item.drop-after { box-shadow: inset -3px 0 0 var(--accent); }

.tab-panel { display: none; }
.tab-panel.is-active { display: block; }

/* ---------- keyword focus view (single-keyword tab) ---------- */
/* Single row: the keyword title is now the dominant element on the left,
   the check picker sits inline right next to it, and "Check now" is pushed
   to the far right via margin-left: auto on the button (same pattern as
   `.topbar .btn-check-all`'s push-right below). Replaces the old two-row
   layout (title + status pill/last-check/frequency meta line, then a
   hairline-divided picker row) — that meta line was cut entirely (see
   public/app.js and DESIGN.md "Keyword focus view" for why), so there's
   nothing left to put on a second line. */
.kwf-header-row { display: flex; align-items: center; flex-wrap: wrap; gap: 14px 18px; }
#kwf-title {
  font-size: 27px; font-weight: 800;
  /* A long unbroken keyword term must never push the row past the viewport
     — the title may shrink, and the term itself may break. */
  min-width: 0; overflow-wrap: anywhere;
}
#kwf-check-now-btn { margin-left: auto; flex-shrink: 0; }

/* Compact date+time picker, replacing the old scrollable "Recent checks"
   list: pick a moment, see the check closest to it (max 3h away — see
   public/app.js's NEAREST_CHECK_WINDOW_MS). Sits inline in the header row
   next to the title — plain native inputs, shared field tokens with the
   Keywords page's add-keyword form (`.field input` below). */
.kwf-datetime-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.kwf-datetime-input {
  border: 1px solid var(--border); border-radius: 10px; padding: 9px 12px;
  font-size: 16px; color: var(--text-900); background: var(--surface);
}
.kwf-datetime-input:focus { outline: 2px solid var(--accent-subtle); border-color: var(--accent); }

/* ---------- worker board (8-worker check design) ----------
   Shown between the keyword-focus header and the SERP snapshot card — a
   grid of vertical "bot cards" (BOT 1..N, N = however many entries
   src/runCheck.js's WORKER_PROFILES has — 8 with the current roster,
   rendered as "BOT 1".."BOT 8"), one per worker. Serves two roles (see
   public/app.js's paintWorkerCards): LIVE and status-only while a check is
   in flight (polling GET /api/keywords/:id/check-progress), transitioning
   pending -> running -> done|error; SELECTABLE once a check's snapshot has
   loaded, each card showing its final status and doubling as the SERP
   snapshot card's worker picker (`.is-selectable`/`.is-selected` below) —
   the only worker selector in the app now, replacing what used to be a
   separate `#kwf-worker-select` dropdown. Hidden entirely (the whole card,
   via the `hidden` attribute) whenever there's nothing to show (no
   live/recently-finished run and no loaded check with `workers`) — never an
   empty card. 4 columns on desktop — with the current 8-worker roster this
   naturally wraps into 2 clean rows of 4 (CSS grid auto-wraps once a row's
   columns fill, no row count needs to be specified) — dropping to 3-per-row
   (980px, still fine: 3+3+2 for 8 cards) then 2-per-row (640px, 4 clean
   rows of 2) — see the matching overrides in the tablet/phone sections
   below. */
.workers-list { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
@media (max-width: 980px) {
  .workers-list { grid-template-columns: repeat(3, 1fr); }
}

.worker-card {
  display: flex; flex-direction: column; min-width: 0;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  overflow: hidden;
}
/* Header band visually separated from the body via a tinted bg + hairline
   border, rather than just a border — the mock's header/body split. */
.worker-card-head {
  display: flex; align-items: center; justify-content: center; gap: 5px;
  padding: 8px 6px;
  background: var(--neutral-bg); border-bottom: 1px solid var(--border);
  font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--text-900);
}
.worker-card-icon { font-size: 13px; line-height: 1; }
.worker-card-body {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 10px 8px 12px; text-align: center;
}
.worker-card-line {
  min-width: 0; max-width: 100%;
  font-size: 11.5px; font-weight: 600; color: var(--text-600);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.worker-card-line.worker-card-city { color: var(--text-400); font-weight: 500; }

/* The one outlined status box per card — an outline-only rectangle by
   default (pending/no-ad), tinted with the matching token pair
   (bg + border + text) for the running/done-ad/error states. */
.worker-status-box {
  margin-top: 6px; width: 100%;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  border: 1px solid var(--border); border-radius: 8px;
  padding: 6px 8px;
  font-size: 11px; font-weight: 700; white-space: nowrap;
  font-variant-numeric: tabular-nums; /* keeps a ticking elapsed readout from jittering box width */
}
.worker-status-box.is-pending { color: var(--text-400); }
.worker-status-box.is-running { color: var(--accent); border-color: var(--accent-subtle); background: var(--accent-subtle); }
.worker-status-box.is-done-ad { color: var(--success-text); border-color: var(--success-dot); background: var(--success-bg); }
.worker-status-box.is-done-noad { color: var(--neutral-text); background: var(--neutral-bg); }
.worker-status-box.is-error { color: #b91c1c; border-color: #b91c1c; background: #fef2f2; }

/* Per-worker SELECTION (5-worker design): once a check's snapshot has
   loaded, the SAME bot cards above double as the SERP snapshot card's
   worker picker — there is no separate dropdown anymore. `.is-selectable`
   is added to every card in that state (never during a live, in-flight
   render — see public/app.js's paintWorkerCards); `.is-selected` marks
   whichever one is currently driving the SERP snapshot/screenshot panels,
   mirroring the accent-border/tint idiom `.kw-table tr.is-selected` and
   `.nav-item.is-active` already use elsewhere on this page. */
.worker-card.is-selectable { cursor: pointer; }
.worker-card.is-selectable:hover { border-color: var(--accent); }
.worker-card.is-selectable:focus-visible { outline: 2px solid var(--accent-subtle); outline-offset: 2px; }
.worker-card.is-selected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.worker-card.is-selected .worker-card-head { background: var(--accent-subtle); color: var(--accent); }

/* ---------- main grid ---------- */
.grid { display: grid; grid-template-columns: 1.7fr 1fr; gap: 14px; align-items: start; }
@media (max-width: 980px) { .grid { grid-template-columns: 1fr; } }

.card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 16px 18px; margin-bottom: 14px; }
.card-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 12px; gap: 8px; }
.card-title { font-size: 14px; font-weight: 700; }
.card-title-sub { font-size: 11.5px; color: var(--text-400); font-weight: 500; margin-top: 2px; }

/* ---------- keyword table ---------- */
.kw-table { width: 100%; border-collapse: collapse; }
.kw-table th {
  text-align: left; font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-400); padding: 0 8px 8px 8px; border-bottom: 1px solid var(--border);
}
.kw-table td { padding: 10px 8px; border-bottom: 1px solid var(--border-hairline); vertical-align: middle; }
.kw-table tr:last-child td { border-bottom: none; }
.kw-table tbody tr { cursor: pointer; }
.kw-table tbody tr:hover td { background: #fafafc; }
.kw-table tr.is-selected td { background: var(--accent-subtle); }
.kw-table tr.is-selected { box-shadow: inset 3px 0 0 var(--accent); }
.kw-name { font-weight: 600; }

/* ---------- all-keywords table drag-and-drop (keyword reordering) ----------
   Same native HTML5 DnD as the tab strip above — see public/app.js's
   "keyword reordering" section. Only the small "⠿" grip is `draggable`
   (`.kw-drag-handle`), not the whole row, so a plain click anywhere else in
   the row still opens that keyword's tab; the dragged ROW gets the
   lifted/ghost look and the drop-target row shows a thin accent line on
   whichever edge (top/bottom) the drop would land. */
.kw-drag-col { width: 28px; }
.kw-drag-cell { width: 28px; padding-right: 0; }
.kw-drag-handle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 5px; cursor: grab;
  color: var(--text-400); font-size: 14px; line-height: 1;
}
.kw-drag-handle:hover { background: var(--neutral-bg); color: var(--text-600); }
.kw-drag-handle:active { cursor: grabbing; }
.kw-table tbody tr.is-dragging { opacity: 0.4; }
.kw-table tbody tr.drop-before td { box-shadow: inset 0 2px 0 var(--accent); }
.kw-table tbody tr.drop-after td { box-shadow: inset 0 -2px 0 var(--accent); }
/* Dashboard "Tracked keywords" table only (the Keywords page's manage table
   keeps the compact scale): bigger type + roomier rows. */
#kw-table-slot .kw-table th { font-size: 13px; padding: 0 8px 10px 8px; }
#kw-table-slot .kw-table td { font-size: 16px; padding: 14px 8px; }
#kw-table-slot .kw-name { font-size: 17px; }

.pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 700;
  white-space: nowrap; /* a status pill must read as one unit — never wrap in a squeezed table column */
}
.pill-dot { width: 6px; height: 6px; border-radius: 50%; }
.pill-shown { background: var(--success-bg); color: var(--success-text); }
.pill-shown .pill-dot { background: var(--success-dot); }
.pill-hidden { background: var(--neutral-bg); color: var(--neutral-text); }
.pill-hidden .pill-dot { background: var(--neutral-dot); }
/* A keyword's latest check errored — distinct from "Not shown" (a genuine
   reading) and "No checks yet" (never run at all). Same treatment as the
   export report's .pill-error (src/exportReport.js's EXPORT_CSS) and this
   page's own .btn-ghost.danger red, so error states read consistently
   everywhere in the app. */
.pill-error { background: #fef2f2; color: #b91c1c; }
.pill-error .pill-dot { background: #b91c1c; }

/* ---------- empty state ---------- */
.empty-state { padding: 32px 8px; text-align: center; }
.empty-state-title { font-size: 13px; font-weight: 600; color: var(--text-900); }
.empty-state-sub { font-size: 12px; color: var(--text-400); margin-top: 4px; }
.empty-state-link {
  display: inline-flex; margin-top: 14px; background: var(--accent); color: #fff;
  border-radius: 8px; padding: 8px 14px; font-size: 13px; font-weight: 600;
}
.empty-state-link:hover { background: var(--accent-hover); }

/* ---------- snapshot panel ---------- */
.result-row { display: flex; align-items: flex-start; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--border-hairline); }
.result-row:last-child { border-bottom: none; }
.result-pos {
  width: 20px; height: 20px; border-radius: 6px; background: var(--neutral-bg); color: var(--neutral-text);
  font-size: 10.5px; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.result-body { flex: 1; min-width: 0; }
.result-title { font-size: 12px; font-weight: 600; line-height: 1.4; }
.result-title-link { color: inherit; text-decoration: none; cursor: pointer; }
.result-title-link:hover { text-decoration: underline; }
.result-domain { font-size: 11px; color: #16a34a; margin-top: 2px; }
.tag { font-size: 9.5px; font-weight: 800; padding: 2px 6px; border-radius: 4px; letter-spacing: 0.03em; flex-shrink: 0; margin-top: 1px; }
.tag-ad { background: var(--ad-tag-bg); color: var(--ad-tag-text); }
.tag-organic { background: var(--organic-tag-bg); color: var(--organic-tag-text); }
.result-row.is-you { background: var(--accent-subtle); margin: 0 -18px; padding: 9px 18px; border-radius: 8px; }
.you-badge { font-size: 9.5px; font-weight: 800; background: var(--accent); color: #fff; padding: 2px 7px; border-radius: 4px; margin-left: 6px; }
.snapshot-empty { font-size: 12px; color: var(--text-400); padding: 12px 0; }

/* ---------- landing-page preview (ad rows only) ---------- */
.landing-preview-trigger {
  appearance: none; flex-shrink: 0; margin-top: 1px;
  background: var(--surface); border: 1px solid var(--border); color: var(--text-600);
  font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  padding: 3px 7px; border-radius: 4px; cursor: pointer;
}
.landing-preview-trigger:hover { background: var(--accent-subtle); color: var(--accent); border-color: var(--accent-subtle); }
@media (pointer: coarse) {
  /* Slightly larger tap target where there's no hover to fall back on. */
  .landing-preview-trigger { padding: 5px 9px; }
}
.landing-preview-card {
  display: none;
  position: fixed; z-index: 100; width: 320px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  padding: 10px; box-shadow: 0 8px 24px rgba(20, 20, 27, 0.14);
}
.landing-preview-card.is-visible { display: block; }
.landing-preview-domain { font-size: 12.5px; font-weight: 700; color: var(--text-900); margin-bottom: 8px; }
.landing-preview-imgwrap { display: block; border-radius: 7px; overflow: hidden; border: 1px solid var(--border-hairline); cursor: pointer; }
.landing-preview-img { display: block; width: 100%; height: auto; max-height: 220px; object-fit: cover; object-position: top; background: var(--neutral-bg); }
.landing-preview-caption { font-size: 10.5px; color: var(--text-400); margin-top: 6px; }

/* ---------- SERP screenshot ---------- */
.serp-screenshot-link { display: block; border-radius: 8px; overflow: hidden; border: 1px solid var(--border); }
.serp-screenshot-img { display: block; width: 100%; height: auto; max-height: 340px; object-fit: cover; object-position: top; }
.serp-screenshot-link:hover .serp-screenshot-img { opacity: 0.92; }
.serp-screenshot-caption { font-size: 10.5px; color: var(--text-400); margin-top: 6px; }

/* ---------- SERP screenshot: loading state (capture still in flight) ---------- */
.screenshot-loading-skeleton {
  height: 180px; border-radius: 8px; border: 1px solid var(--border);
  background: linear-gradient(100deg, var(--neutral-bg) 30%, var(--border-hairline) 50%, var(--neutral-bg) 70%);
  background-size: 200% 100%;
  animation: screenshot-skeleton-shimmer 1.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .screenshot-loading-skeleton { animation: none; }
  .screenshot-loading-spinner { animation: none; }
  .btn-spinner { animation: none; }
}
@keyframes screenshot-skeleton-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.screenshot-loading-label {
  display: flex; align-items: center; gap: 7px;
  font-size: 10.5px; color: var(--text-400); margin-top: 6px;
}
.screenshot-loading-spinner {
  width: 11px; height: 11px; border-radius: 50%; flex-shrink: 0;
  border: 2px solid var(--border); border-top-color: var(--accent);
  animation: screenshot-spinner-spin 0.8s linear infinite;
}
@keyframes screenshot-spinner-spin { to { transform: rotate(360deg); } }

/* ---------- "Check now" in-flight state (spinner + elapsed timer) ----------
   A single check can legitimately take 1-2 minutes (multi-sample: up to 10
   desktop + 5 mobile impressions), so a static "Checking…" label reads the
   same as a hang. Reuses the screenshot card's spinner visual (same size/
   border/animation as .screenshot-loading-spinner above) so the two loading
   affordances on the page look like one system, just recolored per use:
   `.btn-spinner` (default) is light-on-dark for the accent-filled
   keyword-focus "Check now" button; `.btn-spinner-dark` is the original
   accent-on-border coloring, reused as-is by the worker board's "running"
   status box (see "Worker board" in DESIGN.md). */
.btn-primary.is-checking {
  display: inline-flex; align-items: center; gap: 7px;
}
.btn-spinner {
  width: 11px; height: 11px; border-radius: 50%; flex-shrink: 0;
  border: 2px solid rgba(255, 255, 255, 0.4); border-top-color: #fff;
  animation: screenshot-spinner-spin 0.8s linear infinite;
}
.btn-spinner.btn-spinner-dark { border-color: var(--border); border-top-color: var(--accent); }
/* Tabular numerals keep the elapsed "m:ss" readout from jittering the
   button's width every second as digits change. */
.btn-check-label { font-variant-numeric: tabular-nums; }

/* ================================================================== */
/* Keywords management page (not in design.html; reuses the same tokens) */
/* ================================================================== */

.form-row { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-400); }
.field input, .field select {
  border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px;
  font-size: 13px; color: var(--text-900); background: var(--surface);
  min-height: 34px;
}
.field input:focus, .field select:focus { outline: 2px solid var(--accent-subtle); border-color: var(--accent); }
.field-term { flex: 1; min-width: 220px; }
.btn-primary {
  background: var(--accent); color: #fff; border: none; border-radius: 8px;
  padding: 9px 16px; font-size: 13px; font-weight: 600; height: 34px;
}
.btn-primary:hover { background: var(--accent-hover); }
.btn-primary:disabled { opacity: 0.5; cursor: default; }

.kw-manage-table td { padding: 10px 8px; }
.kw-actions { display: flex; gap: 6px; justify-content: flex-end; align-items: center; }
.btn-ghost {
  background: none; border: 1px solid var(--border); color: var(--text-600);
  border-radius: 7px; padding: 6px 10px; font-size: 12px; font-weight: 600;
}
.btn-ghost:hover { background: #f4f4f6; color: var(--text-900); }
.btn-ghost.danger:hover { background: #fef2f2; color: #b91c1c; border-color: #fca5a5; }

/* ---------- inline delete confirmation (replaces window.confirm()) ----------
   Clicking "Delete" arms this two-step row-local confirmation instead of a
   native dialog: the actions cell swaps to a short prompt + "Yes, delete" /
   "Cancel" pair, on the same .kw-actions/.btn-ghost tokens as the rest of the
   row. A stray second click can't delete anything by itself — "Yes, delete"
   only exists after the first click, in a different position than the
   original "Delete" button, and focus lands on "Cancel" by default (see
   public/keywords.js's showDeleteConfirm) so a stray Enter/click cancels
   rather than confirms. */
.confirm-delete-text { font-size: 12px; font-weight: 600; color: #b91c1c; white-space: nowrap; }
.status-tag {
  font-size: 11px; font-weight: 700; padding: 3px 8px; border-radius: 999px;
  white-space: nowrap; /* same one-unit rule as .pill */
}
.status-tag.active { background: var(--success-bg); color: var(--success-text); }
.status-tag.paused { background: var(--neutral-bg); color: var(--neutral-text); }

.toast {
  position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%);
  background: var(--text-900); color: #fff; font-size: 12.5px; font-weight: 600;
  padding: 10px 16px; border-radius: 8px; opacity: 0; pointer-events: none;
  transition: opacity 0.15s ease; z-index: 50;
}
.toast.is-visible { opacity: 1; }

/* ---------- topbar log out control ---------- */
.btn-logout {
  background: none; border: none; color: var(--text-400);
  font-size: 12px; font-weight: 600; padding: 7px 10px; border-radius: 7px;
  flex-shrink: 0;
}
.btn-logout:hover { background: #f4f4f6; color: var(--text-900); }

/* ---------- language switch (EN / TR) ---------- */
/* Two-letter toggle, deliberately as quiet as .btn-logout next to it — this
   is a preference, not a primary action. Present in the topbar of every
   page (plus a standalone variant on the login card, which has no topbar). */
.lang-switch {
  display: flex; align-items: center; gap: 2px; flex-shrink: 0;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 2px;
}
.lang-btn {
  appearance: none; background: none; border: none; color: var(--text-400);
  font-size: 11px; font-weight: 700; letter-spacing: 0.02em;
  padding: 5px 9px; border-radius: 6px;
}
.lang-btn:hover { color: var(--text-900); }
.lang-btn.is-active { background: var(--accent); color: #fff; }
.lang-switch-login { align-self: flex-end; margin-bottom: -10px; }

/* ================================================================== */
/* Ads Takibi page (not in design.html; reuses the same tokens)       */
/* ================================================================== */

/* ---------- summary strip ---------- */
.ads-summary-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.ads-stat { background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; text-align: center; }
.ads-stat-value { font-size: 22px; font-weight: 800; color: var(--text-900); font-variant-numeric: tabular-nums; }
.ads-stat-label { font-size: 11px; font-weight: 600; color: var(--text-400); text-transform: uppercase; letter-spacing: 0.03em; margin-top: 4px; }
/* Show-time row under the 4 tiles: same boxed treatment, one full-width
   line — label + bold total on the left, per-position breakdown pushed
   right (wraps under on narrow screens). */
.ads-showtime {
  grid-column: 1 / -1; background: var(--bg); border: 1px solid var(--border); border-radius: 10px;
  padding: 12px 16px; display: flex; align-items: baseline; gap: 6px 14px; flex-wrap: wrap;
}
.ads-showtime-label { font-size: 11px; font-weight: 600; color: var(--text-400); text-transform: uppercase; letter-spacing: 0.03em; }
.ads-showtime-total { font-size: 16px; font-weight: 800; color: var(--text-900); font-variant-numeric: tabular-nums; }
.ads-showtime-breakdown { font-size: 12px; color: var(--text-400); margin-left: auto; font-variant-numeric: tabular-nums; }

/* ---------- day-grouped accordion ----------
   Replaces the old flat, newest-first occurrence list: one collapsible
   section per calendar day with >=1 occurrence, collapsed by default —
   multiple days can be open at once (see public/ads.js's renderDayList).
   Deliberately reuses several existing tokens/classes rather than inventing
   new ones: `.result-title-link`/`.result-domain`/`.you-badge` (snapshot
   panel, see above) and `.landing-preview-trigger` (also doubling here as
   the plain "Screenshot" link's look — both are small bordered utility
   buttons, no visual reason for a second style). */
.ads-day-list { display: flex; flex-direction: column; }
.ads-day-item { border-bottom: 1px solid var(--border-hairline); }
.ads-day-item:last-child { border-bottom: none; }
.ads-day-header {
  width: 100%; display: flex; align-items: center; gap: 10px;
  background: none; border: none; padding: 12px 2px; text-align: left; cursor: pointer;
  border-radius: 6px;
}
.ads-day-header:hover { background: #fafafc; }
.ads-day-date {
  font-size: 13px; font-weight: 700; color: var(--text-900); flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ads-day-badge {
  font-size: 11px; font-weight: 700; color: var(--neutral-text); background: var(--neutral-bg);
  padding: 3px 9px; border-radius: 999px; flex-shrink: 0; white-space: nowrap;
}
.ads-day-chevron { font-size: 14px; color: var(--text-400); flex-shrink: 0; transition: transform 0.15s ease; }
.ads-day-item.is-open .ads-day-chevron { transform: rotate(90deg); color: var(--accent); }

/* Smooth expand/collapse with no JS height measuring: a 0fr -> 1fr grid
   track transition on the wrapper, with the inner content clipped by
   `overflow: hidden` — a well-supported modern CSS-only accordion. */
.ads-day-body-wrap { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.2s ease; }
.ads-day-item.is-open .ads-day-body-wrap { grid-template-rows: 1fr; }
.ads-day-body { overflow: hidden; min-height: 0; }
.ads-day-body-inner { padding: 2px 2px 14px; display: flex; flex-direction: column; gap: 12px; }
/* #1-spot leaderboard at the top of an expanded day: rank / domain / bar /
   count per row, bar widths relative to that day's leader, the searched
   ad's own row picked out with the accent treatment. */
.ads-topspot { border: 1px solid var(--border-hairline); border-radius: 8px; background: var(--bg); }
.ads-topspot-toggle {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 14px; background: none; border: none; cursor: pointer; text-align: left;
  font-size: 11px; font-weight: 600; color: var(--text-400); text-transform: uppercase; letter-spacing: 0.03em;
}
.ads-topspot-toggle:hover { color: var(--text-900); }
.ads-topspot-chevron { font-size: 14px; color: var(--text-400); flex-shrink: 0; transition: transform 0.15s ease; }
.ads-topspot.is-open .ads-topspot-chevron { transform: rotate(90deg); color: var(--accent); }
.ads-topspot-list { display: none; padding: 0 14px 12px; }
.ads-topspot.is-open .ads-topspot-list { display: block; }
.ads-topspot-row { display: flex; align-items: center; gap: 10px; padding: 3px 0; font-size: 13px; }
.ads-topspot-rank { flex: 0 0 16px; text-align: right; font-weight: 700; color: var(--text-400); font-variant-numeric: tabular-nums; }
.ads-topspot-domain { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-900); }
.ads-topspot-tag { margin-left: 6px; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--accent); background: var(--accent-subtle); border-radius: 4px; padding: 1px 5px; }
.ads-topspot-hours { flex: 0 0 auto; font-size: 11px; color: var(--text-400); white-space: nowrap; font-variant-numeric: tabular-nums; }
.ads-topspot-bar-wrap { flex: 1 1 60px; height: 6px; border-radius: 3px; background: var(--border-hairline); overflow: hidden; }
.ads-topspot-bar { display: block; height: 100%; border-radius: 3px; background: var(--text-400); }
.ads-topspot-row.is-searched .ads-topspot-bar { background: var(--accent); }
.ads-topspot-row.is-searched .ads-topspot-domain { font-weight: 700; }
.ads-topspot-count { flex: 0 0 auto; font-weight: 600; color: var(--text-600); font-variant-numeric: tabular-nums; font-size: 12px; }
.ads-day-loading, .ads-day-error { font-size: 12px; color: var(--text-400); padding: 10px 2px; }

/* One visual sub-block per check (impression moment) inside an expanded day. */
.ads-check-block { border: 1px solid var(--border-hairline); border-radius: 8px; padding: 12px 14px; background: var(--bg); }
.ads-check-top { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; flex-wrap: wrap; }
.ads-check-time { font-size: 15px; font-weight: 800; color: var(--text-900); font-variant-numeric: tabular-nums; }
.ads-check-kw-pill {
  font-size: 10.5px; font-weight: 700; color: var(--accent); background: var(--accent-subtle);
  padding: 3px 8px; border-radius: 999px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
.ads-check-title-row { display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px; font-size: 12.5px; font-weight: 600; }
.ads-check-media { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }

.ads-bot-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
/* Shared by both the interactive <button> (a row with a bot — see
   public/ads.js's buildBotChip) and the plain, inert <span> (a legacy row
   with no worker_id at all — src/schema.js's serpResults.workerId) — same
   look either way; `appearance: none` strips the UA's own button chrome so
   the two render identically. */
.ads-bot-chip {
  appearance: none; display: inline-flex; align-items: center; gap: 5px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 999px;
  padding: 3px 9px 3px 3px; font-size: 11px; font-weight: 600; color: var(--text-600);
}
.ads-bot-chip-pos {
  display: inline-flex; align-items: center; justify-content: center;
  width: 17px; height: 17px; border-radius: 50%; background: var(--neutral-bg); color: var(--neutral-text);
  font-size: 10px; font-weight: 800; flex-shrink: 0;
}
/* Selected bot ("rotate through bots" — public/ads.js's selectWorker):
   solid accent fill, same active treatment `.lang-btn.is-active` uses. */
button.ads-bot-chip.is-selected { background: var(--accent); border-color: var(--accent); color: #fff; }
button.ads-bot-chip.is-selected .ads-bot-chip-pos { background: rgba(255, 255, 255, 0.28); color: #fff; }
button.ads-bot-chip:focus-visible { outline: 2px solid var(--accent-subtle); outline-offset: 2px; }

/* Two thumb cards side by side in a check block's media row: the landing
   page preview (same for every bot — it's per-URL) and the SERP screenshot
   (follows the selected bot chip — see renderSerpThumbInto), each captioned
   so they read as visually distinct things rather than two of the same. */
.ads-thumb-card { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.ads-thumb-link { display: block; border-radius: 6px; overflow: hidden; border: 1px solid var(--border-hairline); flex-shrink: 0; }
.ads-thumb { display: block; width: 64px; height: 64px; object-fit: cover; object-position: top; background: var(--neutral-bg); }
.ads-thumb-caption { font-size: 9.5px; color: var(--text-400); text-align: center; }
.ads-thumb-note { font-size: 10.5px; color: var(--text-400); max-width: 140px; }

/* ================================================================== */
/* Login page (not in design.html; reuses the same tokens)            */
/* ================================================================== */

.login-body { display: flex; align-items: center; justify-content: center; min-height: 100vh; }
.login-wrap { width: 100%; max-width: 360px; padding: 24px; }
.login-card {
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  padding: 28px 24px; display: flex; flex-direction: column; gap: 22px;
}
.login-brand { justify-content: center; font-size: 16px; }
.login-form { display: flex; flex-direction: column; gap: 14px; }
.login-submit { width: 100%; }
.login-error {
  font-size: 12px; font-weight: 600; color: #b91c1c;
  min-height: 15px; text-align: center;
}

/* ================================================================== */
/* Responsive layout (phones & narrow viewports)                       */
/* The v1 spec (DESIGN.md) is desktop-locked at 1180px; everything     */
/* below adapts that layout downward without changing any tokens.      */
/* ================================================================== */

/* Tables can outgrow a phone-width card (the manage table's 6 columns
   never fit 390px) — scroll the table inside its own slot instead of
   crushing columns or clipping them against html/body's overflow-x:hidden
   (which leaves them unreachable — there's nothing to scroll). The
   min-widths keep every column readable; at desktop widths both tables
   are wider than these anyway, so nothing changes there. */
/* A grid track is minmax(auto, 1fr) by default, so a wide table would
   blow the whole column/card out past the viewport instead of scrolling
   inside its slot — min-width: 0 on the grid's direct children is what
   lets the slot's overflow-x actually engage. */
.grid > * { min-width: 0; }
#kw-table-slot, #kw-list-slot { overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: thin; }
#kw-table-slot .kw-table { min-width: 520px; }
.kw-manage-table { min-width: 760px; }

/* Never let the fixed-width preview card or the toast outgrow a small
   viewport (the JS clamps the card's position, not its size). */
.landing-preview-card { max-width: calc(100vw - 16px); }
.toast { max-width: calc(100vw - 32px); bottom: calc(20px + env(safe-area-inset-bottom, 0px)); }

@media (pointer: coarse) {
  /* Same rationale as .landing-preview-trigger's coarse-pointer bump
     above: bigger tap targets where there's no hover to fall back on. */
  .lang-btn { padding: 7px 11px; }
  .btn-logout { padding: 9px 12px; }
}

/* ---------- topbar: three tiers ----------
   Fewer topbar items after the minimalism pass (no sync meta, no CSV link)
   than when these thresholds were first tuned — kept as conservative
   fallbacks (they now trigger a little earlier than strictly necessary,
   never later) rather than re-measuring exact pixel cutoffs. */

/* Tier 1 — just tighten the gaps. */
@media (max-width: 1180px) {
  .topbar { gap: 14px; }
}

/* Tier 2 — two deliberate rows: brand + nav, then the action cluster,
   right-aligned. The spacer's 100% flex-basis is what forces the row
   break, so both languages get the same layout instead of wrapping at a
   string-length-dependent point. */
@media (max-width: 1080px) {
  .topbar { flex-wrap: wrap; height: auto; min-height: 56px; padding-top: 8px; padding-bottom: 8px; gap: 8px 14px; }
  .topbar-spacer { flex: 1 1 100%; height: 0; }
  /* .btn-check-all is the first element after the spacer in this wrapped
     row (see markup) — margin-left: auto on it pushes the whole action
     cluster after it to the right edge together. */
  .topbar .btn-check-all { margin-left: auto; }
}

/* Tier 3 — phone: stack into ordered rows (identity/session, navigation,
   actions). Scoped to .topbar so the login card's standalone
   .lang-switch-login keeps its own placement. */
@media (max-width: 720px) {
  /* The stacked bar is several rows tall — let it scroll away rather than
     pin a third of a phone screen. */
  .topbar { position: static; padding: 10px 16px 12px; }
  .topbar .brand { order: 1; }
  .topbar .lang-switch { order: 2; margin-left: auto; }
  .topbar .btn-logout { order: 3; }
  .topbar .nav { order: 4; width: 100%; }
  .topbar .btn-check-all { order: 5; flex: 1 1 auto; }
  .topbar .export-group { order: 6; flex: 1 1 auto; }
  .topbar .topbar-spacer { display: none; }
  .topbar .btn-export { flex: 1 1 auto; justify-content: center; }

  .page { padding: 16px 16px 32px 16px; }
}

/* ---------- phone-width content ---------- */
@media (max-width: 640px) {
  /* Focus header: title first (full width), then the check picker, then a
     full-width Check now button — each its own line once the row wraps
     (matches DOM order, so no `order` overrides needed); the datetime
     picker's own inputs wrap onto their own line too if they don't fit. */
  .kwf-header-row { flex-wrap: wrap; }
  #kwf-title { flex: 1 1 100%; }
  .kwf-datetime-row { flex: 1 1 100%; flex-wrap: wrap; }
  .kwf-datetime-input { flex: 1 1 auto; }
  .kwf-header-row .btn-primary { flex: 1 1 100%; margin-left: 0; }

  /* 16px is the threshold below which iOS Safari auto-zooms a focused
     input — phone-only so desktop keeps the locked 13px type scale. */
  .field input, .field select, .kwf-datetime-input { font-size: 16px; }

  /* Worker board: 2 bot cards per row at phone width (see the 980px tablet
     override above for the 3-per-row step) — cards remain individually
     tappable at this size, whether status-only (live) or selectable. Still
     fine with 8 cards: 4 clean rows of 2. */
  .workers-list { grid-template-columns: repeat(2, 1fr); }

  /* Ads Takibi: 2-per-row stat tiles, and a smaller thumbnail so a check
     block's media row (thumbnail + screenshot link) never crowds a narrow
     viewport — bot chips already wrap on their own via flex-wrap. */
  .ads-summary-grid { grid-template-columns: repeat(2, 1fr); }
  .ads-thumb-link, .ads-thumb { width: 48px; height: 48px; }
}
