/*
 * The conversation, ported from the `.demo__` mockup at pretor.ar (see conversations/_message and
 * siblings). Renamed for what these are in a real app rather than what they are in a marketing demo:
 * `.chat__msg--in` / `--out` instead of `.demo__msg--in` / `--out`, and so on.
 *
 * Always the dark world: ConversationsController sets it, so the raw dark tokens (--black, --panel,
 * --gold...) are used directly here rather than through pretor-worlds' semantic layer, the same way
 * the source CSS does. Mobile first throughout; `@media (width >= 48rem)` is the only enhancement.
 */

.chat {
  display: flex;
  flex-direction: column;
  /* min() rather than a bare 720px: a flex column still hands its children the container's width,
     but any descendant that cannot shrink (a filename, a format badge, an unbroken citation) sets
     the min-content width of everything above it, and on a phone that is what pushed the panel past
     the edge of the screen. Capping here contains it no matter what arrives from core. */
  max-width: min(720px, 100%);
  margin-inline: auto;
  background: var(--black);
  border: 1px solid var(--dline);
  border-radius: var(--radius);
  box-shadow: 22px 40px -28px rgba(40, 24, 12, .35);
  overflow: hidden;
}

.chat__bar {
  display: flex;
  align-items: center;
  gap: .8rem;
  padding: .85rem 1.2rem;
  background: var(--panel);
  border-bottom: 1px solid var(--dline);
}

.chat__avatar {
  display: grid;
  place-items: center;
  flex: none;
  width: 2.2rem;
  height: 2.2rem;
  border-radius: 50%;
  font-family: var(--serif);
  font-weight: 600;
  color: var(--black);
  background: linear-gradient(150deg, var(--gold-soft), var(--gold));
}

.chat__bar-info {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.chat__peer {
  color: var(--marble);
  font-weight: 600;
  letter-spacing: .02em;
}

/* The one place this reads: it names the matter the next turn will thread on, so it sits where a
   client is already looking, not in a stray paragraph above the fold. */
.chat__case {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .02em;
  color: var(--dmuted);
}

.chat__status {
  margin-left: auto;
  flex: none;
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .08em;
  color: var(--gold);
}

.chat__scroll {
  display: flex;
  flex-direction: column;
  gap: .7rem;
  margin: 0;
  padding: clamp(1.1rem, 4vw, 1.5rem);
  list-style: none;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  max-height: min(60dvh, 640px);
  background:
    radial-gradient(120% 90% at 85% 0%, rgba(205, 164, 85, .06), transparent 55%),
    var(--black);
}

.chat__msg {
  position: relative;
  max-width: 88%;
  /* Pretor answers with statute citations and official URLs, and a URL has no break opportunity in
     it. Without this one link decides how wide the conversation is. */
  overflow-wrap: anywhere;
  padding: .8rem 1rem .6rem;
  border-radius: 12px;
  font-size: .92rem;
  line-height: 1.55;
}

/* Pretor's turn is markdown, rendered by Markup::Render, and it lands as blocks directly inside the
   bubble. The rules below are the whole of the typography for it: an answer cites statutes, quotes
   articles and tabulates vigencia, so headings, quotes, code and tables all have to read inside a
   88%-wide bubble on a 360px screen without becoming a document.

   Sized down rather than up. A bubble is not a page: an h2 that keeps its page-level size makes the
   answer look like a section of the site rather than something said in a conversation.

   The two rules below are the whole spacing model, and they are written as one pair on purpose. A
   per-element `margin: 0` (which is what `.chat__msg p` used to be) is more specific than the
   sibling gap, so it wins the cascade whatever the order, and every block in a multi-paragraph
   answer would sit flush against the next with nothing in the suite able to see it. So the reset
   and the gap share a specificity and the gap comes second. Nothing below this may set a margin on
   a direct child of the bubble. */
.chat__msg > * {
  margin: 0;
}

.chat__msg > * + * {
  margin-top: .6rem;
}

/* The prose markdown nests inside a block, which the reset above cannot reach. */
.chat__msg li p,
.chat__msg blockquote p {
  margin: 0;
}

.chat__msg h1,
.chat__msg h2,
.chat__msg h3,
.chat__msg h4,
.chat__msg h5,
.chat__msg h6 {
  font-family: var(--sans);
  font-size: 1em;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--gold-soft);
}

.chat__msg ul,
.chat__msg ol {
  padding-left: 1.25rem;
}

.chat__msg li + li {
  margin-top: .25rem;
}

.chat__msg a {
  color: var(--gold);
  text-underline-offset: .18em;
}

.chat__msg blockquote {
  padding-left: .75rem;
  border-left: 2px solid var(--gold);
  color: var(--dmuted);
  font-style: italic;
}

.chat__msg code {
  font-family: var(--mono);
  font-size: .88em;
  padding: .1em .3em;
  border-radius: 4px;
  background: rgba(200, 163, 91, .1);
}

/* A quoted article is the one thing in an answer that must not be reflowed, so the block scrolls
   sideways rather than wrapping, and the code inside it drops the inline pill background. */
.chat__msg pre {
  padding: .6rem .7rem;
  border-radius: 8px;
  background: rgba(0, 0, 0, .35);
  border: 1px solid var(--dline);
  overflow-x: auto;
}

.chat__msg pre code {
  padding: 0;
  background: none;
}

/* Its own scroller for the same reason, and the bubble stays the width the conversation decided. */
.chat__msg table {
  display: block;
  overflow-x: auto;
  border-collapse: collapse;
  font-size: .92em;
}

.chat__msg th,
.chat__msg td {
  padding: .3rem .5rem;
  border: 1px solid var(--dline);
  text-align: left;
  vertical-align: top;
}

.chat__msg th {
  color: var(--gold-soft);
  font-weight: 600;
}

.chat__msg hr {
  border: 0;
  border-top: 1px solid var(--dline);
}

.chat__msg--in {
  align-self: flex-start;
  border-bottom-left-radius: 4px;
  background: var(--panel-2);
  border: 1px solid var(--dline);
  color: #d9d0bd;
}

.chat__msg--in::before {
  content: "";
  position: absolute;
  bottom: 0;
  left: -7px;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 0 0 10px 8px;
  border-color: transparent transparent var(--panel-2) transparent;
}

.chat__msg--out {
  align-self: flex-end;
  border-bottom-right-radius: 4px;
  background: color-mix(in srgb, var(--oxblood) 52%, var(--black));
  color: var(--marble);
}

.chat__msg--out::before {
  content: "";
  position: absolute;
  bottom: 0;
  right: -7px;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 0 8px 10px 0;
  border-color: transparent transparent color-mix(in srgb, var(--oxblood) 52%, var(--black)) transparent;
}

/* The answer as it is being written. Two affordances for the two things an open bubble can mean, and
   both are drawn off :empty rather than off a class the server toggles: core sends one token at a
   time and does not re-render the bubble to say the first one landed, so the paragraph having
   content is the only signal that exists at that moment. conversations/_streaming_message renders
   this element with tag.p for the same reason, a newline inside it would be a text node and would
   make it non-empty before any token arrived. */
.chat__stream {
  /* Core streams its own newlines and this is the one place text reaches the page without going
     through simple_format, which would have made paragraphs of them. */
  white-space: pre-line;
}

/* Nothing written yet: the three dots every chat app has trained a client to read as "answering". */
.chat__msg--writing .chat__stream:empty::after {
  content: "•••";
  letter-spacing: .18em;
  color: var(--gold);
  animation: chat-dots 1.4s var(--ease) infinite;
}

/* Mid-sentence: a caret, which is the smaller claim. The dots say "about to speak" and would be a
   lie next to text that is already arriving. */
.chat__msg--writing .chat__stream:not(:empty)::after {
  content: "";
  display: inline-block;
  width: .5ch;
  height: .95em;
  margin-left: .12em;
  vertical-align: text-bottom;
  background: var(--gold);
  animation: chat-caret 1s steps(2, start) infinite;
}

/* The dots and a growing answer are two live affordances for one thing, so the dots come down once
   the first token lands, and come back if Pretor keeps working after the message ends. Hidden rather
   than removed from the timeline: #typing's lifetime is the run's, and it is what tells the composer
   the turn is still open (see timeline_controller). */
.chat__scroll:has(.chat__msg--writing:not(#typing)) #typing {
  display: none;
}

@keyframes chat-dots {
  0%, 100% { opacity: .35 }
  50% { opacity: 1 }
}

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

/* Both of these blink, which is exactly the kind of motion this setting is asked for. The
   affordance stays; only the animation stops. */
@media (prefers-reduced-motion: reduce) {
  .chat__msg--writing .chat__stream::after {
    animation: none;
  }
}

.chat__meta {
  display: block;
  margin-top: .3rem;
  text-align: right;
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .06em;
  color: color-mix(in srgb, var(--dmuted) 70%, transparent);
}

/* core's own heartbeat line: a gold dashed pill, the site's `.demo__sys`. Its container stays
   outside #timeline on purpose (see conversations/show): it is what Pretor is doing right now, not
   a turn that happened, so replacing it wholesale on every heartbeat is correct where appending
   into the timeline is not. */
.chat__activity {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .4rem;
  padding: 0 clamp(1.1rem, 4vw, 1.5rem);
}

.chat__activity--empty {
  display: none;
}

.chat__sys {
  align-self: center;
  margin: 0;
  padding: .2rem .9rem;
  border: 1px dashed color-mix(in srgb, var(--gold) 35%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--gold) 6%, transparent);
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .04em;
  color: var(--gold);
  text-align: center;
}

/* The strip: one task, one line, replaced in place. Its parts sit on one row rather than on three,
   because the three lines it used to show were a chat message that had been shipped here whole. */
.chat__task-label {
  font-weight: 600;
}

.chat__task-detail,
.chat__task-elapsed,
.chat__task-count {
  opacity: .75;
}

/* Separator dots between whichever of the strip's parts are present. Drawn by the element that
   FOLLOWS, except around the bar: `<progress>` is a replaced element, so its own ::before is never
   rendered and it cannot draw the dots in front of it. Those come from the element ahead of it
   instead, and only when a bar is really there, which is the counted case alone. */
.chat__task-label + *::before,
.chat__task-detail + *::before,
.chat__task-elapsed + *::before,
.chat__task-bar + *::before,
.chat__task-label:has(+ .chat__task-bar)::after,
.chat__task-detail:has(+ .chat__task-bar)::after,
.chat__task-elapsed:has(+ .chat__task-bar)::after {
  content: " · ";
  opacity: .5;
}

/* Only ever drawn when core sent a real denominator; there is no percentage on the wire without
   one. Sized down to a hairline so it reads as a measurement rather than as decoration. */
.chat__task-bar {
  width: 4rem;
  height: .35rem;
  vertical-align: middle;
}

/* The list: what has happened this run, accumulating. Nothing here is ever removed, which is the
   whole point of it being a list and not a strip. */
.chat__steps {
  display: flex;
  flex-direction: column;
  gap: .15rem;
  margin: 0;
  padding: 0 clamp(1.1rem, 4vw, 1.5rem);
  list-style: none;
}

.chat__step {
  display: flex;
  align-items: baseline;
  gap: .45rem;
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .04em;
  color: var(--gold);
}

/* A finished step stays on the page and steps back, so the live one is the one the eye lands on.
   Dimming rather than removing is the direction a lawyer asked to see. */
.chat__step--done {
  opacity: .5;
}

.chat__step-mark {
  width: .8rem;
  text-align: center;
}

.chat__step[aria-current] .chat__step-mark {
  animation: chat-step-pulse 1.2s ease-in-out infinite;
}

@keyframes chat-step-pulse {
  50% { opacity: .25; }
}

@media (prefers-reduced-motion: reduce) {
  .chat__step[aria-current] .chat__step-mark {
    animation: none;
  }
}

.chat__doc {
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: .7rem;
  max-width: 88%;
  padding: .55rem .8rem;
  border: 1px solid var(--dline);
  border-radius: 10px;
  background: var(--panel);
  font-family: var(--mono);
  font-size: .78rem;
  color: #d9d0bd;
}

/* Generated filenames run long ("demanda-por-despido-2026-07-27.docx") and carry no space to break
   on, so the name truncates rather than deciding the width of the chip that holds it. min-width
   because a flex item defaults to min-width:auto and would otherwise refuse to shrink at all. */
.chat__doc a {
  color: inherit;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat__doc-ico {
  flex: none;
  padding: .3em .5em;
  border: 1px solid color-mix(in srgb, var(--gold) 45%, transparent);
  border-radius: 6px;
  font-size: .62rem;
  letter-spacing: .1em;
  color: var(--gold);
}

.chat__actions {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
  margin-top: .8rem;
  padding-bottom: .3rem;
}

/* The two forms exist for the two independent PATCH requests; `display: contents` lets their
   buttons sit in the flex row above as if the forms were not there at all. */
.chat__action-form {
  display: contents;
}

.chat__btn {
  padding: .5em 1.1em;
  border-radius: 999px;
  border: 1px solid color-mix(in srgb, var(--gold) 50%, transparent);
  background: transparent;
  font-family: var(--sans);
  font-size: .8rem;
  font-weight: 600;
  color: var(--gold-soft);
  cursor: pointer;
  transition: background .2s var(--ease), color .2s var(--ease);
}

.chat__btn:hover {
  background: color-mix(in srgb, var(--gold) 12%, transparent);
}

.chat__btn--ok {
  background: var(--gold);
  border-color: var(--gold);
  color: var(--black);
}

.chat__btn--ok:hover {
  background: var(--gold-soft);
}

/* Saying an answer was wrong (PRE-77). Not a bubble: it is about the answer above it rather than
   another turn in the conversation, so it sits under that bubble as a quiet line and never carries
   the panel or the tail a .chat__msg does. */
.chat__report {
  margin: -.4rem 0 .4rem;
  padding: 0 .2rem;
  list-style: none;
}

/* The disclosure is what keeps a text field off the page under every answer: the composer is what
   the lawyer is writing in, and a second box competing with it would be the louder mistake. */
.chat__report-open {
  display: inline-block;
  padding: .15rem 0;
  color: var(--dmuted);
  font-family: var(--sans);
  font-size: .72rem;
  cursor: pointer;
  transition: color .2s var(--ease);
}

.chat__report-open:hover,
details[open] > .chat__report-open {
  color: var(--gold-soft);
}

.chat__report-open:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

/* What leaves the firm, said before the click. Quiet, because it is a fact rather than a warning,
   and still legible: a disclosure nobody can read is not one. */
.chat__report-note {
  /* Its own row. The form is a wrap flex container and the note is a shrink-to-fit item, so without
     a full basis it competes with the input for the first row and lands BESIDE the box on a
     desktop-width column -- which is not "before the click", which is the whole point of it. */
  flex: 1 1 100%;
  margin: .5rem 0 0;
  max-width: 42ch;
  color: var(--dmuted);
  font-family: var(--sans);
  font-size: .72rem;
  line-height: 1.45;
}

/* Wraps at 360px, which is where this is designed: the field takes the row and the button drops
   under it rather than squeezing both into an unreadable line. */
.chat__report-form {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: .5rem;
}

.chat__report-input {
  flex: 1 1 14rem;
  min-width: 0;
  padding: .5rem .8rem;
  border: 1px solid var(--dline);
  border-radius: var(--radius-sm);
  background: var(--black);
  color: var(--marble);
  font-family: var(--sans);
  font-size: .85rem;
}

.chat__report-input::placeholder {
  color: var(--dmuted);
}

.chat__report-input:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

.chat__composer {
  display: flex;
  align-items: flex-end;
  gap: .6rem;
  padding: .85rem 1.2rem;
  background: var(--panel);
  border-top: 1px solid var(--dline);
}

/* Auto-grows via the composer Stimulus controller, which sets height from scrollHeight on input.
   The CSS only bounds it, so a runaway paste cannot grow the box past a readable size. */
.chat__input {
  flex: 1;
  resize: none;
  min-height: 2.6rem;
  max-height: 11rem;
  overflow-y: auto;
  padding: .65rem .9rem;
  border: 1px solid var(--dline);
  border-radius: var(--radius-sm);
  background: var(--black);
  color: var(--marble);
  font-family: var(--sans);
  font-size: .92rem;
  line-height: 1.4;
}

.chat__input::placeholder {
  color: var(--dmuted);
}

/* Closed for as long as Pretor is answering. Dimmed rather than hidden, and the text stays legible,
   because a client whose dictated paragraph is sitting in there has to be able to read it while they
   wait. */
.chat__input:disabled,
.chat__send:disabled {
  opacity: .55;
  cursor: not-allowed;
}

.chat__input:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

.chat__send {
  flex: none;
  padding: .65em 1.2em;
  border: 0;
  border-radius: 999px;
  background: var(--gold);
  color: var(--black);
  font-weight: 600;
  font-size: .85rem;
  cursor: pointer;
  transition: background .2s var(--ease);
}

.chat__send:hover {
  background: var(--gold-soft);
}

@media (width >= 48rem) {
  .chat__scroll {
    max-height: min(65dvh, 720px);
  }
}

/* The composer carries four controls and two announcements now, so it wraps: the textarea keeps the
   first row and the announcements sit under it. */
.chat__composer {
  flex-wrap: wrap;
}

/* The drop zone is a <label>, styled as the button it behaves like. :focus-within rather than
   :focus, because the thing that actually takes focus is the visually-hidden input inside it —
   without this a keyboard client would tab onto an attach control with no visible focus at all. */
.chat__drop {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.6rem;
  height: 2.6rem;
  border: 1px dashed var(--dline);
  border-radius: var(--radius-sm);
  color: var(--dmuted);
  cursor: pointer;
  transition: border-color .2s var(--ease), background .2s var(--ease);
}

.chat__drop:hover,
.chat__drop--over {
  border-color: var(--gold);
  background: color-mix(in srgb, var(--gold) 12%, transparent);
}

.chat__drop:focus-within {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

.chat__record {
  flex: none;
  padding: .65em 1.1em;
  border: 1px solid var(--dline);
  border-radius: 999px;
  background: transparent;
  color: var(--marble);
  font-family: var(--sans);
  font-size: .85rem;
  cursor: pointer;
}

.chat__record:disabled,
.chat__drop:has(:disabled) {
  opacity: .55;
  cursor: not-allowed;
}

/* Both announcements take the whole width under the row, so neither can squeeze the textarea.
   Empty is the resting state: a live region with nothing in it says nothing. */
.chat__attachment,
.chat__upload {
  flex-basis: 100%;
  font-size: .78rem;
  color: var(--dmuted);
}

.chat__attachment:empty {
  display: none;
}

.chat__upload {
  height: .35rem;
  margin-top: .4rem;
}

/* The upload chip, which is conversations/_document pointing the other way: the same shape, on the
   client's side of the conversation. */
.chat__doc--out {
  align-self: flex-end;
}

.chat__doc-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
