:root {
  --bg: #f5f6f8;
  --surface: #ffffff;
  --border: #e2e5ea;
  --text: #16191d;
  /* One step darker than #6b7280 so 11px muted metadata (.feedback-thanks, card subtitles)
     clears contrast on the light surface. The dark theme keeps its own lighter --muted. */
  --muted: #5b616b;
  --accent: #b3121a;
  --accent-soft: #fdeceb;
  --ok: #0f7a4a;
  --warn: #9a5b00;
  --warn-soft: #fff4e0;
  --radius: 10px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #14161a;
    --surface: #1c1f25;
    --border: #2c313a;
    --text: #e8eaed;
    --muted: #9aa1ac;
    --accent: #ef4b52;
    --accent-soft: #33191b;
    --ok: #4ac68b;
    --warn: #e0a44a;
    --warn-soft: #33291a;
  }
}

* { box-sizing: border-box; }

/* The [hidden] UA rule is display:none at near-zero specificity, so any class that
   sets display (.login-view is display:grid) silently defeats it. Without this the
   login screen stays on top of the app after a successful sign-in. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

.muted { color: var(--muted); }
.error { color: var(--accent); font-size: 14px; }
.empty { text-align: center; padding: 32px; }

/* ---------- buttons ---------- */
.btn {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  padding: 8px 14px;
  border-radius: var(--radius);
  font-size: 14px;
  cursor: pointer;
}
.btn:hover:not(:disabled) { border-color: var(--muted); }
.btn:disabled { opacity: .55; cursor: not-allowed; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.ghost { background: transparent; }
/* Red text alone was not enough of a signal, because red is also this app's brand colour
   (--accent is the active tab, the primary button, the pill). A danger button needs to
   read as different in kind, not merely tinted, so it takes the border too.
   `margin-inline-start: auto` is the other half and matters more: it pushes the
   destructive action to the far end of the action row at every viewport, so it can never
   end up adjacent to - or on a phone, directly underneath - the safe button next to it.
   Spatial separation survives wrapping; a confirm dialog does not survive being clicked
   through. */
.btn.danger {
  color: var(--accent);
  border-color: var(--accent);
  margin-inline-start: auto;
}
.btn.small { padding: 5px 10px; font-size: 13px; }
.btn.icon-only { display: inline-grid; place-items: center; padding: 5px; }
.trash-icon { width: 18px; height: 18px; }

/* ---------- login ---------- */
.login-view {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 20px;
}
.login-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 32px;
  width: min(380px, 100%);
  display: grid;
  gap: 12px;
}
.login-card h1 { margin: 0; font-size: 22px; }
.login-card p { margin: 0; }
.login-card input {
  padding: 11px 13px;
  font-size: 17px;
  letter-spacing: 3px;
  text-align: center;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text);
}

/* ---------- shell ---------- */
.app { max-width: 1040px; margin: 0 auto; padding: 0 16px 48px; }

.topbar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 0;
  flex-wrap: wrap;
}
.brand { font-weight: 650; font-size: 17px; }
.pill {
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 11px;
  padding: 2px 7px;
  border-radius: 999px;
  vertical-align: middle;
  text-transform: uppercase;
  letter-spacing: .5px;
}
.topbar > #logout-btn { margin-inline-start: auto; }

/* Alerts, counts, then inventory: the order is the operating priority rather than the
   order fields happen to arrive from /api/stats. This entire surface is admin-only; the
   public shell has no .stats host. */
.stats {
  order: 3;
  flex: 0 0 100%;
  min-width: 0;
  display: grid;
  gap: 10px;
  font-size: 13px;
  color: var(--muted);
}
.stats-alerts { display: grid; gap: 6px; }
.stats-alert {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
  min-width: 0;
  padding: 7px 10px;
  border: 1px solid var(--warn);
  border-radius: var(--radius);
  background: var(--warn-soft);
  color: var(--warn);
}
.stats-alert-icon { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; }
.stats-alert-message { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The hint is an <a>, which otherwise falls back to the browser's default link colour
   (blue/purple) - unreadable on the dark amber banner while the rest of the alert reads in
   var(--warn). Match it, underline it so it still says "link", and pin LTR since the label
   ("check the page") is English even when a vendor name beside it is Arabic. */
.stats-alert-hint {
  font-size: 12px;
  white-space: nowrap;
  direction: ltr;
  color: var(--warn);
  text-decoration: underline;
}
a.stats-alert-hint:hover { text-decoration: none; }
.stat-tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
.stat-tile {
  min-width: 0;
  display: grid;
  gap: 1px;
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--muted);
  font: inherit;
  text-align: start;
}
.stat-tile-link { cursor: pointer; }
.stat-tile-link:hover { border-color: var(--warn); }
.stat-tile-link:focus-visible { outline: 2px solid var(--warn); outline-offset: 2px; }
.stat-tile-link .stat-number { color: var(--warn); }
.stat-number { color: var(--text); font-size: 20px; line-height: 1.15; font-variant-numeric: tabular-nums; }

/* The scraper heartbeat: always present while the poller is on, quiet by design. It sits
   between the red alert cards (problems only) and the catalog tiles, so a healthy poller
   still has one line proving it ran. */
.scraper-status {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  font-size: 12px;
  color: var(--muted);
}
.scraper-status-label {
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .4px;
  font-size: 11px;
}
.vendor-details { padding-top: 2px; border-top: 1px solid var(--border); }
.vendor-details summary { width: fit-content; padding-top: 5px; color: var(--muted); cursor: pointer; }
.vendor-total { margin-inline-start: 4px; font-variant-numeric: tabular-nums; opacity: .7; }
.vendor-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 6px 12px;
  padding: 9px 0 2px;
}
.vendor-item { display: inline-flex; align-items: center; gap: 6px; min-width: 0; color: var(--text); }
.vendor-branches {
  flex: 0 0 auto;
  padding: 1px 6px;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--muted);
  font-size: 11px;
}

.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin-bottom: 20px; }
.tab {
  border: none;
  background: none;
  color: var(--muted);
  padding: 10px 16px;
  font-size: 15px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.tab.active { color: var(--text); border-bottom-color: var(--accent); font-weight: 600; }

.tab-panel { display: none; }
.tab-panel.active { display: block; }

/* ---------- analytics (default tab) ---------- */
/* Cards reuse .stat-tile; the row wraps instead of the header's fixed 4 columns because it
   carries seven small numbers, not four. */
.analytics-cards { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); margin-bottom: 22px; }
.analytics-up .stat-number { color: var(--ok); }
.analytics-down .stat-number { color: var(--accent); }
.analytics-trend { margin-bottom: 22px; }
.analytics-trend-title {
  margin: 0 0 10px;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .4px;
  color: var(--muted);
}
/* Fixed-height plot the bars grow inside, so heights read as a real proportion of the peak. */
.analytics-bars {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  height: 120px;
  padding: 4px 0;
}
.analytics-bar-col {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  height: 100%;
  gap: 4px;
}
.analytics-bar-count { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.analytics-bar {
  width: 100%;
  max-width: 40px;
  background: var(--accent);
  border-radius: 4px 4px 0 0;
  min-height: 2px;
}
.analytics-bar.empty { background: var(--border); }
.analytics-bar-label { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.analytics-theme-list {
  margin: 0;
  padding-inline-start: 20px;
  display: grid;
  gap: 4px;
}
.analytics-theme-list li { color: var(--text); }
.analytics-theme-term { margin-inline-end: 8px; }
.analytics-theme-count {
  padding: 1px 7px;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

/* ---------- poll now ---------- */
.images-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}
.poll-result { font-size: 13px; color: var(--muted); display: grid; gap: 6px; min-width: 0; }
.poll-result.bad { color: var(--accent); }
.poll-result-line { overflow-wrap: anywhere; }
.poll-result-errors { margin: 0; padding-inline-start: 18px; display: grid; gap: 2px; }
.poll-result-errors li { color: var(--accent); overflow-wrap: anywhere; }

/* ---------- upload ---------- */
.dropzone {
  display: grid;
  gap: 4px;
  place-items: center;
  padding: 30px;
  border: 1.5px dashed var(--border);
  border-radius: 12px;
  background: var(--surface);
  cursor: pointer;
  text-align: center;
}
.dropzone .small { font-size: 12px; }
.dropzone.dragover { border-color: var(--accent); background: var(--accent-soft); }
.feedback { margin: 12px 0; font-size: 14px; }
.feedback .bad { color: var(--accent); }

.progress {
  height: 6px;
  border-radius: 999px;
  background: var(--border);
  overflow: hidden;
  margin-bottom: 6px;
}
.progress-bar {
  height: 100%;
  background: var(--accent);
  transition: width .2s ease;
}
.progress-label { font-size: 13px; color: var(--muted); }

/* ---------- image cards ---------- */
.image-list { display: grid; gap: 12px; margin-top: 18px; }
.image-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
}
.image-head { display: flex; gap: 14px; padding: 12px; align-items: center; }
.thumb {
  width: 58px;
  height: 78px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--border);
  background: var(--bg);
  flex-shrink: 0;
  cursor: zoom-in;
}
.thumb:hover { border-color: var(--accent); }
.image-meta { flex: 1; min-width: 0; }
.image-title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.image-sub { font-size: 13px; color: var(--muted); margin-top: 2px; }
.image-actions { display: flex; gap: 6px; flex-shrink: 0; }

/* The inline end-date form that replaced the window.prompt() on "Confirm dates". Sits under
   the card head, reusing the review card's field styling. A hairline separates it from the
   card above so it reads as a drawer the button opened, not a permanent row. */
.date-confirm {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border-top: 1px solid var(--border);
  background: color-mix(in srgb, var(--surface) 60%, transparent);
}
.date-confirm-hint { font-size: 12px; }
.date-confirm .review-error { flex-basis: 100%; margin: 0; }

.badge {
  display: inline-block;
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--border);
}
.badge.done { color: var(--ok); border-color: currentColor; }
.badge.pending { color: var(--muted); }
.badge.failed { color: var(--accent); border-color: currentColor; }
.badge.review { color: var(--warn); border-color: currentColor; background: var(--warn-soft); }

/* ---------- offers table ---------- */
.offers { border-top: 1px solid var(--border); padding: 4px 12px 12px; overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 14px; min-width: 560px; }
th {
  text-align: left;
  font-weight: 600;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .4px;
  color: var(--muted);
  padding: 8px 6px;
}
td { padding: 7px 6px; border-top: 1px solid var(--border); vertical-align: middle; }
tr.flagged { background: var(--warn-soft); }
.ar { direction: rtl; text-align: right; unicode-bidi: isolate; }
.price-input {
  width: 74px;
  padding: 4px 6px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
}
.reason { font-size: 12px; color: var(--warn); }
.was { text-decoration: line-through; color: var(--muted); }

/* ---------- chat ---------- */
.messages {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px;
  min-height: 340px;
  max-height: 58vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.msg {
  max-width: 80%;
  padding: 9px 13px;
  border-radius: 12px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  /* Alignment follows the bubble's own dir="auto", so a mixed-language answer reads
     correctly without the whole bubble being forced one way. */
  text-align: start;
}
.msg.user { align-self: flex-end; background: var(--accent); color: #fff; border-bottom-right-radius: 3px; }
.msg.bot { align-self: flex-start; background: var(--bg); border: 1px solid var(--border); border-bottom-left-radius: 3px; }
.msg.typing { color: var(--muted); font-style: italic; }
/* Source links sit under the answer they belong to, aligned with the bot bubble. */
.sources { display: flex; flex-wrap: wrap; gap: 6px; align-self: flex-start; margin: -4px 0 2px; }
.source-chip {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  padding: 4px 10px;
  border-radius: 999px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.source-chip:hover { color: var(--text); border-color: var(--accent); }

/* Thumbs sit under the sources, quieter than them: rating is optional, checking the
   printed flyer is the action a shopper came for. Low opacity until the answer is
   hovered keeps the conversation from looking like a survey. */
/* The row holding an answer's rating buttons. Named .rate-row and NOT .feedback for the
   same reason .rate-thumb is not .thumb: `.feedback` is already the upload progress
   block (index.html:49), and this being later in the file it won - so the upload
   progress bar and its label were rendering side by side in a flex row at 45% opacity,
   on the first thing anybody does in this app. Found by the collision guard in
   tests/static, immediately after that guard was written for the .thumb case. */
.rate-row {
  display: flex;
  align-items: center;
  gap: 4px;
  align-self: flex-start;
  margin: -2px 0 4px;
  /* On touch there is no hover to bring these up, so the resting state has to be legible on
     its own - 0.45 read as disabled on a phone, where most of this happens. */
  opacity: 0.6;
  transition: opacity 120ms ease-in;
}
.rate-row:hover, .rate-row:focus-within { opacity: 1; }
/* The chat answer's rating buttons. Named .rate-thumb and NOT .thumb: this block used
   to be a second `.thumb` definition, and being later in the file it won - so every
   flyer thumbnail on the Images and Review tabs rendered as a greyscale ellipse with no
   border, on the one element app/review calls "the affordance that matters most here".
   The @media (max-width:380px) `.thumb { display: none }` rule below, written to drop
   flyer thumbnails on narrow phones, was deleting these rating buttons too. */
.rate-thumb {
  border: 1px solid transparent;
  background: none;
  font-size: 13px;
  line-height: 1;
  padding: 3px 6px;
  border-radius: 999px;
  cursor: pointer;
  filter: grayscale(1);
}
.rate-thumb:hover { border-color: var(--border); filter: grayscale(0); }
.rate-thumb.active { border-color: var(--accent); filter: grayscale(0); opacity: 1; }
.rate-thumb:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.feedback-thanks { font-size: 11px; color: var(--muted); }

/* Starter prompts on the public shell's first open. More inviting than the muted
   .source-chip pills because on the public page these are the primary affordance - the
   first thing a shopper who has never typed a query can tap. dir="auto" is set per chip in
   chat.js so an Arabic prompt reads right-to-left beside a Latin one. */
.starter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-self: flex-start;
  margin: 2px 0 4px;
}
.starter-chip {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 14px;
  padding: 8px 14px;
  border-radius: 999px;
  cursor: pointer;
}
.starter-chip:hover { border-color: var(--accent); color: var(--accent); }
.starter-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.chat-context { font-size: 12px; padding: 8px 2px 0; }
.chat-form { display: flex; gap: 8px; margin-top: 12px; }
.chat-form input {
  flex: 1;
  padding: 11px 13px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font-size: 15px;
}

/* ---------- lightbox ---------- */
/* Relies on the global [hidden] rule above to stay closed, since this sets display. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 28px;
  background: rgba(0, 0, 0, .88);
  cursor: zoom-out;
}
.lightbox-figure {
  margin: 0;
  max-width: 100%;
  max-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
.lightbox img {
  max-width: 100%;
  /* Leave room for the caption so the image never overflows the viewport. */
  max-height: calc(100vh - 100px);
  object-fit: contain;
  border-radius: 6px;
  background: #fff;
}
.lightbox figcaption { color: #d8dade; font-size: 13px; text-align: center; }
.lightbox-close {
  position: fixed;
  top: 14px;
  right: 20px;
  width: 40px;
  height: 40px;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, .12);
  color: #fff;
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}
.lightbox-close:hover { background: rgba(255, 255, 255, .25); }

/* ---------- mobile ----------
   Most uploaders and shoppers are on phones, so this is the primary layout, not an
   afterthought. Priorities: tap targets big enough to hit, no horizontal page scroll,
   the chat input reachable above the on-screen keyboard, and the offers table readable
   without pinch-zoom. */
@media (max-width: 700px) {
  body { font-size: 16px; }             /* 16px avoids iOS zooming on input focus */
  .app { padding: 0 12px 32px; }

  /* Brand and sign-out share the first row; the prioritized stats area owns the width below. */
  .topbar { gap: 8px; padding: 12px 0; }
  .brand { flex: 1; }
  #logout-btn { order: 2; }
  .stats { width: 100%; flex: none; gap: 8px; font-size: 12px; }
  .stat-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stat-tile { padding: 8px 9px; }
  .stats-alert { gap: 6px; padding: 7px 8px; }
  .vendor-grid { grid-template-columns: 1fr; }

  .tab { flex: 1; padding: 12px 8px; font-size: 16px; }

  .dropzone { padding: 24px 16px; }
  .dropzone strong { font-size: 17px; }

  /* Card header stacks; actions get a full-width row of real buttons. */
  .image-head { flex-wrap: wrap; gap: 10px; }
  .image-meta { flex-basis: calc(100% - 70px); }
  .image-actions { width: 100%; }
  .image-actions .btn { flex: 1; padding: 10px 8px; }
  .thumb { width: 52px; height: 70px; }

  /* The offers table cannot fit; relabel each cell and stack the row as a card. */
  .offers table, .offers thead, .offers tbody, .offers tr, .offers td { display: block; width: 100%; }
  .offers table { min-width: 0; }
  .offers thead { display: none; }
  .offers tr {
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 10px;
    margin-bottom: 10px;
  }
  .offers td { border: none; padding: 3px 0; }
  .offers td[data-label]::before {
    content: attr(data-label) ": ";
    color: var(--muted);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: .4px;
  }
  .offers td:empty { display: none; }
  .price-input { width: 96px; padding: 8px; font-size: 16px; }

  /* Chat fills the screen; the composer stays put when the keyboard opens. */
  .messages { min-height: 0; max-height: none; height: calc(100dvh - 260px); }
  /* The public shell carries no tabs and no stats bar, so it can give the conversation the
     room the operator app spends on that chrome. */
  .chat-page .messages { height: calc(100dvh - 150px); }
  .msg { max-width: 90%; }
  .chat-form { position: sticky; bottom: 0; padding: 8px 0; background: var(--bg); }
  .chat-form input { font-size: 16px; padding: 12px; }
  .chat-form .btn { padding: 12px 16px; }

  .lightbox { padding: 8px; }
  .lightbox img { max-height: calc(100dvh - 120px); }
  .lightbox-close { top: 8px; right: 10px; width: 44px; height: 44px; }
}

/* Very narrow phones: drop the thumbnail rather than squeeze the title. */
@media (max-width: 380px) {
  .thumb { display: none; }
  .image-meta { flex-basis: 100%; }
}

/* Coarse pointers get larger hit areas regardless of viewport width. */
@media (pointer: coarse) {
  .btn { min-height: 40px; }
  .tab { min-height: 44px; }
}

/* A cropped page still extracts and still says "done", so this has to be visible enough
   to interrupt - it is the only signal that half a flyer is missing from the catalog. */
.badge.warning { background: #7a4a00; color: #ffd9a0; }
.image-warning {
  margin-top: 6px;
  font-size: 12px;
  color: #ffd9a0;
  background: rgba(122, 74, 0, 0.18);
  border: 1px solid rgba(122, 74, 0, 0.5);
  border-radius: 6px;
  padding: 6px 8px;
}

/* Markdown inside a chat bubble. The model writes bold and bullet lists whatever the
   prompt says, so these exist to keep a rendered reply as compact as the plain text it
   replaced - default margins on p/ul inside a bubble look like a broken layout. */
.msg p { margin: 0 0 6px; }
.msg p:last-child { margin-bottom: 0; }
.msg ul { margin: 4px 0; padding-inline-start: 20px; }
.msg li { margin: 2px 0; }
.msg strong { font-weight: 600; }

/* ---------- review queue ----------
   Deliberately the same visual language as the image cards: a thumbnail on the left, the
   detail beside it, actions on the right. A reviewer moving between the two tabs should
   not have to learn a second layout, and the flyer thumbnail is the affordance that
   matters most here - the printed page is what settles almost every item in the queue. */
.tab-badge {
  display: inline-block;
  min-width: 18px;
  margin-inline-start: 6px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--warn-soft);
  color: var(--warn);
  border: 1px solid currentColor;
  font-size: 11px;
  font-weight: 600;
  vertical-align: middle;
}

.review-intro { font-size: 13px; margin: 0 0 12px; }

/* ---------- filter chips ----------
   Pills at 999px, because that is the shape this UI already means "a small label about
   something" by - .badge and .source-link are both this. The active chip borrows
   .badge.review's exact pattern (colour + currentColor border + soft background) rather
   than introducing a selected-state colour, so nothing new enters the palette for a
   control whose job is to be unremarkable. */
.review-filter, .image-filter { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }

.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  font: inherit;
  font-size: 13px;
  color: var(--muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  cursor: pointer;
}
.filter-chip:hover { color: var(--text); border-color: var(--muted); }
.filter-chip.active {
  color: var(--accent);
  border-color: currentColor;
  background: var(--accent-soft);
  font-weight: 600;
}
/* Focus is never suppressed: this row is six controls in sequence and tabbing through it
   is the only way to reach them without a pointer. */
.filter-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Tabular figures so a chip does not change width as its count ticks down under a
   reviewer working through it - the row would reflow and move the next chip. */
.filter-count { font-variant-numeric: tabular-nums; opacity: .75; }

.filter-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--warn);
}

.review-list { display: grid; gap: 10px; }

/* Faults vs Decisions. A quiet divider, not a headline: the cards are the content. */
.review-section {
  margin: 6px 0 0;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.review-section:first-child { margin-top: 0; }

/* One fault cluster: a header naming the shared misread, then its member cards. The border
   ties the group together so thirty rows read as the few causes behind them. */
.cluster {
  display: grid;
  gap: 8px;
  padding: 8px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: color-mix(in srgb, var(--surface) 60%, transparent);
}
.cluster-head { display: grid; gap: 8px; }
.cluster-top { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.cluster-label { font-weight: 600; }
.cluster-size {
  font-size: 12px;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 1px 8px;
}
.cluster-vendors { font-size: 12px; color: var(--muted); min-width: 0; }
/* The report button sits at the end of the header row. */
.cluster-report { margin-inline-start: auto; }
.cluster-report.active { background: var(--warn-soft); border-color: var(--warn); }
/* An urgent cluster wears the same amber as its urgent cards, so the eye lands on the
   groups a shopper can be told wrong today first. */
.cluster-head.urgent .cluster-label { color: var(--warn-strong, var(--warn)); }

.report-form {
  display: grid;
  gap: 8px;
  padding: 10px;
  border: 1px dashed var(--border);
  border-radius: 10px;
  background: var(--surface);
}
.report-label { display: grid; gap: 4px; font-size: 13px; color: var(--muted); }
.report-note {
  width: 100%;
  font: inherit;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg, var(--surface));
  color: inherit;
  resize: vertical;
}
.report-kinds { display: grid; gap: 4px; font-size: 13px; }
.report-kind { display: flex; align-items: center; gap: 4px; }
.report-actions { display: flex; gap: 6px; }

.review-card {
  display: flex;
  gap: 12px;
  padding: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
}
/* The top two bands are the ones a shopper can be quoted wrong today, so they carry the
   same amber the flagged offer rows already use rather than a new colour. */
.review-card.urgent { background: var(--warn-soft); border-color: var(--warn); }

.review-body { flex: 1; min-width: 0; display: grid; gap: 5px; }
.review-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); }

/* The card's name, e.g. OP-250. Pushed to the far end of the head row and set quiet:
   it has to be findable when a reviewer goes looking for it and invisible when they are
   not, because it is addressing information rather than anything about the offer.
   Monospace so two refs can be compared digit by digit without counting. */
.review-ref {
  margin-inline-start: auto;
  padding: 1px 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
  letter-spacing: .02em;
  color: var(--muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  cursor: pointer;
}
.review-ref:hover { color: var(--text); border-color: var(--border); }
.review-ref:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.review-ref.copied { color: var(--ok); border-color: currentColor; }
.review-title { font-weight: 600; overflow-wrap: anywhere; }
.review-detail { font-size: 13px; color: var(--warn); overflow-wrap: anywhere; }
.review-evidence { margin: 2px 0 0; padding-inline-start: 18px; font-size: 12.5px; color: var(--muted); }
.review-evidence li { margin: 1px 0; overflow-wrap: anywhere; }

.review-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 4px; }
.review-field {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: var(--muted);
}
.review-field input {
  width: 110px;
  padding: 4px 6px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
}
.review-field input.narrow { width: 78px; }
.review-error { font-size: 12px; color: var(--accent); }

@media (max-width: 700px) {
  .review-card { flex-wrap: wrap; }
  .review-body { flex-basis: calc(100% - 70px); }
  /* calc(100% - 70px) budgets for .thumb alone, which was the only sibling this card had
     when that rule was written. A scraped post's card now also carries .source-link, and
     without its own row that budget was wrong on the very card it has to share space
     with - its own line, the same move .review-actions makes below, rather than
     shrinking to fit next to the thumbnail on a screen this narrow. */
  .source-link { flex-basis: 100%; max-width: 100%; }
  .review-actions { width: 100%; }
  .review-field input { font-size: 16px; padding: 8px; }

  /* Seven chips wrap into a two- or three-row block on a 390px screen, pushing the first
     card below the fold - so the queue opens on a filter bar instead of on work. A single
     scrolling strip keeps the list where it should be. Safe precisely because chip order
     is fixed and stated in app.js: a horizontal strip whose contents reshuffled between
     polls would be worse than the wrap. */
  .review-filter, .image-filter {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    /* Bleed to the viewport edge so a half-visible chip signals "more this way". */
    margin-inline: -12px;
    padding-inline: 12px;
  }
  .review-filter::-webkit-scrollbar, .image-filter::-webkit-scrollbar { display: none; }
  .filter-chip { flex: 0 0 auto; }

  /* Most reviewing happens on a phone, and 13px in a 40px tap target reads as disabled
     rather than small. pointer:coarse already guarantees the height. */
  .btn.small { font-size: 15px; }
}

/* Evidence that is not a picture — a scraped post, a PDF, a vendor page. Ships alongside
   a scraped post's own thumbnail now (app.js renders both), so a card can carry .thumb,
   .source-link AND .review-body as three flex siblings, not the two this was drawn for.
   `label` is attacker-influenced text off the post (a page name, a vendor string), so
   nowrap with no width limit let a long one push the row wide rather than wrap - capped
   and ellipsized here instead of trusted to stay short. */
.source-link {
  align-self: flex-start;
  font-size: 12px;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 10px;
  text-decoration: none;
  white-space: nowrap;
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.source-link:hover { color: var(--text); border-color: var(--accent); }

/* What the server just did, in its own words. Quiet by default - this is confirmation,
   not an alert - and it carries the session tally, because the failure mode of this
   queue is abandonment rather than confusion: ten minutes of work should leave a trace. */
.review-said {
  margin: 0 0 10px;
  padding: 6px 10px;
  font-size: 13px;
  color: var(--ok);
  background: var(--surface);
  border: 1px solid currentColor;
  border-radius: var(--radius);
  overflow-wrap: anywhere;
}

/* The one fact a card's decision turns on - on a price card, the pair. Tabular figures so
   two cards' prices line up vertically when a reviewer is scanning down a filtered list
   of thirty, which is the whole reason the filter exists. */
.review-headline {
  font-size: 14px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

/* ---------- grouped card members ----------
   One row per page inside a date card. Quiet by default: the group action is the common
   path and these are the exception, so they must be available without competing with it.
   A conflicting row is the exception to the exception and says so in amber - the same
   amber that means "a shopper can be told this wrong today" everywhere else. */
.review-members { display: grid; gap: 4px; margin-top: 6px; }
.review-member {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  font-size: 12.5px;
  color: var(--muted);
  border: 1px solid transparent;
  border-radius: 6px;
}
.review-member.conflict {
  color: var(--warn);
  border-color: currentColor;
  background: var(--warn-soft);
}
.member-ref {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11px;
}
.member-name { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.member-conflict { flex-basis: 100%; font-size: 12px; }
.member-date {
  padding: 3px 5px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font-size: 13px;
}

@media (max-width: 700px) {
  /* The date box and its button need to be reachable without a horizontal squeeze, so
     the row wraps and they get their own line rather than shrinking. */
  .member-name { flex-basis: 100%; }
  .member-date { font-size: 16px; padding: 6px; }
}

/* =====================================================================================
   PUBLIC SHOPPER LANDING  (index.html only)
   -------------------------------------------------------------------------------------
   A minimalist "old-Google" landing: a near-empty white canvas, the input as the hero,
   coral used only for accents. Everything here is scoped under body.public / .app.chat-page
   so it cannot touch the operator SPA (admin.html), which shares these class names
   (.messages, .msg, .chat-form, .source-chip ...) but never carries those two hooks.
   The page commits to light on purpose - the white canvas is the aesthetic - so it reads
   the same on every phone regardless of the OS theme.
   ===================================================================================== */

body.public {
  background: #ffffff;
  color: #1a1a1a;
  font-family: "Tajawal", -apple-system, "Segoe UI", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* Bilingual copy that lives in the HTML: show the active language, hide the other. The
   tagline is the exception - it is set in JS and intentionally shows both lines. */
[data-lang] { }
html[lang="ar"] [data-lang="en"],
html[lang="en"] [data-lang="ar"] { display: none; }

.public .sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---------- shell ---------- */
.app.chat-page {
  /* Remap the shared chat tokens for a coherent public look: coral accent, white surfaces,
     a single hairline. The reused chat classes (.source-chip, .rate-thumb, .msg ...) pick
     these up automatically, so the accent lands in one place. */
  --bg: #ffffff;
  --surface: #ffffff;
  --border: #ececec;
  --text: #1a1a1a;
  --muted: #6b7280;
  --accent: #e5484d;
  --accent-soft: #fdecec;
  --hairline: #ececec;
  --accent-glow: rgba(229, 72, 77, 0.16);

  max-width: 680px;                 /* ~640px of content plus the gutter */
  margin: 0 auto;
  padding: 0 20px;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

/* ---------- top bar ---------- */
.chat-page .topbar {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 56px;
  padding: 12px 0;
  border-bottom: none;
  flex-wrap: nowrap;
}
/* The small wordmark only appears once the conversation starts - on the landing the hero
   wordmark is the page, the way the old Google logo was until you searched. */
.chat-page .topbar .wordmark {
  font-weight: 700;
  font-size: 16px;
  letter-spacing: -0.01em;
  color: var(--ink, #1a1a1a);
  margin-inline-end: auto;
}
.chat-page[data-state="landing"] .topbar .wordmark { display: none; }

.chat-page .topbar-actions {
  position: absolute;
  inset-block-start: 12px;
  inset-inline-end: 0;         /* mirrors the control cluster with document direction */
  display: flex;
  align-items: center;
  gap: 6px;
}

.chat-page .lang-toggle {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  overflow: hidden;
}
.chat-page .lang-btn {
  min-width: 42px;
  min-height: 44px;              /* tap target */
  padding: 0 12px;
  border: 0;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-weight: 500;
  font-size: 14px;
  cursor: pointer;
}
.chat-page .lang-flag {
  display: block;
  width: 24px;
  height: 16px;
  border-radius: 3px;
  overflow: hidden;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.12);
}
.chat-page .lang-btn + .lang-btn { border-inline-start: 1px solid var(--hairline); }
.chat-page .lang-btn[aria-pressed="true"] {
  color: var(--text);
  font-weight: 700;
  /* Coral as a quiet underline, so the indicator is accent-colored without putting coral
     text on white (which would miss AA for a label this size). */
  box-shadow: inset 0 -2px 0 var(--accent);
}

.chat-page .help-btn {
  width: 44px; height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  background: #fff;
  color: var(--muted);
  font-size: 18px;
  font-weight: 700;
  cursor: pointer;
}
/* Tajawal draws the Latin "?" visually larger than the Arabic "؟" at the same size, so
   the two language glyphs are balanced by eye, not by equal font-size. */
.chat-page .help-btn [data-lang="en"] { font-size: 15px; }
.chat-page .help-btn:hover { color: var(--text); border-color: var(--muted); }

/* A brief "copied · تم النسخ" confirmation beside the share button, driven by textContent
   from chat.js; invisible until it holds text, so it takes no space on the landing. */
.chat-page .share-feedback {
  font-size: 13px;
  color: var(--muted);
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.15s ease;
  pointer-events: none;
}
.chat-page .share-feedback:not(:empty) { opacity: 1; }

.chat-page .lang-btn:focus-visible,
.chat-page .help-btn:focus-visible,
.chat-page .send-btn:focus-visible,
.chat-page .starter-chip:focus-visible,
.chat-page .disclaimer-summary:focus-visible,
.chat-page .help-close:focus-visible,
.chat-page .lucky-btn:focus-visible,
.chat-page .lucky-another:focus-visible,
.chat-page .lucky-close:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- main / hero ---------- */
.chat-page .chat-main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.chat-page[data-state="landing"] .chat-main {
  justify-content: center;
  padding-bottom: 8vh;          /* optical centering, slightly above dead center */
}
.chat-page[data-state="chat"] .chat-main { justify-content: flex-start; }

.chat-page .hero { text-align: center; }
.chat-page[data-state="chat"] .hero { display: none; }

.chat-page .hero-wordmark {
  margin: 0;
  font-weight: 700;
  font-size: clamp(34px, 8vw, 52px);
  letter-spacing: -0.02em;
  line-height: 1.05;
  color: #1a1a1a;
}
/* The beta marker: present but never competing with the wordmark. Hairline border,
   muted ink, no fill - reads as a stamp, not a feature. Hidden once chatting. */
.chat-page .beta-pill {
  display: inline-block;
  margin-top: 10px;
  padding: 2px 10px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.chat-page[data-state="chat"] .beta-pill { display: none; }

.chat-page .hero-tagline {
  margin: 14px 0 0;
  color: var(--muted);
  font-weight: 400;
  font-size: clamp(15px, 3.4vw, 18px);
  line-height: 1.5;
}
.chat-page .hero-tagline .tagline-line { display: block; }

/* Messages belong to the chat state only. */
.chat-page[data-state="landing"] #messages { display: none; }

/* ---------- conversation (chat state) ---------- */
.chat-page .messages {
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 12px 0;
  min-height: 0;
  max-height: none;
  gap: 12px;
}
.chat-page[data-state="chat"] #messages {
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
  height: auto;                 /* neutralize the old fixed-height mobile rule */
  overflow-y: auto;
}
.chat-page .msg {
  max-width: 84%;
  padding: 10px 14px;
  border-radius: 16px;
  line-height: 1.55;
}
/* Ink, not coral: the accent is reserved for the input's craft, and white-on-coral would
   miss AA for body text. Logical radii so the bubble's tail mirrors under RTL. */
.chat-page .msg.user {
  background: #1a1a1a;
  color: #fff;
  border-end-end-radius: 5px;
}
.chat-page .msg.bot {
  background: #f5f5f6;
  border: 1px solid var(--hairline);
  color: #1a1a1a;
  border-end-start-radius: 5px;
}
.chat-page .msg.typing { color: var(--muted); background: transparent; border: 0; }

/* ---------- the input: the signature ---------- */
.chat-page .chat-form {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  max-width: 560px;
  margin: 30px auto 0;
  padding: 6px;
  padding-inline-start: 20px;
  background: #fff;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  transition: box-shadow 0.18s ease, border-color 0.18s ease;
}
/* The warm coral glow is the one indulgence - a soft ring, not a hard outline. */
.chat-page .chat-form:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-glow), 0 1px 3px rgba(0, 0, 0, 0.06);
}
.chat-page .chat-form input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  background: transparent;
  padding: 12px 4px;
  font: inherit;
  font-size: 16px;              /* 16px keeps iOS from zooming the field on focus */
  font-weight: 400;
  color: #1a1a1a;
  caret-color: var(--accent);
}
.chat-page .chat-form input:focus { outline: none; }   /* the ring is on the wrapper */
.chat-page .chat-form input::placeholder { color: var(--muted); opacity: 1; }

.chat-page .send-btn {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;                  /* the arrow is a UI glyph - passes 3:1 on coral */
  cursor: pointer;
  transition: filter 0.15s ease;
}
.chat-page .send-btn:hover:not(:disabled) { filter: brightness(1.06); }
.chat-page .send-btn:disabled { opacity: 0.45; cursor: default; }

/* Landing: the input is the centered hero. Chat: it settles to the bottom and sticks
   above the on-screen keyboard, with the conversation scrolling behind it. */
.chat-page[data-state="landing"] .chat-form { position: static; }
.chat-page[data-state="chat"] .chat-form {
  position: sticky;
  bottom: 10px;
  margin-top: 12px;
  background: #fff;
}

/* ---------- starter chips: small price-tag pills ---------- */
.chat-page .starters {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  width: 100%;
  max-width: 560px;
  margin: 20px auto 0;
}
.chat-page[data-state="chat"] .starters { display: none; }

.chat-page .starter-chip {
  display: inline-flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 8px;
  white-space: nowrap;
  border: 1px solid var(--hairline);
  background: #fff;
  color: #1a1a1a;
  font: inherit;
  font-weight: 500;
  font-size: 14px;
  line-height: 1;
  padding-block: 10px;
  padding-inline: 12px 16px;
  cursor: pointer;
  /* The tag shape: squared at the inline-start (the punch end), rounded at the far end.
     Logical radii so it mirrors under RTL. */
  border-start-start-radius: 6px;
  border-end-start-radius: 6px;
  border-start-end-radius: 999px;
  border-end-end-radius: 999px;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.chat-page .starter-chip::before {
  content: "";
  width: 7px; height: 7px;
  flex: 0 0 7px;
  border: 1.5px solid var(--muted);
  border-radius: 50%;
  background: transparent;
  transition: border-color 0.15s ease;
}
/* Hover keeps the label ink (AA) and spends the coral on the border and the tag hole. The
   explicit color also overrides the operator app's .starter-chip:hover, which turns the label
   coral - too little contrast for the label on the public page. */
.chat-page .starter-chip:hover {
  color: #1a1a1a;
  border-color: var(--accent);
  background: #fff6f6;
}
.chat-page .starter-chip:hover::before { border-color: var(--accent); }

/* ---------- "I'm Feeling Lucky" : a quiet second button + inline card ---------- */
/* Understated by design: a subtle pill beside the search (the way Google sets a second button
   next to Search), never the coral primary. Landing-only - hidden once the chat begins. */
.chat-page .lucky-row {
  display: flex;
  justify-content: center;
  margin-top: 14px;
}
.chat-page[data-state="chat"] .lucky-row { display: none; }

.chat-page .lucky-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: #fff;
  color: var(--muted);
  font: inherit;
  font-size: 14px;
  line-height: 1;
  padding: 9px 16px;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.chat-page .lucky-btn:hover:not(:disabled) {
  color: var(--text);
  border-color: var(--accent);
  background: #fff6f6;
}
.chat-page .lucky-btn:disabled { opacity: 0.55; cursor: default; }
.chat-page .lucky-btn .lucky-spark { font-size: 13px; }

/* The inline card: small, calm, centered under the search. Nothing loud - a hairline surface
   the same as the other public chips, so it reads as part of the minimal landing. */
.chat-page .lucky-card {
  width: 100%;
  max-width: 420px;
  margin: 14px auto 0;
}
.chat-page[data-state="chat"] .lucky-card { display: none; }

.chat-page .lucky-card-inner {
  position: relative;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: #fff;
  padding: 16px 18px;
  text-align: start;
  box-shadow: 0 1px 3px rgba(16, 19, 24, 0.06);
}
.chat-page .lucky-card-inner p { margin: 0; }
.chat-page .lucky-name {
  font-weight: 700;
  font-size: 15px;
  color: var(--text);
  padding-inline-end: 22px; /* clear of the close button */
}
.chat-page .lucky-price { margin-top: 6px; font-size: 14px; color: var(--text); }
.chat-page .lucky-save {
  margin-top: 2px;
  font-size: 14px;
  font-weight: 700;
  color: var(--ok);
}
.chat-page .lucky-shop { margin-top: 4px; font-size: 13px; color: var(--muted); }
.chat-page .lucky-empty { font-size: 14px; color: var(--muted); padding-inline-end: 22px; }

.chat-page .lucky-actions { margin-top: 12px; }
.chat-page .lucky-another {
  border: none;
  background: none;
  color: var(--accent);
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  padding: 0;
  cursor: pointer;
}
.chat-page .lucky-another:hover { text-decoration: underline; }

.chat-page .lucky-close {
  position: absolute;
  inset-block-start: 8px;
  inset-inline-end: 10px;
  border: none;
  background: none;
  color: var(--muted);
  font-size: 20px;
  line-height: 1;
  padding: 2px 4px;
  cursor: pointer;
}
.chat-page .lucky-close:hover { color: var(--text); }

/* ---------- source chips / rating in the public look ---------- */
.chat-page .sources { margin: 2px 0; }
.chat-page .source-chip { background: #fff; border-color: var(--hairline); }

/* ---------- disclaimer footer ---------- */
.chat-page .disclaimer {
  padding: 14px 0 20px;
  text-align: center;
}
.chat-page .disclaimer-details { display: inline-block; max-width: 520px; }
.chat-page .disclaimer-summary {
  display: inline-block;
  list-style: none;
  color: var(--muted);
  font-size: 12.5px;
  cursor: pointer;
  padding: 4px 6px;
  border-radius: 6px;
}
.chat-page .disclaimer-summary::-webkit-details-marker { display: none; }
.chat-page .disclaimer-summary:hover { color: #1a1a1a; }
.chat-page .disclaimer-full {
  margin: 8px auto 0;
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.6;
  text-align: start;
}
.chat-page .disclaimer-full p { margin: 0 0 6px; }
.chat-page .disclaimer-full p:last-child { margin: 0; }

/* Attribution: quieter than the disclaimer teaser above it - a shade smaller and lighter,
   so it reads as a signature rather than another thing to click. The link takes colour
   only on hover, which keeps the landing's white canvas undisturbed. */
.chat-page .byline {
  margin: 10px 0 0;
  color: var(--muted);
  font-size: 11.5px;
  opacity: 0.8;
}
.chat-page .byline a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid rgba(0, 0, 0, 0.18);
}
.chat-page .byline a:hover { color: #1a1a1a; border-bottom-color: rgba(0, 0, 0, 0.4); }

/* ---------- help popover ---------- */
body.public .help-scrim {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: flex-start;
  padding: 72px 16px 16px;
  background: rgba(20, 20, 20, 0.28);
}
body.public .help-card {
  position: absolute;
  inset-block-start: 72px;
  inset-inline-end: max(16px, calc((100vw - 640px) / 2));
  width: min(420px, 100%);
  background: #fff;
  border: 1px solid var(--hairline, #ececec);
  border-radius: 16px;
  padding: 22px;
  box-shadow: 0 14px 44px rgba(0, 0, 0, 0.18);
  font-family: "Tajawal", -apple-system, "Segoe UI", system-ui, sans-serif;
  color: #1a1a1a;
}
body.public .help-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 10px;
}
body.public .help-title { margin: 0; font-size: 18px; font-weight: 700; }
body.public .help-close {
  margin-inline-start: auto;
  width: 36px; height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: #f2f2f3;
  color: #6b7280;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}
body.public .help-close:hover { background: #e8e8ea; color: #1a1a1a; }
body.public .help-lead { margin: 0; color: #4b5563; font-size: 14.5px; line-height: 1.55; }
body.public .help-sub {
  margin: 16px 0 8px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #6b7280;
}
body.public .help-examples { margin: 0; padding: 0; list-style: none; display: grid; gap: 6px; }
body.public .help-examples li {
  padding: 9px 12px;
  background: #f7f7f8;
  border-radius: 8px;
  font-size: 14px;
  color: #1a1a1a;
}

/* ---------- motion: a gentle settle-in, only when welcome ---------- */
@media (prefers-reduced-motion: no-preference) {
  .chat-page[data-state="landing"] .hero,
  .chat-page[data-state="landing"] .chat-form,
  .chat-page[data-state="landing"] .starters {
    animation: jo-settle 0.5s cubic-bezier(0.22, 0.61, 0.36, 1) both;
  }
  .chat-page[data-state="landing"] .chat-form { animation-delay: 0.06s; }
  .chat-page[data-state="landing"] .starters { animation-delay: 0.12s; }
  @keyframes jo-settle {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: none; }
  }
}

/* ---------- mobile ---------- */
@media (max-width: 700px) {
  .app.chat-page { padding: 0 16px; }
  .chat-page .hero-wordmark { font-size: clamp(30px, 11vw, 44px); }
  .chat-page[data-state="landing"] .chat-main { padding-bottom: 6vh; }
  body.public .help-scrim { align-items: center; padding: 16px; }
  body.public .help-card { position: static; margin: auto; }
}

