@layer base, components, views;

@layer base {
  *, *::before, *::after { box-sizing: border-box; }
  * { margin: 0; }

  html { -webkit-text-size-adjust: 100%; }

  body {
    font-family: var(--font-sans);
    font-size: var(--text-base);
    line-height: var(--leading-normal);
    color: var(--text);
    background: var(--bg);
    min-height: 100dvh;
    -webkit-font-smoothing: antialiased;
    /* O WebView pinta um retângulo cinza no toque; ele aparece por cima do
       nosso estado :active e faz o app parecer página. */
    -webkit-tap-highlight-color: transparent;
  }

  ::selection { background: var(--selection); }

  /* Componentes definem display:grid/flex, que sobrescreveria o atributo
     hidden e deixaria botões visíveis antes da hora. */
  [hidden] { display: none !important; }

  button, input, textarea { font: inherit; color: inherit; }
  fieldset { border: none; padding: 0; margin: 0; min-width: 0; }
  legend { padding: 0; }
  button { cursor: pointer; border: none; background: none; }
  :focus-visible { outline: 2px solid var(--brand-500); outline-offset: 2px; border-radius: 4px; }

  /* Título que quebra em duas linhas com uma palavra órfã na segunda lê como
     descuido; `balance` reparte as linhas. Onde não houver suporte, nada
     acontece — a regra é só uma preferência de quebra. */
  h1, h2, h3 { line-height: var(--leading-tight); font-weight: 600; text-wrap: balance; }
  .numeric { font-variant-numeric: tabular-nums; }
  .mono { font-family: var(--font-mono); font-size: 0.95em; }

  .sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }

  /* Troca de tela. O roteador já usava View Transitions quando o WebView
     oferece; sem estas regras, a troca padrão é um fade longo de 250ms que
     parece lentidão. Curta e com 6px de subida: parece app trocando de aba. */
  ::view-transition-old(root) { animation: screen-out var(--dur-fast) var(--ease-out) both; }
  ::view-transition-new(root) { animation: screen-in var(--dur-base) var(--ease-out) both; }
  @keyframes screen-out { to { opacity: 0; } }
  @keyframes screen-in { from { opacity: 0; transform: translateY(6px); } }

  @media (prefers-reduced-motion: reduce) {
    ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
  }
}

@layer components {
  /* ── app shell ───────────────────────────────────────────────────── */
  /* `isolation` prende o halo (.app::before) neste contexto: ele fica acima
     do fundo da página e abaixo de todo conteúdo, sem z-index negativo
     global nem position: fixed — que no Android se comporta mal. */
  .app {
    position: relative; isolation: isolate;
    display: flex; flex-direction: column; min-height: 100dvh;
  }
  .app::before {
    content: ""; position: absolute; inset: 0 0 auto; height: 26rem;
    z-index: -1; pointer-events: none;
    background: radial-gradient(64% 72% at 50% 0%, var(--aura), transparent 72%);
  }

  /* Ícones (SVG). O traço herda a cor do texto, então o mesmo desenho serve
     ao item ativo, ao botão desabilitado e aos dois temas — coisa que emoji
     não faz. */
  .icon {
    width: 1.25em; height: 1.25em; flex: none;
    fill: none; stroke: currentColor; stroke-width: 1.8;
    stroke-linecap: round; stroke-linejoin: round;
  }

  /* O <main> precisa esticar, senão `flex: 1` do chat e da voz não tem contra
     o que crescer: o campo de escrever ficava logo abaixo da última mensagem,
     no meio da tela, com um vazio embaixo. `min-height: 0` deixa o filho
     rolar em vez de empurrar a página. */
  #outlet { flex: 1; min-height: 0; display: flex; flex-direction: column; }

  /* Dentro do app Android a janela JÁ é recuada pelas barras do sistema (o
     contêiner recebe os insets como padding em MainActivity). Sem isto, o CSS
     somava env(safe-area-inset-bottom) por cima e sobrava uma faixa vazia
     abaixo dos rótulos do menu. O marcador é posto pelo app; no navegador ele
     não existe e env() continua valendo, que é o certo lá. */
  :root[data-app] { --safe-bottom: 0px; --safe-top: 0px; }

  .topbar {
    display: flex; align-items: center; gap: var(--space-3);
    padding: calc(var(--space-3) + var(--safe-top, env(safe-area-inset-top, 0px))) var(--space-4) var(--space-3);
    border-bottom: 1px solid var(--border);
    background: color-mix(in oklch, var(--bg) 85%, transparent);
    backdrop-filter: blur(8px);
    position: sticky; top: 0; z-index: 10;
  }
  .topbar__brand {
    font-weight: 600; letter-spacing: -0.02em;
    display: flex; align-items: center; gap: var(--space-2);
  }
  .topbar__dot {
    width: 10px; height: 10px; border-radius: var(--radius-full);
    background: linear-gradient(140deg, var(--brand-300), var(--brand-600));
    box-shadow: 0 0 12px var(--brand-line);
  }
  .topbar__spacer { flex: 1; }

  .nav {
    display: flex; gap: var(--space-1);
    overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none;
    -webkit-overflow-scrolling: touch;
  }
  .nav::-webkit-scrollbar { display: none; }
  .nav__item { white-space: nowrap; }
  .nav__item {
    display: inline-flex; align-items: center; gap: var(--space-2);
    padding: var(--space-2) var(--space-3); border-radius: var(--radius-md);
    color: var(--text-muted); font-size: var(--text-sm); font-weight: 500;
    transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast);
  }
  .nav__item:hover { background: var(--surface-hover); color: var(--text); }
  /* O item ativo é o único que usa a cor da marca: é ela que responde "onde
     eu estou" sem precisar de sublinhado, caixa alta nem negrito. */
  .nav__item[aria-current="page"] {
    background: var(--brand-soft); color: var(--brand-text); font-weight: 600;
  }
  .nav__icon { width: 1.15rem; height: 1.15rem; }

  /* Celular estreito: marca + saldo + idioma + tema não cabiam em 360px por
     7px — e 7px de sobra viram scroll lateral, que faz a página inteira
     "passear" quando o dedo arrasta de lado. Aperto espaços e alvos em vez
     de esconder informação. */
  @media (max-width: 400px) {
    .topbar { gap: var(--space-2); padding-inline: var(--space-3); }
    .topbar__brand { gap: var(--space-1); }
    .balance { padding-inline: var(--space-2); }
    .lang-switch button { padding-inline: var(--space-1); }
    .btn--icon { width: 2rem; height: 2rem; }
  }

  /* Até 360px o que sobra é o nome da marca — o item menos informativo da
     barra (o app já se chama learn2talk no ícone, e o ponto É a marca do
     favicon). Tentei segurar o nome apertando espaços, e o resultado foi uma
     barra espremida que ainda passava 3px; melhor perder o nome do que a
     barra. Daí para cima, o aperto logo acima faz tudo caber. */
  @media (max-width: 360px) {
    .topbar__nome { display: none; }
  }

  /* Entre a barra de baixo (≤640px) e o desktop largo, a barra de cima não
     tem largura para marca + cinco rótulos + saldo + idioma: o último item
     ficava cortado pelo scroll. Nesta faixa o ícone fica e o rótulo sai —
     o aria-label do link mantém o nome para leitor de tela. */
  @media (min-width: 641px) and (max-width: 980px) {
    .nav__label { display: none; }
    .nav__item { padding: var(--space-2) var(--space-3); }
  }

  /* ── avatar do topo ──────────────────────────────────────────────
     Mesma largura do botão de tema que ele substitui quando há sessão:
     a barra já estava no limite em 360px. */
  .avatar {
    display: grid; place-items: center; flex: none;
    width: 2.25rem; height: 2.25rem; border-radius: var(--radius-full);
    overflow: hidden; text-decoration: none;
    outline: 2px solid transparent; outline-offset: 2px;
    transition: outline-color var(--dur-fast);
  }
  .avatar:hover, .avatar[aria-current="page"] { outline-color: var(--brand-500); }
  .avatar__img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
  .avatar__inicial {
    display: grid; place-items: center;
    color: #fff; font-weight: 600; font-size: var(--text-sm); line-height: 1;
  }
  .avatar__inicial[hidden], .conta__foto[hidden] { display: none; }
  @media (max-width: 400px) {
    .avatar { width: 2rem; height: 2rem; }
  }

  /* ── botões ──────────────────────────────────────────────────────── */
  .btn {
    display: inline-flex; align-items: center; justify-content: center;
    gap: var(--space-2);
    /* Vários botões são <a> — sem isto o navegador sublinha, e o "Voz" ao
       lado de "Encerrar e analisar" parecia um link no meio de dois botões.
       Fica na regra base, não em cada variante, porque a variante seguinte
       repetiria o mesmo esquecimento. */
    text-decoration: none;
    padding: var(--space-3) var(--space-5);
    border-radius: var(--radius-md);
    font-size: var(--text-sm); font-weight: 550;
    transition: transform var(--dur-fast) var(--ease-out),
                background var(--dur-fast), box-shadow var(--dur-fast),
                opacity var(--dur-fast);
  }
  .btn:active:not(:disabled) { transform: scale(0.98); }
  .btn:disabled { opacity: 0.5; cursor: not-allowed; }

  /* A sombra separa a ação principal do fundo; sem ela a cor da marca é só
     mais uma forma chapada na tela. */
  .btn--primary {
    background: var(--brand-500); color: var(--text-on-brand);
    box-shadow: var(--shadow-sm);
  }
  .btn--primary:hover:not(:disabled) {
    background: var(--brand-600); box-shadow: var(--shadow-md);
  }

  .btn--ghost { color: var(--text-muted); }
  .btn--ghost:hover:not(:disabled) { background: var(--surface-hover); color: var(--text); }

  .btn--sm { padding: var(--space-2) var(--space-4); font-size: var(--text-xs); }

  /* Botão só de ícone (o do tema). Alvo de 2.25rem: o mesmo do chip de nível,
     que é o menor que o polegar acerta sem mirar. */
  .btn--icon {
    display: grid; place-items: center;
    width: 2.25rem; height: 2.25rem; padding: 0;
    border-radius: var(--radius-full); font-size: var(--text-base);
  }

  /* Encerrar e analisar: é a ação que entrega o valor do produto, então tem
     peso de ação, não de link. */
  .btn--accent {
    background: color-mix(in oklch, var(--brand-500) 18%, transparent);
    color: var(--brand-text); font-weight: 600;
    border: 1px solid color-mix(in oklch, var(--brand-500) 40%, transparent);
  }
  .btn--accent:hover:not(:disabled) {
    background: color-mix(in oklch, var(--brand-500) 26%, transparent);
  }

  .btn--outline { border: 1px solid var(--border-strong); color: var(--text); }

  /* Para o que não tem volta (apagar). Vermelho sólido: não pode ser
     confundido com a ação principal, na cor da marca, do resto do app. */
  /* Escurecido: o --danger-500 puro com texto branco fica em ~4,2:1, abaixo
     dos 4,5:1 do WCAG AA para texto deste tamanho. */
  .btn--perigo {
    background: color-mix(in oklch, var(--danger-500) 82%, black); color: #fff;
    box-shadow: var(--shadow-sm);
  }
  .btn--perigo:hover:not(:disabled) { filter: brightness(1.08); }
  .btn--outline:hover:not(:disabled) { background: var(--surface-hover); }

  /* Entrar com o Google. Antes vinha pronto do SDK; agora é nosso, porque o
     SDK saiu do caminho (ver ADR-0010). Mantém a forma que a pessoa
     reconhece: marca à esquerda, rótulo centrado, alvo de toque cheio. */
  .btn--google {
    width: 100%;
    justify-content: center;
    position: relative;
    padding: var(--space-4) var(--space-5);
    border-radius: var(--radius-pill, 999px);
  }
  .btn--google svg {
    position: absolute; left: var(--space-4);
    width: 1.25rem; height: 1.25rem;
    background: #fff; border-radius: 50%; padding: 2px;
  }

  /* ── chat ────────────────────────────────────────────────────────── */
  .chat { flex: 1; display: flex; flex-direction: column; min-height: 0; }

  /* `min-height: 0` deixa a lista encolher (e rolar) quando o teclado come a
     altura da tela. Sem ele, item de flex com overflow ainda respeita a
     altura do conteúdo: o compositor era empurrado para fora do viewport e a
     página ganhava uma rolagem a mais justo quando não há espaço para rolar
     (o campo de escrever precisa ficar colado na barra de baixo). */
  .chat__scroll {
    flex: 1; min-height: 0; overflow-y: auto;
    padding: var(--space-4) var(--space-4) var(--space-6);
  }
  .chat__list {
    max-width: var(--container-chat); margin: 0 auto;
    display: flex; flex-direction: column; gap: var(--space-4);
  }

  .bubble {
    max-width: 85%; padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-lg); line-height: var(--leading-relaxed);
    animation: bubble-in var(--dur-base) var(--ease-out);
    overflow-wrap: anywhere;
    /* As falas de iniciante vêm em partes, uma por linha (a palavra no
       contexto, a pergunta, como responder). Sem isto as quebras sumiam e
       tudo virava um bloco corrido (captura de 23/09/2026). */
    white-space: pre-line;
  }
  .bubble--assistant {
    align-self: flex-start; background: var(--bg-elevated);
    border: 1px solid var(--border);
    box-shadow: var(--shadow-sm), inset 0 1px 0 var(--edge);
  }
  .bubble--user {
    align-self: flex-end; background: var(--brand-500); color: var(--text-on-brand);
    border-bottom-right-radius: var(--radius-sm);
  }
  .bubble--pending { opacity: 0.6; }
  /* Transcrição descartada — a dica do transcritor, não a pessoa (revisão
     da conversa, views.js). Do lado dela, sem afirmar que ela disse algo. */
  .bubble--sem-fala {
    background: transparent; color: var(--text-muted); font-style: italic;
    border: 1px dashed var(--border-strong);
  }

  /* Segurar para copiar (copiar.js). No celular o balão não é selecionável:
     o toque longo do sistema pegava uma palavra só e competia com o menu do
     app. No computador a seleção continua, para quem quer copiar um trecho. */
  @media (pointer: coarse) {
    .bubble, .conversa-linha {
      -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
    }
  }
  .bubble { transition: transform var(--dur-fast) var(--ease-out); }
  /* Estados do menu ao segurar (menu-segurar.js), os mesmos para balão e
     linha do Histórico: afunda enquanto o dedo segura, contorno com o menu
     aberto — o menu diz de QUEM ele é. */
  .bubble.is-segurando, .conversa-linha.is-segurando .conversa { transform: scale(0.98); }
  .bubble.is-marcado { outline: 2px solid var(--brand-500); outline-offset: 2px; }
  .conversa-linha.is-marcado .conversa { border-color: var(--brand-500); background: var(--brand-tint); }

  .msg-menu {
    position: fixed; z-index: 70; min-width: 10rem; max-width: calc(100vw - 16px);
    padding: var(--space-1); border: 1px solid var(--border-strong);
    border-radius: var(--radius-md); background: var(--bg-elevated); box-shadow: var(--shadow-lg);
    animation: sheet-pop var(--dur-fast) var(--ease-out);
  }
  .msg-menu__item {
    width: 100%; min-height: 2.75rem; padding: var(--space-2) var(--space-3);
    display: flex; align-items: center; gap: var(--space-2); text-align: left;
    border-radius: var(--radius-sm);
    font-size: var(--text-sm); font-weight: 550; color: var(--text);
  }
  .msg-menu__item:hover, .msg-menu__item:focus-visible { background: var(--surface-hover); }
  .msg-menu__item--perigo { color: var(--danger-500); }

  /* Linha do Histórico: a conversa e, ao lado, o "⋯" que abre o mesmo menu
     do segurar. Só com mouse: no toque o gesto é segurar, como na lista de
     conversas do WhatsApp e do Gmail, e a dica acima da lista diz isso. Com
     mouse não há segurar — o "⋯" aparece no hover e no foco do teclado, o
     padrão das ações de linha do Gmail e do Slack. */
  .conversa-linha { display: flex; align-items: center; gap: var(--space-1); min-width: 0; }
  .conversa-linha .conversa { flex: 1; min-width: 0; }
  .conversa__menu {
    display: grid; place-items: center; flex: none;
    width: 2.75rem; height: 2.75rem; border-radius: var(--radius-md);
    font-size: var(--text-xl); color: var(--text-muted);
  }
  .conversa__menu:hover { background: var(--surface-hover); color: var(--text); }
  .historico__aviso { font-size: var(--text-sm); color: var(--text-muted); }
  .historico__gesto { display: none; font-size: var(--text-xs); color: var(--text-subtle); }
  @media (hover: hover) and (pointer: fine) {
    .conversa__menu { opacity: 0; }
    .conversa-linha:hover .conversa__menu, .conversa-linha:focus-within .conversa__menu { opacity: 1; }
  }
  @media (pointer: coarse) {
    .historico__gesto { display: block; }
    .conversa__menu { display: none; }
  }

  /* Barra de voltar das telas internas (termos): uma só, colada abaixo
     da barra do app desde o primeiro instante e presa enquanto a página rola
     — a barra de app do Material 3 e a de navegação do iOS. Mesmo fundo e
     mesma borda da barra do app, de ponta a ponta: lê como a segunda linha
     dela, e não como um bloco no meio do degradê do topo. `--topbar-space` é
     medido em main.js. */
  .barra-voltar {
    position: sticky; top: var(--topbar-space, 0px); z-index: 9;
    flex: none;
    background: color-mix(in oklch, var(--bg) 85%, transparent);
    backdrop-filter: blur(8px);
    border-bottom: 1px solid var(--border);
  }
  /* Alinhada à coluna do texto: o .view recua --space-4, e o botão já tem
     --space-2 de respiro próprio antes da seta. */
  .barra-voltar__conteudo {
    max-width: var(--container-chat); margin: 0 auto;
    padding: var(--space-1) var(--space-2);
  }
  /* No celular a barra do app é chapada (sem desfoque); a de voltar segue. */
  @media (max-width: 640px) {
    .barra-voltar { background: var(--bg); backdrop-filter: none; }
  }
  .barra-voltar__btn {
    display: inline-flex; align-items: center; gap: var(--space-1);
    min-height: 2.75rem; padding: 0 var(--space-3) 0 var(--space-2);
    border-radius: var(--radius-full);
    font-size: var(--text-sm); font-weight: 550; color: var(--text);
    transition: background var(--dur-fast);
  }
  .barra-voltar__btn:hover { background: var(--surface-hover); }
  .barra-voltar__btn .icon { width: 1.25rem; height: 1.25rem; }

  /* Ícone de forma cheia (o "⋯"): traço fino some em três pontos. */
  .icon--cheio { fill: currentColor; stroke: none; }
  .bubble--error {
    align-self: center; background: color-mix(in oklch, var(--danger-500) 12%, var(--bg));
    color: var(--text); font-size: var(--text-sm); max-width: 100%;
  }

  @keyframes bubble-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
  }

  .typing { display: inline-flex; gap: 4px; padding: var(--space-1) 0; }
  .typing span {
    width: 6px; height: 6px; border-radius: var(--radius-full);
    background: var(--text-subtle); animation: blink 1.4s infinite;
  }
  .typing span:nth-child(2) { animation-delay: 0.2s; }
  .typing span:nth-child(3) { animation-delay: 0.4s; }
  @keyframes blink { 0%, 60%, 100% { opacity: 0.25; } 30% { opacity: 1; } }

  .composer {
    border-top: 1px solid var(--border);
    background: var(--bg);
    padding: var(--space-3) var(--space-4) calc(var(--space-4) + var(--safe-bottom, env(safe-area-inset-bottom, 0px)));
  }
  .composer__actions {
    max-width: var(--container-chat); margin: var(--space-3) auto 0; width: 100%;
    display: flex; align-items: center; gap: var(--space-2);
  }
  .composer__actions .btn--accent { margin-left: auto; }

  .composer__inner {
    max-width: var(--container-chat); margin: 0 auto;
    display: flex; gap: var(--space-2); align-items: flex-end;
  }
  /* Mesmo raio do campo: o Enviar com raio menor parecia de outro componente. */
  .composer__inner .btn { border-radius: var(--radius-lg); }
  .composer__input {
    flex: 1; resize: none; max-height: 9rem;
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--border-strong); border-radius: var(--radius-lg);
    background: var(--bg-elevated); line-height: var(--leading-normal);
    transition: border-color var(--dur-fast);
  }
  .composer__input:focus { outline: none; border-color: var(--brand-500); }
  .composer__hint {
    max-width: var(--container-chat); margin: var(--space-2) auto 0;
    font-size: var(--text-xs); color: var(--text-subtle);
    display: flex; justify-content: space-between; gap: var(--space-3);
  }

  /* ── cartões e listas ────────────────────────────────────────────── */
  .card {
    background: var(--bg-elevated); border: 1px solid var(--border);
    border-radius: var(--radius-lg); padding: var(--space-5);
    /* Fio de luz no topo: no escuro é o que dá relevo ao cartão sem clarear
       a borda inteira, que deixaria o contorno visível demais. */
    box-shadow: inset 0 1px 0 var(--edge);
  }
  .card--flat { background: transparent; border: none; padding: 0; box-shadow: none; }

  .chip {
    display: inline-flex; align-items: center; gap: var(--space-2);
    padding: var(--space-1) var(--space-3); border-radius: var(--radius-full);
    font-size: var(--text-xs); font-weight: 500;
    background: var(--surface-hover); color: var(--text-muted);
  }
  /* Dois chips com significado próprio: resumo pronto (positivo) e conversa
     em andamento (presente). No cinza padrão os dois seriam o mesmo cinza. */
  .chip--win {
    background: color-mix(in oklch, var(--win-500) 18%, transparent);
    color: color-mix(in oklch, var(--win-500) 60%, var(--text));
  }
  .chip--brand { background: var(--brand-soft); color: var(--brand-text); }

  .overline {
    font-size: var(--text-xs); font-weight: 550; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--text-subtle);
  }

  /* ── correção ────────────────────────────────────────────────────── */
  .correction { display: grid; gap: var(--space-2); }
  .correction__before {
    font-family: var(--font-mono); font-size: var(--text-sm);
    text-decoration: underline wavy var(--focus-500); text-underline-offset: 4px;
    color: var(--text-muted);
  }
  .correction__after {
    font-family: var(--font-mono); font-size: var(--text-sm); color: var(--win-500);
    font-weight: 500;
  }
  .correction__why { font-size: var(--text-sm); color: var(--text-muted); }

  .meter {
    height: 6px; border-radius: var(--radius-full);
    background: var(--surface-hover); overflow: hidden;
  }
  .meter__fill {
    height: 100%; border-radius: inherit;
    background: linear-gradient(90deg, var(--brand-400), var(--brand-600));
    transition: width var(--dur-slow) var(--ease-out);
  }

  .toast {
    position: fixed; left: 50%; bottom: var(--space-6); translate: -50% 0;
    /* Contraste deliberadamente oposto à tela: claro no tema escuro e escuro
       no tema claro. light-dark também acompanha a preferência do sistema. */
    background: light-dark(var(--gray-900), var(--gray-50));
    color: light-dark(var(--gray-50), var(--gray-900));
    border: 1px solid light-dark(var(--gray-800), var(--gray-200));
    padding: var(--space-3) var(--space-5); border-radius: var(--radius-full);
    font-size: var(--text-sm); box-shadow: var(--shadow-lg); z-index: 50;
    animation: bubble-in var(--dur-base) var(--ease-out);
  }
}

@layer views {
  /* ── conta ────────────────────────────────────────────────────────── */
  .conta__topo {
    display: flex; align-items: center; gap: var(--space-4);
    padding-block: var(--space-5) var(--space-2);
  }
  .conta__avatar {
    width: 4.5rem; height: 4.5rem; flex: none;
    border-radius: var(--radius-full); overflow: hidden;
  }
  .conta__foto {
    width: 100%; height: 100%; object-fit: cover;
    display: grid; place-items: center;
    color: #fff; font-size: var(--text-2xl); font-weight: 600;
  }
  .conta__quem { display: grid; gap: 2px; min-width: 0; }
  .conta__nome { font-size: var(--text-xl); letter-spacing: -0.02em; }
  .conta__email {
    color: var(--text-muted); font-size: var(--text-sm);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .conta__provedor { color: var(--text-subtle); font-size: var(--text-xs); }

  .conta__acoes { padding: var(--space-1); display: grid; }
  .conta__linha {
    display: flex; align-items: center; gap: var(--space-3);
    min-height: 3rem; padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md); text-align: left;
    color: var(--text); text-decoration: none;
  }
  .conta__linha:hover:not(:disabled) { background: var(--surface-hover); }
  .conta__linha:disabled { opacity: 0.5; cursor: wait; }
  .conta__linha + .conta__linha { border-top: 1px solid var(--border); border-radius: 0 0 var(--radius-md) var(--radius-md); }
  .conta__icone { display: grid; color: var(--text-muted); }
  .conta__icone .icon { width: 1.2rem; height: 1.2rem; }
  /* Sair não é perigoso — entrar de novo é um toque —, mas é a ação que
     encerra a sessão, e tem cor própria para não ser tocada por engano. */
  .conta__linha--sair, .conta__linha--sair .conta__icone { color: var(--danger-500); }
  .conta__linha--perigo, .conta__linha--perigo .conta__icone { color: var(--danger-500); }
  .conta__titulo { font-size: var(--text-lg); margin-top: var(--space-4); }
  .conta__resumo { padding-left: var(--space-5); display: grid; gap: var(--space-2); }
  .conta__privacidade { color: var(--text-muted); font-size: var(--text-sm); }
  .conta__detalhe {
    display: block; color: var(--text-muted); font-size: var(--text-xs);
    margin-top: var(--space-1); line-height: var(--leading-normal);
  }
  .conta__seta { display: grid; margin-left: auto; color: var(--text-subtle); }
  .privacy { padding-bottom: var(--space-6); }
  .privacy__lead { font-size: var(--text-lg); line-height: var(--leading-normal); }
  .privacy__list { margin: 0; padding-left: var(--space-5); display: grid; gap: var(--space-2); }
  .privacy__list strong { color: var(--text); }
  .privacy__draft { border-inline-start: 3px solid var(--border-strong); }
  .privacy__links { line-height: var(--leading-relaxed); }
  .dados-exportacao__json {
    width: 100%; min-height: 14rem; height: 35dvh; resize: vertical;
    border: 1px solid var(--border-strong); border-radius: var(--radius-md);
    padding: var(--space-3); background: var(--bg-elevated); color: var(--text);
    font: var(--text-xs)/1.5 var(--font-mono); white-space: pre;
  }

  /* ── tela inicial ─────────────────────────────────────────────────── */
  .home__hero { display: grid; gap: var(--space-2); padding-block: var(--space-5) var(--space-2); }
  .home__hero h1 { font-size: var(--text-2xl); letter-spacing: -0.03em; }
  .home__hero p { color: var(--text-muted); }

  .home__actions { display: grid; gap: var(--space-3); }

  .action {
    display: flex; align-items: center; gap: var(--space-4);
    padding: var(--space-4) var(--space-5);
    border: 1px solid var(--border-strong); border-radius: var(--radius-lg);
    background: var(--bg-elevated); text-decoration: none; color: var(--text);
    transition: background var(--dur-fast), border-color var(--dur-fast),
                transform var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast);
  }
  .action:active { transform: scale(0.99); }
  .action--primary {
    border-color: var(--brand-line);
    background: var(--brand-tint);
  }
  /* O ícone ganha um tijolo próprio. Solto, ele fica do tamanho do texto e as
     duas ações viram dois links um do lado do outro. */
  .action__icon {
    display: grid; place-items: center; flex: none;
    width: 2.75rem; height: 2.75rem; border-radius: var(--radius-md);
    background: var(--surface-hover); color: var(--text-muted);
    font-size: var(--text-lg);
  }
  .action--primary .action__icon { background: var(--brand-tint); color: var(--brand-text); }
  .action__text { display: grid; gap: 2px; min-width: 0; flex: 1; }
  .action__text b { font-size: var(--text-base); font-weight: 600; }
  .action__text small { font-size: var(--text-xs); color: var(--text-subtle); }
  /* A seta é o convite: diz que a linha inteira é tocável — coisa que uma
     borda sozinha não diz. Só com mouse: no toque, ela pisca sem motivo. */
  .action__go {
    color: var(--text-subtle);
    transition: translate var(--dur-base) var(--ease-out), color var(--dur-base);
  }
  @media (hover: hover) {
    .action:hover { background: var(--surface-hover); box-shadow: var(--shadow-md); }
    .action--primary:hover {
      background: color-mix(in oklch, var(--brand-500) 24%, transparent);
    }
    .action:hover .action__go { translate: 3px 0; color: var(--text); }
  }

  .home__meta { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
  .home__pill {
    display: inline-flex; align-items: center; gap: var(--space-1);
    min-height: 2.25rem; padding: var(--space-1) var(--space-3);
    border: 1px solid var(--border-strong); border-radius: var(--radius-full);
    background: var(--bg-elevated); color: var(--text-muted);
    font-size: var(--text-xs); text-decoration: none;
  }
  .home__pill b { color: var(--text); font-weight: 600; }
  .home__pill:hover { background: var(--surface-hover); }

  .home__row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
  .home__link { color: var(--brand-text); font-size: var(--text-xs); text-decoration: none; }
  .home__link:hover { text-decoration: underline; }

  .steps { display: grid; gap: var(--space-3); list-style: none; }
  .step { display: flex; gap: var(--space-3); align-items: flex-start; }
  /* O ícone em traço no tijolo da marca: sem emoji (cor fixa e desenho
     diferente em cada aparelho), o passo ganha marcador com a cor do app. */
  .step__icon {
    display: grid; place-items: center; flex: none;
    width: 2.25rem; height: 2.25rem; border-radius: var(--radius-md);
    background: var(--brand-soft); color: var(--brand-text); line-height: 1;
  }
  .step__text { font-size: var(--text-sm); color: var(--text-muted); line-height: var(--leading-relaxed); }
  .step__text b { color: var(--text); font-weight: 600; }

  .recent {
    display: flex; align-items: center; gap: var(--space-3);
    padding-block: var(--space-2); text-decoration: none; color: var(--text);
  }
  .recent + .recent { border-top: 1px solid var(--border); }
  .recent__text { display: grid; gap: 2px; min-width: 0; flex: 1; }
  .recent__text b { font-weight: 550; }
  .recent__text small { font-size: var(--text-xs); color: var(--text-subtle); }
  .recent__tag { font-size: var(--text-xs); color: var(--brand-text); flex: none; }

  .view { max-width: var(--container-wide); margin: 0 auto; padding: var(--space-6) var(--space-4); width: 100%; }
  .view--narrow { max-width: var(--container-chat); }

  .stack { display: grid; gap: var(--space-5); }
  .stack--tight { gap: var(--space-3); }

  .hero { display: grid; gap: var(--space-4); padding-block: var(--space-7); }

  /* O símbolo aprovado é o mesmo nos temas claro e escuro e acompanha a
     escolha manual ou automática do tema do app. */
  .login__mark { width: 4.5rem; height: 4.5rem; margin-top: var(--space-6); }
  .login__mark-img { display: block; width: 100%; height: 100%; object-fit: contain; }
  /* O resto da entrada (motivos, ação, brilho) está em entrada.css. */
  .login__mark-img--escuro { display: none; }
  :root[data-theme="dark"] .login__mark-img--claro { display: none; }
  :root[data-theme="dark"] .login__mark-img--escuro { display: block; }
  :root[data-theme="light"] .login__mark-img--escuro { display: none; }
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme]) .login__mark-img--claro { display: none; }
    :root:not([data-theme]) .login__mark-img--escuro { display: block; }
  }
  /* Recusa por convite: fica na tela até a pessoa tentar de novo. */
  .login__aviso {
    display: grid; gap: var(--space-2);
    border-color: var(--brand-line); background: var(--brand-soft);
    margin-bottom: var(--space-5);
  }
  .login__aviso[hidden] { display: none; }
  .login__aviso p { color: var(--text-muted); font-size: var(--text-sm); }
  /* Sem conta, a barra de baixo só levaria de volta ao próprio login. */
  body[data-route="/login"] .nav { display: none; }
  /* Os Termos abrem também do login, antes de entrar: sem conta, o menu só
     levaria de volta ao login, como na própria tela de login. */
  body[data-route="/termos"][data-sem-conta] .nav { display: none; }
  /* O aceite vem antes de tudo: o menu levaria a telas que mandam de volta. */
  body[data-route="/aceite"] .nav { display: none; }
  /* O resumo do alto dos termos é o que a pessoa aceitou: destaca do resto. */
  .termos__essencial { border-color: var(--brand-line); }
  .hero h1 { font-size: var(--text-3xl); letter-spacing: -0.03em; }
  .hero p { color: var(--text-muted); max-width: 34rem; }

  .grid-2 { display: grid; gap: var(--space-4); grid-template-columns: 1fr; }
  @media (min-width: 720px) { .grid-2 { grid-template-columns: 1fr 1fr; } }

  .debrief__headline { font-size: var(--text-2xl); letter-spacing: -0.02em; }
  .debrief__section { display: grid; gap: var(--space-3); }

  .win { display: grid; gap: var(--space-1); padding-left: var(--space-4); position: relative; }
  .win::before {
    content: "✓"; position: absolute; left: 0; color: var(--win-500); font-weight: 600;
  }
  .win__quote { font-family: var(--font-mono); font-size: var(--text-sm); }
  .win__note { font-size: var(--text-sm); color: var(--text-muted); }

  .skill-row {
    display: grid; gap: var(--space-2);
    padding: var(--space-4); border: 1px solid var(--border);
    border-radius: var(--radius-md); background: var(--bg-elevated);
    box-shadow: inset 0 1px 0 var(--edge);
  }
  .skill-row__head { display: flex; align-items: baseline; gap: var(--space-3); }
  .skill-row__label { font-weight: 550; }
  .skill-row__meta { margin-left: auto; font-size: var(--text-xs); color: var(--text-subtle); }

  /* Linha do histórico: duas linhas, uma frase cada — o título trunca em vez
     de quebrar, e os números dividem a segunda linha com o estado.

     Antes era um cartão de três linhas de texto corrido (título, data, e a
     pílula caindo sozinha embaixo): com tópico em inglês longo, meia tela
     virava sopa. O que importa — de que foi a conversa e quando — fica nas
     duas linhas; o resto saiu abreviado ("obs.") para caber sem quebrar. */
  .conversa {
    display: grid; grid-template-columns: 1fr auto;
    align-items: center; gap: var(--space-2) var(--space-3);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--border); border-radius: var(--radius-md);
    background: var(--bg-elevated); box-shadow: inset 0 1px 0 var(--edge);
    text-decoration: none; color: inherit;
    transition: border-color var(--dur-fast), background var(--dur-fast),
                transform var(--dur-fast) var(--ease-out);
  }
  .conversa__titulo {
    grid-area: 1 / 1; font-weight: 600; min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .conversa__meta {
    grid-area: 2 / 1; min-width: 0;
    display: flex; align-items: center; gap: var(--space-3);
    font-size: var(--text-xs); color: var(--text-muted);
  }
  .conversa__info {
    min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* O estado encosta na direita, alinhado com a seta do canto — e mais fino
     que o chip padrão, para não esticar a altura da linha. */
  .conversa__meta .chip {
    flex: none; margin-left: auto;
    padding: 1px var(--space-2); font-size: 0.6875rem;
  }
  .conversa__seta {
    grid-area: 1 / 2 / 3 / 3; align-self: center;
    color: var(--text-subtle);
    transition: translate var(--dur-base) var(--ease-out), color var(--dur-base);
  }
  .conversa:active { transform: scale(0.995); }
  @media (hover: hover) {
    .conversa:hover { border-color: var(--border-strong); background: var(--surface-hover); }
    .conversa:hover .conversa__seta { translate: 3px 0; color: var(--text); }
  }

  /* Extrato de créditos: o que foi em cima, de onde veio e quando embaixo.
     O valor alinha à direita, e só entrada ganha cor — débito é o normal. */
  .ledger-row {
    display: flex; align-items: center; gap: var(--space-3);
    padding-block: var(--space-2);
  }
  .ledger-row + .ledger-row { border-top: 1px solid var(--border); }
  .ledger-row__text { display: grid; gap: 2px; min-width: 0; flex: 1; }
  .ledger-row__kind { font-weight: 550; }
  .ledger-row__note { font-size: var(--text-xs); color: var(--text-subtle); }
  .ledger-row__note:empty { display: none; }
  .ledger-row__delta { color: var(--text-muted); font-weight: 550; }
  .ledger-row__delta--in { color: var(--ui-positive); }

  .cat-row { display: grid; grid-template-columns: 10rem 1fr 3rem; gap: var(--space-3); align-items: center; }
  .cat-row__name { font-size: var(--text-sm); color: var(--text-muted); }
  .cat-row__value { font-size: var(--text-sm); text-align: right; }
  @media (max-width: 520px) { .cat-row { grid-template-columns: 7rem 1fr 2.5rem; } }

  .empty {
    text-align: center; padding: var(--space-8) var(--space-4);
    color: var(--text-muted);
    /* Linha de leitura curta: centralizado, texto largo perde o começo da
       linha seguinte e a pessoa relê o parágrafo. */
    max-width: 32rem; margin-inline: auto;
  }
  .empty h2 { font-size: var(--text-lg); margin-bottom: var(--space-2); color: var(--text); }
  /* Tela vazia centralizada: com o conteúdo colado no topo, o resto da tela
     chapado parecia tela quebrada. O #outlet já é flex-coluna, então o
     flex:1 é o que empurra o conteúdo para o meio da área útil. */
  .view.empty {
    flex: 1; min-height: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
  }
  .empty__art {
    display: grid; place-items: center;
    width: 4.5rem; height: 4.5rem; margin-bottom: var(--space-4);
    border-radius: var(--radius-full);
    background: var(--brand-soft); color: var(--brand-text);
  }
  .empty__art .icon { width: 2rem; height: 2rem; }

  .progress-line {
    display: flex; align-items: center; gap: var(--space-3);
    font-size: var(--text-sm); color: var(--text-muted);
  }
}

@layer components {
  /* ── voz ─────────────────────────────────────────────────────────── */
  /* A tela de voz cabe inteira na altura do aparelho, como o chat: o orbe
     ocupa o que sobra e o resto tem altura própria. Antes a coluna crescia
     com as legendas e era centralizada, então com a chamada no ar o microfone
     e o "Encerrar" iam parar debaixo do menu (captura de 23/09/2026). Se nem
     o mínimo couber (celular deitado), a tela rola por dentro. */
  .voice {
    flex: 1; display: flex; flex-direction: column; align-items: center;
    gap: var(--space-5);
    padding: var(--space-6) var(--space-4)
             calc(var(--space-6) + var(--safe-bottom, env(safe-area-inset-bottom, 0px)));
    /* Luz no centro, onde o orbe vive: a tela de voz é quase vazia e o vazio
       chapado parece inacabado — o gradiente dá o palco sem desenhar nada. */
    background: radial-gradient(70% 55% at 50% 44%, var(--bg-elevated), var(--bg-sunken) 72%);
    min-height: 0; overflow-y: auto;
  }
  .voice > * { flex: none; }

  /* Palco do orbe: fica com a altura que sobra, e o orbe se mede por ele
     (container query). O halo com o anel chega a ~2× o orbe quando a voz está
     alta (−22% de cada lado, ×1,22 do halo, ×1,19 do respiro); medido só pelo
     orbe, ele invadia os alvos em cima e o "Ouvindo" embaixo. 50% do lado
     menor do palco deixa o pico encostar na borda, e o normal bem dentro. */
  .voice__palco {
    flex: 1 1 0; min-height: 8rem; width: 100%;
    display: grid; place-items: center;
    container-type: size;
  }

  /* Faixa do topo da tela de voz: cronômetro à esquerda, nível à direita.
     Fica fora do fluxo para não desalinhar o orbe, que é centralizado; o
     padding-top da .voice reserva o espaço dela. */
  .voice { position: relative; padding-top: calc(var(--space-6) + 2.75rem); }
  .voice__top {
    position: absolute; inset: var(--space-4) var(--space-4) auto;
    display: flex; align-items: center; justify-content: flex-end; gap: var(--space-3);
  }
  .voice__timer {
    margin-right: auto;
    font-size: var(--text-sm); color: var(--text-subtle);
    font-variant-numeric: tabular-nums;
  }

  /* O orbe é a presença da conversa — e ondula com a voz de quem fala, como
     o do modo de voz do ChatGPT. O nível chega do JS (RMS do microfone e da
     voz do coach) em `--orb-level` (0..1); o CSS só desenha.

     São três camadas com escalas diferentes: núcleo, halo e anel. É a soma
     delas que lê como ondulação — uma só, crescendo, parece um botão mudando
     de tamanho, que é o que eu tinha antes. */
  .orb {
    --orb-level: 0;
    position: relative;
    width: clamp(160px, 34vw, 260px);   /* sem container query */
    width: min(240px, 50cqmin);
    aspect-ratio: 1;
    border-radius: var(--radius-full);
    background: radial-gradient(circle at 35% 30%,
                var(--brand-300), var(--brand-500) 55%, var(--brand-700));
    box-shadow: 0 0 calc(44px + var(--orb-level) * 46px)
                oklch(58% 0.096 197 / calc(0.3 + var(--orb-level) * 0.3));
    transform: scale(calc(1 + var(--orb-level) * 0.16));
    transition: transform 90ms linear, box-shadow 90ms linear,
                opacity var(--dur-base) var(--ease-out),
                filter var(--dur-base) var(--ease-out);
    will-change: transform;
  }
  /* Halo: a casca que alarga junto com a voz. */
  .orb::before {
    content: ""; position: absolute; inset: -22%; border-radius: inherit;
    background: radial-gradient(circle, transparent 52%,
                oklch(58% 0.096 197 / 0.5) 70%, transparent 76%);
    opacity: calc(0.25 + var(--orb-level) * 0.75);
    transform: scale(calc(1 + var(--orb-level) * 0.22));
    transition: transform 90ms linear, opacity 90ms linear;
    pointer-events: none;
  }
  /* Anel externo: contorno da esfera — é ele que ondula para fora. */
  .orb::after {
    content: ""; position: absolute; inset: -9%; border-radius: inherit;
    border: 1px solid var(--brand-line);
    opacity: calc(0.3 + var(--orb-level) * 0.7);
    transform: scale(calc(1 + var(--orb-level) * 0.3));
    transition: transform 90ms linear, opacity 90ms linear;
  }
  .orb[data-state="connecting"] {
    background: none; border: 3px solid var(--gray-400);
    border-top-color: transparent; box-shadow: none;
    animation: orb-spin 1s linear infinite;
  }
  .orb[data-state="listening"] { animation: orb-breathe 3s ease-in-out infinite; }
  .orb[data-state="thinking"]  { filter: saturate(0.6); opacity: 0.75; }
  .orb[data-state="speaking"] {
    box-shadow: 0 0 calc(60px + var(--orb-level) * 60px)
                oklch(58% 0.096 197 / calc(0.42 + var(--orb-level) * 0.3));
    background: radial-gradient(circle at 35% 30%,
                var(--brand-300), var(--brand-400) 60%, var(--brand-600));
  }
  .orb[data-state="ended"] { filter: grayscale(1); opacity: 0.4; }

  @keyframes orb-spin { to { transform: rotate(360deg); } }
  @keyframes orb-breathe {
    50% { transform: scale(calc(1.03 + var(--orb-level) * 0.16)); }
  }

  @media (prefers-reduced-motion: reduce) {
    .orb, .orb::before, .orb::after {
      animation: none !important; transform: none !important;
    }
    /* Sem movimento, o nível continua legível: o que sobra é o brilho do halo
       e do anel acompanhando a voz. */
    .orb[data-state="listening"] { outline: 3px solid var(--brand-400); outline-offset: 8px; }
    .orb[data-state="speaking"]  { outline: 6px solid var(--brand-300); outline-offset: 8px; }
  }

  .voice__status {
    font-size: var(--text-lg); font-weight: 500; text-align: center;
    min-height: 1.6em;
  }
  .voice__hint { font-size: var(--text-sm); color: var(--text-muted); text-align: center; }

  /* As falas da chamada, nos mesmos balões do chat (legendas.js). Antes
     eram as três últimas linhas numa caixa de ~140px, que mostrava uma fala
     por vez (captura de 25/09/2026). Agora é a conversa inteira, rolando. */
  .captions {
    width: min(100%, var(--container-chat));
    overflow-y: auto; overscroll-behavior: contain;
    display: flex; flex-direction: column; gap: var(--space-3);
    padding-block: var(--space-2);
  }
  .captions[hidden] { display: none; }
  .legenda { max-width: 88%; }

  /* ── chamada no ar (voice-view.js, emChamada) ───────────────────────
     A conversa vira o conteúdo da tela, como no modo de voz dos apps de
     referência: as falas ocupam o que sobra e rolam; o orbe encolhe e desce
     para junto dos controles, ainda ondulando com a voz. Vale desde o toque
     em "Começar a falar": uma mudança só, sem passar pelo orbe grande. */
  .voice[data-chamada] { gap: var(--space-3); }
  .voice[data-chamada] > .captions        { order: 1; }
  .voice[data-chamada] > .voice__palco    { order: 2; }
  .voice[data-chamada] > .voice__estado   { order: 3; }
  .voice[data-chamada] > .voice__controls { order: 4; }
  .voice[data-chamada] > .captions:not([hidden]) { flex: 1 1 0; min-height: 0; }
  .voice[data-chamada] > .voice__palco { flex: 0 0 6.5rem; min-height: 0; }
  /* A frase de ajuda explica a tela antes da chamada; nela, sobra só o
     estado ("Conectando…", "Ouvindo", "Falando"), menor, para não disputar
     com as falas. */
  .voice[data-chamada] .voice__hint { display: none; }
  .voice[data-chamada] .voice__status {
    font-size: var(--text-sm); color: var(--text-muted); min-height: 0;
  }

  /* Tela baixa (≤700px de viewport, celular pequeno com as barras do
     sistema): com a chamada no ar, os controles ficavam 79px debaixo do menu
     a 360×640. Aperta espaços e legendas, e a frase de ajuda sai quando há
     legenda — ela explica o começo da conversa, e a legenda já mostra que a
     conversa está andando. O "Encerrar" não pode depender de rolar. */
  @media (max-height: 700px) {
    .voice { gap: var(--space-4); padding-top: calc(var(--space-4) + 2.75rem); }
    .voice__palco { min-height: 6rem; }
  }

  .voice__controls { display: flex; gap: var(--space-3); align-items: center; }
  .voice__btn {
    display: grid; place-items: center; width: 56px; height: 56px;
    border-radius: var(--radius-full); border: 1px solid var(--border-strong);
    background: var(--bg-elevated); font-size: 1.25rem;
    transition: background var(--dur-fast), transform var(--dur-fast) var(--ease-out);
  }
  .voice__btn:hover:not(:disabled) { background: var(--surface-hover); }
  .voice__btn:active:not(:disabled) { transform: scale(0.94); }
  .voice__btn .icon { width: 1.5rem; height: 1.5rem; }
  /* O vermelho é do microfone no mudo — é o único estado "cuidado" da tela.
     Os outros dois botões da tela usam as classes de `.btn` (o Encerrar e
     analisar é o mesmo do chat de texto). */
  .voice__btn[aria-pressed="true"] {
    background: color-mix(in oklch, var(--danger-500) 18%, var(--bg-elevated));
    border-color: var(--danger-500);
  }
}

@layer views {
  .chat, .conversa-unificada { position: relative; }
  .chat__latest {
    position: absolute; right: var(--space-4); bottom: var(--chat-jump-bottom, 12rem);
    z-index: 3; min-height: 44px; padding: var(--space-2) var(--space-4);
    border: 1px solid var(--border-strong); border-radius: var(--radius-full);
    background: var(--bg-elevated); color: var(--text); box-shadow: var(--shadow-lg);
    font-size: var(--text-sm); font-weight: 600;
  }
  .debrief__phase {
    color: var(--text); font-size: var(--text-sm);
  }
  .debrief__description, .debrief__saved p {
    color: var(--text-muted); line-height: var(--leading-relaxed);
  }
  .debrief__saved h2 { font-size: var(--text-base); }
  .debrief__actions { display: flex; gap: var(--space-3); flex-wrap: wrap; }
  .debrief__meter.is-indeterminate .meter__fill {
    width: 35% !important;
    animation: debrief-wait 1.8s var(--ease-out) infinite alternate;
  }
  @keyframes debrief-wait { from { transform: translateX(0); } to { transform: translateX(185%); } }
  @media (prefers-reduced-motion: reduce) {
    .debrief__meter.is-indeterminate .meter__fill { animation: none; }
  }
  /* a trajetória da conversa: o que mudou do começo para o fim */
  .debrief__progress {
    padding: var(--space-4) var(--space-5);
    border-left: 3px solid var(--brand-500);
    background: var(--bg-sunken);
    border-radius: 0 var(--radius-md) var(--radius-md) 0;
    color: var(--text-muted); line-height: var(--leading-relaxed);
  }
}

@layer components {
  /* ── nível da conversa ──────────────────────────────────────────── */

  /* Seis barrinhas, uma por nível (A1 a C2): lê-se "quão difícil" sem saber
     o que é CEFR. */
  .level-meter { display: inline-flex; align-items: flex-end; gap: 2px; height: 0.75rem; }
  .level-meter__bar {
    width: 3px; border-radius: 1px; background: var(--border-strong);
  }
  .level-meter__bar:nth-child(1) { height: 25%; }
  .level-meter__bar:nth-child(2) { height: 40%; }
  .level-meter__bar:nth-child(3) { height: 55%; }
  .level-meter__bar:nth-child(4) { height: 70%; }
  .level-meter__bar:nth-child(5) { height: 85%; }
  .level-meter__bar:nth-child(6) { height: 100%; }
  .level-meter__bar.is-on { background: var(--brand-500); }

  .level-chip {
    display: inline-flex; align-items: center; gap: var(--space-2);
    min-height: 2.25rem; /* alvo de toque confortável */
    padding: var(--space-1) var(--space-3);
    border: 1px solid var(--border-strong); border-radius: var(--radius-full);
    background: var(--bg-elevated); color: var(--text);
    font-size: var(--text-xs); font-weight: 550; white-space: nowrap;
    transition: background var(--dur-fast), border-color var(--dur-fast);
  }
  .level-chip:hover { background: var(--surface-hover); }
  .level-list { display: grid; gap: var(--space-3); }
  .level-option {
    position: relative; display: grid; gap: var(--space-1); cursor: pointer;
    padding: var(--space-4); border: 1px solid var(--border-strong);
    border-radius: var(--radius-lg); background: var(--bg-elevated);
    transition: border-color var(--dur-fast), background var(--dur-fast);
  }
  .level-option input { position: absolute; opacity: 0; pointer-events: none; }
  .level-option:hover { background: var(--surface-hover); }
  .level-option:has(input:checked) {
    border-color: var(--brand-500);
    background: var(--brand-tint);
  }
  .level-option:has(input:focus-visible) { outline: 2px solid var(--brand-500); outline-offset: 2px; }
  .level-option:has(input:disabled) { cursor: progress; opacity: 0.7; }
  .level-option__head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
  .level-option__name { font-weight: 600; }
  .level-option__desc { font-size: var(--text-sm); color: var(--text-muted); line-height: var(--leading-normal); }
  .level-option__sample {
    margin-top: var(--space-1);
    font-size: var(--text-sm); font-style: italic; color: var(--text);
    padding-left: var(--space-3); border-left: 2px solid var(--border-strong);
  }
  .level-option:has(input:checked) .level-option__sample { border-left-color: var(--brand-500); }

  /* ── folha (dialog) ─────────────────────────────────────────────────
     <dialog> nativo: foco preso dentro, Esc fecha, leitor de tela entende
     como modal — nada disso precisa ser reimplementado. */
  .sheet {
    border: none; padding: 0; margin: auto;
    width: min(34rem, calc(100% - 2 * var(--space-4)));
    max-height: min(85dvh, 44rem);
    background: var(--bg); color: var(--text);
    border-radius: var(--radius-xl); box-shadow: var(--shadow-lg);
    display: flex; flex-direction: column; overflow: hidden;
  }
  .sheet:not([open]) { display: none; }
  .sheet[open] { animation: sheet-pop var(--dur-base) var(--ease-out); }
  .sheet::backdrop { background: oklch(10% 0.01 230 / 0.55); }
  .sheet__head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--space-3); padding: var(--space-5) var(--space-5) var(--space-3);
  }
  .sheet__title { font-size: var(--text-lg); }
  .sheet__sub { font-size: var(--text-sm); color: var(--text-muted); margin-top: var(--space-1); }
  .sheet__close {
    display: grid; place-items: center; flex: none;
    width: 2.5rem; height: 2.5rem; border-radius: var(--radius-full);
    color: var(--text-muted);
  }
  .sheet__close:hover { background: var(--surface-hover); color: var(--text); }
  .sheet__body {
    overflow-y: auto; overscroll-behavior: contain;
    padding: var(--space-2) var(--space-5) var(--space-5);
    display: grid; gap: var(--space-4);
  }
  .sheet__note { font-size: var(--text-xs); color: var(--text-subtle); }
  .sheet__texto { white-space: pre-line; line-height: var(--leading-relaxed); color: var(--text-muted); }

  /* Confirmação no rodapé da folha, fora da área que rola: a lista de níveis
     é mais alta que a tela do celular, e uma pergunta no fim dela ficaria
     escondida justo depois do toque que a fez aparecer. */
  .sheet__foot {
    flex: none; display: grid; gap: var(--space-3);
    padding: var(--space-4) var(--space-5) var(--space-5);
    border-top: 1px solid var(--border);
    background: var(--bg-elevated);
    animation: bubble-in var(--dur-base) var(--ease-out);
  }
  .sheet__ask { font-size: var(--text-sm); line-height: var(--leading-normal); }
  .sheet__actions { display: flex; justify-content: flex-end; gap: var(--space-2); flex-wrap: wrap; }

  /* ── apresentação guiada ──────────────────────────────────────────── */
  .tour { position: fixed; inset: 0; z-index: 80; }

  /* O "buraco" é um retângulo transparente com uma sombra enorme em volta:
     escurece tudo menos o alvo, sem precisar recortar nada. */
  .tour__hole {
    position: absolute; border-radius: var(--radius-md);
    box-shadow: 0 0 0 100vmax oklch(10% 0.01 230 / 0.72);
    outline: 2px solid var(--brand-500); outline-offset: 0;
    transition: all var(--dur-base) var(--ease-out);
    pointer-events: none;
  }

  .tour__bubble {
    position: absolute; width: min(20rem, calc(100vw - 24px));
    /* Alvo mais alto que a tela pode produzir um balão mais alto que ela
       também; melhor rolar por dentro do que sair da área visível. */
    max-height: calc(100dvh - 24px); overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--space-4) var(--space-5);
    background: var(--bg-elevated); color: var(--text);
    border: 1px solid var(--border-strong); border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    display: grid; gap: var(--space-2);
    animation: bubble-in var(--dur-base) var(--ease-out);
  }
  .tour__step { font-size: var(--text-xs); color: var(--text-subtle); }
  .tour__title { font-size: var(--text-base); font-weight: 600; }
  .tour__text { font-size: var(--text-sm); color: var(--text-muted); line-height: var(--leading-relaxed); }
  .tour__actions { display: flex; gap: var(--space-2); justify-content: flex-end; margin-top: var(--space-1); }

  /* Setinha apontando para o alvo. */
  .tour__bubble::after {
    content: ""; position: absolute; left: 50%; translate: -50% 0;
    border: 8px solid transparent;
  }
  .tour__bubble:not(.tour__bubble--below)::after {
    top: 100%; border-top-color: var(--bg-elevated);
  }
  .tour__bubble--below::after {
    bottom: 100%; border-bottom-color: var(--bg-elevated);
  }
  /* Sem seta quando o balão fica POR CIMA do alvo (alvo mais alto que a tela):
     ela apontaria para o próprio balão. */
  .tour__bubble--over::after { display: none; }

  @keyframes sheet-pop {
    from { opacity: 0; transform: translateY(8px) scale(0.98); }
    to   { opacity: 1; transform: none; }
  }

  /* No celular vira folha que sobe do rodapé: fica onde o polegar está. */
  @media (max-width: 640px) {
    .sheet {
      width: 100%; max-width: none; margin: auto 0 0;
      max-height: 88dvh;
      border-radius: var(--radius-xl) var(--radius-xl) 0 0;
      padding-bottom: env(safe-area-inset-bottom, 0px);
    }
    .sheet[open] { animation: sheet-up var(--dur-base) var(--ease-out); }
  }
  @keyframes sheet-up {
    from { transform: translateY(100%); }
    to   { transform: none; }
  }

  /* Faixa do topo do chat: só o nível, à direita — como na voz. A faixa de
     alvos ("Conhecendo seu inglês" + habilidades) saiu em 25/09/2026, por
     decisão do Anderson: não fazia mais falta em nenhum idioma nem chat. */
  .chat__bar {
    max-width: var(--container-chat); margin: 0 auto; width: 100%;
    padding: var(--space-3) var(--space-4) 0;
    display: flex; align-items: center; justify-content: flex-end;
  }

  .lang-switch {
    display: flex; gap: var(--space-1); padding: 2px; flex: none;
    background: var(--surface-hover); border-radius: var(--radius-full);
  }
  .lang-switch button {
    padding: var(--space-1) var(--space-3); border-radius: var(--radius-full);
    font-size: var(--text-xs); font-weight: 550; color: var(--text-muted);
    /* 日本語 quebra em três linhas dentro do botão se o flex apertar — a
       barra de cima inteira cresce por causa disso. */
    white-space: nowrap;
    transition: background var(--dur-fast), color var(--dur-fast);
  }
  .lang-switch button[aria-pressed="true"] {
    background: var(--bg-elevated); color: var(--text); box-shadow: var(--shadow-sm);
  }
}

@layer components {
  /* Em celular a navegação vai para baixo, onde o polegar alcança — e para de
     disputar espaço com a marca e o seletor de idioma na barra de cima. */
  @media (max-width: 640px) {
    .topbar {
      gap: var(--space-3); padding-inline: var(--space-4);
      /* backdrop-filter cria bloco de contenção para descendentes fixed — com
         ele, a nav de baixo fica presa DENTRO da topbar e cobre a marca. */
      backdrop-filter: none;
      background: var(--bg);
    }

    .nav {
      position: fixed; inset: auto 0 0 0; z-index: 20;
      justify-content: space-around; gap: var(--space-1);
      padding: var(--space-2) var(--space-2)
               calc(var(--space-2) + var(--safe-bottom, env(safe-area-inset-bottom, 0px)));
      background: color-mix(in oklch, var(--bg) 92%, transparent);
      backdrop-filter: blur(12px);
      border-top: 1px solid var(--border);
      overflow: visible;
    }

    /* Cada item é um alvo com forma: ícone em cima, nome embaixo, fundo
       próprio quando ativo. Só o sublinhado não dizia onde começava e
       terminava o que dava para tocar. */
    .nav__item {
      flex: 1; min-width: 0;
      display: grid; justify-items: center; gap: 3px;
      padding: var(--space-2) var(--space-1);
      border-radius: var(--radius-md);
      font-size: 0.6875rem; line-height: 1.1; text-align: center;
      text-decoration: none;
    }
    /* Item ativo: pílula atrás do ícone (ideia do Material 3) em vez de um
       bloco tingido do tamanho do item. O "onde estou" continua óbvio e a
       barra fica mais leve. content-box para o padding não encolher o SVG;
       o alvo de toque não muda — o <a> inteiro segue clicável. */
    .nav__item[aria-current="page"] { background: transparent; }
    .nav__icon {
      width: 1.3rem; height: 1.3rem; box-sizing: content-box;
      padding: 4px 20px; border-radius: var(--radius-full);
      transition: background var(--dur-fast) var(--ease-out);
    }
    .nav__item[aria-current="page"] .nav__icon { background: var(--brand-soft); }

    /* A barra é fixa e flutua sobre o conteúdo, então o conteúdo precisa
       abrir mão da altura dela. Esse número é MEDIDO em JavaScript e chega
       aqui como --nav-space (ver main.js).
       Três tentativas de escrevê-lo à mão falharam: a área segura vale 0 num
       aparelho e 48px em outro, e o valor fixo de 4rem sobrava por 1rem. O
       6rem abaixo é só a rede de segurança para o instante antes de a medição
       acontecer — generoso de propósito, porque sobrar espaço é invisível e
       faltar esconde um botão. */
    .composer { padding-bottom: calc(var(--nav-space, 6rem) + var(--space-3)); }
    .voice    { padding-bottom: calc(var(--nav-space, 6rem) + var(--space-6)); }
  }
}

@layer views {
  /* O chat é tela de altura fixa: a lista de mensagens rola por dentro e o
     campo de escrever fica colado acima da barra de baixo.

     A casca usa `min-height: 100dvh` porque as telas de conteúdo (início,
     histórico, debrief) precisam poder crescer e rolar na página. No chat isso
     trabalha contra: com o teclado aberto o viewport encolhe (o app recua o
     contêiner pelo inset do teclado) e a casca ficava maior que a tela — a
     página inteira ganhava rolagem, o `focus()` do campo rolava tudo, o
     cabeçalho do chat sumia atrás da barra de cima e o menu subia para o meio
     da tela (captura de 23/09/2026, teclado aberto).

     `height` prende a casca ao viewport: quem rola é a lista, não a página.
     `:has()` porque é o chat que pede — as outras telas não mudam nada.

     A guarda de altura é para o caso de o viewport ser menor do que o próprio
     chat (campo de escrever + cabeçalho + menu, ~390px): aí não há o que
     prender e a página poder rolar é o que mantém tudo alcançável. */
  @media (min-height: 400px) {
    .app:has(> #outlet > .chat) {
      height: 100vh;      /* rede para WebView sem `dvh` */
      height: 100dvh;
      min-height: 0;
    }
  }

  /* A voz também tem a altura da tela, sem guarda de altura mínima: se o
     conteúdo não couber, quem rola é a própria .voice, não a página. Com a
     página rolando, o microfone e o "Encerrar" ficavam debaixo do menu. */
  .app:has(> #outlet > .voice) {
    height: 100vh;
    height: 100dvh;
    min-height: 0;
  }

  /* ── teclado aberto (marca posta por teclado.js) ─────────────────────
     Com o teclado, a tela do celular fica com uns 450px, e a moldura do chat
     somava ~445px: barra de cima, alvos, nível, Voz/Encerrar, a frase de
     ajuda e o menu de baixo, que subia junto com o teclado. As mensagens
     ficavam sem espaço nenhum (captura de 23/09/2026, 360×517).

     Enquanto a pessoa escreve, fica só o que serve a escrever: as mensagens
     e o campo. Fechar o teclado devolve tudo — é por aí que se chega ao menu,
     ao nível e ao "Encerrar e analisar". A barra de abas sumir com o teclado
     é o que WhatsApp, Telegram e Instagram fazem. */
  @media (max-width: 640px) {
    :root[data-teclado] .nav { display: none; }
  }

  /* Chamada de voz no ar: no celular a tela é só a chamada (voice-view.js,
     emChamada). A barra de cima e o menu levariam para fora — e sair da tela
     derruba a chamada. Sai-se pelo "Encerrar e analisar". */
  @media (max-width: 640px) {
    :root[data-chamada] .nav,
    :root[data-chamada] .app:has(> #outlet > .voice) > .topbar { display: none; }
  }
  :root[data-teclado] .app:has(> #outlet > .chat) > .topbar,
  :root[data-teclado] .chat__bar,
  :root[data-teclado] .composer__actions,
  :root[data-teclado] .composer .composer__hint { display: none; }

  /* Sem a moldura o chat cabe em qualquer altura, então a guarda de 400px
     acima não se aplica: com o teclado ela é justamente o caso comum, e sem a
     casca presa a página inteira rolaria de novo. */
  :root[data-teclado] .app:has(> #outlet > .chat) {
    height: 100vh;
    height: 100dvh;
    min-height: 0;
  }
  /* A barra de cima era quem descontava a área do entalhe; sem ela, a
     primeira mensagem desconta. No app `--safe-top` é zero (a janela já vem
     recuada). */
  :root[data-teclado] .chat__scroll {
    padding-top: calc(var(--space-4) + var(--safe-top, env(safe-area-inset-top, 0px)));
  }
}

@layer views {
  /* Esta regra PRECISA estar na camada `views`.
     `.view { padding }` é declarada em @layer views; a ordem no topo do
     arquivo é `base, components, views`, e camada vence camada — antes de
     especificidade, antes de media query, antes de tudo. A mesma regra
     escrita em `components` é simplesmente ignorada, sem erro e sem aviso.
     Foi o que manteve o botão de "Começar a conversar" debaixo da barra por
     três correções seguidas: a regra que eu ajustava nunca chegava a valer. */
  @media (max-width: 640px) {
    .view { padding-bottom: calc(var(--nav-space, 6rem) + var(--space-6)); }
  }
}

@layer components {
  /* Saldo sempre à vista: crédito que some sem o usuário perceber vira
     surpresa no meio de uma conversa. */
  .balance {
    display: inline-flex; align-items: center; gap: var(--space-1);
    padding: var(--space-1) var(--space-3); border-radius: var(--radius-full);
    background: var(--surface-hover); color: var(--text-muted);
    font-size: var(--text-xs); font-weight: 600; font-variant-numeric: tabular-nums;
    text-decoration: none; transition: background var(--dur-fast);
  }
  /* Minutos de voz (16/10/2026): o microfone de ICON.voz (icons.js) por
     máscara, para seguir a cor do texto como os outros ícones. O glifo "◈"
     vinha da fonte, mudava de desenho por aparelho e lembrava moeda.
     Voltar: content: "◈" e tirar a máscara. */
  .balance {
    --balance-icone: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15.2 6.4a3.2 3.2 0 0 0-6.4 0v5.2a3.2 3.2 0 0 0 6.4 0Z'/%3E%3Cpath d='M5.6 11.4a6.4 6.4 0 0 0 12.8 0'/%3E%3Cpath d='M12 17.8v2.9'/%3E%3C/svg%3E");
  }
  .balance::before {
    content: ""; width: 1rem; height: 1rem; flex: none;
    color: var(--brand-400); background: currentColor;
    -webkit-mask: var(--balance-icone) center / contain no-repeat;
    mask: var(--balance-icone) center / contain no-repeat;
  }
  .balance:hover { background: var(--border); color: var(--text); }
  .balance--empty {
    background: color-mix(in oklch, var(--focus-500) 18%, transparent);
    color: var(--focus-500);
  }
  .balance--empty::before { color: var(--focus-500); }
}


/* Abertura do app (11/10/2026): até o app saber quem é a pessoa (/api/me) e
   desenhar o menu, as barras ficam escondidas. O HTML traz o menu antigo como
   ponto de partida, e ele aparecia por um instante antes do atual. Fora das
   camadas e com !important para nenhuma regra de componente reacender a barra.
   Se demorar mais de 0,3 s, um ponto da marca pulsa no meio da tela. */
body[data-iniciando] .topbar, body[data-iniciando] .nav { visibility: hidden !important; }
body[data-iniciando]::after {
  content: ""; position: fixed; left: 50%; top: 50%; width: 16px; height: 16px; border-radius: 50%;
  background: var(--ui-brand); transform: translate(-50%, -50%); opacity: 0;
  animation: l2t-abrindo 1.2s ease-in-out .3s infinite;
}
@keyframes l2t-abrindo { 0%, 100% { opacity: .25; transform: translate(-50%, -50%) scale(.8); } 50% { opacity: 1; transform: translate(-50%, -50%) scale(1); } }
@media (prefers-reduced-motion: reduce) { body[data-iniciando]::after { animation: none; opacity: .6; } }
