@layer components {
  /* ——— The chat composer ———
     One written line; attach and @ wait beside it; ↵ sends (HANSHI 10). The
     writing surface is the lean composer lexxy.css already draws; this file
     only says what chalet's composer does around it — the pending files above
     the line, the toolbar that wakes on the toggle, the typing indicator,
     the offline state. */
  .chat-composer {
    flex: none;
    /* The typing line hangs off this box's top edge (below). */
    position: relative;
    /* Its own breathing room at BOTH edges, chalet's `padding:
       var(--block-space-half) …`. The composer owns the space it stands in,
       so it reads the same in the sheet and in the panel, keyboard up or
       down. Borrowed from the sheet's foot instead, it was 16px on the page
       and nothing at all in the panel, where the send button sat on the box's
       edge (user, 2026-08-30). */
    padding-block: var(--block-space-half);
    /* The stream's own inset (messages.css), so the line and the send sit
       on the faces' edges, and the rule alone runs to the box. */
    padding-inline: var(--room-inset, var(--inline-space-double));
    /* The rule runs the width of the stream — under the buttons too — so
       the composer and the lines above it share one edge. */
    border-block-start: var(--border);
  }

  /* The rule is the composer's top edge, so the line itself draws none; and
     one line in BOTH states, which is what a chat asks for.

     `--composer-rows` IS A FLOOR, NOT A CEILING. The gem spends it as
     `min-block-size` (lexxy-editor.css), so the editor grows with the words
     on its own and this number only says how tall it is when there are none.
     Read as a ceiling it looks like a line that cannot grow, and raising it to
     the compact composer's 6lh gave the chat a six-line box the moment you
     touched it — placeholder at the top, send button stranded at the bottom,
     the sheet above it squeezed (user, 2026-08-30). Chalet says the same thing
     the same way: one small `--lexxy-editor-rows` and nothing else. */
  .chat-composer__input.composer--compact {
    --composer-rows: 1lh;
    --composer-rows-at-rest: 1lh;

    padding-block-start: 0;
    border-block-start: none;
  }

  /* The words and the buttons on one line: the gem pads its content 1ch on
     every side, which lifted a single line's text a third above the 28px
     circles it sits beside (user, 2026-08-23). No block padding, and the
     line's end-alignment does the rest — a longer line grows upward and the
     buttons stay at its foot, chalet's way. */
  .chat-composer__input .lexxy-editor__content {
    padding-block: 0;

    /* And no rhythm after the last paragraph: the gem's content margin
       belongs between paragraphs, not under the line. Left on, it made the
       settled editor 12px taller than the placeholder Lexical replaces — the
       editor builds its content ASYNC after the element is defined, so the
       whole composer rose by that much one frame after every open, the one
       shift left once the stream stopped flicking (user, 2026-08-30;
       measured frame by frame). Trimmed, rest and settled stand level. */
    > :last-child {
      margin-block-end: 0;
    }
  }

  /* The toolbar is closed until the toggle opens it (composer_controller),
     unlike the lean composer's focus-wake: in a chat ↵ sends, and a toolbar
     that appeared on focus would announce formatting nobody asked for. */
  .chat-composer__input lexxy-toolbar {
    display: none;
  }

  .chat-composer--rich-text .chat-composer__input lexxy-toolbar {
    display: flex;
    /* The gem grounds its toolbar in canvas, which reads as a white strip on
       the panel's tint; the tools stand on whatever the composer stands on. */
    background-color: transparent;
  }

  /* A phone types plainly (chalet): the toolbar option only exists where a
     mouse does. After the flex rule above, so a stale --rich-text class
     cannot re-open what the device cannot use. */
  @media (any-hover: none) {
    .chat-composer__rich-text,
    .chat-composer--rich-text .chat-composer__input lexxy-toolbar {
      display: none;
    }
  }

  /* The composer sits at the foot of its column — the sheet's, the panel's —
     so the toolbar's dropdowns (link, colour, the … overflow) open UPWARD,
     chalet's rule: downward they run into the footer, and in the panel into
     the clipped edge. */
  .chat-composer__input [data-dropdown-panel] {
    inset-block-start: auto;
    inset-block-end: 100%;
    margin-block-end: 0.5em;
  }

  .chat-composer__send {
    --btn-background: var(--color-ink);
    --btn-color: var(--color-canvas);
  }

  /* Offline: the whole line greys and takes nothing (composer#offline). The
     fieldset's wash is the WHOLE costume — the send inside it is :disabled
     too, and .btn's own 0.4 stacked to 0.2, a dark circle that read as a
     broken button rather than an offline line whenever the cable was
     reconnecting (user, 2026-08-30; chalet's stays legible under one wash). */
  .chat-composer fieldset:disabled {
    opacity: 0.5;

    .btn:disabled {
      opacity: 1;
    }
  }

  /* Files waiting to go: a thumbnail each, the name under it, click to
     unpick. */
  .chat-composer__filelist:not(:empty) {
    padding-block-end: var(--block-space-half);
  }

  .chat-composer__file {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--block-space-half);
    inline-size: 8rem;
  }

  .chat-composer__thumbnail {
    display: block;
    inline-size: 100%;
    aspect-ratio: 5 / 4;
    object-fit: cover;
    border: var(--border);
    border-radius: var(--border-radius);
  }

  .chat-composer__thumbnail--file {
    background-color: var(--color-surface);
  }

  .chat-composer__caption {
    display: flex;
    max-inline-size: 100%;
    color: var(--color-ink-subtle);
  }

  /* Who is typing, whispered under the line; shown only while someone is —
     which is the names being there or not (typing_notifications_controller
     writes them and nothing else). The names wear the spinner's dots
     (spinner.css), chalet's shape; the dots only exist while shown, so
     nothing animates under an invisible line.

     It OVERLAYS the foot of the stream, chalet's shape here too. Out of flow,
     nothing beneath it can move when a name appears, so it needs no reserve —
     and it costs the composer no height. Reserving a line for it instead put
     32px of dead canvas between the words and the keyboard on a phone, every
     moment nobody was typing (user, 2026-08-30). */
  .typing-indicator {
    position: absolute;
    inset-block-end: 100%;
    inset-inline-start: var(--room-inset, var(--inline-space-double));
    opacity: 0;
    transition: opacity var(--speed-quick) var(--ease-out);

    &:has(.typing-indicator__author:not(:empty)) {
      opacity: 1;
    }
  }

  .typing-indicator__author {
    padding-inline-start: 1.5em;

    &.spinner:empty {
      display: none;
    }
  }
}
