/* ============================================================================
   BitcoinPuzzles — the transcript

   One conversation, wherever it is held: a direct-message thread between two
   people, and an alliance room with a crew in it. The two surfaces frame it
   differently — a thread is a file on a page, a room is the page — but what
   goes inside the frame is the same object, and it was written twice. It is
   written here once.

   The rule for this sheet: it owns every `.chat-*` selector, including the
   states and variants only one of the two surfaces currently renders (a
   picture in a bubble reaches a room and not a thread; being named reaches a
   room and not a thread). A surface sheet extends the transcript through its
   own block names — `.al-attach` — and never redeclares a `chat-` one. So a
   chat styling fix is made in exactly one place, and both surfaces get it.

   The `@name` a room's lines carry is not one of those extensions: it is
   `mentions.css`, shared with a puzzle's comments, which is a surface with no
   transcript at all.

   Attribution is stated three times over — side, face, name — because a
   transcript is read at scrolling speed and still has to answer "who said
   this" on every line without being studied. Never by colour alone, and never
   by an orange fill: the accent belongs to Enviar.

   Alignment follows the writing direction — a flex row and logical properties
   throughout — so an RTL locale mirrors the whole transcript, gutter included,
   without a single physical property.

   Depends on tokens.css and base.css, and consumes their primitives (.avatar,
   .btn, .input, .empty, .label, .mono) rather than restating them.
   ========================================================================= */

/* ---------------------------------------------------------------------------
   The transcript — the one region on either surface that scrolls.
   ------------------------------------------------------------------------ */

/* The size of the face a message hangs off. One number, because the gutter,
   the measure and the indent every line of a run sits on are all built off it
   — and because .avatar declares --avatar-size on itself, which a descendant
   cannot read back out, the face is handed this instead. */
.chat-transcript {
  --chat-face: 28px;

  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--sp-4) var(--sp-5) var(--sp-5);
  display: flex;
  flex-direction: column;
}

/* A three-message conversation rests on the composer instead of floating at
   the top of an empty well. The margin resolves to zero the moment the
   transcript overflows, so a long one is unaffected. */
.chat-transcript > :first-child {
  margin-block-start: auto;
}

/* The region scrolls, so it has to be reachable and visible from the keyboard.
   Inset, because both frames clip their overflow and a ring drawn outside
   would lose its inline edges and read as two loose bars. */
.chat-transcript:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 2px var(--ink-950), inset 0 0 0 4px var(--accent);
}

/* The transcript is a scrolling flex column, so its empty state centres itself
   in the well rather than sitting at the top of it. Only the centring is
   stated here — the band is `.empty--compact`, shared with every other empty
   state that reports on a small box. */
.chat-transcript > .empty {
  margin-block: auto;
}

/* Day rule — the date lives here so each message only carries its clock time,
   the same division the ledger draws between a day heading and a row. */
.chat-day {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-block: var(--sp-6) var(--sp-1);
}

.chat-day::before,
.chat-day::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

.chat-day__label {
  flex: none;
  color: var(--ink-400);
}

/* ---------------------------------------------------------------------------
   A line.

   The gutter is the face plus its gap, named because two other things are
   measured from it: the row's own width, and the indent the rest of a run
   hangs on. It collapses on the reader's own lines, which carry no face.
   ------------------------------------------------------------------------ */

.chat-msg {
  --chat-gutter: calc(var(--chat-face) + var(--sp-2));

  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  max-width: min(80%, calc(54ch + var(--chat-gutter)));
  margin-block-start: var(--sp-1);
}

/* The message that opens a run takes the wider gap; the ones following it stay
   tight against it, so a burst reads as one turn in the conversation. */
.chat-msg.is-lead {
  margin-block-start: var(--sp-5);
}

.chat-day + .chat-msg.is-lead {
  margin-block-start: var(--sp-2);
}

.chat-msg--them {
  align-self: flex-start;
}

.chat-msg--self {
  --chat-gutter: 0px;

  align-self: flex-end;
}

/* The column beside the face: name, bubble, clock. It is what carries the
   side, so a bubble narrower than the row still sits on its own edge. */
.chat-msg__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.chat-msg--them .chat-msg__body {
  align-items: flex-start;
}

.chat-msg--self .chat-msg__body {
  align-items: flex-end;
}

/* Drawn on the line that opens a run; held open as an empty box on every line
   under it, so the whole run stays hung off the one face. */
.chat-msg__face {
  flex: none;
  width: var(--chat-face);
}

.chat-msg__face .avatar {
  --avatar-size: var(--chat-face);
}

/* Nobody needs their own face to know they said it — the side and the "Você"
   are already that. Which side a line sits on is settled in the browser in a
   room, so this has to be CSS: the markup reaches every reader identical. */
.chat-msg--self .chat-msg__face {
  display: none;
}

/* A room's face is a link to the member's profile and answers a hover; a
   thread's is a plain mark, because there is one other person in it and the
   cover sheet above already carries their face straight to their profile.
   Selected on the element rather than on a second class: the difference is
   literally whether it is a link. */
a.chat-msg__face .avatar {
  transition: border-color var(--dur-1) var(--ease);
}

a.chat-msg__face:hover .avatar {
  border-color: var(--line-hard);
}

/* The name that opens a run, set as a name — not run through the uppercase
   micro-label treatment, which turns "Ana Beatriz Vasconcelos" into a tracked
   banner wider than the message under it, and not left at --ink-400 either:
   at the tertiary weight it read as a second timestamp, and the one thing a
   transcript is scanned for was its quietest mark. */
.chat-msg__who {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-1);
  /* The bubble's own inset, border included, so the name and the stamp line up
     with the first character of the message rather than sitting a ragged
     twelve pixels outside it. */
  padding-inline: calc(var(--sp-4) + 1px);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink-200);
  max-width: 100%;
}

/* The name truncates; the clock beside it never does. */
.chat-msg__name {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  unicode-bidi: isolate;
}

.chat-msg__bubble {
  padding: var(--sp-3) var(--sp-4);
  background: var(--ink-850);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
}

/* A full step of the ramp between the two sides, not the hairline --ink-800
   the room used to take: side and face are the load-bearing signals, but the
   fill is the one that survives a glance at a thumbnail, and two greys six
   hundredths apart were not a signal at all. Still a neutral — the orange is
   Enviar's. */
.chat-msg--self .chat-msg__bubble {
  background: var(--ink-750);
  border-color: var(--line-hi);
}

.chat-msg__text {
  margin: 0;
  font-size: var(--fs-base);
  line-height: var(--lh-body);
  color: var(--ink-100);
  /* Line breaks the sender typed are theirs to keep, and an address pasted
     with no spaces in it has to break somewhere rather than widen the bubble
     past the column. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* --- A picture -----------------------------------------------------------
   Sent on its own it is the bubble: no padding ring around it, because the
   frame would only draw a second border inside the first. With a caption the
   bubble keeps its padding and the picture sits above the text.
   ------------------------------------------------------------------------ */

.chat-msg__bubble--shot {
  padding: 0;
  overflow: hidden;
}

.chat-msg__shot {
  display: block;
  border-radius: var(--r-md);
  overflow: hidden;
}

.chat-msg__bubble--shot .chat-msg__shot {
  border-radius: 0;
}

.chat-msg__shot:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Capped both ways: wide enough to read a screenshot of a terminal, short
   enough that one picture cannot take the whole transcript. Tapping it opens
   the file at full size. */
.chat-msg__img {
  display: block;
  width: auto;
  max-width: 100%;
  max-height: 320px;
  object-fit: contain;
  background: var(--ink-950);
}

.chat-msg__shot + .chat-msg__text {
  margin-block-start: var(--sp-2);
}

.chat-msg__shot:hover .chat-msg__img {
  opacity: 0.92;
}

/* --- The clock -----------------------------------------------------------
   On the line that opens a run it sits beside the name, printed once for the
   whole run — which is at most RUN_GAP long, so it dates every bubble under it
   to within five minutes.

   On those bubbles it hangs in the margin instead, out of the flow entirely so
   it costs the measure nothing, and shown on hover or when anything inside the
   line takes focus. There is always room for it: a line is capped at 80% of
   the transcript and sits on its own side, so the fifth of the column on the
   other side is free by construction. Opacity rather than display, so it is
   still read out and still copied with the transcript.
   ------------------------------------------------------------------------ */

.chat-msg__time {
  flex: none;
  font-size: var(--fs-2xs);
  font-weight: 400;
  color: var(--ink-400);
}

.chat-msg__line {
  position: relative;
  min-width: 0;
  max-width: 100%;
}

.chat-msg__time--run {
  position: absolute;
  inset-block-end: var(--sp-2);
  opacity: 0;
  pointer-events: none;
  white-space: nowrap;
  transition: opacity var(--dur-2) var(--ease);
}

.chat-msg--them .chat-msg__time--run {
  inset-inline-start: calc(100% + var(--sp-2));
}

.chat-msg--self .chat-msg__time--run {
  inset-inline-end: calc(100% + var(--sp-2));
}

.chat-msg:hover .chat-msg__time--run,
.chat-msg:focus-within .chat-msg__time--run {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .chat-msg__time--run {
    transition: none;
  }
}

/* --- Lines that are marked out -------------------------------------------
   Being named is what a member scrolls a room looking for, so the line
   carrying it takes the accent on its edge and can be found while scrolling
   past rather than only read once it is reached. The border alone, at one
   weight all the way round: the mention chip inside is already the loud mark,
   and a bar down one side on top of it would be a third accent doing the
   second one's job. In a column of bubbles ruled in --line, one ruled in
   orange is found at a glance.

   Answering the notification lands on one line in the middle of a wall of
   them. The wash says which without moving anything, then gets out of the way
   — it has done its job the moment the eye arrives.
   ------------------------------------------------------------------------ */

.chat-msg.is-named .chat-msg__bubble {
  border-color: var(--accent-dim);
}

.chat-msg.is-flagged .chat-msg__bubble {
  animation: chat-flag 2.4s var(--ease) forwards;
}

@keyframes chat-flag {
  0%,
  55% {
    background: var(--accent-wash-hi);
    border-color: var(--accent);
  }
}

@media (prefers-reduced-motion: reduce) {
  .chat-msg.is-flagged .chat-msg__bubble {
    animation: none;
    border-color: var(--accent);
  }
}

/* ---------------------------------------------------------------------------
   The composer — the one primary action on either surface, so the one orange.

   Positioned, because a room hangs its name picker over the transcript from
   here. A thread has nothing to hang, and an empty containing block costs it
   nothing.
   ------------------------------------------------------------------------ */

.chat-composer {
  position: relative;
  flex: none;
  padding: var(--sp-3) var(--sp-4);
  background: var(--ink-900);
  border-block-start: 1px solid var(--line);
}

.chat-composer__row {
  display: flex;
  align-items: flex-end;
  gap: var(--sp-3);
}

/* Fill, border, hover and focus all come from .input; what is bespoke here is
   that the field is one line that grows into the message rather than a fixed
   box with a scrollbar in it. Height is set by the composer controller. */
.chat-composer__input {
  flex: 1;
  min-width: 0;
  min-height: 44px;
  padding-block: var(--sp-2);
  font-family: inherit;
  line-height: var(--lh-body);
  resize: none;
  overflow-y: hidden;
}

.chat-composer__send {
  flex: none;
  min-height: 44px;
}

.chat-composer__error {
  margin: var(--sp-2) 0 0;
}

/* ---------------------------------------------------------------------------
   Below 768px — the transcript spends its room on the conversation. Tighter
   insets on all three parts, and a line takes a little more of the column
   because 80% of a 360px screen is not a message, it is a word per line.
   ------------------------------------------------------------------------ */

@media (max-width: 767px) {
  .chat-transcript {
    padding: var(--sp-3) var(--sp-4) var(--sp-4);
  }

  .chat-msg {
    max-width: 86%;
  }

  .chat-composer {
    padding: var(--sp-3);
  }

  .chat-composer__send {
    padding-inline: var(--sp-3);
  }
}
