/* BERC AI chat interface.
 *
 * One centred column and nothing else. That is not a preference: the page this
 * replaced put its header, its transcript and its composer on three different
 * horizontal alignments - measured at 1440px, the header bar and the composer
 * were centred with their left edge at x=320 while the transcript list sat at
 * x=0, so the bot's first sentence started at x=16 with an empty half-page
 * beside it - and the client was right to call that bad. The cause was one
 * declaration: a shared rule centred the three column members with
 * `margin-inline: auto`, and a later `.chat__list { margin: 0 }` silently threw
 * that away for one of them. So the column is now expressed once, in one rule,
 * from one custom property (--chat-column), no rule below it writes the `margin`
 * shorthand on a column member, and tests/test_web_chat.py resolves the cascade
 * of this file to prove both.
 *
 * Every colour here is a reference to a custom property from brand.css. This
 * file contains no literal colour, font stack or radius, so the palette has
 * exactly one home. Where a value had to be derived (a dark scheme, a control
 * edge that meets WCAG 1.4.11) the derivation is a color-mix() of brand tokens
 * and the computed contrast ratio is written next to it.
 *
 * Directionality: the visitor may read Azerbaijani, Russian, English or Turkish,
 * and the same markup has to survive dir="rtl" the day a right-to-left language
 * is added to the language table. Only logical properties are used here -
 * margin-inline-*, padding-inline, inset-inline-start, border-inline-*,
 * text-align: start. Physical box properties are absent by policy and an
 * acceptance test greps this file for them.
 */

:root {
  /* Role tokens. Everything below references only these, so the dark scheme is
   * a single block of overrides rather than a second stylesheet. */
  --chat-canvas: var(--berc-page);
  --chat-text: var(--berc-text);
  --chat-strong: var(--berc-heading);
  /* The footer note under the composer and the input placeholder. --berc-muted
   * is 5.33:1 on the canvas, so unlike the grey this design replaced it is a
   * text colour and may be used as one. */
  --chat-quiet: var(--berc-muted);
  --chat-hairline: var(--berc-border);
  /* The visitor's pill. The bot has no fill at all - its answer is text on the
   * page - so this is the only tinted surface in the transcript. */
  --chat-pill: var(--berc-surface);
  --chat-field: var(--berc-page);
  --chat-primary: var(--berc-primary);
  --chat-on-primary: var(--berc-on-primary);
  --chat-accent: var(--berc-accent);
  --chat-placeholder: var(--chat-quiet);

  /* Derived, each with the ratio it was chosen to reach.
   * Send button under the pointer: 10.05:1 against its white glyph. Lightening
   * rather than darkening is the only direction available - the resting colour
   * is already near-black. */
  --chat-primary-hover: var(--berc-primary-hover);
  /* The hairline is 1.37:1, which is right for a divider and wrong for the edge
   * of a form control: WCAG 1.4.11 asks 3:1 of anything that identifies a
   * control. The composer's edge is therefore the hairline pulled toward the
   * body colour until it reaches 3.28:1 on the field. That is a visibly heavier
   * line than the reference draws, and it is the one place where this
   * implementation deliberately does not follow the reference: the reference's
   * own composer edge does not meet 1.4.11. */
  --chat-control-border: color-mix(in srgb, var(--berc-border) 60%, var(--berc-text));

  /* Spacing. A plain 6 / 10 / 16 / 24 / 32px scale expressed in rem so it
   * follows the visitor's own text size. */
  --chat-space-1: 0.375rem;
  --chat-space-2: 0.625rem;
  --chat-space-3: 1rem;
  --chat-space-4: 1.5rem;
  --chat-space-5: 2rem;

  /* THE column. One value, read by one rule, applied to every member of the
   * layout: the top strip, the transcript, the typing row and the dock that
   * holds the composer and the footer note. A second max-width anywhere in this
   * file is the bug this design was written to remove. */
  --chat-column: 768px;
  /* The breathing room between the column's text and its edge. The only thing
   * the narrow-viewport query changes. */
  --chat-gutter: var(--chat-space-3);

  /* A pill hugs its own content and stops well short of the column edge, so the
   * two speakers stay apart by shape and position, not only by fill. */
  --chat-pill-max: 85%;
  /* Roughly six lines of input before the composer starts scrolling instead of
   * eating the transcript. */
  --chat-input-max: 9rem;
  --chat-ease: ease;
}

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

html {
  block-size: 100%;
  color-scheme: light dark;
}

body.chat-page {
  margin: 0;
  block-size: 100%;
  background-color: var(--chat-canvas);
  color: var(--chat-text);
  font-family: var(--berc-font);
  font-size: var(--berc-size-base);
  line-height: var(--berc-line-base);
  text-align: start;
  /* A chat that rubber-bands the whole page on a phone feels broken; the
   * transcript is the only thing that scrolls. */
  overscroll-behavior: none;
}

/* ---------------------------------------------------------------- layout -- */

.chat {
  /* 100dvh, not 100vh: on iOS the URL bar collapses and 100vh would hide the
   * composer behind it. */
  block-size: 100dvh;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  background-color: var(--chat-canvas);
}

/* The column, stated once.
 *
 * Nothing below this rule may write the `margin` shorthand on any of these four
 * selectors: the shorthand resets margin-inline and the element silently leaves
 * the column. That is exactly how the page ended up with three alignments, and
 * it is why the rules below say `margin-block: 0` where they mean it. */
.chat__bar,
.chat__list,
.chat__typing,
.chat__dock {
  inline-size: 100%;
  max-inline-size: var(--chat-column);
  margin-inline: auto;
  padding-inline: var(--chat-gutter);
}

/* ------------------------------------------------------------ top strip -- */

/* Low chrome: the product name, once, small, and no rule under it. The
 * reference has no divider here either, and the transcript is its own scroll
 * container so nothing ever slides underneath. */
.chat__bar {
  padding-block: var(--chat-space-3);
}

/* Still an h1 - it is the page's one heading and a document without one is a
 * heading-order defect - but sized as chrome rather than as a title. The
 * tagline that used to sit beneath it is gone: it said nothing the first
 * sentence of the conversation does not say better, and it was half of the
 * block the client was looking at. */
.chat__brand {
  margin-block: 0;
  color: var(--chat-strong);
  font-size: var(--berc-size-small);
  font-weight: 600;
  line-height: var(--berc-line-base);
}

/* ------------------------------------------------------------ transcript -- */

.chat__main {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto auto;
  min-block-size: 0;
}

.chat__transcript {
  overflow-y: auto;
  padding-block: var(--chat-space-5);
  /* Keeps the newest message clear of the composer when it scrolls into view. */
  scroll-padding-block-end: var(--chat-space-5);
  /* both-edges, not the bare `stable`. A scrollbar reserved on one side only
   * narrows the transcript's content box asymmetrically, and the column centred
   * inside it lands half a scrollbar off the composer's centre - measured at
   * 1440px: the transcript centred on x=712.5 while everything else centred on
   * x=720, and the bot's text started 7.5px inside the composer's edge. That is
   * the same class of defect this page was rewritten to remove, so the
   * reservation is made symmetric and every centre agrees again at every width.
   * Where the platform draws overlay scrollbars - every phone, and macOS by
   * default - nothing is reserved at all and the edges line up exactly. */
  scrollbar-gutter: stable both-edges;
}

.chat__list {
  list-style: none;
  /* margin-block, never the `margin` shorthand - see the note on the column
   * rule above. This single declaration is the whole of the defect the client
   * reported. */
  margin-block: 0;
  padding-block: 0;
  display: flex;
  flex-direction: column;
  gap: var(--chat-space-5);
}

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

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

/* The bot's answer is not a bubble. It is text on the page, the full width of
 * the column, set looser than the chrome around it - which is what makes a long
 * answer readable and what the reference does. */
.msg__body {
  margin-block: 0;
  color: var(--chat-text);
  line-height: var(--berc-line-relaxed);
  /* An answer can carry a long unbroken token (a URL, an IBAN). Without this it
   * would push the column sideways. */
  overflow-wrap: anywhere;
}

/* The greeting arrives as plain template text, not markup, and its line breaks
 * are part of how it was authored. */
.msg__body--plain {
  white-space: pre-line;
}

/* The visitor speaks in a pill: aligned to the inline end, rounded, and only as
 * wide as what they typed. Alignment is done with flex, which is direction
 * aware, so a right-to-left document mirrors the conversation with no override.
 * Text on the pill measures 15.82:1. */
.msg--visitor {
  align-items: flex-end;
}

.msg--visitor .msg__body {
  max-inline-size: var(--chat-pill-max);
  padding-block: var(--chat-space-2);
  padding-inline: var(--chat-space-4);
  border-radius: var(--berc-radius-xl);
  background-color: var(--chat-pill);
}

/* A refusal or a transport failure. In a monochrome scale a notice cannot be
 * marked by hue, so it is marked by shape - a rule down its inline-start edge -
 * and by the one role label on the page that stays visible. */
.msg--notice .msg__body {
  border-inline-start: 3px solid var(--chat-accent);
  padding-inline-start: var(--chat-space-3);
}

.msg--notice .msg__role {
  color: var(--chat-strong);
}

/* Restricted markup produced by the server: emphasis, lists, paragraphs, links.
 * Nothing else can arrive, so nothing else is styled. */
.msg__body :is(p, ul, ol) {
  margin-block: 0;
}

.msg__body :is(p, ul, ol) + :is(p, ul, ol) {
  margin-block-start: var(--chat-space-3);
}

.msg__body :is(ul, ol) {
  padding-inline-start: var(--chat-space-4);
}

/* Underlined, not coloured. In a monochrome page the underline is the only
 * signal a link has, so it is never removed - WCAG 1.4.1 would otherwise be
 * failed by a link that differs from its paragraph in nothing at all. */
.msg__body a {
  color: var(--chat-accent);
  text-decoration: underline;
}

/* The wrapper around one streamed sentence must impose neither block nor inline
 * layout: the server may send a bare sentence or a whole list item. */
.msg__part {
  display: contents;
}

/* Who spoke is named in text on every message, for a screen reader, and shown
 * on none of them but a notice. Two reasons. The bot's name is already on the
 * page once, in the top strip, and printing it again above every answer is the
 * duplication the client was looking at. And the two speakers are already told
 * apart on screen by position and shape - a full-width block of page text
 * against a pill hugging the inline end - which is not a colour signal, so
 * WCAG 1.4.1 is satisfied without the label being visible. The notice keeps its
 * label because "something went wrong" is not something shape can say. */
.visually-hidden,
.msg--bot .msg__role,
.msg--visitor .msg__role {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border-width: 0;
}

.msg__role {
  margin-block: 0;
  font-size: var(--berc-size-small);
  font-weight: 600;
}

/* ------------------------------------------------------ typing indicator -- */

.chat__typing {
  margin-block: 0;
  padding-block: var(--chat-space-1);
  display: flex;
  align-items: center;
  gap: var(--chat-space-1);
  min-block-size: var(--chat-space-4);
}

.chat__dots {
  display: inline-flex;
  gap: var(--chat-space-1);
  /* Hidden rather than removed: the composer must not jump when the bot starts
   * and stops thinking. */
  visibility: hidden;
}

.chat__typing[data-active="true"] .chat__dots {
  visibility: visible;
}

/* 6px circles: large enough to see the rhythm, small enough that nothing moves
 * when they appear. The 1.2s cycle is outside the 0.2s transition band on
 * purpose - that band is for state changes, and a waiting animation at that
 * speed reads as a flicker. */
.chat__dots i {
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  background-color: var(--chat-quiet);
  animation: chat-dot 1.2s var(--chat-ease) infinite;
}

.chat__dots i:nth-child(2) {
  animation-delay: 0.15s;
}

.chat__dots i:nth-child(3) {
  animation-delay: 0.3s;
}

@keyframes chat-dot {
  0%,
  60%,
  100% {
    opacity: 0.35;
  }

  30% {
    opacity: 1;
  }
}

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

/* The dock is a column member, so the composer box and the footer note share
 * the transcript's exact left and right edges. It carries no fill and no rule
 * of its own: the composer floats above the page rather than sitting on a bar
 * across it. */
.chat__dock {
  padding-block-start: var(--chat-space-3);
  /* Home-indicator clearance on iOS, and a real gap below the box everywhere
   * else - the composer is meant to sit above the bottom edge, not on it. */
  padding-block-end: max(env(safe-area-inset-bottom, 0px), var(--chat-space-3));
  background-color: var(--chat-canvas);
}

.chat__composer {
  margin-block: 0;
}

.chat__composer-inner {
  display: flex;
  align-items: flex-end;
  gap: var(--chat-space-2);
  padding-block: var(--chat-space-2);
  padding-inline: var(--chat-space-3);
  border: 1px solid var(--chat-control-border);
  border-radius: var(--berc-radius-xl);
  background-color: var(--chat-field);
  box-shadow: var(--berc-shadow);
  transition: border-color var(--berc-transition) var(--chat-ease);
}

.chat__composer-inner:hover {
  border-color: var(--chat-strong);
}

/* The field has no edge of its own: the box around it is the control. */
.chat__input {
  flex: 1 1 auto;
  min-inline-size: 0;
  resize: none;
  max-block-size: var(--chat-input-max);
  overflow-y: auto;
  padding-block: var(--chat-space-1);
  padding-inline: var(--chat-space-1);
  border: 0;
  /* Invisible while the field is at rest - the field has no fill and no edge -
   * and the reason the focus ring is a rounded rectangle inside a rounded box
   * rather than a hard corner across it. An outline follows its element's
   * radius, so this is what shapes it. */
  border-radius: var(--berc-radius-lg);
  background-color: transparent;
  color: var(--chat-text);
  font-family: inherit;
  /* 16px is also what stops mobile Safari zooming the page on focus. */
  font-size: var(--berc-size-base);
  line-height: var(--berc-line-base);
  text-align: start;
}

.chat__input::placeholder {
  color: var(--chat-placeholder);
  opacity: 1;
}

/* A small dark round control, the way the reference draws it. 36px: comfortably
 * above the 24px WCAG 2.2 AA target minimum (2.5.8). The 44px of 2.5.5 is AAA
 * and would make the button, not the message, the loudest thing in the
 * composer. */
.chat__send {
  flex: 0 0 auto;
  display: inline-flex;
  /* A button does not inherit the page font in any browser - it takes the
   * platform's own form-control font. Measured on the rendered page: the send
   * button came back as Arial while every other element was Inter. */
  font-family: inherit;
  align-items: center;
  justify-content: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  border: 0;
  border-radius: 50%;
  background-color: var(--chat-primary);
  color: var(--chat-on-primary);
  cursor: pointer;
  transition:
    background-color var(--berc-transition) var(--chat-ease),
    opacity var(--berc-transition) var(--chat-ease);
}

.chat__send:hover {
  background-color: var(--chat-primary-hover);
}

/* The button is never `disabled` while a reply streams: disabling a focused
 * control throws focus to the body and a keyboard visitor loses their place.
 * It is marked busy instead, and the submit handler refuses the second send. */
.chat__send[aria-disabled="true"] {
  opacity: 0.6;
  cursor: default;
}

/* The paper plane points along the reading direction, so it mirrors with it. */
[dir="rtl"] .chat__send-icon {
  transform: scaleX(-1);
}

/* The disclaimer under the composer, centred on the same column. `center` is
 * not a physical value - only `left` and `right` are - so this survives an
 * RTL document unchanged. 5.33:1 on the canvas. */
.chat__footer {
  margin-block: var(--chat-space-2) 0;
  color: var(--chat-quiet);
  font-size: var(--berc-size-small);
  text-align: center;
  text-wrap: pretty;
}

/* ------------------------------------------------------------- focus etc -- */

/* Focus is never removed, only made deliberate. The ring is --berc-accent:
 * 21:1 against the canvas and against the composer, far past the 3:1 WCAG
 * 1.4.11 asks of a focus indicator. The offset matters - against the near-black
 * send button the ring would be 1.16:1, so it is pushed out onto the surface
 * beside it, where it is the highest-contrast pair on the page. */
:where(a, button, textarea, [tabindex]):focus-visible {
  outline: 3px solid var(--chat-accent);
  outline-offset: 2px;
}

/* The field sits inside the composer box; its ring is drawn just inside that
 * box's padding rather than across its edge. */
.chat__input:focus-visible {
  outline-offset: 1px;
}

/* The send button is the one control the ring cannot simply surround: it is a
 * near-black disc, so a near-black ring 2px away from it reads as one blob.
 * The offset is widened until the band of composer background between the two
 * is as thick as the ring itself, which is what actually makes the ring
 * legible - measured against the band, not against the disc. */
.chat__send:focus-visible {
  outline-offset: 3px;
}

.chat__transcript:focus-visible {
  /* The transcript is focusable because it scrolls, and a keyboard visitor must
   * be able to reach that scroll. Its ring sits inside so it is not clipped. */
  outline-offset: -3px;
}

.chat__skip {
  position: absolute;
  inset-block-start: var(--chat-space-2);
  inset-inline-start: var(--chat-space-2);
  z-index: 2;
  padding-block: var(--chat-space-1);
  padding-inline: var(--chat-space-2);
  border-radius: var(--berc-radius-sm);
  background-color: var(--chat-canvas);
  color: var(--chat-strong);
  /* Off-screen until focused - moved, not hidden, so it stays reachable. */
  transform: translateY(-200%);
  transition: transform var(--berc-transition) var(--chat-ease);
}

.chat__skip:focus-visible {
  transform: translateY(0);
}

/* ----------------------------------------------------------- preferences -- */

@media (prefers-reduced-motion: no-preference) {
  .chat__transcript {
    scroll-behavior: smooth;
  }
}

@media (prefers-reduced-motion: reduce) {
  .chat__dots i {
    animation: none;
  }

  .chat__skip,
  .chat__composer-inner,
  .chat__send {
    transition: none;
  }
}

/* Dark is the same scale turned over: the page becomes --berc-heading, the text
 * becomes --berc-surface, and the one dark control becomes the one light one.
 * Every value below is a brand token or a color-mix of two, and the ratio each
 * pair measures is written beside it. */
@media (prefers-color-scheme: dark) {
  :root {
    --chat-canvas: var(--berc-heading);
    /* 17.67:1 on the canvas, 11.32:1 on the pill. */
    --chat-text: var(--berc-surface);
    --chat-strong: var(--berc-page);
    /* 7.98:1 on the canvas, 6.30:1 on the field. */
    --chat-quiet: color-mix(in srgb, var(--berc-muted) 60%, var(--berc-page));
    --chat-hairline: color-mix(in srgb, var(--berc-page) 22%, var(--berc-heading));
    /* Lifted just off the canvas, the way the light pill is: 1.56:1 against it,
     * which is a tint and is meant to be. */
    --chat-pill: color-mix(in srgb, var(--berc-page) 16%, var(--berc-heading));
    --chat-field: color-mix(in srgb, var(--berc-page) 10%, var(--berc-heading));
    /* The dark control inverts with everything else: a light disc with a
     * near-black glyph, 19.44:1, and 12.98:1 under the pointer. */
    --chat-primary: var(--berc-page);
    --chat-on-primary: var(--berc-heading);
    --chat-primary-hover: var(--berc-primary-hover-dark);
    /* Black would vanish here, so the ring inverts too: 19.44:1 on the canvas. */
    --chat-accent: var(--berc-page);
    /* 3.28:1 against the dark field - the same 1.4.11 bar as the light scheme. */
    --chat-control-border: color-mix(in srgb, var(--berc-border) 50%, var(--berc-heading));
  }
}

/* On a narrow viewport only the gutter changes. The column rule is untouched,
 * which is the point of routing every edge through one custom property: there
 * is no second place for the three alignments to come back from. */
@media (max-width: 30rem) {
  :root {
    --chat-gutter: var(--chat-space-2);
  }

  .msg--visitor .msg__body {
    max-inline-size: 100%;
  }
}
