/* Bookfinder — implements DESIGN.md. Tokens only; no raw hex in components. */

:root {
  /* Surfaces */
  --bg: #111827;
  --surface: #1f2937;
  --surface-raised: #374151;
  --border: #374151;
  --border-strong: #4b5563;

  /* Text */
  --text: #f3f4f6;
  --text-body: #d1d5db;
  --text-muted: #9ca3af;
  --text-faint: #6b7280;

  /* Accent */
  --accent: #6366f1;
  --accent-hover: #818cf8;
  --accent-end: #9333ea;
  --focus: #2563eb;

  /* Status */
  --ok: #10b981;
  --warn: #cc7b19;
  --danger: #ef4444;

  /* Spacing */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px;

  /* Radius */
  --r-sm: 4px; --r-md: 8px; --r-lg: 12px; --r-full: 9999px;

  --shadow: 0 4px 12px rgb(0 0 0 / 0.35);

  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "Cascadia Code", Consolas, monospace;
}

* { box-sizing: border-box; }

/* An explicit `display` in a component beats the `hidden` attribute, which is
   only a UA-stylesheet `display: none`. The auto-queue switch is a flex row,
   so it stayed on screen for unwatched books despite being marked hidden.
   Restore the attribute's authority once, globally, rather than remembering it
   per component. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text-body);
  font: 400 0.875rem/1.5 var(--font);
  /* Stop iOS inflating text in the narrow columns after a rotate. */
  -webkit-text-size-adjust: 100%;
}

.wrap { max-width: 1100px; margin: 0 auto; padding: 0 var(--s4); }
.spacer { flex: 1; }

/* Detail views put their real heading inside the rendered content, but the
   section still needs one for its aria-labelledby to point at. */
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

code { font-family: var(--mono); font-size: 0.8125rem; color: var(--text-muted); }

/* ---------- top bar + nav ---------- */

.topbar { border-bottom: 1px solid var(--border); background: var(--surface); margin-bottom: var(--s5); }
.topbar-inner { display: flex; align-items: center; gap: var(--s6); padding: 0 var(--s4); min-height: 60px; }

.wordmark {
  font-size: 1.25rem; font-weight: 700; text-decoration: none;
  background-image: linear-gradient(to bottom right, var(--accent), var(--accent-end));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

/* Spacing lives in the links' padding, not the gap: gap-only spacing leaves the
   hit target exactly as wide as the label ("Jobs" was 28px) and still reads
   cramped. The negative margin keeps the first label's text aligned with the
   content edge rather than its padding box. */
.nav {
  display: flex; gap: var(--s1);
  align-self: stretch; align-items: stretch;
  margin-left: calc(var(--s3) * -1);
}
.nav a {
  display: flex; align-items: center; gap: var(--s2);
  padding: 0 var(--s3);
  color: var(--text-muted); text-decoration: none;
  font-size: 0.875rem; font-weight: 600;
  border-bottom: 2px solid transparent;
  border-radius: var(--r-md) var(--r-md) 0 0;
  margin-bottom: -1px;
  white-space: nowrap;
}
.nav a:hover { color: var(--text-body); background: rgb(255 255 255 / 0.04); }
.nav a.is-active { color: var(--text); border-bottom-color: var(--accent); }

.pill {
  font-size: 0.6875rem; font-weight: 600;
  border-radius: var(--r-full); padding: 1px var(--s2);
  background: rgb(99 102 241 / 0.15); color: var(--accent-hover);
}

.health { font-size: 0.6875rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; white-space: nowrap; }
.health.up { color: var(--ok); }
.health.warn { color: var(--warn); }
.health.danger { color: var(--danger); }

/* ---------- view scaffolding ---------- */

.view { padding-bottom: var(--s7); }
/* The heading row needs its own bottom margin: the h1's margin only applies to
   the h1, so a row containing filter chips had nothing separating it from the
   content below. */
.view-head {
  display: flex; align-items: center; gap: var(--s4); flex-wrap: wrap;
  margin-bottom: var(--s4);
}
.view-head .h1 { margin-bottom: 0; }
/* Jobs has no lede paragraph to provide breathing room, so the list adds its
   own. Padding, not margin: adjacent vertical margins collapse to the larger
   of the two, so a margin here would be silently swallowed by the heading
   row's own 16px and change nothing. */
.view-head + .jobs { padding-top: var(--s2); }
.h1 { font-size: 1.5rem; font-weight: 700; color: var(--text); margin: 0 0 var(--s2); }
.h2 { font-size: 1.125rem; font-weight: 600; color: var(--text); margin: 0 0 var(--s3); }
.lede { color: var(--text-muted); margin: 0 0 var(--s4); max-width: 70ch; }
.empty { color: var(--text-faint); padding: var(--s5) 0; text-align: center; }

/* ---------- search ---------- */

.search-modes { margin-bottom: var(--s3); }
.search-row { display: flex; gap: var(--s3); margin-bottom: var(--s4); }
.search {
  flex: 1; padding: var(--s3) var(--s4);
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--r-md); color: var(--text); font: inherit; font-size: 1rem;
}
.search::placeholder { color: var(--text-faint); }
.search:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgb(37 99 235 / 0.4); }

/* ---------- buttons + chips ---------- */

.btn {
  border: 0; border-radius: var(--r-md); padding: var(--s2) var(--s4);
  font: inherit; font-weight: 600; cursor: pointer; color: var(--text);
}
.btn-primary { background-image: linear-gradient(to bottom right, var(--accent), var(--accent-end)); }
.btn-primary:hover:not(:disabled) { filter: brightness(1.08); }
.btn-secondary { background: var(--surface-raised); color: var(--text-body); }
.btn-secondary:hover:not(:disabled) { filter: brightness(1.15); }
.btn-ghost { background: transparent; color: var(--text-muted); padding: var(--s1) var(--s2); }
.btn-ghost:hover:not(:disabled) { color: var(--text-body); }
.btn-sm { padding: var(--s2) var(--s3); font-size: 0.8125rem; }
.btn:disabled { opacity: .45; cursor: not-allowed; }

.filters { display: flex; gap: var(--s2); }
.chip {
  background: var(--surface); color: var(--text-muted);
  border: 1px solid var(--border); border-radius: var(--r-full);
  padding: var(--s1) var(--s3); font: inherit; font-size: 0.8125rem; font-weight: 600;
  cursor: pointer;
}
.chip:hover { color: var(--text-body); }
.chip.is-active { color: var(--text); border-color: var(--accent); background: rgb(99 102 241 / 0.06); }

/* ---------- notes ---------- */

.notes {
  background: var(--surface); border: 1px solid var(--border);
  border-left: 3px solid var(--accent); border-radius: var(--r-md);
  padding: var(--s3) var(--s4); margin-bottom: var(--s4);
  color: var(--text-muted); font-size: 0.8125rem;
}
.notes p { margin: var(--s1) 0; }

/* ---------- work cards (wide variant) ---------- */

.results { display: flex; flex-direction: column; gap: var(--s4); }

.work {
  display: grid; grid-template-columns: 120px 1fr; gap: var(--s4);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--s4); box-shadow: var(--shadow);
}
@media (max-width: 640px) { .work { grid-template-columns: 1fr; } }

.cover, .cover-ph {
  width: 120px; aspect-ratio: 2 / 3; border-radius: var(--r-md);
  background: var(--surface-raised); object-fit: cover;
}

.work-title { margin: 0; font-size: 1.125rem; font-weight: 600; color: var(--text); }
.work-author { color: var(--text-muted); font-size: 0.8125rem; margin: var(--s1) 0 var(--s3); }

.col-label {
  font-size: 0.6875rem; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-faint); margin: var(--s3) 0 var(--s2);
}

/* ---------- candidate rows ---------- */

.cand {
  display: flex; align-items: flex-start; gap: var(--s3);
  padding: var(--s2) var(--s3); border: 1px solid transparent;
  border-radius: var(--r-md); cursor: pointer;
}
.cand:hover { background: var(--surface-raised); }
.cand.selected { border-color: var(--accent); background: rgb(99 102 241 / 0.06); }
.cand input { margin-top: 3px; accent-color: var(--accent); }
.cand-name { font-family: var(--mono); font-size: 0.8125rem; color: var(--text-body); word-break: break-all; }
.cand-meta { color: var(--text-faint); font-size: 0.8125rem; margin-top: 2px; }

/* ---------- badges ---------- */

.badges { display: inline-flex; flex-wrap: wrap; gap: var(--s1); margin-left: var(--s2); vertical-align: middle; }
.badge {
  font-size: 0.6875rem; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; border-radius: var(--r-sm); padding: 2px var(--s2);
}
.badge.ok     { color: var(--ok);     background: rgb(16 185 129 / 0.12); }
.badge.warn   { color: var(--warn);   background: rgb(204 123 25 / 0.12); }
.badge.danger { color: var(--danger); background: rgb(239 68 68 / 0.12); }
.badge.info   { color: var(--accent-hover); background: rgb(99 102 241 / 0.12); }

.actions { margin-top: var(--s4); display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }

/* ---------- poster grid (library) ---------- */

.poster-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--s4);
}
.poster {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--s3); box-shadow: var(--shadow);
}
.poster-art {
  aspect-ratio: 2 / 3; border-radius: var(--r-md); background: var(--surface-raised);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-faint); font-size: 2rem; margin-bottom: var(--s3);
  overflow: hidden;
}
/* Covers vary wildly in aspect ratio; cover-fit keeps the grid even. */
.poster-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.poster-title { color: var(--text); font-weight: 600; font-size: 0.9375rem; line-height: 1.3; }
.poster-meta { color: var(--text-muted); font-size: 0.8125rem; margin-top: var(--s1); }

/* ---------- detail hero (book + author) ----------
   Cover left, everything else right, mirroring the wide work card's proportions
   at a larger scale. Collapses to a single column below 640px, where a 200px
   cover beside text leaves ~130px for the title. */

.hero {
  display: grid; grid-template-columns: 200px 1fr; gap: var(--s5);
  margin-bottom: var(--s6);
}
.hero-art { width: 200px; }
.hero-img {
  width: 100%; aspect-ratio: 2 / 3; border-radius: var(--r-lg);
  background: var(--surface-raised); object-fit: cover; display: block;
  box-shadow: var(--shadow);
}
.hero-info { min-width: 0; }
.hero-by { color: var(--text-muted); margin: 0 0 var(--s3); }
.hero-desc { color: var(--text-body); margin: var(--s4) 0 0; max-width: 70ch; }
.link { color: var(--accent-hover); text-decoration: none; font-weight: 600; }
.link:hover { text-decoration: underline; }

/* Subjects are labels, not filters, so they never take the active treatment. */
.subject-tags { flex-wrap: wrap; margin-top: var(--s4); }
.subject-tags .chip { cursor: default; }

.watch-box { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; margin-top: var(--s4); }
.btn.is-on { border: 1px solid var(--accent); background: rgb(99 102 241 / 0.12); color: var(--text); }
.switch { display: flex; align-items: center; gap: var(--s2); font-size: 0.8125rem; color: var(--text-muted); }
.switch input { accent-color: var(--accent); }

/* ---------- panel (releases) ---------- */

.panel {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--s4); box-shadow: var(--shadow);
  margin-bottom: var(--s6);
}
.panel-head { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.panel-head .h2 { margin: 0; }
.panel-note { color: var(--text-muted); font-size: 0.8125rem; margin-top: var(--s2); }

.author-hits { display: flex; gap: var(--s2); flex-wrap: wrap; margin-bottom: var(--s4); }
.author-hits .btn { text-decoration: none; }

/* ---------- discover ---------- */

.genre-filters { flex-wrap: wrap; margin-bottom: var(--s5); }

.row { margin-bottom: var(--s6); }
.row-title {
  font-size: 1.0625rem; font-weight: 600; color: var(--text);
  margin: 0 0 var(--s3);
}
/* Horizontal rail rather than a grid: rows are for browsing past things, and
   a rail keeps each row to one line so several fit on screen at once. */
.rail {
  display: grid; grid-auto-flow: column; grid-auto-columns: 150px;
  gap: var(--s4); overflow-x: auto; padding-bottom: var(--s2);
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
}
.rail > * { scroll-snap-align: start; }

.rec {
  background: transparent; border: 0; padding: 0; cursor: pointer;
  text-align: left; color: inherit; font: inherit;
  border-radius: var(--r-md); text-decoration: none;
  display: block; min-width: 0;
}
.rec:hover .rec-art { filter: brightness(1.12); }
/* The book you are already looking at, inside its own series rail. */
.rec.is-current .rec-art { outline: 2px solid var(--accent); outline-offset: 2px; }
.rec.is-current .rec-title { color: var(--accent-hover); }
.rec .badges { margin-left: 0; margin-top: var(--s1); }
.rec-art {
  width: 100%; aspect-ratio: 2 / 3; border-radius: var(--r-md);
  background: var(--surface-raised); object-fit: cover; display: block;
  transition: filter .15s ease;
}
.rec-ph {
  display: flex; align-items: center; justify-content: center;
  color: var(--text-faint); font-size: 1.75rem;
}
.rec-title {
  color: var(--text); font-size: 0.8125rem; font-weight: 600;
  margin-top: var(--s2); line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.rec-meta { color: var(--text-faint); font-size: 0.8125rem; margin-top: 2px; }

/* ---------- jobs ---------- */

.jobs { display: flex; flex-direction: column; gap: var(--s2); }

.job {
  position: relative; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: var(--s3) var(--s4); overflow: hidden;
}
.job-main { display: flex; align-items: center; gap: var(--s3); }
/* min-width:0 lets long paths wrap instead of forcing the row wider. */
.job-body { flex: 1; min-width: 0; }
.dot { width: 8px; height: 8px; border-radius: var(--r-full); flex: none; background: var(--text-faint); }
.dot.active { background: var(--accent); animation: pulse 1.4s ease-in-out infinite; }
.dot.ok { background: var(--ok); }
.dot.danger { background: var(--danger); }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

.job-title { color: var(--text); font-weight: 600; font-size: 0.9375rem; }
.job-detail { color: var(--text-muted); font-size: 0.8125rem; word-break: break-word; }
.job-bar { position: absolute; left: 0; bottom: 0; height: 2px; background: var(--accent); transition: width .3s ease; }

.job-expand { margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--border); }

/* ---------- stat tiles ---------- */

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: var(--s2); }
.stat { background: var(--surface-raised); border-radius: var(--r-md); padding: var(--s3); }
.stat-label {
  font-size: 0.6875rem; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-faint);
}
.stat-value { font-size: 1.125rem; font-weight: 600; color: var(--text); margin-top: 2px; }
.stat-value.ok { color: var(--ok); }
.stat-value.danger { color: var(--danger); }

/* ---------- settings ---------- */

.settings { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--s3); }
.setting { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); padding: var(--s4); }
.setting-label { font-size: 0.6875rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text-faint); }
.setting-value { color: var(--text); font-weight: 600; margin-top: var(--s1); word-break: break-all; }
.setting-note { color: var(--text-muted); font-size: 0.8125rem; margin-top: var(--s2); }

/* ---------- skeletons ---------- */

.skeleton { background: var(--surface-raised); border-radius: var(--r-md); position: relative; overflow: hidden; }
.skeleton::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--border-strong), transparent);
  transform: translateX(-100%); animation: sweep 1.6s infinite;
}
@keyframes sweep { to { transform: translateX(100%); } }
.skeleton-card { height: 168px; margin-bottom: var(--s4); }
/* Each matches the height of what replaces it, so nothing jumps on load. */
.skeleton-poster { aspect-ratio: 2 / 3; }
.skeleton-rail { height: 230px; margin-bottom: var(--s5); }
.skeleton-hero { height: 300px; }

/* ---------- toasts ---------- */

.toasts {
  position: fixed; right: var(--s4); bottom: var(--s4);
  display: flex; flex-direction: column; gap: var(--s2); z-index: 50; max-width: 380px;
}
.toast {
  background: var(--surface); border: 1px solid var(--border);
  border-left: 3px solid var(--accent); border-radius: var(--r-md);
  box-shadow: var(--shadow); padding: var(--s3) var(--s4);
  color: var(--text-body); font-size: 0.8125rem; cursor: pointer;
}
.toast.ok { border-left-color: var(--ok); }
.toast.danger { border-left-color: var(--danger); }

/* ---------- login ---------- */

.login-body { display: grid; place-items: center; min-height: 100vh; padding: var(--s4); }
.login-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--s6);
  box-shadow: var(--shadow); width: 100%; max-width: 380px;
}
.login-mark { font-size: 1.75rem; margin: 0 0 var(--s1); }
.login-lede { margin-bottom: var(--s5); }
.login-input { width: 100%; margin: var(--s1) 0 var(--s4); font-size: 0.9375rem; }
.login-submit { width: 100%; }
.login-error { color: var(--danger); font-size: 0.8125rem; margin-top: var(--s3); }

/* ---------- responsive ----------
   The page must never scroll horizontally. The topbar was the offender: a
   rigid flex row of wordmark + 4 nav links + health status needs ~520px, so
   below that it wraps the nav onto its own line. */

.topbar-inner { flex-wrap: wrap; row-gap: var(--s2); }
.search-row { flex-wrap: wrap; }
.search { min-width: 12ch; }
.toasts { max-width: min(380px, calc(100vw - 2 * var(--s4))); }

/* Nav scrolls sideways within itself rather than widening the page. */
.nav { overflow-x: auto; scrollbar-width: none; }
.nav::-webkit-scrollbar { display: none; }

@media (max-width: 720px) {
  .topbar-inner { min-height: 0; padding-top: var(--s2); gap: var(--s3); }
  .spacer { display: none; }
  .health { order: 2; margin-left: auto; }
  .nav { order: 3; width: 100%; gap: var(--s1); padding-bottom: var(--s1); }
  .h1 { font-size: 1.25rem; }
}

@media (max-width: 640px) {
  /* Job rows must stack. As a single flex row the three action buttons hold
     their intrinsic width and squeeze the body into ~130px, which wraps the
     title one word per line and turns the detail text into a vertical ribbon.
     Body claims row one; the actions share row two as equal-width targets. */
  .job-main { flex-wrap: wrap; row-gap: var(--s3); }
  .job-body { flex: 1 1 calc(100% - 20px); }   /* 8px dot + 12px gap */
  .job-main > .btn { flex: 1 1 0; }

  /* Touch wants more than a mouse does. Desktop keeps the lighter sizing. */
  .btn-sm { padding: var(--s2) var(--s3); font-size: 0.875rem; }
  .chip { padding: var(--s2) var(--s3); }
  .filters { width: 100%; }

  /* A 200px cover beside text leaves ~130px for the title. Stack instead, and
     cap the cover so it doesn't fill the screen before the title appears. */
  .hero { grid-template-columns: 1fr; gap: var(--s4); }
  .hero-art { width: 150px; }
  .skeleton-hero { height: 420px; }
}

@media (max-width: 420px) {
  /* Health text is the least critical thing here, and Settings shows the
     same state in full. */
  .health { display: none; }
  .poster-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
  .settings { grid-template-columns: 1fr; }
  .stats { grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); }
}
