/* ==========================================================
   LEDdy — Sunrise components
   Companion to leddy-tokens.css (load tokens FIRST).
   Recreates the handoff prototypes as reusable classes for the
   server-rendered PHP templates. Plain CSS, no runtime.

   Sections
     1  Reset & page shells
     2  Header (chat + admin pages)
     3  Buttons
     4  Forms
     5  Banners
     6  Cards / panels
     7  Modals
     8  Chat: conversation, bubbles, status, feedback
     9  Chat: empty state
    10  Chat: composer
    11  History drawer
    12  History page
    13  Login
    14  Mobile
    15  Request access (split panel)
    16  Image preview (reply grids + lightbox)
   ========================================================== */

/* ---------- 1 · Reset & page shells ---------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
/* min-height, not height: a fixed `height: 100%` makes <body> exactly one
   viewport tall, and a position:sticky child can only stick inside its
   containing block — so the admin header unstuck itself as soon as the page
   scrolled past the first screen. The full-height shells set their own heights
   (.ldv-shell uses 100svh, .ldv-page/.ldv-login use min-height). */
html, body { min-height: 100%; }

/* Warm Sunrise band: 340px gradient at the top, white below.
   Anchored to the viewport, not the document. Scrolled with the page, the band
   left only a warm 64px stripe under the pinned header while everything below
   went white — the gradient stopped reading as a gradient. Fixed, the full
   340px fade is always present at the top of the screen, and any pinned bar
   painting its own slice of it stays seamless at every scroll position.
   (iOS Safari may treat `fixed` as `scroll`; the fallback is simply the old
   behaviour, so nothing breaks.) */
.ldv-band {
  background: var(--ldv-band) 0 0/100% var(--ldv-band-height) no-repeat fixed, #ffffff;
}

/* Sampled values of --ldv-band at the heights where pinned bars sit, so a
   sticky element can paint its own slice of the band and line up with the page
   exactly. (Band = #fff5ef 0 → #fffbf8 45% → #ffffff 100% over 340px.)
   These bars must be fully OPAQUE: a translucent fade lets vivid content —
   an orange user bubble — bleed through, which reads as a rendering fault. */
:root {
  --ldv-band-at-0:   #fff5ef;
  --ldv-band-at-64:  #fff8f3;
  --ldv-band-at-110: #fff9f6;

  /* Photographic backdrops (login + request access). Blurred well past
     recognition so the image reads as abstract colour, and scaled enough that
     the blur's soft edge never enters the frame. */
  --ldv-backdrop-blur: 24px;
  --ldv-backdrop-scale: 1.15;
}

/* Full-height app shell (chat) */
.ldv-shell {
  height: 100svh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* Scrolling document shell (admin pages) */
.ldv-page {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
}
.ldv-page-main { flex: 1; padding: 32px 24px 48px; }
.ldv-container { max-width: 880px; margin: 0 auto; }
.ldv-container--narrow { max-width: 720px; margin: 0 auto; }

.ldv-h1 { margin: 0 0 4px; font-size: 24px; font-weight: 700; color: var(--ldv-black); }
.ldv-h1--sm { font-size: 22px; }
.ldv-sub { margin: 0 0 20px; font-size: 14px; color: var(--ldv-body); }
.ldv-sub--tight { margin: 0 0 24px; font-size: 13px; }
.ldv-fine { font-size: 12px; color: var(--ldv-muted); }
.ldv-req { color: var(--ldv-orange); font-weight: 700; }

/* Section label — "GET THE BEST ANSWERS" */
.ldv-label {
  margin: 0 0 10px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1.2px;
  color: var(--ldv-muted);
}

.ldv-visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- 2 · Header ---------------------------------- */
/* Chromeless: no border, no background — the band shows through. */
.ldv-header {
  display: flex;
  align-items: center;
  gap: 14px;
  height: var(--ldv-header-h);
  flex: none;
  padding-left: 16px;
  position: relative;
  z-index: 6;
  /* Opaque slice of the band: conversation lines scrolling up disappear behind
     it cleanly. Matches the fixed band exactly, so there is no visible seam. */
  background: linear-gradient(180deg, var(--ldv-band-at-0), var(--ldv-band-at-64));
}
/* Admin pages scroll the document, so their header sticks to the top — on a long
   list or transcript the "Back" control must stay reachable. */
.ldv-header--page {
  padding-left: 20px;
  position: sticky;
  top: 0;
  z-index: 10;
  /* Its own opaque slice of the band. Because the band is viewport-fixed, this
     matches the page behind it at every scroll position — no warm block, no
     seam, and nothing bleeds through. */
  background: linear-gradient(180deg, var(--ldv-band-at-0), var(--ldv-band-at-64));
}

/* ☰ History — icon + label */
.ldv-hamburger {
  height: var(--ldv-touch);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 0 0 14px;
  background: none;
  border: none;
  font: inherit;
  cursor: pointer;
}
.ldv-hamburger:hover:not(:disabled) { background: var(--ldv-orange-tint); }
.ldv-hamburger-icon { display: flex; flex-direction: column; gap: 4px; }
.ldv-hamburger-icon span {
  display: block; width: 18px; height: 2px; background: var(--ldv-ink);
}
.ldv-hamburger-label { font-size: 14px; font-weight: 700; color: var(--ldv-ink); }

/* 1×16px hairline, 30px visual gap each side (14px header gap + 16px margin) */
.ldv-header-divider {
  width: 1px;
  height: 16px;
  background: var(--ldv-orange-tint-strong);
  margin: 0 16px;
  align-self: center;
  flex: none;
}

.ldv-wordmark-link { display: block; text-decoration: none; }
/* +3px optical nudge: the wordmark's cap height sits high in its box. */
.ldv-wordmark { height: 24px; width: auto; display: block; margin-top: 3px; }

/* Body grey, not muted: the breadcrumb is the only meta text that sits inside
   the warm band's top rows, where --ldv-muted lands at 4.26:1. */
.ldv-crumb { font-size: 14px; color: var(--ldv-body); }
.ldv-spacer { flex: 1; }

.ldv-nav { display: flex; align-items: center; gap: 4px; }
.ldv-nav-btn {
  height: var(--ldv-touch);
  display: inline-flex;
  align-items: center;
  padding: 0 14px;
  background: none;
  border: none;
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  color: var(--ldv-ink);
  text-decoration: none;
  cursor: pointer;
}
.ldv-nav-btn:hover:not(:disabled) { background: var(--ldv-orange-tint); color: var(--ldv-ink); }
/* `display:inline-flex` above would otherwise defeat the hidden attribute. */
.ldv-nav-btn[hidden] { display: none; }

/* Back-control labels: full text by default, abbreviated on narrow phones.
   aria-label carries the full name either way, so the accessible name never
   changes with the viewport. */
.ldv-lbl-short { display: none; }
@media (max-width: 420px) {
  .ldv-lbl-long  { display: none; }
  .ldv-lbl-short { display: inline; }
}

/* Corporate logo — top-right, flush to the top edge, once per screen. */
.ldv-corp-logo {
  height: var(--ldv-logo-size);
  width: var(--ldv-logo-size);
  display: block;
  margin-left: 16px;
  margin-right: 30px;
  align-self: flex-start;
  flex: none;
}
.ldv-header--page .ldv-corp-logo { margin-left: 18px; margin-right: 16px; }

/* ---------- 3 · Buttons -------------------------------- */
.ldv-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: var(--ldv-touch);
  padding: 0 20px;
  border: none;
  border-radius: var(--ldv-radius);
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
}
/* Primary — signature gradient, white text (approved deviation) */
.ldv-btn--primary { background: var(--ldv-gradient); color: #ffffff; }
.ldv-btn--primary:hover:not(:disabled) { background: var(--ldv-orange); color: #ffffff; }

/* Secondary — orange ghost outline */
.ldv-btn--secondary {
  background: none;
  border: 1px solid var(--ldv-orange);
  color: var(--ldv-orange-link);
}
.ldv-btn--secondary:hover:not(:disabled) { background: var(--ldv-orange-tint); color: var(--ldv-ink); }

/* Quiet — neutral outline, used for Cancel */
.ldv-btn--quiet {
  background: none;
  border: 1px solid var(--ldv-hairline);
  color: var(--ldv-body);
}
.ldv-btn--quiet:hover:not(:disabled) { border-color: var(--ldv-ink); color: var(--ldv-ink); }

.ldv-btn--block { width: 100%; }
.ldv-btn--lg { height: 48px; padding: 0 28px; font-size: 15px; }
.ldv-btn--wide { min-width: 140px; padding: 0 26px; }

/* Inline spinner inside a button */
.ldv-btn-spinner {
  width: 12px; height: 12px; flex: none;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: ldv-spin 1s linear infinite;
}

/* ---------- 4 · Forms ---------------------------------- */
.ldv-form { display: flex; flex-direction: column; gap: 16px; }
.ldv-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.ldv-grid-2--lg { gap: 18px; }

.ldv-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  font-weight: 700;
  color: var(--ldv-ink);
}
.ldv-field--dark { color: var(--ldv-black); }
.ldv-field-note { font-weight: 400; color: var(--ldv-muted); }
/* Scope note alongside a page title, e.g. "Add a new user (admin only)". */
.ldv-h1-note { font-size: 15px; font-weight: 400; color: var(--ldv-muted); }

.ldv-input,
.ldv-select,
.ldv-textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 0 14px;
  height: var(--ldv-touch);
  background: #ffffff;
  border: 1px solid var(--ldv-hairline);
  border-radius: var(--ldv-radius);
  font-family: inherit;
  font-size: 15px;
  font-weight: 400;
  color: var(--ldv-ink);
  -webkit-appearance: none;
  appearance: none;
}
.ldv-select { padding: 0 10px; }
.ldv-textarea {
  height: auto;
  padding: 10px 12px;
  resize: vertical;
  font-size: 14px;
  line-height: 1.5;
}
.ldv-input:focus,
.ldv-select:focus,
.ldv-textarea:focus { border-color: var(--ldv-orange); }

.ldv-form-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 6px; }

/* Honeypot — off-screen, never focusable */
.ldv-honeypot {
  position: absolute;
  left: -9999px;
  width: 0; height: 0;
  border: 0; padding: 0;
}

/* ---------- 5 · Banners -------------------------------- */
.ldv-banner {
  padding: 10px 14px;
  border: 1px solid;
  border-radius: var(--ldv-radius);
  font-size: 13px;
  line-height: 1.5;
}
.ldv-banner strong { font-weight: 700; }
.ldv-banner a { font-weight: 700; text-decoration: underline; color: inherit; }

.ldv-banner--error   { background: var(--ldv-error-bg);   border-color: var(--ldv-error-border);   color: var(--ldv-error-text); }
.ldv-banner--success { background: var(--ldv-success-bg); border-color: var(--ldv-success-border); color: var(--ldv-success-text); }
.ldv-banner--info    { background: var(--ldv-info-bg);    border-color: var(--ldv-info-border);    color: var(--ldv-info-text); }

/* Banner carrying a button (e.g. Resend): lay text and action out as a row so
   the control can never sit on top of the message. Wraps to two lines when the
   text is long or the viewport is narrow. */
.ldv-banner--action {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.ldv-banner--action > :first-child { flex: 1 1 auto; min-width: 0; }
.ldv-banner--action > :last-child  { flex: 0 0 auto; }

/* Long-wait notice — spinner + elapsed time */
.ldv-banner--info.ldv-banner--spinner { display: flex; align-items: center; gap: 10px; }
.ldv-spinner {
  flex: none;
  width: 14px; height: 14px;
  border: 2px solid var(--ldv-info-border);
  border-top-color: transparent;
  border-radius: 50%;
  animation: ldv-spin 1s linear infinite;
}

/* Retry control inside an error banner */
.ldv-banner-btn {
  height: 32px;
  padding: 0 10px;
  background: none;
  border: 1px solid var(--ldv-error-text);
  border-radius: var(--ldv-radius);
  font-family: inherit;
  font-size: 12px;
  font-weight: 700;
  color: var(--ldv-error-text);
  cursor: pointer;
}
.ldv-banner-btn:hover { background: #f9ccd2; }

/* ---------- 6 · Cards / panels ------------------------- */
.ldv-panel {
  background: #ffffff;
  box-shadow: var(--ldv-shadow-card);
  border-radius: var(--ldv-radius);
  padding: 32px;
}
.ldv-tile {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 14px 16px;
  background: var(--ldv-orange-wash);
}
.ldv-tile-key { font-size: 13px; font-weight: 700; color: var(--ldv-orange-link); }
.ldv-tile-desc { font-size: 14px; line-height: 1.5; color: var(--ldv-body); }

/* ---------- 7 · Modals -------------------------------- */
.ldv-scrim {
  position: fixed;
  inset: 0;
  background: var(--ldv-scrim);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 50;
}
.ldv-scrim[aria-hidden="true"] { display: none; }
.ldv-modal {
  width: 440px;
  max-width: 90vw;
  max-height: 90svh;
  overflow-y: auto;
  background: #ffffff;
  border-radius: var(--ldv-radius);
  padding: 24px;
  box-shadow: var(--ldv-shadow-modal);
  text-align: left;
}
.ldv-modal--guidelines { width: 520px; padding: 36px 40px; }
.ldv-modal--sm { width: 400px; }
.ldv-modal h2 { margin: 0 0 6px; font-size: 18px; font-weight: 700; color: var(--ldv-black); }
.ldv-modal p { margin: 0 0 18px; font-size: 14px; color: var(--ldv-body); }
.ldv-modal-logo { height: 40px; width: auto; display: block; margin: 0 auto 18px; }
.ldv-modal-lead { margin: 0 0 16px; font-size: 14px; color: var(--ldv-body); }
.ldv-modal-subhead { margin: 0 0 8px; font-size: 14px; font-weight: 700; color: var(--ldv-black); }
.ldv-modal ul {
  margin: 0 0 22px;
  padding-left: 20px;
  font-size: 14px;
  line-height: 1.6;
  color: var(--ldv-body);
}
.ldv-modal li { margin-bottom: 6px; }
.ldv-modal li:last-child { margin-bottom: 0; }
.ldv-modal-cta { display: flex; justify-content: center; }

/* ---------- 8 · Chat ----------------------------------- */
.ldv-body-row {
  flex: 1;
  display: flex;
  min-height: 0;
  position: relative;
}
.ldv-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.ldv-scroll {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 24px 24px 8px;
  scroll-padding-bottom: 24px;
}
.ldv-column {
  max-width: 820px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 26px;
}

/* Loading veil while a stored conversation is fetched */
.ldv-chat-loading {
  max-width: 820px;
  margin: 0 auto 16px;
  padding: 10px 14px;
  background: var(--ldv-orange-wash);
  font-size: 13px;
  font-weight: 700;
  color: var(--ldv-orange-link);
}
.ldv-chat-loading[hidden] { display: none !important; }

/* Turns. `.msg`/`.bubble` class names are kept — the chat JS creates them. */
#messages { display: flex; flex-direction: column; gap: 26px; }
#messages:empty { display: none; }
.msg { display: flex; }
.msg.user { justify-content: flex-end; }
.msg.bot { align-items: flex-start; }

/* User bubble — gradient fill, white text, right-aligned */
.msg.user .bubble {
  max-width: 620px;
  padding: 13px 17px;
  background: var(--ldv-gradient);
  border-radius: var(--ldv-radius);
  color: #ffffff;
  font-size: 15px;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* AI reply — no container; sits directly on the canvas.
   Typography comes from leddy-reply-markdown.css via .leddy-reply. */
.msg.bot .bubble {
  flex: 1;
  min-width: 0;
  background: none;
  padding: 0;
  border: none;
}

/* Markdown table wrapper emitted by markdown-it: scroll, never break the page.
   The base sheet sets `table { width: 100% }`, which squeezes a 14-column spec
   comparison into unreadable stacked fragments. Inside the scroller the table
   takes its natural width and cells stay on one line, so it scrolls instead. */
.leddy-reply .tbl-wrap { overflow-x: auto; margin: 0 0 14px; }
.leddy-reply .tbl-wrap table { margin: 0; width: auto; min-width: 100%; }
.leddy-reply .tbl-wrap th,
.leddy-reply .tbl-wrap td { white-space: nowrap; }

/* Streaming status — pulsing gradient dot + live stage text.
   The 12px dot is the one intentional circle in the design.
   Constrained to the conversation column so it lines up with the transcript. */
#typing {
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: 820px;
  margin: 0 auto 8px;
  padding: 0 2px;
  min-height: 20px;
}
#typing[hidden] { display: none !important; }
.ldv-status-dot {
  flex: none;
  width: 12px; height: 12px;
  border-radius: 50%;
  background: var(--ldv-gradient);
  animation: ldv-pulse 1.1s infinite;
}
.lv-status-text {
  font-size: 13px;
  font-weight: 700;
  color: var(--ldv-orange-link);
}

/* Per-reply feedback. Class names kept — attachFeedbackUI() creates them. */
.feedback-trigger {
  display: inline-flex;
  align-items: center;
  height: 36px;
  margin-top: 14px;
  padding: 0 12px;
  background: none;
  border: none;
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  color: var(--ldv-orange-link);
  cursor: pointer;
}
.feedback-trigger:hover { background: var(--ldv-orange-tint); color: var(--ldv-ink); }

.feedback-box {
  display: none;
  margin-top: 12px;
  padding: 14px;
  background: #ffffff;
  border-radius: var(--ldv-radius);
  box-shadow: var(--ldv-shadow-popover);
}
.feedback-box.open { display: block; }
.feedback-box textarea.fb-text {
  width: 100%;
  box-sizing: border-box;
  min-height: 76px;
  resize: vertical;
  padding: 10px 12px;
  background: #ffffff;
  border: 1px solid var(--ldv-hairline);
  border-radius: var(--ldv-radius);
  font-family: inherit;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ldv-ink);
}
.feedback-box textarea.fb-text:focus { border-color: var(--ldv-orange); }

.feedback-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  margin-top: 10px;
}
.feedback-actions .btn-mini {
  height: 40px;
  padding: 0 16px;
  border: none;
  border-radius: var(--ldv-radius);
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.feedback-actions .btn-cancel {
  background: none;
  border: 1px solid var(--ldv-hairline);
  color: var(--ldv-body);
}
.feedback-actions .btn-cancel:hover:not(:disabled) { border-color: var(--ldv-ink); color: var(--ldv-ink); }
.feedback-actions .btn-submit {
  padding: 0 18px;
  background: var(--ldv-gradient);
  color: #ffffff;
}
.feedback-actions .btn-submit:hover:not(:disabled) { background: var(--ldv-orange); }

.feedback-confirm {
  display: none;
  margin-top: 12px;
  padding: 10px 14px;
  background: var(--ldv-success-bg);
  border: 1px solid var(--ldv-success-border);
  font-size: 13px;
  color: var(--ldv-success-text);
}
.feedback-confirm.show { display: block; }
.feedback-confirm .fb-echo {
  display: block;
  margin-top: 6px;
  font-style: italic;
  color: var(--ldv-body);
}

/* ---------- 9 · Empty state ---------------------------- */
.ldv-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 56px 0 32px;
  text-align: center;
}
.ldv-empty[hidden] { display: none !important; }
.ldv-empty-bulb { height: 96px; width: auto; animation: ldv-pulse 2.6s infinite; }
.ldv-empty h1 { margin: 10px 0 0; font-size: 30px; font-weight: 700; color: var(--ldv-black); }
.ldv-empty-lead {
  margin: 0;
  font-size: 15px;
  color: var(--ldv-body);
  max-width: 440px;
}
.ldv-empty-block {
  max-width: 660px;
  width: 100%;
  margin-top: 24px;
  text-align: left;
}
.ldv-tips { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.ldv-label--spaced { margin: 24px 0 10px; }

.ldv-chips { display: flex; gap: 10px; flex-wrap: wrap; }
.ldv-chip {
  min-height: var(--ldv-touch);
  padding: 10px 16px;
  background: #ffffff;
  border: 1px solid var(--ldv-orange-tint-strong);
  border-radius: var(--ldv-radius);
  font-family: inherit;
  font-size: 14px;
  font-weight: 400;
  color: var(--ldv-ink);
  text-align: left;
  cursor: pointer;
}
.ldv-chip:hover { border-color: var(--ldv-orange); background: var(--ldv-orange-wash); }

/* ---------- 10 · Composer ------------------------------ */
.ldv-composer-wrap { flex: none; padding: 0 24px; }
.ldv-composer-banners {
  max-width: 820px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ldv-composer-banners:empty { display: none; }

#composer {
  flex: none;
  padding: 14px 24px calc(16px + env(safe-area-inset-bottom, 0px));
  background: transparent;
}
#composer form {
  max-width: 820px;
  margin: 0 auto;
  display: flex;
  /* Bottom-pinned, not stretched: on a long message `stretch` turned Send into
     a full-height orange slab down the side of the bar. */
  align-items: flex-end;
  background: #ffffff;
  border-radius: var(--ldv-radius);
  box-shadow: var(--ldv-shadow-composer);
}

/* --- 3 · one focus indicator, on the bar ---------------------------------
   Focusing the textarea used to draw the global ink ring *inside* the bar,
   which read as two nested boxes once the message was more than a line. The
   bar now carries the indicator for its own text field. The ring is on the
   container, so this still satisfies WCAG 2.4.7 -- and Send/Stop keep their
   own rings, because when you tab between them you have to be able to see
   which one you are on. */
/* Scoped through #composer so it outranks `#composer form`'s resting shadow --
   a bare .ldv-composer rule loses on specificity and the glow never deepens. */
#composer form.ldv-composer:focus-within {
  outline: 2px solid var(--ldv-orange);
  outline-offset: -2px;
  box-shadow: 0 6px 28px rgba(246, 99, 0, .32);
}
#composer form.ldv-composer textarea:focus { outline: none; }
#composer textarea#input {
  flex: 1;
  /* Matches the button height, so a single line fills the bar exactly instead
     of leaving a gap above it once the buttons stopped stretching. */
  min-height: 52px;
  /* Grows with its content, then scrolls. `field-sizing` does this natively;
     autoGrowInput() in index.php covers browsers that lack it, and this
     max-height caps both paths. */
  field-sizing: content;
  max-height: 170px;
  box-sizing: border-box;
  resize: none;
  padding: 13px 16px;
  background: none;
  border: none;
  border-radius: var(--ldv-radius);
  font-family: inherit;
  font-size: 15px;
  line-height: 1.4;
  color: var(--ldv-ink);
  overflow-y: auto;
}

#composer #send {
  flex: none;
  height: 52px;
  padding: 0 26px;
  background: var(--ldv-gradient);
  border: none;
  border-radius: var(--ldv-radius);
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  color: #ffffff;
  cursor: pointer;
}
#composer #send:hover:not(:disabled) { background: var(--ldv-orange); }
#composer #send:focus-visible { outline-offset: -2px; }

/* Stop — white block left of Send, shown only while streaming */
#composer #stopBtn {
  flex: none;
  height: 52px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 18px;
  background: #ffffff;
  border: none;
  border-radius: var(--ldv-radius);
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  color: var(--ldv-ink);
  cursor: pointer;
}
#composer #stopBtn:hover:not(:disabled) { background: var(--ldv-orange-tint); }
#composer #stopBtn:focus-visible { outline-offset: -2px; }
#composer #stopBtn[hidden] { display: none !important; }
.ldv-stop-square { width: 10px; height: 10px; background: var(--ldv-ink); }

.viewer-badge {
  max-width: 820px;
  margin: 8px auto 0;
  padding: 0 2px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--ldv-muted);
}
.viewer-badge strong { font-weight: 700; color: var(--ldv-body); }
.viewer-badge a { color: var(--ldv-orange-link); }

/* ---------- 11 · History drawer ------------------------ */
.history-drawer {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 320px;
  max-width: 85%;
  background: #ffffff;
  box-shadow: var(--ldv-shadow-drawer);
  z-index: 20;
  display: flex;
  flex-direction: column;
}
.history-drawer[hidden] { display: none !important; }

.history-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 12px 12px 20px;
  flex: none;
}
.history-title { font-size: 15px; font-weight: 700; color: var(--ldv-ink); }

/* Scoped to the drawer, not `header` — #historyClose lives in <aside>. */
.history-drawer .icon-btn {
  width: var(--ldv-touch);
  height: var(--ldv-touch);
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: var(--ldv-radius);
  font-family: inherit;
  font-size: 20px;
  line-height: 1;
  color: var(--ldv-ink);
  cursor: pointer;
}
.history-drawer .icon-btn:hover { background: var(--ldv-orange-tint); }

/* New chat and the Export/Log out footer are mobile affordances — on desktop
   those actions live in the header nav, so the drawer stays a pure list. */
.history-drawer-new { display: none; padding: 6px 6px 0; flex: none; }
.history-list { flex: 1; overflow-y: auto; padding: 8px; }
.history-drawer-foot {
  display: none;
  flex-direction: column;
  gap: 2px;
  padding: 8px;
  flex: none;
}
.history-drawer-foot .ldv-nav-btn { justify-content: flex-start; width: 100%; }

.history-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: flex-start;
  text-align: left;
  width: 100%;
  min-height: var(--ldv-touch);
  padding: 10px 12px;
  background: none;
  border: none;
  cursor: pointer;
}
.history-item:hover { background: var(--ldv-orange-tint); }
.history-item.active { background: var(--ldv-orange-tint); }
.history-item.active .sid { font-weight: 700; }
.history-item .sid {
  font-size: 14px;
  font-weight: 400;
  color: var(--ldv-ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.history-item .meta { font-size: 12px; color: var(--ldv-muted); }

/* Drawer empty / loading / error */
.history-empty { padding: 32px 20px; text-align: center; }
.history-empty-title { margin: 0 0 4px; font-size: 14px; font-weight: 700; color: var(--ldv-ink); }
.history-empty-note { margin: 0; font-size: 13px; color: var(--ldv-muted); }
.history-skeleton { display: flex; flex-direction: column; gap: 10px; padding: 12px; }
.history-skeleton span { display: block; height: 14px; background: var(--ldv-orange-tint); }
.history-error { margin: 12px; padding: 12px 14px; }
.history-error p { margin: 0 0 6px; font-size: 13px; font-weight: 700; }

/* Desktop: the drawer is an overlay panel, not a modal — no dimming. The
   element stays present so clicking the transcript still closes the drawer. */
.history-backdrop {
  position: absolute;
  inset: 0;
  background: transparent;
  z-index: 19;
}
.history-backdrop[hidden] { display: none !important; }

/* ---------- 12 · History page -------------------------- */
.ldv-hist-groups { display: flex; flex-direction: column; gap: 28px; }
.ldv-hist-group-label {
  margin: 0 0 10px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .8px;
  color: var(--ldv-muted);
  text-transform: uppercase;
}
.ldv-hist-rows { display: flex; flex-direction: column; gap: 8px; }
.ldv-hist-row {
  display: flex;
  align-items: center;
  gap: 16px;
  min-height: var(--ldv-touch);
  padding: 14px 18px;
  background: #ffffff;
  border: 1px solid var(--ldv-orange-tint);
  text-decoration: none;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  width: 100%;
}
.ldv-hist-row:hover { border-color: var(--ldv-orange); }
.ldv-hist-row-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ldv-hist-row-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--ldv-black);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ldv-hist-row-preview {
  font-size: 13px;
  color: var(--ldv-body);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ldv-hist-row-time { flex: none; font-size: 12px; color: var(--ldv-muted); text-align: right; line-height: 1.5; }
/* Search-hit highlight inside a result snippet. */
.ldv-hist-row-preview mark {
  background: var(--ldv-orange-tint);
  color: var(--ldv-ink);
  font-weight: 700;
  padding: 0 1px;
}
.ldv-hist-row-title .ext-flag { color: var(--ldv-ink); font-weight: 700; }
.ldv-hist-row-arrow { flex: none; font-size: 16px; color: var(--ldv-orange-link); }

/* Transcript view — title block above the messages.
   Pinned under the toolbar so the date and counts stay visible while a long
   day scrolls. The background only appears once it is actually stuck (class
   toggled by an IntersectionObserver), so at rest it sits flush on the band
   with no colour seam. */
.ldv-transcript-head {
  display: flex;
  flex-direction: column;
  gap: 2px;
  position: sticky;
  top: var(--ldv-header-h);
  z-index: 9;
  padding: 8px 0;
}
.ldv-transcript-head.is-stuck {
  /* Continues the band across its own height — opaque, so a vivid user bubble
     scrolling underneath is hidden rather than showing through. */
  background: linear-gradient(180deg, var(--ldv-band-at-64), var(--ldv-band-at-110));
  padding-bottom: 12px;
}
.ldv-transcript-title { font-size: 15px; font-weight: 700; color: var(--ldv-black); }
.ldv-transcript-meta  { font-size: 13px; color: var(--ldv-muted); }

/* Search field above the conversation list */
.ldv-search { margin-bottom: 24px; }
.ldv-search:focus { border-color: var(--ldv-orange); }

/* "no results" note for the client-side filter */
.ldv-hist-none { padding: 24px 2px; font-size: 14px; color: var(--ldv-body); }

.ldv-hist-skeleton { display: flex; flex-direction: column; gap: 12px; }
.ldv-hist-skeleton span {
  display: block;
  height: 64px;
  background: #ffffff;
  border: 1px solid var(--ldv-orange-tint);
}
.ldv-empty-panel {
  text-align: center;
  padding: 64px 20px;
  background: #ffffff;
  border: 1px solid var(--ldv-orange-tint);
}
.ldv-empty-panel img { height: 56px; width: auto; }
.ldv-empty-panel-title { margin: 0 0 4px; font-size: 16px; font-weight: 700; color: var(--ldv-black); }
.ldv-empty-panel-note { margin: 0 0 16px; font-size: 14px; color: var(--ldv-body); }

/* ---------- 13 · Login -------------------------------- */
.ldv-login {
  min-height: 100svh;
  position: relative;
  overflow: hidden;
  background: var(--ldv-orange);
}
/* Blurred backdrop — never stretched sharp.
   Fixed rather than absolute: the request-access form is taller than the
   viewport, and an absolutely-positioned backdrop stops at the container box,
   leaving a bare strip at the bottom of the scroll. A fixed element covers the
   viewport at every scroll position. (`position: fixed` is safe here; it is
   `background-attachment: fixed` that misbehaves on iOS.) */
.ldv-login-bg {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: blur(var(--ldv-backdrop-blur));
  /* Scaled past the blur radius so the softened edges stay outside the frame. */
  transform: scale(var(--ldv-backdrop-scale));
  z-index: 0;
}
/* Dark scrim — ALL breakpoints, mobile included */
.ldv-login-scrim { position: fixed; inset: 0; background: rgba(0, 0, 0, .38); z-index: 0; }
/* Flush to the top edge per the guideline, but inset from the right by the same
   30px the chat header uses — so the corporate logo keeps one consistent
   protection zone across signed-out and signed-in screens. */
.ldv-login-corp {
  position: absolute;
  top: 0;
  right: 30px;
  height: 96px;
  width: 96px;
  display: block;
  z-index: 2;
}
.ldv-login-center {
  position: relative;
  z-index: 1;
  min-height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 48px 20px;
}
.ldv-card {
  width: 400px;
  max-width: 100%;
  background: #ffffff;
  border-radius: var(--ldv-radius);
  padding: 32px;
  box-shadow: 0 16px 48px rgba(0, 0, 0, .3);
}
.ldv-card--wide { width: 560px; }
.ldv-card-mark { height: 32px; width: auto; display: block; }
.ldv-card-lead { margin: 6px 0 22px; font-size: 14px; color: var(--ldv-body); }
.ldv-card h1 { margin: 16px 0 4px; font-size: 20px; font-weight: 700; color: var(--ldv-black); }

.ldv-linkbtn {
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  color: var(--ldv-orange-link);
  text-decoration: underline;
  cursor: pointer;
}
.ldv-linkbtn:hover { color: var(--ldv-orange-link-hover); }
.ldv-card-foot { margin: 18px 0 0; font-size: 13px; color: var(--ldv-body); }
.ldv-card-fine { margin: 14px 0 0; font-size: 12px; color: var(--ldv-muted); }
.ldv-center { text-align: center; }

/* ---------- 14 · Mobile ------------------------------- */
@media (max-width: 700px) {
  :root { --ldv-header-h: var(--ldv-header-h-mobile); }

  .ldv-header { gap: 8px; padding-left: 10px; }
  /* Hamburger collapses to a 44px icon-only target */
  .ldv-hamburger {
    width: var(--ldv-touch);
    padding: 0;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 4px;
  }
  .ldv-hamburger-label { display: none; }
  .ldv-header-divider { display: none; }
  .ldv-wordmark { height: 30px; margin-top: 0; }
  /* New chat / Export PDF / Log out move into the drawer */
  .ldv-header .ldv-nav { display: none; }
  .ldv-corp-logo {
    height: 62px;
    width: 62px;
    margin-left: 4px;
    margin-right: 21px;
  }

  .ldv-scroll { padding: 16px 12px 8px; }
  .ldv-column { gap: 16px; }
  #messages { gap: 16px; }
  .msg.user .bubble { max-width: 85%; padding: 10px 14px; }
  #composer #stopBtn .ldv-stop-label { display: none; }

  .ldv-composer-wrap { padding: 0 12px; }
  #composer { padding: 10px 12px calc(12px + env(safe-area-inset-bottom, 0px)); }
  /* 16px prevents iOS Safari from zooming on focus */
  #composer textarea#input {
    font-size: 16px;
    padding: 12px;
    min-height: 48px;
    max-height: 128px;   /* ~5 lines */
  }
  /* Fixed square buttons pinned to the bottom edge -- never stretched. */
  #composer #send {
    width: 48px;
    height: 48px;
    flex: none;
    align-self: flex-end;
    padding: 0;
    font-size: 18px;
  }
  #composer #stopBtn {
    width: 48px;
    height: 48px;
    flex: none;
    align-self: flex-end;
    justify-content: center;
    padding: 0;
  }
  #composer #stopBtn .ldv-stop-square { width: 11px; height: 11px; }
  #composer #send .ldv-send-label { display: none; }
  #composer #send .ldv-send-arrow { display: inline; }

  .viewer-badge { font-size: 11px; margin-top: 6px; }

  .ldv-empty { padding: 40px 8px 24px; }
  .ldv-empty-bulb { height: 84px; }
  .ldv-empty h1 { font-size: 24px; }
  .ldv-tips { grid-template-columns: 1fr; }
  .ldv-chips { flex-direction: column; }
  .ldv-chip { width: 100%; }

  /* Mobile: the drawer is modal — dim the page behind it and expose the
     actions the hidden header nav would otherwise have provided. */
  .history-drawer { width: 300px; }
  .history-drawer-new { display: block; }
  .history-drawer-foot { display: flex; }
  .history-backdrop { background: rgba(0, 0, 0, .45); }

  /* Admin header at 390px: the breadcrumb and the back label both wrap to three
     lines, which triples the height of a now-sticky bar. The <h1> below already
     names the page, so the breadcrumb is redundant on a phone. */
  .ldv-header--page .ldv-crumb { display: none; }
  .ldv-header--page .ldv-nav-btn { white-space: nowrap; padding: 0 10px; }
  .ldv-header--page .ldv-corp-logo { margin-left: 0; margin-right: 10px; }
  /* The history prototype's own header wordmark size. */
  .ldv-header--page .ldv-wordmark { height: 26px; }

  .ldv-page-main { padding: 24px 16px 40px; }
  .ldv-panel { padding: 20px; }
  .ldv-grid-2 { grid-template-columns: 1fr; }
  .ldv-form-actions { flex-direction: column-reverse; }
  .ldv-form-actions .ldv-btn { width: 100%; }

  /* 62px logo + 21px gap, matching the mobile chat header. */
  .ldv-login-corp { height: 62px; width: 62px; right: 21px; }
  .ldv-login-center { padding: 72px 16px 40px; }
  .ldv-card, .ldv-card--wide { width: 100%; padding: 24px 20px; }
  .ldv-card-mark { height: 30px; }
  /* 16px prevents iOS Safari zoom-on-focus */
  .ldv-input, .ldv-select, .ldv-textarea { font-size: 16px; }

  .ldv-hist-row { flex-wrap: wrap; gap: 8px 12px; }
}

/* Below ~360px a paired field row leaves too little room for the labels. */
@media (max-width: 359px) {
  .ldv-split-form .ldv-grid-2 { grid-template-columns: 1fr; }
}

/* Phones: the reply sheet collapses tables to stacked cards, so the desktop
   nowrap must be released or the card values would overflow their row. */
@media (max-width: 640px) {
  .leddy-reply .tbl-wrap { overflow-x: visible; }
  .leddy-reply .tbl-wrap table { width: 100%; min-width: 0; }
  .leddy-reply .tbl-wrap th,
  .leddy-reply .tbl-wrap td { white-space: normal; }

  /* markdown-it puts an inline `text-align:right` on cells of a `---:` column,
     which is right for a real table but leaves numeric rows (Wattage, Lumens,
     LPW…) hanging off the right edge of a stacked card while every other row
     reads from the left. Inline styles can only be beaten with !important. */
  .leddy-reply td,
  .leddy-reply th { text-align: left !important; }
}

/* Desktop keeps the word "Send", mobile uses the ↑ glyph */
.ldv-send-arrow { display: none; }

/* Wide screens: nudge the corporate logo clear of the nav */
@media (min-width: 1441px) {
  .ldv-corp-logo { margin-left: 24px; }
}

@media (prefers-reduced-motion: reduce) {
  .ldv-empty-bulb,
  .ldv-status-dot,
  .ldv-spinner,
  .ldv-btn-spinner { animation: none; }
}

/* ==========================================================
   15 · Request access — split panel (UPDATE-4)
   Left brand panel + right form area on desktop; the panel
   collapses to a scrimmed header on phones.
   ========================================================== */
.ldv-split {
  min-height: 100svh;
  display: flex;
  background: #ffffff;
}

/* --- brand panel --------------------------------------- */
.ldv-split-brand {
  flex: 0 0 42%;
  position: relative;
  overflow: hidden;
  background: var(--ldv-orange);
  display: flex;
  flex-direction: column;
}
/* Sized to the whole viewport and anchored top-left, so the panel is a natural
   left-edge crop of a full-page backdrop rather than a squeezed thumbnail. */
.ldv-split-bg {
  position: absolute;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  max-width: none;
  object-fit: cover;
  filter: blur(var(--ldv-backdrop-blur));
  transform: scale(var(--ldv-backdrop-scale));
  transform-origin: top left;
  opacity: .85;
}
.ldv-split-scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    rgba(60, 20, 0, .55) 0%, rgba(60, 20, 0, .25) 45%, rgba(60, 20, 0, .6) 100%);
}
.ldv-split-brand-inner {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: 40px 48px;
}
.ldv-split-mark { height: 34px; width: auto; align-self: flex-start; }
.ldv-split-pitch { display: flex; flex-direction: column; gap: 18px; max-width: 380px; }
.ldv-split-bulb {
  height: 64px;
  width: auto;
  align-self: flex-start;
  /* The bulb ships orange; this is the supplied asset recoloured, not redrawn. */
  filter: brightness(0) invert(1);
  animation: ldv-pulse 2.6s infinite;
}
.ldv-split-pitch h1 {
  margin: 0;
  font-size: 32px;
  line-height: 1.25;
  font-weight: 700;
  color: #ffffff;
}
.ldv-split-pitch p { margin: 0; font-size: 15px; line-height: 1.6; color: var(--ldv-orange-tint); }
.ldv-split-fine { margin: 0; font-size: 12px; color: var(--ldv-orange-tint); }

/* --- form area ----------------------------------------- */
.ldv-split-main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--ldv-band) 0 0/100% var(--ldv-band-height) no-repeat fixed, #ffffff;
}
/* Corporate logo: flush to the top edge with the same 30px right gap used on
   every other screen (chat header, login, admin pages). */
.ldv-split-logo {
  align-self: flex-end;
  height: var(--ldv-logo-size);
  width: var(--ldv-logo-size);
  display: block;
  margin-right: 30px;
  flex: none;
}
/* One logo element per breakpoint: on desktop it belongs to the form area
   (top-right of the page), on mobile to the scrimmed header. */
.ldv-split-logo--mobile { display: none; }
.ldv-split-scroll { flex: 1; display: flex; justify-content: center; }
.ldv-split-form {
  width: 520px;
  max-width: 88%;
  padding: 16px 0 48px;
  display: flex;
  flex-direction: column;
  gap: 22px;
}
.ldv-split-form h1 { margin: 0 0 6px; font-size: 26px; font-weight: 700; color: var(--ldv-black); }
.ldv-split-lead { margin: 0; font-size: 14px; color: var(--ldv-body); }
.ldv-fieldset { border: 0; margin: 0; padding: 0; }
.ldv-fieldset > legend {
  padding: 0;
  margin: 0 0 12px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1.2px;
  color: var(--ldv-muted);
}
/* min-width:0 stops a long placeholder forcing the grid column wider. */
.ldv-split-form .ldv-field { min-width: 0; }
/* One wrapping row holds submit, Clear and Back, so the two viewports can
   rearrange them with flex-basis and order instead of duplicating markup.
   Desktop: [submit][Clear] / [Back].  Mobile: [submit] / [Back .... Clear]. */
.ldv-split-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  margin-top: 6px;
}
/* flex-basis auto, not `flex: 1`: in the mobile column layout a 0 basis with
   nothing to grow into collapsed the button to its text height. */
.ldv-split-actions .ldv-btn--primary { flex: 1 1 auto; height: 48px; font-size: 15px; }
.ldv-split-actions .ldv-linkbtn--quiet { flex: 0 0 auto; }
.ldv-split-actions .ldv-back-link { flex: 1 0 100%; }
.ldv-linkbtn--quiet {
  height: 48px;
  padding: 0 22px;
  background: none;
  border: none;
  font-family: inherit;
  font-size: 14px;
  font-weight: 700;
  color: var(--ldv-body);
  text-decoration: underline;
  cursor: pointer;
}
.ldv-linkbtn--quiet:hover { color: var(--ldv-ink); }
/* inline-flex + min-height so the link is a 44px target, not a 17px text run. */
.ldv-back-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--ldv-touch);
  font-size: 14px;
  font-weight: 700;
  color: var(--ldv-orange-link);
  text-decoration: none;
  align-self: flex-start;
}
.ldv-back-link:hover { text-decoration: underline; }

/* --- mobile: panel becomes a scrimmed header ------------ */
@media (max-width: 700px) {
  .ldv-split { flex-direction: column; }
  .ldv-split-brand { flex: none; }
  /* Full screen height, anchored top-left: the header is a crop of a
     full-screen backdrop, so it matches the login page's framing. */
  .ldv-split-bg { width: 100%; height: 100svh; }
  .ldv-split-scrim {
    background: linear-gradient(180deg, rgba(60, 20, 0, .55) 0%, rgba(60, 20, 0, .45) 100%);
  }
  .ldv-split-brand-inner { padding: 20px 24px 26px; gap: 14px; justify-content: flex-start; }
  .ldv-split-head { display: flex; align-items: flex-start; justify-content: space-between; }
  .ldv-split-mark { height: 28px; margin-top: 14px; }
  /* Flush to the top edge, 21px clear of the right — the mobile gap used by the
     chat header and login. The negative top margin cancels the panel padding so
     the logo still touches the edge, per the brand guideline. */
  .ldv-split-logo {
    height: 62px;
    width: 62px;
    align-self: flex-start;
    /* Panel padding is 20px top / 24px right. -20px pulls the logo flush to the
       top edge (brand guideline); -3px leaves a 21px gap on the right, the same
       clearance the chat header and login use on phones. */
    margin: -20px -3px 0 0;
  }
  .ldv-split-bulb { display: none; }
  .ldv-split-pitch { gap: 8px; max-width: none; }
  .ldv-split-pitch h1 { font-size: 24px; line-height: 1.3; }
  .ldv-split-pitch p { font-size: 14px; line-height: 1.55; }
  .ldv-split-brand .ldv-split-fine { display: none; }

  .ldv-split-main { background: var(--ldv-band) 0 0/100% 260px no-repeat, #ffffff; }
  /* The prototype keeps First/Last and Country/State paired on phones, unlike
     the generic .ldv-grid-2 collapse; only very narrow screens stack. */
  .ldv-split-form .ldv-grid-2 { grid-template-columns: 1fr 1fr; gap: 12px; }
  .ldv-split-logo--desktop { display: none; }
  .ldv-split-logo--mobile  { display: block; }
  .ldv-split-scroll { display: block; }
  .ldv-split-form {
    width: auto;
    max-width: none;
    padding: 24px 24px 40px;
    gap: 20px;
  }
  /* 16px prevents iOS Safari zooming on focus; 48px keeps the touch target. */
  .ldv-split-form .ldv-input,
  .ldv-split-form .ldv-select { height: 48px; font-size: 16px; }
  .ldv-split-actions { gap: 12px; }
  .ldv-split-actions .ldv-btn--primary { flex: 1 0 100%; height: 52px; font-size: 16px; }
  /* Back and Clear share one 44px row beneath the submit. */
  .ldv-split-actions .ldv-back-link { flex: 0 1 auto; order: 2; }
  .ldv-split-actions .ldv-linkbtn--quiet {
    order: 3;
    margin-left: auto;
    height: auto;
    min-height: var(--ldv-touch);
    padding: 0 8px;
  }
}

/* ==========================================================
   16 · Image preview — reply thumbnail grids + lightbox
   A list of image links in an AI reply is converted, client
   side, into a grid of thumbnails; clicking one opens an
   in-page lightbox scoped to that group.

   Collapsing is done in CSS, not by re-rendering: every tile
   is in the DOM and the overflow is hidden with :nth-child.
   The two breakpoints hide a different number of tiles and
   need a different "+N" label, so the counts are rendered as
   two spans and swapped by the media query. That way the
   grid needs no resize handler and cannot get out of step
   with the viewport.
   ========================================================== */
@keyframes ldv-img-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes ldv-img-zoom-in {
  from { opacity: 0; transform: scale(.96); }
  to   { opacity: 1; transform: scale(1); }
}

/* ---------- the grid ------------------------------------ */
.ldv-imggrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 10px;
  margin: 0 0 14px;
}

.ldv-imgtile,
.ldv-imgmore {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 110px;
  padding: 12px;
  background: #ffffff;
  border: 1px solid var(--ldv-orange-tint);
  cursor: pointer;
  font-family: inherit;
  text-decoration: none;
}
.ldv-imgtile:hover,
.ldv-imgmore:hover { border-color: var(--ldv-orange); }

.ldv-imgtile img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  animation: ldv-img-fade-in .5s ease-out both;
}

/* "+16 / Show all 21" */
.ldv-imgmore {
  flex-direction: column;
  gap: 4px;
  background: var(--ldv-orange-wash);
  border-width: 1px;
  color: var(--ldv-orange-link);
}
.ldv-imgmore-n { font-size: 18px; font-weight: 700; }
.ldv-imgmore-all { font-size: 12px; font-weight: 700; }

/* The counter is its own column so "+16" sits above "Show all 21".
   It is a flex CHILD of the tile, so the tile's flex-direction does not
   reach these two spans -- they need their own. */
.ldv-imgmore-d,
.ldv-imgmore-m {
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

/* The tile only exists when the group actually overflows at
   this breakpoint, and each breakpoint carries its own count. */
.ldv-imgmore,
.ldv-imgmore-m { display: none; }
.ldv-imgmore-d { display: flex; }
.ldv-imggrid[data-more-desktop] .ldv-imgmore { display: flex; }
.ldv-imggrid[data-collapsed][data-more-desktop] .ldv-imgtile:nth-child(n+6) { display: none; }

/* A tile whose image 404s or is hotlink-blocked is swapped back
   to a plain text link, so a reply never shows a broken icon. */
.ldv-imgfallback { display: block; margin: 0 0 6px; }

/* ---------- the lightbox -------------------------------- */
.ldv-lb {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  background: rgba(0, 0, 0, .8);
  animation: ldv-img-fade-in .35s ease-out;
  /* A horizontal drag that reaches the end of a scroller inside the overlay
     must not turn into the browser's back-navigation gesture. */
  overscroll-behavior: contain;
  /* A swipe should never select the title or leave text highlighted behind. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.ldv-lb[hidden] { display: none; }

/* The overlay is dark, so the near-black global focus ring is
   invisible on it. Every control switches to a white ring. */
.ldv-lb :where(a, button):focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

.ldv-lb-bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 20px;
}
.ldv-lb-title {
  font-size: 14px;
  font-weight: 700;
  color: #ffffff;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ldv-lb-spacer { flex: 1; }

.ldv-lb-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  height: 40px;
  padding: 0 14px;
  background: none;
  border: 1px solid rgba(255, 255, 255, .5);
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  color: #ffffff;
  text-decoration: none;
  cursor: pointer;
}
.ldv-lb-btn:hover {
  background: rgba(255, 255, 255, .12);
  border-color: #ffffff;
  color: #ffffff;
}
.ldv-lb-close { width: 40px; padding: 0; font-size: 18px; line-height: 1; }

.ldv-lb-stage {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  min-height: 0;
  padding: 0 20px;
  /* Horizontal is ours (swipe to page); vertical stays with the browser.
     Without this the browser claims the gesture first and the swipe either
     does nothing or triggers back-navigation. */
  touch-action: pan-y;
}
.ldv-lb-nav {
  width: 48px;
  height: 48px;
  flex: none;
  padding: 0;
  background: none;
  border: 1px solid rgba(255, 255, 255, .5);
  font-family: inherit;
  font-size: 20px;
  line-height: 1;
  color: #ffffff;
  cursor: pointer;
}
.ldv-lb-nav:hover { background: rgba(255, 255, 255, .12); border-color: #ffffff; }
/* One image in the group: nothing to page to. */
.ldv-lb[data-single] .ldv-lb-nav,
.ldv-lb[data-single] .ldv-lb-strip { display: none; }

.ldv-lb-pane {
  flex: 1;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  padding: 16px;
  position: relative;
}
.ldv-lb-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  background: #ffffff;
  padding: 28px;
  animation: ldv-img-zoom-in .45s ease-out;
  transition: opacity .28s ease, transform .22s cubic-bezier(.2, .8, .3, 1);
  will-change: transform;
}
.ldv-lb[data-fading] .ldv-lb-img { opacity: 0; }
/* While a finger is down the image tracks it 1:1 -- any transition here would
   make it lag behind the touch. */
.ldv-lb[data-dragging] .ldv-lb-img {
  transition: none;
  animation: none;
}

/* Only shown if the next image takes longer than ~300ms. */
.ldv-lb-spinner {
  position: absolute;
  width: 28px;
  height: 28px;
  border: 3px solid rgba(255, 255, 255, .35);
  border-top-color: #ffffff;
  border-radius: 50%;
  animation: ldv-spin 1s linear infinite;
}
.ldv-lb-spinner[hidden] { display: none; }

.ldv-lb-strip {
  flex: none;
  display: flex;
  gap: 8px;
  padding: 14px 20px;
  overflow-x: auto;
  /* A short strip is centred. A long one must NOT be: plain `center` pushes
     the overflow out past the start edge, where it cannot be scrolled back
     into view, and the first thumbnail is permanently half cut off. `safe`
     falls back to flex-start exactly when that would happen. */
  justify-content: center;
  justify-content: safe center;
  /* Touch: momentum, a gentle snap, and no chaining into the page behind. */
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  scroll-behavior: smooth;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
}
.ldv-lb-strip::-webkit-scrollbar { display: none; }
.ldv-lb-thumb { scroll-snap-align: center; }
.ldv-lb-thumb {
  flex: none;
  width: 64px;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 6px;
  background: #ffffff;
  border: 2px solid rgba(255, 255, 255, .4);
  cursor: pointer;
  transition: border-color .3s ease;
}
.ldv-lb-thumb[aria-current="true"] { border-color: var(--ldv-orange); }
.ldv-lb-thumb img { max-width: 100%; max-height: 100%; object-fit: contain; }

/* While the lightbox is open the page behind it must not scroll. */
body.ldv-lb-open { overflow: hidden; }

/* ---------- mobile -------------------------------------- */
@media (max-width: 700px) {
  .ldv-imggrid { grid-template-columns: 1fr 1fr; gap: 8px; }
  .ldv-imgtile,
  .ldv-imgmore { height: 96px; padding: 10px; }
  .ldv-imgmore-n { font-size: 17px; }
  .ldv-imgmore-all { font-size: 11px; }

  /* Swap which "+N" label shows, and hide one tile earlier. */
  .ldv-imgmore-d { display: none; }
  .ldv-imgmore-m { display: flex; }
  .ldv-imggrid[data-more-desktop] .ldv-imgmore { display: none; }
  .ldv-imggrid[data-more-mobile] .ldv-imgmore { display: flex; }
  .ldv-imggrid[data-collapsed][data-more-desktop] .ldv-imgtile:nth-child(n+6) { display: flex; }
  .ldv-imggrid[data-collapsed][data-more-mobile] .ldv-imgtile:nth-child(n+4) { display: none; }

  .ldv-lb { background: rgba(0, 0, 0, .85); }
  .ldv-lb-bar { gap: 10px; padding: 12px 14px; }
  /* At 390px the bar cannot hold group + position + format beside the two
     buttons. Drop the format rather than truncate away "Image 3 of 21". */
  .ldv-lb-title { font-size: 13px; }
  .ldv-lb-fmt { display: none; }
  .ldv-lb-btn { min-height: var(--ldv-touch); height: var(--ldv-touch); padding: 0 12px; }
  .ldv-lb-close { width: var(--ldv-touch); }

  /* Arrows sit over the image rather than beside it: at 390px
     there is no room for a 48px gutter on each side. */
  .ldv-lb-stage { gap: 0; padding: 0 8px; position: relative; }
  .ldv-lb-nav {
    position: absolute;
    width: var(--ldv-touch);
    height: var(--ldv-touch);
    background: rgba(0, 0, 0, .4);
    z-index: 1;
  }
  .ldv-lb-prev { left: 8px; }
  .ldv-lb-next { right: 8px; }
  .ldv-lb-pane { padding: 0; }
  .ldv-lb-img { max-width: 80%; max-height: 88%; padding: 18px; }

  /* Left-aligned, and the row itself is the scroll surface -- a centred
     short strip would fight the snap. */
  .ldv-lb-strip { gap: 10px; padding: 12px 14px; justify-content: flex-start; }
  .ldv-lb-thumb { width: 56px; height: 56px; padding: 5px; }
}

@media (prefers-reduced-motion: reduce) {
  .ldv-lb,
  .ldv-lb-img,
  .ldv-imgtile img { animation: none; }
  .ldv-lb-img,
  .ldv-lb-thumb { transition: none; }
  .ldv-lb-strip { scroll-behavior: auto; }
}
