/* css/main/chat-compose-and-attachments.css — part 27/30 of the app styles (was css/main.css; loaded in this order by index.html). */
      /* ══════════════════════════════════════════════════════════════
         WHO READS YOU / AS WHOM YOU SPEAK — Santi's request, 2026-09-01

         ⭐ The colours are NOT decorative: they are the bubbles' colours. The
         left half uses the background of the OTHER's message and the right the
         one of YOURS, and since in the conversation the other is on the left
         and you on the right, the mapping is learned by looking at it once. The
         idea is his: *"the one who reads me is on the left side and I on the right"*.

         ⚠️ It has NO `position: sticky` nor z-index: it is a sibling of `.tlch-head`
         inside the same flex column, and what scrolls is the
         conversation. Pinning it with `sticky` would solve the same thing twice.
         ══════════════════════════════════════════════════════════════ */
      .tlch-q {
        display: flex; align-items: stretch; flex: 0 0 auto;
        border-bottom: 1px solid var(--border-soft);
      }
      .tlch-q > div { flex: 1 1 50%; min-width: 0; padding: 5px 10px 6px; }
      .tlch-q-l {
        background: var(--chat-bot-bg); color: var(--chat-bot-fg);
        border-right: 1px solid var(--border-soft);
      }
      .tlch-q-r {
        background: var(--chat-user-bg); color: var(--chat-user-fg);
        text-align: right;
      }
      .tlch-q-k {
        font-family: "Oswald", sans-serif; font-size: 9px; letter-spacing: 1px;
        text-transform: uppercase; opacity: .72; line-height: 1.3;
      }
      /* ⚠️ One line with ellipsis, and the whole text in `title`: a long role
         cannot push the conversation down on the only screen where
         space IS the product. */
      .tlch-q-v {
        font-size: 12px; font-weight: 600; line-height: 1.3;
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
      }
      .tlch-head-c { display: flex; align-items: center; gap: 6px; font-size: 14px; font-weight: 700; }
      .tlch-head-c svg { width: 16px; height: 16px; }
      /* ⭐ THE FACE OF WHO YOU TALK TO, in the private-chat header (Santi,
         2026-09-16). Same 34 px as its row in the list (`.tlch-priv-av`):
         one family per job, so the circle just tapped in the
         list is the same one that shows up on top on entering.
         ⭐ MEASURED BEFORE WRITING IT, in both themes and at 390 px: the header
         does NOT grow —62 px with and without avatar—, because it fits within the height
         the two text lines already took. The text block goes from 266 to
         224 px and nothing is cut or overflows. With a very long club name the
         header rises to 77 px, but it rises THE SAME without an avatar: the name
         wraps it, not the circle. */
      .tlch-head-av { flex: 0 0 34px; width: 34px; height: 34px; }
      .tlch-head-av .tlav { width: 34px; height: 34px; }
      .tlch-head-av .tlav-txt { font-size: 12px; }
      .tlch-head-c.tlch-cn-main  { color: var(--accent); }
      .tlch-head-c.tlch-cn-org   { color: var(--muted); }
      .tlch-head-c.cv-type-aire  { color: var(--cv-type-aire-fg); }
      .tlch-head-c.cv-type-fuego { color: var(--cv-type-fuego-fg); }
      .tlch-head-c.cv-type-aahh  { color: var(--cv-type-aahh-fg); }
      .tlch-head-c.cv-type-plato { color: var(--cv-type-plato-fg); }
      @media (pointer: coarse) {
        .tlch-atras { width: 44px; height: 44px; flex: 0 0 44px; }
      }

      /* ── The compose bar ──────────────────────────────────────────── */
      .tlch-comp {
        display: flex; align-items: flex-end; gap: 8px;
        padding: 9px 11px; flex: 0 0 auto;
        background: var(--surface); border-top: 1px solid var(--border-soft);
      }
      /* THE PILL: the field and the two buttons floating inside it. ⛔ The
         `flex: 1` lives HERE and not on the `<textarea>`: whoever shares out the
         row's width is this box. `min-width: 0` because a flex box does not go below
         its content by default, and without it the field would push "send" off
         the screen as soon as a long word was typed. */
      .tlch-campo { flex: 1; min-width: 0; position: relative; display: flex; }
      .tlch-in {
        flex: 1; min-width: 0; min-height: 44px;
        /* The room for the buttons, which is NOT symmetric: on the left goes the
           smiley (46) and on the right TWO —the clip and the camera— (44+46). Without this
           the text runs under the buttons and is half readable.
           ⚠️ And that is why the padding is not a normal field's: here the usual 16
           would leave the text under the icon.
           ⛔ If a button is ever added to or removed from the bar, THIS number
           changes with it. Watched by `tests/unit-chat-barra-anclajes.test.js`. */
        padding: 11px 90px 11px 46px;
        background: var(--card);
        border: 1px solid var(--border-soft); color: var(--text);
        font: inherit; font-size: 16px; line-height: 1.35; resize: none;

        /* PILL while it is one line tall, which is how the mock-up was approved.
           ⛔ And ONLY while it is one line tall: with an infinite radius, as soon as the
           field grows it becomes an ELLIPSE with bulging ends.
           Santi saw it writing a long message (2026-08-28): "it keeps
           turning oval". On growing it switches to the panel radius (`.tlch-multi`,
           below), which is no longer a control but a small text box.
           ⚠️ Here I put 22 px —"half the height, it looks the same"— and
           `tests/unit-escala-visual.test.js` stopped it: the mock-up has FOUR
           radii (6 control · 10 card · 14 dialog · 999 pill) and a
           fifth intermediate value is exactly the drift that guard
           exists to prevent. With both approved it comes out better. */
        border-radius: 999px;

        /* ⛔⛔ And THIS WAS A REAL BUG, not an aesthetic detail: here it
           had `overflow: hidden`. On reaching the cap, the field stopped
           growing and **CLIPPED** the text instead of scrolling it: one kept
           typing blind, seeing neither the cursor nor the last words.
           A dictated message goes past four lines with no effort.
           `auto` keeps the cursor in view, which is what any
           editor does. `overscroll-behavior` so that on reaching the end the gesture
           does not chain to the conversation behind (same rule as
           `.tlch-conv`; guard: scripts/scroll-overlay-check.py). */
        overflow-y: auto;
        overscroll-behavior: contain;

        /* How far it grows before scrolling: about four lines.
           ⛔ HERE A "CLEVER" CAP LASTED TEN MINUTES. I tied it to the height
           the keyboard leaves —`min(112px, calc(var(--app-h) …))`— so that on a
           small phone it would not eat the conversation. Measured in the browser,
           it gave **0 px**: the field stayed stuck at one line and scrolled
           from the first word. `--app-h` is written by the app at run
           time and can be `0px` (see js/app.js), so a `max-height`
           hung on it inherits that zero. A fixed number cannot fail like that.
           ⚠️ And it is not an invented cap: four lines is what fitted before.
           What was fixed is not how much it grows, it is what happens on reaching it. */
        max-height: 112px;
      }
      /* As soon as it goes past one line it stops being a control and is a small
         text box: dialog radius (14), which is what prevents the oval. The class
         is set by `_autoAlto()` measuring the real height, not counting characters
         —the width depends on the device and the chosen font size—. */
      .tlch-in.tlch-multi { border-radius: 14px; }
      /* ⛔ 16 px and not 15: below 16, iOS ZOOMS the page on focusing the
         field and never goes back. It is the trap of reference_ios_zoom_16px_controles,
         and the mock-up —which is not opened on an iPhone— could not see it. */
      .tlch-in:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

      .tlch-icon-btn {
        width: 44px; height: 44px; flex: 0 0 44px; border-radius: 50%;
        border: 0; cursor: pointer; display: grid; place-items: center;
        background: var(--surface2); color: var(--text);
        transition: background .2s, opacity .2s;
      }
      .tlch-icon-btn.tlch-send { background: var(--accent); color: var(--accent-fg); }
      .tlch-icon-btn:disabled { opacity: .45; cursor: default; }
      .tlch-icon-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
      .tlch-icon-btn svg { width: 21px; height: 21px; }
      /* ── THE TWO BUTTONS LIVING INSIDE THE PILL ────────────────────────
         The smiley on the left and the clip on the right, as in the WhatsApp
         screenshot Santi brought on 2026-08-29. ⛔ They measure their 44×44 touch
         target even though the drawing is 21: what is tapped is the whole button.
         ⛔ And they stick to the BOTTOM, not centred: when the field grows to four
         lines they stay next to the line being typed, which is where
         the person is looking.
         ⚠️ FLAT icon, no fill: what separates the clip from "send" is no longer
         distance but that they are two different things —this one is flat and
         inside; send is an accent-filled circle and outside—. If someday
         someone gives these a background, that separation disappears. */
      .tlch-campo-btn {
        position: absolute; bottom: 0;
        width: 44px; height: 44px; padding: 0;
        display: grid; place-items: center;
        background: none; border: 0; border-radius: 50%; cursor: pointer;
        color: var(--muted); transition: background .2s, color .2s;
      }
      .tlch-campo-btn svg { width: 21px; height: 21px; }
      /* ⛔ THE BACKGROUND, NOT JUST THE COLOUR — and this is a bug that was already shipped.
         In the DARK theme `--muted` and `--text` have the same value (#f0ede8), so
         a `:hover` that only changes the colour **does absolutely nothing** there:
         the button had no response at all on hover, and only in
         the light theme was it noticeable. The background works in both.
         ⚠️ It is a TRANSIENT state: the resting one stays flat, which is what
         keeps the distinction from "send". */
      .tlch-campo-btn:hover { background: var(--surface2); color: var(--text); }
      .tlch-campo-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
      .tlch-campo-btn:disabled { opacity: .45; cursor: default; }
      /* ⛔⛔ EVERY BAR BUTTON CARRIES ITS ANCHOR, AND IT IS NOT DECORATION.
         `.tlch-campo-btn` is `position: absolute`, so a button WITHOUT `left`
         or `right` falls into its "static position" — and inside a flex
         container that position is the TOP-LEFT CORNER. That is,
         exactly on top of the smiley, and painted later, so it covers it.
         It happened when adding the camera (ticket #143) and Santi saw it on 2026-08-30:
         *"on adding the take-a-photo icon you have crushed the emoticons
         icon… the emoticons that used to show above the text are no longer
         seen"*. And it was not the panel: the button that opens it was under
         another, so the tap went to the camera.
         ⭐ The order is that of the WhatsApp screenshot he took as reference:
         smiley inside on the left; clip and camera inside on the right, in that
         order; and send OUTSIDE the pill.
         Guard: `tests/unit-chat-barra-anclajes.test.js`. */
      .tlch-emo-btn { left: 0; }
      .tlch-adj { right: 44px; }
      .tlch-cam { right: 0; }
      /* The open smiley stays MARKED. Without this, the only sign that
         the panel is open is the panel itself, and on a small screen with
         the keyboard up that is not always fully visible. */
      .tlch-emo-btn[aria-expanded="true"] { background: var(--surface2); color: var(--accent); }
      @media (prefers-reduced-motion: reduce) { .tlch-campo-btn { transition: none; } }

      /* ── THE EMOTICON GRID ─────────────────────────────────────────────
         ⛔ IN THE FLOW, ABOVE THE BAR — it is not a layer. Floating it would have
         needed its place in the `z-index` scale and would have brought back
         the layers' scroll jam, which in this project has
         come back three times (scripts/scroll-overlay-check.py). Here the
         conversation simply shrinks, as it already does when the text
         field grows.
         ⚠️ The panel is hidden with the `hidden` attribute and has NO patch
         of its own: the general rule `[hidden] { display: none !important }` already
         beats this `display: flex`. Here I wrote the per-selector patch and
         `tests/unit-hidden-gana.test.js` stopped it, which exists precisely so this
         is not solved case by case again —it has been three times—. */
      /* ⛔⛔ `0 1 auto` AND `min-height: 0`, NOT `0 0 auto`, AND THIS WAS THE REAL
         BUG. The chat column does not scroll —the `.content` that holds it
         has `overflow-y: hidden`—, so a panel that CANNOT SHRINK
         takes whatever is below it along: with the keyboard open on a small
         phone, or in landscape, or with the font size raised, the compose
         bar leaves the screen. And the button that closes the panel lives
         right there, so the exit goes with it: the chat gets
         stuck and you have to leave the screen to recover it.
         ⭐ With `flex-shrink: 1` the panel yields height before anyone else —the
         conversation has `flex-basis: 0` and cannot yield— and the grid, which
         already scrolls, simply shows fewer rows. */
      /* ── THE STRIP OF THE ATTACHMENT IN PLACE — ticket #143 ─────────────
         Above the field, like the emoticon panel and with the same
         surface: it is the place Santi asked for the thumbnail. ⛔ It INHERITS from
         `.tlch-emo` visually (same `--surface`, same top border) so
         that the two things appearing over the keyboard read as the same
         family and not as two patches. */
      .tlch-adjp {
        flex: 0 0 auto;
        background: var(--surface); border-top: 1px solid var(--border-soft);
        padding: 8px 10px;
      }
      .tlch-adjp-c {
        display: flex; align-items: center; gap: 10px;
        background: var(--card); border: 1px solid var(--border-soft);
        border-radius: 10px; padding: 6px 8px;
      }
      /* The thumbnail, square and cropped: a landscape photo and a portrait one
         must take the same room, or the bar jumps in height on choosing. */
      .tlch-adjp-mini {
        flex: 0 0 auto; width: 44px; height: 44px;
        /* CONTROL radius (6), which is one of the mock-up's four. */
        object-fit: cover; border-radius: 6px; display: block;
      }
      .tlch-adjp-ic { flex: 0 0 auto; display: inline-flex; color: var(--muted); }
      .tlch-adjp-ic svg { width: 24px; height: 24px; }
      /* The name is clipped; the size is NOT. ⛔ A PDF called "acta-2026-…" eats
         the whole row and pushes out the remove cross, which is the only
         way to undo. */
      .tlch-adjp-n {
        flex: 1 1 auto; min-width: 0; color: var(--text);
        font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
      }
      .tlch-adjp-b { flex: 0 0 auto; color: var(--muted); font-size: 12px; }
      .tlch-adjp-x {
        flex: 0 0 auto; width: 44px; height: 44px; padding: 0;
        display: inline-flex; align-items: center; justify-content: center;
        background: none; border: 0; border-radius: 6px;
        color: var(--muted); cursor: pointer;
      }
      .tlch-adjp-x:hover { color: var(--text); }
      .tlch-adjp-x:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
      .tlch-adjp-x svg { width: 20px; height: 20px; }

      .tlch-emo {
        flex: 0 1 auto; min-height: 0;
        display: flex; flex-direction: column;
        background: var(--surface); border-top: 1px solid var(--border-soft);
      }
      .tlch-emo-tabs {
        display: flex; gap: 4px; padding: 4px 8px 0;
        border-bottom: 1px solid var(--border-soft);
      }
      .tlch-emo-tab {
        flex: 1; min-width: 44px; height: 44px; padding: 0;
        display: grid; place-items: center;
        background: none; border: 0; cursor: pointer;
        color: var(--muted); border-radius: 6px 6px 0 0;
        /* ⛔ The active tab is marked with an UNDERLINE as well as colour: colour
           cannot be the only signal (UX rule §2). The border is always there,
           transparent when not due, so that on switching tab the row
           does not jump by two pixels. */
        border-bottom: 2px solid transparent; margin-bottom: -1px;
        transition: color .2s, background .2s, border-color .2s;
      }
      .tlch-emo-tab svg { width: 21px; height: 21px; }
      .tlch-emo-tab:hover { background: var(--surface2); }
      .tlch-emo-tab.is-on { color: var(--accent); border-bottom-color: var(--accent); }
      .tlch-emo-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
      /* ⛔ FIXED HEIGHT IN PIXELS, and on purpose. Tying it to `--app-h` or `vh`
         is the trap that already cost ten minutes on the text field right
         next door: `--app-h` is written by the app at run time and can be
         `0px`, and `vh` on a phone does NOT subtract the keyboard. Four rows is
         what fits with the keyboard open on the smallest device.
         `overscroll-behavior` so that on reaching the end the gesture does not
         chain to the conversation behind; and NO
         `-webkit-overflow-scrolling`, which is iOS's "I reach the bottom and it won't go up"
         (guard: scripts/scroll-overlay-check.py). */
      .tlch-emo-grid {
        display: grid; grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
        gap: 4px; padding: 8px;
        /* ⛔ TWO ROWS, NOT FOUR — and it is a decision of Santi's of 2026-08-29,
           not a round number. A web page cannot close the keyboard at will
           (only removing focus from the field lowers it), so the grid and the
           keyboard coexist by force and between them covered almost the whole
           conversation: *"you see the emoticons, you see the keyboard, too much
           is hidden"*. With two rows it covers half, and the 138 are still there — the
           grid scrolls.
           ⚠️ The good alternative was to **replace** the keyboard with the grid,
           like other apps. It was offered to him with its cost and he chose this: it lives
           in the iOS resize zone that already cost six attempts on
           27/08. If it is ever taken up again, it is taken up there.
           44 + 4 + 44 = 92 for the two rows, plus the 16 of padding. */
        max-height: 108px;
        overflow-y: auto; overscroll-behavior: contain;
        /* The other half of the shrinking above: without `min-height: 0` a grid
           does not go below its content and the panel would yield nothing.
           ⛔ ZERO, not "one row". A 44 px floor looks better, but stops being
           a floor exactly in the case this comes to close: if the room does not
           suffice, what gets lost is again the compose bar. Better an ugly
           grid than a chat you cannot leave. */
        flex: 1 1 auto; min-height: 0;
      }
      .tlch-emo-b {
        min-width: 44px; height: 44px; padding: 0;
        background: none; border: 0; border-radius: 6px; cursor: pointer;
        /* 24 px is the scale step that leaves the drawing legible inside a
           44 cell: below it turns into a colour smudge, which is
           precisely what prevents picking the right one. */
        font-size: 24px; line-height: 1;
        display: grid; place-items: center;
        transition: background .2s;
      }
      .tlch-emo-b:hover { background: var(--surface2); }
      .tlch-emo-b:active { background: var(--hover-soft); }
      .tlch-emo-b:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
      /* The rule's 8 px separation between touch targets is asked for
         between DIFFERENT actions. Here there are 138 cells of the same family and
         a mistake costs one deletion: what matters is that the cell measures
         its 44, and that is already met. */
      @media (prefers-reduced-motion: reduce) {
        .tlch-emo-tab, .tlch-emo-b { transition: none; }
      }

      /* ── THE ATTACHMENT, INSIDE THE BUBBLE ────────────────────────────
         ⛔ It is a CARD that is tapped, not a thumbnail, and it is a decision:
         the file's address is signed ON TAPPING and lasts five minutes, so
         painting thumbnails would force one signature per photo on
         opening the conversation —twenty photos, twenty calls, nineteen for
         photos nobody will look at—. */
      /* ── A PHOTO'S THUMBNAIL ───────────────────────────────────────────
         ⛔ THE SPACE IS RESERVED FROM THE FIRST PAINT, and it is no detail:
         the thumbnail arrives late —there is a signature and a download in between—, so
         without a fixed height the conversation jumps right while it is
         being read. It is the rule of "reserve room for what arrives later".
         ⛔ And the icon underneath is NOT a decoration: it is the usual CARD
         peeking through. If the thumbnail never arrives —a photo uploaded before
         this existed, or a half upload— what remains is exactly the
         previous behaviour, without a single error.
         ⚠️ `cover` crops the framing on purpose: a square preview reads
         at a glance and the whole photo is one tap away. What is SAVED is not
         cropped — the thumbnail keeps its proportions. */
      .tlch-foto {
        position: relative; display: block; overflow: hidden;
        width: 200px; max-width: 100%; height: 200px;
        margin: 2px 0 6px; padding: 0;
        border-radius: 10px; border: 1px solid var(--border-soft);
        background: var(--surface-soft); cursor: pointer;
      }
      .tlch-foto:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
      .tlch-foto:disabled { opacity: .55; cursor: default; }
      .tlch-foto-ph {
        position: absolute; inset: 0; display: grid; place-items: center;
        color: var(--muted);
      }
      .tlch-foto-ph svg { width: 28px; height: 28px; }
      .tlch-foto-img {
        position: relative; width: 100%; height: 100%;
        object-fit: cover; display: block;
        /* Appears once it has really loaded. ⛔ Without this the edge of a
           broken image shows over the icon until it arrives. */
        opacity: 0; transition: opacity .2s;
      }
      .tlch-foto-img.tlch-foto-ok { opacity: 1; }
      @media (prefers-reduced-motion: reduce) { .tlch-foto-img { transition: none; } }

      /* ⛔ ONE SINGLE FAMILY WITH TWO NAMES, and the block is ONE on purpose.
         The chat's document card and a note's are the SAME object
         —name, size, tapped and opened— and giving them two CSS blocks is
         exactly how they drift apart over time without anyone noticing. The prefix
         `tlch-` means "chat", so outside the chat `.doc-card` is used.
         Guard: `tests/unit-nota-documento.test.js`. */
      .tlch-adjunto,
      .doc-card {
        width: 100%;
        min-height: 44px;
        display: flex; align-items: center; gap: 10px;
        margin: 2px 0 6px;
        padding: 8px 10px;
        border-radius: 10px;
        border: 1px solid var(--border-soft);
        background: var(--surface-soft);
        color: var(--text);
        font: inherit; font-size: 13px; text-align: left;
        cursor: pointer;
        transition: background .2s, border-color .2s;
      }
      .tlch-adjunto:hover, .doc-card:hover { background: var(--hover-soft); }
      .tlch-adjunto:focus-visible, .doc-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
      .tlch-adjunto:disabled, .doc-card:disabled { opacity: .55; cursor: default; }
      .tlch-adjunto-ic, .doc-card-ic { flex: 0 0 auto; display: inline-flex; color: var(--accent); }
      .tlch-adjunto-ic svg, .doc-card-ic svg { width: 20px; height: 20px; }
      /* Explicit `min-width: 0`: without it, a long file name stretches the
         card and pushes the bubble off the screen instead of being clipped. */
      .tlch-adjunto-t, .doc-card-t { min-width: 0; display: flex; flex-direction: column; gap: 1px; }
      .tlch-adjunto-n,
      .doc-card-n {
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
      }
      .tlch-adjunto-b, .doc-card-b { font-size: 11px; color: var(--muted); }
      /* Muting a channel: the speaker in the header.
         ⛔ When off it is DIMMED, but the real state is told by the DRAWING (struck through or
         not) and the `aria-pressed`, not the opacity: colour cannot be the only
         indicator (UX rules §2), and merely dimmed it reads as "cannot be
         pressed", which is the opposite — it is precisely the one to press to
         hear it again. */
      .tlch-mudo-btn.es-mudo { color: var(--text-dim); }
      /* The muted channel, in the tree: the dimmed row. The state is told by the
         DRAWING of its bell (struck through) and its `aria-pressed`, not the opacity. */
      .tlch-canal.tlch-cn-mudo { opacity: .62; }
      /* ⛔ THE LIST'S BELL (M2, 2026-09-14): mute without entering.
         · SIBLING of the row inside `.tl-arbol-nodo`, never inside the button.
         · No background and the icon in `--muted`: next to it goes the unread
           counter, which is what must draw attention, not the bell.
         · 44 px on touch (UX rules §1); with a mouse 40 wide is enough so as not to
           steal room from the channel name.
         · Hovering changes colour and background, NEVER the size: the row does not
           move, and what is tapped next is where it was. */
      .tlch-nodo-mudo { display: flex; align-items: stretch; gap: 6px; }
      .tlch-nodo-mudo > .tlch-canal { flex: 1 1 auto; min-width: 0; width: auto; }
      .tlch-arbol-mudo {
        flex: 0 0 40px; min-height: 44px;
        display: inline-flex; align-items: center; justify-content: center;
        padding: 0; border-radius: 10px; border: 1px solid var(--border-soft);
        background: transparent; color: var(--muted);
        cursor: pointer; transition: background .2s, color .2s;
      }
      /* ⚠️ DIMMED WITH OPACITY, not with another colour: `--muted` and `--text-dim`
         have the same value in both themes, so switching variable dimmed
         nothing (seen by the code review). With .7 it stays well above the 3:1
         of an icon in both themes; on hover or focus, full. */
      .tlch-arbol-mudo svg { width: 18px; height: 18px; opacity: .7; }
      .tlch-arbol-mudo:hover { background: var(--surface2); color: var(--text); }
      .tlch-arbol-mudo:hover svg, .tlch-arbol-mudo:focus-visible svg { opacity: 1; }
      .tlch-arbol-mudo:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
      @media (pointer: coarse) { .tlch-arbol-mudo { flex-basis: 44px; min-width: 44px; } }
      @media (prefers-reduced-motion: reduce) { .tlch-arbol-mudo { transition: none; } }
      /* The channel in READ-ONLY for whoever looks, in the tree (D-5, point 3):
         *"let them quickly see they can only read"*. Padlock AND text, never
         only the padlock nor only a colour (UX rules §2). It does not dim the row:
         the channel opens just the same and is read in full. */
      /* ⛔ ONLY THE PADLOCK since 2026-09-16 (Santi): the words "Read only"
         did not fit next to the counter and pushed it to the next line. No border or background: it
         is not a button, the whole row is. 16 px and not 14, because now it goes alone. */
      .tlch-canal-ro {
        margin-left: auto; display: inline-flex; align-items: center;
        color: var(--text-dim);
      }
      .tlch-canal-ro svg { width: 16px; height: 16px; flex: 0 0 auto; }
      /* ⛔ Here a dangling comma was left on 2026-09-14 on removing the little "muted"
         icon, and the counter of a read-only channel inherited the red border and
         colour of the "reported" one below: red number on a red circle.
         Guard: `tests/unit-css-selector-colgado.test.js`. */
      .tlch-canal-ro + .tlch-canal-b { margin-left: 8px; }

      /* ── REPORTED (6.8) — only the organisation sees it ────────────────
         ⛔ IT IS NOT ANOTHER LITTLE RED CIRCLE WITH A NUMBER (Santi, 2026-08-25): it would be
         confused with the unread one. It goes with BORDER, FLAG and its figure, and a
         transparent fill — the solid one is for "unread". And the flag is not
         an ornament: colour is never the only indicator (UX rules §2). */
      .tlch-canal-rep {
        margin-left: auto; display: inline-flex; align-items: center; gap: 3px;
        min-height: 22px; padding: 0 7px 0 5px; border-radius: 999px;
        border: 1.5px solid var(--danger); color: var(--danger);
        font-size: 12px; font-weight: 800; line-height: 1; white-space: nowrap;
      }
      .tlch-canal-rep svg { width: 12px; height: 12px; flex: 0 0 auto; }
      .tlch-canal-rep + .tlch-canal-ro,
      .tlch-canal-rep + .tlch-canal-b { margin-left: 8px; }
      /* The message, in its place in the conversation: border on the left and
         a label with who reported it above the text. */
      .tlch-row.tlch-reportado .tlch-bub {
        border: 1px solid var(--danger); border-left-width: 3px;
      }
      .tlch-rep-tag {
        display: inline-flex; align-items: center; gap: 4px; max-width: 100%;
        margin-bottom: 4px; padding: 2px 7px 2px 5px; border-radius: 6px;
        border: 1px solid var(--danger); color: var(--danger);
        font-size: 12px; font-weight: 700; line-height: 1.3;
      }
      .tlch-rep-tag svg { width: 12px; height: 12px; flex: 0 0 auto; }
      /* ⛔ IT WRAPS, IT DOES NOT CLIP: what the organisation needs to read here is
         WHO reported, and with an ellipsis the third name vanished —
         seen in the harness screenshot on 2026-09-14—. */
      .tlch-rep-tag { align-items: flex-start; }
      .tlch-rep-tag svg { margin-top: 1px; }
      .tlch-rep-tag span { overflow-wrap: anywhere; }
      /* Two lines per report (Santi, 2026-09-14): who on top, the reason
         below. Several reports, one under another. */
      .tlch-rep-lista { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
      .tlch-rep-uno { display: flex; flex-direction: column; }
      .tlch-rep-quien { font-weight: 700; }
      .tlch-rep-motivo { font-weight: 500; }

      /* READ-ONLY bar (this is how the Main channel is born, D-5).
         ⛔ Icon AND text, never colour alone (UX rule §2). */
      .tlch-ro {
        display: flex; align-items: center; justify-content: center; gap: 8px;
        padding: 14px 16px; flex: 0 0 auto;
        background: var(--surface); border-top: 1px solid var(--border-soft);
        font-size: 13px; color: var(--muted); text-align: center; line-height: 1.4;
      }
      .tlch-ro svg { width: 16px; height: 16px; flex: 0 0 16px; }

      /* ── CLOSED CONVERSATION (#134) ────────────────────────────────────
         Whoever handled the private chat left the team: the history is read and nothing is
         written. ⛔ STACKED —notice on top, exit below—: on a phone the notice
         takes two lines and a button on the same row squashed it. It is the same
         read-only bar (`.tlch-ro`) with an exit, not a new family.
         ⚠️ `.btn-secondary` carries `width: 100%` for forms: here it goes at its
         own width, with the house's touch height. */
      /* ── "OPENING THE CHAT…" WHILE THE NETWORK HAS NOT ANSWERED (2026-09-14) ──
         It appears DELAYED and softly: on a normal reload the answer arrives
         first and nothing shows, which is what was wanted —Santi saw
         "There are no messages yet" flicker and then the messages—. Only if it is slow, it says so.
         With reduced motion requested, no animation: it shows at once. */
      .tlch-conv [data-esperando] { animation: dlgFadeIn .25s ease .45s both; }
      @media (prefers-reduced-motion: reduce) {
        .tlch-conv [data-esperando] { animation: none; }
      }
      .tlch-ro.tlch-ro-cerrada { flex-direction: column; gap: 10px; padding-block: 12px; }
      .tlch-ro-cerrada .tlch-ro-t { display: flex; align-items: center; justify-content: center; gap: 8px; }
      .tlch-ro-cerrada .tlch-ro-btn { width: auto; max-width: 100%; min-height: 44px; padding-inline: 18px; }

      /* ── THE CONNECTION STATE ──────────────────────────────────────────
         ⛔ WHY IT EXISTS. A RECONNECTING chat and a BROKEN chat looked
         exactly the same: the screen still and mute. In the early hours of
         2026-08-28 the phone spent three minutes in the background, the system
         closed its socket, and on coming back there were a few seconds in which nothing came in
         or went out. No error, because there was none — but from outside it
         was identical to the breakdown that had cost us the whole afternoon.

         ⛔ And it is NOT RED, on purpose. This is not an error: it is "wait
         a moment". Painting it like the notice next to it teaches people not to read
         notices. It goes in the house grey, with a pulsing dot — dot AND
         text, never colour alone (UX rule §2). */
      .tlch-conn {
        display: flex; align-items: center; gap: 8px;
        margin: 0 11px 6px; padding: 7px 11px; border-radius: 10px;
        background: var(--surface-soft); border: 1px solid var(--border-soft);
        font-size: 13px; line-height: 1.4; color: var(--muted);
      }
      .tlch-conn-dot {
        width: 8px; height: 8px; flex: 0 0 8px; border-radius: 50%;
        background: var(--muted); animation: tlchLatido 1.4s ease-in-out infinite;
      }
      @keyframes tlchLatido { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
      /* Whoever asks for less motion does not get it: the dot stays still and
         visible, and the text still says the same. */
      @media (prefers-reduced-motion: reduce) {
        .tlch-conn-dot { animation: none; opacity: 1; }
      }

      /* The notice that something did not go out (a link, a long text). It goes
         INLINE and above the bar, not in a dialog that has to be closed:
         it is a correction, not an error to be informed of. */
      .tlch-warn {
        margin: 0 11px 6px; padding: 8px 11px; border-radius: 10px;
        background: var(--alerta-dim, rgba(255,90,77,.12));
        border: 1px solid var(--danger); color: var(--danger);
        font-size: 13px; line-height: 1.4;
      }

      @media (pointer: coarse) {
        .tlch-icon-btn { width: 48px; height: 48px; flex: 0 0 48px; }
      }

