/* ═══════════════════════════════════════════════════════════════════════════
   discordo — folha de estilos

   Três decisões guiam o resto do arquivo:

   1. SUPERFÍCIES EM CAMADAS. O fundo é quase preto com um viés azul-violeta, e
      cada nível acima dele clareia um degrau e ganha uma borda de 1px em branco
      translúcido. É a borda, não a sombra, que faz o olho separar painel de
      palco num tema escuro — sombra preta sobre fundo preto não aparece.

   2. O VÍDEO É O CONTEÚDO. Tudo que não é vídeo recua: cinzas dessaturados,
      texto secundário mais apagado, controles em superfície neutra. A cor forte
      fica reservada para estado (falando, mudo, transmitindo).

   3. UMA ESCALA SÓ. Raios, espaços e sombras saem das variáveis abaixo. Quando
      um valor solto aparece no meio do arquivo, é porque tem motivo e o motivo
      está escrito ao lado.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* Superfícies, do fundo para a frente */
  --bg-950: #0c0d12;
  --bg-900: #111319;
  --bg-800: #171a22;
  --bg-700: #1e222c;
  --bg-600: #272c38;
  --bg-500: #333949;

  /* Traços e divisões — translúcidos, para funcionarem sobre qualquer camada */
  --linha: rgb(255 255 255 / .07);
  --linha-forte: rgb(255 255 255 / .13);

  /* Texto */
  --text: #eef0f6;
  --text-dim: #9aa1b4;
  /* O terceiro degrau do texto. Era #6b7285, que dava 3,86:1 sobre o --bg-900
     onde ele quase sempre aparece — abaixo do mínimo de 4,5:1 do WCAG AA, e é
     justamente a cor da hora da mensagem, do nome do canal e dos rótulos de
     painel. #7d8497 leva a 4,97:1 sem clarear o suficiente para competir com o
     --text-dim: os três degraus continuam sendo três. Quem confere é o
     testes/contraste.mjs, que mede a tela de verdade e não esta linha. */
  --text-fraco: #7d8497;

  /* Marca e estados */
  --accent: #6366f1;
  --accent-claro: #8b8ff8;
  --accent-fundo: rgb(99 102 241 / .16);
  /* Os dois tons "cheios", para quando a cor vira FUNDO de texto branco.
     `--accent` e `--red` foram escolhidos para brilhar sobre o escuro, e nessa
     função eles rendem 4,6:1 e 3,2:1 com branco em cima — o segundo reprova no
     WCAG AA. Estes são os mesmos matizes um degrau abaixo, onde o branco passa.
     Ver testes/contraste.mjs. */
  --accent-forte: #4f52e5;
  --red-forte: #c0353a;
  --green: #2dd4a7;
  --amber: #f5b544;
  --red: #f2555a;

  /* Escala */
  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-full: 999px;
  --radius: var(--r-md);

  --sombra-md: 0 4px 16px rgb(0 0 0 / .35);
  --sombra-lg: 0 16px 48px rgb(0 0 0 / .5), 0 2px 8px rgb(0 0 0 / .3);

  /* Realce de linha. Branco translúcido, e não um degrau da rampa de fundos:
     a mesma regra serve a lista de canais (sobre --bg-900), a de membros
     (sobre --bg-900) e o rodapé do usuário (sobre --bg-950) sem que nenhuma
     precise da sua própria cor. É o que o Discord faz, e é o que faz três
     listas parecerem uma coisa só. */
  --hover: rgb(255 255 255 / .04);
  --ativo: rgb(255 255 255 / .075);

  --side-w: 360px;
  --trilha-w: 72px;
  --canais-w: 240px;
  --pessoas-w: 240px;
  /* 48px é a altura do cabeçalho no Discord, e o cabeçalho do servidor usa a
     mesma medida — as duas barras do topo têm que fechar na mesma linha. */
  --top-h: 48px;
  --foco: 0 0 0 2px var(--bg-900), 0 0 0 4px var(--accent-claro);
  --seguro-b: env(safe-area-inset-bottom, 0px);

  color-scheme: dark;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }

html, body {
  height: 100%;
  margin: 0;
  background: var(--bg-900);
  color: var(--text);
  font: 15px/1.55 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

button { font: inherit; cursor: pointer; border: 0; color: inherit; }
select { font: inherit; }
.muted { color: var(--text-dim); }

/* Foco de teclado visível em tudo. `:focus-visible` deixa o clique de mouse
   limpo e mostra o anel só para quem está navegando no Tab. */
:where(button, input, select, [tabindex]):focus-visible {
  outline: none;
  box-shadow: var(--foco);
  border-radius: var(--r-sm);
  position: relative;
  z-index: 1;
}

/* ═════════════════════ entrada (conta) ═════════════════════ */

#entrada {
  min-height: 100%;
  display: grid;
  place-items: center;
  padding: 24px;
  position: relative;
  overflow: hidden;
  background: var(--bg-950);
}
/* Dois halos suaves em vez de um gradiente chapado: dá profundidade sem virar
   um degradê óbvio atrás do cartão. */
#entrada::before {
  content: "";
  position: absolute;
  inset: -20% -10% auto -10%;
  height: 70%;
  background:
    radial-gradient(50% 60% at 30% 0%, rgb(99 102 241 / .22), transparent 70%),
    radial-gradient(45% 55% at 75% 10%, rgb(45 212 167 / .10), transparent 70%);
  filter: blur(20px);
  pointer-events: none;
}

#cartao-conta {
  position: relative;
  width: min(430px, 100%);
  background: linear-gradient(180deg, var(--bg-800), var(--bg-900));
  border: 1px solid var(--linha);
  padding: 34px 32px 28px;
  border-radius: 20px;
  box-shadow: var(--sombra-lg);
  display: flex;
  flex-direction: column;
  gap: 6px;
  animation: sobe .24s cubic-bezier(.2, .8, .3, 1);
}
#cartao-conta h1 {
  margin: 0;
  font-size: 32px;
  font-weight: 700;
  letter-spacing: -.03em;
  background: linear-gradient(100deg, var(--text), var(--accent-claro));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
#cartao-conta .sub { margin: 6px 0 18px; color: var(--text-dim); font-size: 14px; }
/* O formulário herda a coluna do cartão: sem isto o botão principal encolhe
   até o tamanho do texto, porque botão é inline por natureza. */
#cartao-conta form { display: flex; flex-direction: column; }
#cartao-conta label {
  font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .07em;
  color: var(--text-fraco);
  margin-top: 14px;
}
#cartao-conta input:not([type=checkbox]) {
  background: var(--bg-950);
  border: 1px solid var(--linha);
  color: var(--text);
  padding: 12px 13px;
  border-radius: var(--r-sm);
  width: 100%;
  transition: border-color .14s, box-shadow .14s, background .14s;
}
#cartao-conta input:not([type=checkbox]):hover { border-color: var(--linha-forte); }
#cartao-conta input:not([type=checkbox]):focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-fundo);
}
#cartao-conta input::placeholder { color: var(--text-fraco); }
#cartao-conta .row { display: flex; gap: 8px; }
#form-senha-obrigatoria h2 { margin: 0; font-size: 21px; }
#form-senha-obrigatoria .hint { margin-top: 8px; }
#temporaria-sair { margin-top: 10px; }
#reset-resultado { color: var(--text); }
#reset-resultado::backdrop { background: rgb(6 7 10 / .68); backdrop-filter: blur(3px); }

.primary {
  margin-top: 22px;
  background: linear-gradient(180deg, var(--accent-claro), var(--accent));
  color: #fff;
  padding: 13px;
  border-radius: var(--r-sm);
  font-weight: 650;
  letter-spacing: .01em;
  box-shadow: 0 6px 18px rgb(99 102 241 / .35);
  transition: filter .14s, transform .07s, box-shadow .14s;
}
.primary:hover:not(:disabled) { filter: brightness(1.08); box-shadow: 0 8px 24px rgb(99 102 241 / .45); }
.primary:active:not(:disabled) { transform: translateY(1px); }
.primary:disabled { opacity: .55; cursor: wait; box-shadow: none; }
/* Um link que precisa parecer botão: `.primary` foi escrito para <button>, que
   já nasce inline-block. Num <a> o padding sairia atravessado sem isto. */
.como-botao {
  display: inline-block;
  text-decoration: none;
  text-align: center;
}

/* Oferta do instalador na tela de login: uma linha discreta, do tamanho das
   outras dicas — quem chegou aqui veio para entrar, não para baixar. */
.hint.baixar { margin-top: 14px; }
.hint.baixar a { color: var(--accent-claro); text-decoration: none; font-weight: 600; }
.hint.baixar a:hover { text-decoration: underline; }

.hint { font-size: 12.5px; color: var(--text-fraco); margin: 16px 0 0; line-height: 1.5; }
.error { color: var(--red); font-size: 14px; margin: 12px 0 0; }
/* O par do .error: dizer "deu certo" custa uma linha e evita a pessoa apertar
   o botão de novo sem saber se a primeira vez pegou. */
.sucesso { color: var(--green); font-size: 14px; margin: 12px 0 0; }

/* Os campos aparecem e somem conforme o modo (entrar / criar conta). */
.campo { display: contents; }
.campo[hidden] { display: none; }

/* Alternador de dois estados — entrar/criar, criar/convite. Um segmento, não
   duas abas soltas: deixa claro que são as duas faces da mesma coisa. */
.alternar {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: 4px; padding: 4px;
  background: var(--bg-950);
  border: 1px solid var(--linha);
  border-radius: var(--r-sm);
  margin: 4px 0 6px;
}
.alternar button {
  background: none; color: var(--text-fraco);
  padding: 9px 10px; border-radius: 6px;
  font-size: 13px; font-weight: 600;
  transition: background .14s, color .14s;
}
.alternar button:hover { color: var(--text-dim); }
.alternar button.ativo { background: var(--bg-700); color: var(--text); box-shadow: inset 0 0 0 1px var(--linha); }

/* ═════════════════════ estrutura da chamada ═════════════════════ */

/* A tela é uma grade de quatro colunas — trilha de servidores, canais,
   conteúdo — e o conteúdo se divide de novo entre palco e chat. Toda a lógica
   de "onde fica o chat" mora aqui, em duas regras, e não no JavaScript:
   sem transmissão o chat É o conteúdo; com transmissão o vídeo toma o meio e o
   chat encolhe para a coluna da direita. */

#app {
  height: 100%;
  display: grid;
  grid-template-columns: var(--trilha-w) var(--canais-w) minmax(0, 1fr);
  background: var(--bg-950);
}
#app[hidden] { display: none; }

#conteudo {
  display: grid;
  grid-template-rows: var(--top-h) minmax(0, 1fr);
  grid-template-areas: "top" "corpo";
  min-width: 0;
  /* Item de grade nasce com `min-height: auto`, que é "não encolha abaixo do seu
     conteúdo". Com um histórico longo isso fazia a linha inteira do #app crescer
     além da janela: a página ganhava rolagem própria e, ao rolar, a barra de
     cima e os controles de voz saíam de vista — o app inteiro andava junto,
     quando só o chat devia andar. */
  min-height: 0;
}

#corpo {
  grid-area: corpo;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  min-height: 0;
  /* Quadro de referência do painel quando ele sai da linha e vira folha (ver
     `painel-flutuante` abaixo): assim ele cobre o palco e só ele, sem passar
     por cima da barra de canais. */
  position: relative;
}
/* O momento em que alguém transmite: o palco entra, e o chat vira coluna.
   A alça de redimensionar mora na mesma célula do chat, encostada na borda
   esquerda — sem fixar a célula, ela era colocada automaticamente no lugar do
   painel e empurrava o chat para uma segunda linha, embaixo do vídeo. */
body.com-palco #corpo { grid-template-columns: minmax(0, 1fr) var(--side-w); }
body.com-palco #side,
body.com-palco .alca { grid-column: 2; grid-row: 1; }

/* Quando não cabem os dois na linha.

   Entre o breakpoint estreito e mais ou menos 980px de janela, palco e painel
   somados não têm espaço: o vídeo ficava com 200px, ou menos, e continuava
   encolhendo conforme o painel fosse arrastado. O `ajustarPainel` do ui.js
   mede o que sobra e liga esta classe quando nem o painel no mínimo deixa
   palco suficiente — daí ele sai da linha e vira folha por cima do vídeo,
   exatamente como já faz no celular.

   `min-width` porque abaixo de 861px o bloco de telas estreitas já resolve
   isto do seu jeito, e duas regras disputando o mesmo painel é como se ganha
   um bug que só aparece numa faixa de larguras. */
@media (min-width: 861px) {
  body.painel-flutuante.com-palco #corpo { grid-template-columns: minmax(0, 1fr); }
  body.painel-flutuante.com-palco .alca { display: none; }
  /* `grid-column: auto` não é enfeite, e custou uma medição para aparecer.
     Num contêiner de grade, um filho `absolute` que tem posição de grade
     explícita passa a ter como quadro de referência *aquela área*, e não o
     painel do pai. Com a grade em uma coluna só, a coluna 2 que o `com-palco`
     pediu lá em cima não existe — e o painel colapsava para 1px grudado na
     borda direita, visível no DOM e invisível na tela. */
  body.painel-flutuante.com-palco #side {
    grid-column: auto; grid-row: auto;
    position: absolute; inset: 0 0 0 auto;
    width: min(var(--side-w), 100%);
    z-index: 20;
    display: none;
    box-shadow: -12px 0 40px rgb(0 0 0 / .55);
  }
  body.painel-flutuante.com-palco #side.open { display: flex; }
  /* Os dois nascem com `.so-estreito`, que este mesmo breakpoint esconde: aqui
     eles voltam, porque uma folha por cima precisa de quem a abra e de quem a
     feche. O `!important` é para vencer o `!important` de lá; a especificidade
     desta regra é maior, então ela ganha. */
  body.painel-flutuante.com-palco #btn-side,
  body.painel-flutuante.com-palco #side-fechar { display: inline-flex !important; }
}

/* A barra de voz mora na coluna da esquerda, encostada na caixa de usuário —
   é lá que a mão já está quando se entra num canal, e é onde o Discord a põe.
   Ela só existe enquanto há chamada; fora dela seria uma fileira de botões que
   não fazem nada. */
body.na-voz .controls { display: flex; }

/* ───────── topo ───────── */
/* O topo do Discord não tem borda: ele projeta uma sombra curta sobre a
   conversa. A diferença importa num tema escuro — uma borda de 1px vira mais
   uma linha entre tantas, e a sombra diz "esta barra está por cima", que é a
   informação certa quando o histórico rola por baixo dela. */
.topbar {
  grid-area: top;
  position: relative; z-index: 2;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 0 8px 0 16px;
  background: var(--bg-900);
  box-shadow: 0 1px 0 rgb(0 0 0 / .28), 0 1px 3px rgb(0 0 0 / .16);
}
.room-id {
  display: flex; align-items: center; gap: 8px;
  font-size: 15px; min-width: 0; overflow: hidden;
}
.room-id strong {
  font-weight: 650; letter-spacing: -.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* O ícone do tipo de canal vem antes do nome, no lugar do "#" fixo que existia
   quando só havia salas: agora "#" e alto-falante distinguem texto de voz. */
.canal-icone { color: var(--text-fraco); font-size: 17px; }
#peer-count { white-space: nowrap; color: var(--text-fraco); font-size: 13px; }

.dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--green); flex: none;
  box-shadow: 0 0 0 3px rgb(45 212 167 / .16);
}
.dot.off { background: var(--red); box-shadow: 0 0 0 3px rgb(242 85 90 / .16); }
.dot.wait {
  background: var(--amber);
  box-shadow: 0 0 0 3px rgb(245 181 68 / .16);
  animation: pulsa 1.1s ease-in-out infinite;
}

.topbar-actions { display: flex; align-items: center; gap: 2px; flex: none; }
.topbar-actions button { white-space: nowrap; }

/* Ícone sem moldura, 32×32, com o realce só no hover — a fileira do canto
   direito do cabeçalho do Discord. Sem caixa, cinco ícones lidos como um grupo;
   com caixa, cinco botões disputando atenção com o nome do canal. */
.acao-topo {
  position: relative;
  flex: none;
  width: 32px; height: 32px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: none;
  border-radius: var(--r-sm);
  color: var(--text-dim);
  transition: background .12s, color .12s;
}
.acao-topo svg { width: 20px; height: 20px; fill: currentColor; }
.acao-topo:hover { background: var(--hover); color: var(--text); }
.acao-topo[aria-expanded="true"],
.acao-topo[aria-pressed="false"] { background: var(--ativo); color: var(--text); }
.acao-topo[hidden] { display: none; }

.ghost {
  background: var(--bg-700);
  border: 1px solid var(--linha);
  color: var(--text-dim);
  padding: 7px 13px;
  border-radius: var(--r-sm);
  font-size: 13px; font-weight: 550;
  display: inline-flex; align-items: center; gap: 7px;
  transition: background .14s, color .14s, border-color .14s;
}
.ghost:hover { background: var(--bg-600); color: var(--text); border-color: var(--linha-forte); }

.rot-curto { display: none; }

/* ───────── palco ───────── */
.stage {
  display: none;
  flex-direction: column; gap: 12px;
  /* O palco é uma caixa de tamanho fixo e as transmissões cabem dentro dela.
     Com `auto` era o contrário: os tiles definiam a altura e o palco rolava —
     numa janela larga, parte do vídeo ficava fora da vista. */
  overflow: hidden;
  padding: 14px;
  min-width: 0; min-height: 0;
}
body.com-palco .stage { display: flex; }
.grid {
  display: grid;
  gap: 12px;
  /* O número de colunas é conta, não palpite: quem escreve --colunas é o
     `ajustarColunas` do ui.js, que testa cada arranjo e fica com o que dá o
     maior tile. `auto-fit` não servia porque só enxerga largura. */
  grid-template-columns: repeat(var(--colunas, 1), minmax(0, 1fr));
  grid-auto-rows: minmax(0, 1fr);
  flex: 1;
  min-height: 0;
}
/* Aqui existia um teto de 820px amarrado a `(100vh - 240px) * 16/9`, para o
   participante sozinho não virar um retângulo alto demais. Era remendo para o
   palco que crescia sem limite — agora o tile é limitado pela altura da moldura
   e pela proporção da própria fonte, então o teto só desperdiçava largura e
   reintroduzia a suposição de 16:9 que o resto deixou de fazer. */
/* Em destaque, uma transmissão ocupa o palco e as outras viram uma tira embaixo.
   Embaixo e não ao lado porque tela é larga: uma coluna à direita cobraria da
   largura, que é justamente a dimensão que a transmissão precisa.

   `--tira` é a quantidade de miniaturas, escrita pelo ui.js — é ela que permite
   o destaque atravessar a linha inteira (`1 / -1`) com filhos soltos, sem
   embrulhar a tira num elemento à parte. */
.grid.focused {
  /* Frações, não pixels: assim a grade explícita ocupa a largura inteira e o
     destaque, que atravessa de `1 / -1`, pode usá-la toda. Com colunas de
     largura fixa ele ficava preso à soma das miniaturas — encolhendo justo
     quando há pouca gente. O tamanho de cada miniatura é escrito pelo ui.js. */
  grid-template-columns: repeat(var(--tira, 1), minmax(0, 1fr));
  grid-template-rows: minmax(0, 1fr) auto;
  justify-content: center;
  align-content: center;
}
/* Destaque sem ninguém na tira: uma linha só. A segunda, mesmo vazia, ainda
   reserva o espaço entre linhas — e o destaque ficava curto por 12px. */
.grid.focused.sem-tira { grid-template-rows: minmax(0, 1fr); }
.grid.focused .tile.focus { grid-row: 1; grid-column: 1 / -1; }
.grid.focused .tile:not(.focus) { grid-row: 2; }
/* Na tira o que importa é reconhecer de quem é, não ler o conteúdo: some tudo
   que só faz sentido no tamanho grande. */
.grid.focused .tile:not(.focus) .tile-actions,
.grid.focused .tile:not(.focus) .tile-stats { display: none; }
.grid.focused .tile:not(.focus) { cursor: pointer; }
.grid.focused .tile:not(.focus) .tile-label { font-size: 11px; }
/* Miniatura que não coube na tira. Some em vez de encolher até virar um risco:
   abaixo de 34px não se reconhece mais quem é. */
.grid.focused .tile.fora-da-tira { display: none; }
/* Quantas ficaram de fora — sem isso a pessoa não saberia que há mais gente. */
.grid.focused[data-escondidas]:not([data-escondidas="0"])::after {
  content: "+" attr(data-escondidas);
  grid-row: 2;
  align-self: center;
  padding: 6px 10px;
  border: 1px dashed var(--linha-forte);
  border-radius: var(--r-sm);
  color: var(--text-fraco);
  font-size: 11.5px;
  white-space: nowrap;
}

.empty-hint {
  text-align: center;
  color: var(--text-dim);
  margin: 4px 0 8px;
  order: -1;  /* sozinho na sala, "manda o convite" é a ação — vem antes do avatar */
}
.empty-hint p { margin: 3px 0; }
.empty-hint p:first-child { font-size: 17px; color: var(--text); font-weight: 600; letter-spacing: -.01em; }

/* ───────── tiles ───────── */
.tile {
  position: relative;
  background: radial-gradient(120% 120% at 50% 0%, var(--bg-700), var(--bg-950) 70%);
  border-radius: var(--r-lg);
  overflow: hidden;
  /* A proporção vem da fonte (escrita pelo ui.js quando o vídeo carrega); 16:9
     é só o palpite de quem ainda não tem vídeo — avatar, oferta de assistir. */
  aspect-ratio: var(--proporcao, 16 / 9);
  /* Limitado pelos DOIS lados da célula: é isto que impede o tile de crescer
     além da moldura. `margin: auto` centraliza o que sobrar.

     `width: 100%` não é redundante: `margin: auto` desliga o esticamento
     automático do item de grade, e sem uma largura definida o tile passa a
     medir o conteúdo. Um vídeo tem tamanho próprio e sobrevivia; um avatar
     não, e virava uma bolinha perdida no meio do palco. */
  /* Estes valores valem só até o `ajustarColunas` medir a moldura e escrever
     largura e altura em pixels — o CSS não consegue exprimir "largura = altura
     da célula × proporção", e é esse o caso quando a altura é o limite. */
  width: 100%; max-width: 100%; max-height: 100%; margin: auto;
  display: grid; place-items: center;
  border: 1px solid var(--linha);
  box-shadow: var(--sombra-md);
  transition: border-color .16s ease, box-shadow .16s ease;
}
.tile:hover { border-color: var(--linha-forte); }
/* O anel verde sozinho era discreto demais num grid de seis; o brilho ancora o
   olho sem piscar. Fica no box-shadow para não mexer no layout. */
.tile.speaking {
  border-color: var(--green);
  box-shadow: 0 0 0 1px var(--green), 0 0 28px -6px rgb(45 212 167 / .75), var(--sombra-md);
}
/* `aspect-ratio: auto` porque em destaque quem dá o tamanho é o ui.js, em
   pixels. Aqui havia também um `min-height: 320px` — piso do tempo em que o
   tile em destaque esticava para preencher a célula. Com o tamanho calculado
   ele virou um chão que ganhava da conta: numa janela estreita o destaque saía
   com 1.24 de proporção em vez de 1.78. */
.tile.focus { aspect-ratio: auto; }
.tile video {
  width: 100%; height: 100%;
  object-fit: contain; background: #000; display: block;
  transform-origin: center center; will-change: transform;
}
.tile.cam video { object-fit: cover; }

/* Tela cheia é do tile, e não do vídeo — assim o zoom e os controles continuam
   existindo lá dentro. O que muda aqui é só o que a moldura tinha de moldura:
   proporção fixa, cantos e borda não fazem sentido ocupando o monitor. */
.tile:fullscreen {
  width: 100vw; height: 100vh;
  aspect-ratio: auto;
  border: 0; border-radius: 0;
  background: #000;
}
/* Ali os controles são a única saída além do Esc, então não ficam escondidos
   esperando o ponteiro achar o canto certo. */
.tile:fullscreen .tile-actions { opacity: 1; }

.avatar {
  width: 92px; height: 92px; border-radius: 50%;
  background: linear-gradient(160deg, hsl(var(--tom, 245) 62% 58%), hsl(var(--tom, 245) 58% 42%));
  display: grid; place-items: center;
  font-size: 30px; font-weight: 700; color: #fff;
  text-transform: uppercase;
  box-shadow: 0 8px 26px hsl(var(--tom, 245) 60% 20% / .8), inset 0 1px 0 rgb(255 255 255 / .22);
}

.tile-label {
  position: absolute; left: 10px; bottom: 10px;
  background: rgb(8 9 13 / .72);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border: 1px solid var(--linha);
  padding: 4px 10px;
  border-radius: var(--r-full);
  font-size: 12.5px; font-weight: 550;
  display: flex; align-items: center; gap: 6px;
  max-width: calc(100% - 20px);
}
.tile-label .who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tile-label .tag { color: var(--text-fraco); font-size: 11px; flex: none; }
.tile-stats {
  position: absolute; right: 10px; top: 10px;
  background: rgb(8 9 13 / .72);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border: 1px solid var(--linha);
  padding: 3px 9px; border-radius: var(--r-full);
  font-size: 11px; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* Convite para assistir: ocupa o lugar que o vídeo ocuparia, para a grade não
   dançar quando o vídeo chega. */
.oferta {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; height: 100%; padding: 20px 20px 42px; text-align: center;
}
.oferta-icone { font-size: 30px; opacity: .8; }
.oferta-texto { font-size: 14px; color: var(--text-dim); margin: 0; }
.oferta-texto strong { color: var(--text); font-weight: 650; }
.oferta-botao {
  background: linear-gradient(180deg, var(--accent-claro), var(--accent));
  color: #fff;
  font-size: 13.5px; font-weight: 650;
  padding: 9px 22px; border-radius: var(--r-full);
  box-shadow: 0 6px 18px rgb(99 102 241 / .35);
  transition: filter .14s, opacity .14s;
}
.oferta-botao:hover:not(:disabled) { filter: brightness(1.08); }
.oferta-botao:disabled { opacity: .55; cursor: default; }

.tile-actions {
  position: absolute; right: 10px; bottom: 10px;
  display: flex; gap: 6px;
  /* Num tile estreito a fila não cabe numa linha só; quebrar é melhor que
     vazar por cima do nome de quem transmite. */
  flex-wrap: wrap; justify-content: flex-end;
  max-width: calc(100% - 20px);
  opacity: 0; transform: translateY(4px);
  transition: opacity .16s, transform .16s;
}
.tile:hover .tile-actions,
.tile:focus-within .tile-actions { opacity: 1; transform: none; }
.tile-actions button, .zoom-grupo button {
  background: rgb(8 9 13 / .8);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border: 1px solid var(--linha);
  color: #fff;
  padding: 6px 10px; border-radius: var(--r-sm);
  font-size: 12px; font-weight: 550;
  transition: background .14s, border-color .14s;
}
/* Flutuando agora: o botão fica aceso, porque a janelinha pode estar atrás de
   outra coisa e o tile é o único lugar onde se descobre que ela existe. */
.tile-actions button.ativo {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.tile-actions button:hover, .zoom-grupo button:hover {
  background: rgb(30 34 44 / .95); border-color: var(--linha-forte);
}
/* Volume de quem se está assistindo. Fica no canto oposto às outras ações —
   na mesma fila delas, um tile estreito quebrava tudo em três linhas. Aparece
   no hover como o resto, mas não some enquanto estiver silenciado. */
.tile-vol {
  position: absolute; left: 10px; bottom: 46px;
  display: flex; align-items: center; gap: 6px;
  opacity: 0; transform: translateY(4px);
  transition: opacity .16s, transform .16s;
  background: rgb(8 9 13 / .8);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border: 1px solid var(--linha);
  border-radius: var(--r-sm);
  padding: 0 8px 0 2px;
}
.tile-vol button {
  background: none; border: none; padding: 5px 6px;
  display: grid; place-items: center;
  color: #fff;
}
.tile-vol button:hover { background: none; color: var(--accent-claro); }
.tile-vol button .ic { width: 15px; height: 15px; }
.tile-vol input[type=range] { width: 84px; }
.tile-vol .tile-vol-valor {
  font-size: 11px; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
  min-width: 30px; text-align: right;
}
.tile:hover .tile-vol,
.tile:focus-within .tile-vol { opacity: 1; transform: none; }
/* Silenciado é estado, não gesto passageiro: fica à vista mesmo sem o mouse em
   cima, senão a tela muda vira mistério. */
.tile-vol.mudo { opacity: 1; transform: none; border-color: rgb(245 200 107 / .5); }
.tile-vol.mudo button { color: #f5c86b; }

.zoom-grupo { display: flex; gap: 4px; }
.zoom-grupo button { min-width: 28px; font-variant-numeric: tabular-nums; }
/* Com zoom aplicado, os controles ficam sempre à mão. */
.tile:has(video[style*="scale("]):not(:has(video[style*="scale(1)"])) .tile-actions {
  opacity: 1; transform: none;
}

/* ═════════════════════ painel lateral ═════════════════════ */

#side {
  background: var(--bg-900);
  min-width: 0;
  border-left: 1px solid var(--linha);
  display: flex; flex-direction: column;
  min-height: 0;
}
.tabs {
  display: flex; align-items: center;
  padding: 6px 8px 0;
  gap: 4px;
  border-bottom: 1px solid var(--linha);
}
/* Abas em pílula, não sublinhado: o alvo de clique fica óbvio e a aba ativa
   lê como um botão pressionado, não como um link. */
.tab {
  flex: 1;
  background: none; color: var(--text-fraco);
  height: 32px; padding: 0 12px; margin-bottom: 6px;
  font-size: 13.5px; font-weight: 600;
  border-radius: var(--r-sm);
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  transition: background .12s, color .12s;
}
.tab:hover { color: var(--text-dim); background: var(--hover); }
.tab.active { color: var(--text); background: var(--ativo); }

/* O número fica na quina do ícone, e não ao lado: ao lado ele empurraria o
   botão de fechar a cada mensagem fixada. */
.fixadas-contagem {
  position: absolute; top: -1px; right: -2px;
  min-width: 14px; height: 14px; padding: 0 3px;
  display: grid; place-content: center;
  background: var(--accent); color: #fff;
  border-radius: var(--r-full);
  font-size: 9.5px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums;
}
.fixadas-contagem[hidden] { display: none; }

.badge {
  background: var(--red); color: #fff;
  border-radius: var(--r-full);
  min-width: 18px; padding: 0 5px;
  font-size: 11px; font-weight: 700; line-height: 18px;
  text-align: center;
}

.tab-panel { display: none; flex: 1; min-height: 0; flex-direction: column; }
.tab-panel.active { display: flex; }

#side-fechar {
  background: none; color: var(--text-fraco);
  padding: 0 10px; margin-bottom: 6px;
  font-size: 15px; flex: none;
  align-self: stretch;
}
#side-fechar:hover { color: var(--text); }

/* ───────── pessoas ───────── */
#people { list-style: none; margin: 0; padding: 0 8px 4px; overflow-y: auto; }
#people li {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  min-height: 42px; padding: 4px 8px; border-radius: var(--r-sm);
  font-size: 14.5px;
  transition: background .12s;
}
#people li:hover { background: var(--hover); }
#people .pin {
  width: 32px; height: 32px; border-radius: 50%;
  background: linear-gradient(160deg, hsl(var(--tom, 245) 62% 56%), hsl(var(--tom, 245) 58% 40%));
  color: #fff;
  display: grid; place-items: center;
  font-size: 11.5px; font-weight: 700; text-transform: uppercase;
  flex: none;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .2);
  transition: box-shadow .16s;
}
#people .pin.speaking { box-shadow: 0 0 0 2px var(--green), 0 0 12px -2px rgb(45 212 167 / .9); }
#people .name {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: 500;
}

#people .flags { display: flex; gap: 5px; font-size: 10.5px; font-weight: 600; letter-spacing: .02em; }
#people .flags span { padding: 2px 7px; border-radius: var(--r-full); text-transform: uppercase; }
#people .flags .on { color: var(--green); background: rgb(45 212 167 / .14); }
#people .flags .off { color: var(--red); background: rgb(242 85 90 / .14); }
/* Ausente não é erro nem conquista: cinza, para não competir com mudo e tela. */
#people .flags .neutro { color: var(--text-fraco); background: var(--bg-700); }

#people .conexao { width: 8px; height: 8px; border-radius: 50%; flex: none; }
#people .conexao.connected { background: var(--green); }
#people .conexao.connecting,
#people .conexao.new { background: var(--amber); animation: pulsa 1.1s ease-in-out infinite; }
#people .conexao.disconnected { background: var(--amber); }
/* Travado parou de piscar de propósito — piscar sugere que ainda vai conectar. */
#people .conexao.failed,
#people .conexao.travado,
#people .conexao.closed { background: var(--red); animation: none; }

#people .volume {
  flex-basis: 100%;
  display: flex; align-items: center; gap: 9px;
  padding: 4px 0 0 42px;
}
/* Onde há ponteiro fino, o slider recolhe: a lista volta a ser uma lista de
   pessoas. Quem já mexeu no volume de alguém continua vendo aquele slider. */
@media (hover: hover) and (pointer: fine) {
  #people li:not(:hover):not(:focus-within):not(.vol-alterado) .volume {
    display: none;
  }
}
#people .volume input[type=range] { flex: 1; }
#people .volume span {
  font-size: 11px; color: var(--text-fraco);
  min-width: 34px; text-align: right;
  font-variant-numeric: tabular-nums;
}
#people li.mudo-local .name { opacity: .45; text-decoration: line-through; }

/* ───────── chat ───────── */
/* A conversa é uma coluna de blocos com uma SARJETA à esquerda: 68px vazios
   onde mora o avatar de quem fala — e, nas linhas coladas, a hora que só
   aparece com o ponteiro em cima. É a anatomia do Discord, e ela resolve duas
   coisas de uma vez: o olho acha o dono do bloco pela cor do disco antes de ler
   o nome, e o realce de hover pode cobrir a linha inteira de ponta a ponta,
   porque agora é o <li> que tem a largura toda e não o texto.

   Por isso o padding horizontal saiu da lista e foi para cada linha: com ele na
   lista, o realce parava a 12px da borda e ficava com cara de cartão. */
#messages {
  --sarjeta: 68px;
  --avatar-msg: 40px;
  list-style: none; margin: 0; padding: 0 0 20px;
  overflow-y: auto; flex: 1;
  /* Sem isto o `overflow-y: auto` acima é decorativo: a lista cresce com o
     histórico em vez de rolar dentro do próprio quadro. Ver a mesma armadilha
     em #conteudo. */
  min-height: 0;
  display: flex; flex-direction: column; gap: 0;
}
/* Numa coluna flex com rolagem, o padrão `flex-shrink: 1` faz cada linha ceder
   altura quando o histórico passa da moldura: as mensagens ficavam esmagadas e
   escritas por cima umas das outras, e só depois de rolar até o fim. Nenhuma
   linha da conversa deve encolher — quem rola é a lista. */
#messages > * { flex: 0 0 auto; }
#messages li { font-size: 15px; word-break: break-word; line-height: 1.45; }
/* A mensagem pode ter várias linhas desde que o campo virou textarea, e as
   quebras chegam aqui como texto. `pre-wrap` é o que as desenha — e de quebra
   preserva a indentação de quem colou um trecho de código, que o servidor já
   guardava intacta. O `break-word` acima continua cuidando da linha comprida
   sem espaço nenhum. */
#messages .corpo { white-space: pre-wrap; }
/* A barrinha de ações fica ancorada na mensagem, e o realce de citação precisa
   de um fundo que passe da margem do texto — os dois pedem posicionamento. */
#messages li.msg {
  position: relative;
  padding: 1px 52px 1px var(--sarjeta);
  min-height: 21px;
}
/* Um bloco novo respira; uma linha colada não. O espaço é margem do primeiro
   da vez, e não `gap` da lista: `gap` separaria TODAS as linhas, inclusive as
   que devem ler como um parágrafo só. */
#messages li.msg:not(.seguida) { margin-top: 17px; padding-top: 2px; }
#messages li.msg:first-child { margin-top: 8px; }
/* O realce de hover cobre a linha inteira, sarjeta inclusa — é ele que diz
   qual mensagem a barra de ações lá em cima vai afetar. */
#messages li.msg:hover { background: rgb(255 255 255 / .035); }

/* Com o palco no ar, a conversa vira uma coluna de ~360px. A sarjeta de 68px
   comeria um quinto da linha, então ela encolhe junto com o avatar — e a folga
   da direita, que só existe para a barra de ações não cobrir texto, cede o
   lugar: ali a barra passa a flutuar por cima mesmo, que é o que o Discord faz
   quando o espaço aperta. */
body.com-palco #side:not(.largo) #messages { --sarjeta: 52px; --avatar-msg: 32px; }
body.com-palco #side:not(.largo) #messages li.msg { padding-right: 16px; }
body.com-palco #side:not(.largo) #messages .msg-avatar { left: 12px; top: 3px; }

#messages .msg-avatar {
  position: absolute;
  left: 16px; top: 2px;
  width: var(--avatar-msg); height: var(--avatar-msg);
  border-radius: 50%;
  background: linear-gradient(160deg, hsl(var(--tom, 245) 62% 56%), hsl(var(--tom, 245) 58% 40%));
  color: #fff;
  display: grid; place-items: center;
  font-size: 13px; font-weight: 700; line-height: 1; text-transform: uppercase;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .18);
  user-select: none;
}
#messages .msg-avatar[data-membro] { cursor: pointer; }
#messages .msg-avatar[data-membro]:hover { filter: brightness(1.12); }

/* A hora de uma linha colada, no lugar vago do avatar. Ela ocupa o espaço o
   tempo todo (não é `display:none`), então acender no hover não empurra nada. */
#messages .time-sarjeta {
  position: absolute;
  left: 0; top: 3px;
  width: var(--sarjeta);
  padding-right: 14px;
  text-align: right;
  font-size: 10.5px; line-height: 1.5;
  color: var(--text-fraco);
  font-variant-numeric: tabular-nums;
  opacity: 0;
  transition: opacity .1s;
  user-select: none;
}
#messages li.msg:hover .time-sarjeta { opacity: 1; }

/* Nome e hora numa caixa de bloco: o corpo começa na linha de baixo sem
   depender de um <br>, e um nome comprido embrulha dentro do próprio recuo. */
#messages .cabeca { display: block; line-height: 1.375; }
#messages .who { font-weight: 600; font-size: 15px; margin-right: 8px; }
#messages .who[data-membro] { cursor: pointer; }
#messages .who[data-membro]:hover { text-decoration: underline; }
#messages .time { color: var(--text-fraco); font-size: 11px; font-variant-numeric: tabular-nums; }
#messages li.sys {
  color: var(--text-fraco); font-size: 12.5px;
  text-align: center;
  padding: 2px 16px;
  margin: 10px 0;
  position: relative;
}
/* Linha de sistema com filetes dos dois lados: separa os eventos da sala das
   falas das pessoas sem precisar de itálico. */
#messages li.sys::before, #messages li.sys::after {
  content: ""; position: absolute; top: 50%;
  width: 14%; height: 1px; background: var(--linha);
}
#messages li.sys::before { left: 16px; }
#messages li.sys::after { right: 16px; }

/* "O discordo mudou": o que veio na última atualização, uma vez só.
   Cartão e não linha de sistema — as linhas de sistema são eventos da sala
   ("fulano entrou"), curtos e descartáveis, e esta é uma lista que alguém
   escreveu para ser lida. A faixa à esquerda usa a cor da marca porque é o app
   falando de si mesmo, e não alguém falando na sala. */
#messages li.atualizacao {
  align-self: stretch;
  margin: 14px 16px 4px;
  padding: 10px 14px;
  background: var(--accent-fundo);
  border: 1px solid rgb(99 102 241 / .28);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-size: 13px;
}
#messages .atualizacao-titulo {
  font-weight: 650;
  color: var(--accent-claro);
  margin-bottom: 5px;
}
#messages .atualizacao-lista {
  margin: 0; padding-left: 18px;
  display: flex; flex-direction: column; gap: 3px;
  color: var(--text-dim);
}
#messages .atualizacao-lista li { list-style: disc; font-size: 13px; }

/* A imagem na conversa é uma miniatura, e o clique abre o tamanho real.
   Antes ela ocupava a largura inteira da coluna e a altura que quisesse: uma
   foto em pé de 1200×1600 saía com 800px de altura e empurrava a conversa toda
   para fora da tela — quem mandava um print enterrava o que estava sendo dito.

   Os dois limites juntos são o que preserva a proporção. Com `max-width`
   sozinho, uma foto em pé encolhia de largura e continuava altíssima; com
   `max-height` sozinho, uma panorâmica estouraria a coluna. Para um <img> de
   dimensão automática, o navegador resolve os dois mantendo a proporção — é
   regra do CSS para elemento substituído, não sorte. */
#messages .anexo {
  display: block; margin-top: 7px;
  /* Os atributos width/height já vêm com a medida da miniatura, calculada no
     `medidaDaMiniatura` — a caixa é exata e o espaço fica reservado antes de a
     imagem chegar. O que sobra para o CSS é a janela estreita: aí a largura
     cede e a altura acompanha pela proporção.

     Tentar os dois limites só com `max-width`/`max-height` não funciona: com a
     largura presa no atributo, é a altura que cede sozinha e a imagem sai
     esmagada. Foi medido. */
  height: auto;
  max-width: 100%;
  border-radius: var(--r-md);
  border: 1px solid var(--linha);
  cursor: zoom-in;
  background: var(--bg-950);
  transition: border-color .14s, filter .14s;
}
#messages .anexo:hover { border-color: var(--linha-forte); filter: brightness(1.06); }

/* ───────── miniatura de link (ver public/js/embutir.js) ───────── */

#messages .embutido {
  display: block;
  margin-top: 7px;
  max-width: min(400px, 100%);
  border-radius: var(--r-md);
  overflow: hidden;
}

/* Link que aponta direto para uma imagem. Sem atributo de tamanho — ninguém
   sabe as dimensões antes de ela chegar —, então são os dois limites do CSS que
   a encaixam. Com os dois lados livres o navegador mantém a proporção; foi ter
   um lado preso num atributo que esmagava a imagem do anexo. */
#messages .embutido-imagem {
  max-height: 300px;
  border: 1px solid var(--linha);
  cursor: zoom-in;
  background: var(--bg-950);
  transition: border-color .14s, filter .14s;
}
#messages .embutido-imagem:hover { border-color: var(--linha-forte); filter: brightness(1.06); }

#messages .embutido.youtube {
  border: 1px solid var(--linha);
  background: var(--bg-950);
}
/* A moldura é 16:9 e a capa do YouTube vem 4:3, com tarja preta em cima e
   embaixo. `cover` corta exatamente as duas tarjas: a capa é 480×360 com o
   vídeo ocupando 480×270 no meio, e a conta fecha. */
#messages .embutido-capa {
  display: block; position: relative;
  width: 100%; aspect-ratio: 16 / 9;
  padding: 0; border: 0;
  background: #000;
  cursor: pointer;
}
#messages .embutido-capa img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
/* O botão de play: vermelho do YouTube, para o olho reconhecer antes de ler. */
#messages .embutido-play {
  position: absolute; inset: 0;
  display: grid; place-content: center;
  pointer-events: none;
}
#messages .embutido-play svg {
  /* `content-box` porque a folha põe `border-box` em tudo, e ali o padding
     comeria o desenho por dentro em vez de virar a pastilha em volta dele: o
     botão saía do tamanho do ícone, 26px, em vez dos 50 que a pastilha pede. */
  box-sizing: content-box;
  width: 26px; height: 26px; fill: #fff;
  padding: 9px 11px 9px 13px;
  background: rgb(220 40 40 / .92);
  border-radius: var(--r-full);
  box-shadow: 0 2px 12px rgb(0 0 0 / .45);
  transition: transform .14s, background .14s;
}
#messages .embutido-capa:hover .embutido-play svg { transform: scale(1.08); background: rgb(235 45 45 / .96); }
#messages .embutido iframe { display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; }
/* O rodapé leva para o site de origem — quem quer comentário, descrição ou
   tela cheia de verdade vai para lá, e não fica preso num tocador de 400px. */
#messages .embutido-fonte {
  display: block;
  padding: 6px 10px;
  font-size: 11.5px; font-weight: 600;
  color: var(--text-dim);
  text-decoration: none;
  border-top: 1px solid var(--linha);
}
#messages .embutido-fonte:hover { color: var(--text); background: var(--bg-900); }
#messages .progresso {
  margin-top: 7px; height: 5px;
  border-radius: var(--r-full);
  background: var(--bg-700);
  overflow: hidden;
}
#messages .progresso i {
  display: block; height: 100%; width: 0;
  border-radius: var(--r-full);
  background: linear-gradient(90deg, var(--accent), var(--accent-claro));
  transition: width .12s linear;
}
/* O texto de serviço do balão que está subindo ("enviando… 40%"). Some quando
   a imagem chega; não confundir com a legenda, que é o que a pessoa escreveu. */
#messages .envio-aviso { font-size: 11.5px; color: var(--text-fraco); margin-top: 5px; }
/* A legenda do balão em envio ocupa a linha inteira, como o texto de uma
   mensagem de verdade — e com as mesmas quebras, que é o que `pre-wrap` faz. */
#messages .corpo-texto { display: block; white-space: pre-wrap; }
#messages .corpo-texto[hidden] { display: none; }
/* O prazo venceu e o arquivo saiu do disco. A mensagem continua na conversa —
   apagá-la abriria um buraco no histórico —, e este é o lugar onde a imagem
   estava, dizendo o que houve. */
#messages .anexo-expirado {
  margin-top: 6px;
  padding: 14px 16px;
  border: 1px dashed var(--linha);
  border-radius: var(--r-sm);
  font-size: 12.5px; color: var(--text-fraco);
}

/* ───────── citar alguém, editar, apagar ───────── */

/* A pílula de uma citação. Quem foi citado é você: o fundo fica mais forte e o
   texto assume a cor da marca, para o olho achar isso rolando a conversa. */
/* Endereço no meio da conversa. Sublinhado discreto e não a cor forte inteira:
   num canal onde se trocam links o tempo todo, texto colorido em cada linha
   compete com a conversa. O `break-all` no hover não existe — a URL comprida já
   quebra pelo `word-break` da mensagem. */
#messages .link {
  color: var(--accent-claro);
  text-decoration: underline;
  text-decoration-color: rgb(139 143 248 / .4);
  text-underline-offset: 2px;
}
#messages .link:hover { text-decoration-color: currentColor; }

#messages .mencao {
  color: var(--accent-claro);
  background: rgb(99 102 241 / .1);
  border-radius: 3px;
  padding: 0 2px;
  font-weight: 600;
  /* A pílula não pode quebrar no meio do nome. */
  white-space: nowrap;
}
#messages .mencao.eu { background: var(--accent-fundo); color: #c7c9ff; }

/* ───────── formatação do texto (ver public/js/markdown.js) ───────── */

/* Um bloco de código ou uma citação ocupam a linha inteira, e o `.corpo` é um
   <span>. Caixa de bloco dentro de caixa em linha funciona — o navegador parte
   o inline em volta —, mas a marca "(editado)" logo depois ficaria pendurada
   numa linha sozinha. Com `display: block` no corpo inteiro, ela cai embaixo do
   bloco, que é onde o olho a procura. O `:has` custa o preço de uma consulta
   por mensagem desenhada, e só nas que têm bloco. */
#messages .corpo:has(> pre, > blockquote) { display: block; }

#messages .corpo strong { font-weight: 700; }
#messages .corpo em { font-style: italic; }
/* O sublinhado do markdown compete com o do link. Fica mais fino e afastado,
   para os dois poderem aparecer na mesma frase sem virar a mesma coisa. */
#messages .corpo u { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 2px; }
#messages .corpo s { text-decoration-color: var(--text-fraco); }

/* `código` no meio da frase. Fundo mais escuro que a conversa e não mais claro:
   o texto de código recua um passo, como tudo que não é fala. */
#messages .codigo {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: .88em;
  background: var(--bg-950);
  border: 1px solid var(--linha);
  border-radius: 4px;
  padding: .1em .32em;
  /* A crase não quebra no meio: um identificador partido em duas linhas deixa
     de ser copiável com um duplo clique. */
  white-space: pre-wrap;
  word-break: break-word;
}

/* O quadro de código. Rola na horizontal em vez de quebrar a linha — uma pilha
   de erro ou uma tabela alinhada por espaços perdem o sentido reflowadas, e a
   coluna do chat é estreita demais para caber a maioria delas. */
#messages .bloco-codigo {
  margin: 6px 0 0;
  padding: 10px 12px;
  background: var(--bg-950);
  border: 1px solid var(--linha);
  border-radius: var(--r-sm);
  overflow-x: auto;
  max-width: 100%;
}
#messages .bloco-codigo code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 12.5px;
  line-height: 1.45;
  white-space: pre;
  background: none; border: 0; padding: 0;
  color: var(--text);
}
/* O nome da linguagem, quando veio na cerca. Canto de cima, apagado: serve para
   confirmar o que se colou, não para ser lido. */
#messages .bloco-codigo[data-lingua]::before {
  content: attr(data-lingua);
  display: block;
  font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-fraco);
  margin-bottom: 6px;
}

/* Citação: a barra à esquerda é o que se enxerga rolando, e por isso ela é a
   única coisa colorida aqui — o texto citado continua com a cor do texto. */
#messages .citado {
  margin: 4px 0 0;
  padding: 1px 0 1px 10px;
  border-left: 3px solid var(--bg-500);
  border-radius: 1px;
  color: var(--text-dim);
}

/* O borrão que só abre no clique. É um <button> de verdade (chega-se por Tab),
   com toda a aparência de botão desfeita. */
#messages .spoiler {
  font: inherit; color: transparent;
  background: var(--bg-600);
  border: 0; border-radius: 4px;
  padding: 0 3px; margin: 0;
  cursor: pointer;
  user-select: none;
  transition: background .12s, color .12s;
}
#messages .spoiler:hover { background: var(--bg-500); }
#messages .spoiler.aberto {
  color: inherit;
  background: var(--linha);
  cursor: text;
  user-select: text;
}

/* A mensagem inteira que chama você. A faixa à esquerda é o que se enxerga de
   longe; o fundo âmbar bem lavado é para não competir com um print ao lado. */
/* Citação: a linha inteira acesa e um traço âmbar na borda. Com o <li> indo de
   ponta a ponta, o realce não precisa mais de margem negativa para escapar do
   recuo do texto — ele já nasce do tamanho da linha. */
#messages li.mencionado { background: rgb(245 181 68 / .07); box-shadow: inset 2px 0 0 var(--amber); }
#messages li.mencionado:hover { background: rgb(245 181 68 / .11); }

#messages .editado {
  color: var(--text-fraco);
  font-size: 10.5px;
  margin-left: 5px;
  user-select: none;
}

/* Mensagem fixada. O alfinete fica na margem, onde não empurra o texto, e a
   linha ganha um traço à esquerda — o mesmo recurso da citação de alguém, que
   é o que o olho já aprendeu a ler como "esta linha é diferente". */
#messages li.fixada {
  box-shadow: inset 2px 0 0 var(--accent);
  background: rgb(99 102 241 / .05);
}
#messages li.fixada:hover { background: rgb(99 102 241 / .09); }
/* Ao lado da hora, e não na margem: a lista de mensagens tem overflow, e o que
   fica à esquerda dela some atrás da borda. */
#messages .fixada-marca {
  display: inline-flex; vertical-align: -1px;
  margin-left: 5px;
  color: var(--accent-claro);
}
#messages .fixada-marca svg { width: 11px; height: 11px; fill: currentColor; }
/* Fixada e citando você ao mesmo tempo: a faixa âmbar do "isto é comigo" vence,
   porque é a que exige ação. O alfinete continua na margem dizendo o resto. */
#messages li.fixada.mencionado { box-shadow: inset 2px 0 0 var(--amber); }

/* O painel das fixadas: um cartão por cima da conversa, não uma terceira aba. */
.painel-fixadas {
  position: fixed; z-index: 46;
  width: min(340px, calc(100vw - 16px));
  display: flex; flex-direction: column;
  overflow-y: auto;
  background: var(--bg-800);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-md);
  box-shadow: var(--sombra-lg);
  padding: 6px;
}
.fixadas-topo {
  padding: 6px 8px 8px;
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-fraco);
}
.fixadas-vazio {
  margin: 0; padding: 2px 8px 10px;
  font-size: 12.5px; line-height: 1.5; color: var(--text-dim);
}
.fixada-item {
  display: flex; align-items: stretch; gap: 2px;
  border-radius: var(--r-sm);
}
.fixada-item:hover { background: var(--bg-700); }
.fixada-ir {
  flex: 1; min-width: 0;
  display: grid; grid-template-columns: auto auto 1fr; align-items: baseline; gap: 6px;
  padding: 7px 8px;
  background: none; border: 0; border-radius: var(--r-sm);
  font: inherit; text-align: left;
  cursor: pointer;
}
.fixada-quem { font-size: 12.5px; font-weight: 650; }
.fixada-hora { font-size: 10.5px; color: var(--text-fraco); font-variant-numeric: tabular-nums; }
/* Uma linha só e reticências: o painel é uma lista de atalhos, e um parágrafo
   inteiro aqui faria dele um segundo histórico. */
.fixada-texto {
  grid-column: 1 / -1;
  font-size: 12.5px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fixada-texto.anexo { font-style: italic; }
.fixada-tirar {
  flex: none; width: 28px;
  background: none; color: var(--text-fraco);
  border: 0; border-radius: var(--r-sm);
  font-size: 13px; cursor: pointer;
  opacity: 0;
  transition: opacity .12s, color .12s, background .12s;
}
/* Aparece com o ponteiro no item, e sempre para quem navega por teclado —
   um botão que só existe no hover é um botão que não existe para o Tab. */
.fixada-item:hover .fixada-tirar, .fixada-tirar:focus-visible { opacity: 1; }
.fixada-tirar:hover { color: var(--red); background: rgb(242 85 90 / .12); }

/* Ações da mensagem: escondidas até o mouse chegar perto. Ficam num cartão
   flutuando sobre o canto de cima, como no Discord — dentro do texto elas
   empurrariam a linha e mudariam a conversa de lugar a cada passagem do mouse. */
.msg-acoes {
  position: absolute; top: -14px; right: 16px;
  display: flex; gap: 2px;
  padding: 2px;
  background: var(--bg-800);
  border: 1px solid var(--linha);
  border-radius: var(--r-sm);
  box-shadow: var(--sombra-md);
  opacity: 0; pointer-events: none;
  transition: opacity .1s;
}
#messages li:hover .msg-acoes,
.msg-acoes:focus-within { opacity: 1; pointer-events: auto; }
.msg-acoes button {
  background: none; border: 0; cursor: pointer;
  padding: 4px; border-radius: 5px;
  color: var(--text-dim);
  display: grid; place-items: center;
}
.msg-acoes button svg { width: 16px; height: 16px; fill: currentColor; }
.msg-acoes button:hover { background: var(--bg-600); color: var(--text); }
.msg-acoes button.perigo:hover { color: var(--red); }
/* Enquanto se edita, as ações sairiam por cima do campo. */
#messages li.editando .msg-acoes { display: none; }
#messages li.editando > .corpo, #messages li.editando > .editado { display: none; }

.msg-editor { position: relative; margin-top: 4px; }
/* Editar usa o mesmo campo de escrever, pelo mesmo motivo: a mensagem que se
   está corrigindo pode ter as várias linhas com que foi escrita. */
.msg-editor textarea {
  width: 100%;
  background: var(--bg-950);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-sm);
  color: var(--text);
  padding: 9px 11px;
  font: inherit;
  line-height: 1.45;
  resize: none;
  display: block;
  max-height: calc(1.45em * 10 + 18px);
  overflow-y: auto;
}
.msg-editor textarea:focus { outline: none; border-color: var(--accent); }
.msg-editor .dica { font-size: 11px; color: var(--text-fraco); margin-top: 4px; }
.msg-editor .dica button {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--accent-claro); font: inherit;
}
.msg-editor .dica button:hover { text-decoration: underline; }

/* O menu que abre ao digitar "@". Sobe a partir do campo — em cima dele há
   conversa, e embaixo há a borda da janela. */
.mencoes {
  position: absolute; left: 0; right: 0; bottom: calc(100% + 6px);
  z-index: 20;
  max-height: 236px; overflow-y: auto;
  background: var(--bg-800);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-md);
  box-shadow: var(--sombra-lg);
  padding: 6px;
}
.mencao-op {
  display: flex; align-items: center; gap: 9px;
  width: 100%;
  background: none; border: 0; cursor: pointer;
  padding: 6px 8px; border-radius: var(--r-sm);
  color: var(--text-dim); font: inherit; text-align: left;
}
.mencao-op.ativa { background: var(--bg-600); color: var(--text); }
.mencao-op .pin {
  width: 24px; height: 24px; flex: none;
  border-radius: var(--r-full);
  background: hsl(var(--tom) 45% 42%);
  color: #fff; font-size: 10px; font-weight: 700;
  display: grid; place-items: center;
  text-transform: uppercase;
}
.mencao-op .pin.todos { background: var(--bg-500); }
.mencao-op .nome { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mencao-op .dica { font-size: 11px; color: var(--text-fraco); }

/* Canal com citação sua: o "@" cabe no mesmo selo, que já é vermelho. */
.badge.cita { padding: 0 6px; letter-spacing: .2px; }

/* ── reagir ─────────────────────────────────────────────────────────────── */

/* As pílulas vivem embaixo da mensagem, na largura do texto. Vazia, a caixa não
   ocupa nada — ela existe sempre para o redesenho ter o que substituir quando a
   primeira reação chegar. */
.reacoes {
  display: flex; flex-wrap: wrap; gap: 4px;
  margin-top: 5px;
}
.reacoes:empty { display: none; }

.reacao {
  display: inline-flex; align-items: center; gap: 5px;
  background: var(--bg-700);
  border: 1px solid var(--linha);
  border-radius: var(--r-full);
  padding: 2px 8px 2px 6px;
  font-size: 12.5px; line-height: 1.5;
  color: var(--text-dim);
  transition: background .12s, border-color .12s;
}
.reacao:hover { background: var(--bg-600); border-color: var(--linha-forte); }
/* A sua reação fica marcada, e não só contada: o número diz quantos, e a borda
   diz se você é um deles — que é a pergunta que se faz antes de clicar. */
.reacao.minha {
  background: var(--accent-fundo);
  border-color: var(--accent);
  color: var(--accent-claro);
}
.reacao-emoji { font-size: 14px; line-height: 1; }
.reacao-conta { font-variant-numeric: tabular-nums; font-weight: 600; }
/* O "+" é um botão como as outras pílulas, e por isso tem a cor delas. Ele já
   foi mais apagado, para recuar; sobre o --bg-700 da pílula isso dava 3,31:1, e
   um controle ilegível não recua — some. */
.reacao.mais {
  padding: 2px 9px;
  color: var(--text-dim);
  font-size: 14px; font-weight: 600;
}

/* O seletor de emoji. `fixed` porque a âncora vive numa lista que rola: um
   `absolute` relativo a ela sairia junto com a rolagem. */
.seletor-emoji {
  position: fixed;
  z-index: 45;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 2px;
  padding: 6px;
  background: var(--bg-800);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-md);
  box-shadow: var(--sombra-lg);
  animation: sobe .12s ease-out;
}
.seletor-emoji button {
  width: 34px; height: 34px;
  display: grid; place-items: center;
  background: none; border: 0; border-radius: var(--r-sm);
  font-size: 19px; line-height: 1;
}
.seletor-emoji button:hover { background: var(--bg-600); }

/* ── responder ──────────────────────────────────────────────────────────── */

/* O cabeçalho citado, acima da mensagem. Recuado e apagado de propósito: é
   contexto, não conteúdo — quem lê a conversa passa o olho por ele e vai para o
   texto de baixo. Ele é um <button> porque leva a algum lugar. */
.responde {
  position: relative;
  display: flex; align-items: center; gap: 6px;
  width: 100%;
  background: none; border: 0; padding: 0 0 3px 0;
  font: inherit; font-size: 12.5px;
  color: var(--text-fraco);
  text-align: left;
  min-width: 0;
  cursor: pointer;
}
/* O cotovelo que liga a citação à mensagem, desenhado na sarjeta com duas
   bordas e um canto arredondado — o mesmo traço do Discord. Ele parte de onde o
   avatar começa e sobe até a metade da linha citada, que é o que faz as duas
   lerem como uma coisa só e não como duas linhas empilhadas. */
.responde::before {
  content: "";
  position: absolute;
  left: calc(16px + var(--avatar-msg, 40px) / 2 - var(--sarjeta, 68px));
  right: 100%;
  top: 50%; bottom: 0;
  margin-right: 4px;
  border-left: 2px solid var(--linha-forte);
  border-top: 2px solid var(--linha-forte);
  border-top-left-radius: 6px;
  pointer-events: none;
}
.responde:hover .responde-previa { color: var(--text-dim); }
.responde:hover::before { border-color: var(--text-fraco); }
.responde-seta { display: none; }
/* O avatarzinho de quem está sendo respondido: a mesma cara do bloco lá em
   cima, no tamanho de uma linha de texto. */
.responde-pin {
  width: 16px; height: 16px; flex: none;
  border-radius: 50%;
  background: linear-gradient(160deg, hsl(var(--tom, 245) 62% 56%), hsl(var(--tom, 245) 58% 40%));
  color: #fff;
  display: grid; place-items: center;
  font-size: 7.5px; font-weight: 700; line-height: 1; text-transform: uppercase;
}
.responde-quem { font-weight: 650; flex: none; }
.responde-previa {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  min-width: 0;
}
.responde-previa.anexo { font-style: italic; }

/* A mensagem alcançada por um clique no cabeçalho. Um pulso, e não um destaque
   fixo: o objetivo é dizer "é esta aqui" e sair da frente. */
@keyframes pisca-msg {
  0%   { background: var(--accent-fundo); }
  100% { background: transparent; }
}
#messages li.piscando {
  animation: pisca-msg 1.6s ease-out;
  border-radius: var(--r-sm);
  box-shadow: 0 0 0 6px transparent;
}

/* A faixa acima do campo, enquanto se escreve a resposta. */
.respondendo {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 16px;
  margin: 0 16px;
  background: var(--bg-800);
  border: 0;
  border-radius: var(--r-sm) var(--r-sm) 0 0;
  font-size: 12.5px;
  color: var(--text-dim);
}
.respondendo[hidden] { display: none; }
/* A faixa e o campo são duas caixas coladas: sem isto aparece uma emenda entre
   a borda de baixo dela e a de cima do formulário. */
/* Colado embaixo da faixa: as duas caixas viram uma só. */
.respondendo + #chat-form { border-radius: 0 0 var(--r-sm) var(--r-sm); }
.respondendo-seta {
  width: 12px; height: 12px; flex: none;
  border-left: 2px solid var(--linha-forte);
  border-top: 2px solid var(--linha-forte);
  border-top-left-radius: 4px;
  margin: 0 2px 4px 2px;
  align-self: flex-end;
}
.respondendo-texto { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.respondendo-texto strong { color: var(--text); font-weight: 650; }
.respondendo button {
  background: none; border: 0; padding: 2px 6px;
  color: var(--text-fraco); font-size: 13px; line-height: 1;
  border-radius: var(--r-sm);
  flex: none;
}
.respondendo button:hover { background: var(--bg-600); color: var(--text); }

/* O campo é UMA CAIXA, e não uma barra com um botão do lado.
 *
 * A diferença não é estética: com o clipe fora do campo, a linha de baixo da
 * conversa tinha três caixas (botão, campo, borda de cima) e nenhuma delas
 * dizia onde se escreve. Juntando tudo numa caixa arredondada com o botão
 * dentro, sobra um alvo só — que é o que o Discord faz, e é por isso que lá
 * ninguém precisa procurar onde clicar para digitar. */
#chat-form {
  display: flex; gap: 12px;
  /* A fila de imagens é a primeira linha da caixa e ocupa a largura toda; o
     clipe, o campo e o emoji formam a segunda. Sem o `wrap` os três dividiriam
     a linha com ela e o campo de escrever viraria uma tira. */
  flex-wrap: wrap;
  /* `flex-end` porque o campo cresce para cima quando a mensagem tem várias
     linhas: com `center`, o clipe subiria junto e ficaria boiando no meio. */
  align-items: flex-end;
  margin: 0 16px 6px;
  padding: 0 16px;
  background: var(--bg-700);
  border-radius: var(--r-sm);
  /* Âncora do menu de "@", que flutua acima do campo. */
  position: relative;
  transition: box-shadow .14s;
}
#chat-form:focus-within { box-shadow: inset 0 0 0 1px var(--linha-forte); }

/* ───────── a fila de imagens à espera (ver public/js/chat.js) ─────────

   Colar um print não manda nada: ele espera aqui, com miniatura e nome, até o
   Enter que manda a mensagem inteira. É o que o Discord faz, e o motivo é o
   mesmo — quem cola uma imagem quase sempre tem uma frase para escrever junto,
   e mandando na hora essa frase vira um segundo balão logo abaixo.

   A linha rola de lado quando não cabe mais: a alternativa seria a fila
   empilhar e comer a conversa, que é justamente o que o campo não pode fazer. */
.anexos {
  flex-basis: 100%;
  display: flex; gap: 8px;
  padding: 12px 0 10px;
  margin-bottom: 2px;
  overflow-x: auto;
  border-bottom: 1px solid var(--linha);
}
.anexos[hidden] { display: none; }

.anexo-pendente {
  position: relative;
  flex: none;
  width: 132px;
  padding: 8px;
  background: var(--bg-800);
  border: 1px solid var(--linha);
  border-radius: var(--r-sm);
}
/* `flex` e não `grid`: a miniatura se limita com `max-height: 100%`, e essa
   porcentagem só encontra a que se referir se a caixa em volta tiver altura
   definida para o filho. No grid ela não resolve, e uma foto em pé passava de
   200px dentro de uma caixa de 88 — aparecia cortada no meio. Foi medido. */
.anexo-pendente-previa {
  height: 88px;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-950);
  border-radius: 4px;
  overflow: hidden;
}
/* `contain` e não `cover`: a miniatura serve para conferir QUAL imagem vai —
   cortada no meio, um print de código vira um retângulo de texto igual a
   qualquer outro. */
.anexo-pendente-previa img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
.anexo-pendente-nome {
  margin-top: 7px;
  font-size: 12px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.anexo-pendente-tamanho { font-size: 11px; color: var(--text-fraco); margin-top: 1px; }
/* O "tirar" fica visível o tempo todo, e não só no hover como no Discord: no
   celular não existe hover, e um botão que só aparece com o mouse em cima é um
   botão que metade das pessoas não tem. */
.anexo-pendente-tirar {
  position: absolute; top: 4px; right: 4px;
  width: 22px; height: 22px;
  display: grid; place-items: center;
  padding: 0;
  background: rgb(17 19 25 / .82);
  border: 0; border-radius: var(--r-full);
  color: var(--text-dim);
  font-size: 12px; line-height: 1;
  transition: background .12s, color .12s;
}
.anexo-pendente-tirar:hover { background: var(--red-forte); color: #fff; }
#chat-input {
  flex: 1;
  min-width: 0;
  background: none;
  border: 0;
  color: var(--text);
  padding: 12px 0;
  border-radius: 0;
  font-size: 15px;
  /* Um textarea nasce com fonte de formulário e um canto de arrastar. As duas
     coisas saem: a fonte é a do app, e a altura é conta do chat.js — deixar a
     pessoa arrastar brigaria com ela. */
  font: inherit;
  resize: none;
  /* `block` mata os ~4px de descida da linha de base que um textarea inline
     ganha, e que apareciam como uma folga só embaixo do campo. */
  display: block;
  /* O teto de altura mora aqui, e o chat.js o lê: 6 linhas mais o padding.
     Passando disso o campo rola em vez de continuar comendo a conversa. */
  line-height: 1.45;
  max-height: calc(1.45em * 6 + 22px);
  overflow-y: auto;
}
#chat-input::placeholder { color: var(--text-fraco); }
#chat-input:focus { outline: none; box-shadow: none; }
#btn-anexo {
  flex: none;
  width: 24px; height: 24px;
  margin-bottom: 10px;
  padding: 0;
  display: grid; place-items: center;
  background: none; border: 0;
  color: var(--text-dim);
  transition: color .12s;
}
#btn-anexo svg { width: 24px; height: 24px; fill: currentColor; display: block; }
#btn-anexo:hover { color: var(--text); }

/* Mesmo botão do outro lado da caixa. Diferente do clipe, ele fica DEPOIS do
   textarea: o emoji é a última coisa que se acrescenta a uma frase, e a mão vai
   para a ponta direita procurar por ele. */
#btn-emoji {
  flex: none;
  width: 24px; height: 24px;
  margin-bottom: 10px;
  padding: 0;
  display: grid; place-items: center;
  background: none; border: 0;
  color: var(--text-dim);
  transition: color .12s, transform .12s;
}
#btn-emoji svg { width: 24px; height: 24px; fill: currentColor; display: block; }
#btn-emoji:hover { color: var(--amber); transform: scale(1.08); }

#tab-chat { position: relative; }

/* O rodapé do chat: a faixa de resposta e o campo, com o aviso de novas
   mensagens ancorado por cima. `relative` só existe para o `bottom: 100%` do
   aviso ter em relação a quê — nada aqui muda de tamanho por causa dele. */
.chat-rodape { position: relative; }

/* A linha de "está digitando". Altura fixa e sempre presente — ver o comentário
   no index.html. Fica **embaixo** do campo e não em cima porque em cima ela
   disputaria o lugar com a faixa de resposta e com o menu de "@", que já
   flutuam ali. */
.digitando {
  height: 20px;
  padding: 0 18px 2px;
  font-size: 12px;
  line-height: 18px;
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-style: italic;
}

/* "N mensagens novas". Aparece quando alguém subiu a rolagem para reler algo e
   a conversa continuou embaixo — a tela não arrasta ninguém para baixo, e sem
   este aviso não haveria nada dizendo que chegou coisa.

   Barra inteira e não pílula no canto: ela some por cima da última linha da
   conversa, e uma pílula pequena some junto com ela. Chega deslizando de baixo
   para o olho não precisar procurar o que mudou. */
.chat-novas {
  position: absolute; bottom: 100%; left: 10px; right: 10px;
  z-index: 6;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 4px 12px;
  border: 0; border-radius: var(--r-sm) var(--r-sm) 0 0;
  /* Índigo claro com texto escuro, e não a cor da marca com texto branco: esta
     é a única barra do app que quer ser vista antes de ser procurada. Branco
     sobre --accent dá 4,47:1, que reprova no WCAG AA por três centésimos —
     invertido dá 6,82:1 e ainda fica mais forte na tela. */
  background: var(--accent-claro);
  color: var(--bg-950);
  font: inherit; font-size: 12.5px; font-weight: 600;
  cursor: pointer;
  box-shadow: 0 -2px 10px rgb(0 0 0 / .3);
  animation: chat-novas-entra .16s ease-out;
}
.chat-novas[hidden] { display: none; }
.chat-novas:hover { background: #a7aafb; }
.chat-novas-seta { display: flex; }
.chat-novas-seta svg { width: 16px; height: 16px; fill: currentColor; }
@keyframes chat-novas-entra { from { transform: translateY(100%); } to { transform: none; } }
@media (prefers-reduced-motion: reduce) { .chat-novas { animation: none; } }
.drop {
  position: absolute; inset: 8px;
  display: grid; place-content: center;
  background: rgb(17 19 25 / .94);
  border: 2px dashed var(--accent);
  border-radius: var(--r-md);
  font-size: 14px; font-weight: 600;
  z-index: 5; pointer-events: none;
}
.drop[hidden] { display: none; }

/* ═════════════════════ barra de controles ═════════════════════ */

#voz-atual {
  flex: none;
  background: var(--bg-950);
  border-top: 1px solid var(--linha);
  padding: 10px;
}
#voz-atual[hidden] { display: none; }

/* Faixa "Voz conectada": diz em qual canal se está falando, que o topo não diz
   mais — lá em cima fica o canal que se está LENDO, e os dois podem ser
   diferentes. Sem ela, sair da chamada viraria adivinhação. */
.voz-linha {
  display: flex; align-items: center; gap: 8px;
  padding: 2px 4px 8px;
}
.voz-estado { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }
.voz-estado > span { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.voz-estado strong { font-size: 12.5px; font-weight: 650; color: var(--green); }
.voz-estado small {
  font-size: 11.5px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.voz-pulso {
  width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--green);
  box-shadow: 0 0 0 3px rgb(45 212 167 / .16);
  animation: pulsa 2.2s ease-in-out infinite;
}
.voz-sair {
  background: none; color: var(--text-dim);
  width: 30px; height: 30px; border-radius: 6px; flex: none;
  display: grid; place-items: center; font-size: 15px;
  transition: background .14s, color .14s;
}
.voz-sair:hover { background: rgb(242 85 90 / .16); color: #ff8f93; }

/* A plateia da própria tela, logo abaixo do canal. Uma linha de texto e não um
   número na quina de um botão: a pergunta de quem transmite é "quem", e um "2"
   responde outra coisa. O ícone é o mesmo do botão de transmitir — é o assunto
   da linha, e repeti-lo custa 13px. */
.voz-plateia {
  display: flex; align-items: center; gap: 6px;
  padding: 0 4px 8px;
  font-size: 11.5px; line-height: 1.3; color: var(--text-dim);
}
.voz-plateia[hidden] { display: none; }
.voz-plateia .ic { width: 13px; height: 13px; flex: none; color: var(--green); }
/* Com sala cheia a lista não cabe: corta com reticências, e o nome inteiro de
   todo mundo fica no `title`. */
.voz-plateia > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.controls {
  display: none; align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

.ctl {
  background: var(--bg-700);
  border: 1px solid var(--linha);
  color: var(--text);
  width: 38px; height: 38px; border-radius: var(--r-full);
  display: grid; place-items: center;
  transition: background .14s, color .14s, border-color .14s, transform .07s;
}
.ctl:hover { background: var(--bg-600); border-color: var(--linha-forte); }
.ctl:active { transform: translateY(1px); }
.ctl.wide { width: auto; padding: 0 14px; gap: 8px; grid-auto-flow: column; }
.ctl.wide .label { font-size: 13.5px; font-weight: 600; }

/* Microfone, câmera e som numa fila; "Tela" e "Junto" dividem a de baixo. Três
   grupos de 58px não cabem ao lado de um botão com rótulo em 244px, e apertar
   os quatro numa linha só deixaria tudo com alvo de clique de 28px.

   A fileira é um elemento de verdade (`.ctl-linha`) e não uma quebra que se
   espera acontecer. Já foi o segundo: quando "Tela" era o único com rótulo, um
   `flex: 1 0 100%` nele bastava para empurrá-lo sozinho para baixo. Com dois
   botões largos a conta passou a depender do tamanho do texto de cada um — e
   "Tocando", que é o rótulo do Junto quando há vídeo no ar, é mais largo que
   "Junto" e derrubava o arranjo só às vezes. Um contêiner não tem esse "às
   vezes".
   
   `order` porque os dois estão no meio da fila no HTML: sem mandar a fileira
   para o fim, o botão de som ficaria sozinho numa terceira linha. */
.ctl-linha {
  order: 1;
  flex: 1 0 100%;
  display: flex; gap: 6px;
  margin-top: 2px;
}
.ctl-linha .ctl-group { flex: 1 1 0; min-width: 0; }
.ctl-linha .ctl.wide { flex: 1; min-width: 0; }
/* O rótulo cede antes do botão: numa coluna estreita é melhor "Compartilh…" que
   um botão transbordando por cima do vizinho. */
.ctl-linha .ctl.wide .label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Estados fortes: fundo cheio, para lerem à distância no meio de uma partida. */
.ctl.off {
  background: var(--red); border-color: transparent; color: #fff;
  box-shadow: 0 4px 14px rgb(242 85 90 / .32);
}
.ctl.off:hover { background: #f4696e; }
.ctl.live {
  background: var(--green); border-color: transparent; color: #06231b;
  box-shadow: 0 4px 14px rgb(45 212 167 / .3);
}
.ctl.live:hover { background: #48dcb4; }
.ctl.danger {
  background: rgb(242 85 90 / .14);
  border-color: rgb(242 85 90 / .35);
  color: #ff8f93;
  width: auto; padding: 0 20px; font-weight: 650;
}
.ctl.danger:hover { background: var(--red); color: #fff; border-color: transparent; }

.ic { width: 21px; height: 21px; display: block; }
.ic svg { width: 100%; height: 100%; fill: currentColor; }

/* Grupo botão + seta: viram uma peça só, coladas, com os cantos externos
   redondos e os internos quase retos. */
.ctl-group { position: relative; display: flex; gap: 2px; }
.ctl-group > .ctl:first-child { border-top-right-radius: 6px; border-bottom-right-radius: 6px; }
.ctl-group > .ctl.caret { border-top-left-radius: 6px; border-bottom-left-radius: 6px; }
.ctl.caret {
  width: 20px; height: 38px; border-radius: var(--r-full);
  font-size: 10px; padding: 0;
  color: var(--text-dim);
}
.ctl.caret[aria-expanded="true"] {
  background: var(--bg-500); color: var(--text); border-color: var(--linha-forte);
}

/* ═════════════════════ menus ═════════════════════ */

/* Ancorado à esquerda do próprio botão, e não centralizado: numa coluna de
   244px um popover de 340px centralizado sairia metade para fora da tela pela
   esquerda. Assim ele nasce no botão e transborda para a direita, por cima do
   conteúdo — que é espaço vazio de qualquer jeito. */
.menu {
  position: absolute;
  bottom: calc(100% + 10px);
  left: 0;
  transform: none;
  width: min(380px, calc(100vw - 24px));
  max-height: min(72vh, 620px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--bg-800);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-md);
  padding: 4px 16px 16px;
  box-shadow: var(--sombra-lg);
  z-index: 30;
  text-align: left;
  animation: sobe .14s ease-out;
}
/* As seções são separadas por espaço, não por filete. Eram cinco linhas
   horizontais num cartão de 380px: a folha ficava riscada e cada assunto
   parecia mais uma célula de tabela do que um ajuste. O título em maiúsculas
   já separa. */
.menu section + section { margin-top: 22px; }
/* O título gruda no topo enquanto a seção rola — num menu com cinco assuntos e
   rolagem, saber "de que ajuste é este controle" some junto com o cabeçalho. */
.menu h4 {
  position: sticky; top: 0;
  z-index: 1;
  margin: 0 -16px 10px;
  padding: 12px 16px 6px;
  background: var(--bg-800);
  font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-fraco);
}
/* Uma escolha entre várias é uma lista de rádios, e não uma fileira de botões:
   com todos iguais, saber qual está valendo exigia caçar a borda colorida. A
   bolinha à direita responde isso de relance, e é a mesma marca que o Discord
   usa nas listas de qualidade e de dispositivo. */
.opts { display: flex; flex-direction: column; gap: 2px; }
.opts button {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 2px 12px;
  background: none;
  border: 0;
  border-radius: var(--r-sm);
  padding: 9px 12px;
  text-align: left;
  line-height: 1.35;
  transition: background .12s;
}
.opts button:hover { background: var(--hover); }
.opts button b { font-weight: 600; font-size: 14px; }
.opts button small { grid-column: 1; color: var(--text-fraco); font-size: 12px; }
/* A bolinha de rádio: dois círculos concêntricos, o de dentro só quando ligado. */
.opts button::after {
  content: "";
  grid-column: 2; grid-row: 1 / -1;
  width: 18px; height: 18px;
  border-radius: 50%;
  box-shadow: inset 0 0 0 2px var(--bg-500);
  transition: box-shadow .14s, background .14s;
}
.opts button:hover::after { box-shadow: inset 0 0 0 2px var(--text-fraco); }
.opts button.active { background: var(--ativo); }
.opts button.active::after {
  background: var(--accent);
  /* O anel interno é da cor do FUNDO por trás da lista, e ela mudou de lugar:
     dentro de um popover é --bg-800, na tela de ajustes é --bg-900. A variável
     resolve os dois sem duplicar a regra. */
  box-shadow: inset 0 0 0 2px var(--accent), inset 0 0 0 5px var(--sob-opts, var(--bg-800));
}
.ajustes-painel .opts { --sob-opts: var(--bg-900); }
.opts button.active small { color: var(--text-dim); }
/* Com o som pedido, dizer de antemão quais superfícies levam áudio. Sem isto a
   pessoa só descobre depois de já estar transmitindo mudo. */
.opts button.com-som b::before { content: "🔊 "; }
.opts button.sem-som b::before { content: "🔇 "; }
.opts button.sem-som small { color: rgb(245 200 107 / .8); }
.opts button.sem-som small::after { content: " · não leva áudio"; }

.menu-info {
  margin: 16px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--linha);
  font-size: 11.5px;
  color: var(--text-fraco);
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
}
.menu-info[hidden] { display: none; }

/* ───────── escolha de dispositivo ───────── */
.escolha { position: relative; display: flex; }

/* Seletor com rótulo ao lado, que é como os três aparecem empilhados na tela de
   Ajustes. Sem uma coluna de largura fixa para o rótulo, cada select começa num
   x diferente — "Microfone", "Saída de áudio" e "Câmera" têm tamanhos
   diferentes — e a coluna vira escada. */
.escolha.rotulada {
  display: grid;
  grid-template-columns: 104px 1fr;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}
.escolha.rotulada > label {
  font-size: 11px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-fraco);
}
.escolha select {
  width: 100%;
  appearance: none;
  background: var(--bg-950);
  border: 1px solid var(--linha);
  color: var(--text);
  padding: 11px 34px 11px 12px;
  border-radius: var(--r-sm);
  font-size: 14px;
  cursor: pointer;
  text-overflow: ellipsis;
  transition: background .14s, border-color .14s;
}
.escolha select:hover:not(:disabled) { border-color: var(--linha-forte); }
.escolha select:disabled { opacity: .5; cursor: not-allowed; }
/* Seta desenhada em CSS: a nativa não aceita cor e destoava do resto. */
.escolha::after {
  content: "";
  position: absolute; right: 13px; top: 50%;
  width: 7px; height: 7px;
  margin-top: -5px;
  border-right: 1.5px solid var(--text-fraco);
  border-bottom: 1.5px solid var(--text-fraco);
  transform: rotate(45deg);
  pointer-events: none;
}
.escolha select option { background: var(--bg-800); color: var(--text); }

/* ───────── linhas e sliders dentro dos menus ───────── */
.linha-acao {
  width: 100%; margin-top: 8px;
  background: var(--bg-700);
  border: 0;
  border-radius: var(--r-sm);
  padding: 10px 12px;
  font-size: 14px; font-weight: 500;
  color: var(--text-dim);
  text-align: left;
  transition: background .12s, color .12s;
}
.linha-acao:hover:not(:disabled) { background: var(--bg-600); color: var(--text); }
.linha-acao[hidden] { display: none; }
/* Desligado, mas ainda dizendo por quê: o rótulo do botão troca para "a fila
   está cheia", então apagá-lo por completo esconderia justamente a explicação. */
.linha-acao:disabled { opacity: .55; cursor: default; }

/* ───────── sliders ─────────
   Trilho e polegar desenhados à mão: `accent-color` deixa a parte não
   preenchida no cinza claro do navegador, que num tema escuro parece um bug.
   A proporção preenchida chega em --pct, escrita pelo ui.js. */
input[type=range] {
  --pct: 50%;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  height: 18px;
  cursor: pointer;
}
input[type=range]::-webkit-slider-runnable-track {
  height: 4px; border-radius: var(--r-full);
  background: linear-gradient(90deg, var(--accent) var(--pct), var(--bg-600) var(--pct));
}
input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 14px; height: 14px; margin-top: -5px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 4px rgb(0 0 0 / .55);
  transition: transform .1s;
}
input[type=range]:hover::-webkit-slider-thumb { transform: scale(1.12); }
input[type=range]:active::-webkit-slider-thumb { transform: scale(.95); }
input[type=range]::-moz-range-track {
  height: 4px; border-radius: var(--r-full); background: var(--bg-600);
}
input[type=range]::-moz-range-progress {
  height: 4px; border-radius: var(--r-full); background: var(--accent);
}
input[type=range]::-moz-range-thumb {
  width: 14px; height: 14px; border: 0; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 4px rgb(0 0 0 / .55);
}

.slider-linha { display: flex; align-items: center; gap: 11px; }
.slider-linha input[type=range] { flex: 1; }
.slider-linha output {
  min-width: 46px; text-align: right;
  font-size: 12px; color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.dica { margin: 9px 0 0; font-size: 12px; color: var(--text-fraco); line-height: 1.5; }
.dica[hidden] { display: none; }
/* Dica que existe para evitar um erro, não para informar: precisa ser lida. */
.dica.alerta {
  color: #f5c86b;
  background: rgb(245 200 107 / .08);
  border: 1px solid rgb(245 200 107 / .22);
  border-radius: var(--r-sm);
  padding: 7px 9px;
}

/* Interruptor no lugar da caixinha de seleção.
 *
 * A caixinha nativa num tema escuro é um quadrado cinza que precisa ser lido
 * para se saber se está marcada — e ela fica à esquerda do texto, onde o olho
 * ainda não chegou. O interruptor fica na direita, tem cor quando ligado e
 * anda quando muda: dá para varrer a coluna e ver o que está ligado sem ler
 * uma palavra. `row-reverse` é o que o põe lá sem inverter a marcação, que
 * precisa do <input> antes para o clique no rótulo continuar valendo. */
.check-linha {
  display: flex; flex-direction: row-reverse; align-items: center; gap: 12px;
  font-size: 14px; margin-bottom: 12px; cursor: pointer;
  line-height: 1.35;
}
.check-linha > span { flex: 1; min-width: 0; }
.check-linha input[type=checkbox] {
  appearance: none; -webkit-appearance: none;
  position: relative;
  width: 40px; height: 24px; flex: none;
  margin: 0;
  border-radius: var(--r-full);
  background: var(--bg-500);
  cursor: pointer;
  transition: background .16s;
}
.check-linha input[type=checkbox]::after {
  content: "";
  position: absolute; top: 4px; left: 4px;
  width: 16px; height: 16px; border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgb(0 0 0 / .4);
  transition: transform .16s cubic-bezier(.2, .8, .3, 1);
}
.check-linha input[type=checkbox]:checked { background: var(--green); }
.check-linha input[type=checkbox]:checked::after { transform: translateX(16px); }
.check-linha input[type=checkbox]:focus-visible { box-shadow: var(--foco); border-radius: var(--r-full); }

/* Seções que só valem com o interruptor ligado. */
#gate-linha, #sons-linha, #ptt-linha { opacity: .38; pointer-events: none; transition: opacity .14s; }
#gate-linha.ativo, #sons-linha.ativo, #ptt-linha.ativo { opacity: 1; pointer-events: auto; }

/* ───────── falar apertando (push-to-talk) ───────── */
.ptt-linha { display: flex; align-items: center; gap: 11px; }
.tecla {
  font: inherit; font-size: 12.5px; padding: 6px 14px; min-width: 104px;
  background: var(--bg-700); color: var(--text);
  border: 1px solid var(--linha-forte); border-radius: var(--r-sm);
  cursor: pointer;
}
.tecla:hover { background: var(--bg-600); }
/* Esperando a tecla: o botão é o único lugar da tela onde algo está acontecendo. */
.tecla.ouvindo { border-color: var(--accent); color: var(--accent-claro); background: var(--accent-fundo); }
/* Retorno de que a tecla certa foi pega, sem precisar falar para descobrir. */
.ptt-estado { font-size: 11.5px; color: var(--green); }
.ptt-estado[hidden] { display: none; }

/* ───────── medidor do microfone ───────── */
/* A pista precisa se ver mesmo vazia — é ela que diz "aqui vai aparecer o seu
   nível". Ela saiu de um popover claro (--bg-800) para a tela de ajustes
   (--bg-900), e o contorno de 1px que bastava lá some aqui; daí o traço mais
   forte e a pista um degrau acima do fundo. */
.meter {
  position: relative;
  height: 10px;
  background: var(--bg-950);
  border-radius: var(--r-full);
  box-shadow: inset 0 0 0 1px var(--linha-forte);
  overflow: hidden;
}
.meter i {
  display: block; height: 100%; width: 0;
  border-radius: var(--r-full);
  background: linear-gradient(90deg, var(--green) 0%, var(--green) 62%, var(--amber) 82%, var(--red) 100%);
  background-size: 320px 100%;
  transition: width .07s linear;
}
.meter b {
  position: absolute; top: -3px; bottom: -3px;
  width: 3px;
  background: var(--red);
  /* Contorno escuro: sem ele a linha some sobre o verde da barra. */
  box-shadow: 0 0 0 1px var(--bg-950);
  border-radius: 2px;
  transform: translateX(-1.5px);
  pointer-events: none;
}
.meter b[hidden] { display: none; }
.meter-hint { margin: 9px 0 0; font-size: 11.5px; color: var(--text-fraco); line-height: 1.5; }

/* ═════════════════════ avisos ═════════════════════ */

/* Fica dentro do palco, acima da grade: quando flutuava por cima, escondia o
   rosto de quem estava na chamada. */
.aviso {
  flex: none;
  order: -2;
  display: flex; align-items: center; gap: 12px;
  background: rgb(245 181 68 / .1);
  border: 1px solid rgb(245 181 68 / .35);
  color: #ffd894;
  border-radius: var(--r-md);
  padding: 12px 14px;
  font-size: 13.5px;
  line-height: 1.45;
}
.aviso[hidden] { display: none; }
.aviso span { flex: 1; }
.aviso button {
  background: var(--amber); color: #2a1c00;
  border-radius: var(--r-sm); padding: 8px 14px;
  font-size: 13px; font-weight: 650; flex: none;
}
.aviso button:hover { filter: brightness(1.08); }
#aviso-fechar {
  background: transparent; color: #ffd894;
  padding: 5px 7px; font-weight: 400;
}
#aviso-fechar:hover { background: rgb(255 255 255 / .1); }

/* ───────── arrastar alguém para outro canal ─────────
   Três sinais ao mesmo tempo, cada um respondendo a uma pergunta: de onde saiu,
   o que está na mão, e onde vai cair. */

/* de onde saiu — o lugar fica marcado em vez de a lista "engolir" a pessoa */
.gente-na-voz li.saindo {
  opacity: .35;
  border: 1px dashed var(--linha-forte);
}
.gente-na-voz li[draggable="true"] { cursor: grab; }
.gente-na-voz li[draggable="true"]:active { cursor: grabbing; }

/* o que está na mão */
.arrasto-pilula {
  position: fixed; top: -1000px; left: -1000px; /* fora da tela: só serve de retrato */
  display: flex; align-items: center; gap: 8px;
  padding: 6px 14px;
  border-radius: var(--r-full);
  background: var(--bg-600);
  border: 1px solid var(--linha-forte);
  box-shadow: var(--sombra-lg);
  font-size: 13px; color: var(--text);
  white-space: nowrap;
}

/* onde vai cair */
.canal.alvo-arrasto {
  background: var(--accent-fundo);
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.canal.alvo-arrasto .quantos { color: var(--accent-claro); }
/* Lotado: âmbar em vez do accent, e o cursor já recusa antes da soltura. */
.canal.alvo-lotado {
  background: rgb(245 181 68 / .12);
  box-shadow: inset 0 0 0 1px rgb(245 181 68 / .5);
  cursor: not-allowed;
}

/* ───────── arrastar para reordenar ───────── */

/* Uma linha fina no lugar onde a coisa vai cair, e não um retângulo em volta do
   vizinho: a pergunta que a pessoa faz no meio do arrasto é "vai entrar ANTES
   ou DEPOIS deste?", e um item destacado não responde isso. */
.canal-linha.solta-antes::after,
.canal-linha.solta-depois::after,
#lista-servidores li.solta-antes::after,
#lista-servidores li.solta-depois::after {
  content: "";
  position: absolute;
  height: 2px;
  border-radius: 1px;
  background: var(--accent-claro);
  box-shadow: 0 0 8px rgb(139 143 248 / .6);
  pointer-events: none;
  z-index: 1;
}
.canal-linha.solta-antes::after,
.canal-linha.solta-depois::after { left: 0; right: 8px; }
.canal-linha.solta-antes::after { top: -2px; }
.canal-linha.solta-depois::after { bottom: -2px; }

#lista-servidores li.solta-antes::after,
#lista-servidores li.solta-depois::after { left: 12px; right: 12px; height: 3px; }
#lista-servidores li.solta-antes::after { top: -6px; }
#lista-servidores li.solta-depois::after { bottom: -6px; }

/* O item que saiu da mão fica apagado no lugar de origem — é o que diz "este é
   o que está viajando" quando a lista é longa e o cursor está longe dele. */
.saindo-ordem { opacity: .35; }
.canal-linha[draggable="true"] .canal { cursor: pointer; }
#lista-servidores li[draggable="true"]:active { cursor: grabbing; }

/* ───────── menu de contexto de uma pessoa ─────────
   Mesmos tokens do .menu, mais denso: menu de contexto se lê de relance, o de
   ajustes se lê com calma. Daí 236px em vez de 340, padding 6 em vez de 16, e
   linhas sem caixa — o realce é o fundo, como nas listas. */
/* O menu de contexto de uma pessoa. Ele é o mesmo objeto que o Discord abre no
   botão direito: um cartão escuro com o rosto de quem foi clicado no topo e uma
   fileira de ações embaixo — e não uma lista de texto solta, que é o que ele
   parecia antes. As três mudanças que fazem a diferença: o cabeçalho tem uma
   faixa própria (fundo mais escuro, avatar maior, bolinha de estado), as linhas
   ganharam altura de alvo de clique, e a entrada é um zoom curto a partir do
   ponto onde o cursor estava. */
.menu-pessoa {
  position: fixed;
  width: 250px;
  background: var(--bg-800);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-md);
  padding: 8px;
  box-shadow: var(--sombra-lg);
  z-index: 60; /* acima dos menus de barra (30) e do convite (45) */
  animation: mp-entra .12s cubic-bezier(.2, .8, .3, 1);
  transform-origin: top left;
}
@keyframes mp-entra {
  from { opacity: 0; transform: scale(.94); }
  to { opacity: 1; transform: none; }
}

/* A faixa do cabeçalho vaza para as bordas do cartão — daí as margens
   negativas. Sem isso ela seria um retângulo flutuando dentro de outro. */
.mp-cabeca {
  display: flex; align-items: center; gap: 10px;
  margin: -8px -8px 8px;
  padding: 12px;
  background: var(--bg-950);
  border-radius: var(--r-md) var(--r-md) 0 0;
  border-bottom: 1px solid var(--linha);
}
.mp-pin {
  position: relative;
  width: 40px; height: 40px; border-radius: 50%; flex: none;
  background: linear-gradient(160deg, hsl(var(--tom, 245) 62% 58%), hsl(var(--tom, 245) 58% 42%));
  color: #fff; display: grid; place-items: center;
  font-size: 14px; font-weight: 700; text-transform: uppercase;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .2);
}
/* A mesma bolinha da lista de membros, com o anel na cor da faixa. */
.mp-pin.dentro::after,
.mp-pin.fora::after {
  content: "";
  position: absolute; right: -2px; bottom: -2px;
  width: 13px; height: 13px; border-radius: 50%;
  box-shadow: 0 0 0 3px var(--bg-950);
}
.mp-pin.dentro::after { background: var(--green); }
.mp-pin.fora::after { background: var(--bg-500); }
.mp-quem { min-width: 0; }
.mp-quem strong {
  display: block; font-size: 15px; font-weight: 650; line-height: 1.3;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mp-quem small {
  display: block; font-size: 11.5px; color: var(--text-fraco);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mp-quem small.falando { color: var(--green); }
.mp-quem small.fora { color: var(--text-fraco); }

.mp-divisor { height: 1px; background: var(--linha); margin: 6px 2px; }
.mp-titulo {
  font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em;
  color: var(--text-fraco);
}
.mp-grupo { padding: 2px 8px 6px; }

.mp-lista { display: flex; flex-direction: column; gap: 2px; }
.mp-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  height: 32px; padding: 0 8px;
  background: none; border-radius: var(--r-sm);
  font-size: 14px; color: var(--text-dim);
  text-align: left;
  transition: background .1s, color .1s;
}
.mp-item svg { width: 18px; height: 18px; fill: currentColor; flex: none; }
.mp-rotulo { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mp-sufixo { font-size: 11px; color: var(--text-fraco); font-variant-numeric: tabular-nums; }
/* O realce do item é o roxo da marca, e não um cinza a mais: num menu com
   fundo já escuro, cinza sobre cinza mal se vê, e é o item sob o cursor que
   precisa ser inconfundível antes do clique. */
.mp-item:hover:not(:disabled) { background: var(--accent-forte); color: #fff; }
/* Estado ligado (silenciado, fixado): fica aceso mesmo sem o mouse em cima —
   é informação, não só realce de hover. */
.mp-item.ativo { background: var(--accent-fundo); color: var(--text); }
.mp-item.ativo:hover:not(:disabled) { background: var(--accent-forte); color: #fff; }
.mp-item:disabled { opacity: .4; cursor: default; }
.mp-item.perigo { color: #ff8f93; }
.mp-item.perigo:hover:not(:disabled) { background: var(--red-forte); color: #fff; }

.mp-volume { padding: 2px 8px 8px; }
.mp-volume-topo { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 8px; }
.mp-pct { font-size: 11.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.mp-volume input[type=range] { width: 100%; display: block; }

/* Quem pode abrir menu mostra que pode. */
[data-peer], [data-membro] { -webkit-touch-callout: none; }

/* ───────── convite para baixar o app ───────── */
/* Fixo no canto: entrar na grade do #app custaria mexer no layout, e este
   cartão é passageiro — some no primeiro "agora não" e não volta. */
.convite-app {
  position: fixed;
  right: 18px; bottom: calc(18px + var(--seguro-b));
  width: min(320px, calc(100vw - 36px));
  background: var(--bg-700);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-md);
  box-shadow: var(--sombra-lg);
  padding: 15px 16px;
  z-index: 45;
}
.convite-app[hidden] { display: none; }
.convite-app strong { display: block; font-size: 13.5px; font-weight: 650; }
.convite-app p { margin: 6px 0 0; font-size: 12px; line-height: 1.5; color: var(--text-dim); }
.convite-acoes { display: flex; align-items: center; gap: 8px; margin-top: 13px; }
.convite-acoes .primary { margin-top: 0; padding: 8px 16px; font-size: 13px; }
.convite-acoes .ghost { font-size: 12.5px; padding: 8px 10px; color: var(--text-fraco); }

.toast {
  position: fixed; left: 50%;
  bottom: calc(18px + var(--seguro-b));
  transform: translateX(-50%);
  max-width: min(520px, calc(100vw - 32px));
  background: var(--bg-700);
  color: var(--text);
  border: 1px solid var(--linha-forte);
  padding: 11px 17px; border-radius: var(--r-full);
  font-size: 13.5px; font-weight: 500;
  box-shadow: var(--sombra-lg);
  z-index: 40;
  pointer-events: none; /* nunca rouba um clique de quem está usando a barra */
}
.toast[hidden] { display: none; }
.toast.entrando { animation: toast-entra .24s cubic-bezier(.2, .8, .3, 1); }

.lightbox {
  position: fixed; inset: 0; z-index: 50;
  background: rgb(4 5 8 / .92);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  display: grid; place-items: center;
  padding: 40px;
}
.lightbox[hidden] { display: none; }
.lightbox img { max-width: 100%; max-height: 100%; border-radius: var(--r-md); box-shadow: var(--sombra-lg); }
#lightbox-fechar {
  position: absolute; top: 18px; right: 22px;
  background: rgb(255 255 255 / .1);
  border: 1px solid var(--linha);
  color: #fff;
  width: 38px; height: 38px; border-radius: 50%; font-size: 15px;
}
#lightbox-fechar:hover { background: rgb(255 255 255 / .2); }

/* ═════════════════════ alça de redimensionar ═════════════════════ */

.alca {
  display: none;
  justify-self: start;
  width: 8px;
  margin-left: -4px;
  cursor: col-resize;
  z-index: 12;
  background: transparent;
  transition: background .14s;
}
body.com-palco .alca { display: block; }
.alca:hover, .alca.arrastando { background: var(--accent); }
body.redimensionando { cursor: col-resize; user-select: none; }
body.redimensionando video { pointer-events: none; }


/* ═════════════════════ trilha de servidores ═════════════════════ */

/* A coluna mais escura da tela, e de propósito: ela é moldura, não conteúdo.
   Cada servidor é uma bolha que vira quadrado arredondado quando é a atual —
   a mudança de forma é o que se percebe pelo canto do olho, mais que a cor. */
#trilha {
  background: var(--bg-950);
  border-right: 1px solid var(--linha);
  display: flex; flex-direction: column; align-items: center;
  gap: 8px;
  padding: 12px 0 calc(12px + var(--seguro-b));
  overflow-y: auto;
  scrollbar-width: none;
}
#trilha::-webkit-scrollbar { display: none; }
#lista-servidores { list-style: none; margin: 0; padding: 0; width: 100%; display: flex; flex-direction: column; align-items: center; gap: 8px; }
#lista-servidores li { position: relative; display: flex; justify-content: center; width: 100%; padding: 0 12px; }

/* Círculo que vira quadrado arredondado. É a assinatura visual do Discord, e
   não é enfeite: a MUDANÇA DE FORMA é o que se percebe pelo canto do olho ao
   trocar de servidor, enquanto uma mudança de cor exige olhar. O raio anterior
   ia de 16px a 12px — quatro pixels que ninguém enxerga. */
.bolha {
  width: 48px; height: 48px;
  border-radius: 50%;
  background: linear-gradient(160deg, hsl(var(--tom, 245) 58% 52%), hsl(var(--tom, 245) 54% 36%));
  color: #fff;
  display: grid; place-items: center;
  font-size: 15px; font-weight: 700; letter-spacing: -.02em;
  text-transform: uppercase;
  box-shadow: inset 0 1px 0 rgb(255 255 255 / .18);
  transition: border-radius .18s cubic-bezier(.2, .8, .3, 1), filter .14s, background .14s, color .14s;
  flex: none;
}
.bolha:hover { border-radius: 16px; filter: brightness(1.12); }
.bolha.atual { border-radius: 16px; }
.bolha.nova {
  background: var(--bg-800);
  border: 1px dashed var(--linha-forte);
  color: var(--green);
  font-size: 24px; font-weight: 400;
  line-height: 1;
}
/* Verde no hover, como no Discord: é a única bolha da trilha que cria coisa
   nova em vez de levar a algum lugar, e a cor separa as duas naturezas. */
.bolha.nova:hover {
  background: var(--green);
  border-style: solid; border-color: transparent;
  color: #06110d;
  filter: none;
}

/* A bolha das conversas diretas. Vem antes dos servidores e separada por um
   filete: ela não é um servidor, e entrar na fila com eles diria que é.

   O filete tem 32px e fica no meio da coluna, e não de borda a borda: uma
   linha atravessando os 72px inteiros corta a trilha em dois blocos: a leitura
   passa a ser "duas listas", quando o que se quer dizer é "esta primeira é de
   outra natureza". */
#trilha-diretas {
  position: relative;
  padding: 0 12px 8px;
  margin-bottom: 8px;
  width: 100%;
  display: flex; justify-content: center;
}
#trilha-diretas::after {
  content: "";
  position: absolute; left: 50%; bottom: 0;
  transform: translateX(-50%);
  width: 32px; height: 2px; border-radius: 1px;
  background: var(--linha-forte);
}
.bolha.diretas {
  background: var(--bg-800);
  border: 1px solid var(--linha);
  color: var(--text-dim);
}
.bolha.diretas svg { width: 24px; height: 24px; fill: currentColor; }
.bolha.diretas:hover { color: #fff; background: var(--accent); border-color: transparent; filter: none; }
.bolha.diretas.atual { background: var(--accent); border-color: transparent; color: #fff; }
/* O selo do que as conversas estão devendo, somado. Na quina da bolha, como o
   das reações — ao lado ele empurraria a trilha inteira. */
.bolha .badge {
  position: absolute; right: 4px; bottom: 4px;
  min-width: 17px; height: 17px; padding: 0 4px;
  display: grid; place-content: center;
  background: var(--red); color: #fff;
  border: 2px solid var(--bg-950);
  border-radius: var(--r-full);
  font-size: 10px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums;
}

/* Uma conversa na coluna do meio: avatar em vez de cerquilha, porque o nome
   ali é de uma pessoa e não de um lugar. */
.canal.direta { gap: 8px; }
.canal.direta .pin {
  position: relative;
  width: 22px; height: 22px; flex: none;
  border-radius: 50%;
  background: linear-gradient(160deg, hsl(var(--tom, 245) 62% 56%), hsl(var(--tom, 245) 58% 40%));
  color: #fff;
  display: grid; place-items: center;
  font-size: 9px; font-weight: 700; text-transform: uppercase;
}
/* A bolinha é forma, e não só cor: "mais claro" e "mais escuro" some no canto
   do olho, e some de vez para quem não distingue bem contraste. */
.canal.direta .pin.online::after {
  content: "";
  position: absolute; right: -2px; bottom: -2px;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--green);
  box-shadow: 0 0 0 2px var(--bg-900);
}
.diretas-vazio {
  margin: 4px 10px; padding: 0;
  font-size: 12.5px; line-height: 1.5; color: var(--text-dim);
}

/* O ícone do topo vira desenho quando o "canal" é uma pessoa. */
.canal-icone.em-svg { display: inline-flex; }
.canal-icone.em-svg svg { width: 15px; height: 15px; fill: currentColor; }
/* Marca de "você está aqui": um traço colado na borda esquerda, como no
   Discord. A pílula tem 8px de largura com 4 deles fora da tela — o que sobra
   é uma barra de 4px com a ponta direita arredondada, sem precisar desenhar
   meio raio. A altura é o que fala: 0 parado, 20px no hover, 36px no atual, e
   8px (um ponto) quando há mensagem por ler num servidor que não é este. */
#lista-servidores li::before,
#trilha-diretas::before {
  content: "";
  position: absolute; left: -4px; top: 50%;
  width: 8px; height: 0;
  border-radius: 4px;
  background: var(--text);
  transform: translateY(-50%);
  transition: height .2s cubic-bezier(.2, .8, .3, 1);
  pointer-events: none;
}
#trilha-diretas::before { top: calc(50% - 5px); }
#lista-servidores li.tem-novidade::before,
#trilha-diretas.tem-novidade::before { height: 8px; }
#lista-servidores li:hover::before,
#trilha-diretas:hover::before { height: 20px; }
#lista-servidores li.atual::before,
#trilha-diretas:has(.bolha.atual)::before { height: 36px; }

/* ═════════════════════ canais ═════════════════════ */

#barra-canais {
  background: var(--bg-900);
  border-right: 1px solid var(--linha);
  display: flex; flex-direction: column;
  min-height: 0; min-width: 0;
}

/* Mesma sombra do topo da conversa: as duas barras fecham na mesma linha e
   projetam a mesma aresta sobre o que rola por baixo. */
#cabecalho-servidor {
  position: relative; z-index: 2;
  height: var(--top-h); flex: none;
  box-shadow: 0 1px 0 rgb(0 0 0 / .28), 0 1px 3px rgb(0 0 0 / .16);
}
/* O botão é o cabeçalho inteiro. */
#btn-menu-servidor {
  width: 100%; height: 100%;
  display: flex; align-items: center; gap: 6px;
  padding: 0 16px;
  background: none;
  transition: background .1s;
}
#btn-menu-servidor:hover { background: var(--hover); }
#btn-menu-servidor[aria-expanded="true"] { background: var(--ativo); }
#btn-menu-servidor[hidden] { display: none; }
#nome-servidor {
  flex: 1; min-width: 0; text-align: left;
  font-size: 15px; font-weight: 650; letter-spacing: -.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#btn-menu-servidor .chevron {
  flex: none;
  display: grid; place-items: center;
  width: 18px; height: 18px;
  color: var(--text-dim);
  transition: transform .16s cubic-bezier(.2, .8, .3, 1);
}
#btn-menu-servidor .chevron svg { width: 16px; height: 16px; fill: currentColor; display: block; }
#btn-menu-servidor[aria-expanded="true"] .chevron { transform: rotate(180deg); }
/* Este menu desce do cabeçalho; os da barra de voz sobem.
 *
 * Ele NÃO se limita à largura da coluna. Preso nos 240px da barra de canais,
 * os dois seletores de ausência ficavam com 90px cada e a dica virava uma
 * coluna de três palavras por linha. Transbordar para a direita, por cima da
 * conversa, é o que os menus da barra de voz já fazem — e o espaço ali é
 * vazio de qualquer jeito enquanto o menu está aberto. */
#menu-servidor {
  top: calc(100% + 6px); bottom: auto;
  left: 8px; right: auto;
  transform: none;
  width: min(340px, calc(100vw - 24px));
}

/* A lista não tem recuo à esquerda: ele mora em cada linha (`.canal-linha`), e
   os 8px que sobram na borda da coluna são onde a pílula de "tem coisa nova"
   encosta. Com o recuo na lista, ela ficava dentro do recuo e parecia um
   marcador de item. */
#lista-canais { flex: 1; overflow-y: auto; padding: 8px 8px 8px 0; min-height: 0; }

.grupo-canais {
  margin: 0;
  padding: 16px 8px 4px 16px;
  font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-fraco);
  display: flex; align-items: center; gap: 4px;
}
.grupo-canais .titulo {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 2px;
  margin-left: -14px;
  padding: 0;
  background: none; color: inherit;
  font: inherit; letter-spacing: inherit; text-transform: inherit;
  text-align: left;
  transition: color .1s;
}
.grupo-canais .titulo:hover { color: var(--text-dim); }
/* Quantos canais o recolhimento escondeu.
   Existe porque a categoria fechada é uma linha de 11px que ninguém lê, e quem
   a fecha sem querer conclui que os canais foram apagados — foi exatamente o que
   aconteceu ("as salas sumiram, sobrou uma"). O número diz "estão aqui dentro",
   e o cabeçalho inteiro já é o botão que os traz de volta. */
.grupo-canais .titulo .escondidos {
  flex: none;
  margin-left: 6px;
  padding: 0 5px;
  border-radius: var(--r-full);
  background: var(--bg-600);
  color: var(--text-dim);
  font-size: 10px; font-weight: 700; line-height: 15px;
  letter-spacing: 0;
}
.grupo-canais .titulo:hover .escondidos { background: var(--bg-500); color: var(--text); }
.grupo-canais .titulo .rot { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A seta aponta para baixo quando a categoria está aberta e gira ao fechar —
   a mesma convenção do Discord, e a única pista de que a linha é clicável. */
.grupo-canais .chevron {
  flex: none;
  width: 12px; height: 12px;
  display: grid; place-items: center;
  transition: transform .15s cubic-bezier(.2, .8, .3, 1);
}
.grupo-canais .chevron svg { width: 12px; height: 12px; fill: currentColor; display: block; }
.grupo-canais.fechado .chevron { transform: rotate(-90deg); }
/* O "+" só aparece com o ponteiro na categoria, como no Discord — e sempre
   para quem navega por teclado, senão ele deixa de existir no Tab. */
.grupo-canais .mais {
  flex: none;
  width: 18px; height: 18px;
  display: grid; place-items: center;
  background: none; color: var(--text-fraco);
  font-size: 17px; line-height: 1;
  border-radius: 4px;
  opacity: 0;
  transition: opacity .12s, color .12s;
}
.grupo-canais:hover .mais,
.grupo-canais .mais:focus-visible { opacity: 1; }
.grupo-canais .mais:hover { color: var(--text); }
@media (hover: none) { .grupo-canais .mais { opacity: .6; } }

/* A linha de um canal: 32px de altura, raio pequeno, recuo de 8px da borda da
   coluna. São as medidas do Discord, e o que elas compram é densidade — cabem
   mais canais na mesma coluna sem a lista ficar apertada. */
.canal {
  width: 100%;
  display: flex; align-items: center; gap: 6px;
  height: 32px;
  padding: 0 8px;
  border-radius: var(--r-sm);
  background: none;
  color: var(--text-fraco);
  font-size: 14.5px; font-weight: 500;
  text-align: left;
  transition: background .1s, color .1s;
}
.canal:hover { background: var(--hover); color: var(--text-dim); }
.canal.atual { background: var(--ativo); color: var(--text); }
/* Canal com mensagem por ler: o nome acende em branco. É o sinal que faltava —
   antes só o selo vermelho de citação aparecia, e conversa sem citação nenhuma
   passava despercebida até alguém abrir o canal por acaso. */
.canal.novidade { color: var(--text); font-weight: 600; }
.canal .canal-icone { color: var(--text-fraco); font-size: 16px; flex: none; width: 20px; text-align: center; }
.canal.atual .canal-icone { color: var(--text-dim); }
.canal .nome { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A contagem de quem está no canal de voz só aparece com o ponteiro na linha —
   fora disso ela é ruído numa coluna que se lê de relance. */
.canal .quantos {
  font-size: 11px; color: var(--text-fraco); font-variant-numeric: tabular-nums;
  opacity: 0; transition: opacity .12s;
}
.canal-linha:hover .quantos { opacity: 1; }
@media (hover: none) { .canal .quantos { opacity: 1; } }

/* O lápis de renomear mora ao LADO do canal, não dentro dele: botão dentro de
   botão é marcação inválida, e o clique de dentro abriria o canal junto. */
.canal-linha {
  position: relative;
  display: flex; align-items: center; gap: 2px;
  margin: 1px 0 1px 8px;
}
.canal-linha .canal { flex: 1; min-width: 0; width: auto; }
/* A pílula branca na borda da coluna: "tem coisa por ler aqui". Fica colada em
   x=0 da barra inteira, fora do recuo da linha — é ela que se vê ao correr o
   olho pela lateral sem ler nome nenhum. */
.canal-linha.novidade::before {
  content: "";
  position: absolute; left: -8px; top: 50%;
  width: 4px; height: 8px;
  margin-top: -4px;
  border-radius: 0 4px 4px 0;
  background: var(--text);
}
.canal-editar {
  flex: none;
  display: grid; place-items: center;
  width: 26px; height: 26px;
  padding: 0;
  border-radius: 6px;
  background: none;
  color: var(--text-fraco);
  opacity: 0;
  transition: opacity .12s, color .12s, background .12s;
}
.canal-editar svg { width: 14px; height: 14px; fill: currentColor; display: block; }
.canal-linha:hover .canal-editar,
.canal-editar:focus-visible { opacity: 1; }
.canal-editar:hover { color: var(--text); background: var(--bg-700); }
/* Onde não existe hover (toque) o lápis fica sempre visível, senão ele seria
   inalcançável no celular. */
@media (hover: none) { .canal-editar { opacity: .55; } }
/* Canal de voz em que EU estou: o verde é o mesmo do botão de transmitir. */
.canal.na-voz { color: var(--green); }
.canal.na-voz .canal-icone { color: var(--green); }

/* Quem está na chamada aparece embaixo do canal, recuado — é o que faz a
   lateral responder "tem gente lá?" sem precisar entrar para descobrir. */
.gente-na-voz { list-style: none; margin: 0 0 2px; padding: 0 0 0 30px; }
.gente-na-voz li {
  display: flex; align-items: center; gap: 8px;
  height: 30px; padding: 0 8px; margin-right: 8px;
  border-radius: var(--r-sm);
  font-size: 13.5px; color: var(--text-fraco);
  transition: background .1s, color .1s;
}
.gente-na-voz li:hover { background: var(--hover); color: var(--text-dim); }
.gente-na-voz .pin {
  width: 24px; height: 24px; border-radius: 50%; flex: none;
  background: linear-gradient(160deg, hsl(var(--tom, 245) 62% 56%), hsl(var(--tom, 245) 58% 40%));
  color: #fff;
  display: grid; place-items: center;
  font-size: 9.5px; font-weight: 700; text-transform: uppercase;
  transition: box-shadow .16s;
}
/* Mesmo anel da lista "na chamada", mais fino porque o pin aqui é menor. */
.gente-na-voz .pin.speaking { box-shadow: 0 0 0 2px var(--green), 0 0 10px -3px rgb(45 212 167 / .9); }
.gente-na-voz .nome { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Microfone e fone são dois ícones, não um: mudo é "não fala", surdo é "não
   ouve", e do lado de fora a diferença decide se adianta chamar a pessoa. */
.gente-na-voz .sinal { flex: none; display: grid; place-items: center; }
.gente-na-voz .sinal svg { width: 13px; height: 13px; fill: currentColor; display: block; }
.gente-na-voz .sinal.mudo,
.gente-na-voz .sinal.surdo { color: var(--red); }
.gente-na-voz .sinal.tela { color: var(--green); }
.gente-na-voz .sinal.afk { color: var(--text-fraco); }
/* Ausente sai um pouco do primeiro plano — é a mesma leitura do Discord: a
   pessoa continua na sala, mas não é com ela que você vai falar agora. */
.gente-na-voz li.ausente .pin,
.gente-na-voz li.ausente .nome { opacity: .5; }

/* O rodapé do usuário: 52px, avatar com bolinha de estado, nome e o que está
   acontecendo — e os botões só com ícone, sem moldura. É a caixa do canto
   inferior esquerdo do Discord, e ela ancora a coluna: sem um bloco fechando
   embaixo, a lista de canais parece derramar no vazio. */
#caixa-usuario {
  flex: none;
  display: flex; align-items: center; gap: 8px;
  min-height: 52px;
  padding: 0 8px;
  padding-bottom: var(--seguro-b);
  background: var(--bg-950);
  border-top: 1px solid var(--linha);
}
#caixa-usuario .avatar {
  position: relative;
  width: 32px; height: 32px; border-radius: 50%; flex: none;
  background: linear-gradient(160deg, hsl(var(--tom, 245) 62% 56%), hsl(var(--tom, 245) 58% 40%));
  color: #fff;
  display: grid; place-items: center;
  font-size: 12px; font-weight: 700; text-transform: uppercase;
}
/* A bolinha de estado recortada no avatar, com o anel da cor do fundo — o
   mesmo desenho da lista de membros, para que a mesma coisa se leia igual nos
   dois lugares. Âmbar enquanto reconecta. */
#caixa-usuario .avatar::after {
  content: "";
  position: absolute; right: -2px; bottom: -2px;
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--green);
  box-shadow: 0 0 0 3px var(--bg-950);
  transition: background .2s;
}
body.reconectando #caixa-usuario .avatar::after { background: var(--amber); }
#caixa-usuario .quem { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.25; }
#caixa-usuario .quem strong { font-size: 13.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#caixa-usuario .quem small { font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#caixa-usuario .ghost {
  flex: none;
  width: 32px; height: 32px; padding: 0;
  display: grid; place-items: center;
  background: none; border: 0;
  border-radius: var(--r-sm);
  color: var(--text-dim);
  font-size: 14px;
  transition: background .1s, color .1s;
}
#caixa-usuario .ghost:hover { background: var(--hover); color: var(--text); border-color: transparent; }
#caixa-usuario .ghost .ic { display: block; }
/* Ícone menor que o da barra de controles: aqui ele divide 32px de altura com
   o avatar e o nome. */
.ic.pequeno { width: 16px; height: 16px; }

/* Rótulo à esquerda, controle à direita — para os ajustes que são uma escolha
   e não um botão. */
.escolha-linha {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 8px;
  font-size: 13px; color: var(--text-dim);
}
.escolha-linha select {
  flex: 1; min-width: 0; max-width: 60%;
  background: var(--bg-950);
  border: 1px solid var(--linha);
  color: var(--text);
  padding: 7px 9px;
  border-radius: var(--r-sm);
  font-size: 13px;
}
.escolha-linha select:focus { outline: none; border-color: var(--accent); }

.convite-linha { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.convite-linha code {
  flex: 1;
  background: var(--bg-950);
  border: 1px solid var(--linha);
  border-radius: var(--r-sm);
  padding: 9px 11px;
  font: 600 15px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .12em;
  color: var(--accent-claro);
  text-align: center;
}
.convite-linha button {
  background: var(--bg-700); border: 1px solid var(--linha);
  border-radius: var(--r-sm); padding: 9px 13px;
  font-size: 13px; font-weight: 600; color: var(--text-dim);
}
.convite-linha button:hover { background: var(--bg-600); color: var(--text); }

/* Escuta própria ligada: o botão precisa parecer aceso, porque esquecer isto
   ligado numa máquina com caixa de som é realimentação. */
.linha-acao.ativo {
  color: var(--green);
  border-color: rgb(45 212 167 / .45);
  background: rgb(45 212 167 / .12);
}

/* Uma linha por pessoa no painel do áudio que chega. Só aparece quem tem o que
   relatar, então toda linha aqui é notícia ruim — daí o tom. */
.linha-audio { margin: 2px 0; }
.linha-audio.alerta { color: #ffcf8f; }

.linha-acao.primario { background: var(--accent-forte); color: #fff; font-weight: 600; }
.linha-acao.primario:hover:not(:disabled) { background: var(--accent); color: #fff; }
.linha-acao.perigo { color: #ff8f93; }
.linha-acao.perigo:hover { background: rgb(242 85 90 / .14); border-color: rgb(242 85 90 / .35); }

/* ═════════════════════ chat como conteúdo ═════════════════════ */

/* Sem transmissão o painel não é "lateral": ele é a tela. Aqui ele abre em
   duas colunas — conversa e membros — e as abas somem, porque não há mais o que
   alternar. Com o palco no ar, volta a ser painel e as abas voltam com ele. */
#side.largo {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--pessoas-w);
  grid-template-areas: "chat pessoas";
  border-left: 0;
}
#side.largo .tabs { display: none; }
#side.largo #tab-chat { display: flex; grid-area: chat; }
#side.largo #tab-people { display: flex; grid-area: pessoas; border-left: 1px solid var(--linha); }

/* O botão de "lista de membros" no topo, como no Discord. A coluna some e a
   conversa ocupa o lugar dela. */
body.sem-membros #side.largo {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "chat";
}
body.sem-membros #side.largo #tab-people { display: none; }

/* Com transmissão no ar, o painel da direita é SÓ a conversa.
 *
 * A aba "Pessoas" ali repetia o que a barra da esquerda já mostra — quem está
 * no canal de voz, com mudo, surdo e tela — e ainda obrigava a escolher entre
 * ver a conversa e ver a lista. No Discord aquele painel é o chat do canal, e
 * ponto. Quem quer o volume de alguém abre o menu da pessoa, que é onde ele
 * também está.
 *
 * A linha de abas some junto; só no painel flutuante ela fica, porque é lá que
 * mora o ✕ que fecha a folha. */
@media (min-width: 861px) {
  body.com-palco:not(.painel-flutuante) #side .tabs { display: none; }
  body.com-palco.painel-flutuante #side .tabs .tab { display: none; }
  body.com-palco #side #tab-chat { display: flex; }
  body.com-palco #side #tab-people { display: none; }
  body.com-palco #btn-membros { display: none; }
}
/* Em tela estreita a coluna da esquerda é uma gaveta fechada, então a lista de
   pessoas da chamada volta a ser a única — e as abas voltam com ela. O botão do
   topo, esse não: quem manda ali é o #btn-side. */
@media (max-width: 860px) {
  #btn-membros { display: none; }
}

/* A coluna de membros do Discord: dois grupos com contagem, linhas de 42px e
   avatar de 32. A altura fixa é o que faz a coluna ler como uma lista de
   pessoas e não como um parágrafo de nomes — e é o que dá espaço para a
   bolinha de estado no canto do avatar sem apertar a linha vizinha. */
#secao-chamada { flex: none; }
#secao-chamada[hidden] { display: none; }

.titulo-painel {
  margin: 16px 8px 4px 16px;
  font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-fraco);
}
#membros { list-style: none; margin: 0; padding: 0 8px 20px; overflow-y: auto; flex: 1; min-height: 0; }
#membros .grupo-membros {
  padding: 20px 8px 4px;
  font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-fraco);
}
#membros li {
  display: flex; align-items: center; gap: 12px;
  height: 42px; padding: 0 8px; border-radius: var(--r-sm);
  font-size: 14.5px; color: var(--text-dim);
  transition: background .1s, color .1s;
}
#membros li.grupo-membros { display: block; height: auto; }
#membros li:not(.grupo-membros):hover { background: var(--hover); color: var(--text); }
#membros .pin {
  position: relative;
  width: 32px; height: 32px; border-radius: 50%; flex: none;
  background: linear-gradient(160deg, hsl(var(--tom, 245) 62% 56%), hsl(var(--tom, 245) 58% 40%));
  color: #fff;
  display: grid; place-items: center;
  font-size: 11.5px; font-weight: 700; text-transform: uppercase;
}
/* Bolinha no canto do avatar: quem está com o app aberto agora.

   Ela existe porque só apagar o nome não basta — "mais claro" e "mais escuro"
   é uma diferença que some no canto do olho e desaparece de vez para quem não
   distingue bem contraste. A bolinha é forma, não só cor. */
#membros .pin::after {
  content: "";
  position: absolute; right: -2px; bottom: -2px;
  width: 11px; height: 11px; border-radius: 50%;
  background: var(--green);
  /* O anel recorta a bolinha do avatar. Cor do painel, que é o fundo por baixo. */
  box-shadow: 0 0 0 3px var(--bg-900);
}
#membros .nome { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#membros .dono { font-size: 11px; color: var(--amber); }

#membros li.offline { color: var(--text-fraco); }
#membros li.offline .pin { filter: grayscale(.6); opacity: .5; }
#membros li.offline .pin::after { background: var(--bg-500); }

.chat-topo { padding: 10px 12px 0; text-align: center; flex: none; }
.chat-topo button {
  background: var(--bg-800); border: 1px solid var(--linha);
  color: var(--text-dim); font-size: 12.5px;
  padding: 6px 12px; border-radius: var(--r-full);
}
.chat-topo button:hover { background: var(--bg-700); color: var(--text); }

/* Marco do dia entre as mensagens: sem ele, "14:03" de ontem e de hoje são a
   mesma coisa na tela. */
.dia {
  display: flex; align-items: center; gap: 12px;
  margin: 18px 16px 8px;
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-fraco);
}
.dia::before, .dia::after { content: ""; flex: 1; height: 1px; background: var(--linha); }

/* A linha de "novas mensagens". Mesmo desenho da divisória de dia, em vermelho:
   o olho já aprendeu a ler aquela régua com um rótulo no meio, e trocar a cor
   diz "esta é de outra natureza" sem inventar um componente novo. */
.novas {
  display: flex; align-items: center; gap: 10px;
  margin: 14px 16px 6px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--red);
  /* Sem isto ela herda o espaçamento entre mensagens e fica boiando longe da
     primeira nova, que é justamente o que ela deveria estar apontando. */
  scroll-margin-top: 8px;
}
.novas::before, .novas::after { content: ""; flex: 1; height: 1px; background: rgb(242 85 90 / .45); }
.novas span { flex: none; }

/* Canal vazio. No Discord isto não é um aviso centralizado no meio do nada: é
   a PRIMEIRA COISA da conversa, encostada em baixo, com o nome do canal em
   corpo grande — como se a conversa começasse ali. `margin-top: auto` é o que
   empurra o bloco para o fim da coluna sem precisar medir altura nenhuma. */
.vazio-chat {
  margin: auto 0 8px; padding: 16px 16px 8px;
  text-align: left;
  color: var(--text-dim); font-size: 14.5px; line-height: 1.5;
}
.vazio-chat::before {
  content: "#";
  display: grid; place-items: center;
  width: 68px; height: 68px;
  margin-bottom: 14px;
  border-radius: 50%;
  background: var(--bg-700);
  color: var(--text);
  font-size: 36px; font-weight: 600; line-height: 1;
}
body[data-canal="direta"] .vazio-chat::before { content: "@"; }
body[data-canal="voz"] .vazio-chat::before { content: "🔊"; font-size: 28px; }
.vazio-chat strong {
  display: block; color: var(--text);
  font-size: 28px; font-weight: 700; letter-spacing: -.02em;
  line-height: 1.2; margin-bottom: 8px;
}

/* ═════════════════════ modal ═════════════════════ */

.modal {
  position: fixed; inset: 0; z-index: 40;
  display: grid; place-items: center;
  padding: 20px;
  background: rgb(6 7 10 / .68);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}
.modal[hidden] { display: none; }
.modal-cartao {
  width: min(420px, 100%);
  /* O cartão da conta é bem mais alto que o de criar servidor: sem teto e sem
     rolagem, o botão de fechar caía fora da tela num notebook deitado — e não
     havia como sair do diálogo a não ser pelo Esc. */
  max-height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: linear-gradient(180deg, var(--bg-800), var(--bg-900));
  border: 1px solid var(--linha);
  border-radius: var(--r-lg);
  box-shadow: var(--sombra-lg);
  padding: 22px;
  animation: sobe .18s ease-out;
}
.modal-cartao h3 { margin: 0 0 14px; font-size: 18px; font-weight: 650; letter-spacing: -.02em; }
/* Título e ✕ na mesma linha. O h3 continua com a margem de baixo dele: é ela
   que separa o cabeçalho do primeiro campo. */
.modal-topo { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.modal-topo h3 { flex: 1; min-width: 0; }
.modal-x {
  flex: none;
  width: 30px; height: 30px; padding: 0;
  display: grid; place-items: center;
  background: none; border: 0;
  border-radius: var(--r-sm);
  color: var(--text-fraco);
  font-size: 15px;
  transition: background .12s, color .12s;
}
.modal-x:hover { background: var(--hover); color: var(--text); }
.modal-cartao label {
  display: block;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .07em;
  color: var(--text-fraco);
  margin: 14px 0 6px;
}
.modal-cartao input {
  width: 100%;
  background: var(--bg-950);
  border: 1px solid var(--linha);
  color: var(--text);
  padding: 12px 13px;
  border-radius: var(--r-sm);
}
.modal-cartao input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-fundo); }
/* Dois formulários independentes no mesmo cartão: a linha separa "o que muda
   sozinho" de "o que muda junto", e o título diz qual é qual. */
.divisor { border: 0; border-top: 1px solid var(--linha); margin: 22px 0 4px; }
.titulo-secao {
  margin: 14px 0 0;
  font-size: 13px; font-weight: 650; letter-spacing: -.01em;
  color: var(--text);
}
.modal-acoes { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; }
.modal-acoes .primary { margin-top: 0; padding: 11px 20px; }

/* ═════════════════════ tela de ajustes ═════════════════════ */

/* Tela cheia com navegação à esquerda, como a do Discord. Ela ocupa tudo em vez
   de ser um cartão no meio porque o que mora aqui é preferência: quem entrou
   veio resolver uma coisa e sair, e um diálogo pequeno com quatro assuntos
   dentro obriga a rolar dentro de uma caixa dentro de uma tela. */
.ajustes {
  position: fixed; inset: 0; z-index: 50;
  background: rgb(6 7 10 / .82);
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  display: grid; place-items: stretch;
  animation: ajustes-entra .16s ease-out;
}
.ajustes[hidden] { display: none; }
@keyframes ajustes-entra { from { opacity: 0; } }

.ajustes-caixa {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  min-height: 0;
  background: var(--bg-900);
}

/* ───────── navegação ───────── */
.ajustes-nav {
  background: var(--bg-950);
  border-right: 1px solid var(--linha);
  padding: 56px 8px 24px;
  overflow-y: auto;
  display: flex; flex-direction: column; gap: 2px;
  /* A coluna encosta na direita: com a lista alinhada à borda do conteúdo, o
     olho vai do nome da seção para o título dela sem atravessar um vão. */
  align-items: stretch;
}
.ajustes-nav h4 {
  margin: 0 0 8px;
  padding: 0 10px;
  font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-fraco);
}
.ajustes-aba {
  width: 100%;
  height: 34px; padding: 0 10px;
  display: flex; align-items: center;
  border-radius: var(--r-sm);
  background: none;
  color: var(--text-fraco);
  font-size: 14.5px; font-weight: 500;
  text-align: left;
  transition: background .1s, color .1s;
}
.ajustes-aba:hover { background: var(--hover); color: var(--text-dim); }
.ajustes-aba.ativo { background: var(--ativo); color: var(--text); }
.ajustes-aba.perigo { color: #ff8f93; }
.ajustes-aba.perigo:hover { background: rgb(242 85 90 / .14); color: #ff8f93; }
.ajustes-nav .divisor { margin: 8px 10px; }

/* ───────── conteúdo ───────── */
.ajustes-corpo {
  position: relative;
  overflow-y: auto;
  padding: 56px 40px 80px;
  min-width: 0;
}
/* Uma coluna de leitura, e não a largura da janela: linha de 1400px é linha que
   ninguém termina de ler. */
.ajustes-painel { display: none; width: min(660px, 100%); }
.ajustes-painel.ativo { display: block; }
.ajustes-painel > h3 {
  margin: 0 0 22px;
  font-size: 20px; font-weight: 650; letter-spacing: -.02em;
}
.ajustes-painel section + section,
.ajustes-painel form + section,
.ajustes-painel section + form { margin-top: 28px; }
.ajustes-painel h4 {
  margin: 0 0 12px;
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-fraco);
}
/* Os campos da conta vieram do diálogo antigo e herdam as regras dele. */
.ajustes-painel label:not(.check-linha):not(.escolha-linha) {
  display: block;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .07em;
  color: var(--text-fraco);
  margin: 14px 0 6px;
}
.ajustes-painel input:not([type=checkbox]):not([type=range]) {
  width: 100%;
  background: var(--bg-950);
  border: 1px solid var(--linha);
  color: var(--text);
  padding: 12px 13px;
  border-radius: var(--r-sm);
}
.ajustes-painel input:not([type=checkbox]):not([type=range]):focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-fundo);
}
.ajustes-painel .hint { margin: 8px 0 0; font-size: 12.5px; color: var(--text-fraco); line-height: 1.55; }
.ajustes-painel .modal-acoes { margin-top: 16px; }
.ajustes-painel .titulo-secao { margin: 18px 0 0; font-size: 15px; font-weight: 650; color: var(--text); text-transform: none; letter-spacing: -.01em; }

.ajustes-x {
  position: absolute; top: 20px; right: 24px;
  width: 36px; height: 36px;
  display: grid; place-items: center;
  background: none;
  border: 2px solid var(--linha-forte);
  border-radius: 50%;
  color: var(--text-dim);
  font-size: 15px;
  transition: background .12s, color .12s, border-color .12s;
}
.ajustes-x:hover { background: var(--hover); color: var(--text); border-color: var(--text-fraco); }

/* Numa janela estreita a navegação vira uma fita horizontal em cima: duas
   colunas de 232px + conteúdo não cabem em 400px de largura. */
@media (max-width: 720px) {
  .ajustes-caixa { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
  .ajustes-nav {
    flex-direction: row; align-items: center;
    gap: 4px;
    padding: 12px 56px 12px 12px;
    border-right: 0; border-bottom: 1px solid var(--linha);
    overflow-x: auto; scrollbar-width: none;
  }
  .ajustes-nav::-webkit-scrollbar { display: none; }
  .ajustes-nav h4 { display: none; }
  .ajustes-nav .divisor { display: none; }
  .ajustes-aba { width: auto; flex: none; white-space: nowrap; }
  .ajustes-corpo { padding: 24px 16px 60px; }
  .ajustes-x { top: 12px; right: 12px; width: 32px; height: 32px; }
}

/* Os popovers que sobraram na barra de voz ficaram com um assunto só, e a
   largura de 380px passou a ser folga. */
.menu.compacto { width: min(300px, calc(100vw - 24px)); }
.menu .linha-acao.abre-ajustes { margin-top: 12px; color: var(--accent-claro); }
.menu .linha-acao.abre-ajustes:hover { background: var(--accent-fundo); color: var(--text); }

/* ───────── novidades ───────── */

/* Uma coluna de blocos com um filete à esquerda, um por atualização. O filete é
   o que junta as linhas de um mesmo dia sem precisar de moldura: numa lista com
   doze entradas, doze cartões com borda viram doze caixas disputando atenção. */
.novidades { margin-top: 20px; display: flex; flex-direction: column; gap: 24px; }
.novidade {
  position: relative;
  padding-left: 16px;
  border-left: 2px solid var(--linha-forte);
}
/* A mais recente acende: é a resposta para "o que acabou de mudar". */
.novidade:first-child { border-left-color: var(--accent); }
.novidade-topo {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin-bottom: 8px;
}
/* Sem `capitalize`: em português ele maiúscula também os "de", e a data sai
   "21 De Agosto De 2026". O `toLocaleDateString` já entrega a forma certa. */
.novidade-data { font-size: 13px; font-weight: 650; color: var(--text); }
.novidade-selo {
  padding: 2px 8px;
  border-radius: var(--r-full);
  background: var(--accent-fundo);
  color: var(--accent-claro);
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
}
/* O commit fica na ponta, apagado: serve para conferir contra o que está no ar
   quando alguma coisa não bate, e não para ser lido junto com o resto. */
.novidade-id {
  margin-left: auto;
  font: 400 11px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--text-fraco);
}
.novidade-lista {
  margin: 0; padding-left: 18px;
  display: flex; flex-direction: column; gap: 7px;
  color: var(--text-dim); font-size: 14.5px; line-height: 1.5;
}
.novidade-lista li { list-style: disc; }
.novidade-lista strong { color: var(--text); font-weight: 650; }
.novidades-vazio {
  margin: 20px 0 0;
  color: var(--text-fraco); font-size: 14px; line-height: 1.6;
}

/* A porta do cartão da conversa para a lista inteira. */
.atualizacao-tudo {
  margin-top: 10px;
  padding: 0;
  background: none;
  color: var(--accent-claro);
  font-size: 12.5px; font-weight: 600;
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 3px;
  transition: text-decoration-color .12s;
}
.atualizacao-tudo:hover { text-decoration-color: currentColor; }

/* ═════════════════════ animações ═════════════════════ */

@keyframes sobe { from { transform: translateY(10px); opacity: 0; } }
@keyframes pulsa { 50% { opacity: .3; } }
@keyframes toast-entra { from { opacity: 0; transform: translate(-50%, 10px) scale(.97); } }

/* ═════════════════════ toque ═════════════════════ */

/* `:hover` não existe num dedo: as ações do tile eram invisíveis no celular. */
@media (hover: none) {
  .tile-actions { opacity: 1; transform: none; }
  /* No celular os controles ficam sempre visíveis; o slider precisa caber. */
  .tile-vol { opacity: 1; transform: none; }
  .tile-vol input[type=range] { width: 64px; }
  .tile-actions button, .zoom-grupo button { padding: 9px 12px; }
}
@media (pointer: coarse) {
  .ghost { padding: 10px 14px; }
  #people li { padding: 11px 8px; }
  .escolha select { padding-top: 12px; padding-bottom: 12px; }
}

/* ═════════════════════ telas estreitas ═════════════════════ */

/* Os atalhos de painel só existem aqui — no desktop ele já está à vista. A
   condição é invertida (min-width) porque o bloco que os mostra vem depois:
   uma regra sem condição venceria por ordem de cascata. */
@media (min-width: 861px) {
  .so-estreito { display: none !important; }
}

@media (max-width: 860px) {
  /* Espaço para três colunas não existe num telefone: a trilha encolhe e
     continua à vista (é ela que troca de servidor), e a lista de canais vira
     gaveta por cima do conteúdo. */
  :root { --trilha-w: 56px; }
  #app { grid-template-columns: var(--trilha-w) minmax(0, 1fr); }
  /* A gaveta sai de cena por `left`, não por `display: none` nem por
     `transform`, e o motivo é a barra de voz que mora dentro dela: um filho
     `position: fixed` some junto com um pai escondido, e um pai transformado
     vira o quadro de referência dele — nos dois casos a barra iria embora da
     tela. Com `left` negativo ela continua existindo e pode se fixar sozinha no
     rodapé, que é o que o próximo bloco faz. */
  #barra-canais {
    position: fixed; top: 0; bottom: 0;
    left: calc(-1 * var(--canais-w));
    width: min(var(--canais-w), calc(100vw - var(--trilha-w) - 40px));
    z-index: 25;
    visibility: hidden;
    box-shadow: 20px 0 40px rgb(0 0 0 / .5);
    transition: left .18s ease;
  }
  body.menu-aberto #barra-canais { left: var(--trilha-w); visibility: visible; }

  /* Silenciar o microfone não pode depender de abrir uma gaveta: no telefone a
     barra de voz descola e vira faixa fixa no rodapé, à mão o tempo todo. */
  body.na-voz #voz-atual {
    position: fixed; left: 0; right: 0; bottom: 0;
    z-index: 26;
    visibility: visible;
    display: flex; align-items: center; gap: 8px;
    background: var(--bg-900);
    border-top: 1px solid var(--linha);
    padding: 8px 10px calc(8px + var(--seguro-b));
  }
  body.na-voz #voz-atual .voz-linha { padding: 0; flex: none; }
  body.na-voz #voz-atual .voz-estado strong { display: none; } /* o nome do canal basta */
  /* A faixa do telefone é uma linha só, e não há onde pôr a plateia sem empurrar
     os botões para fora. Quem transmite do celular fica sabendo pelo aviso que
     sobe a cada pessoa que entra, e pela etiqueta da prévia se ela estiver
     ligada — é menos, e é o que cabe. */
  body.na-voz .voz-plateia { display: none; }
  body.na-voz .controls { flex: 1; justify-content: flex-end; flex-wrap: nowrap; }
  /* `display: contents` some com a fileira e devolve os dois grupos à barra como
     filhos diretos — que é o que esta faixa fixa quer: tudo numa linha só, sem
     rótulo, encostado à direita. */
  body.na-voz .ctl-linha { display: contents; }
  body.na-voz .ctl-linha .ctl-group { flex: 0 1 auto; margin-top: 0; }
  body.na-voz .ctl.wide .label { display: none; }
  /* A faixa é fixa, então flutua por cima: o conteúdo recua para o campo de
     mensagem não ficar escondido embaixo dela — e o toast sobe junto, senão
     avisos apareceriam atrás dos botões. */
  body.na-voz #corpo { padding-bottom: calc(56px + var(--seguro-b)); }
  body.na-voz .toast,
  body.na-voz .convite-app { bottom: calc(74px + var(--seguro-b)); }
  body.menu-aberto::after {
    content: ""; position: fixed; inset: 0; z-index: 24;
    background: rgb(6 7 10 / .5);
  }
  /* Bolha menor, mas com a mesma dança de forma: só o raio "fechado" encolhe
     junto, senão um círculo de 40px vira um quadrado quase reto. */
  .bolha { width: 40px; height: 40px; font-size: 13px; }
  .bolha:hover, .bolha.atual { border-radius: 13px; }
  #lista-servidores li { padding: 0 8px; }
  #trilha-diretas { padding: 0 8px 8px; }
  #lista-servidores li.atual::before, #trilha-diretas:has(.bolha.atual)::before { height: 30px; }

  /* Com o painel virando folha por cima, o palco fica com a largura toda. */
  body.com-palco #corpo { grid-template-columns: minmax(0, 1fr); }
  /* Numa tela estreita não cabem conversa e lista de membros lado a lado: o
     painel volta a ser uma pilha com abas, que é como ele já funcionava. */
  #side.largo { display: flex; flex-direction: column; }
  #side.largo .tabs { display: flex; }
  /* Por id, e não por classe, de propósito: a regra de duas colunas lá de cima
     também usa id, e uma classe perderia a disputa de especificidade. */
  #side.largo #tab-chat, #side.largo #tab-people { display: none; border-left: 0; }
  #side.largo #tab-chat.active, #side.largo #tab-people.active { display: flex; }
  /* Fechar o painel só faz sentido quando ele é folha por cima de algo. O
     !important existe para vencer o de `.so-estreito`, que o botão também tem. */
  body:not(.com-palco) #side-fechar { display: none !important; }

  /* Aqui está a diferença que faltava no primeiro desenho para celular: sem
     transmissão, o chat NÃO é um painel — ele é a tela inteira, e escondê-lo
     deixava o aparelho mostrando um retângulo preto. Só quando o palco entra é
     que ele volta a ser folha por cima, aberta pelo botão do topo. */
  #side { border-left: 0; }
  body.com-palco #side {
    position: fixed; inset: var(--top-h) 0 0; z-index: 20;
    display: none !important;
    box-shadow: 0 -10px 40px rgb(0 0 0 / .6);
    animation: sobe .18s ease-out;
  }
  body.com-palco #side.open { display: flex !important; }
  .so-estreito { display: inline-flex !important; }
  /* O botão que abre o painel só faz sentido quando existe painel a abrir. */
  #btn-side { display: none !important; }
  body.com-palco #btn-side { display: inline-flex !important; }

  /* Aqui a coluna lateral do destaque virava uma coluna só, e as miniaturas
     eram empilhadas nela. Com a tira embaixo isso passou a empilhar todas na
     mesma célula — o layout estreito já funciona com as mesmas regras do
     largo, então não há mais o que sobrescrever. */

  .topbar { padding: 0 10px; }
  .rot-longo { display: none; }
  .rot-curto { display: inline; }
  #peer-count { display: none; } /* a lista de pessoas fica a um toque daqui */

  .alca { display: none; }
  .stage { padding: 10px; }
}

/* Centralizar um popover de 340px numa tela de 375 joga metade dele para fora,
   e não há como rolar até lá. Fica no fim do arquivo porque `.menu` é definido
   depois do bloco acima, e de lá estas regras perderiam na cascata. */
@media (max-width: 860px) {
  .menu {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    width: auto; transform: none;
    max-height: 76vh;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    border-width: 1px 0 0;
    padding: 4px 16px calc(18px + var(--seguro-b));
  }
  .menu h4 { margin-left: -16px; margin-right: -16px; padding-left: 16px; padding-right: 16px; }
  /* O do servidor também: por id ele vence a regra acima, e sem isto um menu de
     340px sairia pela direita de uma gaveta de 240 numa tela de 375. */
  #menu-servidor {
    position: fixed;
    top: auto; left: 0; right: 0; bottom: 0;
    width: auto;
  }
}

/* ═════════════════════ movimento reduzido ═════════════════════ */

/* Quem pediu menos animação no sistema não deveria receber painel deslizando
   nem toast saltando. As transições de cor ficam: elas informam estado. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
  }
  .tile video { will-change: auto; }
  #people .conexao.connecting,
  #people .conexao.new,
  .dot.wait { animation: none; }
}

/* ═════════════════════ barras de rolagem ═════════════════════ */

/* A do sistema, no macOS, é uma faixa clara que aparece do nada por cima do
   texto e some — no meio de uma conversa escura ela pisca e chama atenção para
   o lugar errado. A do Discord é o contrário: sempre presente, na cor do fundo,
   e larga o bastante para ser agarrada.

   O truque das quatro bordas transparentes com `background-clip: padding-box` é
   o que dá um polegar de 8px dentro de uma pista de 16px sem desenhar duas
   caixas: a borda não é pintada, então ela vira folga. */
/* As duas famílias de propriedade NÃO convivem: quando `scrollbar-width` está
   presente, o Chrome desenha a barra do sistema e ignora os pseudoelementos —
   e no macOS a barra do sistema é aquela que aparece por cima do texto e some.
   Que é exatamente a que se quer trocar aqui. Por isso o padrão fica só para
   quem não tem os pseudoelementos (Firefox), atrás do @supports. */
@supports not selector(::-webkit-scrollbar) {
  * { scrollbar-color: var(--bg-600) transparent; scrollbar-width: thin; }
}

::-webkit-scrollbar { width: 16px; height: 16px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--bg-600);
  background-clip: padding-box;
  border: 5px solid transparent;
  border-radius: 8px;
  min-height: 40px;
}
::-webkit-scrollbar-thumb:hover { background: var(--bg-500); background-clip: padding-box; }
::-webkit-scrollbar-corner { background: transparent; }

/* Nos menus e painéis flutuantes, que são estreitos, 16px de pista comem uma
   fatia do conteúdo. Ali a barra é fina. */
.menu::-webkit-scrollbar,
.menu-pessoa::-webkit-scrollbar,
.painel-fixadas::-webkit-scrollbar,
#lista-canais::-webkit-scrollbar,
#membros::-webkit-scrollbar,
#people::-webkit-scrollbar { width: 10px; }
.menu::-webkit-scrollbar-thumb,
.menu-pessoa::-webkit-scrollbar-thumb,
.painel-fixadas::-webkit-scrollbar-thumb,
#lista-canais::-webkit-scrollbar-thumb,
#membros::-webkit-scrollbar-thumb,
#people::-webkit-scrollbar-thumb { border-width: 3px; }

/* ═════════════════════ dicas flutuantes ═════════════════════ */

/* Ver public/js/dica.js: um elemento só, no <body>, porque a trilha rola e
   quem rola recorta. */
.dica-flutuante {
  position: fixed;
  z-index: 70;
  max-width: 240px;
  padding: 7px 10px;
  background: var(--bg-950);
  border: 1px solid var(--linha-forte);
  border-radius: var(--r-sm);
  box-shadow: var(--sombra-md);
  color: var(--text);
  font-size: 13px; font-weight: 600; line-height: 1.3;
  white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
  pointer-events: none;
  animation: dica-entra .1s ease-out;
}
.dica-flutuante[hidden] { display: none; }
/* A setinha, desenhada com um quadrado girado — um triângulo em `border` não
   aceitaria a borda de 1px do balão. */
.dica-flutuante::after {
  content: "";
  position: absolute;
  width: 8px; height: 8px;
  background: var(--bg-950);
  border-left: 1px solid var(--linha-forte);
  border-bottom: 1px solid var(--linha-forte);
  transform: rotate(45deg);
}
.dica-flutuante[data-lado="direita"]::after { left: -5px; top: 50%; margin-top: -4px; }
.dica-flutuante[data-lado="baixo"]::after {
  left: 50%; top: -5px; margin-left: -4px;
  transform: rotate(135deg);
}
@keyframes dica-entra { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: none; } }

/* ─────────────────────────── assistir junto ───────────────────────────
   O tile do vídeo que a sala está vendo. Ele não tem <video> nosso dentro: o
   que mora ali é o iframe do YouTube, e quase tudo aqui existe por causa dessa
   diferença. */

/* Um <video> tem tamanho próprio e sobrevive centralizado num grid; um iframe
   não tem, e nasceria com os 300×150 que todo iframe sem CSS tem. Por isso o
   corpo pede o tile inteiro em vez de deixar o conteúdo mandar. */
.tile .body.junto { width: 100%; height: 100%; position: relative; display: block; }
.tile .body.junto iframe,
.tile .body.junto .junto-player {
  width: 100%; height: 100%;
  border: 0; display: block; background: #000;
}

/* O convite para destravar o autoplay. Cobre o player inteiro porque o player,
   nesse momento, está mostrando a capa do vídeo e mais nada — e um botão
   pequeno num canto seria procurado por quem já não entendeu por que o filme
   dos outros começou e o dele não. */
.junto-destravar {
  position: absolute; inset: 0;
  display: grid; place-content: center; gap: 6px; justify-items: center;
  background: rgb(0 0 0 / .72);
  border: 0; color: #fff; cursor: pointer;
  font-size: 17px; font-weight: 600;
  text-align: center; padding: 16px;
}
.junto-destravar span { font-size: 34px; line-height: 1; }
.junto-destravar small { font-size: 12.5px; font-weight: 400; opacity: .78; max-width: 24ch; }
.junto-destravar:hover { background: rgb(0 0 0 / .62); }

.junto-erro {
  position: absolute; left: 12px; right: 12px; bottom: 12px;
  background: var(--bg-950); border: 1px solid var(--linha-forte);
  border-radius: var(--r-sm);
  padding: 10px 12px; font-size: 13px; color: var(--text);
}

/* ───────── a fila, dentro do menu ─────────
   Uma linha por vídeo que a sala vai ver depois: capa, quem pôs, e os botões.
   Sem título do vídeo porque não há de onde tirar um sem perguntar ao YouTube
   (ver embutir.js) — e a capa reconhece melhor que o título, de relance. */
.junto-fila { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.fila-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  background: var(--bg-700);
  border-radius: var(--r-sm);
  padding: 5px 7px;
}
/* 16:9 com `cover` sobre a capa 480×360: o corte come exatamente as duas tarjas
   pretas que o YouTube põe em cima e embaixo. A conta está em embutir.js. */
.fila-capa {
  width: 56px; aspect-ratio: 16 / 9;
  object-fit: cover; border-radius: 4px;
  background: var(--bg-950);
}
.fila-texto { min-width: 0; font-size: 12px; line-height: 1.35; }
.fila-texto b { display: block; font-weight: 600; color: var(--text-dim); }
/* O nome de quem pôs pode ser longo, e a linha não pode crescer por causa dele:
   quem escolhe um apelido de trinta caracteres não pode empurrar os botões
   para fora do menu. */
.fila-texto small {
  display: block; color: var(--text-fraco);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fila-acoes { display: flex; gap: 2px; }
.fila-acoes button,
.fila-vago {
  width: 26px; height: 26px;
  display: grid; place-content: center;
  background: none; border: 0; border-radius: var(--r-sm);
  color: var(--text-fraco); font-size: 13px; line-height: 1;
  transition: background .12s, color .12s;
  flex: none;
}
.fila-acoes button:hover { background: var(--bg-600); color: var(--text); }
.fila-acoes button[data-acao="remover"]:hover { color: #ff8f93; }

/* ───────── o registro, embaixo da fila ─────────
   Quem botou qual vídeo e quem mexeu no que estava tocando, do mais novo para o
   mais velho. Rola por dentro, com altura própria: sem isso, vinte linhas
   empurrariam a fila e o campo do link para fora da tela toda vez que o menu
   abrisse, e o assunto principal do menu é o de cima. */
.junto-log {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 1px;
  max-height: 216px;
  overflow-y: auto; overscroll-behavior: contain;
}
.log-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 9px;
  padding: 4px 6px;
  border-radius: var(--r-sm);
}
/* A capa e o sinal ocupam exatamente o mesmo retângulo: numa lista em que as
   duas formas de linha se alternam, é o que mantém os nomes numa coluna só. */
.log-capa,
.log-marca {
  width: 40px; aspect-ratio: 16 / 9;
  border-radius: 3px;
  flex: none;
}
.log-capa { object-fit: cover; background: var(--bg-950); }
.log-marca {
  display: grid; place-content: center;
  background: var(--bg-700); color: var(--text-fraco);
  font-size: 11px; line-height: 1;
}
/* Quebra em vez de cortar. A linha da fila corta com reticências porque o que
   vem depois do nome ali são botões; aqui o que vem depois é o **verbo**, e um
   apelido de vinte caracteres comendo o "tirou um da fila" deixaria a linha
   dizendo só quem — que é metade da resposta. */
.log-texto {
  min-width: 0; font-size: 12px; line-height: 1.35; color: var(--text-fraco);
  word-break: break-word;
}
.log-texto b { color: var(--text-dim); font-weight: 600; }
/* Tabular para as horas ficarem numa coluna reta, e não dançando com a largura
   do 1. */
.log-hora {
  font-size: 10.5px; color: var(--text-fraco);
  font-variant-numeric: tabular-nums;
}

/* O campo do link, com a mesma pele dos <select> do mesmo menu. */
.escolha input[type="url"] {
  width: 100%;
  background: var(--bg-950);
  border: 1px solid var(--linha);
  color: var(--text);
  padding: 11px 12px;
  border-radius: var(--r-sm);
  font-size: 14px;
  transition: border-color .14s;
}
.escolha input[type="url"]:focus { outline: none; border-color: var(--acento, var(--linha-forte)); }
.escolha input[type="url"]::placeholder { color: var(--muted); }

/* ──────────────── sinais de atenção: para onde a pessoa olha ────────────────
   Dois selos que aparecem justamente quando a tela já está cheia — alguém
   transmitindo e um vídeo tocando ao mesmo tempo. Por isso são ícone e não
   palavra: mais duas palavras na fila de "mudo / surdo / tela / cam" fariam a
   lista inteira virar texto corrido. */
.selo-ic { display: inline-grid; place-items: center; }
.selo-ic svg { display: block; fill: currentColor; }

/* Na lista de pessoas herdam a cápsula verde do `.on`; só o miolo muda, porque
   um ícone precisa de menos respiro lateral que uma palavra em maiúsculas. */
#people .flags .selo-ic { padding: 3px 6px; }
#people .flags .selo-ic svg { width: 11px; height: 11px; }

/* No tile não há cápsula: a etiqueta já é uma, e uma pílula dentro da outra
   vira enfeite. Fica só o ícone, na cor de quem está com algo ligado. */
.tile-label .sinais { display: inline-flex; align-items: center; gap: 5px; flex: none; }
.tile-label .sinais .selo-ic { color: var(--green); }
.tile-label .sinais .selo-ic svg { width: 12px; height: 12px; }

/* Tela cheia é o sinal mais forte que existe — a pessoa entregou o monitor
   inteiro àquilo. Some a distinção entre "colorido" e "cheio" e os três jeitos
   de assistir viram um só, que era o estado anterior deste código. */
#people .flags .selo-ic.forte { background: var(--green); color: var(--bg-950); }
.tile-label .sinais .selo-ic.forte {
  background: var(--green); color: var(--bg-950);
  border-radius: var(--r-full); padding: 2px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   JOGOS

   A partida ocupa o palco — o mesmo espaço da tela compartilhada e do
   "assistir junto" —, e por isso quase tudo aqui é layout dentro de `#palco`.
   O tabuleiro é o único componente do app com proporção travada: um quadrado
   que cresce até onde couber, porque um tabuleiro esticado deixa de ser um
   tabuleiro.
   ═══════════════════════════════════════════════════════════════════════════ */

/* A marca de "te chamaram para jogar" no canal da lista. Um dado, e não um
   número: convite é sim ou não, e um contador ali seria sempre 1. Fica antes do
   selo de não lidas porque "estão esperando você" pesa mais que "tem conversa
   para ler" — e, ao contrário do selo, só some quando a pessoa RESPONDE. */
.convite-jogo {
  margin-left: auto;
  flex: none;
  font-size: 13px;
  line-height: 1;
  filter: saturate(1.2);
}
/* Com selo junto, o dado perde o empurrão e os dois ficam lado a lado na ponta. */
.canal:has(.badge) .convite-jogo { margin-left: auto; margin-right: 4px; }
.canal:has(.convite-jogo) .badge { margin-left: 0; }

/* O botão do cabeçalho. Aceso quando há partida no canal; com um ponto quando
   ela espera alguma coisa de você. */
.acao-topo.aceso { color: var(--text); }
.acao-topo.aceso svg { fill: currentColor; }
.jogar-ponto {
  position: absolute; top: 4px; right: 4px;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--green);
  box-shadow: 0 0 0 2px var(--bg-900);
}

/* Na aba de jogos o palco é do tabuleiro: a grade de vídeo sai da frente, mas
   só quando existe partida — quem está numa chamada COM tabuleiro aberto veria
   o vídeo sumir sem pedir. */
body.com-jogo #grid,
body.com-jogo .empty-hint { display: none; }

.jogo-palco {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: auto;
}

/* ───────── o menu de chamar alguém ───────── */

/* O `.menu` do app abre para CIMA, ancorado na barra de voz — daí o
   `bottom: calc(100% + 10px)` dele. Este abre para baixo, a partir do
   cabeçalho, e é posicionado por coordenadas de viewport. O `bottom: auto` é o
   que faz a diferença: sem ele o cartão herda os dois lados presos ao mesmo
   tempo, e a altura é esmagada até caber entre eles — 22px de menu, com o
   conteúdo todo lá dentro, invisível. */
#jogar-menu {
  position: fixed;
  bottom: auto;
  width: min(320px, calc(100vw - 16px));
  max-height: min(72vh, 620px);
}
#jogar-menu .dica { margin: 0 0 8px; }
/* O título da seção carrega o botão de voltar, e por isso deixa de ser só
   texto: `flex` com o `‹` na frente mantém os dois na mesma linha de base sem
   um segundo cabeçalho por cima do primeiro. */
#jogar-menu h4 { display: flex; align-items: center; gap: 6px; }
.jogo-voltar {
  flex: none;
  width: 20px; height: 20px;
  margin-left: -4px;
  display: grid; place-content: center;
  background: none; border: 0; border-radius: var(--r-sm);
  color: var(--text-fraco);
  font-size: 17px; line-height: 1;
  transition: background .12s, color .12s;
}
.jogo-voltar:hover { background: var(--hover); color: var(--text); }

/* ───────── passo 1: que jogo ─────────
   Um cartão por jogo, com o resumo embaixo do nome. A lista inteira de gente
   vinha antes debaixo de CADA jogo, e dois jogos com doze pessoas viravam vinte
   e quatro botões iguais: escolher virava achar o cruzamento certo numa parede
   de nomes repetidos. Aqui escolhe-se uma coisa de cada vez. */
.jogo-lista { display: flex; flex-direction: column; gap: 4px; }
.jogo-cartao {
  position: relative;
  display: grid;
  /* As três posições são ditas por extenso. Com colocação automática, o
     `grid-row: 1 / -1` do ícone não vira duas linhas — a grade explícita tem
     uma linha só — e o nome do jogo era jogado para uma terceira coluna
     implícita, saindo pela direita do cartão. */
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  align-items: center;
  gap: 1px 12px;
  padding: 10px 28px 10px 12px;
  background: var(--bg-700);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  text-align: left;
  transition: background .12s, border-color .12s;
}
.jogo-cartao:hover { background: var(--bg-600); border-color: var(--linha-forte); }
.jogo-cartao .jogo-icone { grid-column: 1; grid-row: 1 / span 2; font-size: 24px; line-height: 1; }
.jogo-cartao b { grid-column: 2; grid-row: 1; font-size: 14px; font-weight: 600; color: var(--text); }
/* `--text-dim`, e não `--text-fraco`: o cartão está um degrau ACIMA do fundo do
   menu (`--bg-700` sobre `--bg-800`), e o degrau mais fraco do texto cai para
   4,26:1 ali — reprova no AA. Quem cobra é o testes/contraste.mjs. */
.jogo-cartao small { grid-column: 2; grid-row: 2; color: var(--text-dim); font-size: 12px; line-height: 1.35; }
/* A seta diz que o clique leva a outro passo, e não que ele já começa a
   partida — a diferença entre escolher e disparar um convite para alguém. */
.jogo-cartao::after {
  content: "›";
  position: absolute; right: 12px;
  color: var(--text-fraco); font-size: 16px;
}
/* ───────── passo 2: quem ─────────
   O campo de procurar só aparece com muita gente (ver jogos.js): num canal de
   quatro pessoas ele seria um passo a mais para chegar no que já está à vista. */
.jogo-busca {
  width: 100%;
  margin: 0 0 6px;
  padding: 8px 10px;
  background: var(--bg-900);
  border: 1px solid var(--linha);
  border-radius: var(--r-sm);
  color: var(--text);
  font-size: 13.5px;
}
.jogo-busca::placeholder { color: var(--text-fraco); }
/* Quem joga com quem em cima, o que está havendo embaixo. Numa linha só,
   "Ana × Bruno — Bruno ganhou — desistência" vira uma fila de travessões. */
.jogo-resumo-partida { display: flex; flex-direction: column; gap: 2px; }
.jogo-resumo-partida b { font-weight: 600; color: var(--text-dim); }
.jogo-busca:focus { outline: none; border-color: var(--accent); }

.jogo-gente { display: flex; flex-direction: column; gap: 2px; }
.jogo-pessoa {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 7px 10px;
  border-radius: var(--r-sm);
  background: none;
  color: var(--text-dim);
  font-size: 14px;
  text-align: left;
  transition: background .1s, color .1s;
}
/* O realce do ponteiro é da cor da ação, e não um cinza a mais: o clique aqui
   MANDA UM CONVITE, e a lista precisa parecer o gatilho que ela é. Chegou a
   trocar o "agora/offline" por um "chamar" nesta hora, e era pior — escondia
   justamente o que qualifica o clique no instante de dá-lo. Quem diz o que o
   clique faz é o "Quem você quer chamar?" logo acima, que fica sempre à vista. */
.jogo-pessoa:hover { background: var(--accent-fundo); color: var(--text); }
/* Com o realce, a linha inteira sobe um degrau — inclusive o "offline". No tom
   de sempre ele cai para 3,92:1 sobre o fundo tingido, e reprova no AA. */
.jogo-pessoa:hover .estado { color: var(--text-dim); }
.jogo-pessoa .nome { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jogo-pessoa .estado { flex: none; font-size: 12px; color: var(--text-fraco); }
/* Quem está com o app aberto agora sobe de tom: é com quem dá para jogar a
   partida inteira sem esperar. */
.jogo-pessoa.online { color: var(--text); }
.jogo-pessoa.online .estado { color: var(--green); }
.jogo-pessoa:disabled { opacity: .5; }

.jogo-erro {
  margin: 8px 0 0;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  background: rgb(242 85 90 / .14);
  border: 1px solid rgb(242 85 90 / .3);
  color: var(--red);
  font-size: 13px;
}

/* ───────── a partida ───────── */

.jogo-partida {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  padding: 16px;
}
.jogo-topo {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  text-align: center;
}
/* O nome do jogo é a legenda, e não o título: quem olha o palco quer saber
   quem está jogando e de quem é a vez — o "Damas" ali em cima é contexto. */
.jogo-nome {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-fraco);
}
/* O ícone é elemento, e não caractere colado no nome: com o título espaçado e
   em maiúsculas, o espaço do texto encolhe e sai "⚪DAMAS". O `gap` do flex
   não encolhe. */
.jogo-nome .jogo-icone { font-size: 13px; letter-spacing: 0; }
/* Os dois lados lado a lado, e o de quem tem a vez aceso. A frase embaixo diz
   a mesma coisa por extenso; esta linha responde de relance, que é como um
   tabuleiro é olhado. */
.jogo-placar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center; }
.jogo-lado {
  padding: 3px 10px;
  border-radius: var(--r-full);
  background: var(--bg-700);
  color: var(--text-dim);
  font-size: 14px; font-weight: 600;
  transition: background .15s, color .15s;
}
.jogo-lado.vez { background: var(--accent-fundo); color: var(--text); box-shadow: inset 0 0 0 1px rgb(99 102 241 / .45); }
.jogo-lado.venceu { background: rgb(45 212 167 / .16); color: var(--green); box-shadow: inset 0 0 0 1px rgb(45 212 167 / .35); }
.jogo-x { color: var(--text-fraco); font-size: 12px; }
.jogo-situacao { font-size: 13.5px; color: var(--text-dim); }
/* "Sua vez" é a única informação da tela que pede ação. Ganha cor. */
.jogo-situacao.minha-vez { color: var(--green); font-weight: 600; }

/* O tabuleiro cresce até o menor entre a largura e a altura disponíveis. É o
   que mantém o quadrado sem depender de conta em JavaScript: `min()` sobre as
   duas medidas do contêiner faz o mesmo que um observador de redimensionamento
   faria, e sem um quadro de atraso a cada arrasto da alça do painel. */
.jogo-tabuleiro {
  flex: 1;
  min-height: 0;
  display: grid;
  place-items: center;
  width: 100%;
  /* Vira um contêiner medido nos dois eixos, e é o que dá ao tabuleiro uma
     altura para consultar. Pode: a altura desta caixa vem do `flex: 1` da
     coluna acima, e não do que tem dentro dela — não há laço. */
  container-type: size;
}
/* O que fica no lugar do tabuleiro enquanto o convite não é respondido.
   Mostrar as damas armadas prometeria uma partida que ainda pode ser recusada,
   mas o palco vazio entre o cabeçalho e dois botões parecia coisa quebrada. */
.jogo-convite {
  margin: auto;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  max-width: 320px;
  text-align: center;
}
.jogo-convite-icone {
  width: 88px; height: 88px;
  display: grid; place-content: center;
  border-radius: 50%;
  background: var(--bg-800);
  box-shadow: inset 0 0 0 1px var(--linha-forte);
  font-size: 40px; line-height: 1;
}
.jogo-convite b { margin-top: 6px; font-size: 15px; color: var(--text); }
.jogo-convite p { margin: 0; font-size: 13px; color: var(--text-fraco); line-height: 1.45; }

.jogo-pe { display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap; }
/* A pergunta do "desistir?", no lugar de um confirm() do navegador — que rouba
   o foco da janela e abre colado no topo da tela, longe do botão clicado. */
.jogo-pergunta { margin: 0; font-size: 13px; color: var(--text-dim); }
.jogo-acao {
  padding: 8px 16px;
  border-radius: var(--r-sm);
  background: var(--bg-700);
  border: 1px solid var(--linha);
  color: var(--text-dim);
  font-size: 13.5px; font-weight: 600;
  transition: background .12s, color .12s;
}
.jogo-acao:hover { background: var(--bg-600); color: var(--text); }
.jogo-acao.primario { background: var(--accent-forte); border-color: transparent; color: #fff; }
.jogo-acao.primario:hover { background: var(--accent); color: #fff; }
.jogo-acao.perigo:hover { background: var(--red-forte); border-color: transparent; color: #fff; }
/* "Voltar ao chat": é saída, e não ação da partida — por isso sem caixa. Ela
   aparece para jogador e para espectador, e é o único botão que o espectador
   tinha para receber: sem ela, quem só assiste ficava com a conversa coberta
   por uma partida dos outros até trocar de canal. */
.jogo-acao.discreto { background: none; border-color: transparent; color: var(--text-fraco); font-weight: 500; }
.jogo-acao.discreto:hover { background: var(--hover); color: var(--text-dim); }
.jogo-acao:disabled { opacity: .5; }

/* ═══════════════════════ damas ═══════════════════════ */

/* O quadrado que cabe na caixa, sem conta em JavaScript.
   A primeira versão era `width: min(620px, 100%)` com `max-height: 100%`, e o
   `max-height` não segurava nada: o tabuleiro saía com 532px de altura dentro
   de uma caixa de 424 e **cobria o rodapé** — os botões de desistir e de voltar
   ao chat ficavam embaixo do tabuleiro, fora da tela.
   As unidades de contêiner resolvem porque medem a caixa, e não uma
   porcentagem que precisa de uma altura já resolvida: `100cqh` é a altura que
   sobrou, e o menor entre ela, a largura e o teto de 620 é o lado do quadrado.
   O `max()` é o piso para o instante em que a caixa ainda não tem altura. */
.damas {
  aspect-ratio: 1;
  width: max(180px, min(620px, 100cqw, 100cqh));
  margin: auto;
}
.damas-grade {
  width: 100%; height: 100%;
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  grid-template-rows: repeat(8, 1fr);
  border-radius: var(--r-md);
  overflow: hidden;
  border: 1px solid var(--linha-forte);
  box-shadow: var(--sombra-md);
}
.damas-casa {
  position: relative;
  display: grid; place-items: center;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: var(--bg-700);
}
.damas-casa.escura { background: #2a3040; }
/* A casa de onde e para onde foi o último lance. Fica por baixo de tudo — é
   memória do que aconteceu, não convite para clicar. */
.damas-casa.ultimo { background: color-mix(in srgb, var(--amber) 22%, #2a3040); }
.damas-casa.selecionada { background: color-mix(in srgb, var(--accent) 45%, #2a3040); }
.damas-casa.escura:not(:disabled):hover { filter: brightness(1.25); }

/* A peça. Um círculo com brilho em cima e sombra embaixo — o suficiente para
   parecer apoiada no tabuleiro sem virar desenho. */
.damas-peca {
  width: 74%; height: 74%;
  border-radius: 50%;
  display: grid; place-items: center;
  font-size: clamp(11px, 2vw, 24px);
  line-height: 1;
  box-shadow: inset 0 2px 0 rgb(255 255 255 / .25), 0 2px 5px rgb(0 0 0 / .45);
}
.damas-peca.lugar-0 { background: linear-gradient(160deg, #f2f4fa, #c3c8d8); color: #2a3040; }
.damas-peca.lugar-1 { background: linear-gradient(160deg, #4a5165, #232834); color: #eef0f6; }
/* A coroa da dama, e a borda dourada: duas marcas em vez de uma, porque só a
   cor não separa peça de dama para quem não distingue bem contraste. */
.damas-peca.dama { box-shadow: inset 0 0 0 3px var(--amber), inset 0 2px 0 rgb(255 255 255 / .2), 0 2px 5px rgb(0 0 0 / .45); }

/* A casa para onde dá para ir. Um ponto, e não a casa inteira acesa: com a
   captura obrigatória são poucas casas, e o ponto diz "aqui" sem apagar a
   leitura do tabuleiro. */
.damas-alvo {
  width: 30%; height: 30%;
  border-radius: 50%;
  background: var(--green);
  box-shadow: 0 0 0 3px rgb(45 212 167 / .25);
}
/* A peça que pode jogar neste turno. Com captura obrigatória, é a interface
   dizendo a regra sem escrever a regra em lugar nenhum. */
.damas-casa.jogavel .damas-peca { outline: 2px solid var(--green); outline-offset: 2px; }
.damas-casa:disabled { cursor: default; }

/* ═══════════════════════ sinuca ═══════════════════════ */

/* Ao contrário de damas, aqui **não** há `aspect-ratio`: o canvas ocupa o que
   sobra e a mesa se centra dentro dele (ver `enquadrar()` na vista). O motivo é
   a faixa de baixo — uma proporção fixa não sabe descontar a altura dela, e o
   que sobra é mesa esticada, que num canvas quer dizer bola elíptica. */
.sinuca {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  height: 100%;
  min-height: 0;
}
.sinuca-mesa {
  flex: 1;
  min-height: 120px;
  width: 100%;
  display: block;
  border-radius: var(--r-md);
  cursor: crosshair;
}
.sinuca-mesa:focus { outline: none; }
.sinuca-mesa:focus-visible { box-shadow: var(--foco); }

.sinuca-abaixo {
  flex: none;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  min-height: 24px;
}

/* Quais bolas ainda são suas. Num jogo em que metade das bolas na mesa é do
   outro, olhar o pano e contar é justamente o trabalho que a interface faz. */
.sinuca-restam { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.sinuca-lado { display: flex; align-items: center; gap: 4px; }
.sinuca-lado .rot {
  font-size: 12px;
  color: var(--text-fraco);
  margin-right: 2px;
}
.sinuca-bolinha {
  width: 13px; height: 13px;
  border-radius: 50%;
  background: var(--cor);
  box-shadow: inset 0 -2px 3px rgb(0 0 0 / .35), inset 0 2px 2px rgb(255 255 255 / .25);
}
/* Listrada é branca com faixa, como a bola: a cor sozinha não separa os dois
   grupos, e o grupo é a informação inteira desta faixa. */
.sinuca-bolinha.listrada {
  background:
    linear-gradient(to bottom,
      #f2f3f7 0 30%, var(--cor) 30% 70%, #f2f3f7 70% 100%);
}

.sinuca-recado {
  flex: 1;
  min-width: 140px;
  margin: 0;
  font-size: 12px;
  color: var(--text-dim);
}
.sinuca-recado.falta { color: var(--amber); }

/* A força. Confirma o que o taco já mostra recuando — e é a única leitura de
   força para quem está jogando pelo teclado.

   As alturas vêm calculadas do JavaScript, e não de uma lista de `nth-child`:
   a escala passou de oito para dezesseis níveis, e a lista teria de ser
   reescrita inteira a cada mudança de tamanho. O número ao lado existe porque
   com dezesseis traços finos contar vira trabalho. */
.sinuca-forca { display: flex; align-items: flex-end; gap: 2px; height: 22px; }
.sinuca-forca[hidden] { display: none; }
.sinuca-forca i {
  width: 5px;
  border-radius: 2px;
  background: var(--bg-500);
  transition: background .12s ease;
}
.sinuca-forca i.acesa { background: var(--green); }
.sinuca-forca i.acesa.alta { background: var(--amber); }
.sinuca-nivel {
  align-self: center;
  margin-left: 7px;
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  color: var(--text-fraco);
}

@media (prefers-reduced-motion: reduce) {
  .jogo-acao, .jogo-pessoa, .jogo-cartao, .jogo-lado, .jogo-voltar,
  .damas-casa, .sinuca-forca i { transition: none; }
}
