/* Koda Meet — companies, admin, emoji.
   Added with the multi-company work, and kept in its own file because it is
   a whole new surface rather than a tweak to an existing one. Same vocabulary
   as styles.css: pebble containers, pill controls, one accent per voice, and
   every colour a token so the dark theme needs no work here. */

/* ── hidden means hidden ───────────────────────────────────────────────
   The `hidden` attribute is styled by the browser's own stylesheet, so any
   author rule that sets `display` on the same element beats it - and a
   component whose base rule is `display:flex` is therefore permanently
   visible no matter what the script sets. That has now caused three
   separate bugs here: the incoming-call banner that could not be dismissed,
   the sign-in form showing "Your company" and "Join code" at the same time,
   and the emoji maker's drop hint sitting on top of the picture.
   One rule, once, instead of remembering it per component. */
[hidden] { display: none !important; }

/* a small glyph beside a card heading, so the admin page scans by shape */
.h-glyph { width:17px; height:17px; margin-right:.1rem; vertical-align:-3px;
  color:var(--plum); }
.bar h2 { display:inline-flex; align-items:center; gap:.45rem; }

/* ── the admin page ────────────────────────────────────────────────── */
#view-admin { overflow:auto; }
.admin-wrap { display:flex; flex-direction:column; gap:1.1rem;
  padding-bottom:2rem; max-width:62rem; }
.admin-wrap .card { overflow:visible; }

.org-body { padding:1.15rem 1.35rem 1.35rem; display:flex; flex-direction:column;
  gap:1.1rem; }
.field { display:flex; flex-direction:column; gap:.4rem; }
.field > span { font-size:.78rem; font-weight:600; color:var(--muted); }
.field .note, .field .hint { font-size:.775rem; color:var(--faint);
  font-style:normal; font-family:"Instrument Sans",sans-serif; line-height:1.45; }
.field .note.bad { color:var(--danger); }
.org-body input[type="text"] { font-family:inherit; font-size:.94rem;
  color:var(--ink); background:var(--tone); border:1.5px solid var(--line);
  border-radius:var(--r-md); padding:.7rem .9rem; max-width:26rem;
  transition:border-color .2s, box-shadow .2s; }
.org-body input[type="text"]:focus { outline:none; border-color:var(--plum);
  box-shadow:0 0 0 3.5px color-mix(in srgb, var(--plum) 20%, transparent); }

.codebox { display:flex; align-items:center; gap:.5rem; flex-wrap:wrap; }
.codebox code { font-family:"JetBrains Mono",ui-monospace,monospace; font-size:1.02rem;
  font-weight:500; letter-spacing:.06em; background:var(--plum-soft);
  color:var(--plum-ink); border-radius:var(--r-sm); padding:.6rem .85rem;
  user-select:all; }
.ctl.warnbtn { color:var(--warn); background:var(--warn-soft); }
.ctl.warnbtn:hover { background:var(--warn); color:var(--on-warn); }
.iconbtn.danger { color:var(--danger); }
.iconbtn.danger:hover { background:var(--danger); color:var(--on-danger); }
.ctl.busy { opacity:.55; pointer-events:none; }

/* A switch, not a checkbox. "Let anyone with the code walk in" is a decision
   with consequences, and it should look like one. */
.switch { display:flex; align-items:flex-start; gap:.75rem; cursor:pointer;
  max-width:34rem; }
.switch input { position:absolute; opacity:0; width:0; height:0; }
.switch .track { flex:none; width:2.9rem; height:1.7rem; border-radius:999px;
  background:var(--line-firm); position:relative; margin-top:.1rem;
  transition:background .25s var(--spring); }
.switch .track i { position:absolute; top:.2rem; left:.2rem; width:1.3rem;
  height:1.3rem; border-radius:50%; background:var(--surface);
  box-shadow:var(--shadow-1); transition:transform .25s var(--spring); }
.switch input:checked + .track { background:var(--live); }
.switch input:checked + .track i { transform:translateX(1.2rem); }
.switch input:focus-visible + .track { outline:2px solid var(--plum);
  outline-offset:2px; }
.switch-text { display:flex; flex-direction:column; gap:.15rem; }
.switch-text b { font-size:.87rem; font-weight:600; }
.switch-text em { font-size:.775rem; color:var(--faint); font-style:normal;
  font-family:"Instrument Sans",sans-serif; }

.empty-note { margin:0; padding:1.3rem 1.35rem; font-size:.85rem;
  color:var(--faint); font-family:"Instrument Sans",sans-serif; }

/* People at the door: the one thing on this page that is a queue, so it is
   the one thing that gets a ring around it when it is not empty. */
#pending-card.has-waiting { box-shadow:var(--shadow-2),
  0 0 0 2px color-mix(in srgb, var(--warn) 55%, transparent); }
.pending, .members { display:flex; flex-direction:column; }
.prow, .mrow { display:grid; align-items:center; gap:.85rem;
  padding:.85rem 1.35rem; border-bottom:1px solid var(--line); }
.prow { grid-template-columns:auto minmax(0,1fr) auto; }
.mrow { grid-template-columns:auto minmax(0,1fr) auto auto; }
.prow:last-child, .mrow:last-child { border-bottom:0; }
.prow:hover, .mrow:hover { background:var(--tone); }
.mrow.off { opacity:.62; }
.pwho, .mwho { display:flex; flex-direction:column; gap:.1rem; min-width:0; }
.pwho b, .mwho b { font-size:.9rem; font-weight:600; display:flex;
  align-items:center; gap:.4rem; }
.pwho span, .mwho span { font-size:.78rem; color:var(--faint);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pwho em { font-size:.74rem; color:var(--warn); font-style:normal;
  font-weight:600; }
.you-tag { font-size:.66rem; font-weight:700; text-transform:uppercase;
  letter-spacing:.04em; background:var(--well); color:var(--muted);
  border-radius:999px; padding:.1rem .42rem; }
.pacts, .macts { display:flex; align-items:center; gap:.4rem; flex-wrap:wrap;
  justify-content:flex-end; }
.macts-note { font-size:.74rem; color:var(--faint); }
.mbadges { display:flex; gap:.35rem; }
.role { font-size:.7rem; font-weight:700; border-radius:999px;
  padding:.22rem .6rem; background:var(--well); color:var(--muted);
  white-space:nowrap; }
.role.owner { background:var(--plum-soft); color:var(--plum-ink); }
.role.admin { background:var(--a-sky-soft); color:var(--a-sky); }
.role.pending { background:var(--warn-soft); color:var(--warn); }
.role.off { background:var(--danger-soft); color:var(--danger); }

/* the emoji a company owns */
.emoji-manage { display:flex; flex-wrap:wrap; gap:.6rem;
  padding:1.1rem 1.35rem 1.3rem; }
.etile { position:relative; display:flex; flex-direction:column;
  align-items:center; gap:.28rem; width:6.4rem; padding:.7rem .4rem .6rem;
  border-radius:var(--r-md); background:var(--tone);
  transition:background .2s, transform .2s var(--spring); }
.etile:hover { background:var(--well); transform:translateY(-2px); }
.etile img { width:2.6rem; height:2.6rem; object-fit:contain; }
.etile code { font-family:"JetBrains Mono",ui-monospace,monospace; font-size:.7rem;
  color:var(--muted); max-width:100%; overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; }
.etile em { font-size:.68rem; color:var(--faint); font-style:normal; }
.etile .iconbtn { position:absolute; top:.15rem; right:.15rem; width:1.6rem;
  height:1.6rem; opacity:0; transition:opacity .18s; }
.etile .iconbtn svg { width:12px; height:12px; }
.etile:hover .iconbtn, .etile:focus-within .iconbtn { opacity:1; }

/* ── waiting to be let in ──────────────────────────────────────────── */
.waiting { position:fixed; inset:0; z-index:60; display:grid; place-items:center;
  padding:2rem 1.25rem; background:
    radial-gradient(110% 85% at 80% 8%,
      color-mix(in srgb, var(--a-apricot) 20%, transparent), transparent 60%),
    var(--ground); }
/* The author rule has to beat the attribute, or the panel is permanently on -
   which is exactly the bug the ring banner had. */
.waiting[hidden] { display:none !important; }
.waiting-card { width:100%; max-width:26rem; background:var(--surface);
  border-radius:var(--r-2xl); box-shadow:var(--shadow-2);
  padding:2rem 1.9rem 1.6rem; display:flex; flex-direction:column;
  align-items:center; gap:.85rem; text-align:center; }
.waiting-glyph { width:3.4rem; height:3.4rem; border-radius:50%;
  background:var(--plum-soft); color:var(--plum); display:grid;
  place-items:center; }
.waiting-glyph svg { width:1.7rem; height:1.7rem; }
.waiting-card h2 { margin:0; font-size:1.2rem; font-weight:700;
  letter-spacing:-.02em; }
.waiting-card p { margin:0; font-family:"Instrument Sans",sans-serif;
  font-size:.9rem; line-height:1.55; color:var(--muted); }
.waiting-dots { display:flex; gap:.35rem; margin:.2rem 0 .1rem; }
.waiting-dots i { width:.45rem; height:.45rem; border-radius:50%;
  background:var(--plum); opacity:.35;
  animation:breathe 1.4s ease-in-out infinite; }
.waiting-dots i:nth-child(2) { animation-delay:.18s; }
.waiting-dots i:nth-child(3) { animation-delay:.36s; }
@keyframes breathe { 0%,100%{opacity:.28; transform:scale(.85)}
  50%{opacity:1; transform:scale(1.15)} }
.waiting.stopped .waiting-glyph { background:var(--danger-soft);
  color:var(--danger); }
.waiting.stopped .waiting-dots { display:none; }

/* ── emoji in messages ─────────────────────────────────────────────── */
.cemoji { width:1.35em; height:1.35em; object-fit:contain; vertical-align:-.3em;
  margin:0 .04em; }
/* A message that is nothing but emoji is drawn large, the way every other
   chat app does it - a lone thumbs-up at body size reads as a mistake. */
.tx.jumbo { font-size:1.9rem; line-height:1.25; }
.tx.jumbo .cemoji { width:1.15em; height:1.15em; vertical-align:-.2em; }

/* ── the picker ────────────────────────────────────────────────────── */
.emoji-pop { position:fixed; z-index:70; width:22.5rem;
  max-width:calc(100vw - 24px); background:var(--surface);
  border-radius:var(--r-xl); box-shadow:var(--shadow-2); display:flex;
  flex-direction:column; overflow:hidden; animation:pop .18s var(--spring) both; }
.emoji-pop[hidden] { display:none !important; }
@keyframes pop { from{opacity:0; transform:translateY(6px) scale(.97)}
  to{opacity:1; transform:none} }
.emoji-head { display:flex; gap:.4rem; padding:.7rem .7rem .5rem; }
.emoji-head input { flex:1; font-family:inherit; font-size:.87rem;
  color:var(--ink); background:var(--well); border:0; border-radius:999px;
  padding:.55rem .9rem; }
.emoji-head input:focus { outline:2px solid var(--plum); outline-offset:0; }
.emoji-tabs { display:flex; flex-wrap:wrap; gap:.1rem; padding:0 .6rem .45rem;
  justify-content:flex-start; }
.emoji-tabs button { flex:0 0 auto; border:0; background:none; cursor:pointer;
  font-size:1.05rem; line-height:1; padding:.38rem .45rem;
  border-radius:var(--r-sm);
  transition:background .18s, transform .18s var(--spring); }
.emoji-tabs button:hover { background:var(--well); transform:translateY(-1px); }
.emoji-tabs button[aria-pressed="true"] { background:var(--plum-soft); }
.emoji-grid { display:grid; grid-template-columns:repeat(8, 1fr); gap:.1rem;
  padding:.35rem .55rem .5rem; max-height:15rem; overflow-y:auto; }
.ecell { border:0; background:none; cursor:pointer; font-size:1.28rem;
  line-height:1; aspect-ratio:1; border-radius:var(--r-sm); display:grid;
  place-items:center; transition:background .15s, transform .15s var(--spring); }
.ecell:hover { background:var(--well); transform:scale(1.22); }
.ecell:focus-visible { outline:2px solid var(--plum); outline-offset:-2px; }
.ecell img { width:1.45rem; height:1.45rem; object-fit:contain; }
.emoji-empty { grid-column:1/-1; margin:0; padding:1.4rem .6rem;
  text-align:center; font-size:.83rem; color:var(--faint);
  font-family:"Instrument Sans",sans-serif; }
.emoji-foot { padding:.5rem .95rem .7rem; border-top:1px solid var(--line);
  font-size:.76rem; color:var(--faint); font-family:"Instrument Sans",sans-serif;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }

/* ── the maker ─────────────────────────────────────────────────────── */
.sheet.wide { max-width:44rem; width:calc(100vw - 3rem); }
.maker { display:grid; grid-template-columns:minmax(0,15rem) minmax(0,1fr);
  gap:1.2rem; align-items:start; }
.maker-stage { display:flex; flex-direction:column; gap:.7rem;
  align-items:center; }
/* The chequerboard is the point: it shows where the image is transparent,
   which is the whole question when making an emoji. */
.stage-ring { position:relative; width:15rem; height:15rem;
  border-radius:var(--r-xl); background:
    repeating-conic-gradient(var(--well) 0 25%, var(--tone) 0 50%)
    0 0/1.1rem 1.1rem;
  display:grid; place-items:center; cursor:pointer; overflow:hidden;
  box-shadow:inset 0 0 0 1.5px var(--line); transition:box-shadow .2s; }
.stage-ring.over { box-shadow:inset 0 0 0 2.5px var(--plum); }
.stage-ring canvas { width:15rem; height:15rem; touch-action:none; cursor:grab; }
.stage-ring canvas:active { cursor:grabbing; }
.stage-empty { position:absolute; inset:0; display:flex; flex-direction:column;
  align-items:center; justify-content:center; gap:.3rem; pointer-events:none;
  color:var(--faint); text-align:center; padding:1rem; }
.stage-empty[hidden] { display:none !important; }
.stage-empty svg { width:2.1rem; height:2.1rem; margin-bottom:.2rem; }
.stage-empty b { font-size:.87rem; color:var(--muted); }
.stage-empty span { font-size:.78rem; }
.stage-sizes { display:flex; flex-direction:column; align-items:center;
  gap:.35rem; }
.sz-note { font-size:.74rem; color:var(--faint);
  font-family:"Instrument Sans",sans-serif; }
.sz-row { display:flex; align-items:flex-end; gap:.6rem; padding:.45rem .75rem;
  background:var(--tone); border-radius:999px; }

.maker-controls { display:flex; flex-direction:column; gap:.75rem; }
.shortcode { display:flex; align-items:center; gap:.15rem; background:var(--well);
  border-radius:var(--r-md); padding:.15rem .6rem; }
.shortcode i { font-style:normal;
  font-family:"JetBrains Mono",ui-monospace,monospace; color:var(--faint);
  font-size:.95rem; }
.shortcode input { flex:1; border:0; background:none;
  font-family:"JetBrains Mono",ui-monospace,monospace; font-size:.92rem;
  color:var(--ink); padding:.6rem .1rem; }
.shortcode input:focus { outline:none; }
.slider { display:grid; grid-template-columns:5.4rem minmax(0,1fr);
  align-items:center; gap:.7rem; }
.slider > span { font-size:.78rem; font-weight:600; color:var(--muted); }
.slider input[type="range"] { -webkit-appearance:none; appearance:none;
  height:.4rem; border-radius:999px; background:var(--well); }
.slider input[type="range"]::-webkit-slider-thumb { -webkit-appearance:none;
  width:1.05rem; height:1.05rem; border-radius:50%; background:var(--plum);
  box-shadow:var(--shadow-1); cursor:pointer; }
.slider input[type="range"]::-moz-range-thumb { width:1.05rem; height:1.05rem;
  border:0; border-radius:50%; background:var(--plum); cursor:pointer; }
.slider input[type="range"]:focus-visible { outline:2px solid var(--plum);
  outline-offset:3px; }
.chipset { display:flex; flex-wrap:wrap; gap:.4rem; margin-top:.15rem; }
.chipset .ctl[aria-pressed="true"] { background:var(--plum-soft);
  color:var(--plum-ink); }
.maker-problem { margin:0; font-size:.82rem; font-weight:600;
  color:var(--danger); background:var(--danger-soft);
  border-radius:var(--r-sm); padding:.55rem .75rem; }
.maker-problem[hidden] { display:none !important; }

@media (max-width:46rem) {
  .maker { grid-template-columns:1fr; }
  .maker-stage { order:-1; }
}

/* ── cross-company conversations ───────────────────────────────────── */
/* Marked everywhere they appear. Nobody should ever be unsure whether the
   person reading along works at their company. */
.chip.shared { background:var(--a-apricot-soft); color:var(--a-apricot);
  display:inline-flex; align-items:center; gap:.35rem; border-radius:999px;
  padding:.24rem .65rem; font-size:.72rem; font-weight:700; }
.chip.shared svg { width:12px; height:12px; }
.thread .shared-tag { background:var(--a-apricot-soft);
  color:var(--a-apricot); font-size:.64rem; font-weight:700; border-radius:999px;
  padding:.1rem .4rem; margin-left:.35rem; }
.tsec.shared-sec { color:var(--a-apricot); }

/* ══ conversations with people outside the company ═══════════════════
   These sit in the same list as colleagues now, so the difference has to
   be visible rather than filed away behind its own tab. A row keeps a
   coloured edge; an open one tints the whole pane and says the rule out
   loud, because "external" tells you something is different without
   telling you what. */
.thread.external { border-left:3px solid var(--a-apricot); border-radius:var(--r-md); }
.thread.external[aria-current="true"] { background:var(--a-apricot-soft); }
.newchat.outside { color:var(--a-apricot); }

.chat-main.outside .bar { background:var(--a-apricot-soft);
  border-bottom-color:color-mix(in srgb, var(--a-apricot) 30%, transparent); }
.chat-main.outside .composer { border-top:2px solid var(--a-apricot); }

.outside-note { display:flex; align-items:flex-start; gap:.55rem;
  padding:.6rem 1.35rem .65rem; font-size:.78rem; line-height:1.45;
  color:var(--ink); background:var(--a-apricot-soft);
  border-bottom:1px solid color-mix(in srgb, var(--a-apricot) 26%, transparent); }
.outside-note svg { width:15px; height:15px; flex:0 0 auto; margin-top:.12rem;
  color:var(--a-apricot); }
.outside-note b { font-weight:700; }
/* The names sit right after the sentence, not pinned to the far edge.
   Pinned, they left a stripe of empty banner across the middle of a wide
   window - the texture showing through a gap between two bits of text that
   belong together. The close button takes the far edge instead. */
.outside-who { padding-left:.55rem; font-weight:700;
  color:var(--a-apricot); white-space:nowrap; }
.outside-x { margin-left:auto; }
@media (max-width:60rem) {
  .outside-who { margin-left:0; width:100%; white-space:normal; }
}
.msg.outside .nm::after { content:" · outside"; font-weight:600;
  color:var(--a-apricot); font-size:.92em; }
.companies { display:flex; align-items:center; gap:.3rem; flex-wrap:wrap; }
/* "with Koda", not a bare "Koda". A company name on its own next to a row of
   chips reads as a label for something; the word in front says it is who you
   are talking to.
   The word itself comes from the catalogue now, not from `content:` - a
   string in a stylesheet is a string no translation can reach. */
.companies .with { font-size:.68rem; font-weight:600; color:var(--faint);
  letter-spacing:.01em; }
.companies span { font-size:.71rem; font-weight:700; color:var(--a-apricot);
  background:var(--a-apricot-soft); border-radius:999px;
  padding:.18rem .55rem; }

/* the link list, on the admin page and in a shared thread's header */
.links { display:flex; flex-direction:column; }
.lrow { display:grid; grid-template-columns:minmax(0,1fr) auto auto; gap:.7rem;
  align-items:center; padding:.7rem 1.35rem; border-bottom:1px solid var(--line); }
.lrow:last-child { border-bottom:0; }
.lrow code { font-family:"JetBrains Mono",ui-monospace,monospace; font-size:.76rem;
  color:var(--muted); overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; }
.lrow .when { font-size:.74rem; color:var(--faint); white-space:nowrap; }
.lrow.spent { opacity:.5; }

/* the button that reaches back into a long conversation */
.load-older { align-self:center; margin:.6rem auto .3rem; border:0; cursor:pointer;
  background:var(--well); color:var(--muted); border-radius:999px;
  padding:.45rem 1rem; font-family:inherit; font-size:.79rem; font-weight:600;
  transition:background .2s, color .2s, transform .2s var(--spring); }
.load-older:hover { background:var(--plum-soft); color:var(--plum-ink);
  transform:translateY(-1px); }
.load-older[disabled] { opacity:.6; pointer-events:none; }

/* ══ THE CALL, AT ANY WINDOW SIZE ══════════════════════════════════════
   Two problems this fixes, both visible in one screenshot: the app was
   wider and taller than the window - rail clipped on the left, People
   clipped on the right, "Share screen" clipped off the bottom - and the
   translated transcript was hidden whenever video was on, which is why
   nobody could find it. */

/* ── the transcript lives *with* the video, not instead of it ────────
   `setStage("video")` used to set display:none on the transcript. In a
   translated call the transcript is the entire point of the product, so it
   now sits beneath the faces as a strip, the way captions do everywhere
   else. Capped by percentage so it grows on a tall window and gets out of
   the way on a short one. */
.stage.with-video .stream {
  flex:0 0 auto; max-height:38%; min-height:6.5rem;
  border-top:1px solid var(--line);
  padding-top:.7rem;
  /* Newest at the bottom, like every transcript. */
  justify-content:flex-end;
}
.stage.with-video .stream:empty,
.stage.with-video .stream .hollow { min-height:0; }

/* A quiet label, so the strip is obviously the transcript and not chat. */
.stage.with-video .stream::before {
  content:"Live transcript";
  position:sticky; top:0; align-self:flex-start;
  font-size:.66rem; font-weight:700; letter-spacing:.06em;
  text-transform:uppercase; color:var(--faint);
  background:var(--surface); padding:0 .4rem .25rem 0;
}

/* Without video the transcript takes the whole stage, as before. */
.stage:not(.with-video) .stream { flex:1 1 auto; max-height:none; }

/* The control bar wraps rather than overflowing, and never eats the stage. */
.controls { flex:0 0 auto; flex-wrap:wrap; row-gap:.45rem; }

/* ── narrower windows ────────────────────────────────────────────────── */

/* The side dock is the first thing to give up width. */
@media (max-width: 1180px) {
  .call-body { grid-template-columns:minmax(0, 1fr) 19rem; }
  .controls .ctl { padding:.6rem .8rem; font-size:.78rem; }
}

/* Below this the dock stops being a column and becomes a row underneath:
   two panels side by side never both work under ~1000px, and a squeezed
   People list is worse than a short one. */
@media (max-width: 1000px) {
  .call-body { grid-template-columns:minmax(0, 1fr); grid-template-rows:minmax(0, 1fr) auto; }
  .dock { border-left:0; border-top:1px solid var(--line);
          max-height:min(42vh, 22rem); }
  .stage.with-video .stream { max-height:32%; min-height:5rem; }
}

/* The rail loses its labels before it loses its icons - an icon you
   recognise beats a label you cannot read. */
@media (max-width: 900px) {
  .app { grid-template-columns:3.6rem minmax(0, 1fr); }
  .rail button { width:2.9rem; height:2.9rem; font-size:0; gap:0; }
  .rail button svg { width:20px; height:20px; }
  .rail .mark { width:32px; height:32px; border-radius:11px; font-size:.85rem; }
  .topbar-search { max-width:none; }
}

/* Phone-shaped. The rail goes to the bottom, where a thumb is. */
@media (max-width: 680px) {
  body { height:100dvh; }        /* dvh, so a mobile URL bar cannot clip it */
  .app { grid-template-columns:minmax(0, 1fr); grid-template-rows:minmax(0, 1fr) auto;
         padding:0 .45rem .45rem; gap:.45rem; }
  .rail { flex-direction:row; justify-content:space-around; order:2;
          padding:.3rem .2rem; background:var(--surface);
          border-radius:var(--r-lg); box-shadow:var(--shadow-1); }
  .rail .mark, .rail .grow { display:none; }
  .rail button { width:auto; flex:1; height:2.7rem; }
  .call-body { grid-template-rows:minmax(0, 1fr) auto; }
  .dock { max-height:38vh; }
  .stage-head { padding:.6rem .8rem .35rem; flex-wrap:wrap; row-gap:.4rem; }
  .tiles, .stream { padding-left:.8rem; padding-right:.8rem; }
  .controls { padding:.6rem .7rem; gap:.35rem; }
  .controls .ctl { padding:.55rem .7rem; font-size:.75rem; }
  .bar { padding:.75rem .9rem .65rem; }
  /* The chat list and the conversation cannot share a phone screen. */
  .chat-body { grid-template-columns:minmax(0, 1fr); }
  .sidebar { display:none; }
  .sidebar.on { display:flex; }
}

/* Short windows - a laptop with a browser toolbar, or a split screen. The
   transcript strip is what gives way, because a video call with no video
   is not the thing anybody joined. */
@media (max-height: 700px) {
  .stage.with-video .stream { max-height:30%; min-height:4.5rem; }
  .stage-head { padding-top:.5rem; padding-bottom:.3rem; }
  .controls { padding:.55rem 1rem; }
}
@media (max-height: 560px) {
  .stage.with-video .stream { display:none; }   /* video wins */
  .stage.with-video .no-room {
    display:block; padding:.3rem 1.35rem .6rem;
    font-size:.72rem; color:var(--faint); }
}
.no-room { display:none; }

/* ══ THE CALENDAR ══════════════════════════════════════════════════════
   A real time grid, not an agenda list. Six pastels from styles.css, one
   per meeting, stable across reloads and themes. */

#view-calendar { overflow:hidden; }
#view-calendar .card { grid-template-rows:auto auto minmax(0, 1fr); }

.cal-bar { flex-wrap:wrap; row-gap:.5rem; }
.cal-bar h2 { font-size:1.12rem; font-weight:600; letter-spacing:-.02em;
  min-width:11rem; }
.cal-nav { display:flex; align-items:center; gap:.3rem; }
.cal-nav .iconbtn { width:2.1rem; height:2.1rem; }
.cal-nav .ctl { padding:.5rem .85rem; font-size:.79rem; }
.cal-views button { padding:.38rem .7rem; font-size:.74rem; }

/* ── the day headings, above the scrolling grid ────────────────────── */
.cal-days { display:grid; grid-template-columns:3.6rem repeat(var(--cols, 5), 1fr);
  border-bottom:1px solid var(--line); padding-right:9px; }
.cal-days .spacer { border-right:1px solid var(--line); }
.cal-dayhead { padding:.55rem .4rem .5rem; text-align:center;
  border-right:1px solid var(--line); }
.cal-dayhead:last-child { border-right:0; }
.cal-dayhead b { display:block; font-size:1.32rem; font-weight:600;
  letter-spacing:-.03em; line-height:1.1; }
.cal-dayhead span { display:block; font-size:.68rem; font-weight:600;
  letter-spacing:.07em; text-transform:uppercase; color:var(--faint);
  margin-top:.1rem; }
/* Today gets the accent on the number, the way a wall calendar circles it. */
.cal-dayhead.today b { color:var(--plum); }
.cal-dayhead.today span { color:var(--plum); }
.cal-dayhead.today { background:var(--plum-soft); }
.cal-dayhead.today:first-of-type { border-top-left-radius:var(--r-sm); }

/* ── the scrolling time grid ───────────────────────────────────────── */
.cal-body { min-height:0; overflow:hidden; display:flex; }
/* Each hour is a comfortable fixed height and the day scrolls - a full 24h
   is taller than any screen, and cramming it to fit made everything too
   small to read. --hour-h is the one knob: change it and the gutter, the
   slots and the JS positioning all follow. */
.cal-grid { --hour-h:52px; flex:1; display:grid;
  grid-template-columns:3.6rem repeat(var(--cols, 5), 1fr);
  overflow-y:auto; overflow-x:hidden; }
.cal-grid::-webkit-scrollbar { width:9px; }
.cal-grid::-webkit-scrollbar-thumb { background:var(--line-firm); border-radius:9px; }

.cal-gutter { border-right:1px solid var(--line); }
.cal-hour { height:var(--hour-h); position:relative; }
.cal-hour span { position:absolute; top:-.55rem; right:.5rem;
  font-size:.68rem; font-weight:500; color:var(--faint);
  font-variant-numeric:tabular-nums; }

.cal-col { position:relative; border-right:1px solid var(--line); }
.cal-col:last-child { border-right:0; }
.cal-col.today { background:color-mix(in srgb, var(--plum) 4%, transparent); }

/* An hour box with a lighter half-hour line down the middle. The line is a
   percentage so it stays centred whatever --hour-h is set to. */
.cal-slot { height:var(--hour-h); border-bottom:1px solid var(--line);
  background-image:linear-gradient(to bottom,
    transparent calc(50% - 0.5px),
    color-mix(in srgb, var(--line) 55%, transparent) calc(50% - 0.5px),
    color-mix(in srgb, var(--line) 55%, transparent) calc(50% + 0.5px),
    transparent calc(50% + 0.5px));
  cursor:pointer; transition:background-color .15s; }
.cal-slot:hover { background-color:color-mix(in srgb, var(--plum) 7%, transparent); }

.cal-nowline { position:absolute; left:0; right:0; height:2px;
  background:var(--danger); z-index:3; pointer-events:none;
  box-shadow:0 0 0 1px color-mix(in srgb, var(--danger) 25%, transparent); }
.cal-nowline::before { content:""; position:absolute; left:-4px; top:-3px;
  width:8px; height:8px; border-radius:50%; background:var(--danger); }

/* ── an event ──────────────────────────────────────────────────────── */
.cal-event { position:absolute; z-index:2; overflow:hidden; cursor:pointer;
  border:0; text-align:left; display:flex; flex-direction:column; gap:.1rem;
  padding:.35rem .5rem; border-radius:.55rem; font-family:inherit;
  border-left:5px solid var(--line-firm);
  box-shadow:0 1px 3px rgba(0,0,0,.08);
  transition:transform .16s var(--spring), box-shadow .16s, filter .16s; }
.cal-event:hover { transform:translateY(-1px) scale(1.012);
  box-shadow:var(--shadow-2); z-index:4; }
.cal-event:focus-visible { outline:2px solid var(--plum); outline-offset:1px; }
.cal-what { font-size:.85rem; font-weight:700; line-height:1.25;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.cal-when { font-size:.72rem; font-weight:600; opacity:.85;
  font-variant-numeric:tabular-nums; }
.cal-who { font-size:.66rem; opacity:.62; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap; margin-top:.05rem; }
.cal-now { align-self:flex-start; margin-top:auto; font-size:.6rem;
  font-weight:700; letter-spacing:.05em; text-transform:uppercase;
  background:var(--danger); color:var(--on-danger); border-radius:999px;
  padding:.05rem .4rem; }
.cal-event.past { filter:saturate(.5) opacity(.62); }
.cal-event.live { box-shadow:0 0 0 2px var(--danger); }

/* The six pastels. Fill, edge and ink together, so the text on a pale box
   is never grey-on-grey. */
.e1 { background:var(--e1); border-left-color:var(--e1-line); color:var(--e1-ink); }
.e2 { background:var(--e2); border-left-color:var(--e2-line); color:var(--e2-ink); }
.e3 { background:var(--e3); border-left-color:var(--e3-line); color:var(--e3-ink); }
.e4 { background:var(--e4); border-left-color:var(--e4-line); color:var(--e4-ink); }
.e5 { background:var(--e5); border-left-color:var(--e5-line); color:var(--e5-ink); }
.e6 { background:var(--e6); border-left-color:var(--e6-line); color:var(--e6-ink); }

/* ── the month view ────────────────────────────────────────────────── */
.cal-month { flex:1; display:grid; grid-template-columns:repeat(7, 1fr);
  grid-template-rows:auto repeat(6, minmax(0, 1fr)); overflow-y:auto; }
.cal-mhead { padding:.5rem .6rem; font-size:.67rem; font-weight:700;
  letter-spacing:.07em; text-transform:uppercase; color:var(--faint);
  border-bottom:1px solid var(--line); border-right:1px solid var(--line); }
.cal-mhead:last-of-type { border-right:0; }
.cal-cell { border-right:1px solid var(--line);
  border-bottom:1px solid var(--line); padding:.3rem .35rem;
  display:flex; flex-direction:column; gap:.15rem; min-height:0;
  overflow:hidden; cursor:pointer; transition:background-color .15s; }
.cal-cell:hover { background:color-mix(in srgb, var(--plum) 5%, transparent); }
.cal-cell:nth-child(7n+7) { border-right:0; }
.cal-cell.other { background:var(--tone); }
.cal-cell.other .cal-date { color:var(--faint); }
.cal-date { font-size:.78rem; font-weight:600; padding:.05rem .15rem;
  font-variant-numeric:tabular-nums; }
.cal-cell.today .cal-date { background:var(--plum); color:var(--on-plum);
  border-radius:999px; width:1.5rem; height:1.5rem; display:grid;
  place-items:center; }

.cal-chip { display:flex; align-items:center; gap:.3rem; border:0;
  cursor:pointer; font-family:inherit; text-align:left; padding:.15rem .35rem;
  border-radius:.4rem; font-size:.69rem; overflow:hidden;
  transition:transform .15s var(--spring); }
.cal-chip:hover { transform:translateX(1px); }
.cal-chip i { width:.4rem; height:.4rem; border-radius:50%; flex:none;
  background:currentColor; opacity:.75; }
.cal-chip .cal-what { font-size:.69rem; font-weight:600; flex:1; }
.cal-chip .cal-when { font-size:.63rem; flex:none; }
.cal-more { border:0; background:none; cursor:pointer; font-family:inherit;
  font-size:.65rem; font-weight:600; color:var(--muted); text-align:left;
  padding:.05rem .35rem; }
.cal-more:hover { color:var(--plum); }

/* one meeting, opened from the grid */
.ms-people { display:flex; flex-wrap:wrap; gap:.4rem; }
.ms-person { display:inline-flex; align-items:center; gap:.4rem;
  background:var(--tone); border-radius:999px; padding:.22rem .7rem .22rem .25rem;
  font-size:.79rem; font-weight:500; }
.ms-person i { width:1.4rem; height:1.4rem; border-radius:50%;
  display:grid; place-items:center; font-size:.62rem; font-weight:700;
  font-style:normal; background:var(--voice-soft); color:var(--voice); }

@media (max-width: 900px) {
  .cal-bar h2 { min-width:0; font-size:1rem; }
  .cal-views button { padding:.34rem .5rem; font-size:.7rem; }
  .cal-days, .cal-grid { grid-template-columns:2.6rem repeat(var(--cols, 5), 1fr); }
  .cal-dayhead b { font-size:1.05rem; }
}
@media (max-width: 680px) {
  /* A week does not fit a phone. The switcher stays, so Day and Month -
     which do fit - are one tap away. */
  .cal-views #cal-week, .cal-views #cal-work { display:none; }
  .cal-chip .cal-when { display:none; }
}

/* ══ THE TRANSCRIPT, AS A PANEL ════════════════════════════════════════
   It was a strip under the video, which clipped, and before that it
   replaced the video, which hid it. Beside the call is where a record you
   refer back to belongs - with People, Chat and Files. */
.talk-head { display:flex; align-items:center; gap:.5rem;
  padding:.55rem .8rem .5rem; border-bottom:1px solid var(--line); }
.talk-head .seg { margin-left:0; }
.talk-head .seg button { padding:.3rem .6rem; font-size:.7rem; }
.talk-head .iconbtn { margin-left:auto; width:2.1rem; height:2.1rem; }

/* Smaller than a chat message. A transcript is skimmed for the line you
   want, not read like prose, and more of it on screen is worth more than
   bigger type. */
#pane-talk .stream { padding:.6rem .8rem 1rem; gap:.5rem; }
#pane-talk .turn { grid-template-columns:1.6rem minmax(0,1fr); gap:.05rem .5rem; }
#pane-talk .turn .av { width:1.6rem; height:1.6rem; font-size:.58rem; }
#pane-talk .turn .nm { font-size:.68rem; }
#pane-talk .turn .at { font-size:.62rem; }
#pane-talk .turn .tx, #pane-talk .turn .en { font-size:.82rem; line-height:1.4; }
#pane-talk .turn .src, #pane-talk .turn .orig { font-size:.71rem; line-height:1.35; }
#pane-talk .hollow b { font-size:.85rem; }
#pane-talk .hollow span { font-size:.76rem; }

/* Nothing on a camera yet: the stage says so rather than showing a grid of
   grey rectangles. */
.stage-hollow { flex:1; display:none; flex-direction:column; align-items:center;
  justify-content:center; gap:.4rem; text-align:center; padding:2rem 1.5rem;
  color:var(--faint); }
.stage-hollow b { font-size:.95rem; color:var(--muted); }
.stage-hollow span { font-size:.82rem; max-width:32ch; line-height:1.5; }
.stage-hollow .ring { width:3rem; height:3rem; border-radius:50%;
  background:var(--tone); display:grid; place-items:center; margin-bottom:.3rem; }
.stage-hollow .ring svg { width:1.4rem; height:1.4rem; }
.stage:not(.talk-wide) .tiles:empty { display:none; }
.stage:not(.talk-wide) .tiles:empty ~ .stage-hollow { display:flex; }

/* "Transcript full width" moves it out of the dock and across the stage,
   for a call that is a conversation rather than a presentation. */
/* Transcript mode: the faces go and the transcript has the room. The stage
   is hidden outright rather than squeezed - a 2rem-wide column of video is
   not video - and the switch back lives in the call's header, which is why
   it moved out of the stage. */
.stage.talk-wide { display:none; }
.call-body:has(.stage.talk-wide) .dock { flex:1 1 auto; border-left:0; }
/* Full width, but not full measure: a line of text 1200px long is one you
   lose your place in. */
.call-body:has(.stage.talk-wide) #pane-talk .stream > * { max-width:64rem; }
.call-body:has(.stage.talk-wide) #pane-talk .talk-head { max-width:64rem; }


/* ══ a shared screen ═════════════════════════════════════════════════ */
/* Slides are the point of the call while they are up, so the screen takes
   the width and the faces line up under it. object-fit:contain, not cover:
   a cropped spreadsheet is a useless spreadsheet, and the letterboxing is
   the honest way to show a 16:9 screen in a 4:3 hole. */
.tile.screen { grid-column:1 / -1; grid-row:span 2; background:#0e0f14; }
.tile.screen video { object-fit:contain; transform:none; }
.tile.screen .nameplate { background:color-mix(in srgb, var(--plum) 82%, transparent); }
.tile.screen .placeholder { display:none; }
@media (max-width:60rem) { .tile.screen { grid-row:span 1; } }


/* ══ which controls carry a label ════════════════════════════════════
   Eleven labelled pills are wider than the bar at any window size - the
   bar sits under the stage *and* the dock, so its width is a layout
   outcome, not something a viewport breakpoint can predict. It wrapped to
   a second row and ate the video's height.

   So the bar never wraps, and the controls you use during a call - join,
   mute, camera, share - keep their words. The ones you press once are the
   icon alone with a tooltip; .visually-hidden keeps the label in the
   accessibility tree, because a screen reader reading out "button" nine
   times would be no app at all. */
.controls { flex-wrap:nowrap; }
.controls .ctl.quiet { padding-left:.72rem; padding-right:.72rem; gap:0; }
.controls .ctl.quiet > span {
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0; }
.controls .ctl.quiet svg { width:17px; height:17px; }

/* Narrower than this and even four labels are too many; the icons stay put
   and nothing moves anywhere. */
@media (max-width:78rem) {
  .controls .ctl { padding-left:.72rem; padding-right:.72rem; gap:0; }
  .controls .ctl > span:not(#join-l) {
    position:absolute; width:1px; height:1px; margin:-1px; padding:0;
    overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0; }
  .controls .pick { max-width:8.5rem; }
}
@media (max-width:58rem) {
  .controls #join-l {
    position:absolute; width:1px; height:1px; margin:-1px; padding:0;
    overflow:hidden; clip-path:inset(50%); white-space:nowrap; }
  .controls .pick { max-width:6rem; }
}

/* The Video / Transcript switch sits in the call's header now. A little air
   after the "Not connected" text so the two do not read as one control.

   flex:0 0 auto is not optional: the blanket min-width:0 that stops flex
   children from refusing to shrink also lets them shrink to nothing, and
   this one did - a switch 0px wide, present in the accessibility tree and
   impossible to click. The room title gives way instead. */
.bar .stage-toggle { margin-left:.35rem; flex:0 0 auto; }
.bar h2 { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.bar .sub { flex:0 1 auto; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* ══ the share picker (desktop app only) ═════════════════════════════
   A browser raises its own picker and this never appears. In the app,
   Electron hands us the thumbnails, so the choice can show what is
   actually on each screen rather than a list of window titles. */
.sheet-card.wide { max-width:min(56rem, 92vw); }
.pick-head { font-size:.74rem; font-weight:700; letter-spacing:.04em;
  text-transform:uppercase; color:var(--faint); margin:.9rem 0 .5rem; }
.pick-grid { display:grid; gap:.7rem; grid-template-columns:repeat(auto-fill, minmax(11rem, 1fr));
  max-height:min(46vh, 26rem); overflow-y:auto; padding:2px; }
.pick { display:flex; flex-direction:column; gap:.45rem; padding:.5rem;
  border:1.5px solid var(--line); border-radius:var(--r-md); background:var(--surface);
  cursor:pointer; text-align:left; transition:border-color .18s, transform .18s var(--spring); }
.pick:hover { border-color:var(--plum); transform:translateY(-2px); }
.pick:focus-visible { outline:2px solid var(--plum); outline-offset:2px; }
.pick-shot { width:100%; aspect-ratio:16/10; object-fit:cover; border-radius:calc(var(--r-md) - 4px);
  background:var(--well); display:block; }
.pick-shot.empty { display:grid; place-items:center; }
.pick-name { display:flex; align-items:center; gap:.4rem; font-size:.76rem;
  font-weight:600; color:var(--ink); min-width:0; }
.pick-name span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pick-icon { width:1rem; height:1rem; flex:0 0 auto; }


/* ══ everybody's animal ══════════════════════════════════════════════
   Hover a person and their initials turn into the animal that is theirs.

   Not decoration. This app puts you in rooms with people whose language
   you cannot read and whose names you may not be able to tell apart at a
   glance - "MK" and "MC" are two grey circles. An otter and a heron are
   two different people, in any language, with nothing to translate.

   The .av is already display:grid with place-items:center, so both the
   initials and the animal land in the same cell and cross-fade in place.
   No layout moves, which is what stops a roster of forty people from
   twitching as the pointer crosses it. */
.av { position:relative; overflow:hidden; }
/* Hidden with display, not opacity. An opacity fade sounds nicer but it left
   a real bug: adding the class hid the initials (colour transparent) while
   the beast's opacity transition never finished in the app, so the avatar
   went to an empty circle. display:none -> grid cannot get stuck half-way -
   the beast is either there or not, and the little pop is a one-shot
   animation whose end state is "visible" regardless of whether it plays. */
.av > .av-beast {
  position:absolute; inset:0; display:none; place-items:center;
  text-align:center;
  /* Big, but inside the circle's inscribed square, not just its width. .av
     clips to a circle, and a glyph sized to the full width has its corners -
     and, for a bottom-heavy emoji, its top - shaved by the round mask, which
     reads as "sitting low and off-centre". The box:font ratio of every .av
     size is ~3:1, so this one multiplier stays clear of the rim on a big
     roster avatar and a tiny in-call one alike. */
  font-size:2.2em; line-height:1;
  pointer-events:none;                 /* the avatar keeps its own clicks */
  /* The beast carries the avatar's own colour as its background, so when it
     shows it covers the initials underneath. That is the safety net: the
     initials are never hidden by anything except the beast sitting on top of
     them, so the worst case is "you still see initials", never an empty
     circle. */
  background:var(--voice, var(--plum)); border-radius:inherit;
}
/* Driven by the .as-animal class from animals.js, not a bare :hover - the
   avatar is a small target inside a big row, so the class goes on when the
   pointer enters the whole row. opacity is 1 outright and the beast has its
   own background, so nothing about it showing depends on an animation
   playing or on the initials being hidden. */
.av.as-animal > .av-beast { display:grid; opacity:1; animation:beast-in .32s var(--spring); }
@keyframes beast-in { from { transform:scale(.5) rotate(-12deg); }
  to { transform:none; } }

/* A pale plate for the near-black glyphs.
 *
 * The avatar's background is the person's accent, and in the light palettes
 * the accents are mid-dark: a black spider on one is a smudge, and so are
 * the ant, the bat and the scorpion. The page cannot tell - an emoji is
 * drawn by the system font and its colours are not exposed - so animals.js
 * marks them and this puts something light behind them.
 *
 * A soft disc rather than a flat white circle: the accent still shows at
 * the rim, so the avatar keeps the colour that identifies whose it is, and
 * the glyph sits in a pool of light in the middle. White rather than a
 * palette token on purpose, because what this needs is *light* - and half
 * the palettes' soft tones are dark ones, being soft against a dark ground.
 */
.av > .av-beast[data-dark] {
  background:
    radial-gradient(circle at 50% 54%,
                    rgba(255,255,255,.94) 0 34%,
                    rgba(255,255,255,.66) 52%,
                    rgba(255,255,255,.22) 68%,
                    transparent 78%),
    var(--voice, var(--plum));
  /* And a halo on the glyph itself, for the parts that reach past the pool -
     a spider's legs, an eagle's wingtips. */
  text-shadow: 0 0 2px rgba(255,255,255,.85), 0 0 4px rgba(255,255,255,.55);
}

/* Big avatars can carry a hair more; the smallest keep a touch of margin so
   a wide glyph never meets the circle's edge. */
.av.lg > .av-beast { font-size:2.35em; }
.av.sm > .av-beast { font-size:2.1em; }

@media (prefers-reduced-motion:reduce) {
  .av.as-animal > .av-beast { animation:none; }
}


/* ══ the palette picker ══════════════════════════════════════════════
   Nine cards, each painted in the palette it offers. The point is that you
   are looking at the thing rather than at a swatch labelled "Sea" - so the
   miniature is a little app: a ground, a panel on it, two lines of text and
   the accent dots, in that palette's own colours.

   Everything here is deliberately springy. Choosing how your app looks is
   one of the few places in a work tool where somebody is allowed to just
   enjoy themselves. */
.sheet.pal-sheet { max-width:min(60rem, 94vw); }
/* The picker's form is a .sheet form in everything but the class name, so
   it takes the same padding and rhythm. */
.pal-form { padding:1.3rem 1.4rem 1.2rem; display:flex; flex-direction:column;
  gap:.6rem; }
.pal-form h3 { margin:0; font-size:1.05rem; font-weight:700; letter-spacing:-.015em; }
.pal-grid {
  display:grid; gap:.8rem; margin-top:.9rem;
  grid-template-columns:repeat(auto-fill, minmax(11.5rem, 1fr));
  /* There are two dozen palettes now, which is several screens of cards. The
     grid is the part that scrolls, so the heading, the textures switch and -
     the important one - the Apply button stay put instead of being pushed off
     the bottom of the screen where they cannot be reached. */
  max-height:min(54vh, 30rem); overflow-y:auto; overscroll-behavior:contain;
  padding:.2rem .3rem;
}

.pal {
  position:relative; display:flex; flex-direction:column; gap:.5rem;
  padding:.6rem .6rem .7rem; cursor:pointer; text-align:left;
  border:2px solid var(--line); border-radius:var(--r-md);
  background:var(--surface); color:var(--ink);
  transition:transform .3s var(--spring), border-color .2s, box-shadow .3s;
}
.pal:hover { transform:translateY(-4px) rotate(-.6deg); box-shadow:var(--shadow-2); }
.pal:active { transform:translateY(-1px) scale(.985); }
.pal:focus-visible { outline:2px solid var(--plum); outline-offset:3px; }
.pal.on { border-color:var(--plum); }

/* The miniature. aspect-ratio rather than a height, so the cards stay in
   proportion at every column width the grid settles on. */
.pal-mini {
  position:relative; aspect-ratio:16/10; border-radius:calc(var(--r-md) - 6px);
  overflow:hidden; padding:.5rem; display:flex; align-items:flex-end;
  box-shadow:inset 0 0 0 1px rgba(128,128,128,.16);
}
.pal-panel {
  width:74%; border-radius:7px; padding:.42rem .45rem;
  display:flex; flex-direction:column; gap:.28rem;
  box-shadow:0 2px 8px rgba(0,0,0,.14);
}
.pal-line { height:5px; border-radius:3px; width:78%; }
.pal-line.short { width:50%; }
.pal-dots { position:absolute; top:.45rem; right:.5rem; display:flex; gap:.22rem; }
.pal-dots i { width:.52rem; height:.52rem; border-radius:50%; display:block;
  box-shadow:0 0 0 1.5px rgba(255,255,255,.28); }

/* The emoji sits in the name row now, not on the mini - a round badge over
   the mini's rounded corner always left an arc showing behind it. Here there
   is nothing behind it to show. A gentle bob on the chosen one keeps the bit
   of fun the perched badge had. */
.pal-name .pal-emoji { font-size:1.05rem; line-height:1; flex:0 0 auto;
  transition:transform .42s var(--spring); }
.pal:hover .pal-name .pal-emoji { transform:scale(1.2) rotate(-8deg); }
.pal.on .pal-name .pal-emoji { animation:pal-bob 2.6s ease-in-out infinite; }
@keyframes pal-bob {
  0%, 100% { transform:translateY(0) rotate(0deg); }
  50%      { transform:translateY(-2px) rotate(7deg); }
}
@media (prefers-reduced-motion:reduce) {
  .pal-name .pal-emoji, .pal:hover .pal-name .pal-emoji { transition:none; transform:none; }
  .pal.on .pal-name .pal-emoji { animation:none; }
}

/* Pastel has no emoji, so its dots get a touch bigger - the colours are what
   it has to show. */
.pal[data-key="pastel"] .pal-dots i { width:.62rem; height:.62rem; }

.pal-name { display:flex; align-items:center; gap:.4rem; min-width:0; }
.pal-name b { font-size:.86rem; font-weight:700; letter-spacing:-.01em;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pal-emoji { font-size:1rem; line-height:1; flex:0 0 auto; }
.pal-note { font-size:.72rem; color:var(--muted); line-height:1.35;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  overflow:hidden; }

/* The tick lands with a little bounce when a card becomes the chosen one. */
.pal-tick {
  position:absolute; top:-.45rem; right:-.45rem;
  width:1.5rem; height:1.5rem; border-radius:50%;
  background:var(--plum); color:var(--on-plum); font-size:.78rem; font-weight:700;
  display:grid; place-items:center; box-shadow:var(--shadow-1);
  opacity:0; transform:scale(.3); transition:opacity .2s, transform .34s var(--spring);
}
.pal.on .pal-tick { opacity:1; transform:scale(1); }

/* Follow-the-system, apart from the nine because it is not one of them. */
.pal-auto {
  display:flex; align-items:center; gap:.55rem; flex-wrap:wrap;
  margin-top:.9rem; width:100%; cursor:pointer; text-align:left;
  padding:.75rem .9rem; border-radius:var(--r-md);
  border:2px dashed var(--line-firm); background:var(--tone); color:var(--ink);
  transition:border-color .2s, background .2s, transform .25s var(--spring);
}
.pal-auto:hover { border-color:var(--plum); transform:translateY(-2px); }
.pal-auto:focus-visible { outline:2px solid var(--plum); outline-offset:3px; }
.pal-auto.on { border-style:solid; border-color:var(--plum); background:var(--plum-soft); }
.pal-auto b { font-size:.84rem; }
.pal-auto .pal-note { flex:1 1 14rem; margin-left:auto; text-align:right; }

@media (prefers-reduced-motion:reduce) {
  .pal, .pal-auto, .pal-tick { transition:opacity .12s linear; }
  .pal:hover, .pal:active, .pal-auto:hover { transform:none; }
  .pal.on .pal-tick { transform:none; }
}

@media (max-width:34rem) {
  .pal-grid { grid-template-columns:repeat(auto-fill, minmax(8.5rem, 1fr)); }
  .pal-auto .pal-note { text-align:left; margin-left:0; }
}

/* ══ the colours button ══════════════════════════════════════════════
   It wears whichever palette is on - a wave for Sea, a bee for Bee - so it
   reads as a readout as well as a way in. It was a hardcoded moon, which
   stopped being true the moment there were more than two. */
.palbtn #theme-face { display:grid; place-items:center; line-height:1; }
.palbtn .face-emoji { font-size:1.02rem; }
.palbtn .face-swatch { display:flex; gap:2px; }
.palbtn .face-swatch i { width:5px; height:5px; border-radius:50%; display:block; }
.palbtn:hover #theme-face { transform:scale(1.12) rotate(-8deg); }
#theme-face { transition:transform .34s var(--spring); }
@media (prefers-reduced-motion:reduce) {
  #theme-face, .palbtn:hover #theme-face { transition:none; transform:none; }
}

/* ══ screen control ══════════════════════════════════════════════════
   Somebody else is using this computer. That is not a state to be subtle
   about, so the banner is fixed, cannot be dismissed, and carries the way
   out. It sits above everything including the call. */
.control-bar {
  position:fixed; left:50%; top:.9rem; transform:translateX(-50%);
  z-index:9999; display:flex; align-items:center; gap:.7rem;
  padding:.55rem .6rem .55rem .95rem; border-radius:999px;
  background:var(--danger); color:var(--on-danger);
  box-shadow:var(--shadow-2); font-size:.82rem; font-weight:700;
  max-width:calc(100vw - 2rem);
}
.control-bar .control-hint { font-weight:600; opacity:.85; font-size:.76rem; }
.control-bar .ctl { padding:.36rem .8rem; font-size:.76rem;
  background:var(--on-danger); color:var(--danger); }
.control-bar .ctl:hover { background:var(--on-danger); transform:translateY(-1px); }
.control-dot { width:.55rem; height:.55rem; border-radius:50%;
  background:var(--on-danger); animation:control-pulse 1.4s ease-in-out infinite; }
@keyframes control-pulse { 0%,100% { opacity:1; } 50% { opacity:.35; } }

/* The tile you are driving through. The crosshair is the tell that a click
   here lands on somebody else's machine rather than on this page. */
.tile video.driving { cursor:crosshair; }
.tile.screen:has(video.driving) { outline:3px solid var(--danger); outline-offset:-3px; }

.sheet.control-ask .sheet-note.strong { color:var(--danger); font-weight:600; }
.iconbtn.takeover { margin-left:.35rem; }
.person .iconbtn.takeover { opacity:0; transition:opacity .18s; }
.person:hover .iconbtn.takeover, .iconbtn.takeover:focus-visible { opacity:1; }

@media (prefers-reduced-motion:reduce) {
  .control-dot { animation:none; }
}

/* ══ the mark ════════════════════════════════════════════════════════
   Top left, sized to sit level with the search field beside it rather
   than to be noticed. A logo that shouts on every screen of a tool people
   look at all day is a logo they stop seeing. */
.brand-slot { flex:0 0 auto; }
.brand { display:flex; align-items:center; gap:.5rem; padding:.15rem .2rem;
  user-select:none; }
.brand-badge { width:2rem; height:2rem; border-radius:11px; display:grid;
  place-items:center; background:var(--plum); flex:0 0 auto;
  box-shadow:0 6px 16px -8px color-mix(in srgb, var(--plum) 75%, transparent);
  transition:transform .34s var(--spring); }
.brand-badge svg { width:22px; height:22px; display:block; }
.brand:hover .brand-badge { transform:rotate(-7deg) scale(1.06); }
.brand-word { font-size:1.02rem; font-weight:700; letter-spacing:-.02em;
  color:var(--ink); }

/* The name is the first thing to go when the bar gets tight - the mark
   alone still says which app this is. */
@media (max-width:64rem) { .brand-word { display:none; } }
@media (prefers-reduced-motion:reduce) {
  .brand-badge, .brand:hover .brand-badge { transition:none; transform:none; }
}

/* ══ engines & cost (admin) ══════════════════════════════════════════
   What is translating and captioning right now, and whether it runs a
   meter. The one word that matters - Free / Metered / Off - is coloured,
   so the answer is legible at a glance rather than read out of a
   paragraph. */
.engines { display:grid; gap:.7rem; padding:1rem 1.35rem 1.2rem; }
.engine { padding:.75rem .9rem; border:1px solid var(--line);
  border-radius:var(--r-md); background:var(--tone); }
.engine.off { opacity:.72; }
.engine-head { display:flex; align-items:center; gap:.6rem; }
.engine-head b { font-size:.9rem; font-weight:700; }
.engine-tag { margin-left:auto; font-size:.68rem; font-weight:800;
  letter-spacing:.04em; text-transform:uppercase; border-radius:999px;
  padding:.2rem .6rem; }
.engine-tag.is-free { background:var(--live-soft); color:var(--live); }
.engine-tag.is-metered { background:var(--warn-soft); color:var(--warn); }
.engine-tag.is-off { background:var(--well); color:var(--faint); }
.engine-engine { display:flex; align-items:center; gap:.45rem;
  margin-top:.4rem; font-size:.82rem; font-weight:600; color:var(--ink); }
.engine-dot { width:.55rem; height:.55rem; border-radius:50%; flex:0 0 auto; }
.engine-dot.free { background:var(--live); }
.engine-dot.paid { background:var(--warn); }
.engine-note { margin-top:.3rem; font-size:.76rem; line-height:1.45;
  color:var(--muted); }

/* ══ the call in its own window ═══════════════════════════════════════
   In the desktop app a call opens as its own window (?call=1), like Teams -
   the main window keeps your chats. That window shows only the call: no
   rail, no topbar, no other views, and the call fills it. */
body.popped .topbar,
body.popped .rail { display: none !important; }
body.popped .app { grid-template-columns: 1fr !important; height: 100vh; }
body.popped .view:not(#view-call) { display: none !important; }
/* Grid, not flex: .view.on is display:grid and stretches the card to fill.
   A flex row sized the card to its *content* instead - so the call sat in a
   ~960px column with dead space beside it, and the width jittered as tiles
   loaded and changed that content width. */
body.popped #view-call { display: grid !important;
  grid-template-columns: 1fr; grid-template-rows: 1fr; }
body.popped #view-call .card { width: 100%; height: 100%; }
/* The pop-out button is pointless inside the pop-out. */
body.popped #popout { display: none; }

/* ══ meeting description ═════════════════════════════════════════════ */
#mt-desc { font-family:inherit; font-size:.9rem; color:var(--ink);
  background:var(--well); border:0; border-radius:var(--r-md);
  padding:.6rem .85rem; resize:vertical; min-height:2.6rem; line-height:1.45; }
#mt-desc:focus { outline:2px solid var(--plum); outline-offset:0; }
.sheet label .opt { font-weight:500; color:var(--faint); font-size:.72rem; }
.ms-desc { margin:.2rem 0 .6rem; font-size:.86rem; line-height:1.5;
  color:var(--ink); background:var(--tone); border-radius:var(--r-md);
  padding:.6rem .8rem; white-space:pre-wrap; }

/* ══ the textures toggle (in the palette picker) ═════════════════════ */
.pal-textures { display:flex; align-items:flex-start; gap:.7rem; cursor:pointer;
  margin-top:1rem; padding:.8rem .9rem; border-radius:var(--r-md);
  background:var(--tone); border:1.5px solid var(--line); }
.pal-textures:hover { border-color:var(--plum); }
.pal-textures input { position:absolute; opacity:0; width:0; height:0; }
.pal-tex-track { flex:0 0 auto; width:2.6rem; height:1.5rem; border-radius:999px;
  background:var(--line-firm); position:relative; margin-top:.1rem;
  transition:background .25s var(--spring); }
.pal-tex-track::after { content:""; position:absolute; top:.18rem; left:.18rem;
  width:1.14rem; height:1.14rem; border-radius:50%; background:var(--surface);
  box-shadow:var(--shadow-1); transition:transform .25s var(--spring); }
.pal-textures input:checked + .pal-tex-track { background:var(--plum); }
.pal-textures input:checked + .pal-tex-track::after { transform:translateX(1.1rem); }
.pal-textures input:focus-visible + .pal-tex-track { outline:2px solid var(--plum);
  outline-offset:2px; }
.pal-tex-text { display:flex; flex-direction:column; gap:.15rem; }
.pal-tex-text b { font-size:.86rem; font-weight:700; }

/* ══ the Calls home ══════════════════════════════════════════════════
   The Call tab before a call: start one, see what is coming up and what
   happened recently. The live surface (#call-live) takes over when a call
   is joined; a popped-out window is only ever the call. */
#view-call { position:relative; }
#call-home { display:grid; }
#call-live { display:none; }
#view-call.live #call-home { display:none; }
#view-call.live #call-live { display:grid; }
body.popped #call-home { display:none !important; }
body.popped #call-live { display:grid !important; }

#call-home {
  grid-template-columns:minmax(0,1.3fr) minmax(0,1fr);
  gap:1rem; padding:1rem; min-height:0; align-items:stretch;
}
#call-home .card { display:flex; flex-direction:column; min-height:0; overflow:hidden; }
.ch-start { min-height:0; }
.ch-search { display:flex; align-items:center; gap:.55rem; margin:0 1.35rem;
  background:var(--well); border-radius:999px; padding:.55rem 1rem; }
.ch-search svg { width:16px; height:16px; color:var(--faint); flex:none; }
.ch-search input { flex:1; background:none; border:0; font-size:.9rem; color:var(--ink); }
.ch-search input:focus { outline:none; }
.ch-people { flex:1; min-height:0; overflow-y:auto; padding:.6rem 1rem;
  display:flex; flex-direction:column; gap:.15rem; }
.ch-person { display:flex; align-items:center; gap:.7rem; padding:.5rem .6rem;
  border-radius:var(--r-md); transition:background .15s; }
.ch-person:hover { background:var(--tone); }
.ch-who { flex:1; min-width:0; display:flex; flex-direction:column; gap:.05rem; }
.ch-who b { font-size:.9rem; font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.ch-who span { font-size:.75rem; color:var(--faint); }
.ch-call { padding:.5rem .7rem; }
.ch-call svg { width:16px; height:16px; }
.ch-group { margin:.4rem 1.1rem 1.1rem; justify-content:center; }

.ch-side { display:flex; flex-direction:column; gap:1rem; min-height:0; }
.ch-card { flex:1; }
.ch-list { flex:1; min-height:0; overflow-y:auto; padding:.5rem .9rem 1rem;
  display:flex; flex-direction:column; gap:.4rem; }
.ch-item { display:flex; flex-direction:column; gap:.1rem; text-align:left;
  border:0; background:var(--tone); border-radius:var(--r-md);
  padding:.6rem .8rem; cursor:pointer; transition:background .15s, transform .15s var(--spring); }
button.ch-item:hover { background:var(--well); transform:translateY(-1px); }
.ch-item.quiet { background:none; cursor:default; padding:.4rem .3rem; }
.ch-item b { font-size:.85rem; font-weight:600; }
.ch-item span { font-size:.75rem; color:var(--faint); }

@media (max-width:60rem) {
  #call-home { grid-template-columns:minmax(0,1fr); overflow-y:auto; }
}

/* ══ Files tabs (shared vs mine) ═════════════════════════════════════ */
.files-tabs { display:flex; gap:.2rem; background:var(--well); border-radius:999px;
  padding:3px; margin-left:.4rem; }
.files-tabs button { border:0; background:none; cursor:pointer; font:inherit;
  font-size:.76rem; font-weight:600; color:var(--muted); border-radius:999px;
  padding:.34rem .8rem; transition:background .2s var(--spring), color .2s; }
.files-tabs button[aria-selected="true"] { background:var(--surface); color:var(--ink);
  box-shadow:var(--shadow-1); }
.files-tabs button:focus-visible { outline:2px solid var(--plum); outline-offset:1px; }
.bar:has(.files-tabs) { flex-wrap:wrap; gap:.6rem; }

/* ══ the master console ══════════════════════════════════════════════
   The system owner's own page - its own screen, not a tab in a company
   workspace, because a master belongs to no company. */
/* The app shell pins body to 100vh with overflow hidden, so its inner panes
   scroll and the page never does. The console is a plain document, not that
   shell, so it opts out: the page scrolls, and the topbar sticks to the top
   of it rather than scrolling away with a long company list. */
body.master { background:var(--ground); display:block;
  height:auto; min-height:100vh; overflow-y:auto; }
body.master .topbar { position:sticky; top:0; z-index:20;
  background:var(--ground); }
.master-tag { font-size:.68rem; font-weight:700; letter-spacing:.04em;
  text-transform:uppercase; background:var(--plum-soft); color:var(--plum-ink);
  border-radius:999px; padding:.2rem .6rem; margin-left:.2rem; }
.master-wrap { max-width:56rem; margin:0 auto; padding:1.5rem 1.25rem 3rem; }
.master-intro { padding-bottom:1.2rem; }
.master-note { padding:0 1.35rem; margin:.2rem 0 0; }
.master-companies { display:flex; flex-direction:column; gap:.8rem;
  padding:1rem 1.35rem 0; }
.master-add { margin:1.1rem 1.35rem 0; align-self:flex-start; }

.master-co { border:1.5px solid var(--line); border-radius:var(--r-lg);
  padding:1rem 1.1rem; background:var(--tone); }
.master-co.off { opacity:.72; border-style:dashed; }
.co-head { display:flex; align-items:center; gap:.7rem; }
.co-title { display:flex; align-items:baseline; gap:.5rem; min-width:0; }
.co-title b { font-size:1rem; font-weight:700; }
.co-slug { font-size:.75rem; color:var(--faint); }
.co-state { margin-left:auto; font-size:.68rem; font-weight:800;
  letter-spacing:.04em; text-transform:uppercase; border-radius:999px;
  padding:.2rem .6rem; }
.co-state.on { background:var(--live-soft); color:var(--live); }
.co-state.off { background:var(--danger-soft); color:var(--danger); }
.co-meta { display:flex; flex-wrap:wrap; gap:.3rem 1rem; margin-top:.5rem;
  font-size:.78rem; color:var(--muted); }
.co-meta span { font-variant-numeric:tabular-nums; }
.co-admins { display:flex; flex-wrap:wrap; align-items:center; gap:.4rem;
  margin-top:.6rem; }
.co-admins-label { font-size:.7rem; font-weight:700; text-transform:uppercase;
  letter-spacing:.04em; color:var(--faint); }
.co-admin { font-size:.76rem; background:var(--surface); border-radius:999px;
  padding:.2rem .6rem; color:var(--ink); }
.co-actions { display:flex; justify-content:space-between; align-items:center;
  gap:.6rem; margin-top:.7rem; }

/* A company card opens its roster. The whole card is the target, so the
   affordance says so, and the Disable button stops the click from bubbling
   into it. */
.master-co { cursor:pointer; transition:border-color .15s, background .15s; }
.master-co:hover { border-color:var(--plum); background:var(--surface); }
.master-co:focus-visible { outline:2px solid var(--plum); outline-offset:2px; }
.co-open { font-size:.78rem; font-weight:700; color:var(--plum);
  display:inline-flex; align-items:center; gap:.3rem; }
.co-open svg { width:14px; height:14px; }

/* ── the roster sheet: who is inside a company, and their admin toggle ── */
#company-sheet { max-width:34rem; }
.co-sheet-head { display:flex; align-items:baseline; gap:.5rem; flex-wrap:wrap; }
.co-sheet-head h3 { margin:0; }
.co-sheet-sub { font-size:.78rem; color:var(--muted); margin:.25rem 0 .1rem;
  font-variant-numeric:tabular-nums; }
.co-people { display:flex; flex-direction:column; gap:.35rem;
  max-height:min(58vh, 30rem); overflow-y:auto; margin:.7rem 0 0;
  padding:.15rem; }
.co-person { display:flex; align-items:center; gap:.7rem;
  padding:.55rem .7rem; border:1px solid var(--line); border-radius:var(--r-md);
  background:var(--tone); }
.cp-main { min-width:0; flex:1; }
.cp-name { font-size:.9rem; font-weight:600; display:flex; align-items:center;
  gap:.45rem; }
.cp-email { font-size:.75rem; color:var(--faint); white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; }
.role-tag { font-size:.62rem; font-weight:800; letter-spacing:.04em;
  text-transform:uppercase; border-radius:999px; padding:.15rem .5rem; flex:none; }
.role-tag.owner  { background:var(--plum-soft);   color:var(--plum-ink); }
.role-tag.admin  { background:var(--live-soft);    color:var(--live); }
.role-tag.member { background:var(--surface);      color:var(--muted); }
.cp-suspended { color:var(--danger); font-weight:700; }
.cp-btn { flex:none; font-size:.76rem; font-weight:700; border:1.5px solid var(--line);
  background:var(--surface); color:var(--ink); border-radius:999px;
  padding:.32rem .7rem; cursor:pointer; transition:border-color .15s, color .15s; }
.cp-btn:hover { border-color:var(--plum); color:var(--plum); }
.cp-btn.revoke:hover { border-color:var(--danger); color:var(--danger); }
.cp-btn:disabled { opacity:.45; cursor:default; }
.co-people-empty { color:var(--muted); font-size:.85rem; padding:.6rem .2rem; }
.nc-sub { margin:.6rem 0 0; font-size:.8rem; font-weight:700; color:var(--muted); }
.nc-hint { margin:0; }
#new-company-form label { display:flex; flex-direction:column; gap:.3rem;
  font-size:.775rem; font-weight:600; color:var(--muted); }
#new-company-form input, #new-company-form select { font-family:inherit;
  font-size:.9rem; color:var(--ink); background:var(--well); border:0;
  border-radius:var(--r-md); padding:.6rem .85rem; }
#new-company-form input:focus, #new-company-form select:focus {
  outline:2px solid var(--plum); outline-offset:0; }

/* ══ palette reveal ══════════════════════════════════════════════════
   The ~2.5-3s scene when a palette is picked. A fixed, click-through overlay
   above everything, built in layers by palette-fx.js: a wash, a glow, an
   optional hero glyph, a crowd of particles, and an optional veil or flash.
   The overlay carries --fxdur (the headline length); each particle carries
   its own --x/--x2/--y/--r/--s/--d/--t. The motion class picks the keyframes
   and positions the glow and hero. Cleared by the script when it ends. */
.theme-fx { position:fixed; inset:0; z-index:9998; pointer-events:none;
  overflow:hidden; display:none; --fxdur:2.6s; }
.theme-fx.on { display:block; }
/* No filter here, deliberately. `filter: drop-shadow` costs a blur pass and
   its own compositor layer *per element*, and a scene has fifty of them -
   measured at 52 filtered nodes, which is work done every frame for
   decoration nobody looks at directly. The hero glyph keeps its shadow:
   that is one element, not fifty. */
.fx-bit { position:absolute; font-size:1.7em; line-height:1;
  will-change:transform, opacity; }

/* ── background layers ────────────────────────────────────────────── */
/* Wash: the new palette's accent flooding up the screen. */
.fx-wash { position:absolute; inset:0; opacity:0;
  background:radial-gradient(135% 95% at 50% 120%, var(--plum) 0%, transparent 60%);
  animation:fxWash var(--fxdur) ease-out both; }
@keyframes fxWash {
  0% { opacity:0; transform:scale(1.15); }
  22% { opacity:.34; }
  65% { opacity:.14; }
  100% { opacity:0; transform:scale(1); } }

/* Glow: a soft bloom that swells and fades; screen-blended so it lifts the
   colour rather than greying it. Centred by default; some motions move it. */
.fx-glow { position:absolute; inset:-25%; opacity:0; mix-blend-mode:screen;
  background:radial-gradient(closest-side, var(--plum), transparent 72%);
  animation:fxGlow var(--fxdur) ease-in-out both; }
@keyframes fxGlow {
  0% { opacity:0; transform:scale(.55); }
  32% { opacity:.30; }
  70% { opacity:.16; }
  100% { opacity:0; transform:scale(1.15); } }
.theme-fx.fx-rise .fx-glow {
  inset:auto -25% -45% -25%; height:80%; }
.theme-fx.fx-fall .fx-glow, .theme-fx.fx-bloom .fx-glow {
  inset:-45% -25% auto -25%; height:80%; }

/* Veil: the edges close in (frost crystallising, night falling). */
.fx-veil { position:absolute; inset:0; opacity:0;
  box-shadow:inset 0 0 24vmax 9vmax var(--plum);
  animation:fxVeil var(--fxdur) ease-out both; }
@keyframes fxVeil { 0% { opacity:0; } 40% { opacity:.42; } 100% { opacity:0; } }

/* Flash: a quick full-screen hit of accent, for the electric palettes. */
.fx-flash { position:absolute; inset:0; opacity:0; background:var(--plum);
  mix-blend-mode:screen; animation:fxFlash .55s ease-out both; }
@keyframes fxFlash { 0% { opacity:0; } 12% { opacity:.55; } 100% { opacity:0; } }

/* Hero: the one big focal glyph. Base sits dead centre; each motion gives it
   its own entrance. */
/* The hero glyph: the thing the palette is named after, big, in the middle,
   with the small stuff going on around it.

   The default animation lives HERE, not only on the four motions that had
   one. That was a real regression: a palette declares `hero` in the script,
   the element is built with opacity:0, and if its motion had no `.fx-hero`
   rule there was nothing to reveal it - so Lobster's lobster and Mango's
   mango were created and never shown, leaving only the little particles.
   Seven palettes were silently heroless. Now every hero arrives by default,
   and the four with something specific to say override this. */
.fx-hero { position:absolute; left:50%; top:50%; font-size:11em; line-height:1;
  opacity:0; will-change:transform, opacity;
  filter:drop-shadow(0 10px 26px rgba(0,0,0,.32));
  animation:fxHero var(--fxdur) cubic-bezier(.2,.7,.2,1) both; }
@keyframes fxHero {
  0% { opacity:0; transform:translate(-50%,-40%) scale(.3) rotate(-10deg); }
  20% { opacity:1; }
  45% { transform:translate(-50%,-50%) scale(1) rotate(0deg); }
  72% { opacity:1; transform:translate(-50%,-52%) scale(1.05) rotate(2deg); }
  100% { opacity:0; transform:translate(-50%,-56%) scale(1.12) rotate(0deg); } }
.theme-fx.fx-night .fx-hero { animation:fxMoon var(--fxdur) cubic-bezier(.2,.7,.2,1) both; }
@keyframes fxMoon {
  0% { opacity:0; transform:translate(-50%,32vh) scale(.6) rotate(-16deg); }
  30% { opacity:1; }
  55% { transform:translate(-50%,-50%) scale(1) rotate(0); }
  82% { opacity:.95; }
  100% { opacity:0; transform:translate(-50%,-54%) scale(1.05); } }
.theme-fx.fx-bloom .fx-hero { animation:fxSun var(--fxdur) ease-out both; }
@keyframes fxSun {
  0% { opacity:0; transform:translate(-50%,-50%) scale(.2) rotate(0); }
  38% { opacity:.95; transform:translate(-50%,-50%) scale(1) rotate(30deg); }
  70% { opacity:.5; }
  100% { opacity:0; transform:translate(-50%,-50%) scale(1.55) rotate(80deg); } }
/* Sea is a tsunami drawn frame by frame on a canvas (palette-fx.js,
   seaScene) rather than out of DOM shapes - so it has no centred hero and
   no CSS layers of its own. The canvas fills the overlay; the script paints
   the old palette ahead of the wave and leaves the new one showing behind it,
   then fades out once the water has fully left the screen. */
.fx-canvas { position:absolute; inset:0; width:100%; height:100%;
  display:block; transition:opacity .1s linear; }

/* The sweep every other palette arrives behind: a panel in the new palette's
   own colours, wider than the viewport, crossing it left to right. It is
   opaque across its middle, so for the middle stretch of its travel the
   screen is fully covered - that is when palette-fx.js switches the theme,
   which is why the change is never seen as a jump. The easing is symmetric so
   halfway through the duration really is halfway across. */
/* One screen wide, not two and a bit.
   It used to be 220vw so that its middle covered the viewport somewhere in
   the middle of a single sweep - which at 1920x1080 is a 4224x1188 layer,
   five megapixels of texture being composited and moved for the whole scene.
   A curtain does the same job for less than half of that: slide in, HOLD
   while the palette is switched behind it, slide out. The hold is also why
   it can be fully opaque instead of feathered - it covers exactly, so there
   are no soft edges for the corners to show through. */
.fx-wipe { position:absolute; top:-5vh; left:0; height:110vh; width:100vw;
  z-index:0; transform:translateX(-101%);
  animation:fxWipe var(--fxdur) cubic-bezier(.45,0,.55,1) both; }
@keyframes fxWipe {
  0% { transform:translateX(-101%); }
  42% { transform:translateX(0); }
  58% { transform:translateX(0); }
  100% { transform:translateX(101%); } }
.theme-fx.fx-rise .fx-hero { top:auto; bottom:-8vh; font-size:12em;
  animation:fxBigFlame var(--fxdur) ease-out both; }
@keyframes fxBigFlame {
  0% { opacity:0; transform:translate(-50%,32vh) scale(.7); }
  26% { opacity:.85; transform:translate(-50%,0) scale(1); }
  60% { opacity:.5; transform:translate(-50%,-12vh) scale(1.06); }
  100% { opacity:0; transform:translate(-50%,-42vh) scale(.9); } }
.theme-fx.fx-zap .fx-hero { animation:fxBigZap var(--fxdur) steps(4, end) both; }
@keyframes fxBigZap {
  0% { opacity:0; transform:translate(-50%,-50%) scale(.4); }
  8% { opacity:1; transform:translate(-50%,-50%) scale(1.35); }
  16% { opacity:.1; } 24% { opacity:1; } 40% { opacity:.2; }
  55% { opacity:1; } 75% { opacity:.3; }
  100% { opacity:0; transform:translate(-50%,-50%) scale(1); } }

/* ── the particle crowds, one motion each ─────────────────────────── */
/* Bee: a swarm streaming left to right, riding a long wobble. */
.theme-fx.fx-swarm .fx-bit { left:-14vw; top:var(--y);
  animation:fxSwarm var(--t) linear var(--d) both; }
@keyframes fxSwarm {
  0% { transform:translate(0,0) scale(var(--s)); opacity:0; }
  8% { opacity:1; }
  25% { transform:translate(32vw,-7vh) rotate(11deg) scale(var(--s)); }
  50% { transform:translate(60vw,7vh) rotate(-9deg) scale(var(--s)); }
  75% { transform:translate(92vw,-5vh) rotate(9deg) scale(var(--s)); }
  92% { opacity:1; }
  100% { transform:translate(130vw,4vh) rotate(-6deg) scale(var(--s)); opacity:0; } }

/* Critters (Autumn's hedgehogs): they trundle along the foot of the screen
   rather than falling, flipped to face the way they are going, with a little
   bounce in the step. Above the falling leaves so they are not buried. */
.fx-critter { position:absolute; left:0; z-index:2; font-size:2.6em;
  line-height:1; filter:drop-shadow(0 3px 6px rgba(0,0,0,.3));
  animation:fxScurry var(--t) linear var(--d) both; }
@keyframes fxScurry {
  0% { transform:translateX(-12vw) scaleX(-1) scale(var(--s)); opacity:0; }
  6% { opacity:1; }
  25% { transform:translateX(20vw) translateY(-1.2vh) scaleX(-1) scale(var(--s)); }
  50% { transform:translateX(50vw) scaleX(-1) scale(var(--s)); }
  75% { transform:translateX(80vw) translateY(-1.2vh) scaleX(-1) scale(var(--s)); }
  94% { opacity:1; }
  100% { transform:translateX(114vw) scaleX(-1) scale(var(--s)); opacity:0; } }

/* Nature: leaves and petals tumbling down, drifting sideways as they go. */
.theme-fx.fx-fall .fx-bit { left:var(--x); top:-14vh;
  animation:fxFall var(--t) ease-in var(--d) both; }
@keyframes fxFall {
  0% { transform:translate(0,0) rotate(0) scale(var(--s)); opacity:0; }
  10% { opacity:1; }
  50% { transform:translate(calc((var(--x2) - var(--x)) * .5),58vh)
          rotate(calc(var(--r) * .5)) scale(var(--s)); }
  100% { transform:translate(calc(var(--x2) - var(--x)),122vh)
           rotate(var(--r)) scale(var(--s)); opacity:.85; } }

/* Frost: slower, straighter snow with a gentle side-to-side sway. */
.theme-fx.fx-frost .fx-bit { left:var(--x); top:-14vh;
  animation:fxFrost var(--t) linear var(--d) both; }
@keyframes fxFrost {
  0% { transform:translate(0,0) rotate(0) scale(var(--s)); opacity:0; }
  12% { opacity:.95; }
  33% { transform:translate(4vw,32vh) rotate(60deg) scale(var(--s)); }
  66% { transform:translate(-4vw,72vh) rotate(150deg) scale(var(--s)); }
  100% { transform:translate(3vw,122vh) rotate(250deg) scale(var(--s)); opacity:.7; } }

/* Pastel: soft things drifting gently upward, bobbing side to side. */
.theme-fx.fx-float .fx-bit { left:var(--x); top:var(--y);
  animation:fxFloat var(--t) ease-in-out var(--d) both; }
@keyframes fxFloat {
  0% { transform:translate(0,0) scale(0); opacity:0; }
  22% { transform:translate(2vw,-6vh) scale(var(--s)); opacity:1; }
  60% { transform:translate(-2vw,-17vh) scale(var(--s)); opacity:1; }
  100% { transform:translate(1vw,-30vh) scale(calc(var(--s) * .9)); opacity:0; } }

/* Ember: sparks climbing from the bottom, swaying and cooling as they rise. */
.theme-fx.fx-rise .fx-bit { left:var(--x); top:auto; bottom:-12vh;
  animation:fxRise var(--t) ease-out var(--d) both; }
@keyframes fxRise {
  0% { transform:translate(0,0) scale(var(--s)); opacity:0; }
  12% { opacity:1; }
  45% { transform:translate(calc((var(--x2) - var(--x)) * .5),-56vh)
          scale(calc(var(--s) * .85)); opacity:.9; }
  100% { transform:translate(calc(var(--x2) - var(--x)),-124vh)
           scale(calc(var(--s) * .4)) rotate(var(--r)); opacity:0; } }

/* Neon: glyphs snapping in, flickering, cutting out - all edges, no easing. */
.theme-fx.fx-zap .fx-bit { left:var(--x); top:var(--y);
  animation:fxZap var(--t) steps(3, end) var(--d) both; }
@keyframes fxZap {
  0% { transform:scale(0) rotate(var(--r)); opacity:0; }
  10% { transform:scale(calc(var(--s) * 1.5)); opacity:1; }
  20% { opacity:.2; } 30% { opacity:1; } 45% { opacity:.3; }
  60% { opacity:1; } 80% { opacity:.4; }
  100% { transform:scale(var(--s)); opacity:0; } }

/* Porcelain / day: a warm bloom, glyphs opening and spinning away. */
.theme-fx.fx-bloom .fx-bit { left:var(--x); top:var(--y);
  animation:fxBloom var(--t) ease-out var(--d) both; }
@keyframes fxBloom {
  0% { transform:scale(0) rotate(0); opacity:0; }
  30% { opacity:1; }
  100% { transform:scale(calc(var(--s) * 1.8)) rotate(55deg); opacity:0; } }

/* Midnight: stars coming out - twinkling up in place, drifting, fading. */
.theme-fx.fx-night .fx-bit { left:var(--x); top:var(--y);
  animation:fxNight var(--t) ease-in-out var(--d) both; }
@keyframes fxNight {
  0% { transform:scale(0); opacity:0; }
  30% { transform:scale(var(--s)); opacity:1; }
  60% { transform:scale(calc(var(--s) * .88)); opacity:.85; }
  100% { transform:scale(calc(var(--s) * 1.05)) translateY(-4vh); opacity:0; } }

@media (prefers-reduced-motion: reduce) {
  /* The script already skips the scene; this is the belt-and-braces stop for
     anything that slipped through. */
  .theme-fx { display:none !important; } }

/* ══ teams and channels ══════════════════════════════════════════════
   The sidebar's lower half. Channels are threads, so this is presentation
   only: a collapsible team heading and a list of channels under it.

   Both lists scroll independently and the teams block is capped, because a
   company with a dozen teams would otherwise push the chats off the top and
   a company with none would leave a gap. */
.teams-block { flex:0 1 auto; min-height:0; display:flex; flex-direction:column;
  border-top:1px solid var(--line); }
.teams-head { display:flex; align-items:center; gap:.4rem;
  padding:.6rem .95rem .3rem; font-size:.665rem; font-weight:700;
  letter-spacing:.06em; text-transform:uppercase; color:var(--faint); }
.teams-head span { flex:1; min-width:0; }
.teams-add { width:1.5rem; height:1.5rem; flex:none; border:0; background:none;
  border-radius:999px; color:var(--faint); cursor:pointer; display:grid;
  place-items:center; transition:background .2s, color .2s; }
.teams-add svg { width:13px; height:13px; }
.teams-add:hover { background:var(--plum-soft); color:var(--plum); }
.teams-add:focus-visible { outline:2px solid var(--plum); outline-offset:1px; }
.teamlist { overflow-y:auto; padding:0 .6rem .7rem; display:flex;
  flex-direction:column; gap:.1rem; max-height:min(42vh, 22rem); }

.team-row { display:flex; align-items:center; gap:.2rem; }
.team-name { flex:1; min-width:0; display:flex; align-items:center; gap:.4rem;
  border:0; background:none; cursor:pointer; color:inherit; text-align:left;
  font:inherit; padding:.42rem .5rem; border-radius:var(--r-md);
  transition:background .2s; }
.team-name:hover { background:var(--tone); }
.team-name:focus-visible { outline:2px solid var(--plum); outline-offset:-2px; }
.team-name b { font-size:.845rem; font-weight:700; white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis; }
/* The twisty points down when open, right when shut. */
.team-name svg { width:13px; height:13px; flex:none; color:var(--faint);
  transform:rotate(90deg); transition:transform .2s; }
.team-row.shut .team-name svg { transform:rotate(0deg); }
.team-plus, .team-del { width:1.5rem; height:1.5rem; flex:none; border:0;
  background:none; border-radius:999px; color:var(--faint); cursor:pointer;
  display:grid; place-items:center; opacity:0;
  transition:background .2s, color .2s, opacity .2s; }
.team-plus svg { width:12px; height:12px; }
.team-del svg { width:12px; height:12px; }
.team-row:hover .team-plus, .team-plus:focus-visible,
.team-row:hover .team-del, .team-del:focus-visible { opacity:1; }
.team-plus:hover { background:var(--plum-soft); color:var(--plum); }
/* Deleting a team takes every channel in it, so it is the one control here
   that turns red before you press it. */
.team-del:hover { background:var(--danger-soft); color:var(--danger); }
.team-del:focus-visible { outline:2px solid var(--danger); outline-offset:1px; }

/* The channel and its delete control sit side by side; the channel takes
   whatever is left, so the name still truncates rather than pushing the
   control off the edge. */
.chan-row { display:flex; align-items:center; gap:.1rem; }
.chan { display:flex; align-items:center; gap:.45rem; flex:1; min-width:0;
  padding:.38rem .55rem .38rem 1.5rem; border:0; background:none;
  border-radius:var(--r-md); cursor:pointer; color:inherit; text-align:left;
  font:inherit; transition:background .2s; }
/* Hidden until the row is hovered or it is focused, so a sidebar of channels
   is not a row of delete buttons. Never hidden from the keyboard: tabbing to
   it brings it into view. */
.chan-del { width:1.45rem; height:1.45rem; flex:none; border:0; background:none;
  border-radius:999px; color:var(--faint); cursor:pointer; display:grid;
  place-items:center; opacity:0;
  transition:background .2s, color .2s, opacity .2s; }
.chan-del svg { width:12px; height:12px; }
.chan-row:hover .chan-del, .chan-del:focus-visible { opacity:1; }
.chan-del:hover { background:var(--danger-soft); color:var(--danger); }
.chan-del:focus-visible { outline:2px solid var(--danger); outline-offset:1px; }
.chan:hover { background:var(--tone); }
.chan:focus-visible { outline:2px solid var(--plum); outline-offset:-2px; }
.chan[aria-current="true"] { background:var(--plum-soft); }
.chan[aria-current="true"] .chan-name { color:var(--plum-ink); font-weight:700; }
.chan-hash { flex:none; font-size:.78rem; color:var(--faint); width:1rem;
  text-align:center; }
.chan-name { font-size:.82rem; white-space:nowrap; overflow:hidden;
  text-overflow:ellipsis; }
/* A private channel is named plainly but marked, so nobody has to guess
   whether what they are about to write is company-wide. */
.chan.locked .chan-name { font-style:italic; }
.chan-none { padding:.2rem .55rem .4rem 1.5rem; font-size:.75rem; }

/* The private switch in the new-channel sheet. */
.chan-priv { flex-direction:row !important; align-items:flex-start; gap:.55rem; }
.chan-priv > span { display:flex; flex-direction:column; gap:.15rem; }
.chan-priv b { font-size:.82rem; font-weight:700; color:var(--ink); }

/* The delete control on a file row. Same manners as the channel one: hidden
   until the row is hovered, red on approach, never hidden from the keyboard.
   Only rendered when the server would actually allow it - yours, or anyone's
   if you are an admin. */
/* A fourth track, only on rows that actually carry the control - the grid is
   otherwise `auto 1fr auto` with everything explicitly placed. */
.file:has(.fdel) { grid-template-columns:auto 1fr auto auto; }
.file .fdel { grid-column:4; grid-row:1/span 2; width:1.7rem; height:1.7rem; border:0;
  background:none; border-radius:999px; color:var(--faint); cursor:pointer;
  display:grid; place-items:center; opacity:0;
  transition:background .2s, color .2s, opacity .2s; }
.file .fdel svg { width:14px; height:14px; }
.file:hover .fdel, .file .fdel:focus-visible { opacity:1; }
.file .fdel:hover { background:var(--danger-soft); color:var(--danger); }
.file .fdel:focus-visible { outline:2px solid var(--danger); outline-offset:1px; }

/* ── more motions, so the palettes stop sharing four of them ──────────
   fx-fall was carrying eight palettes at one point, which is why the newer
   ones felt interchangeable: same leaves-coming-down, different emoji. These
   are all transform/opacity only, so they cost the compositor nothing extra
   over the ones they replace. Bottom-anchored motions place themselves from
   --y compressed into the lower half, since the script hands out 0-100vh. */

/* Lobster: skitters sideways along the floor, facing where it is going. */
.theme-fx.fx-scuttle .fx-bit { left:-10vw; top:calc(55vh + var(--y) * 0.4);
  animation:fxScuttle var(--t) linear var(--d) both; }
@keyframes fxScuttle {
  0% { transform:translate(0,0) scaleX(-1) scale(var(--s)); opacity:0; }
  8% { opacity:1; }
  25% { transform:translate(30vw,-2vh) scaleX(-1) scale(var(--s)); }
  45% { transform:translate(54vw,0) scaleX(-1) scale(var(--s)); }
  65% { transform:translate(78vw,-2vh) scaleX(-1) scale(var(--s)); }
  92% { opacity:1; }
  100% { transform:translate(124vw,0) scaleX(-1) scale(var(--s)); opacity:0; } }

/* Flamingo: a feather does not drop, it swings down. */
.theme-fx.fx-sway .fx-bit { left:var(--x); top:-12vh;
  animation:fxSway var(--t) ease-in var(--d) both; }
@keyframes fxSway {
  0% { transform:translate(0,0) rotate(-25deg) scale(var(--s)); opacity:0; }
  10% { opacity:1; }
  32% { transform:translate(4vw,30vh) rotate(20deg) scale(var(--s)); }
  56% { transform:translate(-3vw,58vh) rotate(-18deg) scale(var(--s)); }
  80% { transform:translate(3vw,86vh) rotate(14deg) scale(var(--s)); }
  100% { transform:translate(-1vw,118vh) rotate(-10deg) scale(var(--s)); opacity:.85; } }

/* Mango, Coffee: poured from above, fanning out as it falls. */
.theme-fx.fx-pour .fx-bit { left:50%; top:-10vh;
  animation:fxPour var(--t) ease-in var(--d) both; }
@keyframes fxPour {
  0% { transform:translate(-50%,0) scale(calc(var(--s) * .45)); opacity:0; }
  12% { opacity:1; }
  100% { transform:translate(calc(-50% + var(--x) - 50vw),120vh)
           rotate(var(--r)) scale(var(--s)); opacity:.85; } }

/* Galaxy: bodies spiralling out from the middle. rotate-then-translateX is
   what turns a straight line into an orbit. */
.theme-fx.fx-spiral .fx-bit { left:50%; top:50%;
  animation:fxSpiral var(--t) ease-out var(--d) both; }
@keyframes fxSpiral {
  0% { transform:translate(-50%,-50%) rotate(0deg) translateX(0) scale(0); opacity:0; }
  20% { opacity:1; }
  100% { transform:translate(-50%,-50%) rotate(var(--r)) translateX(46vw)
           scale(var(--s)); opacity:0; } }

/* Peacock: iridescence - it shimmers where it stands rather than travelling. */
.theme-fx.fx-shimmer .fx-bit { left:var(--x); top:var(--y);
  animation:fxShimmer var(--t) ease-in-out var(--d) both; }
@keyframes fxShimmer {
  0% { transform:scale(0) rotate(0deg); opacity:0; }
  25% { transform:scale(var(--s)) rotate(0deg); opacity:1; }
  50% { transform:scale(calc(var(--s) * 1.12)) rotate(6deg); opacity:.85; }
  75% { transform:scale(var(--s)) rotate(-6deg); opacity:1; }
  100% { transform:scale(calc(var(--s) * .9)) rotate(0deg); opacity:0; } }

/* Citrus: a squeeze - straight rays out from the centre. Same construction as
   the spiral, but a fixed angle and a snappier curve, so it reads as a burst
   rather than an orbit. */
.theme-fx.fx-burst .fx-bit { left:50%; top:50%;
  animation:fxBurst var(--t) cubic-bezier(.2,.8,.3,1) var(--d) both; }
@keyframes fxBurst {
  0% { transform:translate(-50%,-50%) rotate(var(--r)) translateX(0) scale(0); opacity:0; }
  15% { opacity:1; }
  100% { transform:translate(-50%,-50%) rotate(var(--r)) translateX(52vw)
           scale(var(--s)); opacity:0; } }

/* Watermelon, Copper: heavy things go end over end. */
.theme-fx.fx-tumble .fx-bit { left:var(--x); top:-14vh;
  animation:fxTumble var(--t) cubic-bezier(.4,0,.7,1) var(--d) both; }
@keyframes fxTumble {
  0% { transform:translate(0,0) rotate(0deg) scale(var(--s)); opacity:0; }
  10% { opacity:1; }
  100% { transform:translate(calc(var(--x2) - var(--x)),122vh)
           rotate(calc(var(--r) * 2.5)) scale(var(--s)); opacity:.9; } }

/* Desert: blown across on the wind, not dropped. */
.theme-fx.fx-drift .fx-bit { left:-12vw; top:calc(28vh + var(--y) * 0.6);
  animation:fxDrift var(--t) linear var(--d) both; }
@keyframes fxDrift {
  0% { transform:translate(0,0) scale(var(--s)); opacity:0; }
  10% { opacity:1; }
  40% { transform:translate(42vw,-4vh) rotate(8deg) scale(var(--s)); }
  70% { transform:translate(82vw,3vh) rotate(-6deg) scale(var(--s)); }
  92% { opacity:1; }
  100% { transform:translate(126vw,0) scale(var(--s)); opacity:0; } }

/* ── minimising Teams and channels ───────────────────────────────────
   Folded, the block keeps only its heading bar. It stays at the bottom of the
   sidebar (the chats list above it takes the freed space, since that is the
   flex child that grows), so one click gets everything back. */
.teams-fold { flex:1; min-width:0; display:flex; align-items:center; gap:.4rem;
  border:0; background:none; cursor:pointer; color:inherit; text-align:left;
  font:inherit; letter-spacing:inherit; text-transform:inherit;
  padding:.1rem .1rem; border-radius:var(--r-sm, 6px); }
.teams-fold span { flex:1; min-width:0; white-space:nowrap; overflow:hidden;
  text-overflow:ellipsis; }
.teams-fold svg { width:12px; height:12px; flex:none; color:var(--faint);
  transition:transform .2s; }
.teams-fold:hover { color:var(--ink); }
.teams-fold:focus-visible { outline:2px solid var(--plum); outline-offset:2px; }
/* Chevron points down when open, right when folded - the same language the
   team rows already use. */
.teams-block.folded .teams-fold svg { transform:rotate(-90deg); }
.teams-block.folded .teamlist { display:none; }
/* Nothing to add a channel to while it is closed. */
.teams-block.folded .teams-add { display:none; }

/* ── the auto-translate chip is a control ────────────────────────────
   It was a chip that looked exactly like the labels either side of it, so
   nothing suggested clicking it would change how every message is displayed.
   Now it brightens on approach and its tooltip leads with the action. */
.chip.chip-act { cursor:pointer;
  transition:background .18s, color .18s, transform .18s, box-shadow .18s; }
.chip.chip-act:hover { background:var(--plum); color:var(--on-plum);
  transform:translateY(-1px); box-shadow:var(--shadow-1); }
.chip.chip-act:focus-visible { outline:2px solid var(--plum); outline-offset:2px; }


/* ── tags, unread counts and notifications ───────────────────────────────
 *
 * Three things that all answer "is any of this about me?", and they are
 * deliberately different colours from everything else in the app. The rest
 * of the interface is built out of --plum; these use --danger, which every
 * palette defines, so the count stands out in all twenty-six of them
 * without a single hard-coded colour.
 */

/* The number on the Chats rail item. Replaces the plain dot, which said only
   that something had happened and not how much. */
.tally { position:absolute; top:.35rem; right:.4rem; min-width:1.15rem;
  height:1.15rem; padding:0 .28rem; border-radius:999px;
  background:var(--danger); color:var(--on-danger);
  font-size:.63rem; font-weight:800; line-height:1.15rem; text-align:center;
  letter-spacing:0; font-variant-numeric:tabular-nums;
  border:2px solid var(--ground);
  box-shadow:0 3px 8px -3px color-mix(in srgb, var(--danger) 75%, transparent);
  animation:tallyPop .34s var(--spring) both; }
.tally[hidden] { display:none !important; }
@keyframes tallyPop {
  from { transform:scale(.4); opacity:0; }
  60%  { transform:scale(1.16); }
  to   { transform:scale(1); opacity:1; }
}

/* The same count on the conversation itself, so the rail's total can be
   traced to a row without opening anything. */
.thread .unread, .chan-row .unread {
  min-width:1.1rem; height:1.1rem; padding:0 .3rem; border-radius:999px;
  background:var(--danger); color:var(--on-danger);
  font-size:.64rem; font-weight:800; line-height:1.1rem; text-align:center;
  font-variant-numeric:tabular-nums; flex:0 0 auto; }
/* A third column for it. Empty it costs nothing, so every row gets one and
   the name column keeps the same width whether there is a count or not. */
.thread { grid-template-columns:auto minmax(0,1fr) auto; }
.thread .unread { grid-column:3; grid-row:1/span 2; align-self:center; }
.thread.has-unread .nm { font-weight:800; }
.thread.has-unread .pv { color:var(--ink); }
.chan-row .unread { margin-right:.3rem; }
.chan-row .chan.has-unread .chan-name { font-weight:800; color:var(--ink); }

/* Who you are tagging. Sits over the composer, follows the caret's line
   rather than the mouse, and is a listbox so the keyboard can drive it -
   typing keeps going into the message the whole time. */
.tag-pop { position:fixed; z-index:72; width:19rem; max-width:calc(100vw - 2rem);
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-lg); box-shadow:var(--shadow-2);
  overflow:hidden; animation:tagIn .16s ease both; }
.tag-pop[hidden] { display:none !important; }
@keyframes tagIn { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:none; } }
.tag-list { max-height:15rem; overflow:auto; padding:.3rem; }
.tag-row { display:flex; align-items:center; gap:.55rem; width:100%;
  padding:.4rem .5rem; border:0; border-radius:var(--r-md);
  background:none; color:var(--ink); cursor:pointer; text-align:left;
  font:inherit; font-size:.82rem; }
.tag-row:hover, .tag-row[aria-selected="true"] { background:var(--plum-soft); }
.tag-row[aria-selected="true"] { box-shadow:inset 0 0 0 1.5px var(--plum); }
.tag-row .tag-nm { font-weight:650; flex:1; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.tag-row .tag-lg { font-size:.62rem; font-weight:700; text-transform:uppercase;
  color:var(--muted); background:var(--well); padding:.1rem .32rem;
  border-radius:.35rem; flex:0 0 auto; }
.tag-foot { padding:.35rem .6rem; border-top:1px solid var(--line);
  background:var(--tone); color:var(--muted); font-size:.68rem; }

/* The tag inside a message. A tag of *me* is filled in, because that is the
   one worth spotting when scrolling back through a channel. */
.mention { font-weight:700; color:var(--plum-ink);
  background:var(--plum-soft); border-radius:.35rem; padding:0 .18rem; }
.mention.me { background:var(--danger); color:var(--on-danger); }
.msg.tagme .bubble { box-shadow:inset 2.5px 0 0 var(--danger), var(--shadow-1); }

/* Notifications, bottom right, one on top of the other. Only raised for the
   things that are about you - and only when the app is not the window you
   are looking at, so working in it never produces a popup about itself. */
.nudges { position:fixed; right:1rem; bottom:1rem; z-index:90;
  display:flex; flex-direction:column-reverse; gap:.5rem;
  width:20.5rem; max-width:calc(100vw - 2rem); pointer-events:none; }
.nudge { pointer-events:auto; display:grid; gap:.15rem;
  grid-template-columns:auto 1fr auto; grid-template-areas:
    "av who x" "av txt x" "av whr x";
  align-items:center; column-gap:.6rem;
  background:var(--surface); color:var(--ink);
  border:1px solid var(--line); border-left:3px solid var(--danger);
  border-radius:var(--r-lg); box-shadow:var(--shadow-2);
  padding:.6rem .7rem; cursor:pointer; text-align:left; font:inherit;
  animation:nudgeIn .34s var(--spring) both; }
.nudge.out { animation:nudgeOut .22s ease both; }
.nudge .av { grid-area:av; }
.nudge .n-who { grid-area:who; font-weight:750; font-size:.82rem; }
.nudge .n-txt { grid-area:txt; font-size:.76rem; color:var(--muted);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.nudge .n-where { grid-area:whr; font-size:.66rem; color:var(--faint); }
.nudge .n-x { grid-area:x; align-self:start; width:1.4rem; height:1.4rem;
  border:0; border-radius:50%; background:var(--well); color:var(--muted);
  cursor:pointer; font-size:.9rem; line-height:1; display:grid;
  place-items:center; }
.nudge .n-x:hover { background:var(--danger); color:var(--on-danger); }
@keyframes nudgeIn {
  from { opacity:0; transform:translateX(28px) scale(.96); }
  to   { opacity:1; transform:none; }
}
@keyframes nudgeOut {
  to { opacity:0; transform:translateX(28px) scale(.97); }
}
@media (prefers-reduced-motion: reduce) {
  .tally, .nudge, .tag-pop { animation:none !important; }
}

/* Arrived here from a tag: the message says which one it was, because
   scrolling to roughly the right place in a long channel is not an answer. */
.msg.flash .bubble { animation:msgFlash 1.5s ease both; }
@keyframes msgFlash {
  0%, 55% { background:var(--danger-soft);
            box-shadow:0 0 0 2px var(--danger), var(--shadow-1); }
  100%    { box-shadow:var(--shadow-1); }
}
@media (prefers-reduced-motion: reduce) {
  .msg.flash .bubble { animation:none;
    box-shadow:0 0 0 2px var(--danger), var(--shadow-1); }
}

/* A feed row you can click, because now every row goes somewhere: a tag
   opens the message, a file opens Files. */
.act.act-go { cursor:pointer; border:0; width:100%; font:inherit;
  transition:background .18s, transform .18s; }
.act.act-go:hover { background:var(--plum-soft); transform:translateY(-1px); }
.act.act-go:focus-visible { outline:2px solid var(--plum); outline-offset:-2px; }
/* A tag you have not read yet: an accent down the edge, the same mark the
   notification card uses, so the two read as the same thing in two places.
   A tiny "@" chip was tried first and was simply too small to read. */
.act.act-go.fresh { box-shadow:inset 2.5px 0 0 var(--danger); }
.act.act-go.fresh .t { font-weight:750; }
.act.act-go:not(.fresh) .t { color:var(--muted); }


/* ── the invite-link sheet ───────────────────────────────────────────────
 *
 * It used to be a 26rem sheet with the URL in a one-line monospace block, so
 * a link with a twenty-two character token ran off the side and the dialog
 * grew a horizontal scrollbar. You could not read the link you were about to
 * send, which is the one thing the dialog is for.
 *
 * So: a wider sheet, the URL wrapping inside its own panel, and the copy
 * button in that panel next to the thing it copies.
 */
.sheet.wide { max-width:34rem; }

.linkbox { display:flex; align-items:stretch; gap:.6rem;
  background:var(--well); border:1px solid var(--line);
  border-radius:var(--r-lg); padding:.6rem .65rem; }
.linkbox code { flex:1; min-width:0; align-self:center;
  font-family:"JetBrains Mono",ui-monospace,monospace;
  font-size:.86rem; line-height:1.5; letter-spacing:.01em;
  color:var(--plum-ink); background:none; padding:0;
  /* The whole link, wrapped - never a sideways scrollbar. `anywhere` is what
     lets it break inside the token, which has no spaces or hyphens to break
     at. */
  overflow-wrap:anywhere; word-break:break-word;
  user-select:all; -webkit-user-select:all; }
.linkbox .linkcopy { flex:0 0 auto; align-self:center;
  display:inline-flex; align-items:center; gap:.35rem;
  padding:.5rem .7rem; border:0; border-radius:var(--r-md);
  background:var(--plum); color:var(--on-plum);
  font:inherit; font-size:.78rem; font-weight:700; cursor:pointer;
  transition:background .18s, transform .18s, box-shadow .18s; }
.linkbox .linkcopy svg { width:15px; height:15px; }
.linkbox .linkcopy:hover { transform:translateY(-1px);
  box-shadow:0 6px 14px -6px color-mix(in srgb, var(--plum) 70%, transparent); }
.linkbox .linkcopy:focus-visible { outline:2px solid var(--plum); outline-offset:2px; }
/* Said on the button, where the click was, rather than only in a line of
   text underneath it. */
.linkbox .linkcopy.done { background:var(--live); color:var(--on-live); }

#link-sheet .note { font-size:.78rem; color:var(--faint); font-style:normal; }

@media (max-width: 30rem) {
  /* No room for both on one line: the button goes under the link. */
  .linkbox { flex-direction:column; }
  .linkbox .linkcopy { align-self:flex-end; }
}


/* ── presence ────────────────────────────────────────────────────────────
 *
 * Four states, and every one of them readable without seeing the colour.
 * Teams uses colour alone for three of the four, which leaves the most
 * common form of colour blindness looking at three identical grey dots -
 * and green-versus-red is exactly the pair that goes. So each state has a
 * shape as well:
 *
 *   online    a filled disc
 *   away      a filled disc with a bite out of the top right, like a clock
 *   meeting   a filled disc with a bar across it - the do-not-disturb sign
 *   offline   a hollow ring, which reads as absence rather than as a colour
 *
 * The dot sits in a notch cut out of the avatar rather than on top of it,
 * so it never lands on an eye or a nose of the animal underneath.
 */
.av { position:relative; }

/* Where the dot goes, and why it goes there.
 *
 * Inside the circle. That is not the fashionable choice - Teams and Slack
 * hang it half off the edge - but `.av` clips its children on purpose: the
 * animal is drawn at 2.2em and trimmed to the rim, which is what "almost
 * fills the circle and never overclips" needs. A dot hanging off the edge
 * is a dot sliced into a crescent.
 *
 * Being inside has a real advantage, though, and it is the reason not to
 * fight it: the dot is surrounded by the avatar's own colour rather than by
 * the page, so its ring and its cut-out shapes can be one fixed colour and
 * still read everywhere. An avatar appears on five backgrounds in this app -
 * the surface, a hovered row, a selected row, a well, a call tile - and a
 * dot that overlapped the edge would need a different ring on each.
 *
 * The geometry keeps the dot and its ring clear of the rim. With the dot
 * 30% of the box across, its centre sits 22.6% along each axis from the
 * middle - that is 32% of the box along the diagonal, leaving 15% for the
 * radius and 3% for the ring inside a 50% circle.
 */
.av.has-dot {
  --dot: 30%;
  --dot-in: 12.4%;               /* from the near edges: 100% - 72.6% - 15% */
  --dot-ring: .13em;             /* scales with the avatar, which sets em */
}

/* Each state has a shape as well as a colour.
 *
 * Teams separates three of its four states by hue alone, which leaves the
 * common form of colour blindness looking at three near-identical dots -
 * and green against red is the first pair to go. So the shape carries it:
 *
 *   online    a filled disc
 *   away      three quarters of one, a quarter cut out like a clock
 *   meeting   a disc with a band cut across it - the do-not-disturb sign
 *   offline   a hollow ring, which reads as absence rather than as a colour
 */
.pdot { position:absolute;
  right:var(--dot-in, 12.4%); bottom:var(--dot-in, 12.4%);
  width:var(--dot, 30%); height:var(--dot, 30%);
  border-radius:50%; box-sizing:border-box; display:block;
  background:var(--pcol, var(--faint)); pointer-events:none;
  /* Set off from the avatar underneath. Inside the circle, so this is
     always over the avatar's own colour - never over the page. */
  box-shadow:0 0 0 var(--dot-ring, .13em) var(--surface);
  transition:background .25s ease, transform .25s var(--spring); }

.pdot.is-online  { --pcol:var(--live); }
.pdot.is-away    { --pcol:var(--warn);
  /* A quarter taken out of the top right. */
  background:conic-gradient(from 0deg, var(--surface) 0 25%,
                            var(--pcol) 25% 100%); }
/* A plain red disc. It carried a band across it - the do-not-disturb sign -
   which at menu size read as a "no entry" sign rather than a status dot, and
   next to a green disc and an amber one it was the heaviest thing in the
   list. Red on its own is enough here, and the word "In a meeting" is
   written beside it everywhere it appears. */
.pdot.is-meeting { --pcol:var(--danger); }
.pdot.is-offline { background:var(--surface);
  border:var(--dot-ring, .13em) solid var(--faint); }

/* Away from an avatar - in the hover card, the status menu, the me chip,
   the roster line - it is simply a small round mark, so the offsets that
   tuck it into a corner do not apply and the ring is not wanted either. */
.pdot.flat, .hovercard .pdot, .pmenu .pdot, .me-status .pdot,
.person .pdot { position:static; right:auto; bottom:auto;
  box-shadow:none; --dot-ring:2px; }

/* A group has no status of its own. The three overlapping avatars on a group
   row are there to say who is in it, so three tiny dots crowding each other
   inside 26px circles is noise about nobody. */
.cluster .pdot { display:none; }

/* Just came online: the dot arrives rather than appearing. The only
   animation presence gets - more than that is a distraction on a screen
   with thirty avatars on it. */
@keyframes dotIn { from { transform:scale(.2); } to { transform:scale(1); } }
.pdot.fresh { animation:dotIn .3s var(--spring) both; }
@media (prefers-reduced-motion: reduce) { .pdot { animation:none !important; } }

/* ── the hover card ──────────────────────────────────────────────────────
 * What Teams puts in a tooltip - a name and a word - plus the two things
 * you actually wanted to know before writing to somebody: what language
 * they read, and whether the status is theirs or ours. One element, moved
 * about and positioned fixed, so it is never clipped by a scrolling list.
 */
.hovercard { position:fixed; z-index:88; max-width:17rem;
  background:var(--surface); color:var(--ink);
  border:1px solid var(--line); border-radius:var(--r-lg);
  box-shadow:var(--shadow-2); padding:.6rem .7rem;
  pointer-events:none; opacity:0; transform:translateY(4px);
  transition:opacity .14s ease, transform .14s ease; }
.hovercard.on { opacity:1; transform:none; }
.hovercard[hidden] { display:none !important; }
.hovercard .hc-top { display:flex; align-items:center; gap:.55rem; }
.hovercard .hc-nm { font-weight:750; font-size:.85rem; }
.hovercard .hc-rows { margin-top:.4rem; display:grid; gap:.18rem; }
.hovercard .hc-row { display:flex; align-items:center; gap:.4rem;
  font-size:.74rem; color:var(--muted); }
.hovercard .hc-row .pdot { position:static; width:.55rem; height:.55rem;
  flex:0 0 auto; }
.hovercard .hc-row b { color:var(--ink); font-weight:650; }
.hovercard .hc-pin { font-style:italic; color:var(--faint); }

/* ── choosing your own ───────────────────────────────────────────────────
 * On your own avatar in the top bar, because that is where you already look
 * to check who you are signed in as.
 */
/* The avatar is a real button now, so it needs the browser defaults taken
   off it - a button does not inherit the font or lose its border by itself. */
.me-chip button.av { border:0; padding:0; font:inherit; font-weight:700;
  font-size:.63rem; cursor:pointer; appearance:none; -webkit-appearance:none;
  transition:transform .2s var(--spring), box-shadow .2s; }
.me-chip button.av:hover { transform:scale(1.06); }
.me-chip button.av:focus-visible { outline:2px solid var(--plum); outline-offset:2px; }
.me-chip button.av[aria-expanded="true"] {
  box-shadow:0 0 0 2.5px color-mix(in srgb, var(--plum) 45%, transparent); }
/* Status and language share one line, so the chip does not grow a third. */
.me-under { display:flex; align-items:center; gap:.3rem; }
.me-under #me-lang:not(:empty)::before { content:"·"; margin-right:.28rem;
  color:var(--faint); }
.me-under .me-status[hidden] + #me-lang::before { content:none; }
.me-status { display:flex; align-items:center; gap:.3rem;
  font-size:.66rem; color:var(--muted); }
.me-status .pdot { position:static; width:.5rem; height:.5rem; flex:0 0 auto; }

.pmenu { position:fixed; z-index:92; width:15rem;
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-lg); box-shadow:var(--shadow-2);
  overflow:hidden; padding:.3rem; }
.pmenu[hidden] { display:none !important; }
.pmenu .pm-head { padding:.35rem .5rem .3rem; font-size:.63rem;
  font-weight:700; letter-spacing:.04em; text-transform:uppercase;
  color:var(--faint); }
.pmenu button { display:flex; align-items:center; gap:.55rem; width:100%;
  padding:.45rem .5rem; border:0; border-radius:var(--r-md);
  background:none; color:var(--ink); font:inherit; font-size:.82rem;
  cursor:pointer; text-align:left; }
.pmenu button:hover { background:var(--plum-soft); }
.pmenu button:focus-visible { outline:2px solid var(--plum); outline-offset:-2px; }
.pmenu button .pdot { position:static; width:.6rem; height:.6rem; flex:0 0 auto; }
.pmenu button .pm-l { flex:1; }
.pmenu button .pm-tick { color:var(--plum); font-weight:800; opacity:0; }
.pmenu button[aria-checked="true"] .pm-tick { opacity:1; }
.pmenu button[aria-checked="true"] { background:var(--plum-soft); font-weight:650; }
.pmenu .pm-rule { height:1px; background:var(--line); margin:.3rem .2rem; }
.pmenu .pm-auto { font-size:.75rem; }
.pmenu .pm-why { padding:.15rem .55rem .4rem; font-size:.68rem;
  color:var(--faint); line-height:1.35; }

/* The roster says it in words as well as in a dot: a list is where you go
   to decide who to ask, and a dot alone makes you hover four people. */
.person .pstate { font-size:.68rem; font-weight:650; color:var(--muted); }
.person .pstate.is-online  { color:var(--live); }
.person .pstate.is-away    { color:var(--warn); }
.person .pstate.is-meeting { color:var(--danger); }


/* ── the outside-company reminder ────────────────────────────────────────
 * It can be shut. The warning matters, but it is a banner across the top of
 * a conversation somebody may be in all day, and an unclosable one gets
 * read past rather than read. What stays behind is not nothing: the header
 * keeps its "External" chip, the pane keeps its tint, and any message from
 * outside keeps its label. Shutting it hides the sentence, not the fact.
 */
.outside-x { flex:0 0 auto; width:1.6rem; height:1.6rem; margin-left:.2rem;
  display:grid; place-items:center; border:0; border-radius:50%;
  background:none; color:inherit; opacity:.55; cursor:pointer;
  transition:opacity .18s, background .18s, transform .18s; }
/* The banner's own rule for its warning triangle sets a top margin and a
   size; both are wrong for a close button sitting in the same row. */
.outside-x svg { width:14px; height:14px; margin-top:0; }
.outside-x:hover { opacity:1; transform:scale(1.08);
  background:color-mix(in srgb, currentColor 14%, transparent); }
.outside-x:focus-visible { outline:2px solid currentColor; outline-offset:1px;
  opacity:1; }


/* Five or more in a conversation: four faces and a count, rather than a row
   that grows until it pushes the buttons off the header. */
.members .more-members { display:inline-grid; place-items:center;
  min-width:1.9rem; height:1.9rem; padding:0 .3rem; margin-left:-.5rem;
  border-radius:999px; background:var(--well); color:var(--muted);
  border:2.5px solid var(--surface);
  font-size:.62rem; font-weight:800; font-variant-numeric:tabular-nums; }


/* The language picker inside the status menu. */
.pmenu .pm-lang { display:block; padding:.15rem .5rem .1rem; }
.pmenu .pm-lang select { width:100%; font:inherit; font-size:.82rem;
  color:var(--ink); background:var(--well); border:1px solid var(--line);
  border-radius:var(--r-md); padding:.4rem .5rem; cursor:pointer; }
.pmenu .pm-lang select:focus-visible { outline:2px solid var(--plum);
  outline-offset:1px; }
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }


/* ── things you do to one message ────────────────────────────────────────
 *
 * A bar that appears on hover, reactions underneath, a quote above a reply,
 * and a small menu for the rest. All of it hangs off the message row, which
 * is already `position:relative` for the avatar.
 */
.msg { position:relative; }

/* The bar. Sits above the bubble's top edge, on the side the bubble is on,
   so it never covers the text of the message it belongs to. Hidden until
   the pointer is on the message - or until something in it has focus, which
   is what makes it reachable from the keyboard. */
.msg-acts { position:absolute; top:-1.05rem; z-index:6;
  display:flex; align-items:center; gap:.1rem;
  padding:.2rem .25rem; border-radius:999px;
  background:var(--surface); border:1px solid var(--line);
  box-shadow:var(--shadow-1);
  opacity:0; transform:translateY(3px); pointer-events:none;
  transition:opacity .14s ease, transform .14s ease; }
.msg:not(.mine) .msg-acts { left:2.7rem; }
.msg.mine .msg-acts { right:0; }
.msg:hover .msg-acts,
.msg:focus-within .msg-acts { opacity:1; transform:none; pointer-events:auto; }
@media (hover: none) {
  /* No pointer to hover with: the bar is always there rather than a feature
     nobody on a tablet can reach. */
  .msg-acts { opacity:1; transform:none; pointer-events:auto; }
}

.msg-acts button { width:1.6rem; height:1.6rem; padding:0; border:0;
  border-radius:50%; background:none; cursor:pointer; font-size:.95rem;
  line-height:1; display:grid; place-items:center; color:var(--muted);
  transition:background .14s, transform .14s var(--spring); }
.msg-acts button:hover { background:var(--tone); transform:scale(1.18); }
.msg-acts button:focus-visible { outline:2px solid var(--plum);
  outline-offset:1px; }
.msg-acts button svg { width:14px; height:14px; }
.msg-acts .sep { width:1px; height:1rem; background:var(--line);
  margin:0 .12rem; }

/* The reactions themselves, under the bubble. */
.reacts { display:flex; flex-wrap:wrap; gap:.25rem; margin-top:.3rem; }
.msg.mine .reacts { justify-content:flex-end; }
.reacts button { display:inline-flex; align-items:center; gap:.2rem;
  padding:.1rem .38rem .1rem .3rem; border-radius:999px;
  border:1px solid var(--line); background:var(--surface);
  color:var(--muted); font:inherit; font-size:.74rem; cursor:pointer;
  transition:background .14s, border-color .14s, transform .14s; }
.reacts button:hover { transform:translateY(-1px); border-color:var(--line-firm); }
.reacts button .n { font-weight:700; font-variant-numeric:tabular-nums; }
/* Yours is filled in, because "did I already react" is the one thing the
   row has to answer at a glance. */
.reacts button.mine { background:var(--plum-soft); border-color:var(--plum);
  color:var(--plum-ink); }
.reacts button img { width:1.05em; height:1.05em; vertical-align:-.15em; }

/* A reply: what it answers, above what it says. */
.quote { display:flex; align-items:baseline; gap:.4rem; width:100%;
  margin:0 0 .3rem; padding:.25rem .5rem; border:0; border-left:2.5px solid;
  border-radius:.3rem; background:var(--tone); cursor:pointer;
  font:inherit; font-size:.74rem; text-align:left; color:var(--muted);
  overflow:hidden; }
.quote:hover { background:var(--well); }
.quote:focus-visible { outline:2px solid var(--plum); outline-offset:1px; }
.quote b { font-weight:700; color:var(--ink); flex:0 0 auto; }
.quote span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.quote.gone { font-style:italic; }

/* And the bar above the composer while you are writing one. */
.replying { display:flex; align-items:center; gap:.5rem;
  margin:0 0 .35rem; padding:.35rem .6rem;
  border-left:2.5px solid var(--plum); border-radius:.3rem;
  background:var(--plum-soft); font-size:.76rem; color:var(--plum-ink); }
.replying[hidden] { display:none !important; }
.replying .who { font-weight:700; }
.replying .what { flex:1; min-width:0; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap; opacity:.85; }
.replying button { width:1.4rem; height:1.4rem; border:0; border-radius:50%;
  background:none; color:inherit; cursor:pointer; font-size:.95rem;
  line-height:1; display:grid; place-items:center; }
.replying button:hover { background:color-mix(in srgb, var(--plum) 18%, transparent); }

/* The rest, on a small menu. */
.msg-menu { position:fixed; z-index:86; min-width:12rem;
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-lg); box-shadow:var(--shadow-2); padding:.3rem; }
.msg-menu[hidden] { display:none !important; }
.msg-menu button { display:flex; align-items:center; gap:.5rem; width:100%;
  padding:.42rem .5rem; border:0; border-radius:var(--r-md);
  background:none; color:var(--ink); font:inherit; font-size:.82rem;
  cursor:pointer; text-align:left; }
.msg-menu button:hover { background:var(--plum-soft); }
.msg-menu button:focus-visible { outline:2px solid var(--plum);
  outline-offset:-2px; }
.msg-menu button svg { width:15px; height:15px; flex:0 0 auto;
  color:var(--muted); }

/* A conversation somebody is deliberately keeping unread. The count is
   already there in red; this says it was on purpose rather than because
   they have not looked. */
.thread.held .unread, .chan-row .chan.held + .unread {
  box-shadow:0 0 0 2px color-mix(in srgb, var(--danger) 35%, transparent); }


/* ── who is in this conversation ─────────────────────────────────────────
 *
 * A group used to show a circle per person in its header. That row grew
 * until it pushed the buttons off the end, and it answered nothing you
 * could not get by opening it - four initials are not names. So a group
 * gets one icon and a count, and the count opens the list.
 */
.members .headcount { display:inline-flex; align-items:center; gap:.3rem;
  padding:.22rem .5rem .22rem .38rem; border:1px solid var(--line);
  border-radius:999px; background:var(--surface); color:var(--muted);
  font:inherit; font-size:.74rem; font-weight:700; cursor:pointer;
  transition:background .16s, border-color .16s, transform .16s; }
.members .headcount:hover { background:var(--plum-soft);
  border-color:var(--plum); color:var(--plum-ink); transform:translateY(-1px); }
.members .headcount:focus-visible { outline:2px solid var(--plum);
  outline-offset:2px; }
.members .headcount svg { width:15px; height:15px; }
.members .headcount .n { font-variant-numeric:tabular-nums; }

.people-pop { position:fixed; z-index:84; width:19rem;
  max-width:calc(100vw - 2rem);
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-lg); box-shadow:var(--shadow-2); overflow:hidden; }
.people-pop[hidden] { display:none !important; }
.pp-head { display:flex; align-items:center; gap:.5rem;
  padding:.55rem .65rem; border-bottom:1px solid var(--line);
  background:var(--tone); font-size:.78rem; }
.pp-head b { flex:1; font-weight:750; }
.pp-x { width:1.5rem; height:1.5rem; border:0; border-radius:50%;
  background:none; color:var(--muted); cursor:pointer; font-size:1rem;
  line-height:1; display:grid; place-items:center; }
.pp-x:hover { background:var(--well); color:var(--ink); }
.pp-list { max-height:17rem; overflow:auto; padding:.3rem; }
.pp-row { display:flex; align-items:center; gap:.55rem;
  padding:.36rem .45rem; border-radius:var(--r-md); font-size:.82rem; }
.pp-row .nm { flex:1; min-width:0; font-weight:650;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pp-row .lg { flex:0 0 auto; font-size:.68rem; font-weight:700;
  color:var(--plum-ink); background:var(--plum-soft);
  padding:.12rem .38rem; border-radius:.35rem; }
.pp-row .out { flex:0 0 auto; font-size:.62rem; font-weight:700;
  color:var(--a-apricot); text-transform:uppercase; letter-spacing:.02em; }
.pp-foot { padding:.42rem .65rem; border-top:1px solid var(--line);
  background:var(--tone); color:var(--faint); font-size:.7rem;
  line-height:1.4; }

/* The rename control, beside the title. Quiet until the title is hovered,
   so it is not a pencil shouting on every conversation. */
.bar .rename { width:1.6rem; height:1.6rem; padding:0; border:0;
  border-radius:50%; background:none; color:var(--faint); cursor:pointer;
  display:grid; place-items:center; opacity:0;
  transition:opacity .16s, background .16s, color .16s; }
.bar .rename svg { width:14px; height:14px; }
.bar:hover .rename, .bar .rename:focus-visible { opacity:1; }
.bar .rename:hover { background:var(--plum-soft); color:var(--plum); }
.bar .rename[hidden] { display:none !important; }


/* ── the emoji list ──────────────────────────────────────────────────────
 *
 * One scrolling list with headings, rather than one category at a time. The
 * heading spans the grid, sticks to the top while its own section scrolls
 * past, and is what the row of glyphs above jumps to.
 */
.emoji-head-row { grid-column:1/-1; position:sticky; top:0; z-index:1;
  margin:.35rem 0 .15rem; padding:.25rem .35rem;
  background:var(--surface); color:var(--faint);
  font-size:.63rem; font-weight:800; letter-spacing:.06em;
  text-transform:uppercase; }
.emoji-head-row:first-child { margin-top:0; }
/* The first section is what this person reaches for, so it gets a little
   more room than a category of a hundred. */
.emoji-head-row[data-section="used"] { color:var(--plum); }


/* ── paid features, in the master console ────────────────────────────────
 *
 * On the company card rather than the company's own settings page, because
 * it is a billing decision: live captions cost money per minute of audio
 * and per language they are translated into. A company admin has no route
 * to these at all.
 */
.co-paid { display:flex; align-items:center; flex-wrap:wrap; gap:.35rem;
  padding:.5rem 0 .1rem; }
.co-paid-label { font-size:.63rem; font-weight:800; letter-spacing:.05em;
  text-transform:uppercase; color:var(--faint); margin-right:.15rem; }
.co-feat { display:inline-flex; align-items:center; gap:.35rem;
  padding:.24rem .55rem; border:1px solid var(--line);
  border-radius:999px; background:var(--surface); color:var(--muted);
  font:inherit; font-size:.72rem; font-weight:650; cursor:pointer;
  transition:background .16s, border-color .16s, color .16s, transform .16s; }
.co-feat:hover { transform:translateY(-1px); border-color:var(--line-firm); }
.co-feat:focus-visible { outline:2px solid var(--plum); outline-offset:2px; }
.co-feat-dot { width:.5rem; height:.5rem; border-radius:50%;
  background:none; border:1.5px solid var(--faint); flex:0 0 auto; }
/* On: filled, and in the colour the app uses for a thing that is live. */
.co-feat.on { background:var(--live-soft); border-color:var(--live);
  color:var(--live); }
.co-feat.on .co-feat-dot { background:var(--live); border-color:var(--live); }

/* ── what a company has used, on its own card ───────────────────────────
 *
 * Beside the switches that cause it. A running figure on another page is a
 * figure nobody checks until the invoice is wrong.
 */
.co-used { display:flex; align-items:baseline; flex-wrap:wrap; gap:.4rem;
  padding:.1rem 0 .2rem; font-size:.72rem; }
.co-used-bit { padding:.14rem .45rem; border-radius:.4rem;
  background:var(--plum-soft); color:var(--plum); font-weight:650; }
.co-used-when { color:var(--faint); font-size:.68rem; }
/* The engine chip sits with the paid switches but names both states, since
 * "offline" is a thing a company chooses rather than the absence of one. */
.co-engine { font-variant-numeric:tabular-nums; }

/* ── the billing card ───────────────────────────────────────────────────*/
.master-bill { margin-top:1rem; }
.bill-month { font:inherit; font-size:.78rem; padding:.2rem .4rem;
  border:1px solid var(--line); border-radius:.4rem;
  background:var(--surface); color:var(--ink); }

.bill-rates { display:flex; align-items:flex-end; flex-wrap:wrap;
  gap:.6rem .9rem; padding:.5rem 0 .8rem;
  border-bottom:1px solid var(--line); margin-bottom:.6rem; }
.bill-rates-label { font-size:.63rem; font-weight:800; letter-spacing:.05em;
  text-transform:uppercase; color:var(--faint); width:100%; }
.bill-rate { display:flex; align-items:center; gap:.4rem; font-size:.74rem; }
.bill-rate-name { color:var(--muted); font-weight:650; }
.bill-rate-in { width:5.5rem; font:inherit; font-size:.76rem;
  font-variant-numeric:tabular-nums; padding:.22rem .4rem;
  border:1px solid var(--line); border-radius:.4rem;
  background:var(--surface); color:var(--ink); }
.bill-rate-in:focus-visible { outline:2px solid var(--plum);
  outline-offset:1px; }
.bill-rate-per { color:var(--faint); font-size:.68rem; }

.bill-list { display:flex; flex-direction:column; gap:.4rem; }
.bill-row { display:grid; grid-template-columns:minmax(8rem,1fr) 2fr auto;
  align-items:center; gap:.6rem; padding:.5rem .6rem;
  border:1px solid var(--line); border-radius:.55rem;
  background:var(--surface); }
.bill-who { display:flex; flex-direction:column; gap:.1rem; min-width:0; }
.bill-how { font-size:.66rem; color:var(--faint); }
.bill-what { display:flex; flex-wrap:wrap; gap:.3rem; min-width:0; }
.bill-line { display:inline-flex; align-items:baseline; gap:.35rem;
  padding:.16rem .45rem; border-radius:.4rem; background:var(--plum-soft);
  font-size:.7rem; }
.bill-line-what { color:var(--muted); }
.bill-line-size { font-variant-numeric:tabular-nums; font-weight:650;
  color:var(--ink); }
.bill-line-cost { font-variant-numeric:tabular-nums; color:var(--plum);
  font-weight:700; }
/* Usage nobody has priced. Marked rather than shown as 0.00, which would
 * read as "nothing owed" on a line that is in fact unbilled. */
.bill-line.unpriced { background:none; border:1px dashed var(--warn); }
.bill-line.unpriced .bill-line-cost { color:var(--warn); font-weight:650; }
.bill-end { display:flex; align-items:center; gap:.5rem; }
.bill-total { font-variant-numeric:tabular-nums; font-size:.9rem; }
.bill-sum { display:flex; justify-content:space-between; align-items:baseline;
  padding:.5rem .6rem; font-size:.78rem; color:var(--muted);
  border-top:1px solid var(--line); margin-top:.2rem; }
.bill-sum b { font-variant-numeric:tabular-nums; font-size:1rem;
  color:var(--ink); }
.bill-none { font-size:.74rem; color:var(--faint); padding:.4rem .2rem;
  margin:0; }

@media (max-width:720px) {
  .bill-row { grid-template-columns:1fr; }
  .bill-end { justify-content:space-between; }
}

/* ── the invoice ────────────────────────────────────────────────────────
 *
 * Printed by the browser rather than generated as a PDF on the server: no
 * PDF library is installed, adding one is a dependency the operator has to
 * accept, and every browser already saves a page as PDF. The print rules
 * below are what make that output look like a document instead of a
 * screenshot of an app.
 */
.invoice-sheet { max-width:44rem; }
.invoice { color:var(--ink); }
.inv-head { display:flex; justify-content:space-between;
  align-items:flex-start; gap:1rem; padding-bottom:.7rem;
  border-bottom:2px solid var(--ink); margin-bottom:.9rem; }
.inv-brand { font-size:1.3rem; font-weight:800; letter-spacing:-.01em; }
.inv-meta { text-align:right; font-size:.78rem; color:var(--muted); }
.inv-no { font-weight:700; color:var(--ink); }
.inv-to { display:flex; flex-direction:column; gap:.1rem;
  margin-bottom:.9rem; }
.inv-label { font-size:.63rem; font-weight:800; letter-spacing:.05em;
  text-transform:uppercase; color:var(--faint); }
.inv-table { width:100%; border-collapse:collapse; font-size:.8rem; }
.inv-table th { text-align:left; font-size:.63rem; font-weight:800;
  letter-spacing:.05em; text-transform:uppercase; color:var(--faint);
  padding:.3rem .4rem; border-bottom:1px solid var(--line); }
.inv-table th:last-child, .inv-amount { text-align:right; }
.inv-table td { padding:.42rem .4rem; border-bottom:1px solid var(--line);
  vertical-align:top; }
.inv-table td:nth-child(2), .inv-table td:nth-child(3), .inv-amount {
  font-variant-numeric:tabular-nums; }
.inv-unpriced td { color:var(--warn); }
.inv-total { display:flex; justify-content:space-between; align-items:baseline;
  padding:.6rem .4rem .2rem; border-top:2px solid var(--ink);
  margin-top:-1px; font-size:.86rem; }
.inv-total b { font-size:1.15rem; font-variant-numeric:tabular-nums; }
.inv-note { font-size:.68rem; color:var(--faint); line-height:1.5;
  margin:.9rem 0 0; }
.inv-warn { font-size:.7rem; color:var(--warn); font-weight:650;
  margin:.5rem 0 0; }

/* What the printer gets. Nothing but the invoice: the console around it is
 * an app, and an app printed onto A4 is a page of chrome with a table in
 * the middle of it. */
@media print {
  body.master > * { display:none !important; }
  .invoice-sheet { display:block !important; position:static;
    max-width:none; width:auto; border:0; box-shadow:none; padding:0;
    margin:0; background:#fff; }
  .invoice-sheet::backdrop { display:none; }
  .no-print { display:none !important; }
  .invoice { color:#000; }
  .inv-head { border-bottom-color:#000; }
  .inv-total { border-top-color:#000; }
  .inv-table th, .inv-table td { border-bottom-color:#bbb; }
  .inv-label, .inv-note, .inv-meta { color:#555; }
  .inv-warn, .inv-unpriced td { color:#000; font-weight:700; }
}

/* ── builds of the app, in the master console ───────────────────────────
 *
 * Two lines are shown together and they must not be confusable: the core
 * build everybody gets, and a build written for one customer. A private
 * build served to the wrong company is that customer's work handed to a
 * competitor, so the line a build belongs to is the first thing on the row
 * and the only place a second colour is spent.
 */
.master-rel { margin-top:1rem; }
.rel-sub { font-size:.86rem; font-weight:700; margin:1.1rem 0 .5rem;
  padding-top:.8rem; border-top:1px solid var(--line); }

.rel-new { display:flex; flex-direction:column; gap:.4rem; padding:.5rem 0 .8rem;
  border-bottom:1px solid var(--line); margin-bottom:.7rem; }
.rel-new-label { font-size:.63rem; font-weight:800; letter-spacing:.05em;
  text-transform:uppercase; color:var(--faint); }
.rel-wait { display:flex; align-items:center; gap:.4rem; flex-wrap:wrap;
  padding:.5rem .6rem; border:1px dashed var(--line-firm); border-radius:.55rem;
  background:var(--tone); }
.rel-in { font:inherit; font-size:.76rem; padding:.26rem .45rem;
  border:1px solid var(--line); border-radius:.4rem;
  background:var(--surface); color:var(--ink); }
.rel-in:focus-visible { outline:2px solid var(--plum); outline-offset:1px; }
.rel-notes-in { flex:1; min-width:8rem; }
.rel-req-box { display:inline-flex; align-items:center; gap:.3rem;
  font-size:.72rem; color:var(--muted); }

.rel-list, .rel-cos { display:flex; flex-direction:column; gap:.35rem; }
.rel-row, .rel-co { display:grid; grid-template-columns:minmax(7rem,auto) 1fr auto;
  align-items:center; gap:.6rem; padding:.5rem .6rem;
  border:1px solid var(--line); border-radius:.55rem; background:var(--surface); }
.rel-row.gone { opacity:.55; }
.rel-who { display:flex; flex-direction:column; gap:.1rem; min-width:0; }
.rel-who b { font-variant-numeric:tabular-nums; }
/* The line a build belongs to. Core is quiet; a customer's own is not. */
.rel-line { font-size:.62rem; font-weight:700; letter-spacing:.04em;
  text-transform:uppercase; color:var(--faint); }
.rel-line.own { color:var(--a-apricot); }
.rel-mid { display:flex; align-items:baseline; gap:.5rem; flex-wrap:wrap; min-width:0; }
.rel-file { font-family:ui-monospace,"Cascadia Mono",monospace;
  font-size:.7rem; color:var(--muted); }
.rel-notes { font-size:.72rem; color:var(--faint); }
.rel-marks { display:inline-flex; gap:.3rem; }
.rel-req, .rel-gone { font-size:.6rem; font-weight:700; padding:.1rem .4rem;
  border-radius:.3rem; }
.rel-req { background:var(--warn-soft); color:var(--warn); }
.rel-gone { background:var(--danger-soft); color:var(--danger); }
.rel-end { display:flex; align-items:center; gap:.4rem; }
.rel-hash { font-family:ui-monospace,"Cascadia Mono",monospace;
  font-size:.66rem; color:var(--faint); }
.rel-gets { font-size:.68rem; color:var(--faint); }
.rel-none { font-size:.74rem; color:var(--faint); margin:.3rem 0; }

@media (max-width:760px) {
  .rel-row, .rel-co { grid-template-columns:1fr; }
  .rel-end { justify-content:flex-start; flex-wrap:wrap; }
}
