/* Tokens e reset base vivem em theme/tokens.css e theme/base.css (carregados
   antes deste arquivo). Aqui fica só o layout específico de cada tela. */

[hidden] {
  display: none !important;
}

/* Seleção de texto só onde faz sentido copiar algo (mensagens do chat,
   modais e campos de digitação) — o resto (nome de canal, de participante,
   badges etc.) virava seleção sem querer ao clicar/arrastar perto, gerando
   confusão visual sem nenhuma utilidade real (não tem por que copiar o
   nome de um canal). */
body {
  user-select: none;
}

.chat-log,
.modal,
input,
textarea,
[contenteditable="true"] {
  user-select: text;
}

label {
  display: grid;
  gap: 0.35rem;
  margin: 0.6rem 0;
}

/* Gates (servidor/login/cadastro): fundo com gradiente radial sutil do acento
   no topo e card elevado com glow — e a primeira impressao do visual novo. */
.gate {
  height: 100vh;
  height: 100dvh;
  display: grid;
  place-items: center;
  background: radial-gradient(
    ellipse 90% 60% at 50% 0%,
    color-mix(in srgb, var(--accent-base) 12%, transparent),
    transparent 60%
  );
}

.gate-card {
  width: min(360px, 90vw);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 24px;
  box-shadow: var(--shadow-2), 0 0 28px color-mix(in srgb, var(--accent-base) 16%, transparent);
}

.gate-card h1 {
  margin: 0 0 4px;
  font-size: 1.3rem;
}

.gate-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
}

.gate-status {
  min-height: 1.2em;
  color: var(--muted);
  font-size: 0.85rem;
}

.workspace-list {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
  display: grid;
  gap: 8px;
  max-height: 50vh;
  overflow-y: auto;
}

.workspace-item-button {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel-soft);
  color: var(--text);
  font-size: 0.95rem;
  text-align: left;
  cursor: pointer;
}

.workspace-item-button:hover {
  border-color: var(--accent-strong);
}

.workspace-item-button.current {
  border-color: var(--accent-strong);
  cursor: default;
}

.workspace-item-current {
  margin-left: auto;
  color: var(--muted);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.settings-server-list {
  max-height: none;
}

/* Nível de aviso por servidor (Configurações → Servidores). Fica embaixo do
   botão do servidor, não dentro dele: o botão inteiro troca de servidor, e um
   select aninhado roubaria o clique. O seletor mora dentro do
   .audio-settings-card, cuja regra de label é `display: grid` — daí a
   especificidade casada aqui, pra deixar rótulo e select na mesma linha. */
.audio-settings-card .server-notify-level:not(.toggle) {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 6px 0 14px;
  padding: 0 12px;
  color: var(--muted);
  font-size: 0.8rem;
}

.audio-settings-card .server-notify-level:not(.toggle) select {
  flex: 1;
  min-width: 0;
}

.settings-join-form {
  display: flex;
  align-items: flex-end;
  gap: 8px;
}

.settings-join-form label {
  flex: 1;
}

.workspace-item-button .channel-icon {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  flex: none;
}

.update-required-image-wrap {
  position: relative;
  width: min(900px, 94vw);
  max-height: 92vh;
}

.update-required-image {
  display: block;
  width: 100%;
  height: auto;
  max-height: 92vh;
  object-fit: contain;
  border-radius: 12px;
  box-shadow: var(--shadow-2);
}

/* Área clicável sobre o ícone no canto superior direito da própria imagem
   (arte antiga apontava pro Google Drive; hoje dispara o mesmo fluxo do
   electron-updater) — posição em % pra acompanhar a imagem em qualquer
   tamanho de tela (ela é responsiva). É um <button>, não mais <a>: reset
   de aparência pra continuar invisível, só a área clicável importa. */
.update-required-drive-link {
  position: absolute;
  left: 68%;
  top: 15.5%;
  width: 14%;
  height: 21%;
  border: 0;
  padding: 0;
  background: transparent;
  cursor: pointer;
}

.update-required-actions {
  flex-direction: column;
  text-align: center;
}

.app-shell {
  display: flex;
  height: 100vh;
  height: 100dvh;
}

.shimo-titlebar { height: 36px; display: flex; align-items: stretch; justify-content: space-between; background: var(--panel); border-bottom: 1px solid var(--border); color: var(--text); -webkit-app-region: drag; user-select: none; }
.shimo-titlebar-brand { display: flex; align-items: center; gap: 8px; padding: 0 12px; font-size: .82rem; font-weight: 700; letter-spacing: .02em; }
.shimo-mark { width: 20px; height: 20px; object-fit: contain; flex: none; }
/* Pill de status do electron-updater (ver initAutoUpdate em app.js) — só
   aparece quando há algo pra fazer (update disponível/baixado); uma
   checagem de rotina sem novidade não mostra nada. */
.update-banner {
  -webkit-app-region: no-drag;
  align-self: center;
  margin: 0 8px;
  padding: 4px 12px;
  border: 0;
  border-radius: 999px;
  background: var(--accent-base);
  color: var(--on-accent);
  font-size: 0.75rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.update-banner:hover {
  filter: brightness(1.08);
}

.shimo-window-controls { display: flex; -webkit-app-region: no-drag; }
.shimo-window-controls button { width: 36px; height: 36px; padding: 0; border: 0; border-radius: 0; background: transparent; color: var(--muted); cursor: pointer; display: grid; place-items: center; }
.shimo-window-controls button:hover { background: color-mix(in srgb, var(--accent-base) 18%, transparent); color: var(--text); }
.shimo-window-controls .is-close:hover { background: #e81123; color: white; }
/* Os três eram glifos de texto (—/□/×) — em fontes diferentes eles NUNCA
   pesam igual (o traço "—" sempre lê mais grosso/largo que o quadrado
   vazado "□"), daí o "ícone de minimizar parece maior que os outros". SVG
   com o mesmo stroke-width nos três resolve de vez, sem depender de fonte. */
.shimo-window-controls button svg {
  width: 10px;
  height: 10px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.1;
  /* SEM stroke-linecap:round: pontas arredondadas "vazam" meia espessura de
     traço além da coordenada de verdade, desalinhando as linhas retas
     (minimizar/fechar) entre si no eixo X. Ponta reta (padrão do SVG)
     mantém as duas dentro do mesmo intervalo exato (1.5 a 8.5 no viewBox
     de 10x10), centralizadas igual. O quadrado do maximizar (rect com
     stroke, não path) tem um problema PARECIDO mas por outro motivo: o
     contorno de um <rect> fica CENTRADO na borda, então "vaza" meia
     espessura de traço pra FORA em todos os 4 lados (esquerda/direita/
     cima/baixo) — por isso o rect em index.html usa coordenadas diferentes
     (2.05/5.9 em vez de 1.5/7): encolhido pra que o contorno pintado (já
     considerando o vazamento do stroke) caia exatamente no mesmo intervalo
     1.5–8.5 das linhas, em vez do próprio path (sem stroke) que ficaria
     maior que os outros dois ícones. */
  stroke-linecap: butt;
}

/* "Bolhas estilo macOS": só troca a APARÊNCIA dos 3 botões (viram bolinhas
   coloridas) - fica no lado de sempre (direita) e na ordem de sempre (a do
   DOM: minimizar, maximizar, fechar), sem mexer em posição/ordem. */
html[data-window-controls="traffic-lights"] .shimo-window-controls {
  gap: 8px;
  align-items: center;
  padding: 0 12px;
}
html[data-window-controls="traffic-lights"] .shimo-window-controls button {
  /* flex:none trava a largura (nada de grow/shrink) e align-self:center
     garante que não estica na vertical mesmo se algo aí em cima mudar o
     align-items do container — sem depender só do container pra isso.
     min-height/min-width:0 é o que faltava: existe um reset genérico
     `button, .btn { min-height: 36px }` (ui-kit.css) valendo pra TODO botão
     do app - min-height sempre vence height quando é maior, então mesmo com
     height:12px aqui a bolinha ficava presa em 36px de altura (12x36).
     box-shadow/outline:none tira o "quadradinho" do anel de foco padrão
     (button:focus-visible, mais abaixo neste arquivo) atrás da bolinha -
     ele não acompanha bem um raio de 50% tão pequeno. */
  flex: none;
  align-self: center;
  width: 12px;
  height: 12px;
  min-width: 0;
  min-height: 0;
  border-radius: 50%;
  box-shadow: none;
  outline: none;
}
html[data-window-controls="traffic-lights"] .shimo-window-controls button svg {
  visibility: hidden;
}
html[data-window-controls="traffic-lights"] .shimo-window-controls button[data-window-action="close"] { background: #ff5f57; }
html[data-window-controls="traffic-lights"] .shimo-window-controls button[data-window-action="minimize"] { background: #febc2e; }
html[data-window-controls="traffic-lights"] .shimo-window-controls button[data-window-action="toggle-maximize"] { background: #28c840; }
html[data-window-controls="traffic-lights"] .shimo-window-controls button:hover { filter: brightness(1.12); }
html[data-window-controls="traffic-lights"] .shimo-window-controls .is-close:hover { background: #ff5f57; }
body.shimo-desktop .app-shell, body.shimo-desktop .gate { height: calc(100vh - 36px); }

/* Faixa consistente para todos os controles de volume: o preenchimento é
   calculado em JS, evitando o falso espaço que o slider nativo deixa no zero. */
input[type="range"] {
  --range-progress: 0%;
  appearance: none;
  -webkit-appearance: none;
  height: 18px;
  padding: 0;
  background: transparent;
  cursor: pointer;
}

input[type="range"]::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 999px;
  background: linear-gradient(to right, var(--accent-base) 0 var(--range-progress), var(--border) var(--range-progress) 100%);
}

input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  margin-top: -4px;
  border: 2px solid var(--panel);
  border-radius: 50%;
  background: var(--accent-strong);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.45);
}

input[type="range"]:focus-visible::-webkit-slider-thumb {
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

/* Arrasta de qualquer ponto da borda (nao so um cantinho, como o resize nativo
   do navegador fazia) — ver o pointerdown/move/up em app.js. */
.resize-handle {
  flex: 0 0 4px;
  width: 4px;
  cursor: col-resize;
  background: transparent;
  transition: background-color 0.12s ease;
}

.resize-handle:hover,
.resize-handle.dragging {
  background: var(--accent-strong);
}

/* "É celular?" daqui pra baixo: estreito (retrato) OU baixo (deitado) — o
   segundo ramo pega telefone em paisagem sem confundir com desktop/tablet
   real (que tem altura de sobra mesmo com a janela redimensionada). Usado
   em todo canto que precisa da mesma resposta (drawers, touch target). */
@media (max-width: 900px), (orientation: landscape) and (max-height: 560px) {
  #memberSidebarResizeHandle {
    display: none;
  }
}

/* A sidebar de canais continua redimensionável; a lista de membros é fixa
   para preservar a leitura dos nomes e controles. */
.member-sidebar {
  background: linear-gradient(180deg, var(--panel) 0%, var(--bg-1) 100%);
  display: flex;
  flex-direction: column;
  flex: 0 0 280px;
  width: 280px;
  min-width: 280px;
  max-width: 280px;
  container-type: inline-size;
  overflow: hidden;
  transition: flex-basis 180ms ease, width 180ms ease, min-width 180ms ease, margin 180ms ease, opacity 140ms ease;
}

@media (max-width: 900px), (orientation: landscape) and (max-height: 560px) {
  /* Tela estreita: em vez de espremer tudo, a lista de membros vira um
     segundo drawer off-canvas (mesma técnica do .sidebar de canais, só que
     entrando pela direita) — #memberSidebarToggle abre/fecha via
     .is-mobile-open (ver setMemberDrawerOpen em app.js). PRECISA vir depois
     da regra base acima: mesma especificidade (.member-sidebar), e é quem
     vem depois no arquivo que ganha as propriedades que colidem (width,
     margin) — por isso não dava pra só adicionar isto antes da base, mesmo
     erro que já mordeu a versão anterior (display:none nunca vencia).
     Antes disto o botão de membros só sumia (display:none) em mobile: sem
     nenhuma forma de abrir a lista de novo. */
  .member-sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    right: 0;
    z-index: 30;
    width: min(280px, 84vw);
    max-width: 84vw;
    margin: 0;
    padding-top: var(--safe-top);
    padding-bottom: var(--safe-bottom);
    transform: translateX(100%);
    transition: transform var(--dur-med) var(--ease-out);
    box-shadow: var(--shadow-2);
  }

  .member-sidebar.is-mobile-open {
    transform: translateX(0);
  }
}

#memberSidebarResizeHandle {
  display: none;
}

.member-sidebar.collapsed {
  flex-basis: 0;
  width: 0 !important;
  min-width: 0;
  max-width: 0;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

#memberSidebarToggle .icon {
  transition: transform 180ms ease;
}

#memberSidebarToggle.is-collapsed .icon {
  transform: translateX(2px) scale(0.9);
}

@media (max-width: 900px), (orientation: landscape) and (max-height: 560px) {
  .member-sidebar.collapsed { display: none; }
}

/* No palco de call a área principal usa a largura inteira: não mostra a lista
   de membros nem o respectivo toggle/resize enquanto estiver em showing-call. */
.app-shell:has(.main-panel.showing-call) .member-sidebar,
.app-shell:has(.main-panel.showing-call) #memberSidebarResizeHandle,
.app-shell:has(.main-panel.showing-call) #memberSidebarToggle {
  display: none;
}

/* Numa conversa direta a coluna não some: troca de CONTEÚDO. No lugar da lista
   de membros (que não existe numa DM) vai o card de perfil do interlocutor, e
   o mesmo #memberSidebarToggle mostra/esconde — com preferência própria
   (dmProfileSidebarCollapsed, ver sidebarPrefKey em app.js). */
.member-sidebar.is-dm-profile .member-sidebar-scroll {
  display: none;
}

.member-sidebar.is-dm-profile {
  flex-basis: 350px;
  width: 350px;
  min-width: 350px;
  max-width: 350px;
}

.dm-profile-panel {
  width: 350px;
  box-sizing: border-box;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--space-3);
  scrollbar-width: thin;
  scrollbar-color: var(--panel-raised) transparent;
}

.dm-profile-panel h2 {
  margin: 0 0 8px;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}

/* O card estica até o fim da coluna (em vez de parar onde o conteúdo acaba),
   pra cor/degradê de fundo cobrirem a coluna toda. */
#dmProfileCard {
  flex: 1;
  min-height: 0;
  display: flex;
}

/* Dentro da coluna o card não flutua — já está numa superfície elevada. */
.dm-profile-panel .profile-card {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  box-shadow: none;
}

.dm-profile-panel .profile-card-body {
  flex: 1;
  align-content: start;
  max-height: none;
  overflow: visible;
}

/* Só isso rola — o player de música (quando em call) fica grudado embaixo,
   igual .sidebar-scroll/.sidebar-footer da sidebar esquerda. */
.member-sidebar-scroll {
  width: 280px;
  box-sizing: border-box;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 12px;
  /* Mesmo tratamento fino/discreto já usado em .sidebar-scroll (canais, à
     esquerda) — esta lista nunca tinha ganhado o mesmo estilo, então
     aparecia com a barra de rolagem padrão do sistema (mais grossa/chamativa)
     sempre que o musicCard expandido sobrava menos altura pra ela. */
  scrollbar-width: thin;
  scrollbar-color: var(--panel-raised) transparent;
}

.member-sidebar-scroll::-webkit-scrollbar {
  width: 6px;
}

.member-sidebar-scroll::-webkit-scrollbar-track {
  background: transparent;
}

.member-sidebar-scroll::-webkit-scrollbar-thumb {
  background-color: var(--panel-raised);
  border-radius: 999px;
}

.member-sidebar-scroll::-webkit-scrollbar-thumb:hover {
  background-color: var(--accent-strong);
}

.member-sidebar h2 {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin: 0 0 8px;
}

.member-list {
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
  display: grid;
  gap: 2px;
}

.member-group-heading {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin: 14px 0 4px;
}

.member-group-heading::after {
  content: "";
  height: 1px;
  flex: 1;
  background: color-mix(in srgb, var(--border) 72%, transparent);
}

.member-group-heading:first-child {
  margin-top: 0;
}

/* Player disponível durante a call no desktop, com visibilidade por conta. */
.music-card {
  position: fixed;
  right: 16px;
  bottom: calc(88px + env(safe-area-inset-bottom, 0px));
  z-index: 75;
  width: min(340px, calc(100vw - 32px));
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow-2);
  flex: none;
  border-top: 1px solid var(--border);
  background: var(--panel);
}

.music-card-bar {
  display: flex;
  align-items: center;
  width: 100%;
  padding: 8px 36px 8px 8px;
  gap: 9px;
}

.music-hide-button {
  position: absolute;
  top: 8px;
  right: 4px;
}

@media (max-width: 900px), (orientation: landscape) and (max-height: 560px) {
  #musicCard, #musicVolumePopover, #musicVisibilityToggle { display: none !important; }
}

.music-card-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  min-width: 0;
  flex: 1;
  padding: 0;
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.music-card-bar .icon {
  flex: none;
  width: 16px;
  height: 16px;
}

.music-card-now-playing {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.82rem;
  color: var(--muted);
}

.music-card-chevron {
  transition: transform 120ms ease;
}

.music-card.expanded .music-card-chevron {
  transform: rotate(180deg);
}

.music-compact-transport {
  display: flex;
  flex: none;
  gap: 2px;
}

.music-compact-preview {
  display: block;
  width: 54px;
  height: 54px;
  flex: none;
}

.music-compact-preview img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 7px;
  background: var(--panel-soft);
}

.music-compact-content {
  display: grid;
  min-width: 0;
  flex: 1;
  gap: 4px;
}

/* grid-template-rows 0fr -> 1fr: anima até a altura intrínseca sem medir
   scrollHeight em JS (não existia nenhuma animação de slide neste projeto
   antes disso — só o giro de ícone acima e troca instantânea via [hidden]). */
.music-card-body-wrap {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 180ms ease;
}

.music-card.expanded .music-card-body-wrap {
  grid-template-rows: 1fr;
}

.music-card-body-wrap > .music-card-body {
  /* Sem isso, recolher o card (grid-template-rows 1fr -> 0fr acima) só
     encolhe a LINHA do grid — o conteúdo (fila, histórico etc.) continua
     com sua altura real por baixo e "vaza" pra fora da linha já colapsada,
     deixando .member-sidebar-scroll rolar bem além de onde há conteúdo de
     verdade. overflow aqui recorta exatamente no que a linha permite a cada
     instante da animação, então o scroll acompanha a altura real. Isso NÃO
     é a mesma causa do estouro horizontal (esse já foi corrigido com
     minmax(0, 1fr) abaixo) — são dois problemas diferentes que essa mesma
     propriedade cobria, um dos dois foi resolvido na raiz, o outro (este)
     continua precisando dela.
  */
  overflow: hidden;
  min-height: 0;
}

.music-card-body {
  display: grid;
  /* minmax(0, 1fr), não só 1fr: sem o mínimo em 0 a única coluna do grid
     cresce pra caber o filho mais largo (o "blowout" clássico de grid de
     coluna única) — algum <p> de status ou item de fila mais comprido do
     que os 280px da sidebar empurrava o card inteiro pra fora, e isso sim
     é que criava o scroll lateral indesejado (não o overflow:hidden que
     estava aqui, que só escondia o sintoma sem corrigir a causa). */
  grid-template-columns: minmax(0, 1fr);
  gap: 10px;
  padding: 0 12px 12px;
  max-height: min(620px, calc(100dvh - 150px));
  overflow-y: auto;
  overscroll-behavior: contain;
}

.music-card-progress-row {
  display: grid;
  gap: 0;
}

.music-card-progress {
  height: 4px;
  border-radius: 2px;
  background: var(--border);
  overflow: hidden;
}

.music-card-progress-fill {
  height: 100%;
  width: 0%;
  background: var(--accent, currentColor);
}

.music-card-mute-toggle {
  font-size: 0.82rem;
}

.music-card-add-form {
  display: flex;
  gap: 6px;
}

.music-card-add-form input {
  flex: 1;
  min-width: 0;
}

.music-add-button {
  flex: none;
  border-color: var(--accent-strong);
  background: var(--accent-strong);
  color: var(--accent-contrast);
}

.music-add-button:hover { filter: brightness(1.08); }

.music-search-results {
  display: grid;
  gap: 4px;
  max-height: 160px;
  overflow-y: auto;
}

.music-search-result {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px;
  border: none;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  border-radius: 4px;
}

.music-search-result:hover {
  background: var(--border);
}

.music-search-result img {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: 4px;
  object-fit: cover;
}

.music-search-result span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.82rem;
}

.music-queue-list,
.music-history-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
  max-height: min(220px, 32dvh);
  overflow-y: auto;
}

.music-queue-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 6px;
  padding: 4px 2px;
  border-radius: 4px;
}

.music-queue-item.is-playing {
  background: var(--border);
}

.music-queue-item-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.82rem;
}

.music-queue-item-meta {
  flex: none;
  font-size: 0.72rem;
  color: var(--muted);
}

.music-queue-item-actions {
  flex: none;
  display: flex;
  gap: 2px;
}

.music-queue-action {
  min-width: 24px;
  min-height: 24px;
  padding: 0 5px;
  line-height: 1;
  color: var(--muted);
}

.music-queue-action:hover { color: var(--text); background: var(--border); }

.music-volume-popover {
  position: fixed;
  z-index: 80;
  display: grid;
  grid-template-columns: auto minmax(120px, 1fr) auto;
  align-items: center;
  gap: 8px;
  width: min(280px, calc(100vw - 16px));
  padding: 9px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel-raised, var(--panel));
  box-shadow: var(--shadow-2);
  font-size: 0.82rem;
}

.music-volume-popover output { color: var(--muted); font-variant-numeric: tabular-nums; }

.music-history-details summary {
  cursor: pointer;
  font-size: 0.78rem;
  color: var(--muted);
}

.music-history-list li {
  font-size: 0.78rem;
  color: var(--muted);
  padding: 2px 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar {
  background: var(--panel);
  display: flex;
  flex-direction: column;
  padding: 12px;
  gap: 6px;
  overflow-x: hidden;
  flex: 0 0 auto;
  width: 305px;
  min-width: 305px;
  max-width: 420px;
  container-type: inline-size;
  background-image: linear-gradient(180deg, color-mix(in srgb, var(--accent-base) 8%, transparent), transparent 140px);
  border-radius: var(--radius-md);
  margin: 9px;
}

/* Drawer de canais em mobile — some do fluxo flex do .app-shell e vira
   off-canvas (fixed + transform), com a classe .is-open controlando a
   entrada. Regra só existe dentro da media query: em desktop (>900px) o
   .sidebar continua exatamente como antes (aditivo, sem risco de regressão).
   Mesmo breakpoint que já esconde a .member-sidebar acima. PRECISA vir
   depois da regra base .sidebar logo acima — mesma especificidade, e a
   última no arquivo é que ganha para as propriedades que colidem (margin,
   border-radius, padding, width/max-width): antes disso, a base sempre
   vencia por vir depois. */
.channel-sidebar-toggle {
  display: none;
}

@media (max-width: 900px), (orientation: landscape) and (max-height: 560px) {
  /* Os dois gatilhos ficam acima do scrim (z-index 29) E dos drawers
     (z-index 30) — sem isso, assim que QUALQUER drawer abre, o scrim cobre
     o cabeçalho inteiro (incluindo os dois botões, que não são
     position:fixed e por isso empilham abaixo dele) e não dá pra reabrir o
     próprio botão pra fechar, nem trocar direto pro outro drawer; só
     sobrava tocar fora (no scrim) pra fechar. */
  .channel-sidebar-toggle,
  #memberSidebarToggle {
    position: relative;
    z-index: 31;
    min-width: 44px;
    min-height: 44px;
  }

  .channel-sidebar-toggle {
    display: flex;
  }

  #sidebarResizeHandle {
    display: none;
  }

  .sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 30;
    width: min(305px, 84vw);
    max-width: 84vw;
    margin: 0;
    border-radius: 0;
    padding-top: calc(12px + var(--safe-top));
    padding-bottom: calc(12px + var(--safe-bottom));
    transform: translateX(-100%);
    transition: transform var(--dur-med) var(--ease-out);
    box-shadow: var(--shadow-2);
  }

  .sidebar.is-open {
    transform: translateX(0);
  }

  .sidebar-scrim {
    position: fixed;
    inset: 0;
    z-index: 29;
    background: rgba(0, 0, 0, 0.55);
  }

  /* Durante uma call, o mesmo :has() que já esconde a member-sidebar some
     também com o gatilho do drawer de canais — prioriza a UI da call, quem
     quiser trocar de canal sai da call primeiro. */
  .app-shell:has(.main-panel.showing-call) .channel-sidebar-toggle {
    visibility: hidden;
  }

  /* Dentro do drawer, os controles de voz (mic/fone/compartilhar/sair) são
     tocados com o dedo, não clicados com mouse — mesmo tratamento de
     touch-target do footer do call viewer. .btn-icon define width:40px fixo
     (não min-width), então precisa do bump explícito nos dois eixos. */
  .voice-controls .btn-icon {
    min-width: 44px;
    width: 44px;
    min-height: 44px;
  }

  .user-panel-share .btn-icon {
    min-height: 44px;
  }
}

/* Só essa parte rola — perfil, controles de voz e status ficam sempre visíveis,
   grudados embaixo. Scroll vertical fino e personalizado; horizontal nunca. */
.sidebar-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  gap: 6px;
  scrollbar-width: thin;
  scrollbar-color: var(--panel-raised) transparent;
}

.sidebar-scroll::-webkit-scrollbar {
  width: 6px;
  height: 0;
}

.sidebar-scroll::-webkit-scrollbar-track {
  background: transparent;
}

.sidebar-scroll::-webkit-scrollbar-thumb {
  background-color: var(--panel-raised);
  border-radius: 999px;
}

.sidebar-scroll::-webkit-scrollbar-thumb:hover {
  background-color: var(--accent-strong);
}

.sidebar-scroll::-webkit-scrollbar-corner {
  background: transparent;
}

/* Só o seu próprio ping (voice.js, updateLocalPingSection) — some quando
   você não está em nenhuma chamada. */
.my-ping-section {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0;
  font-size: 0.78rem;
  color: var(--muted);
}

/* Painel do usuário no rodapé da sidebar: junta ping, identidade/ações e os
   controles de voz num card flutuante com destaque e cantos arredondados. */
.user-panel {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px 12px;
  background: linear-gradient(160deg,
    color-mix(in srgb, var(--accent-base) 18%, var(--panel-raised)) 0%,
    color-mix(in srgb, var(--accent-base) 8%, var(--panel-raised)) 100%);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

/* Faixa que aparece acima de .user-panel-status só enquanto EU estou
   transmitindo: ícone do que está sendo compartilhado (tela/janela/jogo),
   nome da fonte e os fps de envio (na cor de destaque do app), com o botão
   de parar de compartilhar (movido de dentro de .user-panel-status pra cá —
   ver screen-share.js). */
.user-panel-sharing-status {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  background: var(--panel-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

.user-panel-sharing-status .icon {
  flex: none;
  width: 16px;
  height: 16px;
}

.user-panel-sharing-window-icon {
  width: 18px;
  height: 18px;
  flex: none;
  object-fit: contain;
}

.user-panel-sharing-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.user-panel-sharing-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.85rem;
}

.user-panel-sharing-fps {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--accent);
}

.user-panel-sharing-status .btn-icon {
  flex: none;
  margin-left: 2px;
}

/* Linha do topo do painel: ping à esquerda, admin/inspecionar à direita. */
.user-panel-status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

/* Faixa no topo do painel com compartilhar tela, efeitos sonoros,
   configurações e sair da chamada — ocupa a largura toda, botões quadrados,
   fundo transparente pra ficar na cor da seção. */
.user-panel-share {
  align-self: stretch;
  display: flex;
  gap: 8px;
  padding: 6px;
  background: transparent;
  border-radius: var(--radius-md);
}

.user-panel-share .btn-icon {
  flex: 1;
  width: auto;
  border-radius: 10px;
}

.sidebar-identity {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 0;
}

.sidebar-identity-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.sidebar-status-text {
  font-size: 0.75rem;
  color: var(--muted);
}

.sidebar-identity strong {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: none;
}

/* Modo ícone: sidebar estreitada abaixo do limiar esconde texto, mostra só
   avatar/ícones/bolinhas. */
@container (max-width: 100px) {
  .channel-group h2,
  .sidebar-identity strong,
  .channel-name,
  .channel-call-participants,
  #myPingText,
  #voiceStatus {
    display: none;
  }
  .channel-list li button {
    justify-content: center;
  }
  .my-ping-section {
    justify-content: center;
  }
  .sidebar-footer {
    justify-content: center;
    flex-wrap: wrap;
  }
}

/* Quadrado com canto arredondado (nao circulo): distingue avatar de usuario dos
   indicadores de status, que usam .dot circular. */
.avatar-button {
  all: unset;
  cursor: pointer;
  width: 36px;
  height: 36px;
  flex: none;
  border-radius: 30%;
  overflow: hidden;
  background: var(--panel-raised);
}

.avatar-thumb,
.avatar-fallback {
  width: 24px;
  height: 24px;
  border-radius: 30%;
  flex: none;
  object-fit: cover;
}

.avatar-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 700;
}

.avatar-button .avatar-thumb,
.avatar-button .avatar-fallback {
  width: 100%;
  height: 100%;
  border-radius: 0;
  font-size: 0.95rem;
}

.channel-group h2 {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin: 12px 0 4px;
}

.channel-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2px;
}

.channel-list li button {
  all: unset;
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px 8px;
  border-radius: 6px;
  cursor: pointer;
  color: var(--muted);
}

.channel-category-heading {
  margin-top: 10px;
}

.channel-category-heading:first-child {
  margin-top: 0;
}

.channel-category-toggle {
  display: flex;
  align-items: center;
  gap: 4px;
  width: 100%;
  padding: 4px 8px;
  font-family: "Courier New", ui-monospace, monospace;
  font-size: 0.64rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  cursor: pointer;
}

.channel-category-toggle .icon {
  width: 12px;
  height: 12px;
  transition: transform 120ms ease;
}

.channel-category-toggle.collapsed .icon {
  transform: rotate(-90deg);
}

.channel-call-participants {
  list-style: none;
  /* margin-left calculado em JS (app.js), pra alinhar com o começo do nome
     do canal mesmo quando ele tem ícone personalizado. */
  margin: 2px 0 4px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.channel-call-participant {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

/* Respiro entre o canal e o primeiro participante da call. */
.channel-call-participant:first-child {
  margin-top: 10px;
}

.channel-call-participant .avatar-thumb,
.channel-call-participant .avatar-fallback {
  width: 20px;
  height: 20px;
  font-size: 0.64rem;
  flex: none;
}

.channel-call-participant-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.9rem;
  color: #b5bac1 !important;
}

/* "AO VIVO" do lado de quem está compartilhando de verdade (voiceOccupancy
   .broadcasting) — não mais um selo genérico fixo na sidebar. */
.channel-call-participant-live {
  flex: none;
  font-size: 0.56rem;
  padding: 1px 4px;
  /* Único selo com o cardzinho de preview no hover (ver
     attachBroadcastPreviewHover em app.js) — o genérico .channel-live-badge
     (cursor: default) não é clicável em lugar nenhum. */
  cursor: pointer;
}

.channel-call-participant-states {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  flex: none;
  color: #b5bac1;
}

.channel-call-participant-states .icon {
  width: 14px;
  height: 14px;
}

.channel-hash {
  width: 19px;
  height: 19px;
  color: var(--muted);
  flex: none;
  display: grid;
  place-items: center;
}

.channel-speaker {
  width: 19px;
  height: 19px;
  color: var(--muted);
  flex: none;
  display: grid;
  place-items: center;
}

/* Alguém na voz agora (channel.online) — vira verde, igual ao indicador antigo. */
.channel-speaker.online {
  color: #23a55a;
}

.channel-hash .icon,
.channel-speaker .icon {
  stroke-width: 2.5;
}

#voiceLiveBadge {
  display: inline-block;
  margin: 4px 0;
}

.channel-live-badge {
  flex: none;
  font-size: 0.62rem;
  font-weight: 700;
  color: #fff;
  background: #ed4245;
  padding: 1px 5px;
  border-radius: 4px;
  letter-spacing: 0.02em;
  cursor: default;
}

/* Preview (screenshot) de quem está transmitindo, no hover do selo "AO VIVO"
   — ver attachBroadcastPreviewHover em app.js. Virou um cardzinho clicável
   (não só uma dica): imagem + o que está sendo compartilhado + botão pra
   entrar na call já assistindo — por isso pointer-events normal (não mais
   "none" como quando era só uma tooltip). */
.broadcast-preview-popover {
  position: fixed;
  z-index: 200;
  width: 280px;
  padding: 4px;
  background: var(--panel-raised);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
}

.broadcast-preview-thumb img {
  display: block;
  width: 100%;
  max-height: 180px;
  object-fit: cover;
  border-radius: 5px;
  /* Letterbox de captura ultrawide/vertical — preto só ATRÁS de uma imagem que
     existe. Sem quadro, quem aparece é .broadcast-preview-placeholder; um <img>
     vazio com este fundo era o "tela preta" que não dizia nada. */
  background: #000;
}

.broadcast-preview-placeholder {
  display: grid;
  place-items: center;
  gap: 6px;
  aspect-ratio: 16 / 9;
  padding: 8px;
  border: 1px dashed var(--border);
  border-radius: 5px;
  background: var(--panel-soft);
  color: var(--muted);
  font-size: var(--text-xs);
  text-align: center;
}

.broadcast-preview-placeholder .icon {
  width: 24px;
  height: 24px;
  opacity: 0.7;
}

.broadcast-preview-info {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 4px 4px;
}

.broadcast-preview-source {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  color: var(--muted);
  font-size: 0.8rem;
}

.broadcast-preview-source-icon,
.broadcast-preview-source-fallback-icon {
  flex: none;
  width: 16px;
  height: 16px;
  border-radius: 4px;
}

.broadcast-preview-source-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.broadcast-preview-watch-btn {
  flex: none;
  padding: 6px 10px;
  font-size: 0.8rem;
  white-space: nowrap;
}

.channel-icon {
  width: 18px;
  height: 18px;
  border-radius: 30%;
  flex: none;
  object-fit: cover;
}

.main-header .channel-icon {
  width: 22px;
  height: 22px;
  vertical-align: middle;
  margin-right: 6px;
}

.channel-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Canal de texto com mensagem nova ainda nao aberta (sem @mencao/resposta pra
   mim): nome fica branco/negrito + uma bolinha, igual ao "nao lido" do Discord. */
.channel-list li button.has-unread .channel-name {
  color: var(--text);
  font-weight: 700;
}

.channel-unread-dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text);
}

/* Selo de mencao/resposta: bola vermelha com o numero em branco — soma de
   quantas mensagens nao lidas desse canal me citam ou respondem a mim. */
.channel-alert-badge {
  flex: none;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  background: #ed4245;
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
  display: grid;
  place-items: center;
}

/* Facepile de quem mandou as últimas mensagens não lidas de um canal/DM —
   avatares empilhados, cada um ~50% coberto pelo próximo (mais recente na
   frente). */
.avatar-stack {
  display: flex;
  flex: none;
  align-items: center;
}

.avatar-stack .avatar-thumb {
  position: relative;
  border: 2px solid var(--panel);
  box-sizing: content-box;
}

.avatar-stack .avatar-thumb:not(:first-child) {
  margin-left: -14px;
}

.channel-list li button:hover {
  background: var(--panel-raised);
  color: var(--text);
}

.channel-list li button.active {
  background: color-mix(in srgb, var(--accent-base) 34%, var(--panel-raised));
  box-shadow: inset 0 0 0 999px rgba(83, 88, 98, 0.16);
  color: #fff;
}

/* Faixa do Space: arte decorativa, com o texto acima da região mais escura.
   O pseudo-elemento mantém a imagem nos estados hover/active do botão. */
.channel-list li button.channel-space {
  position: relative;
  isolation: isolate;
  min-height: 48px;
  margin-block: 3px;
  background: #101329;
  color: #edf1ff;
  box-shadow: inset 0 0 0 1px #444268;
}

.channel-list li button.channel-space::after {
  content: "";
  position: absolute;
  inset: 1px;
  z-index: -1;
  border-radius: 5px;
  background: linear-gradient(90deg, #101329e6, #10132999 40%, transparent 75%),
    url("./assets/space-channel.svg") right center / 288px 48px no-repeat;
  image-rendering: pixelated;
  pointer-events: none;
}

.channel-space .channel-name {
  font-weight: 700;
  text-shadow: 0 1px 2px #080b1e, 0 0 4px #080b1e;
}

.channel-list li button.channel-space:hover {
  box-shadow: inset 0 0 0 1px #9289c7;
}

.channel-list li button.channel-space.active {
  box-shadow: inset 3px 0 #b8abff, inset 0 0 0 1px #b8abff;
}

.channel-list li button.channel-space:focus-visible {
  outline: 2px solid #d6ceff;
  outline-offset: 2px;
}

.dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #4c5058;
  flex: none;
}

.dot.online {
  background: #23a55a;
}

.presence-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px 8px;
  border-radius: 6px;
  color: var(--muted);
}

.presence-row.member-offline {
  color: var(--muted);
}

/* Hover da lista de membros: um tom levemente puxado pra cor de destaque, pra
   não se confundir com o hover (cinza) da lista de canais. */
.presence-row.has-profile {
  transition: background-color 0.12s ease, color 0.12s ease;
}

.presence-row.has-profile:hover {
  background: color-mix(in srgb, var(--accent-base) 14%, var(--panel-raised));
  color: var(--text);
}

.member-status-line.member-activity-live {
  color: color-mix(in srgb, #ed4245 70%, var(--text));
}

.presence-row.member-offline .avatar-thumb,
.presence-row.member-offline .avatar-fallback,
.presence-row.member-offline .member-offline-name {
  opacity: 0.3;
}

.main-panel {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  min-height: 0;
  position: relative;
}

/* Some enquanto voice.js conecta a chamada — sem isso a única pista era um
   texto sr-only (#voiceStatus), invisível pra quem enxerga. */
.voice-connecting-overlay {
  position: absolute;
  inset: 0;
  z-index: 6;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  background: rgba(10, 11, 13, 0.82);
  color: #fff;
}

.voice-connecting-spinner {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, 0.25);
  border-top-color: var(--accent-strong);
  animation: voice-connecting-spin 0.8s linear infinite;
}

@keyframes voice-connecting-spin {
  to { transform: rotate(360deg); }
}

.main-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--border);
}

.main-header h1 {
  margin: 0;
  font-size: 1.1rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.main-header-action {
  margin-left: auto;
}

.modal-card.audio-settings-card {
  position: relative;
  width: 800px;
  height: 750px;
  max-width: calc(100vw - 24px);
  max-height: calc(100dvh - 24px);
  display: flex;
  flex-direction: column;
  padding: 24px;
  /* "clip", não "hidden": ambos recortam o conteúdo igual, mas "hidden"
     também cria um scrollport de verdade — foco num campo lá no fundo da
     aba Perfil (o toggle "Borda", por trás de um <label>, ganha foco no
     clique como qualquer form control) fazia o navegador rolar ESTE card
     internamente em vez do .audio-settings-content, cortando o modal ao
     meio. "clip" nunca vira alvo de rolagem, então quem rola é sempre o
     painel certo. */
  overflow: clip;
}

.audio-settings-card h2 {
  padding-right: 40px;
  margin-bottom: 14px;
}

/* Corpo em duas colunas: navegação por tema à esquerda, painel da direita com
   scroll próprio (personalizado, igual .chat-log) em vez do scroll nativo. */
.audio-settings-body {
  flex: 1;
  min-height: 0;
  display: flex;
  gap: 14px;
}

.audio-settings-nav {
  flex: 0 0 152px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-self: flex-start;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
}

.audio-settings-nav button {
  display: block;
  width: 100%;
  min-height: 30px;
  padding: 0 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text);
  font-size: 0.82rem;
  font-weight: 500;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}

.audio-settings-nav button:hover:not(:disabled),
.audio-settings-nav button:focus-visible {
  background: var(--accent);
  color: var(--accent-contrast);
  outline: none;
}

.audio-settings-content {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: 2px 10px 2px 4px;
  scrollbar-width: thin;
  scrollbar-color: var(--panel-raised) transparent;
}

.audio-settings-content::-webkit-scrollbar {
  width: 10px;
}

.audio-settings-content::-webkit-scrollbar-track {
  background: transparent;
}

.audio-settings-content::-webkit-scrollbar-thumb {
  background-color: var(--panel-raised);
  border-radius: 999px;
  border: 2px solid var(--panel);
  background-clip: padding-box;
}

.audio-settings-content::-webkit-scrollbar-thumb:hover {
  background-color: var(--accent-strong);
}

.audio-settings-content section[hidden] {
  display: none;
}

.audio-settings-card label:not(.toggle) {
  display: grid;
  gap: 7px;
  margin: 0 0 14px;
  min-width: 0;
}

.audio-settings-card label:not(.toggle) select,
.audio-settings-card label:not(.toggle) input:not([type="color"]),
.audio-settings-card meter {
  width: 100%;
  min-width: 0;
}

.audio-settings-card .toggle {
  min-height: 34px;
  margin: 4px 0;
}

.audio-settings-card #voiceOutputHint,
.audio-settings-card .field-hint {
  margin: -6px 0 12px;
  color: var(--muted);
  font-size: 0.8rem;
}

/* Label com o valor atual à direita (volume): o texto e o número dividem a
   primeira linha e o controle ocupa a segunda inteira. */
.audio-settings-card label.with-value {
  grid-template-columns: 1fr auto;
}

.audio-settings-card label.with-value > output {
  grid-row: 1;
  grid-column: 2;
  justify-self: end;
  color: var(--muted);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}

.audio-settings-card label.with-value > input {
  grid-column: 1 / -1;
}

/* Sensibilidade de entrada manual (ver applyMicProcessor em voice.js): o
   slider é a LINHA do limiar e, atrás dele, a barra mostra até onde a voz
   chega agora — cinza abaixo da linha, verde quando o portão abre. O trilho
   do próprio range fica transparente pra barra aparecer por baixo. */
.voice-sensitivity-track {
  grid-column: 1 / -1;
  position: relative;
  display: block;
  height: 18px;
}
.voice-sensitivity-track::before,
.voice-sensitivity-level {
  content: "";
  position: absolute;
  left: 0;
  top: 6px;
  height: 6px;
  border-radius: 999px;
}
.voice-sensitivity-track::before { right: 0; background: var(--border); }
.voice-sensitivity-level {
  width: 0%;
  background: var(--muted);
  transition: width 60ms linear, background-color var(--dur-fast);
}
.voice-sensitivity-level.is-open { background: #23a55a; } /* mesmo verde do "falando" */
.voice-sensitivity-track input[type="range"] {
  position: relative;
  width: 100%;
  margin: 0;
}
.voice-sensitivity-track input[type="range"]::-webkit-slider-runnable-track { background: transparent; }
.voice-sensitivity-track input[type="range"]::-moz-range-track { background: transparent; }
.voice-sensitivity-track input[type="range"]::-webkit-slider-thumb {
  width: 6px;
  height: 18px;
  margin-top: -6px;
  border-radius: 3px;
}

.audio-settings-card #voiceTest {
  width: 100%;
  margin: 8px 0 14px;
}

.audio-settings-card .gate-actions {
  justify-content: flex-end;
  margin-top: 14px;
}

.audio-settings-card .audio-settings-logout {
  margin-right: auto;
  color: var(--danger);
}

.spotify-connect-block {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px;
  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
}

.spotify-connect-info {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.spotify-connect-info p {
  margin: 2px 0 0;
  font-size: 0.8rem;
  color: var(--muted);
}

.spotify-disconnect-button {
  color: var(--danger);
}

.audio-settings-card .modal-close-x {
  top: 12px;
  right: 12px;
}

.audio-settings-card h3 {
  margin: 4px 0 12px;
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.shortcut-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 6px;
}

.shortcut-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.shortcut-row > span:first-child {
  flex: 1;
  min-width: 0;
}

.shortcut-combo {
  font-family: inherit;
  font-size: 0.85rem;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
}

/* Configurações → Sobre (ver voice.js): mesmo padrão de lista do
   shortcut-list acima, só com o valor (versão) à direita em vez de um botão. */
.about-version-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 6px;
}
.about-version-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel-soft);
}
.about-version-list li strong {
  font-family: var(--font-body);
  font-variant-numeric: tabular-nums;
  color: var(--accent-strong);
}

.shortcut-combo:hover {
  border-color: var(--accent, #5865f2);
}

.shortcut-reset {
  padding: 4px 6px;
}

/* Janela pequena demais pro card de 800x750: ele vira a tela inteira e a
   navegação vira uma faixa horizontal no topo em vez de coluna lateral.
   Chaveado por body.is-mobile (mesma fonte única do "modo mobile" que o
   JS usa pro resto do app — ver mobileModeQuery/syncMobileMode em app.js)
   em vez de um @media próprio: os dois tinham limiares DIFERENTES
   (824px/774px aqui vs. 900px/560px lá), então numa faixa de viewport (ex.
   tablet ~830-900px) o JS já tratava como mobile — escondia o hambúrguer,
   ativava as gavetas por gesto — mas este modal continuava desenhando o
   card de desktop espremido em vez da tela cheia, resultado inconsistente/
   "bugado" bem nessa faixa. */
body.is-mobile .modal-card.audio-settings-card {
  width: 100vw;
  height: 100dvh;
  max-width: none;
  max-height: none;
  padding: 20px 16px 16px;
  border-radius: 0;
}

body.is-mobile .audio-settings-body {
  flex-direction: column;
}

/* No corpo em coluna o eixo transversal é a LARGURA: o align-self: flex-start
   do desktop faria a nav encolher pro tamanho de todos os botões somados e
   estourar o card (overflow: clip), em vez de rolar. Esticada na largura do
   card, com min-width: 0, o overflow-x vira rolagem de verdade. */
body.is-mobile .audio-settings-nav {
  flex: none;
  flex-direction: row;
  align-self: stretch;
  width: 100%;
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

body.is-mobile .audio-settings-nav::-webkit-scrollbar {
  display: none;
}

body.is-mobile .audio-settings-nav button {
  white-space: nowrap;
  width: auto;
  flex: none;
}

body.is-mobile .audio-settings-card .gate-actions,
body.is-mobile .audio-settings-card .gate-actions button {
  width: 100%;
}

.online-badge {
  background: #23a55a;
  color: #fff;
  font-size: 0.72rem;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
}

/* Barra compacta mostrada sempre que a pessoa esta conectada a um canal de
   sessao (video/voz) — a chamada em si so aparece quando ela clica pra abrir
   o visualizador (#callViewerModal), nunca fica embutida "no cantinho". */
.call-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 18px;
  border-bottom: 1px solid var(--border);
  background: var(--panel);
}

.call-bar-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #23a55a;
  flex: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, #23a55a 30%, transparent);
}

.call-bar-status {
  font-size: 0.88rem;
  font-weight: 600;
}

.call-bar-count {
  color: var(--muted);
  font-size: 0.82rem;
}

.call-bar-open {
  margin-left: auto;
}

.chat-log {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 12px 18px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  scrollbar-width: thin;
  scrollbar-color: var(--panel-raised) transparent;
}

/* As linhas são itens flex de uma coluna com altura fixa: por padrão podem
   ENCOLHER quando o histórico passa da área visível. Com min-height:auto o
   conteúdo segura, mas qualquer regra que zere o min-height (era o caso de
   .is-grouped.has-media) deixava a linha ir a 0px e a imagem/figurinha de até
   480px vazava por cima das mensagens seguintes — o "chat sobreposto". Linha
   de chat nunca encolhe; quem rola é o .chat-log. */
.chat-log > * {
  flex-shrink: 0;
}

/* Firefox só tem scrollbar-color acima; WebView2/Chromium usa os
   pseudo-elementos abaixo — junto cobre os dois motores. */
.chat-log::-webkit-scrollbar {
  width: 10px;
}

.chat-log::-webkit-scrollbar-track {
  background: transparent;
}

.chat-log::-webkit-scrollbar-thumb {
  background-color: var(--panel-raised);
  border-radius: 999px;
  border: 2px solid var(--bg);
  background-clip: padding-box;
}

.chat-log::-webkit-scrollbar-thumb:hover {
  background-color: var(--accent-strong);
}

.chat-message {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 1.08rem;
  line-height: 1.4;
}

.chat-message:not(:first-child):not(.is-grouped) {
  margin-top: 2px;
}

.chat-message .avatar-thumb,
.chat-message .avatar-fallback {
  margin-top: 2px;
  /* Bem maior que o avatar-thumb padrao (24px) — só nas mensagens do chat, o
     resto (lista de membros, voz, admin) continua no tamanho normal. */
  width: 40px;
  height: 40px;
  font-size: 1.1rem;
}

.chat-message-content {
  min-width: 0;
  overflow-wrap: break-word;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.chat-message-header {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.message-delete {
  min-height: auto;
  padding: 0 6px;
  font-size: 0.72rem;
  font-weight: 500;
  border: none;
}

.chat-attachment-preview {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 8px 18px 0;
  font-size: 0.82rem;
  color: var(--muted);
}

.chat-attachment-preview button {
  min-height: auto;
  padding: 0 6px;
  font-size: 0.72rem;
  border: none;
}

.chat-attachment-preview-thumb {
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: 6px;
  flex: none;
}

.attachment-link {
  display: block;
  margin-top: 4px;
  min-height: auto;
  padding: 4px 8px;
  font-size: 0.82rem;
  border-radius: 6px;
  background: var(--panel-soft);
  border: 1px solid var(--border);
  color: var(--text);
}

.attachment-link:hover {
  background: var(--panel-raised);
}

.chat-link {
  color: var(--accent-strong);
  text-decoration: underline;
  text-underline-offset: 2px;
  word-break: break-all;
}

.chat-link:hover {
  text-decoration: none;
}

/* Prévia de link (título/descrição/imagem via OpenGraph) — só aparece se o
   servidor achar algo de verdade (ver buildLinkPreviewCard em app.js), senão
   fica escondida e sobra só o <a> clicável do texto. Imagem em cima, em toda
   a largura do card — estilo Discord/Twitter, bem melhor pra thumbnail de
   vídeo (YouTube etc.) do que uma miniatura pequena do lado do texto. */
.link-preview-card {
  display: block;
  margin-top: 6px;
  width: 360px;
  max-width: 100%;
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  background: var(--panel-soft);
  color: var(--text);
  text-decoration: none;
  transition: background 120ms ease, border-color 120ms ease;
}

.link-preview-card:hover {
  background: var(--panel-raised);
  border-color: var(--accent-strong);
}

.link-preview-image {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: #111214;
}

.link-preview-text {
  min-width: 0;
  padding: 8px 10px;
}

.link-preview-site {
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-bottom: 3px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.link-preview-title {
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--accent-strong);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.link-preview-description {
  margin-top: 2px;
  font-size: 0.78rem;
  color: var(--muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Imagem enviada no chat: aparece inline, nao so como link de download. */
.chat-attachment-image {
  all: unset;
  display: block;
  margin-top: 4px;
  cursor: zoom-in;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--border);
  max-width: min(560px, 85vw);
}

.chat-attachment-image img {
  display: block;
  /* width/height vêm como atributo (dimensão original, ver renderMessage em
     app.js) só pra proporção: sem width/height:auto o navegador tratava os
     atributos como tamanho fixo e clampava cada eixo separado (560x480 pra
     qualquer imagem = caixa letterbox com barras vazias). */
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 480px;
  object-fit: contain;
  background: var(--panel-soft);
  /* Mesmo motivo do .chat-gif: mesmo com width/height reservados via atributo
     (ver renderMessage em app.js), o carregamento da prévia ainda é
     assíncrono — sem isso a linha nasce colapsada e "explode" depois. */
  min-width: 160px;
  min-height: 120px;
}

.chat-attachment-image img.is-loaded {
  min-width: 0;
  min-height: 0;
}

.chat-attachment-image:hover {
  border-color: var(--accent-strong);
}

/* Especificidade maior que .modal-card (que vem depois no arquivo e senao
   vence a cascata, travando a largura em min(360px, 90vw) e amassando a
   imagem em vez de deixar o modal acompanhar o tamanho dela). */
.modal-card.image-lightbox-card {
  position: relative;
  width: auto;
  max-width: 92vw;
  padding: 12px;
}

.image-lightbox-card img {
  display: block;
  max-width: 88vw;
  max-height: 78vh;
  border-radius: 8px;
  margin: 0 auto;
}

.modal-close-x {
  position: absolute;
  top: -14px;
  right: -14px;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 50%;
  background: #ed4245;
  color: #fff;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
  z-index: 1;
}

.modal-close-x:hover {
  background: #c73336;
}

.chat-message .author {
  font-weight: 700;
}

.chat-message .time {
  color: var(--muted);
  font-size: 0.72rem;
}

/* Mesmo autor em até quatro minutos: a conversa vira um bloco compacto. */
.chat-message.is-grouped {
  min-height: 24px;
  align-items: center;
  margin-top: 0;
  padding-top: 0;
}

.chat-message.is-grouped > .avatar-thumb,
.chat-message.is-grouped > .avatar-fallback {
  display: none;
}

.chat-message.is-grouped .chat-message-header {
  display: none;
}

.chat-message-grouped-time {
  flex: 0 0 40px;
  color: var(--muted);
  font-size: 0.64rem;
  line-height: 1.2;
  text-align: right;
  font-variant-numeric: tabular-nums;
  opacity: 0;
  transition: opacity var(--dur-fast) ease;
}

.chat-message.is-grouped:hover .chat-message-grouped-time,
.chat-message.is-grouped:focus-within .chat-message-grouped-time {
  opacity: 0.8;
}

.typing-indicator { min-height: 22px; padding: 4px 18px 0; color: var(--muted); font-size: .8rem; }
.typing-indicator strong { color: var(--text); }
.typing-indicator { display: flex; align-items: center; gap: 5px; }
.typing-indicator[hidden] { display: none; }
.typing-gif { height: 16px; width: auto; max-width: 22px; object-fit: contain; flex: none; }
.typing-dots { display: inline-block; width: 14px; overflow: hidden; vertical-align: bottom; animation: typing-dots 1s steps(4, end) infinite; }
@keyframes typing-dots { from { width: 0; } to { width: 14px; } }

.chat-form {
  position: relative;
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 12px 18px;
  /* Zero em quem não tem notch/home indicator (safe-bottom = 0px) — não
     afeta o padding em desktop nem em Android sem gesture bar. */
  padding-bottom: calc(12px + var(--safe-bottom));
  border-top: 1px solid var(--border);
}

.chat-input-shell {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: flex-end;
  gap: 4px;
  padding: 0 4px 0 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel-soft);
}

.chat-input-shell:focus-within { border-color: var(--accent-strong); }

.chat-input-shell textarea {
  flex: 1;
  min-width: 0;
  min-height: 22px;
  max-height: calc(1.4em * 10 + 16px);
  padding: 8px 0;
  border: 0;
  outline: 0;
  resize: none;
  overflow-y: auto;
  background: transparent;
  color: var(--text);
  font: inherit;
  line-height: 1.4;
}

.chat-input-shell .btn-text { flex: none; }

.emote {
  width: 34px;
  height: 34px;
  vertical-align: middle;
  object-fit: contain;
}

/* Mensagem que é só emote(s) — ver renderMessageBody em app.js — fica bem
   maior, igual ao "jumbo" do Discord. */
.emote-jumbo {
  width: 64px;
  height: 64px;
}

/* Mesma ideia do .emote-jumbo, mas pra emoji unicode (texto, não <img>). */
.emoji-jumbo {
  font-size: 2.6em;
  line-height: 1;
  vertical-align: middle;
}

/* Reação usa o mesmo .emote (renderReactions) mas o pill fica gigante do
   contrário — mantém um tamanho menor que o do chat, só aqui. */
.reaction-pill .emote {
  width: 24px;
  height: 24px;
}

/* Flutua sobre o chat em vez de empurrar o composer pra cima — some assim que
   algo é escolhido, então não precisa disputar espaço com o resto da tela. */
.emote-picker {
  position: absolute;
  /* right, não left: o botão de emote fica perto do fim do composer (depois
     do textarea, antes do "Enviar") — left:0 ancorava no início do form
     inteiro (do lado do clipe de anexo), longe do botão que abre o picker. */
  right: 0;
  bottom: calc(100% + 8px);
  width: min(440px, calc(100% - 36px));
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-2);
  padding: 12px;
  z-index: 55;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Altura fixa (nao max-height): os 3 conteudos do picker (emoji/customizados/gif)
   sempre ocupam o mesmo espaco, trocar de aba nunca empurra o visualizador de
   chamada acima. */
.emote-picker-grid {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: 8px;
  height: 300px;
  overflow-y: auto;
}

.emote-picker-grid button {
  all: unset;
  cursor: pointer;
  width: 46px;
  height: 46px;
  font-size: 1.6rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
}

.emote-picker-grid button:hover {
  background: var(--panel-raised);
}

.emote-picker-grid img {
  width: 34px;
  height: 34px;
  object-fit: contain;
}

.emote-create-form {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 8px;
}

.emote-create-form input {
  flex: 1;
}

@media (max-width: 640px) {
  .chat-form {
    padding-left: 12px;
    padding-right: 12px;
  }

  .chat-input-shell textarea {
    /* Metade das 10 linhas de desktop — em tela pequena um composer alto
       demais empurra o chat inteiro pra fora da viewport visível. */
    max-height: calc(1.4em * 5 + 16px);
  }

  /* Popover vira bottom-sheet: fixo na base da viewport, largura total,
     cantos arredondados só em cima, com scrim atrás. O <form> continua
     position:relative mas o picker deixa de ser posicionado em relação a
     ele — ancora direto na tela. */
  .emote-picker {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    width: auto;
    max-height: 60dvh;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    padding-bottom: calc(12px + var(--safe-bottom));
    z-index: 60;
  }

  .emote-picker::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    background: rgba(0, 0, 0, 0.5);
  }

  .emote-picker-grid {
    /* Sem altura fixa em mobile: o sheet já tem max-height próprio, então a
       grade só precisa preencher o que sobrar sem forçar scroll duplo. */
    height: auto;
    flex: 1;
    min-height: 0;
  }
}

/* Modal grande (não mais uma flutuante pequena perto do botão) — mesma
   base .modal/.modal-card do resto do app (admin, configs de áudio, etc.),
   só maior pra caber uma grade de sons confortável. */
.modal-card.soundboard-modal-card {
  position: relative;
  width: min(760px, 92vw);
  max-height: min(680px, 85vh);
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.soundboard-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.soundboard-volume {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--muted);
  cursor: default;
}

.soundboard-volume .icon {
  flex: none;
  width: 15px;
  height: 15px;
}

.soundboard-volume input[type="range"] {
  flex: 1;
  min-width: 0;
}

/* Mesma base .modal/.modal-card do soundboard — painel de compartilhar tela
   aberto pelo botão ao lado de mic/fone/sair (index.html, .voice-controls).
   Largura maior que o padrão (min(360px,90vw) de .modal-card) pra caber a
   grade de miniaturas confortavelmente — mesmo precedente do soundboard. */
.modal-card.screen-share-modal-card {
  position: relative;
  width: min(980px, 94vw);
  max-height: min(760px, 88vh);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.screen-share-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.screen-share-preview {
  width: 100%;
  border-radius: 8px;
  background: #000;
  aspect-ratio: 16 / 9;
  object-fit: contain;
}

.screen-share-stats {
  font-size: 12px;
  color: var(--muted);
}

.screen-share-hint,
.settings-hint {
  font-size: 12px;
  color: var(--muted);
  margin: 0;
}
#settings-app { display: grid; gap: 12px; }

.screen-share-source-picker {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Grade de miniaturas clicáveis, estilo Discord — mesmo padrão de card em
   grade que .soundboard-grid/.soundboard-clip já usa no soundboard. */
.screen-share-source-grid {
  display: grid;
  /* Fixo em 3 por linha (não mais auto-fill) — o modal agora é bem mais
     largo, então uma grade elástica deixava as miniaturas grandes demais;
     o resto rola pra baixo com a mesma barra discreta do resto do app. */
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
  max-height: 420px;
  overflow-y: auto;
  padding: 2px;
}

.screen-share-source-card {
  all: unset;
  box-sizing: border-box;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 6px;
  border-radius: var(--radius-md);
  background: var(--panel-soft);
  border: 2px solid transparent;
}

.screen-share-source-card:hover {
  background: var(--panel-raised);
}

.screen-share-source-card.active {
  border-color: var(--accent-strong);
  background: color-mix(in srgb, var(--accent-strong) 20%, var(--panel-soft));
  box-shadow: var(--glow-accent);
}

.screen-share-source-thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #111214;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
}

.screen-share-source-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: none;
}

.screen-share-source-card.has-thumbnail .screen-share-source-thumb img { display: block; }
.screen-share-source-card.has-thumbnail .screen-share-source-placeholder { display: none; }

/* Hover no preview: escurece e revela um botão branco central que já
   seleciona a fonte E começa a transmitir, sem precisar do "Iniciar" do
   rodapé (ver attachStartOverlay em screen-share.js). */
.screen-share-source-start-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-fast);
}
.screen-share-source-card:hover .screen-share-source-start-overlay,
.screen-share-source-start-button:focus-visible {
  opacity: 1;
  pointer-events: auto;
}
.screen-share-source-start-button {
  background: #fff;
  color: #111;
  border-radius: var(--radius-pill);
  padding: 7px 16px;
  font-size: var(--text-xs);
  font-weight: 600;
  cursor: pointer;
}
.screen-share-source-start-button:hover {
  background: #e5e5e5;
}

.screen-share-source-placeholder {
  width: 28px;
  height: 28px;
}

/* Skeleton enquanto bridge.getSourceThumbnail(id) não volta — é um round-trip
   IPC que reprocessa todas as fontes por chamada, pode demorar visivelmente
   com várias janelas abertas. */
.screen-share-source-card.is-loading .screen-share-source-thumb {
  background: linear-gradient(90deg, #111214 25%, #1c1e22 37%, #111214 63%);
  background-size: 400% 100%;
  animation: screen-share-skeleton 1.4s ease infinite;
}

@keyframes screen-share-skeleton {
  0% { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

.screen-share-source-name {
  font-size: var(--text-xs);
  font-weight: 600;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.screen-share-type-tabs,
#screenShareQuality,
#screenShareFps {
  width: fit-content;
}

.screen-share-type-tabs.is-disabled,
#screenShareQuality.is-disabled,
#screenShareFps.is-disabled,
.screen-share-source-grid.is-disabled {
  pointer-events: none;
  opacity: 0.5;
}

.screen-share-settings-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: center;
}

.screen-share-setting {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.screen-share-setting-label {
  font-size: var(--text-xs);
  color: var(--muted);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

/* Card de jogo detectado (Valorant/LoL), na mesma grade dos cards de tela/
   janela — sem thumbnail (o helper nativo não gera uma), então o destaque vem
   da cor de acento no ícone/borda em vez de uma imagem. */
.screen-share-source-card.is-game .screen-share-source-thumb {
  background: color-mix(in srgb, var(--accent-strong) 14%, #111214);
  color: var(--accent-strong);
}

.screen-share-source-card.is-game .screen-share-source-placeholder {
  width: 32px;
  height: 32px;
}

/* Barra de recuperação de captura de jogo — só existe com uma sessão de jogo
   já ao vivo (screen-share.js: syncGameRecovery). */
.screen-share-game-recovery {
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Lacuna pré-existente: .button-row(.compact) não tinha nenhum layout — os
   botões do painel de jogo empilhavam sem quebra de linha. */
.button-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
}

.button-row.compact {
  gap: 6px;
}

#screenShareButton.is-active {
  color: #fff;
}

/* Scroll personalizado no conteúdo do modal de compartilhamento — mesmo padrão de
   .chat-log (l.1143) e .audio-settings-content (l.917): thumb fino/arredondado
   em vez da scrollbar nativa grossa do Chromium. */
.screen-share-content,
.screen-share-source-grid {
  scrollbar-width: thin;
  scrollbar-color: var(--panel-raised) transparent;
}

.screen-share-content::-webkit-scrollbar,
.screen-share-source-grid::-webkit-scrollbar {
  width: 10px;
}

.screen-share-content::-webkit-scrollbar-track,
.screen-share-source-grid::-webkit-scrollbar-track {
  background: transparent;
}

.screen-share-content::-webkit-scrollbar-thumb,
.screen-share-source-grid::-webkit-scrollbar-thumb {
  background-color: var(--panel-raised);
  border-radius: 999px;
  border: 2px solid var(--panel);
  background-clip: padding-box;
}

.screen-share-content::-webkit-scrollbar-thumb:hover,
.screen-share-source-grid::-webkit-scrollbar-thumb:hover {
  background-color: var(--accent-strong);
}

/* Grade de cards (ícone + nome + duração) em vez de pilulas — cada som fica
   nomeado e reconhecível de longe, igual ao soundboard do Discord. */
.soundboard-grid {
  display: grid;
  /* Fixo em 5 por linha (não mais auto-fill) — grade elástica deixava os
     cards esticados demais; o resto rola só pra baixo com a mesma barra
     discreta do resto do app. */
  grid-template-columns: repeat(5, 1fr);
  gap: 10px;
  flex: 1;
  min-height: 180px;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 2px;
}

.soundboard-empty {
  grid-column: 1 / -1;
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 0.8rem;
  color: var(--muted);
}

.soundboard-search {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border-radius: var(--radius-sm, 6px);
  background: var(--panel-soft);
  border: 1px solid var(--border);
}

.soundboard-search .icon {
  flex: none;
  width: 14px;
  height: 14px;
  color: var(--muted);
}

.soundboard-search input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  padding: 0;
  font-size: 0.85rem;
}

.soundboard-search:focus-within {
  border-color: var(--accent-strong);
}

.soundboard-clip {
  all: unset;
  position: relative;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 10px 6px 8px;
  border-radius: var(--radius-sm, 6px);
  background: var(--panel-soft);
  border: 1px solid transparent;
  text-align: center;
  min-width: 0;
}

@media (max-width: 560px) {
  .soundboard-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.soundboard-clip:hover {
  background: var(--panel-raised);
}

.soundboard-clip.playing {
  background: color-mix(in srgb, var(--accent-strong) 25%, var(--panel-soft));
  border-color: var(--accent-strong);
}

.soundboard-clip-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--panel-raised);
  color: var(--muted);
}

.soundboard-clip.playing .soundboard-clip-icon {
  background: var(--accent-strong);
  color: #fff;
}

.soundboard-clip-icon .icon {
  width: 15px;
  height: 15px;
}

.soundboard-clip-name {
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.76rem;
  font-weight: 600;
}

.soundboard-clip-duration {
  font-size: 0.66rem;
  color: var(--muted);
}

.soundboard-clip-remove {
  position: absolute;
  top: -6px;
  right: -6px;
  display: flex;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  align-items: center;
  justify-content: center;
  background: #ed4245;
  color: #fff;
  /* Borda no tom do fundo do card: separa a bolinha vermelha de trás dela
     (o ícone de lixeira é maior que o antigo x-circle, precisa de mais
     respiro pra não parecer colado no card). */
  border: 2px solid var(--panel-soft);
}

.soundboard-clip-remove .icon {
  width: 13px;
  height: 13px;
}

.soundboard-clip-remove:hover {
  background: #c73336;
}

.soundboard-upload-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Spinner pequeno pra dentro de um botão (envio do som, confirmar exclusão)
   — mesma animação do spinner de "Conectando…" da call, só menor e sem
   depender de nenhuma cor de fundo específica (currentColor). */
.btn-spinner {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor;
  animation: voice-connecting-spin 0.8s linear infinite;
}

.soundboard-delete-confirm-card {
  text-align: left;
}

.soundboard-delete-confirm-card p {
  color: var(--muted);
  margin: 0 0 18px;
}

.soundboard-delete-confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

.soundboard-upload-row {
  display: flex;
  gap: 8px;
  align-items: center;
}

.soundboard-upload-row input {
  flex: 1;
  min-width: 0;
}

/* Corte por arrasto direto no desenho do áudio (wavesurfer.js + plugin de
   regiões, vendorizados em vendor/) em vez de sliders de início/duração —
   ver getSoundWaveform em voice.js. */
.soundboard-trim {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.soundboard-trim-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 0.76rem;
  color: var(--muted);
}

.soundboard-waveform {
  border: 1px solid var(--border);
  border-radius: 6px;
  background: #111214;
  padding: 4px;
}

.soundboard-trim-hint {
  margin: 0;
  font-size: 0.72rem;
  color: var(--muted);
}

.voice-panel { border-top: 1px solid var(--border); padding-top: 10px; font-size: .82rem; }
.voice-panel > button { margin: 6px 0; width: 100%; }

/* Controles de voz no rodapé do painel do usuário — com a identidade (foto,
   status, nome) à esquerda e mutar/fone à direita. */
.voice-controls {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  margin: 0;
}
.voice-panel select { width: 100%; min-width: 0; background: var(--panel-soft); color: var(--text); padding: 6px; }
.voice-panel .check { display: flex; align-items: center; }
.voice-panel .check input { min-height: auto; }
.voice-panel summary { cursor: pointer; margin: 12px 0; }
.voice-panel meter { width: 100%; }
/* Selo da própria qualidade de conexão (verde/amarelo/vermelho) na seção
   fixa acima do rodapé de perfil (#myPingSection) — só a sua, não a dos
   outros participantes (o LiveKit só expõe RTT exato pro próprio cliente). */
.voice-ping {
  display: inline-flex;
  align-items: center;
  width: fit-content;
  line-height: 1;
}

.voice-ping .icon {
  width: 13px;
  height: 13px;
}

.voice-ping.ping-good { color: #3ba55d; }
.voice-ping.ping-fair { color: #f0b232; }
.voice-ping.ping-poor { color: #ed4245; }
.voice-ping.ping-unknown { color: var(--muted); }

.channel-list li button { box-sizing: border-box; }

.modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  display: grid;
  place-items: center;
  /* Precisa ficar acima do PiP da chamada (z-index:50) — senão um modal
     (admin, configurações de áudio, confirmar troca de canal...) abria
     atrás do vídeo flutuante em vez de na frente. */
  z-index: 60;
  /* Sem isto, um arraste vertical começando no fundo (fora do card) no
     celular "vazava" pro <body> por trás do overlay fixo e dava aquele
     rubber-band/scroll da página junto com o modal aberto. */
  overscroll-behavior: contain;
}

/* Trava o scroll do body enquanto um modal está aberto no celular (ver
   classe alternada no JS de abertura/fechamento, ex. audioSettingsButton em
   voice.js) — no desktop o overlay fixo já basta, sem isso aqui. */
body.is-mobile.modal-open {
  overflow: hidden;
}

/* O recorte de avatar/banner é aberto DE DENTRO das configurações (o botão
   fica na aba Perfil) — precisa ficar acima do modal de configurações, senão
   abre atrás dele e some até fechar o outro modal. */
#avatarCropModal {
  z-index: 62;
}

.modal-card {
  width: min(360px, 90vw);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 24px;
}

.modal-card h2 {
  margin: 0 0 12px;
  font-size: 1.1rem;
}

.avatar-crop-viewport {
  width: 300px;
  max-width: 100%;
  aspect-ratio: 1;
  margin: 0 auto;
  border-radius: 8px;
  overflow: hidden;
  background: var(--panel-soft);
  touch-action: none;
}

.avatar-crop-viewport.is-round {
  border-radius: 30%;
}

.avatar-crop-viewport canvas {
  width: 100%;
  height: 100%;
  cursor: grab;
  display: block;
}

.admin-card {
  width: min(560px, 92vw);
  max-height: 85vh;
  overflow-y: auto;
}

.admin-tabs {
  display: flex;
  gap: 6px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--border);
}

.admin-tabs .link-button {
  border-radius: 0;
  padding-bottom: 8px;
}

.admin-tabs .link-button.active {
  color: var(--text);
  border-bottom: 2px solid var(--accent-strong);
}

.admin-roles-list,
.admin-users-list {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
  display: grid;
  gap: 10px;
}

.admin-role-card {
  border: 1px solid var(--border);
  border-left-width: 4px;
  border-radius: 8px;
  padding: 10px;
  background: var(--panel-soft);
}

.admin-role-header {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 8px;
}

.admin-role-header label {
  font-size: 0.78rem;
  color: var(--muted);
  margin: 0;
}

.admin-role-header input {
  display: block;
  margin-top: 2px;
}

.admin-role-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
}

.admin-role-order {
  display: flex;
  gap: 2px;
  margin-right: auto;
}

.admin-role-form {
  border-top: 1px solid var(--border);
  padding-top: 10px;
}

.admin-role-form h3 {
  margin-top: 0;
}

.admin-user-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.admin-user-name {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.admin-user-owner {
  flex: none;
  font-size: 0.72rem;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: 4px;
}

.admin-channels-list {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
  display: grid;
  gap: 8px;
}

.admin-channel-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px;
}

.admin-channel-row .channel-icon,
.admin-channel-row .avatar-fallback {
  width: 32px;
  height: 32px;
  border-radius: 30%;
  flex: none;
  object-fit: cover;
}

.admin-channel-private {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: none;
  white-space: nowrap;
  font-size: 0.85rem;
  color: var(--muted);
  cursor: pointer;
}

.admin-channel-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.admin-channel-icon-picker {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 10px 0;
}

.admin-categories-list {
  list-style: none;
  margin: 0 0 12px;
  padding: 0;
  display: grid;
  gap: 8px;
}

.admin-category-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px;
}

.admin-category-row input {
  flex: 1;
  min-width: 0;
}

.admin-channel-category {
  max-width: 140px;
}

/* ---------- Status (presenca rica) ---------- */

.avatar-with-status {
  position: relative;
  flex: none;
}

.status-dot-button {
  all: unset;
  cursor: pointer;
  position: absolute;
  right: -2px;
  bottom: -2px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--panel);
  display: grid;
  place-items: center;
}

.status-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--status-offline);
}

.status-online { background: var(--live); }
.status-away { background: var(--warning); }
.status-dnd { background: var(--danger); }
.status-offline { background: var(--status-offline); }

.member-status-line {
  font-size: 0.72rem;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: 4px;
}

.dropdown-menu {
  position: fixed;
  z-index: 56;
  background: var(--panel-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-2);
  padding: 4px;
  display: grid;
  gap: 2px;
  min-width: 160px;
}

.dropdown-menu button {
  all: unset;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: calc(var(--radius-sm) - 2px);
  font-size: 0.85rem;
  color: var(--text);
}

.dropdown-menu button:hover {
  background: var(--panel-soft);
}

.dropdown-menu button.message-menu-danger {
  color: #ff9b9c;
}

/* ---------- Composer: responder / editar ---------- */

.composer-context {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 18px;
  font-size: 0.82rem;
  color: var(--muted);
  border-top: 1px solid var(--border);
}

.composer-context span {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Lista de "@" do composer: busca de pessoa pra citar (ver mentionAutocomplete
   em app.js), mesma familia visual do dropdown-menu (menu de status/opcoes). */
/* Resposta do app a um comando recusado, logo acima do composer. */
.composer-status {
  margin: 0 18px 4px;
  font-size: var(--text-xs);
  color: var(--warning);
}

/* Card do comando /top — mensagem de verdade no canal, com o snapshot que o
   servidor tirou na hora (kind:"top" + data em renderMessage). */
.rank-card {
  margin-top: 6px;
  width: 360px;
  max-width: 100%;
  box-sizing: border-box;
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--panel-soft);
  display: grid;
  gap: var(--space-3);
}

.rank-card-header { display: flex; align-items: center; gap: 6px; font-size: var(--text-sm); }
.rank-card-header .icon { width: 16px; height: 16px; color: var(--accent); }

.rank-group { display: grid; gap: 4px; }
.rank-group-title {
  display: flex;
  align-items: center;
  gap: 6px;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--border);
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}
.rank-group-title .icon { width: 12px; height: 12px; color: var(--accent); }

.rank-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.rank-row { display: flex; align-items: center; gap: 6px; font-size: 0.82rem; }
.rank-pos {
  width: 20px;
  flex: none;
  text-align: center;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.rank-row .avatar-thumb { width: 22px; height: 22px; flex: none; }
.rank-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rank-value { flex: none; color: var(--muted); font-variant-numeric: tabular-nums; }
.rank-row-1 .rank-name { font-weight: 700; }
.rank-empty { margin: 0; font-size: var(--text-xs); color: var(--muted); }

/* A lista de comandos reusa o visual do autocomplete de menção; só a descrição
   ao lado do nome é própria. */
.slash-autocomplete .icon { width: 16px; height: 16px; color: var(--accent); }
.slash-autocomplete .slash-desc {
  color: var(--muted);
  font-size: var(--text-xs);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mention-autocomplete {
  list-style: none;
  margin: 0 18px;
  padding: 4px;
  display: grid;
  gap: 2px;
  max-height: 220px;
  overflow-y: auto;
  background: var(--panel-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-2);
}

.mention-autocomplete li button {
  all: unset;
  box-sizing: border-box;
  cursor: pointer;
  width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: calc(var(--radius-sm) - 2px);
  font-size: 0.85rem;
  color: var(--text);
}

.mention-autocomplete li button .avatar-thumb,
.mention-autocomplete li button .avatar-fallback {
  width: 22px;
  height: 22px;
  font-size: 0.66rem;
  flex: none;
}

.mention-autocomplete li button:hover,
.mention-autocomplete li button.active {
  background: var(--panel-soft);
}

/* ---------- Mensagem: toolbar ao passar o mouse, reacoes, resposta ---------- */

.chat-message {
  position: relative;
  border-radius: 8px;
  padding: 3px 6px;
  margin: 0 -6px;
  animation: chat-message-in var(--dur-med) var(--ease-out) both;
}

/* A regra de agrupamento (mesmo autor em menos de 4 min, app.js)
   prevalece sobre o padding padrão acima: reduz só o espaço dentro do bloco. */
.chat-message.is-grouped {
  min-height: 20px;
  padding: 0 6px;
  /* O .chat-log espaça os filhos com gap de 6px; se deixar, as linhas do
     bloco agrupado ficam soltas. Puxa de volta quase todo esse gap pra elas
     ficarem coladas como continuação da mesma mensagem. */
  margin-top: -4px;
}

/* Agrupada que carrega figurinha/GIF/anexo de imagem: as métricas acima (e o
   align-items:center vindo do outro bloco de .is-grouped, mais acima neste
   arquivo) foram pensadas pra uma linha de TEXTO compacta (~20px) — na
   prática de uma mídia de até 340-480px de altura, o margin-top negativo e o
   center empurravam/cortavam errado o espaço com a mensagem seguinte. Volta
   pro espaçamento normal (mesmo de uma mensagem não agrupada), só sem
   repetir avatar/nome/header (isso continua igual, ver regras acima). */
.chat-message.is-grouped.has-media {
  align-items: flex-start;
  min-height: 0;
  margin-top: 4px;
}

@keyframes chat-message-in {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

.chat-message:hover,
.chat-message:focus-within {
  background: var(--panel-soft);
}

/* Mensagem que me cita com "@" ou responde a algo que eu mandei: amarelo
   suave (nao um amarelo berrante) pra destacar o bloco inteiro no log. */
.chat-message.message-alert-for-me {
  background: color-mix(in srgb, var(--mention) 10%, transparent);
  box-shadow: inset 2px 0 0 var(--mention);
}

.chat-message.message-alert-for-me:hover,
.chat-message.message-alert-for-me:focus-within {
  background: color-mix(in srgb, var(--mention) 16%, transparent);
}

/* "@Nome" reconhecido dentro do corpo da mensagem (ver renderMessageBody) —
   chip destacado, igual mencao do Discord; -me e mais forte (sou eu). Segue o
   acento escolhido pelo usuário. */
.mention-chip {
  background: color-mix(in srgb, var(--accent-base) 16%, transparent);
  color: var(--accent-strong);
  border-radius: 4px;
  padding: 0 2px;
  font-weight: 600;
}

.mention-chip.mention-chip-me {
  background: color-mix(in srgb, var(--mention) 22%, transparent);
  color: var(--mention);
}

.chat-message-toolbar {
  position: absolute;
  top: -14px;
  right: 8px;
  display: none;
  background: var(--panel-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-1);
  overflow: hidden;
}

.chat-message:hover .chat-message-toolbar,
.chat-message:focus-within .chat-message-toolbar {
  display: flex;
}

.chat-message-toolbar button {
  all: unset;
  cursor: pointer;
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  color: var(--muted);
}

.chat-message-toolbar button:hover {
  background: var(--panel-soft);
  color: var(--text);
}

.reply-preview {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 0.78rem;
  margin-bottom: 1px;
  min-width: 0;
  max-width: 100%;
  cursor: pointer;
  border-radius: 4px;
}

.reply-preview:hover,
.reply-preview:focus-visible {
  color: var(--text);
  text-decoration: underline;
}

/* Clicar num "responde a ..." rola até a mensagem original e pisca ela
   (mensagem inteira, foto/figurinha incluída, já que estão dentro do mesmo
   .chat-message) na cor de destaque do app — se ela já estiver visível, o
   scrollIntoView praticamente não move nada e só a piscada acontece. */
.message-flash {
  animation: message-flash-pulse 1s var(--ease-out);
}

@keyframes message-flash-pulse {
  0%, 100% { background: transparent; box-shadow: none; }
  25% { background: color-mix(in srgb, var(--accent-base) 22%, transparent); box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-base) 45%, transparent); }
}

.reply-preview b {
  color: var(--text);
  font-weight: 600;
  flex-shrink: 0;
}

.reply-preview .icon {
  flex-shrink: 0;
}

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

/* Divisor "novas mensagens" (estilo Discord): risquinho vermelho atravessando
   o log, com um selo "NOVO" colado na ponta direita da linha. */
.unread-divider {
  position: relative;
  display: flex;
  justify-content: flex-end;
  height: 0;
  margin: 4px 0;
  border-top: 1px solid var(--danger);
}

.unread-divider-label {
  transform: translateY(-50%);
  background: var(--danger);
  color: #fff;
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 1px 8px;
  border-radius: 0 0 6px 6px;
}

.message-reactions {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 2px;
}

.reaction-pill {
  all: unset;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 0.78rem;
  background: var(--panel-soft);
  border: 1px solid var(--border);
  color: var(--text);
}

.reaction-pill.reacted {
  background: color-mix(in srgb, var(--accent-strong) 22%, var(--panel-soft));
  border-color: var(--accent-strong);
}

.message-edited-marker {
  color: var(--muted);
  font-size: 0.7rem;
}

.emote-picker-tabs {
  margin-bottom: 8px;
}

.gif-picker-panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* Maior que os 300px de .emote-picker-grid (emoji/customizados) de
     propósito — GIF precisa de mais espaço vertical pra mostrar um número
     razoável de resultados por vez; trocar de aba muda a altura do picker
     inteiro nesse caso, aceito aqui. */
  height: 500px;
}

.gif-picker-grid {
  display: grid;
  /* 150px deixava so 2 colunas enormes no picker (que tem ~416px uteis) e o
     aspect-ratio:1 + object-fit:cover cortava boa parte de cada GIF, que
     raramente e quadrado. Celulas menores e mais "largas" (4/3) mostram mais
     GIFs por vez e cortam bem menos. */
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  align-content: flex-start;
  /* Sem align-items:start o stretch padrão briga com o aspect-ratio dos
     botões e a linha colapsa pra uma altura minúscula — os cards ficam com
     a largura certa e altura quase zero. */
  align-items: start;
  gap: 6px;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.gif-picker-grid button {
  all: unset;
  cursor: pointer;
  border-radius: 6px;
  overflow: hidden;
  background: var(--panel-soft);
  width: 100%;
  height: auto;
  /* Fallback só pra quando a Klipy não manda width/height (raro) — o normal
     é vir de um aspect-ratio inline com a proporção REAL do GIF (ver
     searchGifs em app.js), pra caixa nunca sobrar fundo/letterbox em volta
     da imagem. Cada botão vira do tamanho do próprio GIF, não um quadro
     fixo — linhas do grid ficam com altura desigual (a do item mais alto),
     e é isso mesmo: sem essa desigualdade GIFs verticais e horizontais
     nunca cabem os dois sem sobra de fundo. */
  aspect-ratio: 4 / 3;
}

.gif-picker-grid img {
  width: 100%;
  height: 100%;
  /* Com a caixa já na proporção real do GIF (aspect-ratio acima), contain e
     cover dão praticamente o mesmo resultado — contain fica de fallback
     seguro pro caso raro de a caixa cair no 4/3 genérico. */
  object-fit: contain;
  display: block;
}

.chat-gif {
  display: block;
  margin-top: 2px;
  /* Só max-width deixava um GIF muito vertical (comum na Klipy) esticar a
     altura da tela inteira — max-height junto faz o navegador escalar pelos
     dois lados mantendo a proporção. */
  max-width: min(420px, 80vw);
  max-height: 340px;
  /* Reserva espaço ENQUANTO carrega (a imagem chega assíncrona, sem width/
     height reservados o navegador não sabe o tamanho até o load terminar e
     a linha nasce colapsada, "explodindo" só depois — ver .is-loaded abaixo,
     que zera isso assim que a imagem carrega de verdade). */
  min-height: 120px;
  min-width: 160px;
  background: var(--panel-soft);
  width: auto;
  height: auto;
  border-radius: 8px;
  border: 1px solid var(--border);
}

.chat-gif.is-loaded {
  min-height: 0;
  min-width: 0;
  background: none;
}

.emoji-picker-category {
  flex-basis: 100%;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin: 4px 0 2px;
}

/* ---------- Visualizador de chamada ---------- */

.call-viewer-header {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}

.call-viewer-header h2 {
  margin: 0;
  font-size: 1.05rem;
}

/* Palco (featured, em cima) + carrossel dos demais participantes (embaixo,
   rola na horizontal em vez de quebrar linha) — ver renderCallViewer em
   voice.js. */
.call-viewer-title { margin: 0; padding: 0; border: 0; background: none; color: inherit; font: inherit; font-size: 1.05rem; }
.call-viewer-title:not(:disabled) { cursor: pointer; }
.call-viewer-title:not(:disabled):hover { text-decoration: underline; }

/* "← nome do canal" — só aparece em mobile (ver @media mais abaixo), no
   lugar do dot+título de cima. Sempre sublinhado (não só no hover): é a
   única pista, num botão sem ícone de menu, de que aquilo é clicável. */
.call-viewer-channel-link {
  display: none;
  align-items: center;
  gap: 2px;
  min-width: 0;
  margin: 0;
  padding: 6px 4px;
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  font-size: 1.05rem;
  text-decoration: underline;
  cursor: pointer;
}

.call-viewer-channel-link .icon {
  flex: none;
  width: 18px;
  height: 18px;
}

.call-viewer-channel-link span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Botão de ocultar/mostrar membros (carrossel, ver showParticipantToggle em
   buildViewerTile): some por padrão, desliza pro centro-baixo da transmissão
   ao passar o mouse — as bordas do tile escurecem levemente junto (ver
   .call-viewer-tile:hover mais abaixo). */
.call-viewer-tile-participants-toggle {
  position: absolute;
  left: 50%;
  bottom: 10px;
  z-index: 2;
  opacity: 0;
  transform: translate(-50%, 6px);
  transition: opacity var(--dur-fast), transform var(--dur-fast);
}
.call-viewer-tile-media:hover .call-viewer-tile-participants-toggle,
.call-viewer-tile-participants-toggle:focus-visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* Canto de baixo à direita do tile: Flutuar e zoom lado a lado (ver
   buildViewerTile em voice.js). O container não pega clique — só os botões. */
.call-viewer-tile-actions {
  position: absolute;
  right: 8px;
  bottom: 8px;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 6px;
  pointer-events: none;
}
.call-viewer-tile-actions > * { pointer-events: auto; }
/* Seletor de zoom da transmissão. Fica no próprio tile: alterar o valor
   escala apenas o vídeo, nunca o palco. */
.call-viewer-tile-zoom {
  min-width: 58px;
  height: 28px;
  padding: 2px 5px;
  border: 1px solid color-mix(in srgb, var(--border) 72%, white);
  border-radius: 6px;
  background: rgba(20, 21, 24, 0.9);
  color: var(--text);
  font: inherit;
  font-size: 0.75rem;
  cursor: pointer;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity var(--dur-fast), transform var(--dur-fast);
}
.call-viewer-tile-media:hover .call-viewer-tile-zoom,
.call-viewer-tile-zoom:focus-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Reações na transmissão (ver buildReactionControls em voice.js): carinha no
   canto de cima à direita — os de baixo já são do nome, zoom e flutuar — e a
   fileira de emojis logo abaixo dela. Mesmo revelar no hover dos outros. */
.call-viewer-tile-react {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 3;
  width: 28px;
  height: 28px;
  background: rgba(20, 21, 24, 0.9);
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity var(--dur-fast), transform var(--dur-fast);
}
.call-viewer-tile-media:hover .call-viewer-tile-react,
.call-viewer-tile-react:focus-visible,
.call-viewer-tile-react.is-active {
  opacity: 1;
  transform: translateY(0);
}
#callViewerFeatured.is-free .call-viewer-tile-react { top: 26px; right: 26px; }
.call-viewer-tile-reaction-bar {
  position: absolute;
  top: 42px;
  right: 8px;
  z-index: 3;
  display: flex;
  gap: 2px;
  padding: 4px;
  border: 1px solid color-mix(in srgb, var(--border) 72%, white);
  border-radius: 999px;
  background: rgba(20, 21, 24, 0.94);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
}
.call-viewer-tile-reaction-bar[hidden] { display: none; }
#callViewerFeatured.is-free .call-viewer-tile-reaction-bar { top: 60px; right: 26px; }
.call-viewer-tile-reaction-bar button {
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  transition: transform var(--dur-fast), background var(--dur-fast);
}
.call-viewer-tile-reaction-bar button:hover,
.call-viewer-tile-reaction-bar button:focus-visible {
  background: rgba(255, 255, 255, 0.12);
  transform: scale(1.18);
}
/* Camada por cima do visualizador inteiro onde os emojis sobem; não pega
   clique (o vídeo embaixo continua arrastável/clicável). */
.call-viewer-reactions {
  position: absolute;
  inset: 0;
  z-index: 6;
  overflow: hidden;
  pointer-events: none;
}
.call-viewer-reaction {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  will-change: transform, opacity;
}
.call-viewer-reaction-emoji {
  font-size: var(--reaction-size, 36px);
  line-height: 1;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.45));
}
.call-viewer-reaction-name {
  max-width: 120px;
  padding: 1px 6px;
  overflow: hidden;
  border-radius: 999px;
  background: rgba(20, 21, 24, 0.78);
  color: #fff;
  font-size: 0.68rem;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Flutuar (PiP nativo): mesmo comportamento da lupa, à esquerda dela. */
.call-viewer-tile-float {
  width: 28px;
  height: 28px;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity var(--dur-fast), transform var(--dur-fast);
}
.call-viewer-tile-media:hover .call-viewer-tile-float,
.call-viewer-tile-float:focus-visible,
.call-viewer-tile-float.is-active {
  opacity: 1;
  transform: translateY(0);
}
/* Enquanto o vídeo está na janelinha do navegador o tile fica com o quadro
   parado (o mesmo <video> não pode estar nos dois lugares) — deixa claro. */
.call-viewer-tile.is-floating .call-viewer-tile-media::after {
  content: "Flutuando";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 12px;
  color: var(--muted);
  background: color-mix(in srgb, var(--bg-0) 70%, transparent);
  pointer-events: none;
}

.call-viewer-grid {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 10px;
  overflow: hidden;
}

/* Documento da janela Document Picture-in-Picture destacada (toggleDetachedViewer
   em voice.js): nasce vazio, recebe o #callViewerGrid movido e as folhas de
   estilo copiadas. */
.call-viewer-pip-document {
  margin: 0;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  background: #0e0f13;
}
.call-viewer-pip-document .call-viewer-grid {
  width: 100%;
  height: 100%;
}

/* O #callViewer INTEIRO agora se muda pra janela destacada (cabeçalho +
   palco + rodapé, ver toggleDetachedViewer em voice.js) — mesma cara e
   funcionamento da tela cheia, só ocupando 100% da janela própria em vez
   de position:fixed sobre a janela principal (.is-fullscreen). */
.call-viewer.is-detached {
  width: 100%;
  height: 100%;
  max-height: none;
  border: none;
  border-radius: 0;
  padding: 0;
}

/* Janela destacada: só o vídeo — sem cabeçalho, sem carrossel de participantes
   e sem os botões de mic/compartilhar/sair (eles já existem na janela
   principal; aqui só atrapalhariam quem só quer ver a tela). O único controle
   que sobra é o de voltar à janela principal (o botão que era de tela cheia —
   ver syncFullscreenButton em voice.js), e some por padrão: só aparece ao
   passar o mouse. */
.call-viewer.is-detached .call-viewer-header,
.call-viewer.is-detached .call-viewer-filmstrip,
.call-viewer.is-detached .call-viewer-footer-actions {
  display: none;
}

.call-viewer.is-detached .call-viewer-footer {
  position: absolute;
  inset: 0;
  padding: 0;
  pointer-events: none;
  z-index: 8;
}

.call-viewer.is-detached .call-viewer-footer-fullscreen {
  position: absolute;
  top: auto;
  right: 10px;
  bottom: 10px;
  transform: none;
  pointer-events: auto;
  opacity: 0;
  transition: opacity 0.15s ease;
}

.call-viewer.is-detached:hover .call-viewer-footer-fullscreen {
  opacity: 1;
}

/* Aviso rápido ao maximizar a janela destacada (vira tela cheia de verdade —
   ver main.js — então o [hidden] nativo do frame some junto; sem isso não
   sobra nenhuma pista de como sair). JS liga/desliga .is-visible pra animar
   (ver syncFullscreenButton/showFullscreenHint em voice.js); some sozinho
   depois de alguns segundos, ou na hora se a pessoa passar o mouse (mesmo
   hover que já revela o botão "<"). */
.call-viewer-fullscreen-hint {
  position: absolute;
  top: 18px;
  left: 50%;
  z-index: 9;
  padding: 8px 16px;
  border-radius: 999px;
  background: rgba(20, 20, 22, 0.85);
  color: #fff;
  font-size: 0.82rem;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -8px);
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.call-viewer-fullscreen-hint kbd {
  padding: 1px 6px;
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.16);
  font: inherit;
}

.call-viewer-fullscreen-hint.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

.call-viewer:not(.is-detached) .call-viewer-fullscreen-hint {
  display: none;
}

/* Mais de uma tela no palco ao mesmo tempo (clicar numa do carrossel soma,
   não substitui — ver buildViewerTile em voice.js): quebra linha, com espaço
   entre elas, e reparte o espaço em vez de espremer tudo numa única linha. */
.call-viewer-featured {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-wrap: wrap;
  align-content: center;
  justify-content: center;
  gap: 10px;
  /* Sem isso, um tile cujo aspect-ratio pede mais altura do que sobra aqui
     (câmera/tela + carrossel disputando o mesmo espaço) vazava por cima do
     carrossel embaixo em vez de encolher — nada deve flutuar sobre nada. */
  overflow: hidden;
  /* Habilita cqw/cqh abaixo: o tile precisa saber o tamanho REAL desta área
     (que muda ao ocultar/mostrar o carrossel ou entrar em tela cheia) nos
     dois eixos pra se ajustar sem nunca esticar além da proporção real. */
  container-type: size;
  container-name: call-viewer-stage;
}

/* Ninguém compartilhando tela agora: sem palco vazio ocupando espaço à toa —
   quem está na call aparece só no carrossel embaixo. */
.call-viewer-featured:empty {
  display: none;
}

.call-viewer-filmstrip {
  flex: none;
  display: flex;
  justify-content: center;
  gap: 10px;
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 2px;
}

.call-viewer-filmstrip:empty {
  display: none;
}

/* Toggle oculta o carrossel (callViewerToggleFilmstrip) — o palco fica com
   toda a altura disponível pra ele. */
.call-viewer-grid.filmstrip-hidden .call-viewer-filmstrip {
  display: none;
}

.call-viewer-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  background: #111214;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
  aspect-ratio: 16 / 10;
}

.call-viewer-featured .call-viewer-tile {
  /* Antes: flex-grow esticava a LARGURA pra preencher a linha e a altura só
     vinha depois via aspect-ratio, travada num max-height:100% — então
     mostrar/ocultar o carrossel (que só muda a altura disponível aqui, nunca
     a largura) fazia a transmissão "comer" altura sem nunca reajustar a
     largura, distorcendo/estourando o card ao trocar de estado (e o mesmo
     valia pra tela cheia, que também só ganha altura). Trocado por um
     tamanho calculado a partir da área real dos dois eixos (cqw/cqh, ver
     container-type acima): o tile cresce até o limite que a proporção 16:9
     permite SEM nunca esticar além do que cabe em largura OU altura —
     redimensionar por qualquer eixo (ocultar membros, tela cheia, janela
     menor) sempre re-encaixa proporcional, nunca distorce nem vaza. */
  flex: none;
  width: min(100%, calc(100cqh * 16 / 9));
  height: min(100%, calc(100cqw * 9 / 16));
  aspect-ratio: 16 / 9;
  border-color: transparent;
}

/* 2+ transmissões no palco ao mesmo tempo, sem nenhuma em destaque (clicar
   uma promove só ela — sai daqui, ver spotlightIdentity/buildViewerTile em
   voice.js): volta pra uma grade com sugestões de layout por quantidade —
   mesmo padrão de :has()+nth-last-child já usado no voice-only acima, só
   que aqui as células são retangulares (o vídeo real letterboxa dentro via
   object-fit:contain no <video>, ver .call-viewer-tile-media video), não
   quadradas feito o card de voz. :not(.voice-only) no ancestral é o que
   evita a colisão com as regras de voz abaixo — sem isso, especificidade
   empatava com elas (mesma soma de classes/pseudo-classes) e QUEM VENCIA
   dependia só de quem vinha depois no arquivo por propriedade, resultado:
   os cards de voz saíam com altura/tamanho bugados (esticados/desiguais)
   porque um "height:100%" pensado pra vídeo vazava pro card de voz junto. */
.call-viewer-grid:not(.voice-only) .call-viewer-featured:has(.call-viewer-tile:nth-child(2)) {
  display: grid;
  gap: 10px;
  align-content: stretch;
  justify-content: stretch;
}
.call-viewer-grid:not(.voice-only) .call-viewer-featured:has(.call-viewer-tile:nth-child(2)) .call-viewer-tile {
  width: 100%;
  height: 100%;
  min-height: 0;
  max-height: none;
  justify-content: center;
}

/* 2/3/4: forma decidida em JS (data-shape, ver applyFeaturedShape em
   voice.js) em vez de :has()+nth-last-child — inserir as divisórias
   arrastáveis como filhos de verdade do palco corrompe essa contagem (elas
   também são filhas de .call-viewer-featured). As variáveis --split-*
   (com fallback pro valor de sempre) deixam o layout padrão idêntico ao
   anterior enquanto ninguém arrasta nada; só mudam depois que o usuário
   arrasta uma divisória (ver setupStageSplitter). */

/* 2 · lado a lado (uma maior, uma menor) — 1 divisória vertical. */
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="2"][data-preset="cols"] {
  grid-template-columns: var(--split-col-a, 2fr) 6px var(--split-col-b, 1fr);
  grid-template-rows: 1fr;
}
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="2"][data-preset="cols"] .call-viewer-tile:nth-child(1) { grid-column: 1; }
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="2"][data-preset="cols"] .call-viewer-tile:nth-child(2) { grid-column: 3; }

/* 2 · um sobre o outro — 1 divisória horizontal. */
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="2"][data-preset="rows"] {
  grid-template-columns: 1fr;
  grid-template-rows: var(--split-row-a, 2fr) 6px var(--split-row-b, 1fr);
}
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="2"][data-preset="rows"] .call-viewer-tile:nth-child(1) { grid-row: 1; }
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="2"][data-preset="rows"] .call-viewer-tile:nth-child(2) { grid-row: 3; }

/* 3 · grande à esquerda (ocupa as duas linhas) + duas pequenas empilhadas —
   divisória vertical (grande vs. pilha) + horizontal (entre as pequenas). */
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="3"][data-preset="big-left"] {
  grid-template-columns: var(--split-col-a, 2fr) 6px var(--split-col-b, 1fr);
  grid-template-rows: var(--split-row-a, 1fr) 6px var(--split-row-b, 1fr);
}
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="3"][data-preset="big-left"] .call-viewer-tile:nth-child(1) { grid-column: 1; grid-row: 1 / 4; }
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="3"][data-preset="big-left"] .call-viewer-tile:nth-child(2) { grid-column: 3; grid-row: 1; }
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="3"][data-preset="big-left"] .call-viewer-tile:nth-child(3) { grid-column: 3; grid-row: 3; }
.call-viewer-featured[data-shape="3"][data-preset="big-left"] .call-viewer-splitter-col { grid-column: 2; grid-row: 1 / -1; }
.call-viewer-featured[data-shape="3"][data-preset="big-left"] .call-viewer-splitter-row { grid-column: 3; grid-row: 2; }

/* 3 · grande em cima (ocupa as duas colunas) + duas pequenas embaixo —
   divisória horizontal (grande vs. fila) + vertical (entre as pequenas). */
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="3"][data-preset="big-top"] {
  grid-template-columns: var(--split-col-a, 1fr) 6px var(--split-col-b, 1fr);
  grid-template-rows: var(--split-row-a, 2fr) 6px var(--split-row-b, 1fr);
}
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="3"][data-preset="big-top"] .call-viewer-tile:nth-child(1) { grid-column: 1 / 4; grid-row: 1; }
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="3"][data-preset="big-top"] .call-viewer-tile:nth-child(2) { grid-column: 1; grid-row: 3; }
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="3"][data-preset="big-top"] .call-viewer-tile:nth-child(3) { grid-column: 3; grid-row: 3; }
.call-viewer-featured[data-shape="3"][data-preset="big-top"] .call-viewer-splitter-row { grid-column: 1 / -1; grid-row: 2; }
.call-viewer-featured[data-shape="3"][data-preset="big-top"] .call-viewer-splitter-col { grid-column: 2; grid-row: 3; }

/* 3 · três colunas iguais — sem divisória. */
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="3"][data-preset="cols"] {
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: 1fr;
}

/* 4 · 2x2 — divisória vertical + horizontal. */
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="4"][data-preset="grid"] {
  grid-template-columns: var(--split-col-a, 1fr) 6px var(--split-col-b, 1fr);
  grid-template-rows: var(--split-row-a, 1fr) 6px var(--split-row-b, 1fr);
}
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="4"][data-preset="grid"] .call-viewer-tile:nth-child(1) { grid-column: 1; grid-row: 1; }
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="4"][data-preset="grid"] .call-viewer-tile:nth-child(2) { grid-column: 3; grid-row: 1; }
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="4"][data-preset="grid"] .call-viewer-tile:nth-child(3) { grid-column: 1; grid-row: 3; }
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="4"][data-preset="grid"] .call-viewer-tile:nth-child(4) { grid-column: 3; grid-row: 3; }
.call-viewer-featured[data-shape="4"][data-preset="grid"] .call-viewer-splitter-col { grid-column: 2; grid-row: 1 / -1; }
.call-viewer-featured[data-shape="4"][data-preset="grid"] .call-viewer-splitter-row { grid-column: 1 / -1; grid-row: 2; }

/* 4 · grande à esquerda (três linhas) + três pequenas empilhadas — só a
   divisória vertical. */
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="4"][data-preset="big-left"] {
  grid-template-columns: var(--split-col-a, 2fr) 6px var(--split-col-b, 1fr);
  grid-template-rows: repeat(3, 1fr);
}
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="4"][data-preset="big-left"] .call-viewer-tile:nth-child(1) { grid-column: 1; grid-row: 1 / 4; }
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="4"][data-preset="big-left"] .call-viewer-tile:nth-child(2) { grid-column: 3; grid-row: 1; }
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="4"][data-preset="big-left"] .call-viewer-tile:nth-child(3) { grid-column: 3; grid-row: 2; }
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="4"][data-preset="big-left"] .call-viewer-tile:nth-child(4) { grid-column: 3; grid-row: 3; }
.call-viewer-featured[data-shape="4"][data-preset="big-left"] .call-viewer-splitter-col { grid-column: 2; grid-row: 1 / -1; }

/* 5+: grade automática, sem tamanho especial por contagem — ajusta sozinha
   conforme mais gente compartilha, igual o auto-fit que o resto do app já usa.
   Sem divisórias (buildStageSplitters só roda pra 2/3/4, ver voice.js). */
.call-viewer-grid:not(.voice-only) .call-viewer-featured[data-shape="5plus"] {
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  grid-auto-rows: 1fr;
}

/* Divisória arrastável entre painéis do palco — só existe (como elemento) no
   dock embutido/tela cheia; PiP flutuante e janela destacada herdam a
   proporção salva via as mesmas variáveis --split-* acima, sem alça própria
   (ver applyFeaturedShape em voice.js). */
.call-viewer-splitter {
  position: relative;
  background: transparent;
  border-radius: 3px;
  transition: background 0.1s ease;
}
.call-viewer-splitter:hover,
.call-viewer-splitter.dragging {
  background: var(--accent, #5865f2);
}
/* A célula de cada divisória depende da forma — ver as regras por
   [data-preset] acima. */
.call-viewer-splitter-col { cursor: col-resize; }
.call-viewer-splitter-row { cursor: row-resize; }
.call-viewer-featured[data-shape="2"] .call-viewer-splitter-col { grid-column: 2; grid-row: 1; }
.call-viewer-featured[data-shape="2"] .call-viewer-splitter-row { grid-column: 1; grid-row: 2; }

/* Modo livre (ver applyFreeLayout em voice.js): o palco vira uma área
   posicionada e cada tile uma janelinha absoluta (left/top/width/height em
   % vêm inline). O id é o que garante vencer as regras de grade acima (que
   somam :has()/:not() na especificidade) sem !important — #callViewerFeatured
   é o mesmo nó mesmo quando o viewer é movido pra janela destacada. */
#callViewerFeatured.is-free {
  display: block;
  position: relative;
}
#callViewerFeatured.is-free > .call-viewer-tile {
  width: auto;
  height: auto;
  min-height: 0;
  max-height: none;
  aspect-ratio: auto;
  box-sizing: border-box;
  border-color: var(--border);
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}
/* A janelinha pode ter qualquer proporção: a mídia preenche o tile todo e
   o <video> (object-fit: contain) letterboxa por conta própria. */
#callViewerFeatured.is-free > .call-viewer-tile > .call-viewer-tile-media {
  flex: 1 1 auto;
  height: 100%;
  aspect-ratio: auto;
}
#callViewerFeatured.is-free > .call-viewer-tile.is-free-tile {
  cursor: grab;
  user-select: none;
  touch-action: none;
}
#callViewerFeatured.is-free > .call-viewer-tile.is-dragging {
  cursor: grabbing;
  opacity: 0.92;
}
#callViewerFeatured.is-free > .call-viewer-tile.is-dragging,
#callViewerFeatured.is-free > .call-viewer-tile.is-resizing {
  border-color: var(--accent, #5865f2);
}
/* Alça de redimensionar no canto; os botões de lupa/flutuar andam pra
   esquerda pra não ficar embaixo dela. */
.call-viewer-tile-resize {
  position: absolute;
  z-index: 5;
  touch-action: none;
}
.call-viewer-tile-resize[data-edge="n"], .call-viewer-tile-resize[data-edge="s"] { left: 16px; right: 16px; height: 8px; cursor: ns-resize; }
.call-viewer-tile-resize[data-edge="e"], .call-viewer-tile-resize[data-edge="w"] { top: 16px; bottom: 16px; width: 8px; cursor: ew-resize; }
.call-viewer-tile-resize[data-edge*="n"] { top: 0; }
.call-viewer-tile-resize[data-edge*="s"] { bottom: 0; }
.call-viewer-tile-resize[data-edge*="e"] { right: 0; }
.call-viewer-tile-resize[data-edge*="w"] { left: 0; }
.call-viewer-tile-resize[data-edge="ne"], .call-viewer-tile-resize[data-edge="sw"] { width: 16px; height: 16px; cursor: nesw-resize; }
.call-viewer-tile-resize[data-edge="nw"], .call-viewer-tile-resize[data-edge="se"] { width: 16px; height: 16px; cursor: nwse-resize; }
.call-viewer-tile-resize[data-edge="se"] { background: linear-gradient(135deg, transparent 50%, var(--accent, #5865f2) 50%); border-bottom-right-radius: var(--radius-md); }
#callViewerFeatured.is-free .call-viewer-tile-actions { right: 26px; }

/* Itens do menu "Layout do palco" com marca de escolhido. */
.context-menu-heading {
  padding: 6px 12px 2px;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.context-menu-item-check {
  padding-left: 28px;
  position: relative;
}
.context-menu-item-check.is-checked::before {
  content: "✓";
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  font-weight: 700;
}

/* Uma chamada só de voz: grade que centraliza e cresce os cards conforme
   menos gente está na call (1 sozinho grande / 2 lado a lado / 3 com o
   terceiro centralizado embaixo dos outros dois / 4 em 2x2) — os seletores
   :has() abaixo contam quantos tiles existem (Chromium recente, cobre este
   app). 5+ cai no grid padrão (auto-fit), sem tamanho especial por contagem. */
.call-viewer-grid.voice-only .call-viewer-featured {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 340px));
  justify-content: center;
  align-content: center;
  /* Sem isto os itens de grid esticam (stretch é o default de align-items)
     pra preencher a linha auto-sized inteira — como só existe 1 linha aqui,
     ela cresce até ocupar toda a altura livre de .call-viewer-featured, e o
     card de voz (aspect-ratio 16/10) ficava do tamanho do palco inteiro em
     vez do próprio min-height/aspect-ratio abaixo. Com align-items:center o
     tile fica no tamanho "padrão" de novo e só muda mesmo pela contagem
     (regras :has() abaixo). */
  align-items: center;
}

.call-viewer-grid.voice-only .call-viewer-featured .call-viewer-tile {
  width: 100%;
  min-height: 180px;
  aspect-ratio: 16 / 10;
}

.call-viewer-grid.voice-only .call-viewer-featured:has(.call-viewer-tile:only-child) {
  grid-template-columns: minmax(0, 560px);
}
.call-viewer-grid.voice-only .call-viewer-featured:has(.call-viewer-tile:only-child) .call-viewer-tile {
  min-height: 320px;
}

.call-viewer-grid.voice-only .call-viewer-featured:has(.call-viewer-tile:first-child:nth-last-child(2)) {
  grid-template-columns: repeat(2, minmax(0, 420px));
}
.call-viewer-grid.voice-only .call-viewer-featured:has(.call-viewer-tile:first-child:nth-last-child(2)) .call-viewer-tile {
  min-height: 260px;
}

.call-viewer-grid.voice-only .call-viewer-featured:has(.call-viewer-tile:first-child:nth-last-child(3)) {
  grid-template-columns: repeat(2, minmax(0, 340px));
}
.call-viewer-grid.voice-only .call-viewer-featured:has(.call-viewer-tile:first-child:nth-last-child(3)) .call-viewer-tile {
  min-height: 200px;
}
/* O terceiro (sem par na linha de baixo) ocupa as duas colunas e fica
   centralizado, em vez de esticar sozinho na largura toda. */
.call-viewer-grid.voice-only .call-viewer-featured:has(.call-viewer-tile:first-child:nth-last-child(3)) .call-viewer-tile:last-child {
  grid-column: 1 / -1;
  justify-self: center;
  width: 340px;
  max-width: 100%;
}

.call-viewer-grid.voice-only .call-viewer-featured:has(.call-viewer-tile:first-child:nth-last-child(4)) {
  grid-template-columns: repeat(2, minmax(0, 340px));
}
.call-viewer-grid.voice-only .call-viewer-featured:has(.call-viewer-tile:first-child:nth-last-child(4)) .call-viewer-tile {
  min-height: 200px;
}

.call-viewer-filmstrip .call-viewer-tile {
  flex: 0 0 168px;
  width: 168px;
}

/* Anel no próprio contêiner de mídia (vídeo OU avatar de fallback, os dois
   são .call-viewer-tile-media) — inset, não "pra fora": .call-viewer-tile
   tem overflow:hidden pra recortar os cantos arredondados, e um box-shadow
   normal desenhado pra fora da caixa do FILHO ficava cortado por isso (por
   isso o efeito não aparecia no viewer, só na sidebar — lá o anel é direto
   no elemento com .speaking, sem pai com overflow no meio). inset nunca é
   afetado por overflow do ancestral. */
.call-viewer-tile.speaking .call-viewer-tile-media {
  box-shadow: inset 0 0 0 3px #23a55a;
}
.call-viewer-tile.playing-sound .call-viewer-tile-media {
  box-shadow: inset 0 0 0 3px #22d3ee;
}

.call-viewer-tile-media {
  flex: 0 0 auto;
  width: 100%;
  aspect-ratio: 16 / 9;
  height: auto;
  min-height: 0;
  border-radius: inherit;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(circle at 50% 50%, color-mix(in srgb, var(--tile-avatar-color, var(--accent-base)) 61%, transparent), transparent 79%),
    #111214;
  cursor: pointer;
}

/* Vinheta bem sutil nas bordas ao passar o mouse — mesmo gatilho que revela
   o botão de ocultar/mostrar membros acima, dá o contraste pro botão sem
   escurecer o meio da transmissão. */
.call-viewer-tile-media:hover {
  box-shadow: inset 0 0 36px 6px rgba(0, 0, 0, 0.25);
}

.call-viewer-tile-media video {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  /* translate antes do scale: o deslocamento é em % do próprio <video> sem
     escala, limitado em voice.js (clampVideoPan) pra nunca mostrar a borda. */
  transform: translate(var(--viewer-video-pan-x, 0), var(--viewer-video-pan-y, 0)) scale(var(--viewer-video-zoom, 1));
  transform-origin: center;
  transition: transform var(--dur-fast);
  /* O <video> em si não pinta nada na sobra do letterbox/pillarbox (quando a
     proporção da transmissão não é 16:9 - uma janela mais "quadrada" que
     isso, por exemplo) - sem isso, o gradiente de acento do avatar em
     .call-viewer-tile-media (pensado só pro fallback sem vídeo) aparecia
     por trás, como uma "faixa verde/da cor de destaque" nas bordas do
     vídeo. Preto sólido aqui garante letterbox preto sempre, independente
     da cor de acento escolhida. */
  background: #000;
}

/* Transmissão ampliada: arrastar move o recorte (ver makeVideoPannable).
   touch-action none pra o dedo no celular arrastar o vídeo, não rolar a
   página; durante o arrasto a transição sai, senão o vídeo "persegue" o
   ponteiro com atraso. */
.call-viewer-tile-media.is-pannable { cursor: grab; touch-action: none; }
.call-viewer-tile-media.is-panning { cursor: grabbing; }
.call-viewer-tile-media.is-panning video { transition: none; }

.call-viewer-tile-avatar {
  width: 72px;
  height: 72px;
  font-size: 1.6rem;
}

/* Mesmo anel (ver .call-viewer-tile.speaking .call-viewer-tile-media acima),
   agora só pra sidebar: classe direto no avatar da lista de quem está na
   call (ver buildChannelListItem em app.js), sem pai com overflow no meio,
   então não precisa de inset aqui. Verde = falando (LiveKit isSpeaking);
   ciano = efeito sonoro do soundboard tocando agora — as duas nunca ficam
   ativas juntas na prática (fontes de áudio diferentes), cada uma some
   sozinha quando para. */
.avatar-thumb.speaking,
.avatar-fallback.speaking {
  box-shadow: 0 0 0 3px #23a55a;
}
.avatar-thumb.playing-sound,
.avatar-fallback.playing-sound {
  box-shadow: 0 0 0 3px #22d3ee;
}

/* Tile de transmissão de tela pausada (toggleWatchingParticipant) — sem
   avatar, é só a transmissão; ver buildViewerTile. */
.call-viewer-tile-paused {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 0.8rem;
}

.call-viewer-tile-paused .icon {
  width: 28px;
  height: 28px;
}

/* Some/revela no hover, mesmo gatilho dos outros controles do tile (toggle
   de membros, lupa) — cobre o quadrado inteiro (inset:0 relativo ao
   .call-viewer-tile, que é quem tem position:relative) com um véu escuro e
   o botão de retomar centralizado por cima. */
.call-viewer-tile-resume-overlay {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.3);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-fast);
}
.call-viewer-tile-media:hover .call-viewer-tile-resume-overlay {
  opacity: 1;
  pointer-events: auto;
}
.call-viewer-tile-resume-button {
  background: #fff;
  color: #111;
  border: none;
  border-radius: var(--radius-pill);
  padding: 8px 20px;
  font-weight: 600;
  font-size: var(--text-sm);
  cursor: pointer;
}
.call-viewer-tile-resume-button:hover {
  background: #e5e5e5;
}

.call-viewer-tile-source {
  position: absolute;
  top: 8px;
  left: 8px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 0.68rem;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* Identidade sobre o vídeo, canto inferior esquerdo: um único quadrado cinza
   chumbo compartilhado pelo ícone de microfone mutado (sempre visível quando
   mutado) e pelo nome (só entra no quadrado com o mouse em cima do card).
   Volume, parar de assistir e expulsar foram pro menu de contexto (botão
   direito no card). */
.call-viewer-tile-id {
  position: absolute;
  left: 8px;
  bottom: 8px;
  display: flex;
  align-items: center;
  gap: 6px;
  max-width: calc(100% - 16px);
  padding: 5px 8px;
  border-radius: 6px;
  background: rgba(43, 45, 50, 0.78);
  color: #e3e5e8;
  opacity: 0;
  transition: opacity var(--dur-fast, 0.12s);
  pointer-events: none;
}

.call-viewer-tile:hover .call-viewer-tile-id,
.call-viewer-tile-id:has(.call-viewer-tile-muted) {
  opacity: 1;
}

.call-viewer-tile-muted {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
}

.call-viewer-tile-muted .icon {
  width: 16px;
  height: 16px;
}

/* O nome mora no mesmo quadrado, mas só aparece com o hover — sozinho quando
   a pessoa está falando, colado à direita do microfone quando está mutada. */
.call-viewer-tile-name {
  display: none;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.8rem;
  font-weight: 600;
  color: #fff;
}

.call-viewer-tile:hover .call-viewer-tile-name {
  display: block;
}

/* Linha de volume do menu de contexto do card (botão direito). */
.context-menu-volume {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 10px;
  font-size: 0.82rem;
  font-weight: 500;
}

.context-menu-volume .call-viewer-tile-volume {
  width: 120px;
  flex: 1;
}

/* O palco da chamada ocupa o conteúdo principal no canal de sessão. Ao navegar
   para texto, o mesmo nó vira PiP: as tracks continuam anexadas e reproduzindo. */
.call-viewer-dock {
  display: contents;
}

.main-panel.showing-call > .chat-log,
.main-panel.showing-call > .emote-picker,
.main-panel.showing-call > .chat-attachment-preview,
.main-panel.showing-call > .chat-form {
  display: none !important;
}

.call-viewer {
  --viewer-height: 62vh;
  position: relative;
  min-height: 260px;
  height: var(--viewer-height);
  max-height: calc(100vh - 54px);
  max-width: 100%;
  padding: 14px;
  display: flex;
  flex-direction: column;
  /* Sem overflow:hidden aqui: cortava a dica (tooltip) dos botões do
     cabeçalho (ex.: "Abrir em outra janela"). Cada tile já se arredonda/
     recorta sozinho (.call-viewer-tile), não depende disso. */
  background: #000;
  border-bottom: 1px solid var(--border);
  border-radius: 15px;
}

.main-panel.showing-call .call-viewer {
  flex: 1;
  height: auto;
  max-height: none;
}

.call-viewer .call-viewer-header {
  flex: none;
  margin-bottom: 10px;
}

.call-viewer .call-viewer-header h2 {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.call-viewer .call-viewer-grid {
  flex: 1;
  min-height: 0;
}

.call-viewer[data-mode="pip"] {
  position: fixed;
  z-index: 50;
  right: 22px;
  bottom: 22px;
  width: min(420px, calc(100vw - 32px));
  height: min(310px, calc(100vh - 32px));
  min-width: 240px;
  min-height: 160px;
  max-width: calc(100vw - 32px);
  max-height: calc(100vh - 32px);
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: 0 18px 55px rgba(0, 0, 0, 0.5);
}

/* PiP só tem espaço pro palco — o carrossel dos demais participantes some
   inteiro (o rodapé próprio do PiP já resume quem mais está na call). */
.call-viewer[data-mode="pip"] .call-viewer-filmstrip {
  display: none;
}

/* Só o vídeo, sem moldura/rodapé do tile próprio — quem compartilha e a
   contagem de espectadores já vêm da barra em .call-viewer-pip-footer. */
.call-viewer[data-mode="pip"] .call-viewer-tile.featured {
  aspect-ratio: auto;
  height: 100%;
  border: none;
  border-radius: 0;
}

/* PiP é uma janelinha pequena — mesmo assistindo duas telas na chamada
   embutida, o PiP só tem espaço pra uma; as outras aparecem de volta ao
   reabrir o modo embutido. */
.call-viewer[data-mode="pip"] .call-viewer-featured .call-viewer-tile:not(:first-child) {
  display: none;
}

.call-viewer[data-mode="pip"] .call-viewer-tile-id,
.call-viewer[data-mode="pip"] .call-viewer-tile-source {
  display: none;
}

.call-viewer[data-mode="pip"] .call-viewer-grid {
  padding: 0;
  gap: 0;
}

/* Só o vídeo recorta pros cantos arredondados — não o .call-viewer inteiro
   (que perdeu o overflow:hidden pra não cortar o tooltip do rodapé). O
   palco (.call-viewer-featured) é quem recorta agora — ele que envolve o
   tile de verdade, o -grid é só a coluna por fora dele. */
.call-viewer[data-mode="pip"] .call-viewer-featured {
  overflow: hidden;
  border-radius: inherit;
}

/* PiP: sem cabeçalho (nome do canal, contador, destacar) — só o vídeo e essa
   barra embaixo, com quem compartilha e um olho com quantos estão vendo. */
.call-viewer[data-mode="pip"] .call-viewer-header {
  display: none;
}

.call-viewer-pip-footer {
  display: none;
}

/* Barra de controles embaixo do visualizador quando alguém está transmitindo:
   três botões centralizados (mic, compartilhar, sair) + tela cheia à esquerda.
   Só aparece no modo embutido; PiP e janela destacada têm suas próprias barras.
   Fica no fluxo normal (não é overlay): o palco/carrossel acima (flex:1 em
   .call-viewer .call-viewer-grid) só ocupa o espaço que sobra, então nada
   fica por baixo da barra. */
.call-viewer-footer {
  position: relative;
  flex: none;
  display: flex;
  align-items: center;
  padding: 10px 12px;
}

.call-viewer-footer[hidden],
.call-viewer[data-mode="pip"] .call-viewer-footer {
  display: none;
}

.call-viewer-footer-actions {
  display: flex;
  align-items: center;
  gap: 15px;
  height: auto;
  padding: 10px;
  margin-inline: auto;
  background: linear-gradient(180deg, #282828 0%, #202020 100%);
  border-radius: 14px;
}

.call-viewer-footer-actions .btn-icon {
  width: 70px;
  height: 32px;
  border-radius: 10px;
}

/* Tela cheia fora do fluxo flex (position:absolute) — do jeito que estava,
   como segundo item flex ao lado de .call-viewer-footer-actions, ele disputava
   espaço com o grupo central e o margin-inline:auto do grupo não centralizava
   de verdade em relação ao rodapé inteiro. Fixo à direita, sem empurrar nada. */
.call-viewer-footer-fullscreen {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
}

.call-viewer:fullscreen {
  border-bottom: none;
  max-height: none;
  height: 100%;
  border-radius: 0;
}

/* Fallback quando a Fullscreen API é rejeitada: cobre a janela toda por cima
   de tudo, como se fosse fullscreen nativo. */
.call-viewer.is-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 70;
  height: 100%;
  max-height: none;
  border-bottom: none;
  border-radius: 0;
}

.call-viewer[data-mode="pip"] .call-viewer-pip-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 6px 10px;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.75), transparent);
  color: #fff;
  font-size: 0.78rem;
  /* Acima da camada de arrastar (z-index:4) — senão o hover/tooltip de quem
     está vendo não recebe o ponteiro do mouse, e o próprio .call-viewer não
     tem mais overflow:hidden, então a dica (tooltip) não é mais cortada. */
  z-index: 7;
}

/* Arrastar (barra no topo) e redimensionar (só bordas, nunca canto) o PiP —
   nada de "resize" nativo aqui, ele só faz os dois juntos pelo canto. */
.call-viewer-pip-drag,
.call-viewer-pip-resize {
  display: none;
}

/* Arrasta de qualquer ponto do vídeo (mãozinha) — só o cantinho de
   redimensionar (abaixo) fica de fora, por ficar por cima dele em z-index. */
.call-viewer[data-mode="pip"] .call-viewer-pip-drag {
  display: block;
  position: absolute;
  inset: 0;
  cursor: grab;
  z-index: 4;
}

.call-viewer[data-mode="pip"] .call-viewer-pip-drag:active {
  cursor: grabbing;
}

/* Sempre no canto OPOSTO de onde o PiP está ancorado (posição via JS,
   voice.js) — arrasta esse cantinho pra fora e cresce nas duas dimensões
   junto, sem precisar de bordas separadas. */
.call-viewer[data-mode="pip"] .call-viewer-pip-resize {
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  width: 22px;
  height: 22px;
  /* Hotspot invisível: mantém o resize sem desenhar uma alça no PiP. */
  margin: 0;
  border-radius: 0;
  background: transparent;
  color: transparent;
  /* Como desenhado (pontas em cima-direita/baixo-esquerda), o ícone já é
     nesw — espelhado (scaleX) ele aponta nwse. */
  cursor: nesw-resize;
  z-index: 8;
}

.call-viewer-pip-resize .icon {
  display: none;
}

.call-viewer-pip-resize.is-mirrored {
  cursor: nwse-resize;
}

.call-viewer-pip-resize.is-mirrored .icon {
  transform: scaleX(-1);
}

.call-viewer-pip-sharer {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Agrupa o olhinho + o botão de parar (quando existe) num bloco só, à
   direita — o rodapé (space-between) só reparte espaço entre ISSO e o nome
   de quem compartilha, então os dois nunca mais se separam ao aparecer o
   botão no hover (antes eram 3 itens soltos no space-between, e o olhinho
   ficava sozinho "flutuando" no meio quando o botão de parar existia). */
.call-viewer-pip-footer-right {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}

.call-viewer-pip-viewers {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: none;
  cursor: default;
}

/* Só existe (não [hidden]) quando EU sou quem está transmitindo — ver
   renderCallViewer. Mesmo padrão de "só aparece no hover" da lupa de zoom
   do tile (.call-viewer-tile-zoom): opacity 0 + leve deslocamento, revelado
   no hover do rodapé (ou foco, pra quem navega por teclado). */
.call-viewer-pip-stop-share {
  flex: none;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity var(--dur-fast), transform var(--dur-fast);
}

.call-viewer-pip-footer:hover .call-viewer-pip-stop-share,
.call-viewer-pip-stop-share:focus-visible {
  opacity: 1;
  transform: translateY(0);
}

/* ---------- Call viewer em mobile ---------- */
/* Retrato estreito (max-width) OU celular deitado (orientation+max-height —
   filtra especificamente telas curtas; desktop/tablet em paisagem tem altura
   de sobra e nunca cai aqui). As duas situações precisam do mesmo touch
   target maior; a landscape ganha, além disso, um layout próprio logo abaixo. */
@media (max-width: 640px), (orientation: landscape) and (max-height: 560px) {
  /* Dot+título viram o link "← nome do canal" (abre a barra lateral, ver
     #callViewerChannelLink em app.js) — em desktop a barra lateral já fica
     sempre visível, então o link nem existe lá (display:none por padrão). */
  .call-bar-dot,
  #callViewerTitle {
    display: none;
  }

  .call-viewer-channel-link {
    display: inline-flex;
    min-height: 44px;
  }

  /* .call-viewer-grid já é flex-direction:column por padrão (featured em
     cima, carrossel embaixo) — em retrato o wireframe pede o contrário
     (carrossel compacto no topo, palco grande embaixo); em paisagem o
     carrossel vira faixa lateral (position:absolute logo abaixo) e o order
     deixa de fazer diferença, então é seguro deixar aqui pros dois casos. */
  .call-viewer-filmstrip {
    order: -1;
  }

  .call-viewer-filmstrip .call-viewer-tile {
    flex: 0 0 96px;
    width: 96px;
  }

  .call-viewer-grid.voice-only .call-viewer-featured {
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  }

  /* Botão de destacar em outra janela (Document PiP) é essencialmente um
     recurso de desktop — no navegador do celular não há "outra janela" pra
     abrir. Download do log de diagnóstico continua disponível. */
  #callViewerDetachButton {
    display: none;
  }

  .call-viewer-header {
    padding: 0 2px;
  }

  .call-viewer-header .btn-icon {
    min-width: 44px;
    min-height: 44px;
  }

  /* 70x32 é bom o bastante de largura, mas raso demais pro dedo — sobe pra
     44px de altura mantendo a largura (só a altura era o problema real). */
  .call-viewer-footer {
    padding: calc(10px + var(--safe-bottom)) 10px 10px;
  }

  .call-viewer-footer-actions {
    gap: 10px;
    width: 100%;
    justify-content: space-evenly;
  }

  .call-viewer-footer-actions .btn-icon {
    min-height: 44px;
    /* Mais respiro ao redor do botão de sair (destrutivo, is-danger) do que
       entre os outros, pra reduzir toque acidental. */
    margin-inline: 2px;
  }

  .call-viewer-footer-actions .btn-icon.is-danger {
    margin-inline: 8px;
  }
}

/* Paisagem no celular (mesmo filtro de altura do bloco acima): vídeo ocupa a
   tela toda, cabeçalho e rodapé flutuam por cima em vez de dividir o espaço
   em linhas separadas — ver o artboard "Call — paisagem" do wireframe
   aprovado. Vem DEPOIS do bloco de cima de propósito: quando as duas
   condições batem ao mesmo tempo (celular deitado), estas regras precisam
   ganhar das genéricas (mesma especificidade, ordem no arquivo decide). */
@media (orientation: landscape) and (max-height: 560px) {
  /* O cabeçalho do app (hambúrguer + título + membros) some enquanto a call
     ocupa a tela — nessa altura (curta, deitado) não sobra espaço pra dois
     cabeçalhos empilhados, e o título da própria call já aparece no
     .call-viewer-header logo abaixo. Trocar de canal nessa hora sai da call
     de qualquer forma (confirmVoiceSwitch), então não faz falta. */
  .main-panel.showing-call .main-header {
    display: none;
  }

  .call-viewer {
    padding: 0;
    border-radius: 0;
    border-bottom: none;
  }

  .call-viewer-header {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 5;
    margin: 0;
    padding: calc(8px + var(--safe-top)) calc(14px + var(--safe-right)) 8px calc(14px + var(--safe-left));
    background: linear-gradient(180deg, rgba(0, 0, 0, 0.55), transparent);
  }

  .call-viewer-grid {
    height: 100%;
  }

  /* O carrossel dos demais participantes vira uma faixa vertical fina perto
     da borda direita, em vez de uma linha inteira competindo por altura com
     o palco (que já não tem muita sobrando numa tela deitada). */
  .call-viewer-filmstrip {
    position: absolute;
    top: 56px;
    right: calc(10px + var(--safe-right));
    bottom: 74px;
    flex-direction: column;
    width: auto;
    justify-content: flex-start;
    overflow-y: auto;
    overflow-x: hidden;
    padding-bottom: 0;
  }

  .call-viewer-filmstrip .call-viewer-tile {
    flex: 0 0 64px;
    width: 64px;
  }

  .call-viewer-footer {
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(10px + var(--safe-bottom));
    justify-content: center;
    padding: 0;
    background: none;
    border-top: none;
  }

  .call-viewer-footer-actions {
    background: rgba(13, 14, 18, 0.55);
    border: 1px solid rgba(255, 255, 255, 0.08);
  }

  .call-viewer-footer-fullscreen {
    right: calc(10px + var(--safe-right));
  }

  /* Tela cheia (API nativa ou fallback .is-fullscreen) deitado no celular:
     nada flutua por cima, só a transmissão ocupa a tela. Sair do fullscreen
     (gesto do sistema) ou girar de volta pra retrato traz os controles.
     Mesmo tratamento com ALGUÉM transmitindo vídeo/tela mesmo sem tela
     cheia nativa (.has-video, ver renderCallViewer em voice.js): um toque no
     palco alterna .controls-visible por uns segundos (toggleLandscapeControls
     em voice.js) — sem isso não haveria como sair da call. */
  .call-viewer:fullscreen:not(.controls-visible) .call-viewer-header,
  .call-viewer:fullscreen:not(.controls-visible) .call-viewer-footer,
  .call-viewer:fullscreen:not(.controls-visible) .call-viewer-filmstrip,
  .call-viewer.is-fullscreen:not(.controls-visible) .call-viewer-header,
  .call-viewer.is-fullscreen:not(.controls-visible) .call-viewer-footer,
  .call-viewer.is-fullscreen:not(.controls-visible) .call-viewer-filmstrip,
  .call-viewer.has-video:not(.controls-visible) .call-viewer-header,
  .call-viewer.has-video:not(.controls-visible) .call-viewer-footer,
  .call-viewer.has-video:not(.controls-visible) .call-viewer-filmstrip {
    display: none;
  }
}

/* ---------- Administracao: modal amplo, navegavel e responsivo ---------- */

.admin-card {
  width: min(920px, calc(100vw - 32px));
  max-height: calc(100dvh - 32px);
  padding: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.admin-modal-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 24px 14px;
  flex: none;
}

.admin-modal-header h2 {
  margin: 0;
  font-size: 1.25rem;
}

.admin-modal-header p,
.admin-panel-intro {
  margin: 5px 0 0;
  color: var(--muted);
  font-size: 0.85rem;
}

.admin-close {
  flex: none;
  width: 36px;
  min-height: 36px;
}

.admin-tabs {
  flex: none;
  gap: 4px;
  margin: 0;
  padding: 0 20px;
  overflow-x: auto;
  scrollbar-width: thin;
}

.admin-tabs .link-button {
  min-width: 96px;
  border-radius: 6px 6px 0 0;
  padding: 0 14px 9px;
  white-space: nowrap;
}

.admin-tabs .link-button.active {
  color: var(--text);
  background: color-mix(in srgb, var(--accent) 32%, transparent);
}

.admin-scroll-area {
  min-height: 0;
  overflow-y: auto;
  scrollbar-gutter: stable;
  padding: 20px 24px 24px;
}

.admin-panel {
  min-width: 0;
}

.admin-panel-intro {
  margin: 0 0 14px;
}

.admin-section-title {
  margin: 22px 0 10px;
  font-size: 0.95rem;
}

.admin-role-card {
  padding: 16px;
}

.admin-role-header {
  display: grid;
  grid-template-columns: minmax(180px, 2fr) 72px minmax(140px, 1fr) minmax(110px, 0.7fr);
  align-items: end;
  gap: 12px;
  margin-bottom: 14px;
}

.admin-role-header label,
.admin-role-form > label {
  display: grid;
  gap: 5px;
  min-width: 0;
  font-size: 0.78rem;
  color: var(--muted);
  margin: 0;
}

.admin-role-header input,
.admin-role-header select,
.admin-role-form > label input {
  width: 100%;
  min-width: 0;
  margin: 0;
}

.admin-role-header input[type="color"] {
  height: 36px;
  padding: 4px;
}

/* auto-fill (não um número fixo de colunas) — com 3 colunas travadas o rótulo
   mais longo ("Apagar mensagens de qualquer pessoa") quebrava em várias
   linhas espremido contra a chave; assim cada coluna tem espaço mínimo pra
   caber numa linha só, e menos colunas cabem sozinhas em telas estreitas. */
.admin-permissions {
  display: grid;
  width: 100%;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 10px;
  margin-bottom: 14px;
}

.toggle {
  position: relative;
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
  line-height: 1.35;
  cursor: pointer;
}

/* Só os toggles de permissão (cargo existente ou "novo cargo", ambos usam
   .admin-permissions) ganham esse visual de linha — não mexe no toggle de
   "Dono" nem nos das configurações de áudio, que usam .toggle cru. */
.admin-permissions .toggle {
  padding: 6px 10px;
  border-radius: var(--radius-sm, 6px);
  background: var(--panel-soft);
}

.toggle input[type="checkbox"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.toggle-track {
  position: relative;
  flex: 0 0 auto;
  width: 38px;
  height: 22px;
  border: 1px solid #72767d;
  border-radius: 999px;
  background: var(--panel);
  transition: background 140ms ease, border-color 140ms ease;
}

.toggle-track::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #b7bbc3;
  transition: transform 140ms ease, background 140ms ease;
}

.toggle input[type="checkbox"]:checked + .toggle-track {
  border-color: var(--accent-strong);
  background: var(--accent-strong);
}

.toggle input[type="checkbox"]:checked + .toggle-track::after {
  background: #fff;
  transform: translateX(16px);
}

.toggle input[type="checkbox"]:focus-visible + .toggle-track {
  outline: none;
  box-shadow: var(--focus-ring);
}

.admin-role-form {
  padding: 16px;
  border: 1px solid var(--border);
}

#adminNewRolePermissions .toggle {
  min-height: 28px;
}

.admin-new-role-fields > label:first-child {
  grid-column: auto;
}

.admin-user-row {
  display: grid;
  grid-template-columns: auto minmax(130px, 1fr) minmax(170px, 240px) auto;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--panel-soft);
}

.admin-user-role {
  width: 100%;
  min-width: 0;
}

.admin-user-owner {
  align-items: center;
  font-size: 0.78rem;
}

.admin-channel-row {
  display: grid;
  grid-template-columns: auto auto minmax(140px, 1fr) auto auto auto auto;
}

.admin-status {
  position: sticky;
  bottom: -24px;
  margin: 18px -24px -24px;
  padding: 10px 24px;
  min-height: 38px;
  border-top: 1px solid var(--border);
  background: color-mix(in srgb, var(--panel) 94%, transparent);
  backdrop-filter: blur(8px);
}

@media (max-width: 680px) {
  .admin-card {
    width: calc(100vw - 16px);
    height: calc(100dvh - 16px);
    max-height: calc(100dvh - 16px);
    border-radius: 12px;
  }

  .admin-modal-header {
    padding: 16px 16px 12px;
  }

  .admin-modal-header p {
    max-width: 28ch;
  }

  .admin-tabs {
    padding: 0 8px;
  }

  .admin-tabs .link-button {
    flex: 1;
    min-width: max-content;
    padding-inline: 12px;
  }

  .admin-scroll-area {
    padding: 14px;
  }

  .admin-role-header {
    grid-template-columns: 72px minmax(0, 1fr);
  }

  .admin-role-header > label:first-child {
    grid-column: 1 / -1;
  }

  .admin-permissions {
    grid-template-columns: 1fr;
  }

  .admin-user-row {
    grid-template-columns: auto minmax(0, 1fr);
    gap: 10px;
  }

  .admin-user-role {
    grid-column: 1 / -1;
  }

  .admin-user-owner {
    grid-column: 2;
  }

  .admin-channel-row {
    grid-template-columns: auto auto minmax(0, 1fr) auto;
  }

  .admin-channel-row .tag,
  .admin-channel-category {
    grid-column: 3;
  }

  .admin-channel-row .link-button {
    grid-column: 2 / -1;
    justify-self: start;
  }

  .admin-status {
    bottom: -14px;
    margin: 14px -14px -14px;
    padding-inline: 14px;
  }
}

@media (max-width: 420px) {
  .admin-modal-header p {
    display: none;
  }

  .admin-role-header {
    grid-template-columns: 1fr;
  }

  .admin-role-header > label:first-child {
    grid-column: auto;
  }
}


/* Modal de compartilhamento: segue a estrutura das configurações — cabeçalho
   e ações ficam fixos; só o corpo usa a barra de rolagem personalizada. */
.modal-card.screen-share-modal-card {
  height: min(760px, 88dvh);
  max-height: calc(100dvh - 24px);
  overflow: hidden;
  gap: 0;
}
.screen-share-header {
  flex: none;
  padding: 0 40px var(--space-3) 0;
  border-bottom: 1px solid var(--border);
}
/* .modal-card ganhou overflow:hidden acima (pra travar cabeçalho/ações fixos
   com só o corpo rolando) — o .modal-close-x da base fica em top:-14px/
   right:-14px, pensado pra flutuar FORA do card (soundboard, lightbox...),
   e o overflow:hidden corta ele fora da área visível/clicável aqui. Só neste
   modal ele precisa morar DENTRO do card. */
.screen-share-modal-card .modal-close-x {
  top: 14px;
  right: 14px;
  z-index: 2;
}
.screen-share-header h2 { margin: 0; font-size: 1.1rem; }
.screen-share-header p {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: var(--text-sm);
}
.screen-share-content {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--space-3) 10px var(--space-3) 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  scrollbar-width: thin;
  scrollbar-color: var(--panel-raised) transparent;
}
.screen-share-content::-webkit-scrollbar { width: 10px; }
.screen-share-content::-webkit-scrollbar-track { background: transparent; }
.screen-share-content::-webkit-scrollbar-thumb {
  background-color: var(--panel-raised);
  border: 2px solid var(--panel);
  border-radius: 999px;
  background-clip: padding-box;
}
.screen-share-content::-webkit-scrollbar-thumb:hover { background-color: var(--accent-strong); }
.screen-share-audio-options {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--panel-soft);
}
.screen-share-audio-options .toggle { min-height: 30px; }
.screen-share-audio-options small {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: var(--text-xs);
}
.screen-share-modal-card > .gate-actions {
  flex: none;
  justify-content: flex-end;
  margin: 0;
  padding: var(--space-3) 0 0;
  border-top: 1px solid var(--border);
}
@media (max-width: 600px) {
  .modal-card.screen-share-modal-card {
    width: calc(100vw - 16px);
    height: calc(100dvh - 16px);
    max-height: none;
    padding: 16px;
  }
  .screen-share-header { padding-bottom: var(--space-2); }
  .screen-share-content { padding-top: var(--space-2); }
  .screen-share-source-grid {
    grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
    max-height: 260px;
  }
  .screen-share-modal-card > .gate-actions { padding-top: var(--space-2); }
}

/* ---------- Mensagens diretas (sidebar) ---------- */

/* Entrada fixa acima dos canais; mesmo desenho de um botão de canal. */
.dm-entry {
  all: unset;
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  box-sizing: border-box;
  padding: 7px 8px;
  margin-bottom: 8px;
  border-radius: 6px;
  cursor: pointer;
  color: var(--muted);
}

.dm-entry .icon {
  width: 16px;
  height: 16px;
  flex: none;
}

.dm-entry:hover {
  background: var(--panel-raised);
  color: var(--text);
}

.dm-entry.active {
  background: color-mix(in srgb, var(--accent-base) 34%, var(--panel-raised));
  box-shadow: inset 0 0 0 999px rgba(83, 88, 98, 0.16);
  color: #fff;
}

.dm-entry.has-unread .channel-name {
  color: var(--text);
  font-weight: 700;
}

.dm-entry .channel-name {
  flex: 1;
  min-width: 0;
}

.dm-entry-avatars {
  display: flex;
  flex: none;
  align-items: center;
}

.dm-panel {
  display: grid;
  gap: 6px;
}

.dm-back {
  all: unset;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 4px 8px;
  border-radius: 6px;
  cursor: pointer;
  color: var(--muted);
  font-size: 0.82rem;
}

.dm-back .icon {
  width: 14px;
  height: 14px;
}

.dm-back:hover {
  color: var(--text);
  background: var(--panel-raised);
}

.dm-panel-title {
  margin: 6px 0 0;
  padding: 0 8px;
  font-family: "Courier New", ui-monospace, monospace;
  font-size: 0.64rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}

.dm-empty {
  margin: 4px 0 0;
  padding: 0 8px;
  font-size: 0.8rem;
  color: var(--muted);
}

/* Linha de conversa: avatar (com o online do par) + nome + selo de não lidas. */
.channel-list li button.dm-row {
  padding: 5px 8px;
}

.dm-row .avatar-thumb {
  width: 28px;
  height: 28px;
  flex: none;
}

.dm-row .channel-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------- Card de perfil ---------- */

/* Nome/avatar clicável em qualquer lugar (lista de membros, autor no chat). */
.has-profile {
  cursor: pointer;
}

.chat-message .author.has-profile:hover {
  text-decoration: underline;
}

.profile-popover {
  position: fixed;
  z-index: 58; /* acima do dropdown (56) e do PiP (50), abaixo dos modais (60) */
  width: min(340px, calc(100vw - 16px));
}

.profile-card {
  --profile-accent: var(--accent-base);
  --card-top: var(--panel-raised);
  --card-bottom: var(--card-top);
  --card-grad-start: 0%;
  --card-grad-end: 100%;
  position: relative;
  background: var(--panel-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-2);
  overflow: hidden;
  color: var(--text);
}

.profile-card-loading {
  padding: 18px;
  font-size: 0.85rem;
  color: var(--muted);
}

/* Sem imagem, a faixa é a cor do banner (uma só). Com imagem, a <img> abaixo
   cobre tudo. */
.profile-card-banner {
  position: relative;
  height: 96px;
  background: var(--profile-accent);
}

/* Corpo do card personalizado (applyCardStyle em profile.js): cor de cima
   até --card-grad-start, transição até --card-grad-end, depois só a cor de baixo. */
.profile-card.has-card-colors {
  background: var(--card-top);
}

.profile-card.has-card-colors .profile-card-body {
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-top) var(--card-grad-start), var(--card-bottom) var(--card-grad-end, 100%), var(--card-bottom) 100%);
}

.profile-card.has-card-colors .profile-card-avatar {
  background: var(--card-top);
}

/* Cor de fonte: o texto secundário e as divisórias derivam dela, senão o
   cinza do tema some num fundo claro escolhido pela pessoa. */
.profile-card.has-card-text {
  --text: var(--card-text);
  --muted: color-mix(in srgb, var(--card-text) 68%, transparent);
  --border: color-mix(in srgb, var(--card-text) 18%, transparent);
  color: var(--card-text);
}

.profile-card.has-card-border {
  border: 2px solid var(--card-border);
}

.profile-card-banner img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Avatar grande, metade em cima do banner. */
.profile-card-avatar {
  position: absolute;
  top: 56px;
  left: 16px;
  padding: 4px;
  border-radius: 40%;
  background: var(--panel-raised);
}

.profile-card-avatar .avatar-thumb {
  width: 80px;
  height: 80px;
  font-size: 2rem;
}

.profile-card-body {
  padding: 52px 16px 14px;
  display: grid;
  gap: 8px;
  /* O card inteiro rola por dentro se a lista de jogos/bio passar da
     tela — a página nunca rola (regra do app). */
  max-height: calc(100vh - 96px - 32px);
  overflow-y: auto;
  scrollbar-width: thin;
}

.profile-card-name {
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.2;
  overflow-wrap: anywhere;
}

.profile-card-handle {
  font-size: 0.82rem;
  color: var(--muted);
  margin-top: -6px;
}

.profile-card-pronouns {
  color: var(--muted);
}

.profile-card-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.profile-badge {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 3px 7px;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 700;
  color: #1a1a1a;
  background: linear-gradient(135deg, #f5c542, #d99a1c);
  cursor: help;
}

.profile-badge .icon {
  width: 12px;
  height: 12px;
}

.profile-badge-top-broadcast {
  background: linear-gradient(135deg, #7ad3ff, #2f8bd6);
}

.profile-badge-top-games {
  background: linear-gradient(135deg, #b48bff, #6e3fd6);
}

.profile-card-role .tag {
  font-size: 0.7rem;
}

.profile-card-section {
  padding-top: 8px;
  border-top: 1px solid var(--border);
  display: grid;
  gap: 4px;
}

.profile-card-section-title {
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}

.profile-card-bio {
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.4;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.profile-card-stats,
.profile-card-games {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 4px;
  font-size: 0.8rem;
}

.profile-card-stats li,
.profile-card-games li {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
}

.profile-card-stats li .icon,
.profile-card-games li .icon {
  flex: none;
  color: var(--profile-accent);
}

.profile-card-stats strong {
  color: var(--text);
}

.profile-card-game-name {
  flex: 1;
  min-width: 0;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.profile-card-game-meta {
  font-variant-numeric: tabular-nums;
}

.profile-card-since {
  font-size: 0.72rem;
  color: var(--muted);
}

.profile-card-actions {
  display: grid;
  gap: 6px;
  padding-top: 4px;
}

.profile-card-actions .btn-default {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.profile-card-error {
  margin: 0;
  font-size: 0.78rem;
  color: var(--danger);
}

/* ---------- Configurações → Perfil ---------- */

.profile-preview {
  margin-bottom: 8px;
}

.profile-preview .profile-card {
  box-shadow: none;
}

.profile-preview .profile-card-body {
  max-height: none;
}

.profile-banner-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.profile-color-field {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

/* O estado "padrão" já é dito pelo texto do botão ao lado ("Padrão"/"Usar
   padrão") — apagar o quadrado (opacity baixa) só deixava a cor real do tema
   quase invisível num fundo escuro. profile.js põe a cor computada do tema
   nesse estado (getComputedStyle de --panel-raised/--text), então o quadrado
   sempre mostra uma cor de verdade, nunca um valor de placeholder sumido. */

.profile-card-style {
  display: grid;
  gap: 10px;
  margin: 4px 0 0;
  padding: 10px 12px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  /* <fieldset> tem min-width:min-content por padrão — dentro de um grid/flex
     estreito isso força o pai a alargar (ou estoura) em vez de encolher com
     ele. */
  min-width: 0;
}

.profile-card-style legend {
  padding: 0 4px;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--muted);
}

.profile-color-inputs {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.profile-color-inputs input[type="color"] {
  width: 44px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  cursor: pointer;
  /* Anel fino em vez de border: alguns navegadores desenham o próprio
     border DENTRO do swatch (encolhendo a cor visível); box-shadow fica por
     fora sem roubar espaço, e ainda dá contraste pra cores bem escuras
     contra um fundo também escuro. */
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--text) 35%, transparent);
}

.profile-color-inputs input[type="color"]::-webkit-color-swatch-wrapper {
  padding: 0;
}

.profile-color-inputs input[type="color"]::-webkit-color-swatch {
  border: none;
  border-radius: 6px;
}

.profile-color-inputs input[type="color"]::-moz-color-swatch {
  border: none;
  border-radius: 6px;
}

.profile-actions {
  align-items: center;
  justify-content: flex-end;
}

/* O ui-kit estiliza input/select, não textarea — mesmo visual aqui. */
#settings-profile textarea {
  font: inherit;
  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  padding: 8px 10px;
  resize: vertical;
  min-height: 64px;
  width: 100%;
  box-sizing: border-box;
}

#settings-profile textarea:focus {
  outline: none;
  border-color: var(--accent-strong);
}

/* ── Notas (editor/preview de markdown, public/notes.js) ───────────────── */
.modal-card.notes-modal-card {
  position: relative;
  width: min(1200px, 96vw);
  height: min(820px, 92vh);
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px;
}
.notes-header { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.notes-header-spacer { flex: 1; }
.notes-name {
  width: 200px;
  background: var(--bg-0);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: inherit;
  padding: 4px 8px;
  font: inherit;
  font-size: 13px;
}
/* Barra de formatação do markdown: mostra de cara o que dá pra escrever, pra
   quem não sabe a sintaxe de cor. Os botões só inserem texto no textarea (ver
   MD_ACTIONS em notes.js) — o preview continua sendo o marked + DOMPurify. */
.notes-toolbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 2px;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel-soft);
}
.notes-toolbar-group { display: flex; gap: 2px; }
.notes-toolbar-sep { width: 1px; align-self: stretch; margin: 2px 6px; background: var(--border); }
.notes-toolbar-spacer { flex: 1; }
.notes-toolbar .btn-icon { color: var(--muted); }
.notes-toolbar .btn-icon:hover { color: var(--text); background: var(--panel-raised); }
.notes-toolbar .btn-icon:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.notes-cheatsheet {
  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  font-size: var(--text-xs);
  max-height: 34%;
  overflow: auto;
}
.notes-cheatsheet dl { display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; margin: 0; }
.notes-cheatsheet dt { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; color: var(--accent); }
.notes-cheatsheet dd { margin: 0; color: var(--muted); }
.notes-cheatsheet code { font-family: inherit; background: var(--bg-1); padding: 0 .25em; border-radius: 3px; }

.notes-body {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 200px 1fr 1fr;
  gap: 10px;
}
.notes-cloud { display: flex; flex-direction: column; gap: 6px; min-height: 0; border-right: 1px solid var(--border); padding-right: 10px; }
.notes-cloud-title { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); display: flex; justify-content: space-between; }
/* Não há cota de conta — o limite é por arquivo. O texto diz isso; o "usado"
   ao lado é informativo, nunca um teto. */
.notes-limit { margin: 0; font-size: 11px; line-height: 1.4; color: var(--muted); }
/* Rolagem interna contida (regra do site: nunca scroll de página). */
.notes-cloud-list { list-style: none; margin: 0; padding: 0; overflow: auto; min-height: 0; display: flex; flex-direction: column; gap: 2px; }
.notes-cloud-item { display: flex; align-items: center; gap: 4px; }
.notes-cloud-item > button:first-child {
  flex: 1; min-width: 0; text-align: left; background: transparent; border: 0; color: inherit; padding: 6px 8px; border-radius: 6px;
  font: inherit; font-size: 13px; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.notes-cloud-item > button:first-child:hover, .notes-cloud-item.is-active > button:first-child { background: var(--bg-0); }
.notes-cloud-item .notes-cloud-size { font-size: 11px; color: var(--muted); margin-left: 6px; }
.notes-editor, .notes-preview {
  min-height: 0;
  overflow: auto;
  background: var(--bg-0);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 12px;
}
.notes-editor {
  resize: none;
  color: inherit;
  font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  outline: none;
}
.notes-editor:focus { border-color: var(--accent); }
.notes-status { min-height: 1.2em; }
.notes-modal-card .btn-text.is-primary { color: var(--accent); }
/* Preview: tipografia mínima pro HTML do marked. */
.markdown-body { font-size: 14px; line-height: 1.55; overflow-wrap: anywhere; }
.markdown-body > :first-child { margin-top: 0; }
.markdown-body h1, .markdown-body h2, .markdown-body h3 { margin: 1.2em 0 .5em; line-height: 1.25; }
.markdown-body h1 { font-size: 1.6em; border-bottom: 1px solid var(--border); padding-bottom: .25em; }
.markdown-body h2 { font-size: 1.3em; }
.markdown-body p, .markdown-body ul, .markdown-body ol, .markdown-body blockquote, .markdown-body pre, .markdown-body table { margin: 0 0 .9em; }
.markdown-body pre { background: var(--bg-1); border: 1px solid var(--border); border-radius: 6px; padding: 10px; overflow: auto; }
.markdown-body code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .92em; background: var(--bg-1); padding: .1em .3em; border-radius: 4px; }
.markdown-body pre code { background: none; padding: 0; }
.markdown-body blockquote { border-left: 3px solid var(--border); margin-left: 0; padding-left: 12px; color: var(--muted); }
.markdown-body table { border-collapse: collapse; }
.markdown-body th, .markdown-body td { border: 1px solid var(--border); padding: 4px 8px; }
.markdown-body img { max-width: 100%; }
.markdown-body a { color: var(--accent); }
@media (max-width: 900px) {
  .notes-body { grid-template-columns: 1fr; grid-template-rows: auto 1fr 1fr; }
  .notes-toolbar-sep { display: none; }
  .notes-cloud { border-right: 0; border-bottom: 1px solid var(--border); padding: 0 0 8px; max-height: 30%; }
}

/* Card compacto (mini card do rodapé): mesmo card, menor e sem estatísticas. */
.profile-card.is-compact .profile-card-banner {
  height: 72px;
}

.profile-card.is-compact .profile-card-avatar {
  top: 36px;
  left: 12px;
}

.profile-card.is-compact .profile-card-avatar .avatar-thumb {
  width: 64px;
  height: 64px;
  font-size: 1.6rem;
}

.profile-card.is-compact .profile-card-body {
  padding: 40px 12px 12px;
  gap: 6px;
  max-height: min(60vh, 420px);
}

.profile-card.is-compact .profile-card-name {
  font-size: 1.02rem;
}

/* Mini card que desliza de baixo, em cima do painel do usuário (clique em
   .sidebar-identity; toggleSelfCard em profile.js). */
.self-card-slide {
  position: fixed;
  z-index: 58;
  width: min(280px, calc(100vw - 16px));
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.18s ease, transform 0.18s ease;
  pointer-events: none;
}

.self-card-slide.is-open {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

@media (prefers-reduced-motion: reduce) {
  .self-card-slide {
    transform: none;
  }
}

.sidebar-identity {
  cursor: pointer;
  border-radius: 6px;
  padding: 2px 4px;
  margin: -2px -4px;
  transition: background-color 0.12s ease;
}

.sidebar-identity:hover,
.sidebar-identity[aria-expanded="true"] {
  background: color-mix(in srgb, var(--text) 7%, transparent);
}

.sidebar-identity:focus-visible {
  outline: 2px solid var(--accent-strong);
  outline-offset: 1px;
}

.profile-card-edit {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 28px;
  height: 28px;
  min-width: 28px;
  min-height: 28px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 6px;
  color: white;
  background: rgb(0 0 0 / 60%);
  cursor: pointer;
  opacity: 0;
}
.profile-card-banner:hover .profile-card-edit,
.profile-card-edit:focus-visible { opacity: 1; }
.profile-card-edit:focus-visible { outline: 2px solid white; outline-offset: 2px; }
@media (hover: none) {
  .profile-card-edit { opacity: 1; }
}

/* ===== Modo mobile (body.is-mobile, ver syncMobileMode em app.js) =====
   Mesma condição das media queries "é celular?" acima (≤900px ou deitado e
   baixo), só que como classe: o JS decide uma vez e CSS/voice.js seguem a
   mesma resposta. Tudo aqui vale SÓ no mobile — desktop segue as regras
   originais. */

/* Gaveta de canais só por gesto (swipe da borda esquerda, app.js) — o
   hambúrguer some. */
body.is-mobile .channel-sidebar-toggle {
  display: none;
}

/* Tile sem proporção fixa: o palco e o carrossel já dimensionam sozinhos
   (.call-viewer-featured .call-viewer-tile e .call-viewer-tile-media). */
body.is-mobile .call-viewer-tile {
  aspect-ratio: auto;
}

/* Anel de fala/som na BORDA do próprio tile (1px, acompanha o canto
   arredondado) em vez do inset de 3px na .call-viewer-tile-media — esta é
   um filho menor que o tile, então o anel desenhava um retângulo "comido"
   por dentro, e o :hover ainda sobrescrevia o box-shadow. */
body.is-mobile .call-viewer-tile.speaking .call-viewer-tile-media,
body.is-mobile .call-viewer-tile.playing-sound .call-viewer-tile-media {
  box-shadow: none;
}
body.is-mobile .call-viewer-tile.speaking {
  border-color: #23a55a;
}
body.is-mobile .call-viewer-tile.playing-sound {
  border-color: #22d3ee;
}

/* Controles do tile (Flutuar, zoom, reação) no toque: aparecem e somem
   junto com os demais (toque no palco → .controls-visible por 3s, voice.js).
   Sem isso o :hover "grudava" depois do primeiro toque e eles nunca mais
   saíam de cima da transmissão; invisíveis, também não pegam toque. */
body.is-mobile .call-viewer:not(.controls-visible) .call-viewer-tile-float,
body.is-mobile .call-viewer:not(.controls-visible) .call-viewer-tile-zoom,
body.is-mobile .call-viewer:not(.controls-visible) .call-viewer-tile-react {
  opacity: 0;
  pointer-events: none;
}
body.is-mobile .call-viewer:not(.controls-visible) .call-viewer-tile-reaction-bar {
  display: none;
}
body.is-mobile .call-viewer.controls-visible .call-viewer-tile-float,
body.is-mobile .call-viewer.controls-visible .call-viewer-tile-zoom,
body.is-mobile .call-viewer.controls-visible .call-viewer-tile-react {
  opacity: 1;
  transform: none;
}

@media (orientation: portrait) {
  /* Em call o cabeçalho principal some (o link "← canal" no
     .call-viewer-header e o swipe cobrem a navegação). */
  body.is-mobile .main-panel.showing-call .main-header {
    display: none;
  }

  /* Assistindo vídeo/tela: cabeçalho e rodapé (mic/câmera/compartilhar/
     sair) só aparecem ao tocar no palco (.controls-visible por 3s, mesmo
     mecanismo da paisagem). O carrossel fica: em retrato ele mora em cima
     do palco e sumir/voltar a cada toque mudaria o layout. Só voz não tem
     .has-video, então nada é escondido. */
  body.is-mobile .call-viewer.has-video:not(.controls-visible) .call-viewer-header,
  body.is-mobile .call-viewer.has-video:not(.controls-visible) .call-viewer-footer {
    display: none;
  }

  body.is-mobile .call-viewer-footer-fullscreen {
    display: none;
  }
}

@media (orientation: landscape) {
  /* Sem ocultar/mostrar participantes deitado — o carrossel lateral é
     forçado visível (renderCallViewer em voice.js ignora filmstripHidden). */
  body.is-mobile .call-viewer-tile-participants-toggle {
    display: none;
  }
}
.music-import-entry { display: flex; flex-direction: column; gap: 6px; margin-block: 10px; }
.music-import-entry select, #musicImportText { width: 100%; max-width: 100%; box-sizing: border-box; }
#musicImportReview { max-height: 45vh; overflow: auto; }
#musicImportText { resize: vertical; }

.music-card.expanded .music-card-body { max-height: min(60vh, 560px); overflow-y: auto; }
@media (max-width: 900px) {
  .music-card { right: 8px; width: min(340px, calc(100vw - 16px)); }
}

/* Convidado (link mágico, ver app.js setGuestMode): o backend já recusa tudo
   isto (guests.go); aqui só tira da frente o que não vai funcionar. */
body.is-guest #dmEntryButton,
body.is-guest #dmPanel,
body.is-guest #notesButton,
body.is-guest .audio-settings-nav [data-section="profile"],
body.is-guest .audio-settings-nav [data-section="servers"],
body.is-guest .audio-settings-nav [data-section="account"],
body.is-guest .profile-dm-button,
body.is-guest .profile-edit-button {
  display: none !important;
}


.chat-history-load {
  display: block;
  margin: 8px auto 16px;
  padding: 8px 12px;
  border: 0;
  border-radius: 6px;
  background: var(--panel-raised);
  color: var(--muted);
  cursor: pointer;
}
.chat-history-load:disabled { cursor: default; }
