/* Lassy Meet — in-meeting screen.

   New Teams puts the call controls in a bar at the *top* of the stage, not
   floating at the bottom, and that is what the reference capture shows:

     ubar-toolbar-wrapper  display:grid
                           grid-template-areas 'indicators horizontalMiddleEnd horizontalEnd'
                           background NeutralBackground1
                           border-bottom .1rem NeutralStroke2
                           padding 0 1.4rem 0 0.8rem
                           z-index 256

   Each control is a 4.8rem-tall column: 20px icon over a 12px label,
   min-width 4.8rem, max-width 7.2rem (`f1msd6ry`/`fj41rt3`/`f1hcleq7`).
   Leave is not a red pill — it is a normal control whose *icon* is tinted
   `--colorStatusDangerForeground1` (`fitc7hk`).
*/

.meeting {
  display: grid;
  /* Controls, timer, stage. Every child is placed EXPLICITLY below, and that
     is not tidiness -- a `display: none` grid item is not placed at all, so
     with the timer hidden the stage auto-flowed into the middle `auto` row and
     the `1fr` row collapsed to zero. The stage then sized to its content,
     whose height comes from `--tile-w`, which layoutGrid() computes from the
     stage: a feedback loop that settles at 40px-wide tiles. */
  grid-template-rows: var(--ubarHeight) auto minmax(0, 1fr);
  height: 100%;
  width: 100%;
  background-color: var(--colorNeutralBackground3);
}

/* --------------------------------------------------------------------- */
/* Control bar                                                            */
/* --------------------------------------------------------------------- */

/* Controls are pushed to the RIGHT, not centred: the shield and call timer
   hold the left edge and everything else groups against the far side. */
.ubar {
  grid-row: 1;
  display: grid;
  grid-template-areas: 'indicators middle';
  grid-template-columns: fit-content(100%) minmax(0, 1fr);
  grid-gap: var(--spacingHorizontalXS);
  align-items: center;
  height: var(--ubarHeight);
  padding: 0 1.4rem 0 0.8rem;
  background-color: var(--colorNeutralBackground1);
  border-bottom: 0.1rem solid var(--colorNeutralStroke2);
  user-select: none;
  z-index: 256;
}

.ubar__indicators { grid-area: indicators; display: flex; align-items: center; gap: var(--spacingHorizontalS); min-width: 0; }
.ubar__middle { grid-area: middle; display: flex; align-items: center; justify-content: flex-end; width: 100%; min-width: 0; }
.ubar__controls { display: flex; align-items: center; }

.ubar__divider {
  width: 1px;
  height: 2.4rem;
  flex-shrink: 0;
  background-color: var(--colorNeutralStroke2);
  margin: 0 var(--spacingHorizontalS);
}

.ubar__duration {
  display: inline-flex;
  align-items: center;
  gap: var(--spacingHorizontalXS);
  font-size: var(--fontSizeBase200);
  line-height: var(--lineHeightBase200);
  color: var(--colorNeutralForeground2);
  font-variant-numeric: tabular-nums;
  padding-left: var(--spacingHorizontalXS);
}

/* More › Timer. Sits with the call duration, and goes red at zero. */
.ubar__timer {
  display: inline-flex;
  align-items: center;
  gap: var(--spacingHorizontalXS);
  padding: 0.2rem var(--spacingHorizontalS);
  border-radius: var(--borderRadiusCircular);
  background-color: var(--colorNeutralBackground3);
  color: var(--colorNeutralForeground2);
  font-size: var(--fontSizeBase200);
  line-height: var(--lineHeightBase200);
  font-variant-numeric: tabular-nums;
}
.ubar__timer .btn__icon { font-size: 16px; line-height: 0; }
.ubar__timer .btn__icon svg { width: 1.6rem; height: 1.6rem; }
.ubar__timer[data-expired='true'] {
  background-color: var(--colorStatusDangerBackground1);
  color: var(--colorStatusDangerForeground1);
  font-weight: var(--fontWeightSemibold);
}

.ubar__recording {
  display: inline-flex;
  align-items: center;
  gap: var(--spacingHorizontalXS);
  padding: 0.2rem var(--spacingHorizontalS);
  border-radius: var(--borderRadiusCircular);
  background-color: var(--colorStatusDangerBackground1);
  color: var(--colorStatusDangerForeground1);
  font-size: var(--fontSizeBase200);
  line-height: var(--lineHeightBase200);
  font-weight: var(--fontWeightSemibold);
}
.ubar__recording__dot {
  width: 0.8rem; height: 0.8rem;
  border-radius: var(--borderRadiusCircular);
  background-color: var(--colorStatusDangerBackground3);
  animation: rec-pulse 2s var(--curveEasyEase) infinite;
}
@keyframes rec-pulse { 50% { opacity: 0.35; } }
@media screen and (prefers-reduced-motion: reduce) { .ubar__recording__dot { animation: none; } }

/* -- a control ------------------------------------------------------- */

.ctrl {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  position: relative;
  height: var(--ubarControlHeight);
  min-width: 4.8rem;
  max-width: 7.2rem;
  width: auto;
  margin: var(--spacingVerticalSNudge) var(--spacingHorizontalXS);
  padding: 0;
  background-color: var(--colorNeutralBackground1);
  /* `.fkfq4zb` / `.fbv2hy8` -- Foreground2, not Foreground1. */
  color: var(--colorNeutralForeground2);
  border: 0.1rem solid transparent;
  border-radius: var(--borderRadiusMedium);
  font-size: var(--fontSizeBase200);
  line-height: var(--lineHeightBase200);
  font-weight: var(--fontWeightRegular);
  cursor: pointer;
  overflow: visible;
  transition: background-color var(--durationFaster) var(--curveEasyEase);
}
.ctrl:hover { background-color: var(--colorNeutralBackground3Hover); color: var(--colorNeutralForeground1Hover); }
.ctrl:hover .ctrl__iconrow,
.ctrl:hover .ctrl__icon { color: var(--ctrl-icon-color, var(--colorNeutralForeground2BrandHover)); }
.ctrl:hover:active { background-color: var(--colorNeutralBackground3Pressed); }
.ctrl:disabled { color: var(--colorNeutralForegroundDisabled); cursor: default; background-color: transparent; }
.ctrl:disabled .ctrl__iconrow,
.ctrl:disabled .ctrl__icon { color: var(--colorNeutralForegroundDisabled); }

/* 25px, off `.fdoo6yt` on the reference's icon wrapper -- not the 20px this
   had, which is a fifth smaller and is what made the whole bar read as
   undersized next to the real one. The button box was already right: height
   4.8rem, min-width 4.8rem, max-width 7.2rem, padding 0, all matching the
   reference's own atoms. It was the glyph inside it. */
.ctrl__icon { font-size: 25px; line-height: 0; color: inherit; }
.ctrl__icon svg { width: 2.5rem; height: 2.5rem; }
/* `.f14d40fx` 1.1rem and `.faqd89r` margin -0.1rem 0 0.1rem 0. */
.ctrl__label {
  max-width: 100%;
  margin: -0.1rem 0 0.1rem 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 1.1rem;
  padding: 0 0.2rem;
}

/* The filled glyph is a HOVER state and only a hover state.
 *
 * An open panel is marked by the indicator underneath and by nothing else: the
 * button keeps the outline glyph in the bar's own foreground, exactly as it
 * looks at rest. Both of the other things this has been -- a grey pill, then a
 * filled brand-purple glyph -- were ours, and the reference does neither.
 *
 * That is also what `--ubar-icon-color` and the `--ubar-icon-filled` /
 * `--ubar-icon-outline` pair are doing in the capture: the fill and the tint
 * travel together, and the class that carries them is the hover one. */
.ctrl:hover .ctrl__icon .icon-filled { display: inline; }
.ctrl:hover .ctrl__icon .icon-regular { display: none; }

/* The selection indicator, transcribed from the reference's trailing span:
   `flo9id0` background-color var(--colorBrandForeground1), `f93tlit`
   border-radius .1rem, `f1euv43f` position absolute, `f1vyn9y` bottom -0.5rem,
   `f1pq1e6r` height .2rem, `fly5x3f` width 100%, and the pair `fvm94jw`
   scale(0, 1) / `f1ipy4ig` scale(1, 1) with `f1onck3e`
   transition: transform 0.3s cubic-bezier(0, 0.7, 0.40, 0.7).

   Scaling rather than toggling width is what makes it grow from the middle
   out. `.ctrl` already supplies the containing block and lets it overflow:
   position relative, overflow visible, padding 0, 0.1rem border. */
.ctrl__indicator {
  position: absolute;
  bottom: -0.5rem;
  width: 100%;
  height: 0.2rem;
  border-radius: 0.1rem;
  background-color: var(--colorBrandForeground1);
  transform: scale(0, 1);
  transition: transform 0.3s cubic-bezier(0, 0.7, 0.40, 0.7) 0s;
}
/* Two ways to be "on": a toggle that stays on (Chat, People, Raise, Share) and
   a flyout that is open (React, View, More). openMenu already writes
   aria-expanded on the anchor it opens from, so the second case needs no state
   of its own -- and the chevron buttons cannot light up by accident, because
   control() only builds an indicator for the main button. */
.ctrl[aria-pressed='true'] .ctrl__indicator,
.ctrl[aria-expanded='true'] .ctrl__indicator { transform: scale(1, 1); }

@media screen and (prefers-reduced-motion: reduce) {
  .ctrl__indicator { transition: none; }
}

/* In the dark theme the bar glows rather than sitting flat.
   A 2px line of saturated brand against a near-black bar is a hard little
   sliver; the reference softens its edge with light spilling around it, which
   is what a bright accent on a dark ground does. Two shadows, a tight one for
   the halo and a wider faint one for the spill, both in the bar's own colour.
   The light theme keeps the flat line -- there is nothing for it to glow
   against on white. */
[data-theme='dark'] .ctrl__indicator {
  box-shadow:
    0 0 0.4rem var(--colorBrandForeground1),
    0 0 1rem color-mix(in srgb, var(--colorBrandForeground1) 55%, transparent);
}

/* Muted and camera-off stay in the normal foreground. The cancel stroke
   through the glyph is the whole signal — the reference does not tint them,
   and Leave is the only red thing in this bar. `data-state="off"` is left on
   the button because it is real state worth exposing, not because anything
   here colours it. */
.ctrl--leave { --ctrl-icon-color: var(--colorStatusDangerForeground1); }
.ctrl--leave .ctrl__icon { color: var(--colorStatusDangerForeground1); }

/* A control paired with a chevron flyout, e.g. mic + audio options. */
.ctrl-group { display: flex; align-items: center; }
.ctrl-group .ctrl--split { border-top-right-radius: 0; border-bottom-right-radius: 0; margin-right: 0; }
.ctrl-group .ctrl--chevron {
  min-width: 1.8rem;
  width: 1.8rem;
  margin-left: 0;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.ctrl-group .ctrl--chevron .ctrl__icon { font-size: 12px; }
.ctrl-group .ctrl--chevron .ctrl__icon svg { width: 1.2rem; height: 1.2rem; }

/* The People headcount sits beside the glyph, not on top of it. */
.ctrl__iconrow { display: flex; align-items: center; gap: 2px; line-height: 0; }
.ctrl__count {
  font-size: var(--fontSizeBase200);
  line-height: 1;
  /* `.flh3ekv{font-weight:var(--fontWeightBold)}` in the reference. */
  font-weight: var(--fontWeightBold);
  /* No colour of its own, exactly as the reference leaves it — which is what
     lets it follow the button through hover, selected and disabled once those
     rules reach `.ctrl__iconrow`. */
  color: inherit;
  font-variant-numeric: tabular-nums;
}

.ctrl__badge {
  position: absolute;
  top: 0.2rem;
  right: 0.4rem;
  min-width: 1.6rem;
  height: 1.6rem;
  padding: 0 0.4rem;
  border-radius: var(--borderRadiusCircular);
  background-color: var(--colorBrandBackground);
  color: var(--colorNeutralForegroundOnBrand);
  font-size: var(--fontSizeBase100);
  line-height: 1.6rem;
  font-weight: var(--fontWeightSemibold);
  text-align: center;
}
/* Raised hands are a request waiting on someone, so the People badge goes
   danger-red rather than brand — it is the one count that needs answering. */
.ctrl__badge--alert { background-color: var(--colorStatusDangerBackground3); }

/* The badge REPLACES the headcount rather than sitting on top of it. In the
   reference the red disc lands over the number and hides it completely; ours
   was a shade narrower than the digits underneath, so an edge of the headcount
   showed around it and read as two numbers fighting.
   `visibility`, not `display`: the count keeps its width, so the glyph beside
   it does not jump sideways the moment somebody raises a hand. */
.ctrl:has(.ctrl__badge:not([hidden])) .ctrl__count { visibility: hidden; }

/* --------------------------------------------------------------------- */
/* Stage                                                                  */
/* --------------------------------------------------------------------- */

.stage-area {
  grid-row: 3;
  display: flex;
  min-height: 0;
  min-width: 0;
  background-color: var(--colorNeutralBackground3);
}

.stage {
  position: relative;
  flex: 1;
  min-width: 0;
  min-height: 0;
  padding: var(--spacingVerticalS);
  display: flex;
  align-items: center;
  justify-content: center;
}
/* With a panel open, the stage's own right padding is redundant: the separator
   is already the gap, and stacking padding + separator + panel margin put
   three spacings where the reference has one. */
.stage-area:has(.panel) .stage { padding-right: 0; }

/* The tile grid. `--cols` is set from JS once the participant count and
   the container aspect ratio are both known — the same thing Teams does
   with its dynamic-layout custom properties. */
/* Flex-wrap rather than a grid, for one reason: an incomplete last row.
   With `grid-template-columns` the odd tile out sits hard against the left
   with a hole beside it; wrapping flex items centre themselves, which is what
   the reference does with three people in a two-column layout.

   The column count still comes from chooseColumns() in meeting.js — the width
   below is exactly the column width that grid would have produced. */
.stage__grid {
  --grid-gap: var(--spacingHorizontalS);
  display: flex;
  flex-wrap: wrap;
  gap: var(--grid-gap);
  width: 100%;
  height: 100%;
  align-content: center;
  justify-content: center;
}
/* `--tile-w` is set by planGallery(): the column width already capped by the
   row height, so 16:9 tiles never total more than the stage. */
.stage__grid > .tile {
  flex: 0 0 var(--tile-w, calc((100% - (var(--cols, 1) - 1) * var(--grid-gap)) / var(--cols, 1)));
  max-width: 100%;
}

/* One other person: their tile takes the whole stage rather than sitting as
   a 16:9 card in the middle of it. */
.stage__grid--solo { align-content: stretch; }
.stage__grid--solo > .tile { aspect-ratio: auto; height: 100%; flex-basis: 100%; max-width: 100%; }

/* Screen share takes the stage; everyone else drops into a filmstrip. */
.stage--shared { flex-direction: column; gap: var(--spacingVerticalS); }
.stage--shared .stage__share { flex: 1; min-height: 0; }
/* With a share up the gallery becomes a filmstrip: a single row that scrolls
   rather than wrapping, so nobody is pushed out of sight below the fold. */
.stage--shared .stage__grid {
  height: 12rem;
  flex: 0 0 12rem;
  flex-wrap: nowrap;
  overflow-x: auto;
  align-content: stretch;
}
.stage--shared .stage__grid > .tile { flex: 0 0 auto; width: auto; max-width: none; height: 100%; }

/* View › Focus on content — the share alone, no filmstrip under it. */
.stage--focus .stage__share { flex: 1; min-height: 0; width: 100%; }

/* View › Speaker — one tile takes the stage, everyone else lines up below.
   The strip scrolls sideways rather than shrinking the faces in it. */
.stage--speaker { flex-direction: column; gap: var(--spacingVerticalS); }
.stage--speaker .stage__grid { flex: 1; min-height: 0; }
.stage__strip {
  display: flex;
  gap: var(--spacingHorizontalS);
  flex: 0 0 12rem;
  height: 12rem;
  width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
}
.stage__strip .tile { flex: 0 0 auto; height: 100%; width: auto; aspect-ratio: 16 / 9; }
/* Lift the self view clear of the filmstrip rather than parking it on top of
   whoever happens to be last in the row. */
.stage--speaker .self-pip { bottom: calc(12rem + 1.2rem + var(--spacingVerticalS)); }

/* View › More options › Show gallery at top. Reversing the column moves the
   filmstrip above the spotlight without touching the DOM order, so no video
   is re-attached — and with the strip no longer at the bottom, the self view
   goes back to sitting in the corner. */
.stage--speaker[data-gallery='top'] { flex-direction: column-reverse; }
.stage--speaker[data-gallery='top'] .self-pip { bottom: 1.2rem; }

/* --------------------------------------------------------------------- */
/* Alone on the stage                                                     */
/* --------------------------------------------------------------------- */

/* No tile chrome: the avatar sits on the stage itself, with the line under
   it. Sized off the stage rather than a container, since there is no tile. */
.stage__alone {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3.2rem;
  pointer-events: none;
}
.stage__alone[hidden] { display: none; }
.stage__alone__avatar {
  width: clamp(9.6rem, 18vh, 17.6rem);
  height: clamp(9.6rem, 18vh, 17.6rem);
  font-size: clamp(var(--fontSizeHero700), 7vh, var(--fontSizeHero1000));
  font-weight: var(--fontWeightSemibold);
  color: var(--avatar-ink, var(--grey14));
}
.stage__alone__text {
  margin: 0;
  font-size: var(--fontSizeBase500);
  line-height: var(--lineHeightBase500);
  color: var(--colorNeutralForeground1);
}

/* --------------------------------------------------------------------- */
/* Lobby prompt (host)                                                    */
/* --------------------------------------------------------------------- */

/* Under the control bar and centred, which is where the reference puts it —
   near People without covering the stage. */
.lobby-card {
  position: absolute;
  top: 1.2rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: var(--spacingVerticalS);
  width: 32rem;
  max-width: calc(100% - 2.4rem);
  padding: var(--spacingVerticalM) var(--spacingHorizontalM);
  background-color: var(--colorNeutralBackground1);
  border: 0.1rem solid var(--colorNeutralStroke2);
  border-radius: var(--borderRadiusMedium);
  box-shadow: var(--shadow16);
}
.lobby-card[hidden] { display: none; }

.lobby-card__head { display: flex; align-items: center; gap: var(--spacingHorizontalS); }
.lobby-card__icon { font-size: 16px; line-height: 0; color: var(--colorNeutralForeground2); }
.lobby-card__icon svg { width: 1.6rem; height: 1.6rem; }
.lobby-card__title {
  flex: 1;
  min-width: 0;
  font-size: var(--fontSizeBase300);
  line-height: var(--lineHeightBase300);
  color: var(--colorNeutralForeground1);
}
.lobby-card__head .btn--icon { margin: -0.4rem -0.4rem -0.4rem 0; }

.lobby-card__who {
  font-size: var(--fontSizeBase300);
  line-height: var(--lineHeightBase300);
  color: var(--colorNeutralForeground1);
}

/* Deny then Admit, both full-width halves — the reference gives them equal
   weight, because denying is not a secondary action. */
.lobby-card__actions { display: flex; gap: var(--spacingHorizontalS); }
.lobby-card__actions .btn { flex: 1; justify-content: center; }
.lobby-card__actions .btn:disabled { cursor: default; }

.share-surface {
  width: 100%;
  height: 100%;
  border-radius: var(--borderRadiusLarge);
  overflow: hidden;
  background-color: var(--grey8);
  display: flex;
  align-items: center;
  justify-content: center;
}
.share-surface video { width: 100%; height: 100%; object-fit: contain; background-color: var(--grey8); }

/* -- participant tile -------------------------------------------------- */

.tile {
  position: relative;
  min-width: 0;
  min-height: 0;
  aspect-ratio: 16 / 9;
  /* Reference: `f11z8e97` — a 0.2rem border in the *stage* colour, so tiles
     are separated by a gutter rather than a stroke, and the same border can
     turn brand for the active speaker or amber for a raised hand without
     anything shifting. */
  border: 0.2rem solid var(--colorNeutralBackground3);
  border-radius: var(--borderRadiusLarge);
  /* Not a coloured plate: the stage colour with four faint corner blooms
     over it, which is what the reference's per-participant `bkg_*` rule
     draws. The tile therefore all but merges with the stage and is
     delimited by its hairline -- that flatness is the reference, not a
     missing background. */
  background-color: var(--colorNeutralBackground3);
  background-image: var(--tile-bg, none);
  background-size: cover;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ffffff;
}

/* The card sits in the bottom-left corner, which is where the reference puts
   it. Removing the `.tile__hand` rules took this with them and the nametag
   fell back to static flow -- it ended up beside the avatar, mid-tile. */
.tile__corner {
  position: absolute;
  left: 0.8rem;
  bottom: 0.8rem;
  right: 0.8rem;
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalXS);
  pointer-events: none;
  z-index: 2;
}

/* The hairline the reference draws inside its gutter border: `fiqy1sp`, a 1px
   stencil stroke at `f1o0qvyv` 6px radius. It needs its own layer rather than
   a box-shadow on `.tile`, because `.self-pip` is also a `.tile` and its own
   shadow would clobber it.

   The radius is the tile's minus the 0.2rem border, not `inherit`. `.tile` is
   `overflow: hidden`, so its padding box is already clipped to the smaller
   radius; inheriting the outer 6px would have the corners shaved off. */
.tile::after {
  content: '';
  position: absolute;
  inset: 0;
  border: var(--strokeWidthThin) solid var(--colorNeutralStencil1Alpha);
  border-radius: calc(var(--borderRadiusLarge) - 0.2rem);
  pointer-events: none;
}

.tile__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background-color: var(--grey8);
}
/* No unconditional mirror here. This rule flipped every self tile regardless
   of the setting, so the toggle further down could only ever re-apply what was
   already applied -- turning mirroring OFF changed nothing, which is exactly
   what "mirror my video is not working" looks like. The `[data-mirror='on']`
   rules are the only ones that decide it now. */

/* Video paints its own frame; the gradient is only for the camera-off state. */
.tile[data-video='true'] { background-image: none; background-color: var(--grey8); }

/* Pale avatar with dark ink, sitting on the gradient.
   Sized from the reference: half the tile height, square, floor 48px and
   ceiling 200px — which is why it is large on a stage-filling tile and small
   in the floating self view, with no per-size rules. */
.tile__avatar {
  /* `f1whvlc6` on the reference's `participant-avatar`: box-shadow
     var(--shadow4). The disc is lifted off the tile rather than sitting flat
     on it. Deliberately here and not on `.avatar` in base.css -- the capture
     carries it on stage avatars only, never on a roster or list row. */
  box-shadow: var(--shadow4);
  color: var(--avatar-ink, var(--grey14));
  font-weight: var(--fontWeightSemibold);
  height: 50%;
  width: auto;
  aspect-ratio: 1 / 1;
  min-width: 48px;
  min-height: 48px;
  max-width: 200px;
  max-height: 200px;
  /* Type tracks the circle so initials stay proportional at every size, and
     the constant is the reference's: its initials are about 34% of the
     circle's diameter (a ~68px face in a 200px circle). The circle is half
     the tile height, so 34% of that is 17cqh — which holds the ratio at every
     size, with the ends matching the 48px floor and 200px ceiling.

     7cqh capped at Hero900 gave 40px in a 200px circle: 20%, half what it
     should be, so the initials read as small and lost in the disc. */
  font-size: clamp(1.6rem, 17cqh, var(--fontSizeHero1000));
}
.tile { container-type: size; }

/* Self view floats bottom-right over the stage once anyone else is present. */
.self-pip {
  position: absolute;
  right: 1.2rem;
  bottom: 1.2rem;
  width: 24rem;
  max-width: 34%;
  aspect-ratio: 16 / 9;
  z-index: 3;
  /* No shadow. The reference's self-view chrome is `f11z8e97 f1o0qvyv` and
     nothing else -- a .2rem border in the stage colour with a large radius --
     so what separates it from the video underneath is a light gutter, not a
     dark halo. `.tile` already supplies that border. */
}
/* Your own view carries the **same nametag as every other tile** — this used
   to be a separate mute badge, and that was the wrong model. In the reference
   the name span itself is `show-only-on-stream-hover`, exactly like the More
   options button beside it, so at rest the pill contains only the mute glyph
   and hovering fills in the rest. One tag with two states, not two elements.

   `:focus-within` and the open-menu case keep it reachable without a pointer,
   for the same reason the More button does. */
.self-pip .tile__corner { left: 0.6rem; bottom: 0.6rem; right: 0.6rem; }
.self-pip .tile__nametag__name { display: none; }

/* The reference sets this emoji to a flat 32px inside a 24px-tall nametag, so
   it overflows the chip top and bottom rather than being fitted into it. The
   negative margins keep it from growing the chip while it does. */
.tile__nametag__hand {
  width: 3.2rem;
  height: 3.2rem;
  flex-shrink: 0;
  display: block;
  margin: -0.4rem 0.2rem -0.4rem -0.2rem;
}
/* A raised hand, at rest, is the hand and the mute glyph -- and not the name.
 *
 * The chip stays: the reference keeps its grey plate under both, tucked into
 * the corner, and only drops the name until you point at the tile. Hovering
 * (or focusing, or opening the menu) brings the name and More back exactly as
 * they are on any other tile. */
.tile[data-hand='true']:not(:hover):not(:focus-within):not(:has(.tile__nametag__more[aria-expanded='true'])) .tile__nametag__name {
  display: none;
}

.self-pip .tile__nametag__muted { margin-left: 0; }
.self-pip:is(:hover, :focus-within, :has(.tile__nametag__more[aria-expanded='true'])) .tile__nametag__name { display: block; }
.self-pip:is(:hover, :focus-within, :has(.tile__nametag__more[aria-expanded='true'])) .tile__nametag__muted { margin-left: 0.8rem; }

/* Active speaker gets the ring Teams draws around the stream outline; a
   raised hand outranks it, since it is a request waiting on someone. */
.tile[data-speaking='true'] { border-color: var(--colorCompoundBrandStroke); }
/* The raised-hand ring is an overlay, not the tile's border: the reference
   draws it as its own absolutely-positioned div (`raise-hand-stream-outline`)
   carrying `border: .4rem solid var(--colorPaletteMarigoldForeground3)`, and
   transitions it over 660ms. Twice the width of the tile's gutter border, and
   inside it -- which is why it reads as a ring around the picture rather than
   as the gutter changing colour. */
.tile::before {
  content: '';
  position: absolute;
  inset: 0;
  border: 0.4rem solid var(--colorRaisedHand);
  border-radius: calc(var(--borderRadiusLarge) - 0.2rem);
  pointer-events: none;
  opacity: 0;
  transition: opacity 660ms ease-in-out;
  z-index: 1;
}
.tile[data-hand='true']::before { opacity: 1; }
@media screen and (prefers-reduced-motion: reduce) {
  .tile::before { transition: none; }
}

/* Name tag: bottom-left, 2.4rem tall, 50% black scrim, static-inverted text.
   `.f1at4ntt` height 2.4rem, `.fkm4jzu` padding .6rem, `.f16w37vj` the scrim,
   `.fonrgv7` the text colour — all off the reference's own nametag node.

   The padding really is .6rem on all four sides even though the box is only
   2.4rem tall: `overflow: hidden` clips at the *padding* box, so the 2.4rem
   More-options button centred on a 1.2rem content box reaches exactly the
   padding-box edges and nothing is lost. */
.tile__nametag {
  display: flex;
  align-items: center;
  min-width: 0;
  max-width: 100%;
  height: 2.4rem;
  padding: 0.6rem;
  /* Not `hidden`: the raise-hand emoji is deliberately taller than the chip.
     `.tile__nametag__name` carries its own overflow/ellipsis, so nothing
     depends on clipping here. */
  overflow: visible;
  /* The corner is click-through so the tile behind it stays hoverable; the
     nametag has a button in it, so it takes its own events back. */
  pointer-events: auto;
  border-radius: var(--borderRadiusMedium);
  background-color: rgba(0, 0, 0, 0.5);
  color: var(--colorNeutralForegroundStaticInverted);
  font-size: var(--fontSizeBase200);
  line-height: 1.5;
  text-shadow: none;
  transition: background var(--durationSlower) var(--curveEasyEase);
}
.tile__nametag__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tile__nametag svg { width: 1.6rem; height: 1.6rem; }

/* `.f1kdaspl` margin-left .8rem, `.fwhlsyb` min-width 1.5rem, `.fez10in`
   line-height 0, font-size 16px. No colour of its own: the reference sets
   none, so it inherits the nametag's static-inverted white. This used to be
   tinted cranberry, which read as an alert for something that is only a
   status. */
.tile__nametag__muted {
  display: flex;
  flex-shrink: 0;
  margin-left: 0.8rem;
  min-width: 1.5rem;
  line-height: 0;
}

/* More options, in the nametag. `.f137enl0` width 2rem, `.fi9hj39 .fui-Button`
   height 2.4rem, `.f1lgfsh5` margin-left .4rem, transparent throughout —
   the reference gives this button no hover background at all, only the icon
   swap every Fluent button does. */
.tile__nametag__more {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2rem;
  height: 2.4rem;
  margin-left: 0.4rem;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--colorNeutralForegroundStaticInverted);
  cursor: pointer;
}
.tile__nametag__more .icon-filled { display: none; }
.tile__nametag__more:hover .icon-filled,
.tile__nametag__more[aria-expanded='true'] .icon-filled { display: inline; }
.tile__nametag__more:hover .icon-regular,
.tile__nametag__more[aria-expanded='true'] .icon-regular { display: none; }

/* `.f1ovgixi:not(:hover):not(:focus-within) .show-only-on-stream-hover:not(:focus)`
   — hidden until the pointer is over the tile, and the `:focus` half is what
   keeps it reachable from the keyboard. `aria-expanded` is ours: without it
   the button vanishes the moment its own menu takes focus away. */
.tile:not(:hover):not(:focus-within) .tile__nametag__more:not(:focus):not([aria-expanded='true']) {
  display: none;
}


.tile__menu-btn {
  position: absolute;
  top: 0.8rem;
  right: 0.8rem;
  width: 2.4rem;
  height: 2.4rem;
  min-width: 2.4rem;
  padding: 0;
  border: 0;
  border-radius: var(--borderRadiusMedium);
  background-color: rgba(0, 0, 0, 0.5);
  color: var(--colorNeutralForegroundStaticInverted);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--durationFaster) var(--curveEasyEase);
}
.tile:hover .tile__menu-btn,
.tile__menu-btn:focus-visible { opacity: 1; }
.tile__menu-btn:hover { background-color: rgba(0, 0, 0, 0.7); }

/* --------------------------------------------------------------------- */
/* The drag handle between stage and panel. Four pixels wide with a wider
   invisible hit area, because a 4px target is a 4px target however forgiving
   the cursor looks. `touch-action: none` is required or the browser claims the
   gesture for scrolling before pointermove ever fires. */
.panel-resizer {
  position: relative;
  flex: 0 0 4px;
  align-self: stretch;
  margin: var(--spacingVerticalS) 0;
  background-color: transparent;
  border-radius: var(--borderRadiusSmall);
  cursor: ew-resize;
  user-select: none;
  touch-action: none;
}
.panel-resizer::before { content: ''; position: absolute; inset: 0 -0.3rem; }
.panel-resizer:hover { background-color: var(--colorNeutralStroke2); }
.panel-resizer:focus-visible,
.panel-resizer[data-dragging] {
  outline: none;
  background-color: var(--colorStrokeFocus2);
}

/* Below the overlay breakpoint the panel is full-width and there is nothing
   left to drag against. */
@media (max-width: 1000px) { .panel-resizer { display: none; } }

/* Hold the cursor and kill selection for the whole drag, not just over the
   handle -- the pointer leaves it constantly while dragging. */
body.is-resizing,
body.is-resizing * { cursor: ew-resize !important; user-select: none !important; }

/* --------------------------------------------------------------------- */
/* Live captions                                                          */
/* --------------------------------------------------------------------- */

/* Over the video, bottom-centre, clear of the control bar. Interim results
   are dimmed so a line that is still being revised reads differently from one
   the recogniser has committed to. */
.caption-layer {
  position: absolute;
  left: 50%;
  bottom: 2.4rem;
  transform: translateX(-50%);
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: var(--spacingVerticalXXS);
  width: min(88%, 96rem);
  pointer-events: none;
}
.caption-layer[hidden] { display: none; }

.caption__line {
  margin: 0;
  padding: 0.6rem var(--spacingHorizontalM);
  border-radius: var(--borderRadiusMedium);
  background-color: rgba(0, 0, 0, 0.75);
  color: var(--colorNeutralForegroundStaticInverted);
  font-size: var(--fontSizeBase400);
  line-height: var(--lineHeightBase400);
  opacity: 0.75;
  transition: opacity var(--durationFaster) var(--curveEasyEase);
}
.caption__line[data-final='true'] { opacity: 1; }
.caption__who {
  font-weight: var(--fontWeightSemibold);
  margin-right: var(--spacingHorizontalS);
}

/* --------------------------------------------------------------------- */
/* Timer -- a bar across the top of the stage                              */
/* --------------------------------------------------------------------- */

/* Not a dialog. It is a shared countdown everyone is meant to keep glancing
   at, and a modal you must dismiss to see the meeting again is the opposite of
   that. Top-left, above the stage, with the track filling the rest. */
.timer-bar {
  grid-row: 2;
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalXS);
  flex-shrink: 0;
  height: 3.2rem;
  padding: 0 var(--spacingHorizontalS);
  background-color: var(--colorNeutralBackground1);
  border-bottom: 0.1rem solid var(--colorNeutralStroke3);
}
.timer-bar[hidden] { display: none; }

.timer-bar__time {
  font-size: var(--fontSizeBase400);
  line-height: var(--lineHeightBase400);
  font-weight: var(--fontWeightSemibold);
  color: var(--colorBrandForeground1);
  font-variant-numeric: tabular-nums;
  min-width: 6rem;
  text-align: center;
}
.timer-bar[data-expired='true'] .timer-bar__time { color: var(--colorStatusDangerForeground1); }

.timer-bar__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  border: 0;
  background: transparent;
  color: var(--colorNeutralForeground2);
  border-radius: var(--borderRadiusMedium);
  padding: 0.4rem;
  cursor: pointer;
  font-size: 16px;
  line-height: 0;
}
.timer-bar__btn:hover { background-color: var(--colorSubtleBackgroundHover); }
.timer-bar__btn svg { width: 1.6rem; height: 1.6rem; }
.timer-bar__clock svg:last-child { width: 1.2rem; height: 1.2rem; }

/* Drains left to right as the countdown runs. */
.timer-bar__track {
  flex: 1;
  height: 0.8rem;
  min-width: 0;
  border-radius: var(--borderRadiusCircular);
  background-color: var(--colorNeutralBackground4);
  overflow: hidden;
}
.timer-bar__fill {
  display: block;
  height: 100%;
  width: 100%;
  background-color: var(--colorBrandBackground2);
  transition: width 1s linear;
}

.timer-setter { display: flex; flex-direction: column; gap: var(--spacingVerticalS); min-width: 20rem; }
.timer-setter__row { display: flex; align-items: center; gap: var(--spacingHorizontalXS); }
.timer-setter__num {
  width: 6rem;
  font-size: var(--fontSizeBase500);
  font-weight: var(--fontWeightSemibold);
  text-align: center;
  font-variant-numeric: tabular-nums;
  border: 0.1rem solid var(--colorNeutralStroke1);
  border-radius: var(--borderRadiusMedium);
  background-color: var(--colorNeutralBackground1);
  color: var(--colorNeutralForeground1);
  padding: 0.4rem;
}
.timer-setter__colon { font-size: var(--fontSizeBase500); font-weight: var(--fontWeightSemibold); }
.timer-setter__quick { display: flex; gap: var(--spacingHorizontalM); }
.timer-setter__actions { display: flex; justify-content: flex-end; gap: var(--spacingHorizontalS); }

/* --------------------------------------------------------------------- */
/* Audio settings panel                                                   */
/* --------------------------------------------------------------------- */

/* Two collapsible groups of radio rows -- Speaker, then Microphone. The rows
   reuse the Fluent radio in base.css, which is also where the greying of the
   unselected labels comes from: `.radio__input:not(:checked) ~ .radio__label`
   is Foreground3, and the checked one is Foreground1. That contrast is the
   reference's, not a special case for this panel. */
.audio-settings { display: flex; flex-direction: column; gap: var(--spacingVerticalM); }
.audio-settings__section { display: flex; flex-direction: column; }

.audio-settings__group {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalXS);
  width: 100%;
  padding: var(--spacingVerticalXS) 0;
  background: none;
  border: 0;
  color: var(--colorNeutralForeground2);
  font-size: var(--fontSizeBase200);
  line-height: var(--lineHeightBase200);
  text-align: left;
  cursor: pointer;
}
.audio-settings__chevron { display: flex; line-height: 0; transition: transform var(--durationFaster) var(--curveEasyEase); }
.audio-settings__chevron svg { width: 1.2rem; height: 1.2rem; }
.audio-settings__chevron[data-collapsed='true'] { transform: rotate(-90deg); }

.audio-settings__list { display: flex; flex-direction: column; }
.audio-settings__list[hidden] { display: none; }

/* The whole row is the label, so the name is as clickable as the dot. */
.audio-settings__row {
  align-items: center;
  width: 100%;
  border-radius: var(--borderRadiusMedium);
  font-size: var(--fontSizeBase300);
  line-height: var(--lineHeightBase300);
}
.audio-settings__row:hover { background-color: var(--colorSubtleBackgroundHover); }
.audio-settings__row .radio__label { min-width: 0; overflow-wrap: anywhere; }

.audio-settings__none {
  margin: 0;
  padding: var(--spacingVerticalS) var(--spacingHorizontalS);
  font-size: var(--fontSizeBase200);
  color: var(--colorNeutralForeground3);
}

/* --------------------------------------------------------------------- */
/* Meeting info panel                                                     */
/* --------------------------------------------------------------------- */

.info-panel { display: flex; flex-direction: column; gap: var(--spacingVerticalS); }
.info-panel__title {
  margin: 0;
  font-size: var(--fontSizeBase500);
  line-height: var(--lineHeightBase500);
  font-weight: var(--fontWeightRegular);
}
.info-panel__when { margin: 0; font-size: var(--fontSizeBase300); }
.info-panel__copy { display: flex; align-items: center; gap: var(--spacingHorizontalM); margin: var(--spacingVerticalM) 0; }
.info-panel__copy .btn { flex: 1; }
.info-panel__link-icon { font-size: 20px; line-height: 0; color: var(--colorNeutralForeground2); }
.info-panel__rule { border: 0; border-top: 0.1rem solid var(--colorNeutralStroke2); margin: var(--spacingVerticalM) 0; width: 100%; }
.info-panel__heading {
  margin: 0;
  font-size: var(--fontSizeBase500);
  line-height: var(--lineHeightBase500);
  font-weight: var(--fontWeightRegular);
}
.info-panel__lead { margin: 0; font-weight: var(--fontWeightSemibold); font-size: var(--fontSizeBase300); }
.info-panel__id { margin: 0; font-size: var(--fontSizeBase300); }

/* Side panel — People, Chat                                              */
/* --------------------------------------------------------------------- */

.panel {
  display: flex;
  flex-direction: column;
  flex: 0 0 var(--sidePanelWidth);
  width: var(--sidePanelWidth);
  min-width: 0;
  /* The separator to its left is the gap; a left margin on top of it doubles
     the space and reads as the panel floating away from the stage. */
  margin: var(--spacingVerticalS) var(--spacingHorizontalXS) var(--spacingVerticalS) 0;
  background-color: var(--colorNeutralBackground1);
  border-radius: var(--borderRadiusXLarge);
  box-shadow: var(--shadow4);
  overflow: hidden;
}

.panel__header {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalS);
  height: 4.8rem;
  flex-shrink: 0;
  padding: 0 var(--spacingHorizontalS) 0 var(--spacingHorizontalL);
  border-bottom: 0.1rem solid var(--colorNeutralStroke3);
}
.panel__title { flex: 1; font-size: var(--fontSizeBase400); line-height: var(--lineHeightBase400); font-weight: var(--fontWeightSemibold); }
.panel__body { flex: 1; min-height: 0; overflow-y: auto; padding: var(--spacingVerticalS); }
.panel__footer { flex-shrink: 0; padding: var(--spacingVerticalS) var(--spacingHorizontalM) var(--spacingVerticalM); border-top: 0.1rem solid var(--colorNeutralStroke3); }

.panel__empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spacingVerticalS);
  height: 100%;
  padding: var(--spacingVerticalXXXL) var(--spacingHorizontalL);
  text-align: center;
  color: var(--colorNeutralForeground3);
  font-size: var(--fontSizeBase300);
}

/* -- roster ------------------------------------------------------------ */

.roster__tools {
  display: flex;
  flex-direction: column;
  gap: var(--spacingVerticalS);
  padding: var(--spacingVerticalXS) var(--spacingHorizontalXS) var(--spacingVerticalM);
}

.roster__search { position: relative; display: flex; align-items: center; }
.roster__search__input {
  width: 100%;
  min-height: 3.2rem;
  padding: 0 3.2rem 0 var(--spacingHorizontalM);
  background-color: var(--colorNeutralBackground1);
  border: 0.1rem solid var(--colorNeutralStroke1);
  border-radius: var(--borderRadiusMedium);
  font-size: var(--fontSizeBase300);
  color: var(--colorNeutralForeground1);
  outline: none;
}
.roster__search__input::placeholder { color: var(--colorNeutralForeground4); opacity: 1; }
.roster__search__input:focus { border-color: var(--colorCompoundBrandStroke); }
.roster__search__icon {
  position: absolute;
  right: var(--spacingHorizontalM);
  line-height: 0;
  color: var(--colorNeutralForeground3);
  pointer-events: none;
}
.roster__search__icon svg { width: 1.8rem; height: 1.8rem; }

.roster__invite {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacingHorizontalS);
  width: 100%;
  min-height: 3.2rem;
  padding: 0 var(--spacingHorizontalM);
  background-color: var(--colorNeutralBackground1);
  border: 0.1rem solid var(--colorNeutralStroke1);
  border-radius: var(--borderRadiusMedium);
  font-size: var(--fontSizeBase300);
  font-weight: var(--fontWeightSemibold);
  color: var(--colorNeutralForeground1);
  cursor: pointer;
}
.roster__invite:hover { background-color: var(--colorNeutralBackground1Hover); }
.roster__invite svg { width: 1.8rem; height: 1.8rem; }

.roster__section { margin-bottom: var(--spacingVerticalM); }

/* Collapsible group header — the count lives here, not in the panel title. */
.roster__group {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalXS);
  width: 100%;
  padding: var(--spacingVerticalS) var(--spacingHorizontalS);
  background: none;
  border: 0;
  font-size: var(--fontSizeBase200);
  line-height: var(--lineHeightBase200);
  font-weight: var(--fontWeightSemibold);
  color: var(--colorNeutralForeground3);
  text-align: left;
  cursor: pointer;
  border-radius: var(--borderRadiusMedium);
}
.roster__group:hover { background-color: var(--colorSubtleBackgroundHover); }
.roster__group__chevron { line-height: 0; transition: transform var(--durationFaster) var(--curveEasyEase); }
.roster__group__chevron svg { width: 1.2rem; height: 1.2rem; }
.roster__group__chevron[data-collapsed='true'] { transform: rotate(-90deg); }

.roster__none {
  padding: var(--spacingVerticalM) var(--spacingHorizontalS);
  font-size: var(--fontSizeBase300);
  color: var(--colorNeutralForeground3);
}

.roster__section__title {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalXS);
  padding: var(--spacingVerticalS) var(--spacingHorizontalS);
  font-size: var(--fontSizeBase200);
  line-height: var(--lineHeightBase200);
  font-weight: var(--fontWeightSemibold);
  color: var(--colorNeutralForeground3);
}

.roster__row {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalM);
  min-height: 4.8rem;
  padding: 0 var(--spacingHorizontalS);
  border-radius: var(--borderRadiusMedium);
  cursor: default;
}
.roster__row:hover { background-color: var(--colorSubtleBackgroundHover); }
.roster__row__text { flex: 1; min-width: 0; }
.roster__row__name { font-size: var(--fontSizeBase300); line-height: var(--lineHeightBase300); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.roster__row__role { font-size: var(--fontSizeBase200); line-height: var(--lineHeightBase200); color: var(--colorNeutralForeground3); }
/* Neutral, not danger: this list reports state, it does not raise an alarm.
   2rem, off the reference's own `font-size="20px"` on `roster-mic-button`. */
.roster__row__icons { display: flex; align-items: center; gap: var(--spacingHorizontalXS); color: var(--colorNeutralForeground2); }
.roster__row__icons svg { width: 2rem; height: 2rem; }
/* Nothing for `data-speaking` here on purpose. The name used to go semibold
   while someone was talking; the reference leaves the roster still. A row that
   thickens and thins with every syllable is movement in a list people scan,
   and it says nothing the tile's own speaking ring does not already say. The
   attribute stays on the row -- it is real state, and the ordering uses it. */

/* Microsoft's raise-hand emoji at the size the reference gives it:
   `min-width: 32px; height: 32px` on `roster-raise-hand-icon`, with the img
   itself `width="32"`. The artwork also carries transparent margin the mic
   glyph does not, which is why 32 beside a 20px icon still looks balanced. */
.roster__hand { width: 3.2rem; height: 3.2rem; min-width: 3.2rem; display: block; flex-shrink: 0; }

/* More options, and the swap that shows it. The reference exchanges the status
   icons for this button on hover instead of adding it beside them, so the row
   holds its width and nothing shifts under the pointer. `:focus-within` keeps
   it reachable from the keyboard, and `aria-expanded` holds it open while its
   own menu has focus. */
.roster__row__more {
  display: none;
  align-items: center;
  justify-content: center;
  width: 3.2rem;
  height: 3.2rem;
  flex-shrink: 0;
  padding: 0;
  border: 0;
  border-radius: var(--borderRadiusMedium);
  background: transparent;
  color: var(--colorNeutralForeground2);
  cursor: pointer;
}
.roster__row__more svg { width: 1.8rem; height: 1.8rem; }
.roster__row__more:hover { background-color: var(--colorNeutralBackground1Hover); }
.roster__row:is(:hover, :focus-within) .roster__row__icons,
.roster__row:has(.roster__row__more[aria-expanded='true']) .roster__row__icons { display: none; }
.roster__row:is(:hover, :focus-within) .roster__row__more,
.roster__row:has(.roster__row__more[aria-expanded='true']) .roster__row__more { display: flex; }

/* A raised hand rings the roster avatar, so the row reads at a glance
   without hunting for the icon on the far side. */
.roster__row[data-hand='true'] .avatar {
  box-shadow: 0 0 0 2px var(--colorNeutralBackground1), 0 0 0 4px var(--colorRaisedHand);
}

/* -- waiting room (host) ---------------------------------------------- */

.lobby__row {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalS);
  padding: var(--spacingVerticalS);
  border-radius: var(--borderRadiusMedium);
  background-color: var(--colorNeutralBackground3);
  margin-bottom: var(--spacingVerticalXS);
}
.lobby__row .btn { min-width: 0; padding: 2px var(--spacingHorizontalS); }

/* -- chat -------------------------------------------------------------- */

/* The thread grows from the bottom, as a chat should: with little in it the
   content sits just above the compose box rather than floating mid-panel. */
/* The thread grows from the bottom. `margin-top: auto` on a short list pins it
   to the bottom edge, and once it is taller than the panel the margin
   collapses to nothing and it scrolls normally — no JS, and no jump the first
   time it overflows.

   There used to be a `justify-content: flex-end` here as well, doing the same
   job the auto margin does — and breaking scrolling while it did. A scroll
   container that packs its content to the end edge puts the overflow at the
   START, where the scroll range does not reach: the thread simply could not be
   scrolled back up. The auto margin has no such problem, which is why it is
   the one that stayed. */
.chat__body { display: flex; flex-direction: column; }
/* 2px between messages, not 12. Consecutive messages from one person are a
   run, and a run is drawn tight -- the space that used to sit between every
   pair of bubbles now sits only where the sender changes (`--lead`), which is
   what makes a run read as one turn in the conversation instead of five.

   The list needs room above it for the hover toolbar, which floats over the
   top edge of whichever message the pointer is on. */
.chat__list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: auto;
  padding-top: 2.4rem;
}

.chat__divider {
  align-self: center;
  font-size: var(--fontSizeBase200);
  line-height: var(--lineHeightBase200);
  color: var(--colorNeutralForeground3);
  padding: var(--spacingVerticalXS) 0;
}

/* Meeting events — joined, left, started. Icon instead of an avatar. */
.chat__system {
  display: grid;
  grid-template-columns: 2rem auto minmax(0, 1fr);
  align-items: baseline;
  gap: var(--spacingHorizontalS);
  font-size: var(--fontSizeBase300);
  line-height: var(--lineHeightBase300);
  color: var(--colorNeutralForeground2);
}
.chat__system__icon { color: var(--colorNeutralForeground3); line-height: 0; align-self: center; }
.chat__system__icon svg { width: 1.8rem; height: 1.8rem; }
.chat__system__time { font-size: var(--fontSizeBase200); color: var(--colorNeutralForeground3); font-variant-numeric: tabular-nums; }
.chat__system__text { min-width: 0; overflow-wrap: anywhere; }

/* One message. `position: relative` is what the hover toolbar anchors to.
   `--gutter` is where the bubble column starts, and it is a variable because
   the bubble, the name above it and the empty gutter of every following
   message all have to agree on it or the run bends. */
.chat__msg {
  /* 0.4rem short of the disc's 3.2rem: the reference tucks the bubble UNDER
     the avatar rather than butting the two together, so the disc overlaps the
     bubble's rounded corner and the column starts behind it. */
  --gutter: 2.8rem;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* The space between runs. Consecutive messages keep the list's 2px. */
.chat__msg--lead { margin-top: var(--spacingVerticalM); }
.chat__list > .chat__msg:first-child { margin-top: 0; }

/* No gap: the avatar overlaps the first bubble of the run rather than sitting
   a space away from it. The timestamp takes its own margin instead. */
.chat__msg__row { position: relative; display: flex; align-items: flex-end; gap: 0; }
/* The margin the reference leaves down the right-hand side, reserved on EVERY
   one of your messages rather than only on the one carrying the tick. That is
   the whole point of it: the tick used to be a flex item, so the last bubble
   was pushed left by exactly its width and ended somewhere the others did not.
   Reserved here, every bubble in a run ends on the same edge and the tick sits
   in the gap beside the last of them. */
.chat__msg--self .chat__msg__row { flex-direction: row-reverse; padding-right: 2rem; }

/* Held open on every message of the run, so the bubbles stay in one column
   even though only the first has a face in it. 3.2rem, the same disc the stage
   draws -- a 2.4rem one beside a 3.4rem bubble read as two different heights,
   which is exactly what it was.

   The negative margin is the overlap: it pulls the bubble 0.4rem back under
   the disc without moving the disc, and `z-index` is what decides which of the
   two is on top once they share those pixels.

   `flex-start`, not `flex-end`: the face belongs beside the FIRST line of what
   the person said. On a two-line message, bottom-aligning it put the disc next
   to the second line with a blank space above it. */
.chat__msg__gutter {
  position: relative;
  z-index: 1;
  flex: 0 0 3.2rem;
  margin-right: -0.4rem;
  align-self: flex-start;
  line-height: 0;
}

/* The name, once, above the run -- indented to the bubble it belongs to. */
.chat__msg__author {
  margin-left: var(--gutter);
  font-size: var(--fontSizeBase200);
  line-height: var(--lineHeightBase200);
  color: var(--colorNeutralForeground2);
}

/* Always in the DOM so hovering never reflows the row, and never on screen
   until you hover -- including the first message of a run. A thread that
   stamps every arrival is a column of times nobody reads; the reference shows
   one only when you go looking for it. */
.chat__msg__time {
  flex: 0 0 auto;
  /* Level with the FIRST line, like the avatar opposite it. Bottom-aligned,
     a two-line message put its timestamp beside the second line. */
  align-self: flex-start;
  margin: 0 var(--spacingHorizontalS);
  padding-top: 0.6rem;
  font-size: var(--fontSizeBase200);
  color: var(--colorNeutralForeground3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  opacity: 0;
  transition: opacity var(--durationFaster) var(--curveEasyEase);
}
.chat__msg:hover .chat__msg__time,
.chat__msg:focus-within .chat__msg__time { opacity: 1; }

.chat__msg__bubble {
  min-width: 0;
  max-width: calc(100% - 5.2rem);
  font-size: var(--fontSizeBase300);
  line-height: var(--lineHeightBase300);
  color: var(--colorNeutralForeground1);
  background-color: var(--colorNeutralBackground3);
  border: 0.1rem solid var(--colorTransparentStroke);
  /* `fui-ChatMyMessage__body`: `.f1wt32pr` border-radius 12px, `.f9ggezi` a
     1px transparent-stroke border, `.f9ijwd5` word-break: break-word,
     `.f16gqiwe` max-width calc(100% - 52px).

     The padding is 5px, not the capture's 8px, and it is derived rather than
     chosen: 20px of line box + 2x5 + 2x1 of border is exactly the 32px of the
     avatar beside it. At 8px the bubble stood 6px taller than the disc, and a
     mismatch that size is visible on every first message of a run. */
  border-radius: var(--borderRadius2XLarge);
  padding: 0.5rem var(--spacingHorizontalM);
  word-break: break-word;
}
.chat__msg__body {
  /* Chat arrives from another client with zero sanitisation anywhere in the
     pipeline. It is inserted with textContent and wrapped, never parsed. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Yours: on the right, and position says who sent it.
 *
 * It is a GRADIENT, not a colour, which is why a flat fill never quite
 * matched. `--chatThemeMyBubbleBackground` resolves nowhere in the saved CSS
 * -- it is a runtime theme variable -- but the theme table is in the bundle,
 * and the default theme defines it as a linear-gradient with white text. That
 * is also why the reference applies it as `background-image` (`.f1tseldd`)
 * over a `background-color` fallback, rather than as a colour.
 *
 * `background-attachment: fixed` (`.f1dsdmen`) is the other half of the look:
 * it anchors the gradient to the viewport instead of to each bubble, so a run
 * of messages reads as one continuous wash -- lighter at the top of the
 * thread, deeper at the bottom -- rather than every bubble repeating the whole
 * ramp. */
.chat__msg--self .chat__msg__bubble {
  background-color: var(--chatMyBubbleFallback);
  background-image: var(--chatMyBubbleBackground);
  background-attachment: fixed;
  color: var(--chatMyBubbleForeground);
}

/* Sent, on the newest of your messages. Icon only: a line of text under a
   bubble is a second message competing with the first.

   Absolutely placed in the row's reserved right margin, beside the bubble and
   level with its bottom. Taking it out of the flow is what stops it moving the
   message it belongs to -- as a flex item it stole its own width from the last
   bubble, which is why that one ended short of all the others. */
.chat__msg__status {
  position: absolute;
  right: 0;
  bottom: 0.2rem;
  display: flex;
  align-items: center;
  color: var(--colorBrandForeground1);
  line-height: 0;
}
.chat__msg__status svg { width: 1.4rem; height: 1.4rem; }

.chat__msg--direct .chat__msg__bubble { border-color: var(--colorBrandStroke2); }
.chat__msg__dm-tag {
  font-size: var(--fontSizeBase100);
  font-weight: var(--fontWeightSemibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--colorBrandForeground1);
}

/* -- the presence dot on a chat avatar -------------------------------- */

/* The avatar clips its own contents, which is what keeps a photo inside the
   circle -- so the dot cannot live in it and has to hang off a wrapper. */
/* Transcribed from `fui-PresenceBadge` in the capture (`r832ydo` and the
   classes on `me-control-avatar-presence`): a 10x10 glyph inset to the
   avatar's own bottom-right corner -- `bottom: 0; right: 0`, INSIDE the box,
   not hanging off it -- on a 1px disc of the surface behind it, which is what
   keeps it legible against a coloured avatar without a heavy white ring.

   The colour is the badge's, and it is the only saturated thing on the disc,
   which is why the glyph is the outline and not the filled plate. */
.avatar-slot { position: relative; display: inline-flex; line-height: 0; }
.presence {
  position: absolute;
  right: 0;
  bottom: 0;
  display: flex;
  padding: 1px;
  line-height: 0;
  border-radius: var(--borderRadiusCircular);
  background-color: var(--colorNeutralBackground1);
  color: var(--colorPaletteLightGreenForeground3, #13a10e);
}
.presence svg { width: 1rem; height: 1rem; }

/* -- reactions on a message ------------------------------------------- */

/* The reaction pill hangs off the bubble's bottom-left corner and OVERLAPS it,
   which is how the reference draws it -- not a row of chips on a line of their
   own underneath. Anchored to the bubble rather than to the message, so it
   lands under the bubble's leading edge whichever side of the thread it is on.

   `.chat__msg--reacted` is the space it needs: the pill is out of flow, so
   without a reserved strip the next message comes up underneath it. It has to
   cover the whole overhang -- the pill is 2.8rem tall and overlaps the bubble
   by 0.8rem, so 2rem of it hangs below -- plus the gap the thread would have
   had anyway. Reserving less is what made a reacted message look no different
   from any other, with its own reactions sitting on the message beneath. */
.chat__msg__bubble { position: relative; --reaction-inset: 0.8rem; }
.chat__msg--reacted { padding-bottom: 2.4rem; }

/* One button per reaction, plus "More reactions" — separate surfaces, not
   chips sharing a tray. `left` is read back by `fitBubbleToReactions()`, which
   widens the bubble to hold the row: the row is out of flow and cannot widen
   anything itself, so a short message used to wear its reactions off the end
   of it while a long one did not. */
.chat__msg__reactions {
  position: absolute;
  top: calc(100% - var(--reaction-inset));
  left: var(--reaction-inset);
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
/* The row hangs off the edge the message is *aligned* to, which is the only
   edge that stands still. Your own messages are flush right, so anchoring
   their reactions to the left put every row at a different place -- one inset
   from a left edge that moves with the length of what you typed. Incoming
   messages are flush left and already had the fixed edge under them. */
.chat__msg--self .chat__msg__reactions { left: auto; right: var(--reaction-inset); }
.chat__msg__reactions[hidden] { display: none; }

/* An attachment bubble clips itself so the picture takes its corners -- which
   would also clip the pill hanging out of it. The clipping moves to the card. */
.chat__msg__bubble--attachment { overflow: visible; }
.chat__msg__bubble--attachment > .chat__file { border-radius: inherit; overflow: hidden; }

/* `diverse-reaction-pill-button`, off the reference's own markup:
     .fmu5hsk   min-width 34px      .f1ltdqvm  height 26px
     .f1cit44s  padding 0 5.5px     .f44lkw9   radius circular
     .fxugw4r   background NeutralBackground1
     .fy9rknc / .fwrc4pm  fontSizeBase200 / lineHeightBase200
   and the emoji inside it is 20px (`width="20"`, `min-width:20px;height:20px`),
   not the 18px that was here. */
.chat__chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.2rem;
  min-width: 3.4rem;
  height: 2.6rem;
  padding: 0 0.55rem;
  border: 0.1rem solid var(--colorNeutralStroke2);
  border-radius: var(--borderRadiusCircular);
  background-color: var(--colorNeutralBackground1);
  /* No shadow. Both buttons' full class attributes are in the capture and
     neither carries one -- the soft edge they seem to have at high zoom is the
     1px NeutralStroke2 border. */
  color: var(--colorNeutralForeground2);
  font-size: var(--fontSizeBase200);
  line-height: var(--lineHeightBase200);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.chat__chip:hover { background-color: var(--colorNeutralBackground1Hover); }
/* Yours is a brand *outline*, not a brand fill: the pressed pill in the
   capture keeps `fxugw4r` (NeutralBackground1) and adds `.f18mn8mw`
   (1.5px solid colorBrandStroke1) with `.faj9fo0` (colorBrandForeground2). */
.chat__chip[data-mine='true'] {
  border: 0.15rem solid var(--colorBrandStroke1);
  color: var(--colorBrandForeground2);
}
.chat__chip .reaction-sprite { width: 2rem; height: 2rem; }
/* Anything from the picker draws as the character itself. */
.chat__chip__emoji { font-size: 1.6rem; line-height: 1; }
/* `add-reaction-picker-entry-point-button`, same source:
     .f5mk7bh / .f1kxe85k / .f17fgpbq  width 34px, max 34px, min 24px
     .f1ltdqvm  height 26px            .fu97m5z  padding 1px
     .fb066o8   border 1px solid colorNeutralStroke2
     .f11d4kpn  color NeutralForeground3
   Its icon slot is 20px but the glyph in it is 16px — a rounded rectangle the
   same size as a reaction pill, not the circle that was here. */
.chat__chip--add {
  width: 3.4rem;
  min-width: 2.4rem;
  max-width: 3.4rem;
  padding: 0.1rem;
  color: var(--colorNeutralForeground3);
}
.chat__chip--add svg { width: 1.6rem; height: 1.6rem; }

/* Hovering it takes the plate away and lights the glyph, rather than shading
   the plate the way every other chip does. All four halves of that are in the
   button's own class list:
     .fxoo9op:hover  background-color colorTransparentBackground
     .fe843f1:hover  border stays: solid 1px colorNeutralStroke2
     .ft1hn21/.fuxngvv:hover  swap fui-Icon-regular out for fui-Icon-filled
     .fut9mon.fui-Icon-filled colorNeutralForeground2BrandSelected
   -- which is the purple face on a background you can see through. */
.chat__chip--add:hover,
.chat__chip--add:focus-visible {
  background-color: var(--colorTransparentBackground);
  border-color: var(--colorNeutralStroke2);
  color: var(--colorNeutralForeground2BrandHover);
}
.chat__chip--add:hover .icon-filled,
.chat__chip--add:focus-visible .icon-filled { display: inline; }
.chat__chip--add:hover .icon-regular,
.chat__chip--add:focus-visible .icon-regular { display: none; }
.chat__chip--add .icon-filled { color: var(--colorNeutralForeground2BrandSelected); }

/* -- the card behind a reaction chip ----------------------------------- */

/* Hovering a chip names the reaction and everyone who gave it. A card rather
   than a tooltip: it has faces in it, and its rows answer the pointer. */
.reaction-card {
  position: fixed;
  z-index: 1000;
  min-width: 16rem;
  max-width: 28rem;
  padding: var(--spacingVerticalS) 0;
  border: var(--strokeWidthThin) solid var(--colorTransparentStroke);
  border-radius: var(--borderRadiusMedium);
  background-color: var(--colorNeutralBackground1);
  box-shadow: var(--shadow16);
  color: var(--colorNeutralForeground1);
  cursor: default;
}
.reaction-card__title {
  padding: 0 var(--spacingHorizontalM) var(--spacingVerticalXS);
  font-size: var(--fontSizeBase200);
  line-height: var(--lineHeightBase200);
  color: var(--colorNeutralForeground2);
}
.reaction-card__person {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalS);
  padding: var(--spacingVerticalXS) var(--spacingHorizontalM);
  font-size: var(--fontSizeBase300);
  line-height: var(--lineHeightBase300);
}
/* The row the pointer is on lifts, as the reference's does. */
.reaction-card__person:hover { background-color: var(--colorSubtleBackgroundHover); }
.reaction-card__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* -- the hover toolbar ------------------------------------------------- */

/* One element, moved into whichever message the pointer is on -- floating over
   its top edge, anchored to the bubble column rather than to the panel's far
   edge, which is where the reference puts it. */
.chat__tools {
  position: absolute;
  /* Clear of the message it belongs to, overlapping the one above by 8px --
     the bar is 3.2rem tall, so -2.4rem leaves the hovered message readable
     underneath it. Sitting lower covered the very text you were reacting to. */
  top: -2.4rem;
  left: var(--gutter);
  z-index: 2;
  display: none;
  align-items: center;
  gap: 0.2rem;
  padding: 0.2rem;
  border-radius: var(--borderRadiusMedium);
  background-color: var(--colorNeutralBackground1);
  box-shadow: var(--shadow8);
}
.chat__msg--self .chat__tools { left: auto; right: 0; }
/* A message that opens a run carries its sender's name above the bubble, and
   the bar would land squarely on it. Lifted clear, so the name you are reading
   stays readable while you react to what is under it. */
.chat__msg--lead:not(.chat__msg--self) .chat__tools { top: -3rem; }
.chat__msg:hover .chat__tools,
.chat__msg:focus-within .chat__tools { display: flex; }

.chat__tools__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.8rem;
  height: 2.8rem;
  padding: 0;
  border: none;
  border-radius: var(--borderRadiusMedium);
  background: transparent;
  color: var(--colorNeutralForeground2);
  cursor: pointer;
}
.chat__tools__btn:hover { background-color: var(--colorNeutralBackground1Hover); }
.chat__tools__btn svg { width: 1.8rem; height: 1.8rem; }
.chat__tools__btn .reaction-sprite { width: 2rem; height: 2rem; }
.chat__tools__rule {
  width: 0.1rem;
  height: 1.8rem;
  margin: 0 0.2rem;
  background-color: var(--colorNeutralStroke2);
}

/* -- quoted replies ---------------------------------------------------- */

.chat__quote {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  margin-bottom: 0.4rem;
  padding-left: var(--spacingHorizontalS);
  border-left: 0.2rem solid var(--colorNeutralStroke1);
  font-size: var(--fontSizeBase200);
  line-height: var(--lineHeightBase200);
  color: var(--colorNeutralForeground3);
}
.chat__msg--self .chat__quote {
  border-left-color: var(--chatMyBubbleForeground);
  color: var(--chatMyBubbleForeground);
  opacity: 0.85;
}
.chat__quote__name { font-weight: var(--fontWeightSemibold); }
.chat__quote__text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.chat__reply-chip {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalS);
  padding: var(--spacingVerticalXS) var(--spacingHorizontalS);
  border-radius: var(--borderRadiusMedium);
  background-color: var(--colorNeutralBackground3);
  font-size: var(--fontSizeBase200);
}
.chat__reply-chip[hidden] { display: none; }
.chat__reply-chip__icon { display: flex; color: var(--colorNeutralForeground3); }
.chat__reply-chip__icon svg { width: 1.6rem; height: 1.6rem; }
.chat__reply-chip__body { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.chat__reply-chip__name { font-weight: var(--fontWeightSemibold); color: var(--colorNeutralForeground2); }
.chat__reply-chip__text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--colorNeutralForeground3);
}

/* -- attachments ------------------------------------------------------- */

.chat__file {
  display: block;
  min-width: 20rem;
  margin: 0.2rem 0;
  border-radius: var(--borderRadiusMedium);
  background-color: var(--colorNeutralBackground1);
  border: 0.1rem solid var(--colorNeutralStroke2);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
}
.chat__msg--self .chat__file { background-color: rgba(255, 255, 255, 0.14); border-color: transparent; }

/* A message that is only an attachment IS the card -- the bubble keeps its
   shape and colour and hands the whole of its inside over, rather than framing
   a second frame. */
.chat__msg__bubble--attachment { padding: 0; overflow: hidden; }
.chat__msg__bubble--attachment > .chat__file {
  margin: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  min-width: 22rem;
}
.chat__file__row {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalS);
  padding: var(--spacingVerticalS) var(--spacingHorizontalS);
}
.chat__file__icon { display: flex; color: var(--colorBrandForeground1); line-height: 0; }
.chat__msg--self .chat__file__icon { color: inherit; }
.chat__file__icon svg { width: 2rem; height: 2rem; }
.chat__file__text { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.chat__file__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fontSizeBase300);
}
.chat__file__size { font-size: var(--fontSizeBase200); color: var(--colorNeutralForeground3); }
.chat__msg--self .chat__file__size { color: inherit; opacity: 0.8; }
.chat__file__save {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.8rem;
  height: 2.8rem;
  flex: 0 0 auto;
  border: none;
  border-radius: var(--borderRadiusMedium);
  background: transparent;
  color: inherit;
  cursor: pointer;
}
.chat__file__save:hover { background-color: var(--colorNeutralBackground1Hover); }
.chat__file__save svg { width: 1.8rem; height: 1.8rem; }
.chat__file__preview { line-height: 0; }
/* Never upscaled. A 64px diagram blown up to the panel's width is a blurry
   band of nothing; a photo still fills the card. */
.chat__file__preview img {
  display: block;
  max-width: 100%;
  max-height: 24rem;
  width: auto;
  height: auto;
}
.chat__file__progress { padding: 0 var(--spacingHorizontalS) var(--spacingVerticalS); }
.chat__file__track {
  display: block;
  height: 0.2rem;
  border-radius: 0.1rem;
  background-color: var(--colorNeutralStroke2);
  overflow: hidden;
}
.chat__file__bar {
  display: block;
  width: 0;
  height: 100%;
  background-color: var(--colorBrandForeground1);
  transition: width var(--durationFaster) linear;
}
.chat__file__failed {
  display: block;
  padding: 0 var(--spacingHorizontalS) var(--spacingVerticalS);
  font-size: var(--fontSizeBase200);
  color: var(--colorStatusDangerForeground1);
}

.link-attach { display: flex; flex-direction: column; gap: var(--spacingVerticalS); padding: var(--spacingVerticalM); width: 32rem; }
.link-attach__hint { font-size: var(--fontSizeBase200); color: var(--colorNeutralForeground3); margin: 0; }
.link-attach__actions { display: flex; justify-content: flex-end; gap: var(--spacingHorizontalS); }

/* Compose is one bordered block: the text area on top, a row of controls
   along its bottom edge — the arrangement the reference uses, rather than a
   send button sitting inline with the text. */
.chat__compose { display: flex; flex-direction: column; gap: var(--spacingVerticalXS); }
.chat__compose__box {
  display: flex;
  flex-direction: column;
  border: 0.1rem solid var(--colorNeutralStroke1);
  border-radius: var(--borderRadiusMedium);
  background-color: var(--colorNeutralBackground1);
  overflow: hidden;
}
.chat__compose__box:focus-within { border-color: var(--colorCompoundBrandStroke); }
.chat__compose textarea {
  width: 100%;
  min-height: 4rem;
  max-height: 12rem;
  resize: none;
  border: 0;
  outline: none;
  background: transparent;
  padding: var(--spacingVerticalMNudge) var(--spacingHorizontalM) var(--spacingVerticalXS);
  font-size: var(--fontSizeBase300);
  line-height: var(--lineHeightBase300);
  color: var(--colorNeutralForeground1);
}
/* The whole group sits at the right-hand end of the box: the five tools, then
   a rule, then Send. The character count is the only thing on the left, and it
   pushes everything after it over. */
.chat__compose__tools {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--spacingHorizontalXXS);
  padding: var(--spacingVerticalXS) var(--spacingHorizontalXS);
}
.chat__compose__count { margin-right: auto; font-size: var(--fontSizeBase200); color: var(--colorNeutralForeground3); }
.chat__compose__count.is-over { color: var(--colorStatusDangerForeground1); }

.chat__tool {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.2rem;
  height: 3.2rem;
  padding: 0;
  border: 0;
  border-radius: var(--borderRadiusMedium);
  background: transparent;
  color: var(--colorNeutralForeground2);
  cursor: pointer;
}
/* Hover is colour and the outline→filled swap, with **no background plate**.
   That is the reference's own treatment: the emoji button under the pointer
   is a solid brand-coloured disc sitting on the same white as its neighbours,
   not a tinted pill. Sharing the `.btn` rule was not an option — a chat tool
   is not a `.btn`, which is exactly why these never swapped before. */
.chat__tool:hover, .chat__tool[aria-expanded='true'] { color: var(--colorNeutralForeground2BrandHover); }
.chat__tool:active { color: var(--colorNeutralForeground2BrandPressed); }
.chat__tool svg { width: 2rem; height: 2rem; }
.chat__tool .icon-filled { display: none; }
.chat__tool:hover > .btn__icon > .icon-filled,
.chat__tool:active > .btn__icon > .icon-filled,
.chat__tool[aria-expanded='true'] > .btn__icon > .icon-filled { display: inline; }
.chat__tool:hover > .btn__icon > .icon-regular,
.chat__tool:active > .btn__icon > .icon-regular,
.chat__tool[aria-expanded='true'] > .btn__icon > .icon-regular { display: none; }

/* --------------------------------------------------------------------- */
/* Camera flyout and Video effects and settings                           */
/* --------------------------------------------------------------------- */

/* Built on `.menu` so it inherits the popover surface, then sized like the
   reference's: a fixed-width column with a live preview at the top. */
.fx-panel {
  display: flex;
  flex-direction: column;
  width: 36rem;
  max-width: calc(100vw - 2.4rem);
  padding: var(--spacingVerticalS);
  gap: var(--spacingVerticalXS);
}

.fx-preview {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--borderRadiusMedium);
  background-color: var(--colorNeutralBackgroundInverted);
  overflow: hidden;
}
/* `object-fit: cover` and not contain: the preview is a crop of what is being
   sent, and letterboxing it would suggest black bars nobody else sees. */
.fx-preview__video { width: 100%; height: 100%; object-fit: cover; display: block; }
.fx-preview[data-empty='true'] .fx-preview__video { display: none; }
.fx-preview[data-empty='true']::after {
  content: 'Your camera is off';
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--colorNeutralForegroundStaticInverted);
  font-size: var(--fontSizeBase200);
}

.fx-section { display: flex; flex-direction: column; gap: var(--spacingVerticalXXS); }
.fx-section__title,
.fx-group__title {
  padding: var(--spacingVerticalXS) var(--spacingHorizontalS) 0;
  color: var(--colorNeutralForeground3);
  font-size: var(--fontSizeBase200);
  font-weight: var(--fontWeightSemibold);
}
.fx-section__body { display: flex; flex-direction: column; }
.fx-none { padding: var(--spacingVerticalS) var(--spacingHorizontalS); color: var(--colorNeutralForeground3); font-size: var(--fontSizeBase200); }

.fx-radio {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalS);
  width: 100%;
  min-height: 3.2rem;
  padding: var(--spacingVerticalXS) var(--spacingHorizontalS);
  border: 0;
  border-radius: var(--borderRadiusMedium);
  background: transparent;
  color: var(--colorNeutralForeground1);
  font: inherit;
  font-size: var(--fontSizeBase300);
  text-align: left;
  cursor: pointer;
}
.fx-radio:hover { background-color: var(--colorSubtleBackgroundHover); }
.fx-radio__label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The dot lives in base.css with the menu rows; `aria-checked` is on the row
   there and on the row here, so the same rule lights both. */
.fx-radio[aria-checked='true'] .menu__item__radio { border-color: var(--colorCompoundBrandStroke); }
.fx-radio[aria-checked='true'] .menu__item__radio::after {
  content: '';
  position: absolute;
  inset: 0.3rem;
  border-radius: var(--borderRadiusCircular);
  background-color: var(--colorCompoundBrandForeground1);
}

/* A row that is a switch: label on the left, dot on the right. */
.fx-row {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalS);
  width: 100%;
  min-height: 4rem;
  padding: var(--spacingVerticalXS) var(--spacingHorizontalS);
  border: 0;
  border-radius: var(--borderRadiusMedium);
  background: transparent;
  color: var(--colorNeutralForeground1);
  font: inherit;
  font-size: var(--fontSizeBase300);
  text-align: left;
  cursor: pointer;
}
.fx-row:hover { background-color: var(--colorSubtleBackgroundHover); }
.fx-row__label { flex: 1 1 auto; min-width: 0; }
.fx-row__info { display: flex; flex: none; color: var(--colorNeutralForeground3); }
.fx-row__info svg { width: 1.6rem; height: 1.6rem; }

/* Fluent's switch track and thumb, sized as the reference has them. */
.switch-dot {
  position: relative;
  flex: none;
  width: 4rem;
  height: 2rem;
  border-radius: var(--borderRadiusCircular);
  background-color: var(--colorNeutralBackground5);
  border: 0.1rem solid var(--colorNeutralStrokeAccessible);
  transition: background-color var(--durationNormal) var(--curveEasyEase);
}
.switch-dot::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 0.3rem;
  width: 1.2rem;
  height: 1.2rem;
  margin-top: -0.6rem;
  border-radius: var(--borderRadiusCircular);
  background-color: var(--colorNeutralStrokeAccessible);
  transition: transform var(--durationNormal) var(--curveEasyEase);
}
[aria-checked='true'] > .switch-dot {
  background-color: var(--colorCompoundBrandBackground);
  border-color: transparent;
}
[aria-checked='true'] > .switch-dot::after {
  background-color: var(--colorNeutralForegroundOnBrand);
  transform: translateX(1.8rem);
}

.fx-link {
  width: 100%;
  padding: var(--spacingVerticalS) var(--spacingHorizontalS);
  border: 0;
  border-radius: var(--borderRadiusMedium);
  background: transparent;
  color: var(--colorNeutralForeground1);
  font: inherit;
  font-size: var(--fontSizeBase300);
  text-align: left;
  cursor: pointer;
}
.fx-link:hover { background-color: var(--colorSubtleBackgroundHover); }

/* The effects strip in the flyout and the grid in the dialog are the same
   tile at two densities, so the tile is styled once. */
.fx-strip { display: flex; gap: var(--spacingHorizontalXS); padding: 0 var(--spacingHorizontalS); }
.fx-strip > .fx-tile { flex: 1 1 0; min-width: 0; }
/* Four tiles in a flyout leave each one about 7rem wide, and "Standard blur"
   does not fit that at body size — the reference sets it smaller here than in
   the dialog rather than truncating it. */
.fx-strip .fx-tile__label { font-size: var(--fontSizeBase100); }
.fx-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: var(--spacingHorizontalS);
}

.fx-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.2rem;
  aspect-ratio: 16 / 10;
  padding: var(--spacingVerticalXS);
  border: 0.2rem solid transparent;
  border-radius: var(--borderRadiusMedium);
  background-color: var(--colorNeutralBackground3);
  background-size: cover;
  background-position: center;
  color: var(--colorNeutralForeground2);
  font: inherit;
  font-size: var(--fontSizeBase200);
  cursor: pointer;
  overflow: hidden;
}
.fx-tile:hover { background-color: var(--colorNeutralBackground3Hover); }
.fx-tile[aria-pressed='true'] { border-color: var(--colorCompoundBrandStroke); }
.fx-tile__icon { display: flex; line-height: 0; }
.fx-tile__icon svg { width: 2rem; height: 2rem; }
.fx-tile__label { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Tick and chevron sit in opposite top corners, over the artwork. On an image
   tile they need their own plate to stay legible against whatever is behind
   them. */
.fx-tile__check,
.fx-tile__chevron {
  position: absolute;
  top: 0.2rem;
  display: none;
  line-height: 0;
  border-radius: var(--borderRadiusSmall);
  color: var(--colorNeutralForeground2);
}
.fx-tile__check { right: 0.2rem; }
.fx-tile__chevron { display: flex; right: 0.2rem; }
.fx-tile[aria-pressed='true'] .fx-tile__check { display: flex; }
/* The blur tile is the only one that can show both, so it is the only one
   that has to move its tick out of the chevron's corner. */
.fx-tile:has(.fx-tile__chevron) .fx-tile__check { right: auto; left: 0.2rem; }
.fx-tile__check svg, .fx-tile__chevron svg { width: 1.4rem; height: 1.4rem; }
.fx-tile--image .fx-tile__check,
.fx-tile--image .fx-tile__chevron {
  padding: 0.2rem;
  background-color: rgba(255, 255, 255, 0.85);
  color: #242424;
}

.fx-dialog { display: flex; flex-direction: column; min-height: 0; }

/* In the rail the surface fills the panel rather than sizing itself: the
   panel's own body does the scrolling, so the dialog's max-height would cap it
   partway down. Thumbnails get a smaller floor too -- the rail can be a third
   of a dialog's width, and 11rem there leaves a single column. */
.fx-panel .panel__body { padding: 0 var(--spacingHorizontalM) var(--spacingVerticalM); }
.fx-panel .fx-dialog { height: 100%; }
.fx-panel .fx-dialog__body { max-height: none; overflow-y: visible; }
.fx-panel .fx-grid { grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); }
.fx-panel .panel__header { padding-bottom: 0; }
.fx-dialog__body { overflow-y: auto; max-height: min(58rem, 60vh); padding-top: var(--spacingVerticalM); }
.fx-tabs { display: flex; gap: var(--spacingHorizontalL); border-bottom: 0.1rem solid var(--colorNeutralStroke2); }
.fx-tab {
  padding: var(--spacingVerticalS) 0;
  border: 0;
  border-bottom: 0.2rem solid transparent;
  background: transparent;
  color: var(--colorNeutralForeground2);
  font: inherit;
  font-size: var(--fontSizeBase300);
  cursor: pointer;
}
.fx-tab:hover { color: var(--colorNeutralForeground1); }
.fx-tab[aria-selected='true'] {
  color: var(--colorBrandForeground1);
  border-bottom-color: var(--colorCompoundBrandStroke);
  font-weight: var(--fontWeightSemibold);
}
.fx-group { margin-bottom: var(--spacingVerticalL); }
.fx-note {
  margin: var(--spacingVerticalM) 0 0;
  color: var(--colorNeutralForeground3);
  font-size: var(--fontSizeBase200);
  max-width: 60ch;
}

/* Mirror my video. Your own view only — the published frames are untouched,
   which is the whole point of doing it in CSS. */
:root[data-mirror='on'] .self-pip .tile__video,
:root[data-mirror='on'] .tile--self .tile__video,
:root[data-mirror='on'] .fx-preview__video,
:root[data-mirror='on'] .preview__video { transform: scaleX(-1); }

/* --------------------------------------------------------------------- */
/* Emoji picker                                                           */
/* --------------------------------------------------------------------- */

/* Built on `.menu`, so it inherits the popover surface, and sized to the
   reference's: a fixed 32rem box that scrolls internally rather than one that
   grows with the category you happen to be looking at. */
.emoji {
  display: flex;
  flex-direction: column;
  width: 32rem;
  max-width: calc(100vw - 2.4rem);
  height: 40rem;
  max-height: calc(100vh - 12rem);
  padding: 0;
  overflow: hidden;
}

.emoji__tabs {
  display: flex;
  align-items: stretch;
  flex: none;
  border-bottom: 0.1rem solid var(--colorNeutralStroke2);
}
.emoji__tab {
  flex: 1 1 0;
  padding: var(--spacingVerticalM) var(--spacingHorizontalS);
  border: 0;
  border-bottom: 0.2rem solid transparent;
  background: transparent;
  color: var(--colorNeutralForeground2);
  font: inherit;
  font-size: var(--fontSizeBase300);
  cursor: pointer;
}
.emoji__tab:hover { color: var(--colorNeutralForeground1); }
.emoji__tab[aria-selected='true'] {
  color: var(--colorBrandForeground1);
  border-bottom-color: var(--colorCompoundBrandStroke);
  font-weight: var(--fontWeightSemibold);
}

.emoji__search {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalS);
  flex: none;
  padding: var(--spacingVerticalS) var(--spacingHorizontalM);
}
.emoji__search__icon,
.emoji__custom {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  color: var(--colorNeutralForeground3);
}
.emoji__search__icon { order: 2; margin-left: -3.2rem; pointer-events: none; }
.emoji__search__input {
  order: 1;
  flex: 1 1 auto;
  min-width: 0;
  height: 3.2rem;
  padding: 0 3.2rem 0 var(--spacingHorizontalM);
  border: 0.1rem solid transparent;
  border-radius: var(--borderRadiusMedium);
  background-color: var(--colorNeutralBackground3);
  color: var(--colorNeutralForeground1);
  font: inherit;
  font-size: var(--fontSizeBase300);
}
.emoji__search__input:focus { outline: none; border-color: var(--colorCompoundBrandStroke); }
/* Chrome draws its own clear button in a search field; the surface already
   has one way to empty it, and two is one too many. */
.emoji__search__input::-webkit-search-cancel-button { display: none; }
.emoji__custom {
  order: 3;
  width: 3.2rem;
  height: 3.2rem;
  border: 0;
  border-radius: var(--borderRadiusMedium);
  background: transparent;
  cursor: pointer;
}
.emoji__custom:hover { background-color: var(--colorSubtleBackgroundHover); color: var(--colorNeutralForeground2BrandHover); }
.emoji__custom svg { width: 2rem; height: 2rem; }

.emoji__grid { flex: 1 1 auto; min-height: 0; padding: 0 var(--spacingHorizontalM) var(--spacingVerticalS); }
.emoji__heading {
  padding: var(--spacingVerticalS) 0 var(--spacingVerticalXS);
  color: var(--colorNeutralForeground3);
  font-size: var(--fontSizeBase200);
  font-weight: var(--fontWeightSemibold);
}
/* auto-fill, not a fixed count: the box has one width here, but the phone
   layout narrows it and a hardcoded six would overflow rather than reflow. */
.emoji__row {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(3.6rem, 1fr));
  gap: 0.2rem;
}
.emoji__cell {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1;
  padding: 0;
  border: 0;
  border-radius: var(--borderRadiusMedium);
  background: transparent;
  /* The system emoji font first, so Windows draws Segoe UI Emoji — which is
     Fluent Emoji — rather than falling back to a monochrome outline. */
  font-family: "Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji", sans-serif;
  font-size: 2.2rem;
  line-height: 1;
  cursor: pointer;
}
.emoji__cell:hover { background-color: var(--colorSubtleBackgroundHover); }
.emoji__empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spacingVerticalXS);
  height: 100%;
  padding: var(--spacingVerticalXXL) var(--spacingHorizontalL);
  color: var(--colorNeutralForeground2);
  text-align: center;
}

.emoji__rail {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex: none;
  gap: 0.2rem;
  padding: var(--spacingVerticalXS) var(--spacingHorizontalS);
  border-top: 0.1rem solid var(--colorNeutralStroke2);
}
.emoji__rail__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3.2rem;
  height: 3.2rem;
  border: 0;
  border-radius: var(--borderRadiusMedium);
  background: transparent;
  color: var(--colorNeutralForeground3);
  cursor: pointer;
}
.emoji__rail__btn svg { width: 2rem; height: 2rem; }
.emoji__rail__btn:hover { background-color: var(--colorSubtleBackgroundHover); color: var(--colorNeutralForeground2); }
.emoji__rail__btn[aria-selected='true'] { color: var(--colorBrandForeground1); }

/* --------------------------------------------------------------------- */
/* Actions and apps                                                       */
/* --------------------------------------------------------------------- */

.actions { width: 30rem; max-width: calc(100vw - 2.4rem); padding: var(--spacingVerticalS); }
.actions__search { display: flex; align-items: center; margin-bottom: var(--spacingVerticalXS); }
.actions__search__input {
  flex: 1 1 auto;
  min-width: 0;
  height: 3.2rem;
  padding: 0 3.2rem 0 var(--spacingHorizontalS);
  border: 0;
  border-bottom: 0.1rem solid var(--colorNeutralStroke1);
  background: transparent;
  color: var(--colorNeutralForeground1);
  font: inherit;
  font-size: var(--fontSizeBase300);
}
.actions__search__input:focus { outline: none; border-bottom-color: var(--colorCompoundBrandStroke); }
.actions__search__input::-webkit-search-cancel-button { display: none; }
.actions__search__icon { margin-left: -2.6rem; color: var(--colorNeutralForeground3); pointer-events: none; }
.actions__none { padding: var(--spacingVerticalM); color: var(--colorNeutralForeground3); font-size: var(--fontSizeBase200); }

/* --------------------------------------------------------------------- */
/* Delivery options                                                       */
/* --------------------------------------------------------------------- */

.chat__priority {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalXS);
  padding: var(--spacingVerticalXS) var(--spacingHorizontalS);
  border-radius: var(--borderRadiusMedium);
  background-color: var(--colorNeutralBackground3);
  color: var(--colorNeutralForeground2);
  font-size: var(--fontSizeBase200);
}
.chat__priority__icon { display: flex; color: var(--colorStatusDangerForeground1); }
.chat__priority__icon svg { width: 1.4rem; height: 1.4rem; }
.chat__priority .link { margin-left: auto; }

/* Teams marks the message itself, not just the composer: an urgent one that
   looked ordinary once sent would make the option pointless. */
.chat__msg__priority {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: var(--spacingVerticalXXS);
  color: var(--colorStatusDangerForeground1);
  font-size: var(--fontSizeBase100);
  font-weight: var(--fontWeightSemibold);
  letter-spacing: 0.03em;
}
.chat__msg__priority__icon { display: flex; }
.chat__msg__priority__icon svg { width: 1.2rem; height: 1.2rem; }
.chat__msg[data-priority] .chat__msg__bubble { border-color: var(--colorStatusDangerBorder1); }

/* --------------------------------------------------------------------- */
/* Loop component card                                                    */
/* --------------------------------------------------------------------- */

/* The reference sizes its host at 32rem and lets the iframe fill it. There is
   no iframe here — see the note in chat.js — so the card is the same box with
   the fields drawn directly into it. */
.loop {
  display: flex;
  flex-direction: column;
  gap: var(--spacingVerticalS);
  height: 32rem;
  margin: 0 var(--spacingHorizontalS) var(--spacingVerticalS);
  padding: var(--spacingVerticalM) var(--spacingHorizontalM);
  border: 0.1rem solid var(--colorNeutralStroke2);
  border-radius: var(--borderRadiusXLarge);
  background-color: var(--colorNeutralBackground1);
}
.loop[hidden] { display: none; }

.loop__head { display: flex; align-items: center; gap: var(--spacingHorizontalS); }
.loop__mark { display: flex; line-height: 0; color: #7b68ee; }
.loop__mark svg { width: 2rem; height: 2rem; }
.loop__kind {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--fontSizeBase300);
  font-weight: var(--fontWeightSemibold);
  color: var(--colorNeutralForeground1);
}
.loop__remove {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 3.2rem;
  height: 3.2rem;
  border: 0;
  border-radius: var(--borderRadiusMedium);
  background: transparent;
  color: var(--colorNeutralForeground2);
  cursor: pointer;
}
.loop__remove:hover { background-color: var(--colorSubtleBackgroundHover); }
.loop__remove svg { width: 2rem; height: 2rem; }

.loop__title,
.loop__body {
  width: 100%;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--colorNeutralForeground1);
  font: inherit;
  padding: 0;
}
.loop__title { font-size: var(--fontSizeBase500); font-weight: var(--fontWeightSemibold); }
.loop__body {
  flex: 1 1 auto;
  min-height: 0;
  resize: none;
  font-size: var(--fontSizeBase300);
  line-height: var(--lineHeightBase300);
}
/* The reference's placeholder is a paragraph of instructions, so it wraps and
   sits in the brand colour rather than the usual grey. */
.loop__body::placeholder { color: var(--colorBrandForeground2); }

.loop__templates { display: flex; align-items: center; gap: var(--spacingHorizontalS); flex-wrap: wrap; }
.loop__template {
  display: inline-flex;
  align-items: center;
  gap: var(--spacingHorizontalXS);
  height: 3.2rem;
  padding: 0 var(--spacingHorizontalM);
  border: 0.1rem solid var(--colorNeutralStroke1);
  border-radius: var(--borderRadiusMedium);
  background-color: var(--colorNeutralBackground1);
  color: var(--colorNeutralForeground1);
  font: inherit;
  font-size: var(--fontSizeBase300);
  cursor: pointer;
}
.loop__template:hover { background-color: var(--colorSubtleBackgroundHover); }
.loop__template svg { width: 2rem; height: 2rem; }
.loop__template--more { padding: 0 var(--spacingHorizontalS); }

/* The rule before Send. */
.chat__compose__rule {
  width: 1px;
  height: 2rem;
  flex-shrink: 0;
  margin: 0 var(--spacingHorizontalXS);
  background-color: var(--colorNeutralStroke2);
}

/* --------------------------------------------------------------------- */
/* Reactions                                                              */
/* --------------------------------------------------------------------- */

.reaction-bar {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalXS);
  padding: var(--spacingVerticalXS);
}
.reaction-bar button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 4rem; height: 4rem;
  min-width: 3.2rem;
  padding: 0.3rem;
  line-height: 1;
  border: 0;
  /* A rounded rectangle, not a circle. A circular highlight has to be as wide
     as the emoji is tall to enclose it, so it always looks larger than the
     picture inside it -- which is what made these read as oversized. */
  border-radius: var(--borderRadiusMedium);
  background: transparent;
  cursor: pointer;
  transition: transform var(--durationFaster) var(--curveEasyEase);
}
/* The strip is 51 frames tall and is clipped by `.reaction-sprite`; forcing
   height here would squash all 51 into one box, so the *width* is what sizes
   it. 2.4rem in a 4rem cell, not the full 3.4rem the padding left it: the
   reference's emoji sit in their cells with air around them, and filling the
   cell is what kept these reading as oversized. */
.reaction-bar button .reaction-sprite { width: 2.4rem; }
.reaction-bar button:hover { background-color: var(--colorSubtleBackgroundHover); }

.reaction-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 4;
}
/* A reaction sits on its sender's own tile, over the lower part of their
   avatar, and animates the whole time it is up. Rising emoji in shared space
   tell you *somebody* reacted and leave you to work out who; on the tile the
   attribution is the position, so nobody has to read a name.

   Sized and placed off the reference: its heart measures about 23% of the
   tile height and sits at roughly 68% down, which puts it across the bottom
   of the avatar rather than beside the nametag. */
/* The tile dims under a reaction rather than lighting up. The brand-coloured
   edge that used to be here was wrong -- the reference shows no highlight at
   all, and the tile behind a landing reaction reads darker, not brighter. The
   scrim rides on the hairline layer, which already spans the tile, and sits
   under the nametag and the emoji because both are positioned above it. */
/* `--reactionScrim`, and its own token rather than Fluent's
   `--colorBackgroundOverlay`, which also dims the page behind every dialog --
   a darker reaction must not drag the dialogs down with it.
   The value is a judgement call and cannot be anything else: nobody reacted
   while the pages were saved and the in-call stage chunk was never fetched, so
   the capture has no reaction scrim in it at all. It started at Fluent's .4/.5
   and is now a step past that, which is where it reads like the real client. */
.tile[data-reacting='true']::after {
  background-color: var(--reactionScrim);
}
.tile::after { transition: background-color 220ms var(--curveEasyEase); }

.reaction-pop {
  position: absolute;
  left: 50%;
  top: 68%;
  /* No measurement backs the exact number: nobody reacted during the capture,
     so there is no reaction overlay in it to measure. Set by eye against the
     reference -- once tuned down from 23cqh/6.4rem for reading too large, now
     back up a step because it was reading too small on a full stage. It is the
     one value here worth re-checking against a live meeting. */
  width: clamp(3.2rem, 20cqh, 6rem);
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 4;
  /* 4.25s, matching the reference's own envelope, and the container no longer
     fades: the opacity is on the image, in Teams' own keyframe. All this does
     now is carry the reaction up and away, which is the one thing the
     reference does that a tile-anchored reaction still needs. */
  animation: reaction-in 4.25s linear forwards;
  will-change: transform;
}

/* The rise. The scale-bounce that used to be here was ours -- the reference
   floats its reactions with `translateY() scale()` and no overshoot, and the
   emoji's own artwork is where the liveliness comes from. */
@keyframes reaction-in {
  0%   { transform: translate(-50%, -50%) scale(0.5); }
  31%  { transform: translate(-50%, -50%) scale(1); }
  100% { transform: translate(-50%, calc(-50% - 2.4rem)) scale(1); }
}

/* The reaction plays its own artwork: Teams ships each one as a vertical
   sprite strip of 51 frames, and the animation IS the emoji rather than a
   transform applied to a still picture. That is why the synthetic shake that
   used to live here is gone -- it was standing in for an animation we did not
   have, and having the real one makes it a second, competing motion.

   Translating the image by its own full height under steps(51) lands on
   frames 0..50 and never on the wrap, which is what the reference does with
   `steps(51)` and `--to-position: -1632px` (51 x 32px at its size). Its stated
   duration is 2.125s; one pass fits inside the 3s the reaction is on screen.

   Deliberately NOT behind `prefers-reduced-motion: no-preference`: Windows
   reports client-area animations off for the whole of any Remote Desktop
   session whatever the Settings toggle says, and Chrome maps that to `reduce`,
   so gating it there left anyone on RDP with a reaction that never moved. The
   reduced-motion block below takes the *travel* away instead -- that is the
   motion the preference is about -- and holds the emoji on a single frame. */
.reaction-sprite {
  display: block;
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: hidden;
}
.reaction-sprite img {
  display: block;
  width: 100%;
  height: auto;      /* 51x the width; the window above clips it to one frame */
  max-width: none;
  will-change: transform;
}
/* TWO passes and then stop, not an endless loop.
 *
 * This is the reference's own composition, read out of the bundle rather than
 * guessed. A meeting reaction there is one element carrying two animations:
 *
 *   animation-name:            f1fwuz3n, fu1n2gc
 *   animation-duration:        2.125s, 4.25s
 *   animation-timing-function: steps(51), linear
 *   animation-iteration-count: 2, 1
 *
 * -- the strip scrubbed twice under steps(51), and a single opacity envelope
 * over the whole 4.25s. `animation-fill-mode` is never set on any reaction
 * class in the capture, so it computes to `none`.
 *
 * The envelope's stops are theirs too:
 *   @keyframes fu1n2gc {0%{opacity:0}15.68%{opacity:0}31.37%{opacity:1}
 *                       81.37%{opacity:1}100%{opacity:0}}
 * which at 4.25s is 667ms invisible, 667ms fading in, 2.1s held, 794ms out.
 * `infinite` was ours, and so was the bespoke bounce that used to sit on the
 * container. */
.reaction-pop .reaction-sprite img {
  animation:
    reaction-frames 2.125s steps(51) 2,
    reaction-envelope 4.25s linear 1;
}
@keyframes reaction-frames {
  from { transform: translateY(0); }
  to   { transform: translateY(-100%); }
}
@keyframes reaction-envelope {
  0%     { opacity: 0; }
  15.68% { opacity: 0; }
  31.37% { opacity: 1; }
  81.37% { opacity: 1; }
  100%   { opacity: 0; }
}

/* The shadow the reference puts around a reaction. `.fm8j1vx` --
   `text-shadow: 0 0 0.1rem black` -- sits in the floating-reaction group in
   griffel, where it decorates a unicode glyph; the equivalent for a sprite
   image is a drop-shadow filter of the same radius and colour. Attribution is
   by griffel source-order adjacency rather than by a class on a captured
   element, because no reaction was ever rendered into these pages. */
.reaction-pop .reaction-sprite img { filter: drop-shadow(0 0 0.1rem black); }

/* In the picker the emoji rests on its first frame and plays while hovered,
   which is how the reference behaves -- its inline style sits at
   `--reaction-animation-iteration-count: 0` until pointed at. */
.reaction-bar button:hover .reaction-sprite img,
.reaction-bar button:focus-visible .reaction-sprite img {
  animation: reaction-frames 2.125s steps(51) infinite both;
}

/* Reduced motion takes away the travel — the reaction stops flying in and
   drifting out, and holds still while it fades — and stops the strip, which
   holds the emoji on its first frame. That is a real picture, not a blank:
   frame 0 of each strip is the emoji at rest. */
@media screen and (prefers-reduced-motion: reduce) {
  .reaction-pop { animation: reaction-hold 4.25s steps(1) forwards; }
  @keyframes reaction-hold { 0%, 90% { opacity: 1; } 100% { opacity: 0; } }

  /* The strip stops, and with it the opacity envelope that rides on the same
     element -- so the hold above is what makes the emoji visible at all. */
  .reaction-pop .reaction-sprite img,
  .reaction-bar button:hover .reaction-sprite img,
  .reaction-bar button:focus-visible .reaction-sprite img {
    animation: none;
  }
}

/* --------------------------------------------------------------------- */
/* Banners over the stage — reconnecting, autoplay blocked, unmute request */
/* --------------------------------------------------------------------- */

.stage__banners {
  position: absolute;
  top: var(--spacingVerticalM);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  gap: var(--spacingVerticalS);
  z-index: 5;
  max-width: min(60rem, calc(100% - 3.2rem));
}
.stage__banner {
  display: flex;
  align-items: center;
  gap: var(--spacingHorizontalS);
  padding: var(--spacingVerticalS) var(--spacingHorizontalL);
  background-color: var(--colorNeutralBackground1);
  border-radius: var(--borderRadiusCircular);
  box-shadow: var(--shadow16);
  font-size: var(--fontSizeBase300);
  color: var(--colorNeutralForeground1);
}
.stage__banner .btn { min-width: 0; padding: 2px var(--spacingHorizontalM); }

/* --------------------------------------------------------------------- */
/* End screen                                                             */
/* --------------------------------------------------------------------- */

.ended {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spacingVerticalL);
  height: 100%;
  padding: var(--spacingVerticalXXXL);
  text-align: center;
  background-color: var(--colorNeutralBackground3);
}
.ended__title { font-size: var(--fontSizeHero700); line-height: var(--lineHeightHero700); font-weight: var(--fontWeightSemibold); }
.ended__sub { font-size: var(--fontSizeBase400); color: var(--colorNeutralForeground2); max-width: 48rem; }
.ended__actions { display: flex; gap: var(--spacingHorizontalS); }

/* The mark, wherever a screen puts one.
   Both dimensions, on the image itself. `meetingGlyph()` is a 256x256 <img>
   with its size in attributes and base.css caps images at `max-width: 100%`:
   a box that sets only its own width leaves the height attribute standing, and
   the mark draws 4.4rem wide by 256px tall straight through the heading below
   it. */
.app-mark { flex: none; width: 4.4rem; height: 4.4rem; }
.app-mark img { display: block; width: 100%; height: 100%; object-fit: contain; }

/* --------------------------------------------------------------------- */
/* Handheld notice — the whole screen on a phone or tablet                */
/* --------------------------------------------------------------------- */

/* Scrolls, because `.shell__main` clips and this is the one screen guaranteed
   to meet a 320x420 landscape phone. Everything else here is one centred
   column that gives way in a fixed order as the window shrinks. */
.handheld {
  height: 100%;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background-color: var(--colorNeutralBackground3);
}

.handheld__inner {
  box-sizing: border-box;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: var(--spacingVerticalL);
  text-align: center;
  /* The insets are why index.html asks for `viewport-fit=cover`: without this
     the button sits under the iPhone's home indicator. */
  padding: calc(var(--spacingVerticalXXL) + env(safe-area-inset-top))
           calc(2.4rem + env(safe-area-inset-right))
           calc(var(--spacingVerticalXXL) + env(safe-area-inset-bottom))
           calc(2.4rem + env(safe-area-inset-left));
}

/* Centred by auto margins rather than `justify-content: center`, which is not
   interchangeable here. Centring a *scroll container* puts the overflow at the
   start, where the scroll range cannot reach it — the top of the column just
   disappears — and a short landscape phone is exactly where that bites. Auto
   margins resolve to zero when there is no free space, so the column centres
   when it fits and falls back to the top when it does not. */
.handheld__inner::before,
.handheld__inner::after { content: ''; margin-block: auto; }

/* Fluid rather than a token. Hero700 is 28px, which runs to four lines on a
   320px phone and leaves no room for anything under it; the ceiling is that
   same token, so a tablet still gets the size the rest of the app uses. */
.handheld__title {
  font-size: clamp(2rem, 6.4vw, var(--fontSizeHero700));
  line-height: 1.25;
  font-weight: var(--fontWeightSemibold);
  text-wrap: balance;
  max-width: 22ch;
}

.handheld__sub {
  font-size: var(--fontSizeBase400);
  line-height: var(--lineHeightBase400);
  color: var(--colorNeutralForeground2);
  max-width: 40ch;
}

.handheld__actions {
  display: flex;
  flex-direction: column;
  gap: var(--spacingVerticalS);
  width: 100%;
  max-width: 32rem;
}
/* 44px is the smallest target a thumb reliably hits; the app's 32px buttons
   are sized for a pointer. */
.handheld__action { width: 100%; min-height: 4.4rem; }

.handheld__hint {
  font-size: var(--fontSizeBase200);
  line-height: var(--lineHeightBase200);
  color: var(--colorNeutralForeground3);
  max-width: 36ch;
}

/* From a tablet up there is room for the button to take its natural width. */
@media (min-width: 600px) {
  .handheld__inner { gap: var(--spacingVerticalXL); }
  .handheld__actions { flex-direction: row; justify-content: center; width: auto; }
  .handheld__action { width: auto; min-width: 16rem; }
  .app-mark { width: 6.4rem; height: 6.4rem; }
}

/* A phone held sideways has about 320px of height for all of this. The mark is
   the first thing to go — it is the only element carrying no information — and
   the column packs to the top so the button is one short scroll away rather
   than centred off-screen. */
@media (max-height: 460px) and (orientation: landscape) {
  .handheld__inner {
    gap: var(--spacingVerticalS);
    padding-top: calc(var(--spacingVerticalL) + env(safe-area-inset-top));
    padding-bottom: calc(var(--spacingVerticalL) + env(safe-area-inset-bottom));
  }
  .handheld .app-mark { display: none; }
  .handheld__title { font-size: 2rem; }
  .handheld__sub { font-size: var(--fontSizeBase300); line-height: var(--lineHeightBase300); }
}

/* --------------------------------------------------------------------- */
/* Tablet and phone                                                       */
/* --------------------------------------------------------------------- */

/* Tablet: the side panel would leave the stage too narrow to be useful, so
   it covers it instead. */
@media (max-width: 1000px) {
  .panel {
    position: absolute;
    inset: 0;
    width: auto;
    flex: none;
    margin: 0;
    border-radius: 0;
    z-index: 6;
  }
}

/* Below this the labels stop fitting under 4.8rem controls. */
@media (max-width: 860px) {
  .ctrl__label { display: none; }
  .ctrl { min-width: 4.4rem; max-width: 4.4rem; height: 4.4rem; }
  .ubar { padding: 0 var(--spacingHorizontalXS); }
}

@media (max-width: 640px) {
  /* The control row is the one thing that must never wrap or overflow the
     viewport, so it scrolls horizontally on its own if it has to. */
  .ubar {
    grid-template-areas: 'indicators middle end';
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: 0;
  }
  .ubar__middle { overflow-x: auto; justify-content: flex-start; scrollbar-width: none; }
  .ubar__middle::-webkit-scrollbar { display: none; }
  .ubar__duration { display: none; }
  .ubar__indicators { gap: var(--spacingHorizontalXXS); }
  /* The vertical margin is not slack. `overflow-x: auto` above makes this a
     scroll container on BOTH axes -- per CSS Overflow 3 a visible `overflow-y`
     computes to `auto` when the other axis is not visible -- so it clips at its
     padding box. The indicator hangs ~4px below each button, and with a zero
     vertical margin it lands outside that box and is silently cut off, the
     scrollbar it creates being hidden by the rule above. 4.4rem + 12px still
     fits inside --ubarHeight. */
  .ctrl { margin: 6px 2px; }

  /* Chevron flyouts are too small to hit reliably on a phone; the control's
     own long-press/menu covers the same ground. */
  .ctrl-group .ctrl--chevron { display: none; }
  .ctrl-group .ctrl--split { border-radius: var(--borderRadiusMedium); }

  .stage { padding: var(--spacingVerticalXS); }
  .stage--shared .stage__grid { height: 8rem; flex-basis: 8rem; }

  .tile__nametag { height: 2rem; font-size: var(--fontSizeBase100); }
  .reaction-fly { font-size: 26px; }

  .stage__banners { max-width: calc(100% - 1.6rem); }
  .stage__banner { padding: var(--spacingVerticalXS) var(--spacingHorizontalM); font-size: var(--fontSizeBase200); }
}

/* Portrait phone: a row of 16:9 tiles is unusable, so the grid goes to a
   single column. Six tiles will not fit the height, so the stage scrolls
   rather than squeezing them — clipped faces are worse than a scrollbar.
   `--cols` is set by layoutGrid() at runtime, hence the override. */
@media (max-width: 560px) and (orientation: portrait) {
  .stage { align-items: flex-start; overflow-y: auto; }
  .stage__grid {
    grid-template-columns: minmax(0, 1fr) !important;
    height: auto;
    align-content: start;
    gap: var(--spacingVerticalXS);
  }
  .tile { aspect-ratio: 16 / 9; }
  .stage--shared { overflow: hidden; }
  .stage--shared .stage__grid { height: 8rem; }
}

@media (pointer: coarse) {
  .ctrl { min-height: 4.4rem; }
  .roster__row { min-height: 4.8rem; }
  .tile__menu-btn { opacity: 1; }
}
