/* ══════════════════════════════════════════════════════════════════════════
   WhatsApp staff inbox - /admin/whatsapp

   Almost every selector in this file is prefixed `.wai-`. The exceptions are
   all deliberate and all narrowly scoped, and they fall into three groups:

     - the shell escapes, scoped by `:has(.wai-page)`, which therefore cannot
       reach any other admin page - the padding/overflow reset near the top and
       the mobile-bar and keyboard-height rules in the media queries at the end;
     - the two palette blocks, which additionally name `.wai-viewer` (Teleported
       to <body>), `.wab-scope` (the bulk-send preview card) and `.per-wa` (the
       Person page's docked WhatsApp panel), because those three render this
       skin's chrome where no `.wai-page` element exists;
     - three groups of BORROWED classes that the two hosts above needed and the
       palette could not deliver on its own: `.btn` / `.btn-primary`, the
       `.adm-sheet` chrome, and the scrollbars. Each is stated once, for both
       `.wai-page` and `.per-wa`, at the point where it is explained.

   Nothing in this file may be widened to `.per-page`, and `.per-wa` must NEVER
   be added to `.admin-main:has(.wai-page)`. That rule kills the admin shell's
   global scroller, which is correct for a viewport-locked inbox and catastrophic
   for a long scrolling page - see the block above that rule, and the ban stated
   at length in the header of `admin-person.css`.

   Design language: WhatsApp Web in light mode. Flat opaque surfaces, hairline
   borders, generous whitespace, ONE accent colour (WhatsApp's brand green), and
   faint shadows mixed from the app's own near-black blue-grey ink rather than
   the admin shell's cool blue. Nothing here is frosted: WhatsApp does not blur
   its panes, so this file does not either. The inbox shares the admin shell's
   light ground - what makes this route read as an embedded WhatsApp client
   instead of one more admin page is the beige doodle wallpaper, the green, and
   the bubble grammar, not a clashing ground. Most rules below still reference
   the `--adm-*` tokens declared on `.admin-layout` in admin.css; the palette
   immediately after this comment re-points them at WhatsApp's own values. The
   brand gradient is deliberately absent: it is a marketing device and this is a
   work surface.

   Linked once from index.html; the production bundler folds it into the
   admin-only stylesheet automatically because scripts/bundle-css.mjs treats
   every href starting with /css/admin as admin CSS. Do not register it again.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── The WhatsApp light palette ──────────────────────────────────────────────

   The inbox is skinned as WhatsApp Web in light mode. These are the app's own
   light-theme values, and they are the only colours this file may use - the
   few rgba() washes further down are mixed from the same near-black ink.

   It is declared in TWO blocks, and the split is load-bearing.

   This first one only names the raw `--wa-*` values, and it hangs off the whole
   shell rather than the inbox. The mobile top bar is a SIBLING of the scroller
   the inbox lives in, so a palette declared on `.wai-page` never reaches it -
   custom properties inherit downward only. Hanging the raw values on the shell
   puts them within reach of the bar without recolouring anything by itself:
   naming a variable paints nothing.

   `.wai-viewer` is listed on both because it Teleports to <body>, outside the
   shell entirely, and would otherwise inherit the light admin chrome.

   `.wab-scope` (the bulk-send preview card, mounted on /admin/leads and
   /admin/registrations) and `.per-wa` (the Person page's docked transcript) are
   the two surfaces that render WhatsApp chrome where no `.wai-page` exists.
   They are LISTED HERE rather than each keeping a private copy of the palette:
   a value repeated in three files is a value that gets retuned in one of them.
   Naming a surface on this selector paints nothing by itself - it only puts the
   variables within reach - so the list can grow without any other file moving.
   `admin-wa-bulk.css` still re-declares sixteen of these on `.wab-scope`; that
   copy is now redundant and can be deleted, values unchanged. */

.admin-layout:has(.wai-page),
.wai-viewer,
.wab-scope,
.per-wa {
  /* Surfaces, back to front */
  --wa-deep: #efeae2;         /* the conversation field, behind the wallpaper - warm beige */
  --wa-panel: #ffffff;        /* list pane, lead panel */
  --wa-header: #f0f2f5;       /* every pane header, the composer bar */
  --wa-raised: #ffffff;       /* inputs and pressed pills sit WHITE on the grey bars */
  --wa-hover: #f5f6f6;        /* row hover inside a panel */
  --wa-active: #f0f2f5;       /* the selected row - WhatsApp selects in grey, not green.
                                 The dark skin reused --wa-raised for inputs AND selection;
                                 on light they diverge, so selection has its own token. */
  --wa-chip: #ffffff;         /* date pill, system bubble, inline notices - a white pill
                                 with a soft shadow is how the app floats them on beige */

  /* Bubbles */
  --wa-in: #ffffff;           /* they said it */
  --wa-out: #d9fdd3;          /* we said it */

  /* Ink */
  --wa-ink: #111b21;
  --wa-ink-2: #54656f;
  --wa-ink-3: #667781;

  /* Lines */
  --wa-line: rgba(134, 150, 160, 0.15);
  --wa-line-strong: rgba(134, 150, 160, 0.35);

  /* Accents */
  --wa-green: #25d366;        /* the one accent: send, active, focus */
  --wa-green-hi: #1da851;     /* hover - a fill darkens on a light ground where the
                                 dark skin lightened it */
  --wa-green-ink: #117335;    /* green TEXT, ICONS and focus rings on light surfaces -
                                 6:1 on white and it still clears AA over the green
                                 wash and the grey bars. #25d366 on white is 1.9:1 and
                                 must NEVER carry small text or thin glyphs (a 2px
                                 focus ring is a thin glyph); it is a fill, not an
                                 ink. */
  --wa-green-wash: rgba(37, 211, 102, 0.12);
  --wa-green-wash-2: rgba(37, 211, 102, 0.2);
  --wa-green-line: rgba(37, 211, 102, 0.55);
  --wa-tick: #53bdeb;         /* the read receipt - the same blue in light mode */
  --wa-link: #026e9e;         /* link text - the tick's blue is too pale to read on
                                 white, so links take the same hue pulled down until
                                 it clears AA even on the green outgoing bubble */

  /* States. WhatsApp's own light-mode red for fills, rings and icons; TEXT gets
     its own step-darker ink, because 4.6:1 on plain white evaporates the moment
     the red sits on its wash or the green bubble. The amber is darkened into an
     ink that stays readable on white and on its own wash. */
  --wa-danger: #ea0038;
  --wa-danger-ink: #d10032;
  --wa-danger-wash: rgba(234, 0, 56, 0.07);
  --wa-danger-line: rgba(234, 0, 56, 0.35);
  --wa-warn: #8a5c00;
  --wa-warn-wash: rgba(255, 171, 0, 0.12);
  --wa-warn-line: rgba(255, 171, 0, 0.45);

  /* Shadows keep WhatsApp's near-black blue-grey, never the shell's cool blue,
     and far less of it than the dark skin threw: a light ground needs almost
     none to read depth. */
  --wa-shadow-sm: 0 1px 0.5px rgba(11, 20, 26, 0.13);
  --wa-shadow-md: 0 2px 8px rgba(11, 20, 26, 0.14);
  --wa-shadow-lg: 0 8px 28px rgba(11, 20, 26, 0.18);

  /* The doodle wallpaper, tiled behind the bubbles. Inlined as an SVG data URI
     rather than a file in public/: it is ~1.4 KB, it is part of the theme
     rather than an asset anyone would swap, and a background-image request is
     one more round trip on a screen agents open all day. Drawn in the ink
     colour at 4% so it reads as texture on the beige and never competes with a
     bubble. */
  --wa-doodle: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='260' height='260' viewBox='0 0 260 260'><g fill='none' stroke='%23111b21' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' opacity='.04'><path d='M30 45c-8-6-12-10-12-15a6 6 0 0 1 12-3 6 6 0 0 1 12 3c0 5-4 9-12 15z'/><circle cx='90' cy='27' r='11'/><path d='M86 24h.01M94 24h.01M84 31a7 7 0 0 0 12 0'/><path d='M139 27h6l3-4h8l3 4h6v14h-26z'/><circle cx='152' cy='34' r='5'/><path d='M210 38V20l14-3v18'/><circle cx='207' cy='39' r='3.5'/><circle cx='221' cy='36' r='3.5'/><path d='M22 90l26-10-10 26-4-10z'/><path d='M85 80h18v10a8 8 0 0 1-8 8h-2a8 8 0 0 1-8-8z'/><path d='M103 83h4a4 4 0 0 1 0 8h-4'/><path d='M88 72v4M95 70v6'/><path d='M143 88a12 12 0 0 1 24 0z'/><path d='M155 88v12a4 4 0 0 0 8 0'/><path d='M215 78l3.4 6.9 7.6 1.1-5.5 5.4 1.3 7.6-6.8-3.6-6.8 3.6 1.3-7.6-5.5-5.4 7.6-1.1z'/><path d='M22 155a6 6 0 0 1 1-11 9 9 0 0 1 17-2 6 6 0 0 1 2 13z'/><path d='M84 142h10a4 4 0 0 1 4 4v14a4 4 0 0 0-4-4H84z'/><path d='M112 142h-10a4 4 0 0 0-4 4v14a4 4 0 0 1 4-4h10z'/><path d='M150 158a8 8 0 1 1 10 0v4h-10z'/><path d='M151 166h8'/><path d='M204 148h22v14h-22z'/><path d='M204 143h22v5h-22zM215 143v19'/><circle cx='24' cy='208' r='5'/><path d='M28 211l12 12M36 219l3 3M33 216l3 3'/><path d='M84 218a8 8 0 0 0 3-6v-4a5 5 0 0 1 10 0v4a8 8 0 0 0 3 6z'/><path d='M89 221a3 3 0 0 0 6 0'/><path d='M146 218c0-10 8-16 18-16 0 10-8 16-18 16z'/><path d='M146 218l14-12'/><circle cx='215' cy='210' r='6'/><path d='M215 199v-4M215 225v-4M226 210h4M200 210h4M223 202l3-3M204 218l-3 3M223 218l3 3M204 202l-3-3'/></g></svg>");

}

/* ── Re-point the inherited tokens ───────────────────────────────────────────

   The second block, and the reason the first one is kept separate: THIS is what
   actually recolours anything, and it must not reach the sidebar. The file
   reads `var(--text-secondary)`, `var(--adm-line)` and friends in ~180 places;
   re-pointing them here recolours all of those at once and leaves one place to
   reason about. The literal fallbacks in those `var(..., fallback)` calls are
   dead code - every one of these variables is defined globally - and they are
   left alone rather than churned.

   Scoped to the inbox, the viewer and the Person page's docked transcript -
   never to `.admin-layout`. The sidebar reads the same `--adm-*` tokens, and
   pointing them at the palette one level higher would re-skin the whole sidebar
   in WhatsApp's chrome on this route - which is expressly not what was asked
   for. The sidebar keeps the shell's own Glass Campus look; the inbox reads as
   WhatsApp on its own surfaces.

   `.per-wa` is on this list and `.wab-scope` deliberately is not. The dock hosts
   a full transcript plus borrowed `.btn` / `.empty-state` controls, so it wants
   the whole re-point; the bulk preview card is a read-only excerpt sitting in a
   sheet full of ordinary admin controls, and re-pointing `--primary-color` there
   would turn the send button green on two screens that are not WhatsApp. */

.wai-page,
.wai-viewer,
.per-wa {
  --text-color: var(--wa-ink);
  --text-secondary: var(--wa-ink-2);
  --text-tertiary: var(--wa-ink-3);
  --bg-surface: var(--wa-in);
  --primary-color: var(--wa-green);
  --primary-hover: var(--wa-green-hi);
  --adm-ink: var(--wa-ink);
  --adm-ink-2: var(--wa-ink-2);
  --adm-ink-3: var(--wa-ink-3);
  --adm-line: var(--wa-line);
  --adm-accent: var(--wa-green);
  --adm-accent-ink: var(--wa-green-ink);
  --adm-glass: var(--wa-header);
  --adm-glass-strong: var(--wa-raised);
  --adm-glass-border: var(--wa-line);
  --adm-glass-shadow: var(--wa-shadow-md);
  --shadow-sm: var(--wa-shadow-sm);
  --shadow-md: var(--wa-shadow-md);

  color-scheme: light;
}

/* ── The height chain ──────────────────────────────────────────────────────

   `.admin-main` is the admin shell's global scroller: `flex: 1; padding: 2rem;
   overflow: auto`, stretched by `.admin-layout { height: 100dvh }`. A
   full-height inbox inside a scrolling, padded parent gives you two scrollbars
   and a page that is taller than the viewport.

   The fix is to flatten the parent for this route only. `:has()` does it with
   zero JS and no shared-file edit. `.admin-main` keeps its flex-stretched
   definite height, loses its padding and stops scrolling, so `.wai-page`'s
   `height: 100%` resolves against the full viewport-locked box.

   `min-height: 0` / `min-width: 0` then appear on every grid and flex child
   below. Without them a grid/flex item's automatic minimum size is its content
   size, the pane refuses to shrink, and the overflow escapes sideways into
   `#app { overflow-x: clip }` where it is invisible AND unscrollable. That is
   the classic trap this whole layout is built to avoid.

   `.wai-page` is in the RTL `direction` group in admin.css but deliberately NOT
   in the horizontal-scroll group. Keep it that way. */

.admin-main:has(.wai-page) {
  padding: 0;
  overflow: hidden;
}

.wai-page {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  font-family: var(--font-body);
  background: var(--wa-panel);
  color: var(--wa-ink);
}

.wai-grid {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  display: grid;
  /* RTL container, so the FIRST column sits on the RIGHT: conversation list
     right, thread centre, lead context left - how WhatsApp mirrors itself in
     Hebrew. `minmax(0, 1fr)` rather than a bare `1fr` so a single unbreakable
     URL cannot push the middle column past the viewport. */
  grid-template-columns: 340px minmax(0, 1fr) 320px;
  overflow: hidden;
}

/* ── Shared atoms ────────────────────────────────────────────────────────── */

.wai-empty {
  margin: 0;
  padding: var(--space-xl, 40px) var(--space-lg, 24px);
  text-align: center;
  font-size: 0.9rem;
  color: var(--text-secondary, #5a5b6a);
}

.wai-empty--pane {
  grid-row: 1 / -1;
  place-self: center;
}

.wai-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm, 8px);
  padding: var(--space-xl, 40px) var(--space-lg, 24px);
  text-align: center;
  font-size: 0.9rem;
  color: var(--wa-danger-ink);
}

.wai-muted {
  color: var(--text-secondary, #5a5b6a);
}

/* ── The shell's buttons, inside the inbox ───────────────────────────────────

   `.btn` and `.btn-primary` come from admin.css and are painted for the shell:
   a white pill with #333 ink, and the shell's blue for the primary. Four
   places inside the inbox use them - the retry control when a load fails,
   "load older messages", "load more conversations", and the canned-reply form's
   actions - and the shell's blue is the one accent this skin must never show,
   so both are restated in the inbox's own ink and green.

   Scoped to `.wai-page`, so the same two classes keep their shell treatment on
   every other admin screen. `.wai-page .btn` outranks the bare `.btn` on
   specificity alone, which means this holds whatever order the two stylesheets
   end up in.

   `.per-wa` - the Person page's docked transcript - is named on all four lists,
   for the same reason it is named on the two palette blocks above: it renders
   this skin's chrome where no `.wai-page` exists, and the palette alone does
   NOT reach these two classes. `admin.css` hard-codes
   `.btn-primary { background: #0071E3 }` as a LITERAL and not as
   `var(--primary-color)`, so the re-point to `--wa-green` cannot recolour it -
   without this line a send or a retry inside the docked thread comes out admin
   blue in the middle of a WhatsApp-green panel, which is the one accent this
   skin must never show. It already bit at lower volume before a thread was ever
   embedded: `.per-wa__open-thread` is `class="btn btn-sm"` and was taking
   `admin.css`'s `border: 1px solid #ddd` rather than the `--wa-line-strong`
   hairline every other pill in that panel wears.

   Listed HERE rather than restated in `admin-person.css`, on the same argument
   the palette makes: two files painting the same button in the same green is
   one green that gets retuned in one of them. */

.wai-page .btn,
.per-wa .btn {
  background: var(--wa-raised);
  /* The stronger hairline: the dark skin separated this pill from its ground
     by fill, but a white pill on a white pane has only its edge. */
  border-color: var(--wa-line-strong);
  color: var(--wa-ink);
}

.wai-page .btn:hover,
.per-wa .btn:hover {
  background: var(--wa-header);
  border-color: var(--wa-line-strong);
}

/* After the base rule, and at equal specificity, so a button carrying both
   classes lands here. The label is bolded because white on the bright green
   only carries as a bold label or an icon - that is the one way the brand
   itself puts white on this green, and thin white text there is illegible. */
.wai-page .btn-primary,
.per-wa .btn-primary {
  background: var(--wa-green);
  border-color: var(--wa-green);
  color: #fff;
  font-weight: 600;
}

.wai-page .btn-primary:hover,
.per-wa .btn-primary:hover {
  background: var(--wa-green-hi);
  border-color: var(--wa-green-hi);
}

/* ── Status strip ────────────────────────────────────────────────────────── */

/* The strip is the top pane header of the inbox, so it wears the same flat
   header surface WhatsApp gives every bar in the app. Nothing is frosted and
   nothing is lifted: on WhatsApp's flat chrome a single hairline is all that
   separates one surface from the next, and a drop shadow would only muddy it. */
.wai-strip {
  flex: 0 0 auto;
  padding: var(--space-sm, 8px) var(--space-md, 16px);
  background: var(--wa-header);
  border-block-end: 1px solid var(--wa-line);
  box-shadow: none;
}

.wai-strip__row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-sm, 8px) var(--space-md, 16px);
  font-size: 0.8rem;
  color: var(--text-secondary, #5a5b6a);
}

.wai-strip__state {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-weight: 600;
  color: var(--text-color, #191a23);
}

.wai-strip__icon {
  font-size: 0.85rem;
}

/* Health levels. `ok` is the state to trust: quality GREEN and still paired
   with the Business App. A `canSendMessage` of LIMITED is the PERMANENT
   baseline of this number and is never coloured as a fault - see the copy
   contract at the top of WaStatusStrip.vue. Both icons take the ink green:
   the bright fill is invisible as a thin glyph on the light bar. */
.wai-strip--ok .wai-strip__icon {
  color: var(--wa-green-ink);
}

.wai-strip--info .wai-strip__icon {
  color: var(--wa-green-ink);
}

/* The two fault states tint the header instead of replacing it, so the bar
   keeps the weight of a pane header and only its hue tells you something is
   wrong. Laying the wash over the surface colour is what keeps the strip from
   washing out to the page's plain white the moment an alert lands. */
.wai-strip--warn {
  background: linear-gradient(var(--wa-warn-wash), var(--wa-warn-wash)), var(--wa-header);
  border-block-end-color: var(--wa-warn-line);
}

.wai-strip--warn .wai-strip__icon {
  color: var(--wa-warn);
}

.wai-strip--critical {
  background: linear-gradient(var(--wa-danger-wash), var(--wa-danger-wash)), var(--wa-header);
  border-block-end-color: var(--wa-danger-line);
}

.wai-strip--critical .wai-strip__icon {
  color: var(--wa-danger);
}

/* `display: contents` so the chips remain direct flex items of the row and the
   wide layout is unchanged by the grouping. The phone block below turns this
   into one collapsible block. */
.wai-strip__chips {
  display: contents;
}

.wai-strip__chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 2px 10px;
  border: 1px solid var(--wa-line);
  border-radius: var(--radius-full, 980px);
  background: var(--wa-chip);
  color: var(--wa-ink-2);
  white-space: nowrap;
}

.wai-strip__chip--mode {
  border-color: var(--wa-green-line);
  background: var(--wa-green-wash);
  color: var(--wa-green-ink);
  font-weight: 500;
}

.wai-strip__value {
  font-family: var(--font-mono, monospace);
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: var(--text-color, #191a23);
}

.wai-strip__checked {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.72rem;
  color: var(--wa-ink-2);
}

/* 34px painted, 44px to the thumb - the symmetric negative margin on the
   pseudo-element expands the hit area without disturbing the row's rhythm and
   without touching a direction-sensitive property. */
.wai-strip__refresh,
.wai-strip__toggle {
  position: relative;
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  border-radius: var(--radius-full, 980px);
  background: transparent;
  color: var(--wa-ink-2);
  font-size: 0.8rem;
  cursor: pointer;
  transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease;
}

.wai-strip__refresh::after,
.wai-strip__toggle::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: -5px;
}

.wai-strip__refresh {
  margin-inline-start: auto;
}

/* The collapsed-strip control. Phones only - above 767px the whole strip is
   always open and a toggle would have nothing to toggle. */
.wai-strip__toggle {
  display: none;
}

/* Hover fills the disc rather than outlining it, which is how WhatsApp answers
   a pointer on every icon button in its headers. The fill is the ink at 6% -
   on a light bar the disc darkens; a lighter grey would read as a hole. */
.wai-strip__refresh:hover,
.wai-strip__toggle:hover {
  background: rgba(11, 20, 26, 0.06);
  color: var(--wa-ink);
}

.wai-strip__refresh:focus-visible,
.wai-strip__toggle:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

.wai-strip__alerts {
  list-style: none;
  margin: var(--space-sm, 8px) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs, 4px);
}

.wai-strip__alert {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4rem;
  font-size: 0.78rem;
  color: var(--wa-warn);
}

.wai-strip__alert-kind {
  font-family: var(--font-mono, monospace);
  font-size: 0.72rem;
  font-weight: 600;
}

.wai-strip__alert-detail {
  color: var(--text-secondary, #5a5b6a);
}

.wai-strip__alert-time {
  font-size: 0.7rem;
}

/* ── Conversation list pane ──────────────────────────────────────────────── */

.wai-list {
  min-width: 0;
  min-height: 0;
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  background: var(--wa-panel);
  border-inline-end: 1px solid var(--wa-line);
  overflow: hidden;
}

/* A wrapping flex row since the new-chat "+" landed beside the field: the pill
   takes whatever the button leaves, and the too-short hint drops to a full row
   of its own underneath. */
.wai-list__search {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm, 8px);
  padding: var(--space-md, 16px) var(--space-md, 16px) var(--space-sm, 8px);
  background: var(--wa-panel);
}

/* The adornments position against the FIELD, not against the padded row, so
   the icon stays optically centred regardless of the row's padding. */
.wai-list__field {
  position: relative;
  display: block;
  flex: 1 1 auto;
  min-width: 0;
}

/* The field is a grey slab a step down from the panel rather than an outlined
   box: WhatsApp separates its search input from the list by lightness, so the
   border only exists to reserve the pixel the focus state paints into. This is
   the one input that does NOT sit white - on the white pane the field takes
   the bar grey, exactly as the app draws its own search. */
.wai-list__input {
  width: 100%;
  padding: 9px 34px 9px 34px;
  font-family: var(--font-body);
  font-size: 0.85rem;
  color: var(--wa-ink);
  background: var(--wa-header);
  border: 1px solid transparent;
  border-radius: var(--radius-full, 980px);
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.wai-list__input::placeholder {
  color: var(--wa-ink-2);
}

/* Focus is a green edge and nothing else - WhatsApp never rings its search
   field, so a glow here would announce the field as a foreign control. */
.wai-list__input:focus {
  border-color: var(--wa-green-ink);
  box-shadow: none;
}

.wai-list__input::-webkit-search-cancel-button {
  display: none;
}

.wai-list__search-icon {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 12px;
  transform: translateY(-50%);
  font-size: 0.78rem;
  color: var(--wa-ink-2);
  pointer-events: none;
}

.wai-list__search-clear {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-end: 4px;
  transform: translateY(-50%);
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--radius-full, 980px);
  background: none;
  color: var(--wa-ink-2);
  font-size: 0.8rem;
  cursor: pointer;
}

/* Grows the hit area to 44px without widening the painted circle, which would
   crowd the field's own rounded end. */
.wai-list__search-clear::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: -6px;
}

.wai-list__search-clear:hover {
  color: var(--wa-ink);
}

.wai-list__hint {
  flex: 0 0 100%;
  margin: 0 2px;
  font-size: 0.72rem;
  color: var(--wa-ink-2);
}

/* The "+" that starts a conversation with a number the inbox has never seen.
   The same quiet circle as the composer's tool buttons, sized to the search
   pill beside it; the ::after grows the tap area to 44px without letting the
   painted circle crowd the field's rounded end. */
.wai-list__new {
  position: relative;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border: 1px solid var(--wa-line);
  border-radius: var(--radius-full, 980px);
  background: transparent;
  color: var(--wa-ink-2);
  font-size: 0.85rem;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}

.wai-list__new::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: -3px;
}

.wai-list__new:hover {
  background: rgba(11, 20, 26, 0.06);
  color: var(--wa-ink);
  border-color: var(--wa-line-strong);
}

.wai-list__new:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

/* The gap is 8px and not 4: each chip's tap area overhangs its pill by 3px on
   every side, so a 4px gap would let two neighbours' hit areas overlap and the
   chip under the thumb would not always be the one that fires. */
.wai-list__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm, 8px);
  padding: 0 var(--space-md, 16px) var(--space-sm, 8px);
}

/* 38px tall rather than 44: five chips in a 340px column at 44px each wrap to
   three rows and eat the list. The pseudo-element takes the TAP area to 44 in
   both axes while the painted pill stays dense - the compromise the design
   brief allows where 44 genuinely breaks density. */
.wai-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: 38px;
  padding: 4px 13px;
  font-family: var(--font-body);
  font-size: 0.76rem;
  color: var(--wa-ink-2);
  background: var(--wa-chip);
  /* White chip on the white pane: the strong hairline is the outline
     WhatsApp's own light filter pills wear. */
  border: 1px solid var(--wa-line-strong);
  border-radius: var(--radius-full, 980px);
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
}

.wai-chip::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: -3px;
}

.wai-chip:hover {
  color: var(--wa-ink);
  border-color: var(--wa-green-line);
}

.wai-chip--active {
  color: var(--wa-green-ink);
  background: var(--wa-green-wash);
  border-color: var(--wa-green-line);
  font-weight: 500;
}

.wai-chip__count {
  font-size: 0.7rem;
  font-weight: 600;
  opacity: 0.75;
}

/* ── The call-request queue chip ─────────────────────────────────────────────

   Five of these chips are tabs; this one is a QUEUE of people waiting for the
   phone to ring, and it is the only chip whose count is a number of customers
   rather than a number of conversations to browse. So it gets the amber
   attention register - not the green, which is this file's ONE accent and means
   "you are here", and not the red, which means something is broken. Nothing is
   broken when three people are waiting for a call; there is simply work.

   The emphasis is applied by `wai-chip--queue`, which the component only sets
   while the count is above zero. An empty queue is an ordinary grey chip: a
   chip that shouts every day is a chip nobody sees on the day it matters.

   `:not(.wai-chip--active)` rather than relying on source order, because the
   active state is the stronger statement and must win no matter where these
   rules end up if this file is ever reordered. The wash is layered over the
   opaque chip surface exactly the way the header notices layer theirs - the
   chip sits on white, and a bare rgba would let the pane show through and shift
   the colour the moment anything is put behind it. */
.wai-chip--queue:not(.wai-chip--active) {
  color: var(--wa-warn);
  border-color: var(--wa-warn-line);
  background: linear-gradient(var(--wa-warn-wash), var(--wa-warn-wash)), var(--wa-chip);
}

/* The green hover would otherwise pull the queue chip out of its own register
   for as long as the pointer is on it. Deepening the same wash is the hover
   feedback instead. */
.wai-chip--queue:not(.wai-chip--active):hover {
  color: var(--wa-warn);
  border-color: var(--wa-warn-line);
  background:
    linear-gradient(var(--wa-warn-wash), var(--wa-warn-wash)),
    linear-gradient(var(--wa-warn-wash), var(--wa-warn-wash)), var(--wa-chip);
}

/* On a queue chip the number IS the message, so it does not fade out with the
   rest of the counts. */
.wai-chip--queue .wai-chip__count {
  opacity: 1;
}

/* Sized under the label rather than with it: the icon is a marker that says
   "this chip is a different kind of thing", and at label size it reads as a
   second word. */
.wai-chip__icon {
  font-size: 0.66rem;
}

.wai-list__rows {
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior-y: contain;
  -webkit-overflow-scrolling: touch;
  padding-block-end: var(--space-md, 16px);
}

.wai-list__more {
  display: flex;
  justify-content: center;
  padding: var(--space-md, 16px);
}

/* ── Conversation row ────────────────────────────────────────────────────── */

/* WhatsApp insets the divider so it starts where the text starts and the run of
   avatars is left unbroken down the column. A border on the row itself would
   cut straight through them, so the line is drawn by the text block instead
   (`__main` below) and the row carries none.

   `position: relative` is what lets that pseudo-element hang on the row's
   padding box rather than the text block's, so the line reaches the row's
   inline-end edge. */
.wai-row {
  position: relative;
  width: 100%;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px var(--space-md, 16px);
  font-family: var(--font-body);
  text-align: start;
  background: transparent;
  border: none;
  cursor: pointer;
  transition: background 0.15s ease;
}

/* The divider. Absolutely positioned children resolve against the PADDING box,
   so the inline-start offset has to clear the row's own padding as well as the
   avatar and the gap beside it - 16 + 44 + 10 - and the line then runs to the
   row's far edge. Logical offsets throughout: in this RTL island it begins
   under the name and ends at the left border. */
.wai-row::after {
  content: '';
  position: absolute;
  inset-block-end: 0;
  inset-inline-start: calc(var(--space-md, 16px) + 44px + 10px);
  inset-inline-end: 0;
  height: 1px;
  background: var(--wa-line);
}

/* The last row has nothing under it to be divided from. */
.wai-row:last-child::after {
  content: none;
}

.wai-row:hover {
  background: var(--wa-hover);
}

.wai-row--active {
  background: var(--wa-active);
}

/* :hover outranks the modifier on specificity, and the hover grey is a shade
   LIGHTER than the selected grey - without this, pointing at the selected row
   visibly lifts it. The app holds the selected row still under the pointer. */
.wai-row--active:hover {
  background: var(--wa-active);
}

/* The default avatar is a neutral grey disc with grey initials. WhatsApp never
   tints it with the accent - green is reserved for state, not decoration. The
   disc borrows the bar grey, the darkest neutral surface this skin has. */
.wai-row__avatar {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full, 980px);
  background: var(--wa-header);
  color: var(--wa-ink-2);
  font-size: 1rem;
  font-weight: 600;
}

.wai-row__main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.wai-row__top {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm, 8px);
  min-width: 0;
}

.wai-row__name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--wa-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wai-row--unread .wai-row__name {
  font-weight: 600;
}

.wai-row__time {
  flex: 0 0 auto;
  font-size: 0.7rem;
  color: var(--wa-ink-2);
}

/* On a row with unread messages the timestamp turns green alongside the badge,
   so a scan down the list picks up both signals in one colour. The ink green:
   the badge's bright fill cannot carry 0.7rem text on white. */
.wai-row--unread .wai-row__time {
  color: var(--wa-green-ink);
}

.wai-row__bottom {
  display: flex;
  align-items: center;
  gap: var(--space-sm, 8px);
  min-width: 0;
}

.wai-row__preview {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: 4px;
  font-size: 0.78rem;
  color: var(--wa-ink-2);
}

.wai-row__preview-you {
  flex: 0 0 auto;
  color: var(--wa-ink-3);
}

.wai-row__preview-text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wai-row__badges {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.wai-row__flag {
  font-size: 0.7rem;
  color: var(--wa-ink-2);
  opacity: 0.7;
}

.wai-row__flag--stop {
  color: var(--wa-danger);
  opacity: 1;
}

/* The delivery flags. Full opacity and a colour, unlike the grey conversation
   flags above, because these two are the only ones that say something is WRONG
   rather than something is set.

   Red is reserved for a PROVEN non-delivery; an undetermined send is amber. The
   distinction is the same one .wab-report__status--unknown carries in
   admin-wa-bulk.css and it is not cosmetic: an `unknown` message may already be
   on the customer's phone, so painting it as a failure is how a paying customer
   receives the same Hebrew sentence twice. Never merge these two rules. */
.wai-row__flag--undelivered {
  color: var(--wa-danger);
  opacity: 1;
}

.wai-row__flag--undetermined {
  color: var(--wa-warn);
  opacity: 1;
}

/* Unread count is a bold numeral, so it takes a SOLID accent - the brand
   gradient is forbidden at font-weight 500 and above. WhatsApp fills the whole
   pill with its green and knocks the number out in white - bold and 11px+, the
   one way white is allowed onto the bright green - so the badge carries at a
   glance instead of whispering a tinted wash. */
.wai-row__unread {
  min-width: 20px;
  padding: 1px 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full, 980px);
  background: var(--wa-green);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 600;
}

.wai-row__labels {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  margin-block-start: 2px;
}

.wai-label {
  padding: 1px 8px;
  border: 1px solid transparent;
  border-radius: var(--radius-full, 980px);
  font-size: 0.68rem;
  font-weight: 500;
  max-width: 140px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wai-row__assignee {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 0.68rem;
  color: var(--wa-ink-2);
}

/* ── Thread pane ─────────────────────────────────────────────────────────── */

.wai-thread {
  min-width: 0;
  min-height: 0;
  display: grid;
  /* The implicit column is `auto`, which sizes to the widest child's min-content
     and therefore ignores the track it is in. That is how one long held message
     - `__held-text` is `white-space: nowrap` - inflated the composer to 470px
     inside a 390px pane, and since the pane is `overflow: hidden` and the layout
     is RTL, what got clipped was the inline-start end, which is exactly where
     the cancel button lives. `min-width: 0` on the composer fixes today's case;
     this clamps the class, so the next nowrap child cannot do it again. */
  grid-template-columns: minmax(0, 1fr);
  /* Four rows: header, lead strip, message list, composer. Rows are assigned
     EXPLICITLY below rather than left to auto-placement, because the lead strip
     is conditional - it only exists when the conversation has a linked lead,
     and with auto-placement its absence would slide the list into its `auto`
     row and hand the 1fr to the composer. An empty row just collapses.

     It used to be six: an amber closed-window banner and a standing
     external-WhatsApp pill each held a row of their own, and a footer restated
     the window state a third time. All three were removed on 2026-08-01 - the
     header chip and the composer's blocked text say it once each, and the
     hand-off is an icon in the header.

     The composer is a real grid row and NOT `position: sticky`: a sticky
     composer overlays the last message, and on a short thread it floats in the
     middle of the pane. The last row keeps it pinned to the bottom by layout. */
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  overflow: hidden;
  /* The conversation field itself. The doodle tiles over the deep ground at the
     app's own subtlety, which is what makes the pane read as WhatsApp before a
     single bubble is drawn - the bubbles then sit on their own surfaces above
     it and the texture never competes with them. */
  background-color: var(--wa-deep);
  background-image: var(--wa-doodle);
  background-repeat: repeat;
  background-size: 260px 260px;
}

/* EVERY child sits in the one and only column, stated once and up front.
   Rows below are assigned explicitly; columns are not, and left to itself the
   auto-placement algorithm does NOT put two items with the same definite row
   in the same cell - it walks the column cursor along until it finds a free
   one. The jump pill shares the message list's row (see the rule at the foot
   of this block), so it landed in an IMPLICIT SECOND COLUMN, which
   `grid-auto-columns: auto` then sized to the pill: 44px plus its 16px
   margins, 76px.

   The explicit `minmax(0, 1fr)` track only ever gets what is left over, so on
   a 393px phone every row - header, lead strip, transcript, composer - shrank
   to 317px and was pinned to the inline start (the RIGHT edge, RTL island),
   leaving a bare 76px strip of the doodle wallpaper down the left. The
   wallpaper is painted by `.wai-thread` itself and stayed full-bleed, which is
   what made it read as the pane having slid sideways rather than narrowed.

   It appeared and disappeared under the agent's thumb, because the pill is
   rendered only while the thread is scrolled away from the bottom. */
.wai-thread > * {
  grid-column: 1;
}

.wai-thread > .wai-thread__header {
  grid-row: 1;
}

.wai-thread > .wai-thread__lead-strip {
  grid-row: 2;
}

.wai-thread > .wai-thread__list {
  grid-row: 3;
}

.wai-thread > .wai-composer {
  grid-row: 4;
}

/* Shares the list's cell rather than taking a row: a pill that reserved height
   would push the newest message up by its own size whenever it appeared, which
   is the moment the agent is least interested in the layout moving. Sharing a
   cell is exactly what needs the column pinned as well as the row - the `> *`
   rule at the head of this block is what actually makes this the list's cell
   and not a column of its own. */
.wai-thread > .wai-thread__jump {
  grid-row: 3;
  align-self: end;
  /* RTL island: `end` on the inline axis is the LEFT edge, which is the
     outbound side and where Hebrew WhatsApp puts the same control. */
  justify-self: end;
  z-index: 4;
}

.wai-thread > .wai-empty--pane {
  grid-row: 1 / -1;
}

.wai-thread__header {
  display: flex;
  align-items: center;
  gap: var(--space-sm, 8px);
  padding: 10px var(--space-md, 16px);
  background: var(--wa-header);
  border-block-end: 1px solid var(--wa-line);
}

/* The same disc the list row shows, at the head of the open chat. It is what
   ties the row the agent clicked to the thread that opened - WhatsApp leans on
   exactly this repetition, and without it the header is a bare line of text
   over a very wide pane. Decorative, hence aria-hidden in the template: the
   name beside it is the accessible label. */
.wai-thread__avatar {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full, 980px);
  background: var(--wa-header);
  color: var(--wa-ink-2);
  font-size: 0.95rem;
  font-weight: 600;
}

/* Back is a mobile-only control; at 767px and below the page is a single pane
   and this is the way out of the thread. 40px painted, 44px to the thumb: the
   pseudo-element expands the hit area symmetrically, which needs no logical
   properties because it grows equally on every side. */
.wai-thread__back {
  position: relative;
  display: none;
  width: 40px;
  height: 40px;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--radius-full, 980px);
  background: none;
  color: var(--wa-ink-2);
  font-size: 1rem;
  cursor: pointer;
}

.wai-thread__back::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: -2px;
}

.wai-thread__back:hover {
  color: var(--wa-ink);
}

.wai-thread__id {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.wai-thread__name {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--wa-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wai-thread__phone {
  font-family: var(--font-mono, monospace);
  font-size: 0.72rem;
  color: var(--wa-ink-2);
}

.wai-thread__window {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 3px 10px;
  border: 1px solid var(--wa-green-line);
  border-radius: var(--radius-full, 980px);
  background: var(--wa-green-wash);
  color: var(--wa-green-ink);
  font-size: 0.72rem;
}

.wai-thread__window--closed {
  border-color: var(--wa-line);
  background: var(--wa-chip);
  color: var(--wa-ink-2);
}

/* How long is left in the window. Full-strength ink - the dark skin whispered
   this at 85% opacity, which a light ground cannot afford - and tabular digits
   so the countdown does not wobble the chip's width as it ticks. Dropped on a
   phone, where the header already carries two rows - the chip's tooltip keeps
   the number reachable. */
.wai-thread__window-left {
  font-variant-numeric: tabular-nums;
}

/* The channel controls travel together. `display: contents` keeps the wide
   layout byte-identical to what it was before they were grouped; the phone
   block turns this into the header's second row. */
.wai-thread__meta {
  display: contents;
}

/* ── Assignee picker ─────────────────────────────────────────────────────── */

.wai-thread__assign {
  flex: 0 1 auto;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding-inline: 8px;
  min-height: 32px;
  border: 1px solid var(--wa-line);
  border-radius: var(--radius-full, 980px);
  background: var(--wa-raised);
  color: var(--wa-ink-2);
}

.wai-thread__assign:focus-within {
  border-color: var(--wa-green-line);
  box-shadow: 0 0 0 3px var(--wa-green-wash);
}

.wai-thread__assign-icon {
  flex: none;
  font-size: 0.68rem;
  opacity: 0.7;
}

/* A native select, deliberately: it is the one picker that gets a real
   platform wheel on a phone and needs no keyboard, focus or list-virtualisation
   code of its own. Stripped to look like the pills around it. The surface is
   stated rather than left at `none` because the native control paints its own
   UA chrome otherwise, and the closed control must share the pill's white. */
.wai-thread__assign-select {
  min-width: 0;
  max-width: 148px;
  padding: 0;
  padding-inline-end: 2px;
  border: 0;
  background-color: var(--wa-raised);
  font-family: var(--font-body);
  font-size: 0.74rem;
  color: var(--wa-ink);
  cursor: pointer;
  outline: none;
  text-overflow: ellipsis;
}

.wai-thread__assign-select:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

.wai-thread__assign-error {
  flex: none;
  font-size: 0.7rem;
  color: var(--wa-warn);
}

/* The labelled escape hatches (WhatsApp Web / the app), rendered by the shared
   `WaActionGroup`. Muted on purpose - the thread itself is the default door,
   and neither of these records anything. It was one unlabelled icon until
   2026-08-04; the labels are what let a staff member choose a client instead of
   discovering one. `flex: none` keeps the pair out of the header's shrink
   negotiation, where the assignee select is the thing that should give. */
.wai-thread__external {
  flex: none;
}

/* The lead pane is a grid column above 1024px, so its toggle only exists at
   1024px and below where the pane becomes a slide-over. */
.wai-thread__lead-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  min-height: 40px;
  min-width: 40px;
  padding: 5px 12px;
  border: 1px solid var(--wa-line);
  border-radius: var(--radius-full, 980px);
  background: var(--wa-raised);
  font-family: var(--font-body);
  font-size: 0.76rem;
  color: var(--wa-ink-2);
  cursor: pointer;
}

.wai-thread__lead-toggle:hover {
  color: var(--wa-ink);
  border-color: var(--wa-green-line);
}

/* ── Lead strip (1024px and below) ───────────────────────────────────────────

   Above 1024px the full lead panel is a permanent third column, so this line
   would only repeat it. Below it the panel is a drawer, and these are the two
   facts an agent wants before typing a word. The whole row is one tap target
   into the same drawer - a strip with three separate small controls on it would
   be three ways to miss. */

.wai-thread__lead-strip {
  display: none;
  align-items: center;
  gap: var(--space-sm, 8px);
  width: 100%;
  min-height: 44px;
  padding: 6px var(--space-md, 16px);
  border: 0;
  border-block-end: 1px solid var(--wa-line);
  background: var(--wa-header);
  font-family: var(--font-body);
  text-align: start;
  cursor: pointer;
}

/* A shade down from the bar, layered over it so the strip stays opaque above
   the wallpaper it scrolls against. */
.wai-thread__lead-strip:hover {
  background: linear-gradient(rgba(11, 20, 26, 0.04), rgba(11, 20, 26, 0.04)), var(--wa-header);
}

.wai-thread__lead-strip:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: -2px;
}

.wai-thread__lead-status {
  flex: 0 1 auto;
  min-width: 0;
  padding: 2px 10px;
  border: 1px solid transparent;
  border-radius: var(--radius-full, 980px);
  font-size: 0.72rem;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wai-thread__lead-course {
  flex: 0 1 auto;
  min-width: 0;
  font-size: 0.74rem;
  color: var(--wa-ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wai-thread__lead-follow {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.72rem;
  color: var(--wa-ink-2);
}

/* A follow-up whose date has passed. Red, because this one is not a label -
   it is work that was due and is not done. */
.wai-thread__lead-follow--overdue {
  color: var(--wa-danger-ink);
  font-weight: 500;
}

.wai-thread__lead-chevron {
  flex: none;
  margin-inline-start: auto;
  font-size: 0.68rem;
  color: var(--wa-ink-3);
}

.wai-thread__list {
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior-y: contain;
  -webkit-overflow-scrolling: touch;
  display: flex;
  flex-direction: column;
  /* Tight inside a run, loose between runs. WhatsApp packs consecutive messages
     from one speaker almost against each other and only opens a real gap when
     the speaker changes; `--tail` marks that change (see the bubble section) and
     buys back the space. A uniform gap is what makes an imitation read as a
     list of rows rather than a conversation. */
  gap: 2px;
  padding: var(--space-md, 16px);
}

.wai-thread__older {
  display: flex;
  justify-content: center;
  padding-block: var(--space-sm, 8px);
}

/* Paging back through history is a thumb action at the top of a scroller, which
   is the least comfortable place on a phone to reach - it gets a full target.
   It also takes the chip surface here: over the beige field the white chip is
   the same surface the date pill floats on, so the control reads as part of
   the transcript's grammar rather than a stray admin button. */
.wai-thread__older .btn,
.wai-list__more .btn {
  min-height: 44px;
  padding-inline: 18px;
  border-color: var(--wa-line);
  background: var(--wa-chip);
  color: var(--wa-ink-2);
}

/* Shown only when the ceiling has trimmed the newest page away, so the swap is
   visible and reversible rather than silent. */
.wai-thread__older--newer {
  border-block-start: 1px solid var(--wa-line);
  margin-block-start: var(--space-sm, 8px);
  padding-block-start: var(--space-md, 16px);
}

.wai-thread__notice {
  margin: 0 auto var(--space-sm, 8px);
  max-width: 520px;
  padding: 8px 14px;
  border: 1px solid var(--wa-line);
  border-radius: var(--radius-md, 12px);
  background: var(--wa-chip);
  font-size: 0.76rem;
  line-height: 1.5;
  text-align: center;
  color: var(--wa-ink-2);
}

.wai-thread__end {
  flex: 0 0 auto;
  height: 1px;
}

/* Sticky, so the day you are reading stays named while you scroll through it.
   Consecutive pills push each other out of the way with no JS at all - that is
   simply what two sticky siblings in one scroller do.

   The fill has to be opaque now that bubbles slide UNDER it: a translucent wash
   was legible over the conversation field and unreadable over a message, so the
   pill takes the flat chip surface the app itself uses. z-index 2 puts it above
   the bubbles, which carry none. */
.wai-day {
  position: sticky;
  inset-block-start: 8px;
  z-index: 2;
  align-self: center;
  margin: var(--space-sm, 8px) 0;
  padding: 5px 12px;
  border: none;
  border-radius: 7.5px;
  background: var(--wa-chip);
  box-shadow: var(--wa-shadow-sm);
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  /* Every pill the same width, and centred, so a stack of them covers exactly.
     All the day pills in a thread are siblings in ONE flex container, which
     means they all share the same sticky rectangle and every pill you have
     scrolled past parks at this same offset - a pile, not a queue. The pile is
     harmless because the last one in document order is both the most recent day
     you passed and the one that paints on top, so the label is right. What was
     wrong was that a narrow label ("היום", 47px) sat on top of a wide one
     ("30/07/2026", 85px) and let its neighbour's ends stick out either side,
     which read as one corrupted pill. Matching the widths hides the pile
     instead of restructuring the transcript to avoid it. 92px clears the widest
     label with room for the font to differ. */
  min-width: 92px;
  text-align: center;
  color: var(--wa-ink-2);
}

/* ── First-unread divider ────────────────────────────────────────────────────

   A full-width rule with the label sitting on it. Accent-coloured rather than
   grey: it is the one line in the thread the agent is looking for, and it has
   to be findable while scrolling past. NOT sticky - it marks a fixed place in
   the transcript, and a marker that followed the viewport would stop meaning
   "you were here". */
.wai-unread {
  display: flex;
  align-items: center;
  gap: var(--space-sm, 8px);
  margin: var(--space-sm, 8px) 0;
  font-size: 0.7rem;
  font-weight: 500;
  color: var(--wa-green-ink);
}

.wai-unread::before,
.wai-unread::after {
  content: '';
  flex: 1 1 auto;
  height: 1px;
  background: var(--wa-green-line);
}

.wai-unread__text {
  flex: none;
}

/* ── Jump to the newest message ──────────────────────────────────────────────

   Appears only when the thread is scrolled away from the bottom, with a count
   of what arrived meanwhile. Circular and 44px: it is a one-handed control on a
   phone, reached with the thumb from the composer. */
.wai-thread__jump {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: 0 var(--space-md, 16px) var(--space-md, 16px);
  border: 1px solid var(--wa-line);
  border-radius: var(--radius-full, 980px);
  background: var(--wa-chip);
  box-shadow: var(--wa-shadow-lg);
  color: var(--wa-ink-2);
  font-size: 0.9rem;
  cursor: pointer;
  transition: color 0.15s ease, box-shadow 0.15s ease;
}

.wai-thread__jump:hover {
  color: var(--wa-ink);
  box-shadow: var(--wa-shadow-lg);
}

.wai-thread__jump:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

/* A bold numeral, so it takes a SOLID accent - the brand gradient is forbidden
   at font-weight 500 and above. White, bold, on the green fill: exactly how
   the app prints its own unread counts, and the one place white may sit on
   the bright green. */
.wai-thread__jump-count {
  position: absolute;
  inset-block-start: -4px;
  inset-inline-start: -4px;
  min-width: 20px;
  padding: 1px 5px;
  border-radius: var(--radius-full, 980px);
  background: var(--wa-green);
  color: #fff;
  /* 0.7rem, not smaller: white-on-green is licensed by size and weight alone,
     and this must match the list badge it echoes. */
  font-size: 0.7rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
}

/* ── Message bubble ──────────────────────────────────────────────────────── */

/* BUBBLE SIDE COMES FROM `direction`, NEVER FROM `dir`.
   The list is a column flex container inside the RTL island, so the cross axis
   follows the inline direction: `flex-start` is the RIGHT edge. Inbound sits at
   the inline start (right, the customer) and outbound/echo at the inline end
   (left, us) - exactly how WhatsApp mirrors itself in Hebrew. An English
   message from the customer still belongs on the right, which is precisely why
   the text's own `dir` attribute must never be allowed to move the bubble. */
.wai-bubble {
  max-width: min(78%, 620px);
  padding: 6px 9px 8px;
  border: none;
  border-radius: 7.5px;
  background: var(--wa-in);
  box-shadow: var(--wa-shadow-sm);
  color: var(--wa-ink);
  /* The tail below is an absolutely positioned pseudo-element, so the bubble
     has to be its containing block. */
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.wai-bubble--in {
  align-self: flex-start;
  border-start-start-radius: 0;
}

.wai-bubble--out {
  align-self: flex-end;
  border-start-end-radius: 0;
  background: var(--wa-out);
}

/* The tail that fills the squared corner, on the first bubble of a run only -
   WhatsApp draws it once per speaker turn, not on every message. `background:
   inherit` is what makes one pair of rules serve every bubble colour: the
   triangle always picks up whatever the bubble itself resolved to, including
   the neutral grey a `--sending` row drops back to. Offsets are logical, so
   the tail mirrors with the island instead of stranding on the wrong side. */
.wai-bubble--tail::before {
  content: '';
  position: absolute;
  inset-block-start: 0;
  width: 8px;
  height: 13px;
  background: inherit;
}

/* Opening a run also opens the gap the list's 2px `gap` deliberately withholds.
   `margin` rather than a bigger `gap` because only this bubble should move; the
   ones tucked under it stay packed. */
.wai-bubble--tail {
  margin-block-start: 10px;
}

/* Except at the very top of the transcript, and directly under a date pill or
   the first-unread rule - all three already stand the bubble off from what
   precedes it, and the margin would just leave a hole. */
.wai-thread__list > .wai-bubble--tail:first-child,
.wai-day + .wai-bubble--tail,
.wai-unread + .wai-bubble--tail {
  margin-block-start: 0;
}

.wai-bubble--in.wai-bubble--tail::before {
  inset-inline-start: -8px;
  clip-path: polygon(100% 0, 0 0, 100% 100%);
}

.wai-bubble--out.wai-bubble--tail::before {
  inset-inline-end: -8px;
  clip-path: polygon(0 0, 100% 0, 0 100%);
}

/* A system notice belongs to neither speaker, so it is centred as a plain chip
   with no tail - the same object WhatsApp uses for "messages are encrypted". */
.wai-bubble--system {
  align-self: center;
  max-width: 90%;
  border: none;
  background: var(--wa-chip);
  box-shadow: var(--wa-shadow-sm);
  color: var(--wa-ink-2);
  text-align: center;
}

/* The bubble carries no border any more, so every state that used to speak
   through one speaks through a 1px ring instead. */
.wai-bubble--failed {
  box-shadow: 0 0 0 1px var(--wa-danger-line), var(--wa-shadow-sm);
}

/* An outbound message whose fate we do not know: the provider never answered,
   or we stopped waiting for it to.

   Amber, and a RING rather than the failure red. A receipt can still arrive
   hours later and move this row to delivered, so an outline that reads as a
   verdict would be a lie - and it is precisely the lie that makes an agent
   retype a message the customer is already reading. The thin amber ring
   carries "not settled yet", the same way the held row above the composer
   does. */
.wai-bubble--undetermined {
  box-shadow: 0 0 0 1px var(--wa-warn-line), var(--wa-shadow-sm);
}

/* Still inside the undo window: there is no server row for this bubble at all,
   and the agent can still stop it. Ringed in the composer's own send green so
   the bubble reads as the same object as the cancellable row above it, and
   held back in opacity because it is not part of the record yet. Before this
   it was pixel-identical to a message the server had really accepted, so
   nothing on screen said which of the two could still be stopped. */
.wai-bubble--holding {
  box-shadow: 0 0 0 1px var(--wa-green-line), var(--wa-shadow-sm);
  opacity: 0.75;
}

/* On the wire. The ring goes because the decision has left the agent's hands,
   but the bubble drops OUT of the outbound green instead of keeping it: the
   neutral incoming grey, slightly dimmed, says "in transit, not yet on the
   record", where the green of a plain `--out` row means the server has it.
   Distinct from `--failed` (red ring) and `--undetermined` (amber ring) on
   purpose, and deliberately calm - a send in flight is the normal case. */
.wai-bubble--sending {
  background: var(--wa-in);
  opacity: 0.75;
}

/* ── A message deleted for everyone ────────────────────────────────────────
   The content is KEPT and greyed rather than removed: the durable archive is
   the entire point of this inbox, and staff have to be able to see what was
   retracted - in practice it is usually one of our own wrong links.

   The greying is applied to the CONTENT CHILDREN, not to the bubble. Putting
   `opacity` on `.wai-bubble--deleted` itself would take the label below down
   with it, and the label is the one thing in this bubble that must stay at full
   strength. It also has to name every content kind explicitly: the rule this
   replaced selected `.wai-bubble__text` alone, so a deleted image, voice note,
   document or menu tap carried no visual mark at all - which is exactly how the
   one deleted sticker in the archive has been rendering as an ordinary sticker
   since June. */
.wai-bubble--deleted .wai-bubble__text,
.wai-bubble--deleted .wai-placeholder,
.wai-bubble--deleted .wai-media-thumb,
.wai-bubble--deleted .wai-media-audio,
.wai-bubble--deleted .wai-media-file,
.wai-bubble--deleted .wai-media,
.wai-bubble--deleted .wai-tpl__footertext {
  opacity: 0.55;
  filter: grayscale(1);
}

/* The label. Above the content, at full opacity, and in the danger ink the rest
   of this stylesheet reserves for "something here is not what it looks like".
   Not a footer caption: what a bubble MEANS has to be readable before its
   content, and the footer is where the clock lives. */
.wai-bubble__deleted {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  margin: 0 0 2px;
  font-size: 0.74rem;
  font-weight: 500;
  line-height: 1.5;
  color: var(--wa-danger-ink);
}

/* ── The quoted original, inside the bubble that answers it ────────────────

   Rendered directly under the template header and above the media block, on any
   row that carries a resolved `context` pointer - OUR reply to a customer and
   the customer's reply to us alike.

   Two objects in this file already do most of the work and are echoed rather
   than reinvented:

     - the recessed surface of `.wai-bubble__notice` - the ink at 5%. That value
       is not a taste call: the quote has to recess into whichever colour the
       bubble under it happens to be, and a white chip vanishes on the white
       inbound bubble exactly as a green one would on the outbound. Ink at 5%
       darkens both by the same amount.
     - the accent rail of `.wai-activity__item` - a solid inline-start edge that
       says "this belongs to something else". Thicker (3px) and in the stronger
       `--wa-green-line` rather than that rail's `--wa-green-wash-2`, because
       this one sits on the 5% wash, and on the outbound bubble on the 5% wash
       over pale green: a 20%-alpha green disappears into that. `--wa-green-line`
       is a rail, not a glyph, so the palette's ban on the bright green carrying
       thin marks does not reach it.

   SIZING. `align-self: stretch` and a px floor, never a percentage width. The
   bubble is a shrink-to-fit flex column, so a percentage resolves against a box
   that is itself sizing to this child - the same circular reference that
   collapsed `.wai-media-audio` to ~34px (see its comment below). Stretch is
   already the bubble's default `align-items`, and it is restated here because
   the sibling `.wai-bubble__meta` opts out of it and a reader should not have to
   check which way this one goes. The `min(…, 100%)` floor is the form
   `.wai-bubble--tpl` uses for the same purpose. */
.wai-quote {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: min(120px, 100%);
  margin: 0;
  padding: 4px 8px;
  border: 0;
  border-inline-start: 3px solid var(--wa-green-line);
  border-radius: var(--radius-sm, 8px);
  background: rgba(11, 20, 26, 0.05);
  /* The root is a <button> whenever the original is in the transcript, so the
     UA's centred, 13px, system-font, `color: buttontext` styling has to be
     unwound here rather than in the clickable modifier - otherwise a quote that
     happens to be jumpable renders in a different typeface from one that is
     not. Harmless on the <div> the unjumpable case renders. `text-align: start`
     also protects the block from `.wai-bubble--system`'s centring. */
  font: inherit;
  text-align: start;
  color: inherit;
}

/* NEVER `--wa-green`: 1.9:1 on white, and the palette comment forbids it for
   small text and thin glyphs. `--wa-green-ink` is 6:1 on white and still clears
   AA over the outbound bubble's green. */
.wai-quote__author {
  font-size: 0.72rem;
  font-weight: 500;
  line-height: 1.5;
  color: var(--wa-green-ink);
}

/* Two lines, then ellipsis - the server truncates the excerpt at 300 characters
   (QUOTE_BODY_MAX) and two lines of those 300 is as much context as a quote can
   carry before it competes with the message quoting it. Same clamp recipe as
   `.wai-composer__unknown-text`. `anywhere` stops a quoted URL from setting the
   bubble's width. */
.wai-quote__body {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--wa-ink-2);
  overflow-wrap: anywhere;
}

/* Jumpable: the original is in the loaded transcript and pressing this scrolls
   to it. Hover is the `.wai-media-file` answer - the same recessed card object,
   inside the same bubble - lifted by a shadow, plus the sanctioned 6% disc wash
   so the state is legible on the green bubble too, where a shadow alone is
   nearly invisible. */
.wai-quote--clickable {
  cursor: pointer;
  transition: background-color 0.16s ease, box-shadow 0.16s ease;
}

.wai-quote--clickable:hover {
  background: rgba(11, 20, 26, 0.06);
  box-shadow: var(--wa-shadow-md);
}

/* A positive offset is safe here: the quote is stretched to the bubble's CONTENT
   box and the bubble pads 9px on the inline sides, so a 2px ring 2px out still
   lands inside the bubble - nothing on this path clips it. */
.wai-quote--clickable:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

/* The original is not in the archive. Twelve such pointers exist in production
   and they are not defects: the message they point at predates ingest, or was
   sent from the handset before the number was paired.

   Absence, not failure. This file's colour grammar reserves red for a verdict
   and amber for an open question, and an original that was archived away before
   we started listening is neither - so the block loses its accent rail to a
   plain hairline and its body drops to the third ink in italic. Nothing here is
   pressable, so there is no hover or focus state to match. */
.wai-quote--missing {
  border-inline-start-color: var(--wa-line-strong);
}

.wai-quote--missing .wai-quote__body {
  color: var(--wa-ink-3);
  font-style: italic;
}

/* DELIBERATE: `.wai-quote` is NOT in the `.wai-bubble--deleted` greying list
   above, and this is the second half of the decision that list's comment
   records.

   That list enumerates CONTENT - the thing that was retracted. A quote is not
   this message's content; it is a pointer at a DIFFERENT message, one that was
   not deleted and is still sitting up the transcript at full strength. Greying
   it would state something false about the message it names.

   The list is also already drawn along exactly this line without saying so:
   `.wai-tpl__footertext` (content) is in it, `.wai-tpl__buttons` (interactive)
   is not. A jumpable quote is interactive, and 0.55 opacity plus `grayscale(1)`
   is precisely how this stylesheet renders a control that has been DISABLED -
   see `.wai-bubble__resend:disabled`. A working button dressed as a dead one is
   a worse bug than the greying is a fix. */

/* ── Reply to this message ─────────────────────────────────────────────────

   First child of the bubble's footer, before the captions and the clock.

   The space is ALWAYS reserved and only `opacity` moves. Revealing the button by
   inserting it would reflow the footer under the cursor, and the footer is where
   the resend control lives - a row that changes width at the moment you reach
   for it is how a mis-click happens.

   `.wai-bubble:hover` is the first hover rule this file has ever put on a
   bubble; it is left as tight as it can be (one descendant, and bubbles do not
   nest) so it can never become a general "bubbles react to the pointer" rule.
   The bubble itself deliberately does not change under the cursor: WhatsApp's
   own transcript is inert, and a 78%-wide surface lighting up on hover reads as
   a list of rows rather than a conversation. */
.wai-bubble__reply {
  position: relative;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full, 980px);
  background: none;
  color: var(--wa-ink-3);
  font: inherit;
  font-size: 0.72rem;
  line-height: 1;
  cursor: pointer;
  /* Idle. `pointer-events: none` matters as much as the opacity: an invisible
     22px button sitting in the footer would otherwise swallow the pointer on its
     way to the captions beside it. Keyboard focus still reaches it - focus is
     not a pointer event - which is what makes the `:focus-visible` reveal below
     work at all. */
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease, color 0.15s ease, background-color 0.15s ease;
}

.wai-bubble:hover .wai-bubble__reply,
.wai-bubble__reply:focus-visible {
  opacity: 1;
  pointer-events: auto;
}

/* The disc, at the ink's 6% - the same answer every icon button in this file
   gives a pointer, and the one wash that darkens the white inbound bubble and
   the green outbound one by the same amount. */
.wai-bubble__reply:hover {
  background: rgba(11, 20, 26, 0.06);
  color: var(--wa-ink-2);
}

.wai-bubble__reply:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

/* `:hover` never fires on a phone, so on a coarse pointer an opacity-0 control
   is not "subtle", it is unreachable. It is simply always on there, and it takes
   the 44px the thumb needs through the same transparent `::after` expander the
   strip and back controls use - the glyph stays 22px and dense, only the hit
   box grows.

   The expander is scoped to this query rather than declared on the base rule,
   because a 44px invisible box in the footer is not free: it is positioned, so
   it paints above the non-positioned captions and the resend pill 6px along the
   row, and above the last line of body text 4px above. On a fine pointer that
   trade buys nothing - the mouse can hit 22px - so it is not made there. On a
   coarse one it is worth taking, and the one collision that matters is benign in
   the only direction it can happen: a thumb aimed at the leading edge of the
   resend pill opens the composer's quote bar instead, which is a cancel away.
   The reverse - aiming at reply and resending a message - cannot occur, because
   the resend pill is not positioned and can never paint over this. */
@media (hover: none) {
  .wai-bubble__reply {
    opacity: 1;
    pointer-events: auto;
  }

  .wai-bubble__reply::after {
    content: '';
    position: absolute;
    inset: 0;
    margin: -11px;
  }
}

/* ── Jump-to-original flash ────────────────────────────────────────────────

   Pressing a quote scrolls to the message it names; this is what tells you which
   of the bubbles now on screen you were sent to. The first (and only) keyframed
   animation in this file.

   It is painted by `::after` because `::before` is taken by the speaker-run tail
   (`.wai-bubble--tail::before`, which fills its squared corner with
   `background: inherit`), and as an overlay INSIDE the bubble rather than as a
   ring or a raised z-index around it: `.wai-bubble` is `position: relative` with
   NO z-index, and giving it one would lift it over the sticky `.wai-day` date
   pill (z-index 2), which was deliberately re-engineered to be opaque and to
   paint on top of the bubbles sliding under it.

   The overlay is above the bubble's text - a positioned pseudo-element paints
   after in-flow content - which is why it is the accent at 20% and gone inside a
   second and a half rather than anything more emphatic.

   `forwards` is load-bearing, not polish: without a fill mode the element snaps
   back to the 0% keyframe's computed value when the animation ends, which here
   would leave a permanent green wash on any bubble whose class the store forgets
   to clear. Holding the LAST keyframe means the worst case of a stuck class is
   an invisible overlay.

   THE DURATION IS PINNED TO `FLASH_MS` IN WaThread.vue (1200ms), which is the
   timer that strips the class. The two have to agree in both directions and the
   CSS is the side that gives way: a longer animation than the timer is cut off
   mid-fade and the wash vanishes in one frame, which is the exact "did something
   just break" flicker the fade exists to avoid; a shorter one leaves the bubble
   plain for the remainder and the class does nothing. Change one, change both. */
@keyframes wai-bubble-flash {
  0% { opacity: 0; }
  10% { opacity: 1; }
  55% { opacity: 1; }
  100% { opacity: 0; }
}

.wai-bubble--flash::after {
  content: '';
  position: absolute;
  inset: 0;
  /* Follows every corner the bubble resolved, including the squared one a
     `--in` / `--out` bubble opens its run with. */
  border-radius: inherit;
  background: var(--wa-green-wash-2);
  pointer-events: none;
  animation: wai-bubble-flash 1.2s ease-out forwards;
}

/* ── An approved template, as the customer received it ─────────────────────
   A template is a COMPOSED message, not a paragraph: a header image bled to
   the bubble's edges, the body, a small grey sign-off, and a block of buttons
   ruled off across the foot. Everything below is geometry that only applies
   when the row carries a snapshot of that structure - a template sent before
   the snapshot column existed keeps the plain bubble it has always had.

   A minimum width, because a flex column shrink-wraps its content: a two-word
   template body would otherwise squeeze the button rows into a sliver. */
.wai-bubble--tpl {
  min-width: min(260px, 100%);
}

/* The header image reaches the bubble's edges. The bubble pads 6px top and 9px
   sides, so pulling back by exactly that much lands the image flush, and a
   slightly tighter radius than the bubble's 7.5px keeps the corner from
   showing a sliver of green inside the curve. */
.wai-bubble--tpl .wai-tpl__hero {
  margin: -6px -9px 0;
  width: calc(100% + 18px);
  max-width: none;
  /* Logical, and slightly tighter than the bubble's own 7.5px so no sliver of
     green shows inside the curve. The bottom corners are square because the
     body sits directly under the image. */
  border-start-start-radius: 6px;
  border-start-end-radius: 6px;
  border-end-start-radius: 0;
  border-end-end-radius: 0;
}

/* The bubble squares its own tail corner (`.wai-bubble--out` sets
   `border-start-end-radius: 0`), so the image has to square the SAME corner or
   a rounded image corner sits inside a square bubble corner and leaves a green
   wedge. Outbound only: a template is a message we send, and an echo of one
   wears `--out` too. */
.wai-bubble--out.wai-bubble--tpl .wai-tpl__hero {
  border-start-end-radius: 0;
}

/* The button block runs edge to edge across the foot of the bubble, and the
   hairline above it is the single most visible tell of the whole thing: a
   divider that stops 9px short of the edge reads as a mock-up. -4px absorbs
   the bubble's own 4px flex gap so the rule sits tight under the timestamp;
   -8px carries the last row down to the bubble's bottom edge. */
.wai-bubble--tpl .wai-tpl__buttons {
  align-self: stretch;
  margin: -4px -9px -8px;
  border-block-start: 1px solid var(--wa-line);
}

.wai-bubble__text {
  margin: 0;
  font-size: 0.87rem;
  line-height: 1.55;
  color: var(--wa-ink);
  /* pre-wrap keeps the sender's line breaks; `anywhere` stops a 300-character
     URL from widening the grid and creating a horizontal scrollbar. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.wai-bubble__text--caption {
  font-size: 0.8rem;
  color: var(--wa-ink-2);
}

/* Links get their own darker blue rather than the send green or the receipt
   blue: green on the green outbound bubble would vanish, and the tick's pale
   blue cannot carry text on white - --wa-link is the same hue pulled down to
   AA. */
.wai-bubble a {
  color: var(--wa-link);
}

.wai-seg--ltr {
  font-family: var(--font-mono, monospace);
  font-size: 0.82em;
}

/* ── WhatsApp's own inline formatting ──────────────────────────────────────
   `*bold*`, `_italic_`, `~strike~` and ```mono```. The delimiters are consumed
   by segmentBody, so what is left here is only the styling they asked for -
   which is the whole point: the customer's phone rendered these, and an inbox
   printing the asterisks was showing staff a different message from the one
   that was sent.

   Weights are deliberately modest. 600 rather than 700 because the bubble text
   sits at 0.87rem on a pale green ground, where a true bold blocks up; this is
   the same weight the app itself lands on at this size. */
.wai-fmt--bold {
  font-weight: 600;
}

.wai-fmt--italic {
  font-style: italic;
}

.wai-fmt--strike {
  text-decoration: line-through;
}

/* Monospace shifts FAMILY only - no background chip, no border. WhatsApp draws
   it as bare monospace text, and a chip inside a bubble that already carries a
   colour reads as a second message. 0.92em keeps the x-height honest next to
   the body font, which runs larger at the same nominal size. */
.wai-fmt--mono {
  font-family: var(--font-mono, monospace);
  font-size: 0.92em;
}

.wai-placeholder {
  margin: 0;
  padding: 4px 0;
  font-size: 0.8rem;
  font-style: italic;
  color: var(--wa-ink-2);
}

/* The NOT-stored treatments: pending, failed, expired, too_large, plus the
   pre-ingest rows whose bytes were never captured. It sits in the same
   recessed ink wash as the file card below but offers nothing to open - no
   link, no filename, just the status line - so it reads as an absence where
   the card below reads as a file you have. The wash, ink at 5%, recesses on
   the white inbound bubble and the green outbound one alike, which is how the
   app itself tiles attachments into either colour. Never a provider link
   here: those URLs are signed, five-minute artefacts. */
.wai-media {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm, 8px);
  padding: 8px 10px;
  border: none;
  border-radius: 7.5px;
  background: rgba(11, 20, 26, 0.05);
}

.wai-media__icon {
  margin-block-start: 2px;
  font-size: 0.85rem;
  color: var(--wa-ink-2);
}

.wai-media__lines {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.wai-media__text {
  font-size: 0.8rem;
  color: var(--wa-ink-2);
}

.wai-media__kind {
  font-size: 0.7rem;
  color: var(--wa-ink-2);
  opacity: 0.8;
}

.wai-media__name {
  font-family: var(--font-mono, monospace);
  font-size: 0.7rem;
  color: var(--wa-ink);
  overflow-wrap: anywhere;
}

/* ── Stored media, inside the bubble ──────────────────────────────────────
   There are no thumbnails: `thumb_storage_key` has no writer and sharp is
   refused on a 512MB box, so the ORIGINAL is served and the client is made
   cheap instead - loading="lazy" (no request until the bubble nears the
   viewport), decoding="async", and the dimensions constrained here in CSS. */

.wai-media-thumb {
  display: block;
  /* The bubble is a column flex container, so without this the button stretches
     to the full bubble width and draws its placeholder surface across empty
     space beside the photo. Hug the image instead. */
  align-self: flex-start;
  width: fit-content;
  max-width: 100%;
  /* Reserves space before the lazy image resolves, so a thread does not jump
     as it scrolls. Without intrinsic dimensions on the row there is nothing
     more precise to reserve. */
  min-width: 96px;
  min-height: 96px;
  padding: 0;
  border: none;
  border-radius: 7.5px;
  background: rgba(11, 20, 26, 0.05);
  overflow: hidden;
  cursor: pointer;
  /* The image is a replaced inline element; without this its baseline gap
     leaves a few stray pixels inside the rounded corner. */
  line-height: 0;
  transition: box-shadow 0.16s ease;
}

.wai-media-thumb:hover {
  box-shadow: var(--wa-shadow-md);
}

.wai-media-thumb:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

.wai-media-thumb__img {
  display: block;
  width: auto;
  height: auto;
  max-width: min(260px, 100%);
  max-height: 320px;
  object-fit: contain;
}

.wai-media-thumb--video {
  display: flex;
  align-items: center;
  gap: var(--space-sm, 8px);
  min-width: 0;
  min-height: 0;
  padding: 10px 12px;
  line-height: normal;
  text-align: start;
}

.wai-media-thumb__play {
  font-size: 1.1rem;
  color: var(--wa-ink);
}

.wai-media-thumb__label {
  font-size: 0.8rem;
  color: var(--wa-ink);
}

/* The native player IS the feature - there is no custom scrubber. `preload`
   is "none" in the template, so nothing is fetched until play is pressed.

   Width is a FIXED px and not `min(280px, 100%)`. The bubble is a shrink-to-fit
   flex column, so a percentage width resolves against a container that is
   itself sizing to this element - the circular reference collapses the player
   to about 34px, which is narrower than its own play button. A fixed basis
   gives the bubble something to size from; max-width still rescues it on a
   narrow screen. */
.wai-media-audio {
  display: block;
  width: 280px;
  max-width: 100%;
}

/* Documents download in one click rather than opening a viewer first. */
.wai-media-file {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: none;
  border-radius: 7.5px;
  background: rgba(11, 20, 26, 0.05);
  color: inherit;
  text-decoration: none;
  transition: box-shadow 0.16s ease;
}

.wai-media-file:hover {
  box-shadow: var(--wa-shadow-md);
}

.wai-media-file:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

.wai-media-file__icon {
  flex: none;
  font-size: 1.05rem;
  color: var(--wa-ink-2);
}

.wai-media-file__lines {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.wai-media-file__name {
  font-family: var(--font-mono, monospace);
  font-size: 0.72rem;
  color: var(--wa-ink);
  overflow-wrap: anywhere;
}

.wai-media-file__meta {
  font-size: 0.7rem;
  color: var(--wa-ink-2);
}

/* ── Full-size viewer ─────────────────────────────────────────────────────
   Teleported to <body>, so it sits OUTSIDE .admin-layout and inherits neither
   the RTL island nor the --adm-* token scope. The direction is re-established
   here, the palette block at the top of the file names .wai-viewer alongside
   .wai-page so the --wa-* tokens reach it too, and every var still carries a
   fallback. */

.wai-viewer {
  position: fixed;
  inset: 0;
  z-index: 2100;
  direction: rtl;
  display: flex;
  flex-direction: column;
  font-family: var(--font-body, "Helvetica Neue", sans-serif);
  /* WhatsApp Web keeps its media viewer LIGHT: the bar grey at 95%, not a
     black-out - the media is judged against the same light chrome the app
     itself uses, and the last 5% keeps the viewer reading as a layer over the
     inbox rather than a separate screen. */
  background: rgba(240, 242, 245, 0.95);
}

.wai-viewer__bar {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md, 16px);
  padding: 12px 16px;
  background: var(--wa-header);
  border-block-end: 1px solid var(--wa-line);
}

.wai-viewer__title {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
  color: var(--wa-ink);
}

.wai-viewer__kind {
  font-size: 0.85rem;
}

.wai-viewer__name {
  font-family: var(--font-mono, monospace);
  font-size: 0.75rem;
  color: var(--wa-ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wai-viewer__size {
  font-size: 0.72rem;
  color: var(--wa-ink-2);
}

.wai-viewer__actions {
  flex: none;
  display: flex;
  align-items: center;
  gap: 4px;
}

.wai-viewer__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full, 980px);
  background: transparent;
  color: var(--wa-ink-2);
  font-size: 0.95rem;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.16s ease, color 0.16s ease;
}

.wai-viewer__btn:hover {
  background: rgba(11, 20, 26, 0.06);
  color: var(--wa-ink);
}

.wai-viewer__btn:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

/* The viewer is full-screen, so in a standalone PWA on a notched phone its
   bottom edge sits under the home indicator. The stage is what reaches it -
   the viewer has no bottom bar of its own. */
.wai-viewer__stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-lg, 24px);
  padding-block-end: calc(var(--space-lg, 24px) + env(safe-area-inset-bottom));
}

.wai-viewer__image,
.wai-viewer__video {
  display: block;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: var(--radius-sm, 8px);
}

.wai-viewer__fallback {
  display: flex;
  align-items: center;
  gap: var(--space-sm, 8px);
  margin: 0;
  color: var(--wa-ink-2);
  font-size: 0.9rem;
}

/* Reached by paging onto a document with the arrow keys. A document is never
   rendered inline - it is offered as a download, exactly as in the bubble.
   On the light stage the card is a plain white panel that separates by
   hairline and shadow; the dark skin's white-alpha washes have nothing to
   wash against here. */
.wai-viewer__doc {
  display: flex;
  align-items: center;
  gap: var(--space-md, 16px);
  max-width: min(420px, 100%);
  padding: var(--space-lg, 24px);
  border: 1px solid var(--wa-line);
  border-radius: var(--radius-lg, 16px);
  background: var(--wa-panel);
  box-shadow: var(--wa-shadow-md);
  color: var(--wa-ink);
  text-decoration: none;
  transition: background-color 0.16s ease, border-color 0.16s ease;
}

.wai-viewer__doc:hover {
  border-color: var(--wa-line-strong);
  background: var(--wa-hover);
}

.wai-viewer__doc:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

.wai-viewer__doc-icon {
  flex: none;
  font-size: 1.6rem;
  color: var(--wa-ink-2);
}

.wai-viewer__doc-lines {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.wai-viewer__doc-name {
  font-family: var(--font-mono, monospace);
  font-size: 0.78rem;
  overflow-wrap: anywhere;
}

.wai-viewer__doc-size {
  font-size: 0.72rem;
  color: var(--wa-ink-3);
}

.wai-viewer__doc-cta {
  flex: none;
  margin-inline-start: auto;
  font-size: 0.82rem;
  color: var(--wa-ink-2);
}

.wai-bubble__reactions {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.wai-reaction {
  padding: 1px 7px;
  /* Strong hairline: a white pill sitting on the white inbound bubble has
     nothing but this edge to exist by. */
  border: 1px solid var(--wa-line-strong);
  border-radius: var(--radius-full, 980px);
  background: var(--wa-chip);
  font-size: 0.8rem;
  line-height: 1.4;
}

.wai-bubble__error {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  margin: 0;
  font-size: 0.74rem;
  color: var(--wa-danger-ink);
}

/* The honest-unknown twin of `__error`. Amber rather than red for the same
   reason the ring is: we are reporting an open question, not a verdict. */
.wai-bubble__note {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  margin: 0;
  font-size: 0.74rem;
  line-height: 1.5;
  color: var(--wa-warn);
}

/* The result of a resend, per message. Never routed through the composer's
   global error line, which the next send wipes. */
.wai-bubble__notice {
  margin: 0;
  padding: 5px 8px;
  border-radius: var(--radius-sm, 8px);
  /* The ink at 5%, not the white chip: inside a bubble the notice has to
     recess into whichever colour the bubble is, and white on the white
     inbound bubble would vanish. */
  background: rgba(11, 20, 26, 0.05);
  font-size: 0.74rem;
  line-height: 1.5;
  color: var(--text-secondary, #5a5b6a);
}

/* Kind names come straight from the store's notice kinds, so the mapping stays
   one-to-one and needs no translation table in the template. The base rule
   above IS the `info` kind, which is why there is no `--info` selector.

   `--caution` is amber and NEVER red. It is the "we do not know whether the
   customer has this" reading, and dressing it as a failure is what makes an
   agent send the message a second time by hand. */
.wai-bubble__notice--error {
  background: var(--wa-danger-wash);
  color: var(--wa-danger-ink);
}

.wai-bubble__notice--caution {
  background: var(--wa-warn-wash);
  color: var(--wa-warn);
}

/* Pinned to the bubble's inline end, the way the app parks the time and tick
   under the last line of text. */
.wai-bubble__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  align-self: flex-end;
  gap: 6px;
  font-size: 0.68rem;
  color: var(--wa-ink-2);
}

/* Sits with the captions, before the time/tick pair that `__time` pins to the
   inline end - so the action never crowds the metadata it belongs to. */
.wai-bubble__resend {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 10px;
  border: 1px solid var(--wa-warn-line);
  border-radius: var(--radius-full, 980px);
  background: transparent;
  color: var(--wa-warn);
  font: inherit;
  font-size: 0.7rem;
  cursor: pointer;
  transition: background 0.15s ease;
}

.wai-bubble__resend:hover:not(:disabled) {
  background: var(--wa-warn-wash);
}

.wai-bubble__resend:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

/* In flight. The control has to stop LOOKING pressable, not just stop
   responding - a button that swallows clicks silently gets clicked again. */
.wai-bubble__resend:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.wai-bubble__caption {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}

.wai-bubble__caption--strong {
  color: var(--wa-danger-ink);
  font-weight: 500;
}

/* The canned auto-reply. Slightly more present than a plain caption because
   "no human wrote this" is the single fact an agent scanning a thread has to
   register before they answer - but still a caption, not a badge competing
   with the message itself. */
.wai-bubble__caption--auto {
  color: var(--wa-ink-2);
  opacity: 1;
  font-weight: 500;
}

/* The "תבנית" mark on a template bubble: an outlined micro-pill rather than a
   caption, because "this cost money and ignored the window" is a different
   kind of fact from who typed it. Sized to the footer's caption type. */
.wai-tpl-chip {
  display: inline-flex;
  align-items: center;
  padding: 1px 7px;
  border: 1px solid var(--wa-line-strong);
  border-radius: var(--radius-full, 980px);
  font-size: 0.62rem;
  font-weight: 500;
  color: var(--wa-ink-2);
}

/* Timestamp and tick take the third ink, the exact grey the app stamps under
   its own bubbles in light mode - 4.6:1 on the white bubble, quieter than the
   captions beside them. */
.wai-bubble__time {
  font-family: var(--font-mono, monospace);
  font-size: 0.66rem;
  margin-inline-start: auto;
  color: var(--wa-ink-3);
}

.wai-bubble__tick {
  font-size: 0.66rem;
  color: var(--wa-ink-3);
}

/* The blue double check is the one thing WhatsApp does not paint green, so it
   keeps its own token rather than borrowing the accent. */
.wai-bubble__tick--read {
  color: var(--wa-tick);
}

.wai-bubble__tick--failed {
  color: var(--wa-danger);
}

.wai-bubble__tick--undetermined {
  color: var(--wa-warn);
}

/* ── Lead panel ──────────────────────────────────────────────────────────── */

.wai-lead {
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--wa-panel);
  border-inline-start: 1px solid var(--wa-line);
  overflow: hidden;
}

.wai-lead__header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm, 8px);
  padding: 12px var(--space-md, 16px);
  background: var(--wa-header);
  border-block-end: 1px solid var(--wa-line);
}

.wai-lead__title {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--wa-ink);
}

/* Closing is only meaningful while the panel is a slide-over, which is to say
   on exactly the widths where it has to be a real touch target. */
.wai-lead__close {
  position: relative;
  display: none;
  width: 40px;
  height: 40px;
  margin: -6px;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--radius-full, 980px);
  background: none;
  color: var(--wa-ink-2);
  font-size: 0.95rem;
  cursor: pointer;
}

.wai-lead__close::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: -2px;
}

.wai-lead__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior-y: contain;
  -webkit-overflow-scrolling: touch;
  padding: var(--space-md, 16px);
  display: flex;
  flex-direction: column;
  gap: var(--space-md, 16px);
}

/* The banner is a caution, not a failure: it reports what a classifier thinks it
   saw. On the light panel that reads as the warn ink over its own amber wash,
   which stays legible without shouting the way the danger red would. */
.wai-banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm, 8px);
  padding: 10px 12px;
  border: 1px solid var(--wa-warn-line);
  border-radius: var(--radius-md, 12px);
  background: var(--wa-warn-wash);
  color: var(--wa-warn);
}

.wai-banner__lines {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.wai-banner__title {
  font-size: 0.8rem;
  font-weight: 600;
}

/* The line that says the flag is a machine's reading and not a verdict. Quieter
   than the title and than the quoted evidence, because it is an instruction to
   the reader rather than a fact about the customer. */
.wai-banner__hint {
  font-size: 0.74rem;
  line-height: 1.5;
}

/* "That was not a request like that". Deliberately understated - an outlined
   chip, not a button that invites a reflex click, because dismissing a REAL
   refusal is the expensive direction of this decision. Sits inside the banner
   so the judgement is made next to the evidence it is about. */
.wai-banner__dismiss {
  align-self: flex-start;
  margin-block-start: 6px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px;
  border: 1px solid currentColor;
  border-radius: var(--radius-full, 980px);
  background: transparent;
  color: inherit;
  font-family: inherit;
  font-size: 0.74rem;
  cursor: pointer;
  opacity: 0.85;
  transition: opacity 0.15s ease;
}

.wai-banner__dismiss:hover:not(:disabled) {
  opacity: 1;
}

.wai-banner__dismiss:disabled {
  cursor: default;
  opacity: 0.5;
}

/* The quoted message is evidence, so it drops the banner's amber and returns to
   ordinary panel ink - it is the customer's words, not our warning. */
.wai-banner__quote {
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--wa-ink);
  overflow-wrap: anywhere;
}

.wai-banner__meta {
  font-size: 0.7rem;
}

.wai-card {
  padding: var(--space-md, 16px);
  border: 1px solid var(--wa-line);
  border-radius: 8px;
  background: var(--wa-header);
  box-shadow: var(--wa-shadow-sm);
}

.wai-card__title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-sm, 8px);
  margin: 0 0 10px;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--wa-ink);
}

.wai-lead__name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.95rem;
  overflow-wrap: anywhere;
}

.wai-status-chip {
  padding: 2px 10px;
  border: 1px solid transparent;
  border-radius: var(--radius-full, 980px);
  font-size: 0.7rem;
  font-weight: 500;
}

.wai-fields {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.wai-field {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm, 8px);
  font-size: 0.78rem;
}

.wai-field__label {
  flex: 0 0 88px;
  color: var(--wa-ink-2);
}

.wai-field__value {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  color: var(--wa-ink);
  overflow-wrap: anywhere;
}

.wai-field__value--trunc {
  font-family: var(--font-mono, monospace);
  font-size: 0.7rem;
  max-height: 2.6em;
  overflow: hidden;
}

/* Links take the same darker blue the bubbles use for URLs, so the one
   non-green accent in the skin stays consistent - the receipt blue itself is
   too pale to carry text on white. */
.wai-lead__link {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin-block-start: 10px;
  font-size: 0.78rem;
  color: var(--wa-link);
  text-decoration: none;
}

.wai-lead__link:hover {
  text-decoration: underline;
}

.wai-items {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-sm, 8px);
}

/* An item sits inside a card, so it is painted a step apart from it - a white
   tile lifted off the card's bar grey, where the dark skin recessed it into a
   darker shade instead. */
.wai-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 10px;
  border: 1px solid var(--wa-line);
  border-radius: var(--radius-sm, 8px);
  background: var(--wa-chip);
}

.wai-item__main {
  font-size: 0.79rem;
  font-weight: 500;
  color: var(--wa-ink);
}

.wai-item__sub {
  font-size: 0.72rem;
  color: var(--wa-ink-2);
}

/* ── The ex-VAT parenthetical in this pane ─────────────────────────────────
   Base styling is shared (.exvat / .exvat-legend, admin-money.css); these two
   rules are the surface tuning the load order exists to allow. The GAP is not
   tuned here and must not be: ExVat.vue emits it as a real leading space rather
   than a margin, so it is plain whitespace in this pane's flow and is correct
   in RTL and LTR without anyone choosing a side. A per-view override of that is
   how eleven screens drift apart.

   The pane is already set in small print - 0.72rem tiles, 0.78rem field rows -
   so the shared `--sm` step, which assumes a table cell at 0.85rem, would land
   the ex-VAT figure near 9px: past small and into decorative. It gives one notch
   back and takes nothing else. */
.wai-lead .exvat--sm {
  font-size: 0.88em;
}

/* The legend explains the whole pane, so it reads as a note under the card's
   own title rather than as a first row of content. */
.wai-lead .exvat-legend {
  margin: -2px 0 8px;
}

/* A tile that opens the sale it describes. The anchor - not the <li> - carries
   the padding, so the whole tile is the target rather than the text inside it.
   Only the cohorts list is clickable; the contracts and open-day tiles beside
   it stay plain, which is why the affordance hangs off a modifier instead of
   being added to `.wai-item` itself. */
.wai-item--link {
  padding: 0;
}

.wai-item__open {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 10px;
  border-radius: inherit;
  color: inherit;
  text-decoration: none;
}

.wai-item--link:hover {
  border-color: var(--wa-green-line);
  background: var(--wa-green-wash);
}

.wai-item__open:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: -2px;
}

.wai-activity {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* The rail down the side of an entry is the accent at wash strength: present
   enough to group the entries, too faint to be mistaken for a status. */
.wai-activity__item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-inline-start: 10px;
  border-inline-start: 2px solid var(--wa-green-wash-2);
}

.wai-activity__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 0.72rem;
  color: var(--wa-ink-2);
}

.wai-activity__type {
  font-weight: 600;
  color: var(--wa-ink);
}

.wai-activity__time {
  margin-inline-start: auto;
  color: var(--wa-ink-3);
}

.wai-activity__body {
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--wa-ink);
  overflow-wrap: anywhere;
}

/* ── Scrim (slide-over only) ─────────────────────────────────────────────── */

.wai-scrim {
  display: none;
}

/* ── Composer (Phase 2) ──────────────────────────────────────────────────── */

.wai-composer {
  display: flex;
  flex-direction: column;
  /* Measured on a real 390px viewport, 2026-07-30: WITHOUT this the composer is
     641px wide inside a 390px thread pane and the overflow is clipped by
     `.wai-thread`. It is a grid item, so its automatic minimum size is its
     min-content width, and one long held message - `__held-text` is
     `white-space: nowrap` - drags that past the track. The row is anchored to
     the inline end in RTL, so what gets pushed off the screen is the CANCEL
     BUTTON at the other end: it measured left -222px, unreachable, on exactly
     the phones the undo window exists for. `min-width: 0` lets the track win,
     the row shrink and the text ellipsise, which is what its
     `text-overflow: ellipsis` was always for. */
  min-width: 0;
  gap: 8px;
  padding: 10px var(--space-md, 16px) 14px;
  /* In a standalone PWA on a notched phone the send button lands under the home
     indicator without this. The 0.875rem repeats the 14px above rather than
     replacing it, so the padding is unchanged everywhere the inset is 0. */
  padding-block-end: calc(0.875rem + env(safe-area-inset-bottom));
  background: var(--wa-header);
  border-block-start: 1px solid var(--wa-line);
}

.wai-composer__tabs {
  display: flex;
  align-items: center;
  gap: 4px;
  /* The labelled pills beside the tab pair outgrow a phone's row - wrapping
     drops them to their own line instead of forcing a horizontal scroll. */
  flex-wrap: wrap;
}

/* The pair sits on its own recessed ground - the ink at 5% over the bar - so
   the active tab reads as a white chip lifted out of it, which is how the app
   draws a segmented control. */
.wai-composer__tabgroup {
  display: flex;
  align-items: center;
  gap: 4px;
  background: rgba(11, 20, 26, 0.05);
}

.wai-composer__tab {
  min-height: 40px;
  padding: 4px 14px;
  border: 0;
  border-radius: var(--radius-full, 980px);
  background: transparent;
  color: var(--text-secondary, #5a5b6a);
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

/* Canned replies. Pushed to the far edge of the tab row - it is a tool, not a
   third tab, and putting it beside the two would invite a tap meant for them. */
/* Labelled pills, deliberately NOT pushed to the row's far edge: they sit
   right beside the message/note tab pair (owner call 2026-08-03), so the
   whole "what goes into this box" cluster reads as one group. */
.wai-composer__canned {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 44px;
  padding-inline: 14px;
  border: 1px solid var(--wa-line);
  border-radius: var(--radius-full, 980px);
  background: transparent;
  color: var(--wa-ink-2);
  font-size: 0.9rem;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}

.wai-composer__canned:hover {
  background: rgba(11, 20, 26, 0.06);
  color: var(--wa-ink);
  border-color: var(--wa-line-strong);
}

.wai-composer__canned:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

/* `:not()` is load-bearing, not decoration: `.wai-composer__tab:hover` scores
   (0,2,0) against `--active`'s (0,1,0), so a plain hover rule WINS over the
   active state and greys out the selected tab under the cursor - which is
   exactly where the cursor is after you click it. */
.wai-composer__tab:hover:not(.wai-composer__tab--active) {
  background: rgba(11, 20, 26, 0.04);
}

.wai-composer__tab--active {
  background: var(--wa-raised);
  color: var(--wa-ink);
}

.wai-composer__tab:focus-visible,
.wai-composer__send:focus-visible,
.wai-composer__undo:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

.wai-composer__row {
  display: flex;
  align-items: flex-end;
  gap: 8px;
}

.wai-composer__input {
  flex: 1 1 auto;
  min-width: 0;
  /* Grows with the text but never eats the thread: ~6 lines then scrolls. */
  min-height: 38px;
  max-height: 140px;
  padding: 9px 12px;
  /* A real hairline, unlike the search field's reserved pixel: the white
     field needs an edge against the grey bar it sits on. Focus repaints the
     same pixel green. */
  border: 1px solid var(--wa-line);
  border-radius: 8px;
  background: var(--wa-raised);
  color: var(--wa-ink);
  font: inherit;
  font-size: 0.9rem;
  line-height: 1.5;
  resize: vertical;
}

.wai-composer__input::placeholder {
  color: var(--wa-ink-2);
}

/* The app does not ring its message field - focus is the border going green,
   nothing more, so the box never grows a second outline at the moment you
   start typing. */
.wai-composer__input:focus-visible {
  outline: none;
  border-color: var(--wa-green-ink);
}

/* A note is not a message - the amber field is the standing reminder that
   nothing typed here will ever reach the customer. */
.wai-composer__input--note {
  background: var(--wa-warn-wash);
  border-color: var(--wa-warn-line);
}

.wai-composer__send {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  /* On a coarse pointer this is the ONLY way to send - Enter makes a newline
     there - so it is a full 44px target rather than a nicety. */
  min-height: 44px;
  min-width: 44px;
  padding: 9px 16px;
  border: 0;
  border-radius: var(--radius-full, 980px);
  background: var(--wa-green);
  /* White on the bright green carries only as a bold label or an icon - the
     weight is part of the contrast budget, not a styling flourish. */
  color: #fff;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, opacity 0.15s ease;
}

.wai-composer__send:hover:not(:disabled) {
  background: var(--wa-green-hi);
}

/* Disabled is stated in colour rather than in opacity: a faded green reads as
   a rendering glitch, while a flat white chip with tertiary ink reads as a
   control that is simply not available yet. */
.wai-composer__send:disabled {
  background: var(--wa-raised);
  color: var(--wa-ink-3);
  cursor: not-allowed;
}

/* The note save wears the warn ink as a FILL - dark enough that white on it
   is 4.9:1, which the bright green never manages. */
.wai-composer__send--note {
  background: var(--wa-warn);
  color: #fff;
}

/* There is no second amber in the palette to hover into, so the button states
   the hover as a halo in its own wash instead of a second fill. */
.wai-composer__send--note:hover:not(:disabled) {
  background: var(--wa-warn);
  box-shadow: 0 0 0 3px var(--wa-warn-wash);
}

/* Held message: the undo window. Nothing has been sent yet, so this is a real
   cancel and not a recall - keep the wording and the affordance honest. */
.wai-composer__held {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 12px;
  border: 1px dashed var(--wa-green-line);
  border-radius: var(--radius-md, 12px);
  background: var(--wa-chip);
}

/* The same row a second later, with the request already on the wire. Dashed
   means "not settled, your call" everywhere in this file, so it goes solid the
   moment the call stops being the agent's - and it goes neutral, not amber and
   not red: nothing has gone wrong here, the message is simply gone. */
.wai-composer__held--sending {
  border-style: solid;
  border-color: var(--wa-line);
  background: var(--wa-chip);
}

.wai-composer__held-text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.84rem;
  color: var(--text-secondary, #5a5b6a);
}

/* The one control in this UI with a DEADLINE on it: eight seconds, after which
   the message is gone to a customer who cannot be shown an edit or a delete. It
   is therefore the largest and the most solid thing in the row - filled rather
   than outlined, 44px tall, and wide enough to hit without looking. */
.wai-composer__undo {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 44px;
  padding: 4px 16px;
  border: 1px solid var(--wa-green-line);
  border-radius: var(--radius-full, 980px);
  background: var(--wa-green-wash);
  color: var(--wa-green-ink);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s ease;
}

.wai-composer__undo:hover {
  background: var(--wa-green-wash-2);
}

.wai-composer__undo-count {
  font-variant-numeric: tabular-nums;
}

/* Stands in the undo button's slot rather than collapsing it: same font size,
   same padding, a transparent border in place of the button's 1px, and a floor
   on the width so the row keeps its shape. The swap happens mid-countdown,
   under the cursor that was about to press cancel, and a row that changes
   height or jumps sideways at that exact moment reads as a bug. */
.wai-composer__held-state {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  /* The cancel button's own rendered box, so the slot can never come out
     NARROWER or SHORTER than the control it replaced. Both track the undo
     button above - if its padding or type size changes, change these with it. */
  min-width: 5.4rem;
  min-height: 44px;
  padding: 4px 16px;
  border: 1px solid transparent;
  border-radius: var(--radius-full, 980px);
  font-size: 0.8rem;
  color: var(--text-secondary, #5a5b6a);
  white-space: nowrap;
}

/* Sized off the label rather than the icon font, and `line-height: 1` so a
   glyph mid-rotation can never be the thing that sets the row's height. */
.wai-composer__held-spinner {
  flex: 0 0 auto;
  font-size: 0.72rem;
  line-height: 1;
  opacity: 0.75;
}

/* ── The pending quote, above the textarea ─────────────────────────────────

   Appears the moment a bubble's reply button is pressed and stays until the
   message is sent or the quote is dismissed. `.wai-composer` is a flex COLUMN
   with an 8px gap, so this is simply one more row - it needs no positioning of
   its own.

   Modelled on `.wai-composer__held` directly above: same object - a transient
   row over the composer carrying a piece of a message and one dismiss control -
   so it takes the same padding rhythm, the same chip surface and the same
   DASHED border. Dashed is this file's mark for "still your call", solid for a
   decision that has left the agent's hands (see `--held--sending`). A quote that
   has not been sent yet is entirely the agent's call, so it never goes solid.

   Height, measured in a browser rather than estimated: 54px when the excerpt
   fits one line (the 44px cancel is then the tallest child - 44 + 8 padding +
   2 border), 66px when it runs to two (the text column takes over at 18 + 1 +
   38). Add the composer's own 8px gap and the bar costs 62-74px while it is up.
   `.wai-thread` is a grid whose list row is `minmax(0, 1fr)` and whose composer
   row is `auto`, so that comes out of the TRANSCRIPT and never out of the
   viewport - which is the whole reason the height chain was built that way. The
   767px block trims it further, where it matters. */
.wai-composer__reply {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px;
  border: 1px dashed var(--wa-green-line);
  border-radius: var(--radius-md, 12px);
  background: var(--wa-chip);
}

/* `min-width: 0` is not defensive here, it is the fix for a measured bug. The
   composer is a grid item whose automatic minimum size is its min-content width,
   and `.wai-composer__held` proved on 2026-07-30 that ONE unshrinkable child in
   one of these rows drags that past the track: the composer measured 641px
   inside a 390px pane, and because the pane is RTL what was pushed off screen
   was the control at the inline start - the cancel button, at left -222px. This
   column carries a body that can be 300 characters long, so it is the same
   shape of hazard. `min-width: 0` lets the track win and the clamp below do its
   job. */
.wai-composer__reply-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

/* Same rule as the quote inside the bubble: `--wa-green-ink`, never the bright
   `--wa-green`, which is 1.9:1 on this white chip. */
.wai-composer__reply-author {
  font-size: 0.72rem;
  font-weight: 500;
  line-height: 1.5;
  color: var(--wa-green-ink);
}

/* Two lines then ellipsis, the same clamp `.wai-composer__unknown-text` uses and
   for the same reason: a 4096-character body pushing the composer up the screen
   is a worse failure than a truncated preview. */
.wai-composer__reply-body {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--text-secondary, #5a5b6a);
  overflow-wrap: anywhere;
}

/* A full 44px, like every other control on this bar. It is the only way out of
   a quoted reply, and on a phone it sits directly above the on-screen keyboard
   where the thumb is least accurate. Round, transparent, hover fills the disc -
   the icon-button grammar the viewer bar and the strip already use. */
.wai-composer__reply-cancel {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full, 980px);
  background: transparent;
  color: var(--wa-ink-2);
  font: inherit;
  font-size: 0.9rem;
  cursor: pointer;
  transition: background-color 0.16s ease, color 0.16s ease;
}

.wai-composer__reply-cancel:hover {
  background: rgba(11, 20, 26, 0.06);
  color: var(--wa-ink);
}

.wai-composer__reply-cancel:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

/* A send this browser never got an answer for. Amber and dashed - dashed is
   this file's mark for a question still open to the agent, and the amber is the
   one the undetermined bubble wears: the row states an open question and offers
   the one control that can close it, and it must not read as a failure notice. */
.wai-composer__unknown {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 12px;
  border: 1px dashed var(--wa-warn-line);
  border-radius: var(--radius-md, 12px);
  background: var(--wa-chip);
}

.wai-composer__unknown-lead {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin: 0;
  font-size: 0.78rem;
  line-height: 1.55;
  color: var(--wa-warn);
}

.wai-composer__unknown-icon {
  flex: 0 0 auto;
}

.wai-composer__unknown-time {
  flex: 0 0 auto;
  margin-inline-start: auto;
  font-family: var(--font-mono, monospace);
  font-size: 0.7rem;
}

/* The text is repeated here so the agent can identify the message without
   hunting for it - clamped, because a 4096-character body must not push the
   composer off the screen. */
.wai-composer__unknown-text {
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--text-secondary, #5a5b6a);
  overflow-wrap: anywhere;
}

/* The two controls on this row decide the fate of a message nobody is sure was
   delivered: one replays it, the other retires the question for good. They were
   the only sub-44px targets left in the composer - roughly 30px tall and 8px
   apart - which put a duplicate-risk resend one fat-fingered miss away from a
   dismissal that cannot be undone. Both now carry the same 44px the undo and
   send controls do, with a wider gap between them. */
.wai-composer__unknown-actions {
  display: flex;
  align-items: center;
  gap: 12px;
}

.wai-composer__unknown-retry {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 44px;
  padding: 4px 12px;
  border: 1px solid var(--wa-warn-line);
  border-radius: var(--radius-full, 980px);
  background: transparent;
  color: var(--wa-warn);
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
  transition: background 0.15s ease;
}

.wai-composer__unknown-retry:hover:not(:disabled) {
  background: var(--wa-warn-wash);
}

/* Replaying is on the wire. Stop looking pressable, not just stop responding. */
.wai-composer__unknown-retry:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.wai-composer__unknown-dismiss {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 4px 8px;
  border: 0;
  background: transparent;
  color: var(--text-secondary, #5a5b6a);
  font: inherit;
  font-size: 0.78rem;
  text-decoration: underline;
  cursor: pointer;
}

.wai-composer__unknown-retry:focus-visible,
.wai-composer__unknown-dismiss:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

.wai-composer__blocked {
  margin: 0;
  font-size: 0.82rem;
  line-height: 1.6;
  color: var(--text-secondary, #5a5b6a);
}

/* The way THROUGH the shut window: templates are the one message kind the
   provider delivers regardless of it, so this is the blocked state's default
   action and the one control here that wears the accent. Filled enough to
   read first, ahead of the muted external hand-off beside it. */
.wai-tpl-blocked-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  margin-block-start: 6px;
  margin-inline-end: 8px;
  padding-inline: 14px;
  border: 1px solid var(--wa-green-line);
  border-radius: var(--radius-full, 980px);
  background: var(--wa-green-wash);
  color: var(--wa-green-ink);
  font: inherit;
  font-size: 0.8rem;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s ease;
}

.wai-tpl-blocked-btn:hover {
  background: var(--wa-green-wash-2);
}

.wai-tpl-blocked-btn:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

/* The payment-link offer sits beside the generic template button and must not
   compete with it: same shape, outlined instead of filled, so the pair reads as
   one default action plus one specific errand rather than as two equal
   choices. */
.wai-tpl-blocked-btn--pay {
  background: transparent;
  color: var(--wa-ink);
  border-color: var(--wa-line-strong);
}

.wai-tpl-blocked-btn--pay:hover:not(:disabled) {
  background: var(--wa-hover);
}

.wai-tpl-blocked-btn--pay:focus-visible {
  outline-color: var(--wa-ink);
}

/* Dark because Meta has not approved the template yet, with the reason stated
   in the note below it. `cursor: default` and not `not-allowed`: the control is
   waiting on somebody else, not refusing the agent. */
.wai-tpl-blocked-btn--pay:disabled {
  opacity: 0.5;
  cursor: default;
}

.wai-composer__blocked-note {
  display: block;
  margin-block-start: 2px;
  font-size: 0.76rem;
}

/* The ways out of the shut window, next to the explanation of it. Muted, like
   every other route to external WhatsApp in this panel: they record nothing, so
   they must never look like the default. The destinations themselves come from
   the shared `WaActionGroup`; this places the pair and the line that introduces
   them, on their own row under the template button. */
.wai-composer__blocked-out {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin-block-start: 6px;
}

.wai-composer__blocked-lead {
  font-size: 0.78rem;
  color: var(--wa-ink-3);
}

/* A canned-reply placeholder the composer could not fill. Amber, and stated
   under the box it belongs to rather than over it: the send button is already
   disabled, and this says why. */
.wai-composer__vars {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin: 0;
  padding: 4px 2px 0;
  font-size: 0.76rem;
  line-height: 1.5;
  color: var(--wa-warn);
}

.wai-composer__error {
  margin: 0;
  padding: 6px 10px;
  border-radius: var(--radius-sm, 8px);
  background: var(--wa-danger-wash);
  color: var(--wa-danger-ink);
  font-size: 0.8rem;
  line-height: 1.5;
}

/* An internal note that would not save. Same shape and typography as the
   customer-send error above, deliberately NOT its red: nothing was sent to
   anybody and no customer is affected. The gold is the one the note field and
   its save button already wear, so the line reads as belonging to the note. */
.wai-composer__note-error {
  margin: 0;
  padding: 6px 10px;
  border-radius: var(--radius-sm, 8px);
  background: var(--wa-warn-wash);
  color: var(--wa-warn);
  font-size: 0.8rem;
  line-height: 1.5;
}

/* The twin of `__error` for a send the provider ACCEPTED without confirming.
   Same shape so it reads as the same class of information, amber so it never
   reads as the same verdict. */
.wai-composer__notice {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin: 0;
  padding: 6px 10px;
  border-radius: var(--radius-sm, 8px);
  background: var(--wa-chip);
  color: var(--wa-warn);
  font-size: 0.8rem;
  line-height: 1.5;
}

.wai-composer__notice-icon {
  flex: 0 0 auto;
}

/* ── "This person asked us to stop", over the box the reply is typed in ────

   The same object as `.wai-banner` in the lead panel, in the same amber, on
   purpose: one flag showing in two places has to look like one flag. It takes
   the composer's own strip rhythm rather than the panel's - 6px/10px and the
   small radius, like `__error`, `__note-error` and `__notice` directly above -
   because it stacks with those in a flex COLUMN with an 8px gap, and a taller
   box here comes out of the transcript.

   It is deliberately not louder than they are. The flag is a classifier's
   reading of a sentence and it is wrong often enough that the copy says so, and
   it gates NOTHING: the send button beside it stays live. Red, or a dialog,
   would be a product promise the code does not keep. */
.wai-composer__optout {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm, 8px);
  /* Same measured hazard as `__reply` and `__held`: the composer is a grid item
     whose automatic minimum size is its min-content width, so one unshrinkable
     child drags the whole bar past the track and pushes the control at the
     inline start off a 390px screen. The column below carries a customer's
     verbatim sentence, so it is the same shape of risk. */
  min-width: 0;
  padding: 6px 10px;
  border: 1px solid var(--wa-warn-line);
  border-radius: var(--radius-sm, 8px);
  background: var(--wa-warn-wash);
  color: var(--wa-warn);
}

.wai-composer__optout-icon {
  flex: 0 0 auto;
  margin-block-start: 3px;
  font-size: 0.78rem;
}

.wai-composer__optout-lines {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.wai-composer__optout-title {
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1.45;
}

/* The line that says the flag is a machine's reading and not a verdict. Quieter
   than the title and than the quoted evidence, because it is an instruction to
   the reader rather than a fact about the customer. */
.wai-composer__optout-hint {
  font-size: 0.72rem;
  line-height: 1.5;
}

/* The quoted message drops the amber and returns to ordinary ink - these are
   the customer's words, not our warning. Two lines then ellipsis, the clamp
   `.wai-composer__reply-body` and `.wai-composer__unknown-text` already use: a
   long refusal pushing the composer up the screen is a worse failure than a
   truncated one, and the lead panel shows it whole. */
.wai-composer__optout-quote {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 0.76rem;
  line-height: 1.45;
  color: var(--wa-ink);
  overflow-wrap: anywhere;
}
/* ══════════════════════════════════════════════════════════════════════════
   Canned replies - WaCannedSheet.vue, inside an AdminSheet

   The sheet chrome (`.adm-sheet*`) belongs to admin.css; almost everything here
   is the body it renders, bar the short chrome override at the end. Rows are
   >=46px because the whole point of the sheet is that it is reachable
   one-handed while the other hand holds nothing.
   ══════════════════════════════════════════════════════════════════════════ */

.wai-canned {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm, 8px);
}

.wai-canned__tools {
  display: flex;
  align-items: center;
  gap: var(--space-sm, 8px);
}

.wai-canned__field {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
}

.wai-canned__search {
  width: 100%;
  min-height: 44px;
  padding: 9px 34px 9px 12px;
  font-family: var(--font-body);
  font-size: 0.88rem;
  color: var(--wa-ink);
  /* Bar grey on the white sheet, like the list pane's search - an input only
     sits white when the ground under it is grey. */
  background: var(--wa-header);
  border: 1px solid transparent;
  border-radius: var(--radius-md, 12px);
  outline: none;
}

.wai-canned__search:focus {
  border-color: var(--wa-green-ink);
  box-shadow: 0 0 0 3px var(--wa-green-wash);
}

.wai-canned__search::-webkit-search-cancel-button {
  display: none;
}

.wai-canned__search-icon {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 12px;
  transform: translateY(-50%);
  font-size: 0.78rem;
  color: var(--text-secondary, #5a5b6a);
  pointer-events: none;
}

.wai-canned__manage {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding-inline: 14px;
  /* Strong hairline for the same reason as `.wai-page .btn`: white pill,
     white sheet, the edge is all it has. */
  border: 1px solid var(--wa-line-strong);
  border-radius: var(--radius-full, 980px);
  background: var(--wa-raised);
  font-family: var(--font-body);
  font-size: 0.8rem;
  color: var(--text-secondary, #5a5b6a);
  cursor: pointer;
}

.wai-canned__manage--on {
  border-color: var(--wa-green-line);
  background: var(--wa-green-wash);
  color: var(--wa-green-ink);
  font-weight: 500;
}

.wai-canned__new {
  display: flex;
}

.wai-canned__new-btn {
  min-height: 44px;
}

.wai-canned__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm, 8px);
  padding: var(--space-md, 16px);
  border: 1px solid var(--wa-line);
  border-radius: var(--radius-md, 12px);
  background: var(--wa-header);
}

.wai-canned__label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 0.76rem;
  color: var(--text-secondary, #5a5b6a);
}

/* Grey field on the white sheet, like the search above it - a white field with
   a transparent border would have no edge at all until focus. */
.wai-canned__input {
  min-height: 44px;
  padding: 9px 12px;
  border: 1px solid transparent;
  border-radius: var(--radius-md, 12px);
  background: var(--wa-header);
  font-family: var(--font-body);
  font-size: 0.88rem;
  color: var(--wa-ink);
  outline: none;
}

.wai-canned__input:focus {
  border-color: var(--wa-green-ink);
  box-shadow: 0 0 0 3px var(--wa-green-wash);
}

/* A shortcode is latin-only by definition, so it is typed and read LTR even
   though the label beside it is Hebrew. */
.wai-canned__input--code {
  font-family: var(--font-mono, monospace);
  font-size: 0.82rem;
}

.wai-canned__textarea {
  min-height: 96px;
  line-height: 1.55;
  resize: vertical;
}

.wai-canned__check {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  font-size: 0.82rem;
  color: var(--text-color, #191a23);
  cursor: pointer;
}

.wai-canned__check input {
  width: 18px;
  height: 18px;
  accent-color: var(--adm-accent);
}

.wai-canned__form-actions {
  display: flex;
  gap: var(--space-sm, 8px);
}

.wai-canned__form-actions .btn {
  min-height: 44px;
  flex: 1 1 auto;
  justify-content: center;
}

.wai-canned__state {
  margin: 0;
  padding: var(--space-lg, 24px) var(--space-sm, 8px);
  text-align: center;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--text-secondary, #5a5b6a);
}

.wai-canned__error {
  margin: 0;
  padding: 8px 10px;
  border-radius: var(--radius-sm, 8px);
  background: var(--wa-danger-wash);
  color: var(--wa-danger-ink);
  font-size: 0.8rem;
  line-height: 1.5;
}

.wai-canned__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.wai-canned__item {
  display: flex;
  align-items: stretch;
  gap: 4px;
  border-block-end: 1px solid var(--wa-line);
}

.wai-canned__pick {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-height: 46px;
  padding: 10px 4px;
  border: 0;
  background: none;
  font-family: var(--font-body);
  text-align: start;
  cursor: pointer;
  border-radius: var(--radius-sm, 8px);
}

.wai-canned__pick:hover {
  background: var(--wa-hover);
}

.wai-canned__pick:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: -2px;
}

.wai-canned__head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  min-width: 0;
}

.wai-canned__title {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text-color, #191a23);
}

/* Bar grey rather than the white chip: these little badges sit directly on
   the white sheet, where a white pill would dissolve. */
.wai-canned__code {
  font-family: var(--font-mono, monospace);
  font-size: 0.72rem;
  padding: 1px 8px;
  border-radius: var(--radius-full, 980px);
  background: var(--wa-header);
  color: var(--wa-ink-2);
}

.wai-canned__off {
  font-size: 0.68rem;
  padding: 1px 8px;
  border-radius: var(--radius-full, 980px);
  background: var(--wa-header);
  color: var(--text-secondary, #5a5b6a);
}

.wai-canned__body {
  font-size: 0.78rem;
  line-height: 1.5;
  color: var(--text-secondary, #5a5b6a);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wai-canned__actions {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.wai-canned__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: var(--radius-full, 980px);
  background: none;
  color: var(--text-secondary, #5a5b6a);
  font-size: 0.85rem;
  cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}

.wai-canned__icon:hover {
  background: rgba(11, 20, 26, 0.06);
  color: var(--text-color, #191a23);
}

.wai-canned__icon--danger:hover {
  background: var(--wa-danger-wash);
  color: var(--wa-danger);
}

.wai-canned__icon:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

/* The sheet chrome itself comes from admin.css, and AdminSheet renders in place
   inside `.wai-page` instead of teleporting out of it. Re-pointing the shared
   tokens is not enough, because `.adm-sheet` paints its own surface, so the
   chrome is restated here as one more of the inbox's flat panels, under a
   scrim eased for the light UI beneath it.

   `.per-wa` is on every one of these lists because "renders in place" is what
   makes the skin travel. `WaTemplateSheet` is a SIBLING of the composer inside
   `WaThread`, and `WaCannedSheet` is a child of the composer itself - so when
   the Person page's docked panel embeds a live thread, both sheets are mounted
   inside `.per-wa` with no Teleport anywhere. They inherit the `--wa-*` tokens
   from the palette blocks at the top of this file, but `.adm-sheet` paints its
   own light shell chrome on top of them, so without these lines the
   approved-template picker and the canned-reply list open in the admin shell's
   frosted white over a WhatsApp panel.

   Only the geometry escapes the panel, and that is deliberate: `.adm-sheet-layer`
   is `position: fixed; inset: 0` in admin.css, so the sheet covers the VIEWPORT
   rather than the 380px dock it was opened from. That is precisely why nothing
   may put `container-type`, `transform`, `filter` or `contain` on `.per-wa` or on
   any ancestor of it - see contract 5 in admin-person.css. */

.wai-page .adm-sheet,
.per-wa .adm-sheet {
  background: var(--wa-panel);
  border-color: var(--wa-line);
  color: var(--wa-ink);
}

.wai-page .adm-sheet__head,
.wai-page .adm-sheet__footer,
.per-wa .adm-sheet__head,
.per-wa .adm-sheet__footer { border-color: var(--wa-line); }

.wai-page .adm-sheet__title,
.per-wa .adm-sheet__title { color: var(--wa-ink); }

.wai-page .adm-sheet__grab,
.per-wa .adm-sheet__grab { background: var(--wa-line-strong); }

.wai-page .adm-sheet-scrim,
.per-wa .adm-sheet-scrim { background: rgba(11, 20, 26, 0.35); }

/* ══════════════════════════════════════════════════════════════════════════
   Approved templates - WaTemplateSheet.vue, inside an AdminSheet

   Same chrome and same mount constraints as the canned sheet above. The
   centrepiece is the preview card, painted as the OUTBOUND bubble it will
   become - green ground, baked-in buttons as inert rows - so the agent
   confirms the exact thing the customer receives.
   ══════════════════════════════════════════════════════════════════════════ */

.wai-tpl {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm, 8px);
}

.wai-tpl__state {
  margin: 0;
  padding: var(--space-lg, 24px) var(--space-sm, 8px);
  text-align: center;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--text-secondary, #5a5b6a);
}

.wai-tpl__error {
  margin: 0;
  padding: 8px 10px;
  border-radius: var(--radius-sm, 8px);
  background: var(--wa-danger-wash);
  color: var(--wa-danger-ink);
  font-size: 0.8rem;
  line-height: 1.5;
}

.wai-tpl__retry {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm, 8px);
  padding-block: var(--space-md, 16px);
}

.wai-tpl__retry .btn {
  min-height: 44px;
}

/* ── Picker (only when more than one template exists) ── */

.wai-tpl__list {
  margin: 0;
  padding: 0;
  border: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.wai-tpl__legend {
  padding: 0;
  margin-block-end: 2px;
  font-size: 0.76rem;
  color: var(--wa-ink-2);
}

.wai-tpl__option {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 46px;
  padding: 6px 10px;
  border: 1px solid var(--wa-line);
  border-radius: var(--radius-md, 12px);
  background: var(--wa-header);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}

.wai-tpl__option--active {
  border-color: var(--wa-green-line);
  background: var(--wa-green-wash);
}

/* Unsendable rows stay selectable - the preview explains WHY they are off -
   but read as switched off in the list too. */
.wai-tpl__option--off {
  opacity: 0.6;
}

.wai-tpl__radio {
  flex: none;
  width: 16px;
  height: 16px;
  accent-color: var(--wa-green);
}

.wai-tpl__radio:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

.wai-tpl__option-name {
  min-width: 0;
  overflow-wrap: anywhere;
  font-family: var(--font-mono, monospace);
  font-size: 0.8rem;
  color: var(--wa-ink);
}

/* Category and language, e.g. "MARKETING · he". Also the card header's chip. */
.wai-tpl__meta {
  flex: none;
  margin-inline-start: auto;
  padding: 1px 8px;
  border-radius: var(--radius-full, 980px);
  background: var(--wa-chip);
  font-family: var(--font-mono, monospace);
  font-size: 0.66rem;
  color: var(--wa-ink-2);
}

/* ── Preview card ── */

.wai-tpl__card {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm, 8px);
}

.wai-tpl__cardhead {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.wai-tpl__name {
  min-width: 0;
  overflow-wrap: anywhere;
  font-family: var(--font-mono, monospace);
  font-size: 0.82rem;
  color: var(--wa-ink);
}

.wai-tpl__preview {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px;
  border-radius: var(--radius-md, 12px);
  background: var(--wa-out);
  box-shadow: var(--wa-shadow-sm);
}

.wai-tpl__hero {
  display: block;
  width: 100%;
  max-width: 100%;
  border-radius: var(--radius-sm, 8px);
}

.wai-tpl__headertext {
  margin: 0;
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1.45;
  color: var(--wa-ink);
}

.wai-tpl__body {
  margin: 0;
  font-size: 0.88rem;
  line-height: 1.55;
  color: var(--wa-ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* WhatsApp's own muted footer line: the secondary ink, which reads quietly on
   the pale green exactly as it does under the app's own outgoing bubbles. */
.wai-tpl__footertext {
  margin: 0;
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--wa-ink-2);
}

/* Baked-in buttons, drawn as WhatsApp draws them - hairline-separated rows in
   the link blue - and inert: they are part of the template, not of this UI. */
.wai-tpl__buttons {
  display: flex;
  flex-direction: column;
  margin-block-start: 2px;
  border-block-start: 1px solid var(--wa-line);
}

/* One row per button. `justify-content: center` rather than any inline-start /
   -end rule, because the label is centred in the app and centring is the one
   alignment that does not have to know which way the island reads. The row has
   no fill of its own, so it sits on whatever the bubble resolved to - the pale
   outbound green here, the sheet's preview green there. */
.wai-tpl__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 40px;
  font-size: 0.82rem;
  color: var(--wa-link);
}

.wai-tpl__btn + .wai-tpl__btn {
  border-block-start: 1px solid var(--wa-line);
}

.wai-tpl__btn i {
  font-size: 0.7rem;
}

/* ── Variables, notes, send ── */

.wai-tpl__params {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm, 8px);
}

.wai-tpl__param {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 0.76rem;
  color: var(--wa-ink-2);
}

/* Grey on the white sheet, same reasoning as `.wai-canned__input`. */
.wai-tpl__input {
  min-height: 44px;
  padding: 9px 12px;
  border: 1px solid transparent;
  border-radius: var(--radius-md, 12px);
  background: var(--wa-header);
  font-family: var(--font-body);
  font-size: 0.88rem;
  color: var(--wa-ink);
  outline: none;
}

.wai-tpl__input:focus {
  border-color: var(--wa-green-ink);
  box-shadow: 0 0 0 3px var(--wa-green-wash);
}

.wai-tpl__hint {
  margin: 0;
  font-size: 0.74rem;
  line-height: 1.5;
  color: var(--wa-warn);
}

/* Why THIS template cannot be sent from here (named parameters, video header,
   unresolvable image...). Amber: the template is fine at Meta, the panel is
   what cannot build the send. */
.wai-tpl__blocked {
  margin: 0;
  padding: 8px 10px;
  border-radius: var(--radius-sm, 8px);
  background: var(--wa-warn-wash);
  color: var(--wa-warn);
  font-size: 0.8rem;
  line-height: 1.5;
}

/* The standing statement of what a template send IS - paid, window-ignoring.
   Quiet: the confirm dialog restates it at decision time. */
.wai-tpl__note {
  margin: 0;
  font-size: 0.74rem;
  line-height: 1.55;
  color: var(--wa-ink-2);
}

.wai-tpl__send {
  min-height: 44px;
  justify-content: center;
}

/* ══════════════════════════════════════════════════════════════════════════
   New chat - the phone-number dialog AdminWhatsApp.vue hosts for the list's
   "+", inside the same AdminSheet chrome.
   ══════════════════════════════════════════════════════════════════════════ */

.wai-newchat {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm, 8px);
}

.wai-newchat__label {
  font-size: 0.76rem;
  color: var(--wa-ink-2);
}

/* A phone number is LTR content whatever the island's direction; mono so the
   agent can proof-read digits. */
/* Grey on the white sheet, same reasoning as `.wai-canned__input`. */
.wai-newchat__input {
  min-height: 44px;
  padding: 9px 12px;
  border: 1px solid transparent;
  border-radius: var(--radius-md, 12px);
  background: var(--wa-header);
  font-family: var(--font-mono, monospace);
  font-size: 0.95rem;
  letter-spacing: 0.02em;
  color: var(--wa-ink);
  outline: none;
}

.wai-newchat__input:focus {
  border-color: var(--wa-green-ink);
  box-shadow: 0 0 0 3px var(--wa-green-wash);
}

.wai-newchat__input::placeholder {
  color: var(--wa-ink-3);
}

.wai-newchat__note {
  margin: 0;
  font-size: 0.74rem;
  line-height: 1.55;
  color: var(--wa-ink-2);
}

.wai-newchat__error {
  margin: 0;
  padding: 8px 10px;
  border-radius: var(--radius-sm, 8px);
  background: var(--wa-danger-wash);
  color: var(--wa-danger);
  font-size: 0.8rem;
  line-height: 1.5;
}

.wai-newchat__submit {
  min-height: 44px;
  justify-content: center;
}

/* ══════════════════════════════════════════════════════════════════════════
   Tablet - 1024px and below.
   The lead panel leaves the grid and becomes a slide-over anchored to the
   inline start. `.wai-grid` is `position: relative`, so the panel is taken out
   of grid flow entirely and cannot reintroduce a third column.
   ══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 1024px) {
  /* ── The shell's own top bar ─────────────────────────────────────────────

     From 1024px down the sidebar collapses into a drawer and the panel grows a
     sticky bar carrying the hamburger and the brand. It is admin chrome, not
     part of the inbox, and everywhere else in the panel it is correct as it is:
     frosted white over the ambient canvas.

     Above the inbox's flat WhatsApp chrome it is the one frosted, blue-tinged
     element in view, and on a phone that bar is the first thing in view. So it
     alone takes the inbox's flat bar grey, on this route only, via the same
     `:has()` escape the padding reset at the top of this file uses. Everything
     else about the shell - the sidebar, the drawer, every other page - keeps
     its own look: the raw palette reaches this bar, but the `--adm-*` re-point
     that would recolour the rest deliberately does not (see the two palette
     blocks above).

     The blur goes with it. WhatsApp has no frosted chrome, and a translucent
     bar over a scrolling pane picks up whatever slides beneath it. */
  .admin-layout:has(.wai-page) .admin-mobile-bar {
    background: var(--wa-header);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    border-bottom-color: var(--wa-line);
  }

  .admin-layout:has(.wai-page) .admin-mobile-bar .brand-link {
    color: var(--wa-ink);
  }

  .admin-layout:has(.wai-page) .admin-mobile-bar .brand-label {
    color: var(--wa-ink-2);
  }

  .admin-layout:has(.wai-page) .admin-mobile-bar .admin-hamburger {
    color: var(--wa-ink-2);
  }

  .admin-layout:has(.wai-page) .admin-mobile-bar .admin-hamburger:hover {
    color: var(--wa-ink);
    background: rgba(11, 20, 26, 0.06);
  }

  /* ── The on-screen keyboard ──────────────────────────────────────────────

     The admin shell is `height: 100dvh` (admin.css), and `dvh` shrinks for
     browser chrome but NOT for the virtual keyboard - on iOS the keyboard is
     painted OVER the layout viewport. In a viewport-locked layout whose last
     row is a text box, that means the keyboard covers the very control being
     typed into.

     `--app-vvh` is the visible height, published by `useVisualViewport()` from
     AdminWhatsApp.vue. Consuming it here makes the whole chain - shell, page,
     grid, thread, composer - shrink above the keyboard instead of sliding
     under it. `:has(.wai-page)` keeps it to this route without editing
     admin.css, the same escape the padding/overflow reset at the top of this
     file already uses. The `100dvh` fallback is what a browser with no
     visualViewport, and the tick before the first measurement, both get. */
  .admin-layout:has(.wai-page) {
    height: var(--app-vvh, 100dvh);
  }

  .wai-grid {
    grid-template-columns: 300px minmax(0, 1fr);
  }

  /* Six chips, two rows, at a 300px pane - and it comes down to single pixels.
     Measured at 1024 with the real font and the real icon: the second row runs
     59 + 87 + 118 plus two 8px gaps = 280 against 268 of usable width, so the
     call-request chip wrapped onto a THIRD row and the strip grew from 92px to
     138px. The height comment above `.wai-chip` explains why that is not
     affordable: this strip sits on top of the conversation list, and a third
     row costs a whole conversation on the shortest screens.

     Five pixels a side brings the row to 250 and leaves 18px spare, which is
     what a two- or three-digit queue count needs - measured, not guessed: at
     10px the row came to 262 and a count of "10" would have wrapped it again.
     Scoped to this breakpoint rather than solved with a shorter label or a
     dropped icon, because both of those cost the chip the thing that makes it
     findable. The 8px gap is deliberately untouched: it is sized against the
     chips' 3px tap overhang, not against their text. */
  .wai-chip {
    padding-inline: 8px;
  }

  /* The full lead panel is a drawer from here down, so the two facts worth
     knowing before typing move onto a line of their own. */
  .wai-thread__lead-strip {
    display: flex;
  }

  .wai-lead {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    z-index: 30;
    width: min(360px, 90vw);
    /* No logical equivalent exists for a transform, so the sign has to be
       reasoned about explicitly. This island is permanently RTL, which means
       `inset-inline-start: 0` above pins the drawer to the container's RIGHT
       edge (inline-start runs from the right in RTL - it computes to
       `right: 0`). Hiding it therefore means pushing it further RIGHT, out of
       the container, which is a POSITIVE translateX. A negative one moves it
       inward and parks the closed drawer on top of the thread. */
    transform: translateX(100%);
    transition: transform 0.18s ease;
    background: var(--wa-panel);
    box-shadow: var(--wa-shadow-lg);
    border-inline-start: none;
    border-inline-end: 1px solid var(--wa-line);
  }

  .wai-page--lead-open .wai-lead {
    transform: translateX(0);
  }

  .wai-lead__close {
    display: inline-flex;
  }

  .wai-thread__lead-toggle {
    display: inline-flex;
  }

  .wai-scrim {
    display: block;
    position: absolute;
    inset: 0;
    z-index: 20;
    /* Still a dark scrim - a drawer needs the page behind it to recede - but
       eased to 35%: over a light UI the dark skin's 60% read as a blackout. */
    background: rgba(11, 20, 26, 0.35);
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   Mobile - 767px and below. One pane at a time, chosen by whether a
   conversation is selected.
   ══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 767px) {
  .wai-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .wai-list,
  .wai-thread {
    display: none;
  }

  .wai-page--list .wai-list {
    display: grid;
  }

  .wai-page--thread .wai-thread {
    display: grid;
  }

  .wai-list {
    border-inline-end: none;
  }

  .wai-thread__back {
    display: inline-flex;
  }

  /* Row one is back + disc + name + the lead toggle. At 360px the disc has to
     give up some width for the name it introduces. */
  .wai-thread__avatar {
    width: 34px;
    height: 34px;
    font-size: 0.85rem;
  }

  .wai-thread__lead-toggle-label {
    display: none;
  }

  /* ── Two header rows ─────────────────────────────────────────────────────

     Six controls do not fit on one line at 360px. Row one is navigation -
     back, who this is, and the way into the lead. Row two is the channel: the
     window state, who owns the conversation, and the external hand-off. The
     `order` values are what keep the toggle up on row one even though the
     meta group comes before it in the DOM (where it belongs for a screen
     reader, next to the name it describes). */
  .wai-thread__header {
    flex-wrap: wrap;
    row-gap: 6px;
    padding-block: 8px;
  }

  .wai-thread__meta {
    display: flex;
    order: 2;
    width: 100%;
    min-width: 0;
    align-items: center;
    gap: 6px;
  }

  .wai-thread__lead-toggle {
    order: 1;
  }

  /* The one flexible item on the second row: the chip and the icon are fixed,
     so the select absorbs whatever is left and ellipsises a long username. */
  .wai-thread__assign {
    flex: 1 1 auto;
  }

  .wai-thread__assign-select {
    max-width: none;
    width: 100%;
  }

  /* "נותרו כ-13 שעות" beside a chip on a 360px row is what pushes the header
     into a third line. The chip's tooltip still carries it. */
  .wai-thread__window-left {
    display: none;
  }

  .wai-lead {
    width: min(340px, 92vw);
  }

  .wai-bubble {
    max-width: 88%;
  }

  /* 44px is the touch minimum, and these rows sit stacked with a hairline
     between them - the one place in the thread where a mis-tap lands on a
     neighbour rather than on empty bubble. */
  .wai-tpl__btn {
    min-height: 44px;
  }

  /* The quote preview costs the transcript 62-74px while it is up, and it is up
     at exactly the moment the keyboard is also open and the visible height is
     ~400px rather than 760px. Dropping the block padding, together with the
     one-line clamp below, brings the bar to a measured 46px at 390px wide -
     every target on it keeps its full size, because the cancel button's own 44px
     is what sets the row once the text column is one line shorter. The inline
     padding stays: it is what keeps the author line off the dashed border. */
  .wai-composer__reply {
    padding-block: 0;
  }

  /* A 300-character excerpt over two lines at 0.82rem is ~90 characters on a
     390px screen; one line is enough to identify a message you chose to reply to
     seconds ago, and the second line is worth more as transcript. The bubble's
     own quote keeps both lines - it is read cold, weeks later, by someone who
     did not choose it. */
  .wai-composer__reply-body {
    -webkit-line-clamp: 1;
  }

  /* Same trade as the quote preview above, and for the same reason: with the
     keyboard open the visible thread is ~400px, and the strip is up for the
     whole conversation rather than for one message. One line still shows what
     the customer said; the lead panel is where the full sentence is read. */
  .wai-composer__optout-quote {
    -webkit-line-clamp: 1;
  }

  /* ── One-line status strip ───────────────────────────────────────────────

     The chip set is reference material - quality rating, send capability,
     pairing, echo age - that an agent reads once a week, and it was costing
     two wrapped rows of a ~660px viewport on every single visit. Collapsed to
     the level icon and the sentence that names the state, which is the part
     that is ever actually news. The chevron brings the rest back, alerts
     included. */
  .wai-strip {
    padding-block: 0;
  }

  .wai-strip__row {
    font-size: 0.74rem;
    min-height: 40px;
    gap: 8px;
  }

  /* `flex-grow` on the state pushes the two round controls to the far edge, so
     neither needs an auto margin - which matters because the chip block below
     them is a 100%-wide wrap and an auto margin on a wrapped line behaves
     nothing like it does on a full one. `order` keeps the controls on the FIRST
     line when the chips are expanded: without it the 100%-wide chip block
     would wrap between them and strand the collapse control on a third row. */
  .wai-strip__state {
    flex: 1 1 auto;
    min-width: 0;
    order: 0;
  }

  .wai-strip__refresh {
    order: 1;
    margin-inline-start: 0;
  }

  .wai-strip__toggle {
    order: 2;
  }

  .wai-strip__chips {
    order: 3;
  }

  .wai-strip__chips,
  .wai-strip__refresh,
  .wai-strip__alerts {
    display: none;
  }

  .wai-strip__toggle {
    display: inline-flex;
  }

  .wai-strip--expanded .wai-strip__chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding-block-end: 8px;
  }

  .wai-strip--expanded .wai-strip__refresh {
    display: inline-flex;
  }

  .wai-strip--expanded .wai-strip__alerts {
    display: flex;
    padding-block-end: 8px;
  }

  .wai-strip__checked {
    display: none;
  }

  /* The bar has four controls and a phone is 360px wide. The filename is the
     one thing that can be dropped: it is repeated on the download card and in
     the bubble underneath. */
  .wai-viewer__name {
    display: none;
  }

  /* The shorthand would drop the safe-area inset the base rule adds, and a
     phone is the only place that inset is ever non-zero - so it is restated. */
  .wai-viewer__stage {
    padding: var(--space-sm, 8px);
    padding-block-end: calc(var(--space-sm, 8px) + env(safe-area-inset-bottom));
  }
}

@media (prefers-reduced-motion: reduce) {
  .wai-lead,
  .wai-media-thumb,
  .wai-media-file,
  .wai-viewer__btn,
  .wai-viewer__doc,
  .wai-strip__refresh,
  .wai-strip__toggle,
  .wai-thread__jump,
  .wai-composer__tab,
  .wai-composer__canned,
  .wai-composer__send,
  .wai-composer__undo,
  .wai-composer__unknown-retry,
  .wai-composer__reply-cancel,
  .wai-canned__icon,
  .wai-bubble__resend,
  .wai-bubble__reply,
  .wai-quote--clickable,
  .wai-list__new,
  .wai-tpl-blocked-btn,
  .wai-tpl__option {
    transition: none;
  }

  /* The jump flash is the one ANIMATION in this file, so it cannot just be
     enumerated above - and `animation: none` is the wrong answer for it. It is
     not decoration: it is the only thing that says WHICH of the bubbles now on
     screen the quote sent you to, and switching it off leaves the jump silent
     for exactly the users least able to track a scroll that moved on its own.

     So the wash still appears and still clears itself; what goes is the fade.
     `steps(1, end)` holds each keyframe until its interval ends, which turns the
     four stops into two discrete states - on, then off - with no continuous
     change to follow. That also keeps the self-clearing behaviour that
     `forwards` was chosen for: `animation: none` would have stranded the class's
     computed opacity at 1 and left a permanent green wash on any bubble the
     store forgot to un-flag. */
  .wai-bubble--flash::after {
    animation-timing-function: steps(1, end);
  }
}

/* ── Return-context chip ─────────────────────────────────────────────────────
   Rendered by AdminWhatsApp.vue between the status strip and the grid, only
   when the inbox was reached from a lead surface (?from=lead:<id>). One slim
   full-width bar; the arrow points inline-start-ward (back whence you came).
   Switching conversations drops the query and the chip with it. */

.wai-backlead {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 40px;
  padding: 0.3rem 1rem;
  background: var(--wa-chip);
  border-block-end: 1px solid var(--wa-line);
  color: var(--wa-green-ink);
  font-size: 0.82rem;
  font-weight: 600;
  text-decoration: none;
  flex: none;
}

.wai-backlead:hover {
  background: var(--wa-hover);
}

.wai-backlead i {
  font-size: 0.75rem;
}

/* A `?phone=` deep link from a staff email that could not be opened. Same strip
   geometry as the back-to-lead chip above so the shell's height chain is
   unchanged, warm rather than green: it reports a failure, and it is the only
   thing on screen that can explain an inbox the agent did not ask for. */
.wai-linkerror {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
  min-height: 40px;
  margin: 0;
  padding: 0.3rem 1rem;
  /* The wash is translucent, so it is laid over an opaque bar the same way the
     status strip does it - straight onto the page it would pick up whatever
     pane happens to sit behind. */
  background: linear-gradient(var(--wa-warn-wash), var(--wa-warn-wash)), var(--wa-header);
  border-block-end: 1px solid var(--wa-warn-line);
  color: var(--wa-ink, #111b21);
  font-size: 0.82rem;
}

.wai-linkerror > i {
  flex: none;
  font-size: 0.8rem;
  color: var(--wa-warn, #b26a00);
}

.wai-linkerror__text {
  flex: 1 1 auto;
  min-width: 0;
}

.wai-linkerror__close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full, 980px);
  background: none;
  color: var(--wa-ink-2);
  font-size: 0.85rem;
  cursor: pointer;
}

.wai-linkerror__close:hover {
  background: var(--wa-hover);
  color: var(--wa-ink);
}

.wai-linkerror__close:focus-visible {
  outline: 2px solid var(--wa-green-ink);
  outline-offset: 2px;
}

/* ── Scrollbars ──────────────────────────────────────────────────────────────
   WhatsApp paints a thin, low-contrast thumb and no track. The same grey-blue
   the hairlines are mixed from stays visible on the white panes and on the
   beige field alike, where the UA's stock bar would read as foreign chrome.

   `.per-wa` is on all five. These are the one group in this file that no amount
   of token inheritance can substitute for - a scrollbar is not painted from
   `--adm-*` - and `admin-person.css` declares no scrollbar rules at all, so
   without these the docked transcript scrolls on the operating system's stock
   bar while the transcript three inches away in the inbox scrolls on WhatsApp's.
   The `*` is what makes both lists work: the scroller is a descendant
   (`.wai-thread__list`), never the skinned element itself. */

.wai-page *::-webkit-scrollbar,
.per-wa *::-webkit-scrollbar { width: 6px; height: 6px; }
.wai-page *::-webkit-scrollbar-track,
.per-wa *::-webkit-scrollbar-track { background: transparent; }
.wai-page *::-webkit-scrollbar-thumb,
.per-wa *::-webkit-scrollbar-thumb {
  background: rgba(134, 150, 160, 0.3);
  border-radius: 3px;
}
.wai-page *::-webkit-scrollbar-thumb:hover,
.per-wa *::-webkit-scrollbar-thumb:hover { background: rgba(134, 150, 160, 0.5); }
.wai-page *,
.per-wa * { scrollbar-width: thin; scrollbar-color: rgba(134, 150, 160, 0.3) transparent; }
