/*
 * The conversation page (`/chat`).
 *
 * Loaded only by Chat.cshtml, the way cylinder.css is loaded only by the home page: this is the one
 * screen in the site laid out as an application rather than as a document, and none of it applies
 * anywhere else.
 *
 * The shape is the design kit's chat screen (UI-Kit mockup, CHAT panel) and the convention every
 * visitor already knows from a messaging app: the transcript scrolls between a fixed header and a
 * composer pinned to the bottom of the viewport. The skeleton half of that -- body, main, footer --
 * is `.shell-app` in site.css; everything below is what sits inside it.
 *
 * Same rules as the rest of the stylesheets: §2 semantic tokens only, no literal colours or
 * spacings, logical properties throughout so an eventual LTR variant needs no rule flipped.
 */

.chat {
  flex: 1 1 auto;
  min-inline-size: 0;
  min-block-size: 0;
  display: flex;
  flex-direction: column;
}

/* The conversation, the composer and the intro all sit in one column of the same width, so the
   composer reads as belonging to the transcript above it rather than to the page edges. */
.chat__column {
  inline-size: 100%;
  max-inline-size: var(--measure-chat);
  margin-inline: auto;
  padding-inline: var(--space-5);
}

/* ---------- the scrolling transcript ---------- */

.chat__stream {
  flex: 1 1 auto;
  min-block-size: 0;
  overflow-y: auto;
  /* A flick past the end of the transcript should stop there, not start scrolling the page behind
     it -- the page behind it does not scroll. */
  overscroll-behavior-y: contain;
  padding-block: var(--space-6) var(--space-5);
}

/*
 * With nothing asked yet the opening block sits in the middle of the empty space rather than pinned
 * under the header, which is where the eye looks first and where every chat client puts it.
 *
 * `safe` is what makes this sound in a scroll container: plain centring pushes overflow past the
 * start edge, where it cannot be scrolled back to, so on a short viewport the title would be
 * unreachable. Browsers without the keyword drop the declaration and top-align, which is the same
 * layout this had before -- an acceptable floor, not a broken one.
 */
.chat:not(.chat--active) .chat__stream {
  display: flex;
  flex-direction: column;
  justify-content: safe center;
}

.chat-log {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.chat-log:empty { display: none; }

/* ---------- the opening block ----------
 *
 * Title, purpose and the privacy guarantee sit at the top of the transcript itself rather than in
 * page chrome above it: this is the first thing in the space the visitor is about to type into, and
 * it is the moment the disclaimer is worth reading. Once the conversation starts it collapses to a
 * single quiet line and scrolls away with the rest of the history -- it is an opening, not a banner.
 */
/* Modest padding rather than a large top offset: the centring above places this block, and this is
   only what keeps it off the header in a browser that top-aligns instead. */
.chat-intro {
  padding-block: var(--space-5) var(--space-6);
  text-align: center;
}

.chat-intro__mark {
  inline-size: var(--space-7);
  block-size: var(--space-7);
  margin: 0 auto var(--space-4);
  border-radius: 50%;
  background: var(--color-accent-soft);
  border: 1.5px solid var(--color-accent-strong);
  color: var(--color-accent-bright);
  font-size: var(--text-lg);
  font-weight: 700;
  display: grid;
  place-items: center;
}

.chat-intro__title {
  font-size: var(--text-xl);
  line-height: var(--leading-heading);
  font-weight: 700;
  margin-block: 0 var(--space-3);
}

.chat-intro__text {
  color: var(--color-text-secondary);
  margin-block: 0 var(--space-5);
  margin-inline: auto;
  max-inline-size: 52ch;
}

/* Stated on the page rather than buried in a policy document, because the person it matters to is
   the visitor about to type something (ADR-006). */
.chat-privacy {
  font-size: var(--text-sm);
  line-height: var(--leading-ui);
  color: var(--color-accent-bright);
  background: var(--color-accent-soft);
  border-radius: var(--radius-md);
  padding: var(--space-3) var(--space-4);
  margin-block: 0 var(--space-6);
  text-align: start;
}

/* ---------- starter questions ----------
 *
 * An empty conversation with an empty box asks the visitor to invent a question in a register they
 * have no example of. Four real questions show what this can answer and what it cannot -- and each
 * is a button that asks it, not a suggestion to retype.
 */
.chat-starters__label {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin-block: 0 var(--space-3);
}

.chat-starters__list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
}

@media (max-width: 34rem) {
  .chat-starters__list { grid-template-columns: minmax(0, 1fr); }
}

.chat-starter {
  font: inherit;
  font-size: var(--text-sm);
  line-height: var(--leading-ui);
  text-align: start;
  cursor: pointer;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease;
}

.chat-starter:hover {
  border-color: var(--color-border-strong);
  background: var(--color-surface-raised);
  color: var(--color-text);
}

/*
 * Once a question has been asked the opening is history. The description and the starters go; the
 * title shrinks and the privacy note keeps only its one essential line, aligned to the text edge
 * like everything else in the transcript.
 */
.chat--active .chat-intro {
  padding-block: 0 var(--space-5);
  text-align: start;
  border-block-end: 1px solid var(--color-border-subtle);
  margin-block-end: var(--space-5);
}

.chat--active .chat-intro__mark,
.chat--active .chat-intro__text,
.chat--active .chat-starters { display: none; }

.chat--active .chat-intro__title {
  font-size: var(--text-base);
  color: var(--color-text-muted);
  font-weight: 600;
  margin-block-end: var(--space-2);
}

.chat--active .chat-privacy {
  margin-block-end: 0;
  font-size: var(--text-xs);
  background: none;
  padding: 0;
  color: var(--color-text-faint);
}

/* ---------- messages ---------- */

.chat-message { display: flex; flex-direction: column; gap: var(--space-3); }

.chat-message__body {
  white-space: pre-wrap;
  font-size: var(--text-base);
  line-height: var(--leading-ui);
  padding: var(--space-3) var(--space-4);
}

/*
 * The visitor's own messages sit on the inline-end side with the tail turned inward, matching how
 * RTL messaging apps mirror an outgoing message; the assistant answers from inline-start. The
 * radius is written with logical corner properties so both bubbles keep their tails on the correct
 * side under an eventual LTR variant.
 */
.chat-message--user {
  align-items: flex-end;
  max-inline-size: 78%;
  margin-inline-start: auto;
}

.chat-message--user .chat-message__body {
  background: var(--color-accent-strong);
  color: var(--color-text);
  border-radius: var(--radius-lg);
  border-end-end-radius: var(--space-1);
}

.chat-message--assistant {
  align-items: flex-start;
  max-inline-size: 88%;
  margin-inline-end: auto;
}

.chat-message--assistant .chat-message__body {
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border-subtle);
  color: var(--color-text);
  border-radius: var(--radius-lg);
  border-end-start-radius: var(--space-1);
  line-height: var(--leading-prose);
}

.chat-message--error .chat-message__body {
  border-style: dashed;
  border-color: var(--color-error);
  color: var(--color-text-secondary);
}

/* Who is speaking, named once per answer. The book answers as the book. */
.chat-message__author {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

.chat-message__avatar {
  inline-size: var(--space-6);
  block-size: var(--space-6);
  border-radius: 50%;
  background: var(--color-accent-soft);
  border: 1px solid var(--color-accent-strong);
  color: var(--color-accent-bright);
  font-size: var(--text-sm);
  font-weight: 700;
  display: grid;
  place-items: center;
  flex: 0 0 auto;
}

/*
 * While the answer streams, a caret marks where it is up to -- otherwise a pause between tokens is
 * indistinguishable from a finished, oddly truncated answer. It is an ::after on the bubble, so it
 * follows the text without being part of it and never lands in a copy or a screen-reader read-out.
 */
.chat-message--streaming .chat-message__body::after {
  content: "";
  display: inline-block;
  inline-size: 0.5ch;
  block-size: 1em;
  vertical-align: text-bottom;
  margin-inline-start: 2px;
  background: var(--color-accent-bright);
  animation: chat-caret 1s step-end infinite;
}

@keyframes chat-caret {
  0%, 100% { opacity: 1; }
  50% { opacity: 0; }
}

/* Waiting for the first token: retrieval is running and there is nothing to show yet. Three dots
   say "working" without claiming progress that isn't measurable. */
/* Exactly one line of the bubble's own line-height, so the bubble does not change size at the moment
   the dots are replaced by the first line of the answer. */
.chat-typing {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  block-size: calc(var(--leading-prose) * 1em);
}

.chat-typing__dot {
  inline-size: var(--space-2);
  block-size: var(--space-2);
  border-radius: 50%;
  background: var(--color-text-faint);
  animation: chat-typing 1.2s ease-in-out infinite;
}

.chat-typing__dot:nth-child(2) { animation-delay: 0.15s; }
.chat-typing__dot:nth-child(3) { animation-delay: 0.3s; }

@keyframes chat-typing {
  0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-2px); }
}

/* Reduced motion gets a static state, not a blinking one -- and the dots still read as "waiting"
   because they are dimmer than text. The global reduced-motion rule already stops the animations;
   this makes sure what they stop on is legible. */
@media (prefers-reduced-motion: reduce) {
  .chat-typing__dot { opacity: 0.5; }
  .chat-message--streaming .chat-message__body::after { animation: none; }
}

/* Says something about the answer above it -- that it was stopped short -- rather than being part of
   the answer, so it is quiet and outside the bubble. */
.chat-note {
  font-size: var(--text-xs);
  color: var(--color-text-faint);
  margin: 0;
}

/* Real links to real story pages: the book, not the chatbot, is the authority a reader is sent to. */
.chat-citations {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  list-style: none;
  padding: 0;
  margin: 0;
}

/* ---------- per-answer actions ----------
 *
 * Copy and the two thumbs sit quietly below the reply rather than inside the bubble, dim until the
 * answer is hovered or something in the row is focused. Dim, never hidden: a hover-only control does
 * not exist for a keyboard or touch visitor.
 */
.chat-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--color-text-faint);
  opacity: 0.65;
  transition: opacity 0.2s ease;
}

.chat-message--assistant:hover .chat-actions,
.chat-actions:focus-within { opacity: 1; }

/* Separates the question from the copy button sitting beside it, so the row reads as two things
   rather than one run of controls. */
.chat-actions__label {
  color: var(--color-text-faint);
  padding-inline-start: var(--space-2);
}

.chat-action {
  font: inherit;
  font-size: var(--text-sm);
  line-height: 1;
  cursor: pointer;
  background: none;
  border: 1px solid var(--color-border-subtle);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  color: var(--color-text-secondary);
  transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease;
}

.chat-action:hover {
  border-color: var(--color-accent-strong);
  background: var(--color-surface-raised);
  color: var(--color-text);
}

/* The pressed thumb stays lit: the visitor's own answer to the question, still visible afterwards. */
.chat-action[aria-pressed="true"] {
  border-color: var(--color-accent-strong);
  background: var(--color-accent-soft);
  color: var(--color-accent-bright);
}

/* ---------- composer ---------- */

.chat__composer {
  flex: 0 0 auto;
  position: relative;
  background: var(--color-canvas);
  padding-block-end: max(var(--space-5), env(safe-area-inset-bottom));
}

/* The transcript should look like it passes behind the composer rather than stopping dead against
   it: a short fade over the last few pixels of scrolling text. */
.chat__composer::before {
  content: "";
  position: absolute;
  inset-block-end: 100%;
  inset-inline: 0;
  block-size: var(--space-6);
  background: linear-gradient(to top, var(--color-canvas), transparent);
  pointer-events: none;
}

/*
 * One rounded field holding the textarea and the send button, the arrangement every visitor already
 * knows. The box carries the focus ring for whatever is focused inside it (see below), so it reads
 * as a single control.
 */
.composer {
  display: flex;
  align-items: flex-end;
  gap: var(--space-2);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: var(--space-2);
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.composer:focus-within {
  border-color: var(--color-accent-hover);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-focus) 30%, transparent);
}

.composer__input {
  flex: 1 1 auto;
  min-inline-size: 0;
  font: inherit;
  font-size: var(--text-base);
  line-height: var(--leading-ui);
  color: var(--color-text);
  background: none;
  border: 0;
  /* Grown to fit its content by chat.js, up to this ceiling -- past which it scrolls, so a long
     question can never eat the transcript. */
  resize: none;
  max-block-size: 40vh;
  overflow-y: auto;
  padding: var(--space-3);
}

.composer__input::placeholder { color: var(--color-text-faint); }

/* The one place in the stylesheets where a focus outline is removed. The replacement is louder than
   what it replaces: the box around it takes the full mint ring on :focus-within, so the focused
   field is the most obvious thing on the page. */
.composer__input:focus,
.composer__input:focus-visible { outline: none; }

.composer__button {
  flex: 0 0 auto;
  /* 48px: a comfortable touch target, and tall enough to align with the single-line textarea. */
  inline-size: var(--space-7);
  block-size: var(--space-7);
  display: grid;
  place-items: center;
  font: inherit;
  font-size: var(--text-lg);
  line-height: 1;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background 0.2s ease, opacity 0.2s ease;
}

/* `display` from the class above would otherwise beat the UA sheet's `[hidden] { display: none }`,
   and send/stop are swapped by toggling exactly that attribute. */
.composer__button[hidden] { display: none; }

.composer__send {
  background: var(--color-accent);
  color: var(--color-text-on-accent);
}

.composer__send:hover:not(:disabled) { background: var(--color-accent-hover); }

/* Nothing to send yet. Disabled rather than hidden, so the button's place in the box never moves. */
.composer__send:disabled { opacity: 0.4; cursor: default; }

/* An answer can be long and can be wrong; stopping it is the visitor's call, so it replaces the
   send button for exactly as long as stopping is possible. */
.composer__stop {
  background: var(--color-surface-raised);
  border-color: var(--color-border-strong);
  color: var(--color-text);
}

.composer__stop:hover { border-color: var(--color-accent-strong); background: var(--color-surface); }

.composer__footer {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  margin-block-start: var(--space-2);
  font-size: var(--text-xs);
  line-height: var(--leading-ui);
  color: var(--color-text-faint);
}

.composer__hint { margin: 0; }

/* Appears only near the ceiling: a counter at 12 of 2000 characters is noise. */
.composer__count {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
}

.composer__count[hidden] { display: none; }

/* ---------- jump to latest ----------
 *
 * The transcript follows a streaming answer only while the visitor is already at the bottom -- if
 * they have scrolled up to re-read something, yanking them back down is the worse failure. This is
 * how they get back, and its presence is also the signal that something arrived up there.
 */
.chat-jump {
  position: absolute;
  inset-block-end: calc(100% + var(--space-2));
  inset-inline: 0;
  margin-inline: auto;
  inline-size: fit-content;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  background: var(--color-surface-raised);
  color: var(--color-text-secondary);
}

.chat-jump:hover { border-color: var(--color-accent-strong); color: var(--color-text); }

.chat-jump[hidden] { display: none; }

/* ---------- narrow screens ---------- */

@media (max-width: 34rem) {
  .chat__column { padding-inline: var(--space-4); }

  /* Bubbles can run wider when the column itself is narrow -- 78% of a phone screen leaves a
     question broken across more lines than it has words to justify. */
  .chat-message--user { max-inline-size: 90%; }
  .chat-message--assistant { max-inline-size: 96%; }
}
