@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 toolbar on the T, closed until asked and hidden where a finger types,
     was this composer's rule alone; since 2026-10-06 it is every composer's,
     and it lives in lexxy.css. In a chat ↵ sends while the toolbar is closed
     and meta+↵ always (composer_controller). */

  /* 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;
  }

  /* 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;
    }
  }

  /* A long draft scrolls inside the line rather than pushing the room off a
     phone's screen: the stream keeps most of the glass. */
  .chat-composer .lexxy-editor__content {
    max-block-size: 8lh;
    overflow-y: auto;
  }

  /* Files waiting to go: a thumbnail each, the name under it, and the × at
     its corner that says a click takes it back. */
  .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;
    position: relative;

    /* On a canvas disc, so the × reads on any picture. The disc is a span of
       its own: an icon is a mask over its background, so the icon's own
       background would be the ×. */
    .chat-composer__unpick {
      --btn-size: 1.25rem;
      --btn-background: var(--color-canvas);

      position: absolute;
      inset-block-start: var(--block-space-half);
      inset-inline-end: var(--inline-space-half);
    }
  }

  .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;
    }
  }
}
