/* Virality — shared design system used by every page (Optimize / Viral
 * Trends / Content Improver / History). Linked from each HTML file. */

@import url('https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,400;9..40,500;9..40,600;9..40,700&family=Instrument+Serif:ital@0;1&display=swap');

:root {
  --bg:           #F6F4F0;
  --surface:      #FFFFFF;
  --surface-soft: #FAF8F4;
  --surface-tint: #F1EDE5;
  --ink:          #15131C;
  --ink-2:        #3D3A47;
  --ink-3:        #6E6A78;
  --ink-4:        #A39FA9;
  --rule:         #E6E2D8;
  --rule-strong:  #D7D2C5;
  --primary:      #4F46E5;
  --primary-600:  #4338CA;
  --primary-soft: #EEF0FF;
  --primary-line: #C7C7FF;
  --accent:       #F97316;
  --accent-soft:  #FFF1E5;
  --success:      #059669;
  --success-soft: #E7F5EF;
  --success-line: #B8E0CE;
  --warning:      #B45309;
  --warning-soft: #FDF2DC;
  --warning-line: #F0D9A4;
  --danger:       #DC2626;
  --danger-soft:  #FCE8E6;
  --danger-line:  #F3BFB8;
  --viral:        #7C3AED;
  --viral-soft:   #F4ECFF;

  --font-display: "Instrument Serif", "Cambria", "Georgia", serif;
  --font-body:    "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;

  --s-1: 4px; --s-2: 8px; --s-3: 12px; --s-4: 16px;
  --s-5: 24px; --s-6: 32px; --s-7: 48px; --s-8: 64px; --s-9: 96px;

  --r-1: 6px; --r-2: 10px; --r-3: 14px; --r-4: 20px;

  --shadow-sm: 0 1px 0 rgba(21,19,28,0.04), 0 1px 2px rgba(21,19,28,0.04);
  --shadow-md: 0 1px 0 rgba(21,19,28,0.05), 0 8px 24px -12px rgba(21,19,28,0.12);

  --ease:     cubic-bezier(0.2, 0.65, 0.25, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --t-fast:   160ms;
  --t-base:   280ms;
}

*, *::before, *::after { box-sizing: border-box; }

/* v103 — make the `hidden` HTML attribute authoritative everywhere.
 * The browser's default `[hidden]{display:none}` is only a single attribute-
 * selector (specificity 0,1,0), so any component rule that sets `display`
 * (e.g. `.btn{display:inline-flex}`) ties on specificity and — being author
 * CSS that loads after the UA sheet — WINS, leaving elements visible even
 * with `hidden` set in the DOM. That was the long-standing "I set .hidden =
 * true but the button is still there" bug (e.g. Generate-rewrites in image
 * mode). One global !important rule fixes the entire class and removes the
 * need for the per-component `[hidden]{display:none}` patches dotted around
 * this file. JS that toggles visibility via `el.hidden = true/false`
 * (the pattern used throughout) now Just Works. */
[hidden] { display: none !important; }

html, body {
  margin: 0; padding: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.55;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}
body { min-height: 100vh; display: flex; flex-direction: column; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }

/* ── Top nav ──────────────────────────────────────────────────────── */
.topnav {
  position: sticky; top: 0; z-index: 50;
  background: rgba(246, 244, 240, 0.85);
  backdrop-filter: saturate(140%) blur(10px);
  border-bottom: 1px solid var(--rule);
}
.topnav-inner {
  max-width: 1240px; margin: 0 auto;
  padding: 0 var(--s-6); height: 64px;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center; gap: var(--s-6);
}
.brand {
  display: flex; align-items: center; gap: var(--s-2);
  font-weight: 700; font-size: 17px;
  color: var(--ink); letter-spacing: -0.01em;
}
.brand-mark {
  width: 28px; height: 28px; border-radius: 7px;
  background: var(--ink); color: #fff;
  display: grid; place-items: center;
  font-family: var(--font-display); font-style: italic;
  font-weight: 400; font-size: 18px; line-height: 1;
  padding-top: 2px;
}
/* v130 — PerfX logo mark in the nav (replaces the lettered chip). */
.brand-logo {
  /* v140 — dark ink chip variant (logo-nav.svg) + 2px larger for presence
     on the cream nav. The white-box favicon was invisible against the
     theme background. */
  width: 30px; height: 30px; display: block; flex-shrink: 0;
  object-fit: contain;
}
.brand-dot { color: var(--primary); }
.navlinks {
  display: flex; gap: var(--s-2);
  justify-self: center;
  background: var(--surface);
  padding: 4px; border-radius: 999px;
  border: 1px solid var(--rule);
}
.navlinks a {
  font-size: 14px; font-weight: 500;
  color: var(--ink-3); padding: 8px 16px;
  border-radius: 999px; white-space: nowrap;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.navlinks a:hover { color: var(--ink); }
.navlinks a.is-active { background: var(--ink); color: #fff; }
.nav-cta { display: flex; align-items: center; gap: var(--s-3); }
.nav-link {
  font-size: 14px; font-weight: 500;
  color: var(--ink-2); padding: 8px 12px;
  white-space: nowrap;
}
.nav-link:hover { color: var(--ink); }
.btn-nav {
  background: var(--ink); color: #fff;
  padding: 9px 18px; border-radius: 999px;
  font-size: 14px; font-weight: 600;
  border: 0; white-space: nowrap;
}
.btn-nav:hover { background: #2A2735; }
.btn-nav.is-primary { background: var(--primary); }
.btn-nav.is-primary:hover { background: var(--primary-600); }

/* ── Page shell ──────────────────────────────────────────────────── */
.page {
  flex: 1; width: 100%;
  max-width: 1080px; margin: 0 auto;
  padding: var(--s-8) var(--s-6) var(--s-9);
  display: flex; flex-direction: column;
  gap: var(--s-7);
}
.page-head {
  display: flex; flex-direction: column; gap: var(--s-2);
  padding: var(--s-2) 0 var(--s-3);
}
.page-eyebrow {
  font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--primary);
}
.page-title {
  font-family: var(--font-body); font-weight: 700;
  font-size: clamp(36px, 5vw, 52px); letter-spacing: -0.03em;
  line-height: 1.05; color: var(--ink); margin: 0;
}
.page-title em {
  font-family: var(--font-display); font-style: italic;
  font-weight: 400; color: var(--primary);
  letter-spacing: -0.01em; padding: 0 4px;
}
.page-lede {
  font-size: 17px; color: var(--ink-3);
  max-width: 600px; margin: 0;
}

/* ── Cards ───────────────────────────────────────────────────────── */
.card {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-3);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.card-head {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-5) var(--s-5) var(--s-4);
  border-bottom: 1px solid var(--rule);
}
.card-head-lead { flex: 1; min-width: 0; }
.card-title {
  font-size: 18px; font-weight: 600;
  letter-spacing: -0.015em; color: var(--ink); margin: 0;
}
.card-sub { font-size: 13px; color: var(--ink-3); margin-top: 2px; }
.card-meta { font-size: 13px; color: var(--ink-3); font-weight: 500; }
.card-body { padding: var(--s-5); }

/* ── Buttons ─────────────────────────────────────────────────────── */
.btn {
  appearance: none; border: 1px solid transparent;
  font-family: inherit; font-size: 14px; font-weight: 600;
  line-height: 1; padding: 11px 18px; border-radius: 10px;
  display: inline-flex; align-items: center; gap: 7px;
  transition: all var(--t-fast) var(--ease); white-space: nowrap;
}
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-primary {
  background: var(--primary); color: #fff;
  border-color: var(--primary);
  box-shadow: 0 1px 0 var(--primary-600);
}
.btn-primary:hover:not(:disabled) { background: var(--primary-600); }
.btn-secondary {
  background: var(--surface); color: var(--ink);
  border-color: var(--rule-strong);
}
.btn-secondary:hover:not(:disabled) { background: var(--surface-tint); }
.btn-ghost {
  background: transparent; color: var(--ink-2);
  border: 0; padding: 11px 14px;
}
.btn-ghost:hover:not(:disabled) { color: var(--ink); background: var(--surface-tint); }

/* ── Tab pill ────────────────────────────────────────────────────── */
.tabs {
  display: inline-flex; background: var(--surface-tint);
  padding: 3px; border-radius: 10px; gap: 2px;
}
.tab {
  appearance: none; background: transparent; border: 0;
  padding: 7px 14px; font-size: 13px; font-weight: 500;
  color: var(--ink-3); border-radius: 7px;
  display: inline-flex; align-items: center; gap: 6px;
}
.tab:hover:not(:disabled):not(.is-active) { color: var(--ink); }
.tab.is-active {
  background: var(--surface); color: var(--ink);
  box-shadow: 0 1px 2px rgba(21,19,28,0.06);
  font-weight: 600;
}
.tab:disabled { color: var(--ink-4); cursor: not-allowed; }

/* ── Footer ─────────────────────────────────────────────────────── */
.footer {
  border-top: 1px solid var(--rule);
  background: var(--surface);
  margin-top: var(--s-7);
}
.footer-base {
  max-width: 1240px; margin: 0 auto;
  padding: var(--s-5) var(--s-6);
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--s-4);
  font-size: 13px; color: var(--ink-3);
}
.footer-status {
  display: inline-flex; align-items: center; gap: 6px;
}
.footer-status .dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--success);
  box-shadow: 0 0 0 3px var(--success-soft);
}

/* ── Misc helpers ────────────────────────────────────────────────── */
.pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 600;
}
.pill-success { background: var(--success-soft); color: var(--success); }
.pill-warning { background: var(--warning-soft); color: var(--warning); }
.pill-danger  { background: var(--danger-soft);  color: var(--danger); }
.pill-viral   { background: var(--viral-soft);   color: var(--viral); }
.pill-primary { background: var(--primary-soft); color: var(--primary); }
.pill-muted   { background: var(--surface-tint); color: var(--ink-2); }

/* ── Responsive ──────────────────────────────────────────────────── */
@media (max-width: 920px) {
  .topnav-inner { grid-template-columns: auto 1fr; gap: var(--s-3); }
  .navlinks { display: none; }
}
@media (max-width: 640px) {
  .page { padding: var(--s-6) var(--s-4) var(--s-7); gap: var(--s-5); }
  .topnav-inner { padding: 0 var(--s-4); height: 56px; }
  .nav-cta .nav-link { display: none; }
  .card-head, .card-body { padding: var(--s-4); }
  .footer-base { flex-direction: column; align-items: flex-start; padding: var(--s-4); }
}

/* ─────────────────────────────────────────────────────────────────────
   AI Deep-check section (only shown when "Run AI deep-check" is ticked
   on the Optimize page). The new design didn't include this — we keep
   the v15 backend feature with light-theme styling that matches the
   new editorial direction.
   ───────────────────────────────────────────────────────────────────── */
.ai-deep-check {
  margin-top: var(--s-5);
  padding: var(--s-4);
  background: var(--surface-soft);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
}
.ai-deep-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3); margin-bottom: var(--s-3);
}
.ai-deep-title {
  font-size: 14px; font-weight: 600; letter-spacing: -0.01em;
  color: var(--ink); margin: 0;
}
.slop-verdict {
  font-size: 11px; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 4px 8px; border-radius: var(--r-1);
  background: var(--surface-tint); color: var(--ink-2);
}
.slop-verdict.verdict-clean { background: var(--success-soft); color: var(--success); }
.slop-verdict.verdict-light { background: var(--warning-soft); color: var(--warning); }
.slop-verdict.verdict-heavy { background: var(--danger-soft);  color: var(--danger);  }
.slop-row {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: var(--s-2) 0;
}
.slop-label { font-size: 13px; color: var(--ink-3); }
.slop-score {
  font-family: var(--font-display); font-style: italic;
  font-size: 22px; line-height: 1; color: var(--ink);
}
.slop-matches {
  list-style: none; padding: 0; margin: var(--s-2) 0 0;
  display: flex; flex-wrap: wrap; gap: 6px;
}
.slop-matches li {
  font-size: 11px; padding: 3px 8px;
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: 999px; color: var(--ink-3);
}
.hook-quality, #hook-suggestions-wrap { margin-top: var(--s-4); }
.ai-deep-subtitle {
  font-size: 12px; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-3); margin: 0 0 var(--s-2);
}
.hook-feedback { font-size: 14px; color: var(--ink-2); margin: 0; line-height: 1.5; }
.hook-suggestions {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 6px;
}
.hook-suggestions li {
  font-size: 14px; color: var(--ink-2);
  padding: 8px 12px;
  background: var(--surface); border: 1px solid var(--rule);
  border-radius: var(--r-1);
}

/* Error banner — universal */
.error-banner {
  margin-top: var(--s-3);
  padding: 10px 14px;
  background: var(--danger-soft);
  border: 1px solid var(--danger-line);
  border-radius: var(--r-1);
  color: var(--danger);
  font-size: 14px;
}

/* Compact trends list inside the trends-panel on Optimize */
.trends-list-compact {
  list-style: none; padding: 0; margin: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--s-3);
}
.trends-list-compact li {
  display: flex; flex-direction: column; gap: 4px;
  padding: var(--s-3); border: 1px solid var(--rule);
  border-radius: var(--r-2); background: var(--surface);
}
.trends-list-compact .trend-topic { font-size: 14px; font-weight: 600; color: var(--ink); }
.trends-list-compact .trend-meta  { font-size: 12px; color: var(--ink-3); }

/* Footer extension */
.footer-meta {
  font-size: 12px; color: var(--ink-3);
  margin-left: auto;
}
.footer-meta a { color: var(--ink-2); text-decoration: underline; text-decoration-color: var(--rule-strong); }
.colophon-sep { color: var(--ink-4); }

/* Char count warning */
.char-count.over { color: var(--danger); font-weight: 600; }

/* Body loading state — subtle */
body.is-loading { cursor: progress; }

/* ─────────────────────────────────────────────────────────────────────
   Topic chip row (Viral Trends page, live /trending topics rendered as
   tappable chips above the main viral grid).
   ───────────────────────────────────────────────────────────────────── */
.topic-chip-row {
  list-style: none; padding: 0; margin: 0 0 var(--s-5);
  display: flex; flex-wrap: wrap; gap: var(--s-2);
}
.topic-chip {
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: 6px 10px 6px 12px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 999px;
  font-size: 13px; color: var(--ink-2);
  transition: border-color var(--t-fast) var(--ease);
}
.topic-chip:hover { border-color: var(--primary-line); }
.topic-chip-rank {
  font-family: var(--font-display); font-style: italic;
  font-size: 14px; color: var(--ink-4);
}
.topic-chip-text { font-weight: 600; color: var(--ink); }
.topic-chip-meta { font-size: 11px; color: var(--ink-4); }
.topic-chip-btn {
  margin-left: 4px;
  font-size: 12px; font-weight: 600;
  color: var(--primary); background: transparent;
  border: 0; padding: 4px 6px; border-radius: var(--r-1);
  cursor: pointer;
}
.topic-chip-btn:hover { background: var(--primary-soft); }

/* Action button row inside .viral-card and .text-post (new design's
   own rewrite-btn styling matches, we reuse it). */
.viral-actions, .text-post-actions {
  display: flex; flex-wrap: wrap; gap: 6px; margin-top: var(--s-2);
}
/* v34 — base .rewrite-btn styling. Pre-v34 this class only contributed
   flex: 0 0 auto inside .viral-actions / .text-post-actions, which meant
   the same class on the improver's tile-actions buttons (Download, Copy
   URL) fell back to browser-default <button> styling and looked broken.
   Adding a proper base declaration so any .rewrite-btn anywhere in the
   app looks consistent — surface bg, rounded, hover, primary variant. */
.rewrite-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--ink-1);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-1);
  cursor: pointer;
  transition: all var(--t-fast) var(--ease);
  text-decoration: none;
}
.rewrite-btn:hover {
  border-color: var(--primary-line);
  color: var(--primary);
  background: var(--primary-soft);
}
.rewrite-btn:active { transform: translateY(1px); }
.rewrite-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.rewrite-btn.is-primary {
  background: var(--ink);
  color: var(--surface);
  border-color: var(--ink);
}
.rewrite-btn.is-primary:hover {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--surface);
}

.viral-actions .rewrite-btn,
.text-post-actions .rewrite-btn {
  flex: 0 0 auto;
}

/* Pager (Viral Trends) — earlier/later style */
.pager {
  display: flex; align-items: center; justify-content: center;
  gap: var(--s-3);
  margin-top: var(--s-6);
  padding-top: var(--s-4);
  border-top: 1px solid var(--rule);
}
.pager-btn {
  padding: 8px 14px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-1);
  font-size: 13px; font-weight: 500; color: var(--ink-2);
  cursor: pointer;
  transition: all var(--t-fast) var(--ease);
}
.pager-btn:hover:not(:disabled) {
  border-color: var(--primary-line);
  color: var(--primary);
}
.pager-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.pager-btn.is-active {
  background: var(--ink); color: var(--surface); border-color: var(--ink);
}
.pager-meta { font-size: 13px; color: var(--ink-3); }

/* ─────────────────────────────────────────────────────────────────────
   Content Improver — additional styles to wire the new design's recent
   image grid + polished prompt cards to our JS rendering.
   ───────────────────────────────────────────────────────────────────── */

/* The recent grid uses <button> roots; reset button defaults so the
   .recent-item CSS in the page-level <style> block keeps working. */
.recent-grid button.recent-item {
  font-family: inherit; font-size: inherit; text-align: left;
  background: var(--surface); border: 1px solid var(--rule);
  cursor: pointer; padding: 0;
}
.recent-empty {
  grid-column: 1 / -1;
  text-align: center;
  padding: var(--s-6) var(--s-4);
  background: var(--surface-soft);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  align-items: center;
}
.recent-empty-title {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 22px;
  line-height: 1.2;
  color: var(--ink);
}
.recent-empty-sub {
  font-size: 13px;
  color: var(--ink-3);
  line-height: 1.55;
  max-width: 38ch;
}
.recent-empty a { color: var(--accent); text-decoration: underline; text-decoration-color: var(--accent-soft); }

/* v89.1 — Character-add block. Wraps the three sub-controls (Upload,
 * Save-as-character, URL paste) into a single visually-coherent section
 * with proper editorial hierarchy: a small eyebrow header with serif
 * italic accent, a soft warm-tint background to differentiate this
 * block from the surrounding card, and a divider that explicitly
 * positions URL paste as the secondary path. */
.character-add {
  margin-top: var(--s-4);
  padding: var(--s-4);
  background: var(--surface-soft);
  border: 1px solid var(--rule);
  border-radius: var(--r-3);
}
.character-add-head {
  display: flex; align-items: baseline; gap: 6px;
  margin: 0 0 var(--s-3);
  letter-spacing: 0.02em;
}
.character-add-eyebrow {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-3);
}
.character-add-eyebrow-em {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 22px;
  line-height: 1;
  color: var(--ink);
  text-transform: none;
  letter-spacing: 0;
}

/* "or paste a URL" divider — line-through with centered label. Sits
 * between the primary Upload control and the secondary URL paste form. */
.character-add-or {
  display: flex; align-items: center; gap: 10px;
  margin: var(--s-3) 0;
}
.character-add-or-line {
  flex: 1; height: 1px; background: var(--rule);
}
.character-add-or-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-4);
  white-space: nowrap;
}

/* v89.1 — Mini dropzone redesign. Was a thin dashed-border rectangle
 * with no character; per Kyle's feedback ("all one colour and gradient,
 * looks low quality"). Now: warm surface-tint background, no border
 * (relies on parent for separation), proper icon size + breathing room,
 * subtle hover that warms further toward accent-soft. */
.improver-dropzone-mini {
  margin: 0;                              /* parent controls spacing now */
  border: 1px solid transparent;
  border-radius: var(--r-2);
  padding: var(--s-5) var(--s-4);
  text-align: center;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease);
  background: var(--surface-tint);
}
.improver-dropzone-mini:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
}
.improver-dropzone-mini.is-dragover {
  background: var(--accent-soft);
  border-color: var(--accent);
  transform: scale(1.005);
}
.improver-dropzone-mini:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.improver-dropzone-mini .dropzone-inner {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
}
.improver-dropzone-mini .dropzone-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink-2);
  border: 1px solid var(--rule);
  transition: color var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease);
}
.improver-dropzone-mini:hover .dropzone-mark,
.improver-dropzone-mini.is-dragover .dropzone-mark {
  color: var(--accent);
  background: var(--surface);
}
.improver-dropzone-mini .dropzone-title {
  font-size: 14px; font-weight: 600; color: var(--ink);
}
.improver-dropzone-mini .dropzone-help {
  font-size: 11px; color: var(--ink-3); line-height: 1.5;
}

/* Placeholder shown inside #source-canvas before an image is picked */
.source-placeholder {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  width: 100%; height: 100%;
  background: var(--surface-soft);
}

/* Polished prompt cards (revealed inside #polished-panel after /rewrite) */
.polished-variants {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: var(--s-3);
}
.polished-card {
  padding: var(--s-3) var(--s-4);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
}
.polished-card.is-pick {
  border-color: var(--primary-line);
  background: var(--primary-soft);
}
.polished-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3); margin-bottom: var(--s-2);
}
.polished-label {
  font-size: 11px; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-3);
}
.polished-card.is-pick .polished-label { color: var(--primary); }
.polished-len { font-size: 11px; color: var(--ink-4); }
.polished-text {
  font-size: 14px; line-height: 1.55; color: var(--ink);
  margin: 0 0 var(--s-3); white-space: pre-wrap;
}
.polished-actions {
  display: flex; flex-wrap: wrap; gap: 6px;
}

/* Char count warning */
#prompt-count.over { color: var(--danger); font-weight: 600; }

/* ─────────────────────────────────────────────────────────────────────
   History page — supplementary styles for the wired-up version.
   The new design's inline <style> provides the bulk: .history-row,
   .history-group, .detail-grid, .pill-* etc. We just add the bits that
   aren't covered there.
   ───────────────────────────────────────────────────────────────────── */

.history-list {
  display: flex; flex-direction: column; gap: var(--s-6);
}

.history-empty {
  text-align: center; padding: var(--s-7) var(--s-4);
  font-size: 14px; color: var(--ink-3);
  background: var(--surface-soft);
  border: 1px dashed var(--rule);
  border-radius: var(--r-2);
}
.history-empty a { color: var(--primary); text-decoration: underline; text-decoration-color: var(--primary-line); }

/* Filter pills (history-filters extends the new design's .tabs) */
.history-filters .seg-btn {
  cursor: pointer;
}

/* Pill colour token to fill the gap when no severity comes back */
.pill-muted {
  background: var(--surface-tint); color: var(--ink-3);
  border: 1px solid var(--rule);
}

/* ─────────────────────────────────────────────────────────────────────
   v25 IMAGE CONSTRAINTS — restore max-height that was dropped from the
   editorial-paper rewrite. Phone photos are 3000+ pixels wide and
   destroyed the page layout without these caps. Reported in the V22
   repair audit (docs/V22_REPAIR_AUDIT.md, Bug B).
   ─────────────────────────────────────────────────────────────────── */

.dropzone img,
.dropzone-preview,
.dropzone .has-image img,
img.uploaded-image,
.image-preview-thumb,
#findings-canvas img,
.findings-canvas img,
#source-canvas img {
  max-width: 100%;
  max-height: 360px;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
}

/* The findings canvas needs slightly more room so annotation pins
   stay readable when rendered at scale. */
#findings-canvas img,
.findings-canvas img {
  max-height: 480px;
}

@media (max-width: 640px) {
  .dropzone img,
  .dropzone-preview,
  img.uploaded-image,
  #source-canvas img {
    max-height: 280px;
  }
  #findings-canvas img,
  .findings-canvas img {
    max-height: 360px;
  }
}

/* ─────────────────────────────────────────────────────────────────────
   v25 SCORE DISPLAY — keep the "/100" suffix in a sibling element so
   optimize.js's runScoreTickUp animation never wipes it. The previous
   markup nested an <em>/100</em> inside #score-out-of-ten; the
   tick-up's textContent overwrite destroyed it on first animation.
   See docs/V22_REPAIR_AUDIT.md Bug A (variant).
   ─────────────────────────────────────────────────────────────────── */

.score-num-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.score-num-suffix {
  font-size: 28px;
  opacity: 0.4;
  font-style: italic;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

/* Scale legend underneath the verdict pill — makes the 0-100 scale
   obvious to first-time users so they don't have to guess what "39"
   means. */
.score-scale-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  font-size: 12px;
  color: var(--ink-3);
  margin-top: 12px;
  letter-spacing: 0.01em;
}
.score-scale-legend .dot { opacity: 0.5; }

/* ─────────────────────────────────────────────────────────────────────
   v27 DROPZONE CONTAINER CONSTRAINTS — v25 capped the image element but
   didn't cap the .dropzone container itself. Without max-height on the
   wrapper, the dropzone GROWS to fit injected content (paintDropzonePreview
   does `dropzone.innerHTML = ""` then `appendChild(img)`), so a 4032px
   phone photo still pushed the layout out of view even though the img
   was technically constrained.
   
   Fix: hard-cap the dropzone's own height when it's in `.has-image`
   state, and use object-fit: contain on the child img so it scales
   to fit those bounds while preserving aspect ratio.
   ─────────────────────────────────────────────────────────────────── */

.dropzone {
  max-height: 360px;        /* never grow past this */
  overflow: hidden;          /* belt-and-braces: clip if something else slips through */
}
.dropzone.has-image {
  /* When an image is present, the dropzone becomes a fixed-height preview
     frame. Padding goes to zero so the image fills the available area. */
  padding: 0;
  max-height: 360px;
  min-height: 220px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-soft, #FAF8F4);
}
.dropzone.has-image img,
.dropzone-preview {
  /* Fill the parent's bounds, scale-to-fit, preserve aspect ratio.
     This overrides v25's looser rule with higher specificity. */
  width: 100% !important;
  height: 100% !important;
  max-width: 100% !important;
  max-height: 360px !important;
  object-fit: contain !important;
  display: block;
}

@media (max-width: 640px) {
  .dropzone,
  .dropzone.has-image {
    max-height: 280px;
  }
  .dropzone.has-image img,
  .dropzone-preview {
    max-height: 280px !important;
  }
}

/* ─────────────────────────────────────────────────────────────────────
   v29 IMPROVEMENT-CARD RATING UI — surface the per-signal numeric data
   that /score already returns (score, max, sub-rules with weights).
   Before v29 each "What's holding it back" card just showed an "Improve"
   tag; users couldn't tell which signals were close to maxing out vs
   completely missed. Now every card shows score/max + a progress bar +
   each sub-rule with ✓ / ○ for fired/missing and its weight.

   Merged from a parallel-thread audit doc. The data was already in the
   /score response; this just renders it.
   ─────────────────────────────────────────────────────────────────── */

.imp-tag .imp-tag-max {
  font-weight: 400;
  opacity: 0.65;
}

/* Progress bar showing current/max as a percentage */
.imp-bar {
  height: 4px;
  background: rgba(0, 0, 0, 0.06);
  border-radius: 999px;
  margin: 10px 0 12px;
  overflow: hidden;
}
.imp-bar-fill {
  height: 100%;
  border-radius: 999px;
  transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.imp-bar-fill.is-critical { background: var(--danger,  #c83a3a); }
.imp-bar-fill.is-warning  { background: var(--warning, #d68f2f); }
.imp-bar-fill.is-positive { background: var(--success, #4a8c4a); }

/* Sub-rules list: each rule shown as fired (check) or missing (circle),
   with its weight. Compact, secondary visual weight. */
.imp-subrules {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.imp-subrule {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  padding: 4px 0;
}
.imp-subrule.fired   { color: var(--ink-2, #444); }
.imp-subrule.missing { color: var(--ink-3, #888); }
.imp-subrule-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  font-size: 11px;
  flex-shrink: 0;
}
.imp-subrule.fired .imp-subrule-icon {
  background: rgba(74, 140, 74, 0.15);
  color: var(--success, #4a8c4a);
  font-weight: 700;
}
.imp-subrule.missing .imp-subrule-icon {
  background: rgba(0, 0, 0, 0.04);
  color: var(--ink-3, #999);
}
.imp-subrule-name {
  flex: 1;
  text-transform: lowercase;
  letter-spacing: 0.01em;
}
.imp-subrule-weight {
  font-variant-numeric: tabular-nums;
  opacity: 0.6;
  font-size: 11.5px;
  font-weight: 500;
}
.imp-subrule.fired .imp-subrule-weight {
  color: var(--success, #4a8c4a);
  opacity: 0.9;
}

/* ─────────────────────────────────────────────────────────────────────
   v30 IMAGE FINDINGS BANNER — green "Live analysis" pill when the
   Pillow analyzer returned real findings, amber "Preview · sample
   analysis" when we fell back (503 / 413 / network error).

   Static HTML defaults to amber. annotations.js setPreviewBanner(false)
   flips it green on a successful /analyze-image response.
   ─────────────────────────────────────────────────────────────────── */
.card-meta.is-live {
  color: var(--success, #4a8c4a);
  background: rgba(74, 140, 74, 0.10);
  padding: 3px 10px;
  border-radius: 999px;
  font-weight: 500;
}
.card-meta.is-fallback {
  color: var(--ink-3, #888);
  background: rgba(214, 143, 47, 0.10);
  padding: 3px 10px;
  border-radius: 999px;
  font-weight: 500;
}

/* ─────────────────────────────────────────────────────────────────────
   v31 AI-SLOP BADGE + SIGNAL BREAKDOWN

   Every edit result from /edit-image runs through /score-image-slop
   after completion. The 0-100 score lives in a colour-coded badge
   over the result image; the 7 signal probabilities live in a
   collapsible details panel below.

   Colours:
     natural  → green
     mild     → amber
     obvious  → orange
     screams  → red
   ─────────────────────────────────────────────────────────────────── */

.slop-badge {
  position: absolute;
  top: 28px;            /* clears the figcaption */
  right: 8px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.01em;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(4px);
}
.slop-badge.is-checking { color: var(--ink-3, #888); }
.slop-badge.is-natural  { color: #fff; background: var(--success, #4a8c4a); }
.slop-badge.is-mild     { color: #fff; background: var(--warning, #d68f2f); }
.slop-badge.is-obvious  { color: #fff; background: #d35400; }
.slop-badge.is-screams  { color: #fff; background: var(--danger, #c83a3a); }

.slop-details {
  margin-top: var(--s-3);
  border: 1px solid var(--rule);
  border-radius: var(--r-3);
  padding: var(--s-2) var(--s-3);
  background: var(--surface-tint, #faf8f3);
}
.slop-details-summary {
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2, #444);
  padding: 4px 0;
  user-select: none;
}
.slop-details[open] .slop-details-summary { margin-bottom: 8px; }

.slop-signals-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.slop-signal-row {
  display: grid;
  grid-template-columns: 1fr 100px 40px;
  align-items: center;
  gap: 10px;
  padding: 4px 0;
  font-size: 12px;
}
.slop-signal-label { color: var(--ink-2, #444); }
.slop-signal-bar {
  display: block;
  height: 6px;
  background: rgba(0, 0, 0, 0.06);
  border-radius: 999px;
  overflow: hidden;
}
.slop-signal-fill {
  display: block;
  height: 100%;
  background: linear-gradient(to right,
    var(--success, #4a8c4a) 0%,
    var(--warning, #d68f2f) 50%,
    var(--danger,  #c83a3a) 100%);
  transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
}
.slop-signal-pct {
  font-variant-numeric: tabular-nums;
  font-size: 11.5px;
  color: var(--ink-3, #888);
  text-align: right;
}
.slop-disclaimer {
  margin: 10px 0 0;
  font-size: 11.5px;
  color: var(--ink-3, #888);
  line-height: 1.5;
}

/* ─── repair-3: 1-3 image picker ──────────────────────────────────────
   Lives on the Content Improver page between the stage and the foot.
   Editorial-paper styling: soft borders, neutral surfaces, accent only
   on the selected radio. Stays compact at desktop and stacks cleanly
   on mobile. */

.ref-picker {
  margin: var(--s-4, 16px) 0 0;
  padding: 14px 16px;
  background: var(--surface-soft);
  border: 1px solid var(--surface-tint);
  border-radius: 10px;
}

.ref-picker-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.ref-picker-title {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-2);
  letter-spacing: -0.005em;
}

.ref-picker-sub {
  font-size: 12px;
  color: var(--ink-3);
}

.ref-picker-radios {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.ref-picker-radio {
  position: relative;
  display: block;
  cursor: pointer;
}

.ref-picker-radio input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.ref-picker-radio-body {
  display: grid;
  grid-template-columns: 28px 1fr;
  grid-template-rows: auto auto;
  column-gap: 10px;
  row-gap: 2px;
  align-items: center;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--surface-tint);
  border-radius: 8px;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.ref-picker-radio:hover .ref-picker-radio-body {
  border-color: var(--ink-4);
}

.ref-picker-radio input:checked + .ref-picker-radio-body {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.ref-picker-radio input:focus-visible + .ref-picker-radio-body {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.ref-picker-radio-num {
  grid-row: 1 / span 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-2);
  background: var(--surface-tint);
  border-radius: 50%;
  font-variant-numeric: tabular-nums;
}

.ref-picker-radio input:checked + .ref-picker-radio-body .ref-picker-radio-num {
  background: var(--accent);
  color: #fff;
}

.ref-picker-radio-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
}

.ref-picker-radio-help {
  font-size: 11.5px;
  color: var(--ink-3);
  line-height: 1.3;
}

.ref-picker-slots {
  margin-top: 12px;
  display: flex;
  gap: 10px;
}

.ref-picker-slot {
  flex: 0 0 auto;
  width: 92px;
  height: 92px;
  position: relative;
}

.ref-picker-slot-add {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 0;
  background: var(--surface);
  border: 1.5px dashed var(--surface-tint);
  border-radius: 8px;
  color: var(--ink-3);
  font-size: 11.5px;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}

.ref-picker-slot-add:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.ref-picker-slot-add:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.ref-picker-slot-thumb {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--surface-tint);
  background: var(--surface);
}

.ref-picker-slot-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.ref-picker-slot-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(28, 24, 19, 0.78);
  color: #fff;
  border: 0;
  border-radius: 50%;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  padding: 0;
}

.ref-picker-slot-remove:hover {
  background: rgba(28, 24, 19, 0.92);
}

.ref-picker-note {
  margin: 10px 0 0;
  font-size: 11.5px;
  color: var(--ink-3);
  line-height: 1.5;
}

@media (max-width: 720px) {
  .ref-picker-radios {
    grid-template-columns: 1fr;
  }
  .ref-picker-radio-body {
    padding: 8px 10px;
  }
}

/* ─── repair-3 v2: recent-image chips on Content Improver ────────────
   Show findings + score on each recent-image card so users see at a
   glance what the Optimiser flagged before they click through. */

.recent-item {
  position: relative;
}

.recent-score-chip {
  position: absolute;
  top: 8px;
  left: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 28px;
  height: 22px;
  padding: 0 7px;
  background: rgba(28, 24, 19, 0.78);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  border-radius: 999px;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

.recent-item-finding {
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: 38px;
  display: flex;
  gap: 4px;
  pointer-events: none;
}

.recent-finding-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  font-size: 11px;
  font-weight: 600;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.92);
  color: var(--ink-2, #333);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  box-shadow: 0 1px 2px rgba(28, 24, 19, 0.12);
}

.recent-finding-critical {
  background: #fee5e0;
  color: #842712;
}

.recent-finding-warning {
  background: #fff1d6;
  color: #6b4a0d;
}

.recent-finding-info {
  background: #e4ecff;
  color: #1c3b85;
}

.recent-finding-more {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  padding: 0 4px;
  font-size: 10px;
  font-weight: 600;
  background: rgba(0, 0, 0, 0.08);
  border-radius: 6px;
}

/* ─── repair-3 v4: read-only prompt card + N-tile result grid ──────────
   The Content Improver's "Edit instructions" pane is now read-only
   (built from findings, not user-typed). The four states (empty,
   funnel, no-findings, ready) share a left-aligned card layout. */

.prompt-card-empty,
.prompt-card-funnel,
.prompt-card-no-findings {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  width: 100%;
  text-align: left;
  padding: 4px;
}
.prompt-card-empty {
  align-items: center;
  text-align: center;
}
.prompt-card-funnel a.btn {
  margin-top: 4px;
}

.prompt-card-ready {
  width: 100%;
}
.prompt-card-lead {
  margin: 0 0 12px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink-2);
  padding-bottom: 10px;
  border-bottom: 1px solid var(--rule, rgba(0, 0, 0, 0.08));
}
.prompt-card-findings {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.prompt-card-findings li {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--ink-2);
  padding-left: 14px;
  position: relative;
}
.prompt-card-findings li::before {
  content: "•";
  position: absolute;
  left: 4px;
  top: 0;
  color: var(--accent, #3a6ff8);
  font-weight: 700;
}
.prompt-card-findings li strong {
  font-weight: 600;
  color: var(--ink-1);
}

/* The Generate button is disabled when no findings are available.
   Give it a clear visual cue so the user knows why it's not clickable. */
#polish-btn:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* repair-3 v4: per-tile slop badge — same positioning as the legacy
   single-result badge but scoped inside .tile-image-wrap. */
.edit-result-tile .slop-badge {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
}

/* The tile state pill on the figcaption flips green when done,
   red when failed. Drives off inline style.color set by submitEditJob. */
.edit-result-tile .tile-state {
  font-size: 11px;
  font-weight: 500;
  text-transform: lowercase;
  letter-spacing: 0.02em;
}

/* v33 audit fix C: AI deep-check fallback notice. Shown when /analyze
   returns aiMode != "llm" — tells the user the deep-check ran on
   heuristics only (LLM provider had a hiccup) so they know to retry
   instead of trusting an incomplete panel as the "deep" result. */
.ai-fallback-notice {
  margin: 8px 0 12px;
  padding: 8px 12px;
  background: var(--surface-soft, #faf8f4);
  border: 1px solid var(--surface-tint, #e8e2d4);
  border-left: 3px solid var(--warning, #d68f2f);
  border-radius: 6px;
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--ink-3, #777);
}

/* v5: intent picker on Content Improver. Mirrors .ref-picker structure
   but with a text-only radio body (no big number); two-line layout per
   option (label + sub-text) to give the three intents room to breathe. */
.intent-picker {
  margin: var(--s-4, 16px) 0 0;
  padding: 14px 16px;
  background: var(--surface-soft);
  border: 1px solid var(--surface-tint);
  border-radius: 10px;
}

.intent-picker-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.intent-picker-title {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink-2);
  letter-spacing: -0.005em;
}

.intent-picker-sub {
  font-size: 12px;
  color: var(--ink-3);
}

.intent-picker-radios {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.intent-picker-radio {
  position: relative;
  display: block;
  cursor: pointer;
}

.intent-picker-radio input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.intent-picker-radio-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--surface-tint);
  border-radius: 8px;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.intent-picker-radio:hover .intent-picker-radio-body {
  border-color: var(--ink-4);
}

.intent-picker-radio input:checked + .intent-picker-radio-body {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.intent-picker-radio input:focus-visible + .intent-picker-radio-body {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.intent-picker-radio-label {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.2;
}

.intent-picker-radio-sub {
  font-size: 11.5px;
  color: var(--ink-3);
  line-height: 1.3;
}

/* v32 — Trend picker (only visible when Revamp intent is selected).
   Lets the user anchor the re-imagine to a specific trending topic
   instead of letting the system auto-pick the top trend. */
.trend-picker {
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px dashed var(--rule);
}
.trend-picker-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: var(--s-2);
}
.trend-picker-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}
.trend-picker-sub {
  font-size: 11.5px;
  color: var(--ink-3);
  line-height: 1.3;
}
.trend-picker-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.trend-picker-empty {
  font-size: 12px;
  color: var(--ink-3);
  font-style: italic;
  line-height: 1.4;
}
.trend-pill {
  appearance: none;
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: var(--surface);
  padding: 5px 11px;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-2);
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.trend-pill:hover {
  background: var(--surface-tint);
  border-color: var(--rule-strong);
  color: var(--ink);
}
.trend-pill.is-selected {
  background: var(--ink);
  border-color: var(--ink);
  color: #fff;
}
.trend-pill.is-auto {
  /* Subtle visual differentiation from the named pills */
  font-style: italic;
  opacity: 0.95;
}

/* v5: vision-LLM source badge inside prompt-card. Surfaces when the
   prompt was actually generated by Qwen vs the deterministic fallback
   template — lets the user trust (or distrust) what they're about to
   send to WaveSpeed. */
.prompt-source-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 10px;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.prompt-source-badge.is-vision {
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid var(--accent-line, rgba(58, 111, 248, 0.25));
}
.prompt-source-badge.is-fallback {
  background: var(--surface-soft);
  color: var(--ink-3);
  border: 1px solid var(--surface-tint);
}
.prompt-source-badge::before {
  content: "";
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

/* v5: history row image-type thumbnail. Renders the source image
   inside the icon cell when an entry has input.source_thumbnail
   (improver runs + image_analyze entries). Falls back to the
   generic camera icon when no thumb is present. */
.history-type.has-thumb {
  overflow: hidden;
  padding: 0;
  border-radius: 8px;
}
.history-type.has-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ═══════════════════════════════════════════════════════════════════
   xvo-auth modal + user menu (added v33 — billing/auth UI)
   ═══════════════════════════════════════════════════════════════════ */

.xvo-auth-modal {
  position: fixed;
  inset: 0;
  background: rgba(20, 20, 25, 0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10000;
  padding: 16px;
}

.xvo-auth-modal-card {
  background: var(--surface, #fff);
  border: 1px solid var(--surface-tint, #e8e2d4);
  border-radius: 12px;
  padding: 28px 28px 24px;
  max-width: 420px;
  width: 100%;
  position: relative;
  box-shadow: 0 18px 50px rgba(0,0,0,0.18);
}

.xvo-auth-modal-close {
  position: absolute;
  top: 12px;
  right: 14px;
  background: none;
  border: none;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  color: var(--ink-3, #777);
  padding: 4px 8px;
}
.xvo-auth-modal-close:hover { color: var(--ink, #222); }

.xvo-auth-modal-title {
  font-size: 22px;
  font-weight: 600;
  margin: 0 0 4px;
  color: var(--ink, #222);
}

.xvo-auth-modal-sub {
  font-size: 14px;
  color: var(--ink-3, #777);
  margin: 0 0 20px;
  line-height: 1.5;
}

.xvo-auth-tabs {
  display: flex;
  gap: 4px;
  margin-bottom: 18px;
  border-bottom: 1px solid var(--surface-tint, #e8e2d4);
}
.xvo-auth-tab {
  background: none;
  border: none;
  padding: 8px 14px;
  font-size: 14px;
  cursor: pointer;
  color: var(--ink-3, #777);
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.xvo-auth-tab.is-active {
  color: var(--ink, #222);
  border-bottom-color: var(--accent, #d68f2f);
  font-weight: 500;
}

.xvo-auth-form { display: flex; flex-direction: column; gap: 14px; }
.xvo-auth-label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 13px;
  color: var(--ink-2, #555);
}
.xvo-auth-label input {
  padding: 10px 12px;
  border: 1px solid var(--surface-tint, #e8e2d4);
  border-radius: 6px;
  font-size: 14px;
  font-family: inherit;
  background: var(--surface, #fff);
}
.xvo-auth-label input:focus {
  outline: none;
  border-color: var(--accent, #d68f2f);
}

.xvo-auth-error {
  color: #b13c2c;
  background: #fdecea;
  padding: 8px 12px;
  border-radius: 6px;
  font-size: 13px;
  margin: 0;
}

.xvo-auth-submit { margin-top: 4px; }

/* ── Top-nav balance pill + user menu ─────────────────────────────── */

.xvo-balance-pill {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  padding: 4px 10px;
  background: var(--surface-soft, #faf8f4);
  border: 1px solid var(--surface-tint, #e8e2d4);
  border-radius: 999px;
  font-size: 13px;
  color: var(--ink-2, #555);
  text-decoration: none;
  margin-right: 8px;
}
.xvo-balance-pill:hover { background: var(--surface-tint, #e8e2d4); }
.xvo-balance-num { font-weight: 600; color: var(--ink, #222); }
.xvo-balance-label { font-size: 11px; color: var(--ink-3, #777); }

.xvo-user-menu { position: relative; }
.xvo-user-btn {
  background: none;
  border: none;
  padding: 2px;
  cursor: pointer;
}
.xvo-user-avatar {
  display: inline-flex;
  width: 32px;
  height: 32px;
  align-items: center;
  justify-content: center;
  background: var(--accent, #d68f2f);
  color: white;
  border-radius: 50%;
  font-size: 13px;
  font-weight: 600;
}
.xvo-user-dropdown {
  position: absolute;
  top: 42px;
  right: 0;
  background: var(--surface, #fff);
  border: 1px solid var(--surface-tint, #e8e2d4);
  border-radius: 8px;
  list-style: none;
  margin: 0;
  padding: 6px;
  min-width: 200px;
  box-shadow: 0 8px 28px rgba(0,0,0,0.10);
  z-index: 1000;
}
.xvo-user-dropdown li {
  padding: 0;
}
.xvo-user-email {
  padding: 8px 12px 12px;
  border-bottom: 1px solid var(--surface-tint, #e8e2d4);
  color: var(--ink-3, #777);
  font-size: 12px;
  word-break: break-all;
}
.xvo-user-dropdown a,
.xvo-signout-btn {
  display: block;
  padding: 8px 12px;
  color: var(--ink, #222);
  text-decoration: none;
  font-size: 14px;
  border: none;
  background: none;
  width: 100%;
  text-align: left;
  cursor: pointer;
  font-family: inherit;
}
.xvo-user-dropdown a:hover,
.xvo-signout-btn:hover { background: var(--surface-soft, #faf8f4); }

/* ═══════════════════════════════════════════════════════════════════
   Billing page (frontend/billing.html)
   ═══════════════════════════════════════════════════════════════════ */

.billing-packs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0;
  margin: 2.5rem 0 1rem;
  border: 1px solid var(--ink, #1c1813);
}

/* Squared, joined cards sharing borders — reads like a spec sheet, not a
   row of floating rounded SaaS tiles. */
.billing-pack-card {
  background: var(--surface, #fff);
  border-right: 1px solid var(--ink, #1c1813);
  border-radius: 0;
  padding: 30px 26px 26px;
  position: relative;
  text-align: left;
  display: flex;
  flex-direction: column;
}
.billing-pack-card:last-child { border-right: 0; }
/* Heavy top accent bar marks the popular plan instead of a rounded outline. */
.billing-pack-card.is-popular {
  background: var(--surface-soft, #faf6ee);
  box-shadow: inset 0 5px 0 0 var(--accent, #d97a4a);
}
.billing-pack-flag {
  position: absolute;
  top: 14px;
  right: 0;
  background: var(--ink, #1c1813);
  color: var(--surface, #fff);
  font-size: 10px;
  font-weight: 600;
  padding: 4px 12px;
  border-radius: 0;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.billing-pack-name {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin: 0 0 18px;
  color: var(--ink-2, #555);
}
.billing-pack-price {
  display: flex;
  align-items: baseline;
  gap: 7px;
}
.billing-pack-price-main {
  font-size: 52px;
  line-height: 1;
  font-weight: 700;
  letter-spacing: -0.03em;
  color: var(--ink, #1c1813);
  font-variant-numeric: tabular-nums;
}
.billing-pack-price-period {
  font-size: 13px;
  color: var(--ink-3, #777);
}
.billing-pack-credits {
  margin: 16px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--rule, #e8e2d4);
  color: var(--ink, #222);
  font-size: 15px;
  font-weight: 600;
}
.billing-pack-rate {
  font-family: var(--font-display, Georgia, serif);
  font-style: italic;
  font-size: 15px;
  color: var(--accent, #d97a4a);
  margin: 6px 0 22px;
}
.billing-pack-buy {
  width: 100%;
  margin-top: auto;
  border-radius: 0;
}
.billing-pack-error {
  text-align: center;
  color: #b4452f;
  font-size: 14px;
  margin: 0 0 2.5rem;
}
@media (max-width: 720px) {
  .billing-packs { grid-template-columns: 1fr; }
  .billing-pack-card { border-right: 0; border-bottom: 1px solid var(--ink, #1c1813); }
  .billing-pack-card:last-child { border-bottom: 0; }
  .billing-pack-card.is-popular { box-shadow: inset 5px 0 0 0 var(--accent, #d97a4a); }
}
.billing-loading {
  text-align: center;
  color: var(--ink-3, #777);
  padding: 2rem;
}
.billing-notice {
  grid-column: 1 / -1;
  background: var(--surface-soft, #faf8f4);
  border: 1px solid var(--surface-tint, #e8e2d4);
  padding: 10px 14px;
  border-radius: 6px;
  font-size: 13px;
  color: var(--ink-3, #777);
  text-align: center;
}

.billing-account { margin-top: 2.5rem; }
.billing-balance-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 16px 20px;
  background: var(--surface-soft, #faf8f4);
  border: 1px solid var(--surface-tint, #e8e2d4);
  border-radius: 10px;
}
.billing-balance-label { font-size: 13px; color: var(--ink-3, #777); }
.billing-balance-value { font-size: 28px; font-weight: 700; color: var(--ink, #222); }
.billing-balance-unit { font-size: 13px; color: var(--ink-3, #777); }

.billing-history-table,
.billing-usage-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 1rem;
  font-size: 14px;
}
.billing-history-table th,
.billing-history-table td,
.billing-usage-table th,
.billing-usage-table td {
  padding: 10px 12px;
  text-align: left;
  border-bottom: 1px solid var(--surface-tint, #e8e2d4);
}
.billing-history-table th,
.billing-usage-table th {
  font-weight: 600;
  font-size: 12px;
  color: var(--ink-3, #777);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.billing-status {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 600;
}
.billing-status.is-paid { background: #d4edda; color: #155724; }
.billing-status.is-pending { background: #fff3cd; color: #856404; }
.billing-status.is-refunded { background: #d1ecf1; color: #0c5460; }
.billing-status.is-failed { background: #f8d7da; color: #721c24; }

.billing-history-empty {
  color: var(--ink-3, #777);
  font-size: 14px;
  text-align: center;
  padding: 1rem;
}

.billing-signed-out {
  text-align: center;
  padding: 3rem 0;
}

.billing-outcome { color: var(--ink-2, #555); font-size: 13px; }

/* ────────────────────────────────────────────────────────────────────────
   v5.3 — X-algorithm overlay findings.
   Appended to the existing improvements list with a small section
   divider so the X-algo signals are visually distinct from the
   weighted-scorer (signalScores) findings above them.
   ──────────────────────────────────────────────────────────────────────── */

.imp-section-head {
  list-style: none;
  margin: 1rem 0 0.5rem;
  padding: 0.5rem 0 0.25rem;
  border-top: 1px solid rgba(0, 0, 0, 0.08);
}
.imp-section-title {
  font-family: 'DM Serif Display', Georgia, serif;
  font-size: 15px;
  font-weight: 700;
  color: var(--ink-1, #15131C);
}
.imp-section-sub {
  font-size: 12px;
  color: var(--ink-3, #777);
  margin-top: 2px;
}

.imp-section-footer {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0.5rem 0.75rem;
  background: rgba(214, 143, 47, 0.06);
  border-radius: 6px;
  font-size: 13px;
  color: var(--ink-2, #555);
}
.imp-footer-label { color: var(--ink-3, #777); }
.imp-footer-bucket {
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.imp-footer-bucket.bucket-low    { background: #f8d7da; color: #721c24; }
.imp-footer-bucket.bucket-medium { background: #fff3cd; color: #856404; }
.imp-footer-bucket.bucket-high   { background: #d4edda; color: #155724; }

/* v5.3 — Paste-from-clipboard visual feedback. When a user pastes an
   image we flash the dropzone briefly so they can see where the image
   landed (because Ctrl+V doesn't visibly target anywhere otherwise). */
.is-paste-flash {
  animation: paste-flash 600ms ease-out;
}
@keyframes paste-flash {
  0%   { box-shadow: 0 0 0 0 rgba(214, 143, 47, 0.45); }
  50%  { box-shadow: 0 0 0 12px rgba(214, 143, 47, 0.18); }
  100% { box-shadow: 0 0 0 0 rgba(214, 143, 47, 0); }
}

/* ────────────────────────────────────────────────────────────────────────
   v5.3.1 — HEIC/RAW unrenderable file placeholders.
   Browsers can't render HEIC or camera RAW in <img>, so for those formats
   we show a friendly card with the filename + a note. The backend still
   decodes them via pillow-heif; this is purely a preview UX fix.
   ──────────────────────────────────────────────────────────────────────── */

.dropzone-file-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 2rem 1rem;
  color: var(--ink-2, #555);
  text-align: center;
}
.dropzone-file-card svg {
  color: var(--ink-3, #888);
  opacity: 0.7;
}
.dropzone-file-name {
  font-family: 'DM Sans', sans-serif;
  font-weight: 600;
  font-size: 14px;
  color: var(--ink-1, #15131C);
  word-break: break-all;
  max-width: 280px;
}
.dropzone-file-note {
  font-size: 12px;
  color: var(--ink-3, #888);
  max-width: 320px;
  line-height: 1.4;
}

.canvas-no-preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 3rem 1.5rem;
  background: var(--surface-soft, #f5f3ef);
  border-radius: var(--r-3, 6px);
  text-align: center;
  min-height: 200px;
}
.canvas-no-preview svg {
  color: var(--ink-3, #888);
  opacity: 0.5;
}
.canvas-no-preview-title {
  font-family: 'DM Sans', sans-serif;
  font-weight: 600;
  font-size: 14px;
  color: var(--ink-1, #15131C);
  margin: 0;
  word-break: break-all;
  max-width: 280px;
}
.canvas-no-preview-sub {
  font-size: 13px;
  color: var(--ink-3, #888);
  max-width: 360px;
  line-height: 1.5;
  margin: 0;
}

/* Analysis-failed error row in the findings list. Distinct from regular
   findings so the user can see at a glance that something went wrong. */
.finding-error {
  background: rgba(220, 38, 38, 0.06);
  border-left: 3px solid #b91c1c;
  color: #7f1d1d;
  padding: 0.75rem 1rem;
  border-radius: 4px;
  font-size: 13px;
  line-height: 1.5;
}
.finding-error strong { color: #7f1d1d; }

/* ────────────────────────────────────────────────────────────────────────
   v5.3.4 — graceful fallback for legacy localStorage entries whose
   stored dataUrl can't be rendered (e.g. raw HEIC bytes stored before
   v5.3.3 added the JPEG-preview pipeline).
   ──────────────────────────────────────────────────────────────────────── */

.recent-item-broken {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  background: var(--surface-soft, #f5f3ef);
  color: var(--ink-3, #888);
  gap: 0.35rem;
  padding: 0.5rem;
  text-align: center;
}
.recent-item-broken svg { opacity: 0.5; }
.recent-item-broken-label {
  font-size: 11px;
  line-height: 1.3;
  font-family: 'DM Sans', sans-serif;
}

/* ──────────────────────────────────────────────────────────────────────
 * v18 — Rolling virality badges
 *
 * Shown on trend cards next to the handle to signal freshness/momentum.
 *
 *   .viral-badge-new       Brand-new breakout (<6h old)
 *   .viral-badge-trending  Still gaining after the freshness window
 *
 * Both are intentionally subtle so they don't dominate the card. The
 * "new" badge gets a slight accent tint; "still trending" stays neutral
 * (it's informational, not promotional).
 * ──────────────────────────────────────────────────────────────────── */
.viral-badge {
  display: inline-flex;
  align-items: center;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 2px 7px;
  border-radius: 999px;
  margin-left: 6px;
  line-height: 1.4;
  font-family: 'DM Sans', sans-serif;
  white-space: nowrap;
}
.viral-badge-new {
  background: rgba(99, 102, 241, 0.12);
  color: rgb(79, 70, 229);
}
.viral-badge-trending {
  background: rgba(0, 0, 0, 0.045);
  color: rgba(0, 0, 0, 0.6);
}

/* v20 — surface axis "reason" inline when the axis scored ≤10. Without
   this, low-scoring axes look like a broken UI element; with it, the user
   sees exactly which calibrated signal didn't fire (e.g. "no proper nouns
   / numbers" for Specificity = 5). */
.subscore-reason {
  display: block;
  margin-top: 3px;
  font-size: 11px;
  font-weight: 400;
  font-family: 'DM Sans', sans-serif;
  color: rgba(0, 0, 0, 0.5);
  font-style: italic;
  letter-spacing: 0.005em;
}

/* v20 — HEIC upload "uploaded successfully, can't preview" placeholder.
   Was visually identical to the empty-state placeholder, which looked
   like the upload had failed. Green tick + soft success background
   reassures the user the file is there and ready for /analyze-image. */
.image-preview-heic-ok {
  background: rgba(74, 140, 74, 0.06);      /* soft success */
  border: 1.5px dashed rgba(74, 140, 74, 0.35);
  position: relative;
}
.image-preview-heic-ok::before {
  /* Inline SVG green tick, top-right of the placeholder */
  content: "";
  position: absolute;
  top: 12px;
  right: 12px;
  width: 22px;
  height: 22px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%234a8c4a' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='20 6 9 17 4 12'/></svg>");
  background-repeat: no-repeat;
  background-size: contain;
  pointer-events: none;
}
.image-preview-heic-ok .image-preview-heic-label {
  color: rgb(58, 110, 58);
  font-weight: 500;
}

/* ─── v23 — aging indicator badges ─────────────────────────────────────
 *
 * Two new badges complement the v18 "new" and "still trending Nh" ones:
 *
 *   .viral-badge-climbing  — acceleration positive (post is breaking out)
 *   .viral-badge-cooling   — decay_score high (post is plateauing/dying)
 *
 * These reuse the existing .viral-badge base styles (defined above in the
 * v18 block) so they slot in without layout shifts. trends.js can opt-in
 * to rendering them by reading post.acceleration and post.decay_score
 * from /trends/feed responses — both fields are wired in v23.
 */
.viral-badge-climbing {
  background: rgba(33, 150, 243, 0.12);
  color: rgb(21, 101, 192);
  border: 1px solid rgba(33, 150, 243, 0.2);
  font-weight: 600;
}
.viral-badge-cooling {
  background: rgba(120, 120, 120, 0.08);
  color: rgba(80, 80, 80, 0.85);
  border: 1px solid rgba(120, 120, 120, 0.15);
}

/* ─── v23 — trend-card transitions ────────────────────────────────────
 *
 * The existing trends.js renders cards via renderImageTrends /
 * renderTextExamples. v23 keeps those renderers untouched (a full
 * DOM-diff rewrite would risk regressing the live-pill, sort, and
 * pagination wiring) but ADDS lightweight transitions so the eventual
 * smooth-update wiring in a future release can layer cleanly on top.
 *
 * Effect today: when the polling cycle re-renders the grid, the new
 * cards fade in instead of popping. Old cards (about to be replaced)
 * have no exit animation yet — keeping the change scoped to additive
 * CSS rules only.
 */
.live-trend-card,
.trend-card {
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.live-trend-card.is-entering,
.trend-card.is-entering {
  opacity: 0;
  transform: translateY(-6px);
}
.live-trend-card.is-leaving,
.trend-card.is-leaving {
  opacity: 0;
  transform: translateY(6px);
  pointer-events: none;
}

/* ─── v34 — trend card preview modal + image-tile skeleton shimmer ────── */

#trend-modal-root {
  position: fixed; inset: 0;
  z-index: 1000;
  display: none;
  pointer-events: none;
}
#trend-modal-root.is-open { display: block; pointer-events: auto; }

.trend-modal-overlay {
  position: absolute; inset: 0;
  background: rgba(28, 24, 20, 0.78);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  display: flex; align-items: center; justify-content: center;
  padding: 32px;
  opacity: 0;
  animation: trendModalFadeIn 0.18s ease forwards;
}
@keyframes trendModalFadeIn { to { opacity: 1; } }

.trend-modal-card {
  background: var(--surface);
  border-radius: var(--r-3);
  max-width: 720px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  position: relative;
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.3);
}

.trend-modal-close {
  position: absolute;
  top: 12px; right: 12px;
  width: 32px; height: 32px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--rule);
  font-size: 20px; line-height: 1;
  cursor: pointer;
  z-index: 10;
  color: var(--ink-2);
  display: flex; align-items: center; justify-content: center;
}
.trend-modal-close:hover {
  background: var(--surface-soft);
  color: var(--ink-1);
}

.trend-modal-body { display: flex; flex-direction: column; }
.trend-modal-image-wrap { background: var(--surface-soft); }
.trend-modal-image-wrap img {
  width: 100%; height: auto; max-height: 60vh;
  object-fit: contain; display: block;
}

.trend-modal-content { padding: 24px; }
.trend-modal-author {
  font-size: 13px; font-weight: 500;
  color: var(--ink-2); margin-bottom: 8px;
}
.trend-modal-caption {
  font-size: 15px; line-height: 1.5;
  color: var(--ink-1); margin-bottom: 16px;
  white-space: pre-wrap; word-wrap: break-word;
}
.trend-modal-stats {
  display: flex; gap: 16px; flex-wrap: wrap;
  font-size: 13px; color: var(--ink-3);
  padding: 12px 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  margin-bottom: 16px;
}
.trend-modal-actions {
  display: flex; gap: 8px; flex-wrap: wrap;
}

/* Hint that viral cards are clickable */
.viral-card, .text-post {
  cursor: pointer;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.viral-card:hover, .text-post:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(28, 24, 20, 0.08);
}

@media (max-width: 720px) {
  .trend-modal-overlay { padding: 0; }
  .trend-modal-card { max-height: 100vh; border-radius: 0; }
  .trend-modal-image-wrap img { max-height: 50vh; }
}

/* v34 — skeleton shimmer for tile-result while WaveSpeed job runs.
   PATCH 1 puts `tile-result-pending` on the <img>; PATCH 1b removes
   the class once resultImg.src is assigned. */
.tile-result-pending {
  background: linear-gradient(
    90deg,
    var(--surface-soft) 0%,
    var(--surface) 50%,
    var(--surface-soft) 100%
  );
  background-size: 200% 100%;
  animation: tileShimmer 1.4s ease-in-out infinite;
}
@keyframes tileShimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* ─── v47 — Account unlocks banner ───────────────────────────────────
 *
 * Renders below the rewrites grid for guests so they understand what
 * an account gets them: rewrite ratings, cross-device history, sticky
 * credits. Editorial dark theme — warm tobacco, Instrument Serif
 * italic accent, no shadcn, no gradients, no indigo.
 *
 * Removed via the xvo:auth-changed listener once the user signs in.
 * ─────────────────────────────────────────────────────────────────── */
.xvo-account-unlocks {
  margin-top: var(--s-5);
  padding: var(--s-5) var(--s-5);
  background: var(--surface-soft, rgba(236, 225, 203, 0.04));
  border: 1px solid var(--rule-strong, rgba(236, 225, 203, 0.18));
  border-radius: var(--r-3, 12px);
}
.xvo-account-unlocks-title {
  margin: 0 0 var(--s-3, 12px);
  font-family: "Instrument Serif", Georgia, serif;
  font-weight: 400;
  font-size: 22px;
  line-height: 1.2;
  color: var(--ink, #ece1cb);
}
.xvo-account-unlocks-title em {
  font-style: italic;
  color: var(--ember, #d97a4a);
}
.xvo-account-unlocks-list {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--s-4, 16px);
  display: grid;
  gap: var(--s-2, 8px);
}
.xvo-account-unlocks-list li {
  position: relative;
  padding-left: 20px;
  font-size: 14px;
  color: var(--ink-2, rgba(236, 225, 203, 0.78));
  line-height: 1.55;
}
.xvo-account-unlocks-list li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 8px;
  width: 7px;
  height: 7px;
  border-radius: 9999px;
  background: var(--ember, #d97a4a);
  opacity: 0.85;
}
.xvo-account-unlocks-list strong {
  color: var(--ink, #ece1cb);
  font-weight: 600;
}
.xvo-account-unlocks-actions {
  display: flex;
  gap: var(--s-3, 12px);
  flex-wrap: wrap;
  align-items: center;
}

/* ─── v50 / Phase C3 — Authenticity-lock notice ──────────────────────
 *
 * Renders on the Content Improver prompt card when the backend
 * auto-locked intent to Preserve because the source's authenticity
 * score is > 75. Quiet but explicit — the user picked Revamp / Refresh
 * and we overrode them; they deserve to know why and that they can
 * disable the auto-lock from settings.
 *
 * Same editorial dark theme — warm tobacco. No bright reds (this is
 * a guardrail, not an error), no shadcn, no gradients.
 * ─────────────────────────────────────────────────────────────────── */
.prompt-intent-lock {
  display: block;
  margin: var(--s-3, 12px) 0 var(--s-2, 8px);
  padding: var(--s-3, 12px) var(--s-4, 16px);
  background: rgba(217, 122, 74, 0.10);
  border: 1px solid rgba(217, 122, 74, 0.32);
  border-radius: var(--r-2, 8px);
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink-2, rgba(236, 225, 203, 0.82));
}
.prompt-intent-lock strong {
  color: var(--ember, #d97a4a);
  font-weight: 700;
}
.prompt-intent-lock em {
  font-style: italic;
  color: var(--ink-3, rgba(236, 225, 203, 0.6));
  font-size: 12px;
  display: block;
  margin-top: var(--s-1, 4px);
}

/* v73 — Niche vibe picker on the improver page. Mirrors .trend-picker
   but uses a native <select> instead of pills because the niche list
   is short, controlled, and not visually driven. */
.niche-picker {
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px dashed var(--rule);
}
.niche-picker-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: var(--s-2);
}
.niche-picker-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}
.niche-picker-optional {
  font-weight: 400;
  color: var(--ink-3);
  font-size: 11px;
  letter-spacing: 0.04em;
}
.niche-picker-sub {
  font-size: 11px;
  color: var(--ink-3);
  line-height: 1.4;
}
.niche-picker-select {
  font: inherit;
  font-size: 13px;
  margin-top: var(--s-2);
  padding: 8px 10px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 6px;
  color: var(--ink);
  width: 100%;
  cursor: pointer;
}
.niche-picker-select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.niche-picker-state {
  display: block;
  margin-top: 4px;
  font-size: 11px;
  color: var(--ink-3);
  min-height: 14px;
}
.niche-picker-state.is-stale { color: var(--ember, #d97a4a); }
.niche-picker-state.is-fresh { color: var(--ink-2); }

/* v74 — Cookie / device-ID consent modal. Sits on top of the page on
   first visit; required before the anon trial endpoints work. Honest
   styling: editorial dark, no manipulative "free" green shouting. */
.xvo-consent-modal {
  position: fixed;
  inset: 0;
  background: rgba(28, 24, 19, 0.7);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
  padding: 24px;
}
.xvo-consent-card {
  background: var(--surface, #ece1cb);
  color: var(--ink, #1c1813);
  border: 1px solid var(--rule, rgba(28, 24, 19, 0.15));
  border-radius: 10px;
  padding: 24px 28px;
  max-width: 460px;
  width: 100%;
  box-shadow: 0 18px 48px rgba(28, 24, 19, 0.35);
}
.xvo-consent-title {
  font-family: 'Instrument Serif', Georgia, serif;
  font-style: italic;
  font-size: 28px;
  margin: 0 0 12px;
  color: var(--ink, #1c1813);
}
.xvo-consent-body {
  font-size: 14px;
  line-height: 1.55;
  color: var(--ink-2, #4d4439);
  margin: 0 0 10px;
}
.xvo-consent-body-sub {
  color: var(--ink-3, #7d7062);
  font-size: 13px;
}
.xvo-consent-actions {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-top: 18px;
}
.xvo-consent-accept {
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  background: var(--ember, #d97a4a);
  color: #fff;
  border: 0;
  border-radius: 6px;
  padding: 10px 16px;
  cursor: pointer;
}
.xvo-consent-accept:hover { background: #c46836; }
.xvo-consent-accept:focus-visible {
  outline: 2px solid var(--amber, #e3a857);
  outline-offset: 2px;
}
.xvo-consent-signup {
  font-size: 13px;
  color: var(--ink-2, #4d4439);
  text-decoration: underline;
  text-decoration-color: rgba(28, 24, 19, 0.3);
  text-underline-offset: 3px;
}
.xvo-consent-signup:hover {
  color: var(--ember, #d97a4a);
  text-decoration-color: var(--ember, #d97a4a);
}

/* ────────────────────────────────────────────────────────────────────────
   v83 — Trend Studio prompt input (Content Improver page)
   Matches the .niche-picker styling so the form feels like one unit.
   ──────────────────────────────────────────────────────────────────────── */
.trend-prompt-wrap {
  margin-top: var(--s-3);
  padding-top: var(--s-3);
  border-top: 1px dashed var(--rule);
}
.trend-prompt-label {
  display: block;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 4px;
}
.trend-prompt-optional {
  font-weight: 400;
  color: var(--ink-3);
  font-size: 11px;
  letter-spacing: 0.04em;
}
.trend-prompt-input {
  display: block;
  width: 100%;
  font: inherit;
  font-size: 13px;
  padding: 8px 10px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 6px;
  color: var(--ink);
  resize: vertical;
  min-height: 44px;
  max-height: 120px;
}
.trend-prompt-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.trend-prompt-hint {
  display: block;
  margin-top: 6px;
  font-size: 11px;
  color: var(--ink-3);
  line-height: 1.4;
}

/* ────────────────────────────────────────────────────────────────────────
   v85 — Saved Models (Content Improver / Trend Studio left card)
   Replaces the v83 "Recent images" XvoHistory rendering with a URL-based
   list (XvoSavedModels). Styles match the existing .niche-picker and
   .trend-prompt-* language so the form feels like one unit.
   ──────────────────────────────────────────────────────────────────────── */

/* The grid + tile styles for saved models reuse .recent-grid /
   .recent-tile from v83 — same dimensions, same hover. We just add
   the delete-button overlay + name caption styling. */
.saved-model-tile {
  position: relative;
}
.saved-model-tile-name {
  display: block;
  margin-top: 6px;
  font-size: 11px;
  line-height: 1.3;
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.saved-model-tile-delete {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 4px;
  background: rgba(28, 24, 19, 0.75);
  color: var(--ink-2);
  cursor: pointer;
  font: inherit;
  font-size: 14px;
  line-height: 1;
  opacity: 0;
  transition: opacity 120ms ease, background 120ms ease;
}
.saved-model-tile:hover .saved-model-tile-delete,
.saved-model-tile:focus-within .saved-model-tile-delete {
  opacity: 1;
}
.saved-model-tile-delete:hover {
  background: rgba(217, 122, 74, 0.9);
  color: var(--page);
}

/* Empty state — shown when XvoSavedModels.list() is empty.
 * v89.1: shed the dashed-border + grey-text look. Now matches the
 * editorial card style — soft warm background, serif italic title,
 * proper breathing room. The two paths below (Upload, paste URL) are
 * referenced from the body copy so the empty state itself doubles as
 * an onboarding hint rather than just a placeholder. */
.saved-models-empty {
  grid-column: 1 / -1;
  padding: var(--s-5) var(--s-4);
  text-align: center;
  font-size: 13px;
  line-height: 1.55;
  color: var(--ink-3);
  background: var(--surface-soft);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
}
.saved-models-empty-title {
  display: block;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 20px;
  line-height: 1.2;
  color: var(--ink);
  margin-bottom: var(--s-2);
}

/* Add-by-URL section */
/* v89.1 — URL paste form. De-emphasised as the secondary path (primary
 * is the dropzone above). Sits below the "or paste a URL" divider, so
 * no title is needed — the divider speaks for it. Compact spacing,
 * ghost-style Add button, refined focus state. */
.saved-models-add {
  margin: 0;
  padding: 0;
  border-top: none;          /* divider does this work now */
}
.saved-models-add-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: var(--s-2);
}
.saved-models-add-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}
.saved-models-add-sub {
  font-size: 11px;
  color: var(--ink-3);
  line-height: 1.5;
}
.saved-models-add-row {
  display: flex;
  gap: var(--s-2);
  margin-bottom: var(--s-2);
}
.saved-models-add-input {
  flex: 1;
  font: inherit;
  font-size: 13px;
  padding: 9px 12px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-1);
  color: var(--ink);
  min-width: 0;            /* prevents flex overflow */
  transition: border-color var(--t-fast) var(--ease),
              box-shadow var(--t-fast) var(--ease);
}
.saved-models-add-input::placeholder {
  color: var(--ink-4);
}
.saved-models-add-input.saved-models-add-name {
  width: 100%;
  display: block;
}
.saved-models-add-input:focus-visible,
.saved-models-add-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.saved-models-add-btn {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  padding: 9px 16px;
  background: var(--accent);
  color: #FFFFFF;
  border: 1px solid var(--accent);
  border-radius: var(--r-1);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease);
}
.saved-models-add-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.saved-models-add-btn:not(:disabled):hover {
  background: var(--primary);
  border-color: var(--primary);
}
.saved-models-add-btn:not(:disabled):active {
  transform: translateY(1px);
}
/* Ghost variant — used for the Add-by-URL button, since it's the
 * secondary path. Filled treatment is reserved for the primary
 * Save-as-character action that converts a one-off upload into a
 * persistent character. */
.saved-models-add-btn-ghost {
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--rule-strong);
}
.saved-models-add-btn-ghost:not(:disabled):hover {
  background: var(--surface-tint);
  border-color: var(--ink-3);
  color: var(--ink);
}
.saved-models-add-error {
  margin: 4px 0 0;
  font-size: 11px;
  color: var(--danger, #b54242);
  line-height: 1.5;
}

/* v89.1 — Save-as-character contextual panel. Hidden by default;
 * unhidden by improver.js when a file is dropped into the upload
 * zone above. Sits visually directly under the dropzone (no
 * divider between them) so the user reads it as a continuation
 * of the upload action. Warm surface emphasises that this is the
 * resulting action, not another option. */
.save-as-character {
  margin-top: var(--s-3);
  padding: var(--s-3);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  box-shadow: var(--shadow-sm);
}
.save-as-character-head {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin-bottom: var(--s-3);
}
.save-as-character-title {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 18px;
  line-height: 1.2;
  color: var(--ink);
}
.save-as-character-sub {
  font-size: 12px;
  color: var(--ink-3);
  line-height: 1.5;
}
.save-as-character .saved-models-add-input {
  margin-bottom: var(--s-2);
}
.save-as-character .saved-models-add-btn {
  width: 100%;
  padding: 10px 16px;
}

/* "Save as model" pill on output tiles */
.edit-result-save-model {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  padding: 4px 10px;
  background: var(--surface);
  color: var(--ink-2);
  border: 1px solid var(--rule);
  border-radius: 999px;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease;
}
.edit-result-save-model:hover {
  background: var(--ember);
  color: var(--page);
  border-color: var(--ember);
}
.edit-result-save-model[disabled],
.edit-result-save-model.is-saved {
  opacity: 0.7;
  cursor: default;
}
.edit-result-save-model.is-saved::before {
  content: "✓";
  margin-right: 2px;
}

/* ────────────────────────────────────────────────────────────────────────
   v86 — Replicate Your Trend banner + quick-suggestion chips
   Banner appears when the user arrived from trends.html "Improve in editor"
   on a specific card; chips appear above the guidance textarea always.
   Both use the existing tobacco palette tokens.
   ──────────────────────────────────────────────────────────────────────── */

/* Replicate banner — shown on lock, dismissable */
.replicate-banner {
  margin-bottom: var(--s-3);
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--ember);
  border-radius: 8px;
  position: relative;
}
.replicate-banner-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 8px;
}
.replicate-banner-eyebrow {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ember);
}
.replicate-banner-clear {
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  padding: 3px 8px;
  background: transparent;
  color: var(--ink-3);
  border: 1px solid var(--rule);
  border-radius: 4px;
  cursor: pointer;
}
.replicate-banner-clear:hover {
  background: var(--rule);
  color: var(--ink);
}
.replicate-banner-body {
  display: flex;
  gap: 12px;
  align-items: flex-start;
}
.replicate-banner-thumb {
  width: 64px;
  height: 64px;
  object-fit: cover;
  border-radius: 6px;
  flex-shrink: 0;
  background: var(--page);
}
.replicate-banner-meta {
  flex: 1;
  min-width: 0;   /* prevent flex overflow */
}
.replicate-banner-caption {
  margin: 0 0 4px;
  font-size: 13px;
  line-height: 1.4;
  color: var(--ink);
  /* clamp to two lines */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.replicate-banner-hint {
  margin: 0;
  font-size: 11px;
  color: var(--ink-3);
  line-height: 1.4;
}

/* Quick-suggestion chips */
.quick-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: var(--s-2);
  margin-bottom: var(--s-2);
}
.quick-chips:empty {
  display: none;   /* no chips for the current niche → hide the row entirely */
}
.quick-chip {
  font: inherit;
  font-size: 11px;
  font-weight: 500;
  padding: 4px 10px;
  background: var(--surface);
  color: var(--ink-2);
  border: 1px solid var(--rule);
  border-radius: 999px;
  cursor: pointer;
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
  white-space: nowrap;
}
.quick-chip:hover {
  background: var(--ember);
  color: var(--page);
  border-color: var(--ember);
}
.quick-chip:active {
  transform: translateY(1px);
}

/* ────────────────────────────────────────────────────────────────────────
   v87 — Save-as-character panel under the dropzone
   Appears after a file is loaded; gives the user a one-click path to
   upload it to Supabase Storage and add to Saved Models. No URLs.
   ──────────────────────────────────────────────────────────────────────── */
.save-as-character {
  margin-top: var(--s-2);
  padding: 12px;
  background: var(--surface);
  border: 1px solid var(--ember);
  border-radius: 8px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.save-as-character-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.save-as-character-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}
.save-as-character-sub {
  font-size: 11px;
  color: var(--ink-3);
  line-height: 1.4;
}
.save-as-character button.saved-models-add-btn {
  width: 100%;
}
