/* ==========================================================================
   home.css — the composer, the block caret, and the conversation.

   THE BOTTOM of the page is the frame's third anchor, and it is on every
   page: on Home it is the composer, and elsewhere it is one line saying
   what the page is. They share a cell in the status row, so a page change
   crossfades one into the next without anything moving. The row is always
   the composer's height, and the lines sit on its foot, so the band above
   never changes size between pages.

   THE COMPOSER blooms. At rest it is nothing but the block caret, blinking
   on the left axis. No words, no outline: the caret is the whole
   invitation. The moment there is something in it (a word, a photo), or a
   pointer over it, or keyboard focus on one of its tools, a white pill
   opens around it, held by a hairline, and the tools
   slide in at its right end one after another: the chat's mood, the
   microphone, send. (A + for a photo led the row; Tigo took it out. A photo still comes in by
   paste or by drop.) The words never move when it opens, because the
   pill's padding is always there, pulled back out to the axis by a negative
   margin at rest. On a touch screen, where nothing hovers, the pill simply
   stays open, and it stays open everywhere once a conversation has begun:
   by then you know where to type, and a pill that shut between questions
   would only make the page flicker at you.

   This replaced a pill that was always there, full width and 80px tall with
   36px type in it. It worked, and it was the heaviest thing on the page.
   Four leaner ways were built side by side (a line with a hairline under it,
   an outlined pill, a compact Messenger-sized one, and this), and this is
   the one Tigo kept. It had "Ask me anything" beside the caret too, until
   Tigo took the words out for the caret alone; the Home frame put them back
   for a while, as "Ask me anything:", and Tigo took them out again. (A
   small rainbow dot stood before the caret after that, turning the page's
   colours when pressed; it is out for now too.)

   THE FIELD is a real <textarea>. It draws its own text, so clicking to
   place the cursor, dragging a selection, an IME's underline, paste and the
   iOS loupe all behave natively. The only thing drawn by hand is the caret.

   THE CONVERSATION is a thread down the band from its top, as in
   Messenger: your questions in its blue on the right, the answers in the
   grey on the left. The answers hang out into the gutter by their own
   padding, like the composer, so their words stand on the axis the
   composer's words stand on; the questions stop short of the clear square,
   which keeps a column of its own. Everything in the thread arrives by
   fading in where it will stay (js/chat.js). (For a while both kinds ran
   down the left side, the answers under the questions; with nothing but
   colour to say who was speaking, a long thread read as one voice.)
   ========================================================================== */

.status {
  display: grid;
  align-items: end;
  min-height: var(--pill-h);
}
.status > *,
.status-home > * { grid-area: 1 / 1; min-width: 0; }
.status-home { display: grid; align-items: end; }
.status > [hidden], .status-home > [hidden] { display: none; }

/* ---- the pill ---- */
.composer {
  /* No drop shadow: the open pill is white paper on the page's paper, held
     by a single hairline, the way the menu and the bubbles sit flat. (It
     had a four-layer float; Tigo took the shadows out.) At rest the hairline
     is there at nothing, so opening only fades it up. */
  --edge: 0 0 0 1px rgb(var(--line) / var(--edge-a));
  --edge-a: 0;
  position: relative;
  display: flex;
  /* The tools stand on the pill's foot, so when a long question wraps they
     stay beside its last line, where you are typing, rather than floating
     up the middle of a taller pill. On one line it comes to the same. */
  align-items: flex-end;
  min-height: var(--pill-h);
  /* The pill's padding is there all along, so nothing shifts when it opens:
     at rest it is pulled back out by a negative margin, which keeps the
     words on the page's left axis and the pill's right end on the menu
     square's right edge. (For a while it stopped at the answer's measure;
     Tigo wanted the length back. For a while after that it reached past
     the square's edge, to meet questions that did too.) */
  padding: calc(10 * var(--u)) calc(10 * var(--u)) calc(10 * var(--u)) var(--hang);
  margin: 0 0 calc(-10 * var(--u)) calc(-1 * var(--hang));
  border-radius: calc(var(--pill-h) / 2);
  background: rgb(255 255 255 / 0);
  box-shadow: var(--edge);
  transition: background-color 0.35s ease, box-shadow 0.35s ease;
}

/* The words first, on the axis; every tool trails at the right end. The
   field keeps to the middle: on one line (on a phone, a shorter one than the
   tools) that centres the words in the pill, and once it wraps it is the
   tallest thing there, so the middle is everywhere. */
.prompt-field { order: 0; align-self: center; }
.composer-thumb { order: 1; }
.composer-mood { order: 2; --n: 0; }
.composer-mic { order: 3; --n: 1; }
.composer-send { order: 4; --n: 2; }

/* The spacing is set by what you see, not by the boxes. The tools' glyphs
   sit inside round touch areas with air of their own (the mic's is 14 on
   either side of its stroke, the chevron's about 5), so the boxes butt up
   against each other and the gaps come out of that air: the chevron sits
   close after its word, the mood stands about 24 off the mic, and the mic
   17 off the disc, so the two that do something read as a pair and the
   setting stands a step apart. (With a 4px gap and margins on top of that
   air, the mood stood 35 off the mic, twice the mic's distance to send.) */

/* At rest the tools are there (a Tab still reaches them, and focusing one
   opens the pill) but unseen, a little to the left of where they land. */
.composer :is(.composer-btn, .composer-mood, .composer-send) {
  opacity: 0;
  translate: calc(-8 * var(--u)) 0;
  transition:
    opacity 0.2s ease,
    translate var(--t-out) var(--spring-out),
    background-color 0.2s ease,
    color 0.2s ease,
    scale var(--t-pop) var(--spring-pop);
}

/* Open: something typed or attached, a photo dragged over, a pointer on it,
   a tool focused from the keyboard, the mood card up, a touch screen, or a
   conversation going (the frame's data-chat, js/main.js). */
.composer:is(.has-text, .has-image, .is-dropping, .is-touch, :hover,
             :has(:focus-visible:not(.prompt-input)), :has(.composer-mood.is-open)),
.frame[data-chat] .composer {
  --edge-a: 0.1;
  background: var(--surface);

  & .prompt-field { background: var(--surface); }
  & .prompt-caret { background: var(--caret-open); }
  & :is(.composer-btn, .composer-mood, .composer-send) {
    opacity: 1;
    translate: 0 0;
    /* in one after another, left to right, on the settling spring */
    transition:
      opacity 0.3s ease calc(60ms + var(--n) * 35ms),
      translate var(--t-settle) var(--spring-settle) calc(60ms + var(--n) * 35ms),
      background-color 0.2s ease,
      color 0.2s ease,
      scale var(--t-pop) var(--spring-pop);
  }
}
/* a photo dragged over it: the question bubble's blue, as a ring */
.composer.is-dropping {
  box-shadow: 0 0 0 2px var(--bubble);
}

/* ---- the tools ----
   Round, 44px to the touch, drawn in the icons' 28-unit box at a 1.75
   stroke, which at 32px lands at 2px: the same hand as the menu's icons,
   heavy enough to sit beside 36px type and no heavier. Resting in the
   quieter ink, they come up to full ink under the pointer. */
.composer-btn {
  flex: none;
  width: var(--pill-btn);
  height: var(--pill-btn);
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: var(--ink-3);
}
.composer-btn svg { width: var(--pill-icon); height: var(--pill-icon); }
.composer-btn[hidden] { display: none; }
@media (hover: hover) {
  .composer-btn:hover { background: rgb(var(--line) / 0.06); color: var(--fg); }
}
.composer-btn:active { scale: 0.9; }

/* listening: the microphone turns pink and a ring breathes out of it */
.composer-mic { position: relative; }
.composer-mic[aria-pressed='true'] { background: var(--pink); color: var(--mark); }
.composer-mic[aria-pressed='true']::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  animation: mic-listen 1.4s ease-out infinite;
}
@keyframes mic-listen {
  from { box-shadow: 0 0 0 0 color-mix(in oklab, var(--pink), transparent 0%); }
  to { box-shadow: 0 0 0 calc(12 * var(--u)) color-mix(in oklab, var(--pink), transparent 100%); }
}

/* Send: until there is something to send, only a pale arrow; then an ink
   disc a little smaller than the other tools' touch circles, so it reads
   as the one filled thing in the row, and it pops as it fills. (For a
   while it was a light blue disc, the question bubble's blue; Tigo wanted
   the ink back.) */
.composer-send {
  flex: none;
  width: var(--pill-btn);
  height: var(--pill-btn);
  display: grid;
  place-items: center;
  color: var(--ink-4);
}
.composer-send::before {
  content: '';
  grid-area: 1 / 1;
  width: var(--send-disc);
  height: var(--send-disc);
  border-radius: 50%;
  background: var(--fg);
  scale: 0.4;
  opacity: 0;
  transition: scale 0.18s ease, opacity 0.18s ease;
}
.composer-send svg {
  grid-area: 1 / 1;
  position: relative;
  width: calc(var(--pill-icon) * 0.8);
  height: calc(var(--pill-icon) * 0.8);
  transition: color 0.2s ease;
}
.composer-send:not(:disabled) { color: var(--surface); cursor: pointer; }
.composer-send:not(:disabled)::before {
  scale: 1;
  opacity: 1;
  transition: scale var(--t-pop) var(--spring-pop), opacity 0.15s ease;
}
.composer-send:disabled { cursor: default; }
.composer-send:not(:disabled):active { scale: 0.9; }
.composer-send:focus-visible { outline-offset: 0; }

/* the photo about to be sent: a small rounded print with a hairline edge,
   and an ink × at its corner to take it back */
.composer-thumb {
  position: relative;
  flex: none;
  width: var(--pill-btn);
  height: var(--pill-btn);
  margin: 0 calc(10 * var(--u)) 0 calc(2 * var(--u));
}
.composer-thumb[hidden] { display: none; }
.composer-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: calc(12 * var(--u));
  box-shadow: 0 0 0 1px rgb(30 30 30 / 0.08);
}
.composer-thumb-remove {
  position: absolute;
  top: calc(-5 * var(--u));
  right: calc(-5 * var(--u));
  width: calc(20 * var(--u));
  height: calc(20 * var(--u));
  min-width: 18px;
  min-height: 18px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--fg);
  color: var(--surface);
  box-shadow: 0 0 0 2px var(--surface);
  transition: scale var(--t-pop) var(--spring-pop);
}
@media (hover: hover) {
  .composer-thumb-remove:hover { scale: 1.12; }
}
.composer-thumb-remove svg { width: 60%; height: 60%; }

/* ---- the mood: the same three the live chat offers ----
   Each has one of the page's pastels, shown as a small dot: in the chip, so
   the current mood reads at a glance, and beside each choice. At 12px on
   white the pastels themselves all but vanish (the mint and the yellow are
   under 1.1:1), so each dot is its pastel taken a step deeper, the same hue
   the tiles and pills wear, only dense enough to be seen at the size of a
   full stop. */
/* the chip is shorter than the tools; lifted by half the difference, it is
   centred on the line they make */
.composer-mood {
  position: relative;
  flex: none;
  margin: 0 0 calc(2 * var(--u));
}
[data-mood='friendly'] { --mood: #7fdcaa; }
[data-mood='whimsical'] { --mood: #a7a3f4; }
[data-mood='suspicious'] { --mood: #ecd54f; }
.mood-dot {
  flex: none;
  width: calc(12 * var(--u));
  height: calc(12 * var(--u));
  min-width: 8px;
  min-height: 8px;
  border-radius: 50%;
  background: var(--mood, #7fdcaa);
  box-shadow: inset 0 0 0 1px rgb(30 30 30 / 0.1);
}
.composer-chip {
  display: flex;
  align-items: center;
  gap: calc(8 * var(--u));
  height: calc(40 * var(--u));
  /* 12 before the dot, and on the right the chevron's own 5 and 4 more:
     even, to the eye, when the chip's tint is up */
  padding: 0 calc(4 * var(--u)) 0 calc(12 * var(--u));
  border-radius: 999px;
  font-size: var(--pill-small);
  color: var(--ink-3);
  transition: background-color 0.2s ease, color 0.2s ease;
}
.composer-chip-caret {
  width: calc(var(--pill-icon) * 0.6);
  height: calc(var(--pill-icon) * 0.6);
  /* drawn in close, so it reads as the word's own mark, about 6 after it */
  margin-left: calc(-7 * var(--u));
  transition: rotate var(--t-snap) var(--spring-snap);
}
.composer-chip[aria-expanded='true'] .composer-chip-caret { rotate: 180deg; }
@media (hover: hover) {
  .composer-chip:hover { background: rgb(var(--line) / 0.06); color: var(--ink); }
}
.composer-chip[aria-expanded='true'] { background: rgb(var(--line) / 0.06); color: var(--ink); }

/* It opens upward, out of the chip, since the composer sits at the foot of
   the page: a small white card, flat like the pill, held by the same
   hairline. */
.mood-menu {
  position: absolute;
  right: calc(-6 * var(--u));
  bottom: calc(100% + 18 * var(--u));
  width: calc(300 * var(--u));
  padding: calc(6 * var(--u));
  border-radius: calc(22 * var(--u));
  background: var(--surface);
  box-shadow: 0 0 0 1px rgb(var(--line) / 0.1);
  transform-origin: calc(100% - 40 * var(--u)) 100%;
  visibility: hidden;
  opacity: 0;
  scale: 0.94;
  translate: 0 calc(8 * var(--u));
  transition:
    opacity 0.14s ease,
    scale 0.18s ease,
    translate 0.18s ease,
    visibility 0s linear 0.18s;
  z-index: 5;
}
.composer-mood.is-open .mood-menu {
  visibility: visible;
  opacity: 1;
  scale: 1;
  translate: 0 0;
  transition:
    opacity 0.18s ease,
    scale var(--t-pop) var(--spring-pop),
    translate var(--t-settle) var(--spring-settle),
    visibility 0s;
}
.mood-menu button {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  column-gap: calc(14 * var(--u));
  width: 100%;
  padding: calc(12 * var(--u)) calc(14 * var(--u)) calc(12 * var(--u)) calc(16 * var(--u));
  border-radius: calc(16 * var(--u));
  text-align: left;
  transition: background-color 0.15s ease;
}
@media (hover: hover) {
  .mood-menu button:hover { background: rgb(var(--line) / 0.05); }
}
.mood-menu button:focus-visible { outline-offset: -2px; }
.mood-menu .mood-dot { grid-row: 1 / span 2; width: calc(14 * var(--u)); height: calc(14 * var(--u)); }
.mood-name { grid-column: 2; font-size: var(--pill-small); color: var(--ink); line-height: 1.25; }
.mood-note { grid-column: 2; font-size: calc(var(--pill-small) * 0.72); color: var(--ink-3); line-height: 1.35; }
.mood-check {
  grid-column: 3;
  grid-row: 1 / span 2;
  width: calc(var(--pill-icon) * 0.62);
  height: calc(var(--pill-icon) * 0.62);
  color: var(--fg);
  opacity: 0;
  scale: 0.6;
  transition: opacity 0.15s ease, scale var(--t-pop) var(--spring-pop);
}
.mood-menu [aria-checked='true'] .mood-check { opacity: 1; scale: 1; }

/* ---- the field ---- */
.prompt-field {
  position: relative;
  flex: 1;
  min-width: 0;
  font-size: var(--pill-fs, var(--fs));
  margin: 0 calc(20 * var(--u)) 0 0;
  cursor: text;
  /* The caret is blended by `difference` so it can sit over a letter like a
     terminal's block. Blending needs a backdrop, so the field is its own
     group, on the paper at rest and on the pill's white once it opens (and
     fading between them with the pill, so no hard white box shows first). */
  isolation: isolate;
  background: var(--bg);
  transition: background-color 0.35s ease;
}

.prompt-input {
  display: block;
  width: 100%;
  height: calc(var(--lh) * 1em);
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  line-height: var(--lh);
  white-space: pre-wrap;
  overflow-wrap: break-word;
  resize: none;
  overflow: hidden;
  caret-color: transparent;
  outline: none;
  -webkit-appearance: none;
  appearance: none;
}
/* the greeting the caret types out and takes back (js/prompt.js) is the
   placeholder, in the words' own ink, as if it had been typed there */
.prompt-input::placeholder { color: var(--ink); opacity: 1; }
/* the native selection is the only highlight; the block hides while there is one */
.prompt-input::selection { background: var(--bubble); color: var(--bubble-ink); }

/* A twin of the textarea, laid out the same way and never seen. It holds the
   text up to the cursor, then a span with the rest, and the span's offset is
   where the caret goes. Same width, same wrapping, so the two break lines in
   the same places. */
.prompt-mirror {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  visibility: hidden;
  pointer-events: none;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

/* ---- the caret. The frame's block, 0.25em wide, and exactly as tall as a
   capital: from the cap line to the baseline, both measured off the type
   (js/prompt.js), so it stands beside a word like one more letter.
   Under `difference`, #b6b7b7 comes out as exactly #444343 over the paper
   (250 − 182 = 68), and #bbbcbc does the same over the open pill's white
   (255 − 187 = 68); over a letter it inverts the letter — the block cursor
   of a terminal, not a slab over the text. (Each theme has its own pair,
   css/tokens.css, worked out the same way.) ---- */
.prompt-caret {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--caret-w);
  height: var(--caret-h);
  translate: var(--caret-x, 0px) calc(var(--caret-y, 0px) + var(--caret-top));
  background: var(--caret-rest);
  mix-blend-mode: difference;
  pointer-events: none;
  z-index: 2;
  animation: caret-blink var(--t-blink) linear infinite;
}
.prompt-caret.is-gliding { transition: translate 90ms var(--ease-rise); }

/* The live caret's rhythm: 1.06s, lit for the first half. Its edges are
   softened to about 60ms rather than cut with steps(1). At this size a hard
   cut reads as a flicker; a 60ms fade still reads as a blink. */
@keyframes caret-blink {
  0%, 47% { opacity: 1; }
  53%, 94% { opacity: 0; }
  100% { opacity: 1; }
}

/* Real terminals hold the cursor solid while you type. prompt.js sets this
   on every keystroke and takes it off 530ms after the last one, and taking it
   off restarts the blink from lit. */
.prompt-caret.is-typing { animation: none; opacity: 1; }
.prompt-caret.is-hidden { visibility: hidden; }

/* Once the page is scrolled at all, the caret goes quiet: it fades out, and
   stays out until the page is back at the top of Home, where it picks up its
   blink again from lit. A caret blinking at the foot of Home while you read
   Work is the page asking for something at the wrong moment. */
.prompt-caret { transition: opacity 0.2s ease; }
.frame[data-scrolled] .prompt-caret {
  animation: none;
  opacity: 0;
}

/* A Windows high-contrast theme repaints everything in its own colours, and a
   blended block would vanish. There the native caret comes back. */
@media (forced-colors: active) {
  .prompt-caret { display: none; }
  .prompt-input { caret-color: auto; }
}

/* ---- the thread ----
   It fills the band and scrolls inside it, its scrollbar hidden, and keeps
   a little air at its foot above the composer. It reaches out into the left
   gutter as far as the answers hang, so their edges are not clipped.

   It starts at the top, just under where the name was, and each turn goes
   in under the last. (For a while it rested on the composer and grew up out
   of it, as in a messenger; with one short exchange that left the top of
   the page empty.)

   On the right it stops short of the clear square, by the 28px the menu
   leaves between its square and its pills, so the square has a column of
   its own from the top of the page to the composer and nothing ever sits
   under it. (The questions ended on the square's edge, and then a little
   past it; either way a thread scrolled up passed under the square, and at
   rest the first question sat right below it.) With that column clear, the
   thread reaches up past the band to the top of the window, so what you
   scroll back through goes off the top of the page the way a page would.
   It is padding as well as margin, so a thread at rest still starts at the
   band.

   On a phone there is no room for the square's column, so there the
   thread runs the full width and starts under the square instead.

   ITS EDGES are soft, but only where nothing rests: across the top of the
   window, and in the air between the last turn and the composer. What you
   scroll back through fades out and goes a little out of focus as it
   reaches either one: a mask for the fade, and over each edge a strip of
   backdrop blur, itself masked, so the blur is only at the very edge and
   none by the time the fade is done. Turns at rest never meet either edge
   (the bottom one is the thread's own foot padding, the top one sits above
   where the band begins), so a line of an answer is never dimmed while you
   read it. (The edges faded before, into the thread itself and only while
   there was more beyond them, which dimmed the very line an answer was
   arriving on; then for a while they were cut sharp.) */
.view-home { display: grid; }

.thread {
  min-height: 0;
  overflow-y: auto;
  /* at either end of the conversation the wheel goes on to the page, so
     Home scrolls on down to Work from anywhere on it */
  overscroll-behavior: auto;
  scrollbar-width: none;
  margin: calc(-1 * var(--reach)) calc(var(--menu-size) + 28 * var(--u)) 0 calc(-1 * var(--hang));
  padding: var(--reach) 0 var(--edge-b);
  /* the top fades on an ease, so it stays nearly whole until close to the
     window's edge; the bottom one is short enough to be a straight ramp */
  --edges: linear-gradient(to bottom,
    transparent,
    rgb(0 0 0 / 0.45) calc(var(--edge-t) * 0.35),
    rgb(0 0 0 / 0.85) calc(var(--edge-t) * 0.7),
    #000 var(--edge-t),
    #000 calc(100% - var(--edge-b)),
    transparent);
  -webkit-mask-image: var(--edges);
  mask-image: var(--edges);
}
.thread::-webkit-scrollbar { display: none; }

/* the two strips of blur, laid over the thread's edges and no wider than
   it, so the clear square and the composer are never blurred */
.view-home::before,
.view-home::after {
  content: '';
  position: absolute;
  left: calc(-1 * var(--hang));
  right: calc(var(--menu-size) + 28 * var(--u));
  pointer-events: none;
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}
.view-home::before {
  top: calc(-1 * var(--reach));
  height: var(--edge-t);
  -webkit-mask-image: linear-gradient(to bottom, #000, transparent);
  mask-image: linear-gradient(to bottom, #000, transparent);
}
.view-home::after {
  bottom: 0;
  height: var(--edge-b);
  -webkit-mask-image: linear-gradient(to top, #000, transparent);
  mask-image: linear-gradient(to top, #000, transparent);
}

/* A turn is one question and its answer, on their own sides, a little
   apart; the next turn stands further off, so the pairs read as pairs. */
.turn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: calc(12 * var(--u));
}
.turn + .turn { margin-top: calc(36 * var(--u)); }

/* ---- the bubbles ----
   The frame's 36px type in both. A one-line bubble is exactly a menu pill's
   68px (43 of line and 12.5 above and below), and its radius is half of
   that, so a short question or answer is a true pill, and a long answer a
   block with the same round corners. An answer's words wrap at the page's
   measure, 24em, the width its longest lines have always had. A question
   wraps sooner, at 18em: it is the shorter voice, and a question that ran
   as wide as an answer would reach back across to the answers' side and
   blur which side is which.

   An answer's words start on the left axis and its bubble hangs out past
   it into the gutter; a question ends on the thread's right edge, short of
   the clear square's column. */
.bubble {
  position: relative;
  max-width: min(100%, calc(24em + 2 * var(--hang)));
  padding: calc(12.5 * var(--u)) var(--hang);
  border-radius: calc((var(--lh) * 1em + 25 * var(--u)) / 2);
  color: var(--bubble-ink);
  overflow-wrap: break-word;
}
.bubble-q {
  align-self: flex-end;
  max-width: min(78%, calc(18em + 2 * var(--hang)));
  background: var(--bubble);
}
.bubble-a { background: var(--pill); }
/* The page's selection is a blue, which vanishes on the question's blue:
   there it is the bubble's own blue taken two steps deeper. */
.bubble-q ::selection { background: var(--bubble-select); }

/* A photo sent with a question stands over its bubble, on the same side, a
   small print with the bubbles' round corners. Its height is fixed, so the
   thread never moves while it loads; a very wide one is cropped rather than
   shrunk. */
.turn-photo {
  align-self: flex-end;
  height: calc(168 * var(--u));
  width: auto;
  max-width: min(100%, calc(420 * var(--u)));
  object-fit: cover;
  border-radius: calc(22 * var(--u));
  box-shadow: 0 0 0 1px rgb(30 30 30 / 0.06);
}

.bubble-text {
  position: relative;
  min-height: calc(var(--lh) * 1em);
  white-space: pre-line;
}

/* Thinking: the answer's bubble, small, with three dots in it that darken
   one after another, the sign every messenger uses for the other side
   writing. The bubble is a little wider than tall, so it reads as a bubble
   and not a dot. (A block caret used to breathe in here, having flown up
   from the composer with the question.) */
.bubble-a.is-thinking { width: calc((var(--lh) * 1em + 25 * var(--u)) * 1.5); }
.bubble-a.is-thinking .bubble-text {
  display: flex;
  align-items: center;
  justify-content: center;
}
.dots { display: flex; gap: 0.2em; }
.dots i {
  width: 0.24em;
  height: 0.24em;
  border-radius: 50%;
  background: var(--ink-2);
  opacity: 0.35;
  animation: dot 1.2s ease-in-out infinite;
}
.dots i:nth-child(2) { animation-delay: 0.16s; }
.dots i:nth-child(3) { animation-delay: 0.32s; }
@keyframes dot {
  0%, 60%, 100% { opacity: 0.35; }
  30% { opacity: 1; }
}

/* An answer that never came: the bubble's outline only, in the quieter ink */
.bubble-a.is-error {
  background: transparent;
  box-shadow: inset 0 0 0 1px rgb(var(--line) / 0.14);
  color: var(--ink-3);
}

.bubble-text a {
  text-decoration: underline;
  text-decoration-color: color-mix(in oklab, var(--bubble), var(--bubble-ink) 22%);
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.14em;
  text-decoration-skip-ink: none;
}
@media (hover: hover) {
  .bubble-text a:hover { text-decoration-color: var(--bubble-ink); }
}

@media (prefers-reduced-motion: reduce) {
  /* the composer still opens, but by fading alone: no slide, no pop */
  .composer :is(.composer-btn, .composer-mood, .composer-send),
  .composer:is(.has-text, .has-image, .is-dropping, .is-touch, :hover,
               :has(:focus-visible:not(.prompt-input)), :has(.composer-mood.is-open))
    :is(.composer-btn, .composer-mood, .composer-send),
  .frame[data-chat] .composer :is(.composer-btn, .composer-mood, .composer-send) {
    translate: 0 0;
    transition: opacity 0.15s ease, background-color 0.2s ease, color 0.2s ease;
  }
  .composer-send::before, .composer-send:not(:disabled)::before { scale: 1; transition: opacity 0.15s ease; }
  .mood-menu, .composer-mood.is-open .mood-menu { scale: 1; translate: 0 0; transition: opacity 0.15s ease, visibility 0s linear 0.15s; }
  .composer-mood.is-open .mood-menu { transition: opacity 0.15s ease, visibility 0s; }
  .composer-btn:active, .composer-send:not(:disabled):active { scale: 1; }
  /* a blinking caret is motion too (WCAG 2.2.2): under reduced motion it
     stands still, solid */
  .prompt-caret { animation: none; opacity: 1; }
  .prompt-caret.is-gliding { transition: none; }
  .dots i { animation: none; opacity: 0.6; }
}

@media (max-width: 760px) {
  /* a phone's 20px gutter has room for the pill (and the bubbles) to reach
     14px past the axis, not 24 */
  .composer { padding: 6px 6px 6px var(--hang); margin: 0 0 -6px calc(-1 * var(--hang)); }
  .thread { margin-right: 0; }
  .view-home::before, .view-home::after { right: 0; }
  .bubble { padding: 10px var(--hang); border-radius: calc((var(--lh) * 1em + 20px) / 2); }
  .bubble-a.is-thinking { width: calc((var(--lh) * 1em + 20px) * 1.5); }
  .turn { gap: 8px; }
  .turn + .turn { margin-top: 24px; }
  .turn-photo { height: 120px; border-radius: 16px; }
  /* the mood's dot stands in for its word, where the field needs the room */
  .composer-chip { height: 36px; padding: 0 6px 0 12px; gap: 4px; }
  .composer-chip-word { display: none; }
  /* with no word to follow, the chevron stands off the dot by about 6 */
  .composer-chip-caret { margin-left: -2px; }
  .mood-menu { min-width: 0; width: min(260px, calc(100vw - 40px)); }
}
