/* ============================================================
   MUSIKAI — camada compartilhada pelas QUATRO páginas
   (mural, afinador, percepção, aulagame)

   Só componentes NOVOS + ajustes da tab bar de 4 itens. Toda a
   paleta vem das variáveis já definidas em cada página
   (--panel, --ink, --phosphor, --hue-s/--hue-l, --on-hue …),
   por isso este arquivo funciona nos dois temas sem cópia de cor.
   Carregado DEPOIS do <style> inline: pode sobrepor a tab bar.
   ============================================================ */

/* ---------- TAB BAR — dono único (teste-estilo.mjs, DONOS) ----------
   A barra inferior das QUATRO páginas mora INTEIRA aqui: desenho, modo
   A+ e os apertos de largura. Até esta rodada só os ajustes moravam
   nesta camada e o CSS-base estava copiado nos quatro <style> — a
   dívida que o PR #97 deixou anotada quando subiu os ESTADOS da barra.
   Agora há um dono só: quem for mexer na barra mexe AQUI, e o teste
   reprova a página que voltar a declará-la.

   A ORDEM DESTE BLOCO É CONTRATO, e não arrumação:
       base → A+ → ≤600px → ≤430px → ≤330px
   As faixas vêm depois para vencer por ORDEM (a especificidade é a
   mesma), e o A+ vem ANTES delas para que o `font-size:11px` de ≤430
   continue ganhando dos 12px do A+ — que é exatamente o que o aluno já
   via, com o <style> do aulagame pedindo 12px e esta camada, carregada
   depois, corrigindo para 11.

   z-index 20 = ABAIXO da barra de instalação do afinador (30), da barra
   da trilha, dos toasts (60), do picker de reação (70) e do splash (100).
   ESCONDER a barra na trilha e na lição não é assunto deste bloco: é
   estado do <body> e mora no fim deste arquivo, junto do pino --tabbar-h. */
.tabbar{
  position:fixed;left:0;right:0;bottom:0;z-index:20;
  display:flex;align-items:stretch;
  background:var(--panel-2);
  border-top:1px solid var(--line);
  padding-bottom:env(safe-area-inset-bottom,0px);
  -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
}
/* o padding lateral é 3px, e não os 4px que as páginas traziam: com 4
   itens, em 360px cada um fica com 90px, e "PERCEPÇÃO" só cabe sem
   quebrar com rótulo menor e padding curto. Eram duas regras — a base
   da página e a correção desta camada —; no mesmo arquivo, viraram uma.
   Altura de toque continua >=44px. */
.tabbar a{
  flex:1 1 0;min-width:0;min-height:58px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;
  padding:8px 3px;
  color:var(--ink-dim);text-decoration:none;
  font-size:12px;letter-spacing:.06em;text-transform:uppercase;line-height:1;
  transition:color .15s,background-color .15s;
}
.tabbar a svg{
  width:24px;height:24px;flex:none;
  fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round;
}
.tabbar a:hover{color:var(--ink)}
.tabbar a[aria-current="page"]{color:var(--phosphor);background:rgba(var(--phos-rgb),.07)}
.tabbar a:focus-visible{outline:2px solid var(--phosphor);outline-offset:-3px;border-radius:0}

/* MODO A+ DA BARRA. Vinha só do <style> do aulagame, e o efeito era uma
   divergência de dono ausente, não uma decisão: o A+ engordava a barra
   numa página e deixava as outras três com a barra de sempre. O pino
   `--tabbar-h` sobe junto porque é ele que reserva o respiro do corpo —
   sem ele a barra cresceria 6px e o corpo continuaria calculado em 58. */
body.hivis{--tabbar-h:calc(64px + env(safe-area-inset-bottom, 0px))}
body.hivis .tabbar{border-top-width:2px}
body.hivis .tabbar a{min-height:64px;font-size:12px;gap:4px}
body.hivis .tabbar a svg{width:28px;height:28px;stroke-width:2}
body.hivis .tabbar a[aria-current="page"]{background:rgba(var(--phos-rgb),.12)}

/* CELULAR (≤600px): barra mais baixa e rótulo miúdo. Vinha copiada em
   percepção e aulagame; passa a valer nas quatro, que é o que se espera
   de uma barra que é a MESMA barra em todas elas. Mural e afinador
   ganham item de 52px no lugar de 58 — alvo de toque ainda acima dos 44
   do mínimo — e o rótulo de 9px que as outras duas já tinham. */
@media (max-width:600px){
  .tabbar{padding-bottom:max(env(safe-area-inset-bottom,0px),6px)}
  .tabbar a{min-height:52px;padding:6px 2px}
  .tabbar a span{font-size:11px}
}
@media (max-width:430px){
  .tabbar a{font-size:11px;letter-spacing:.03em;padding:8px 2px;gap:2px}
  .tabbar a svg{width:21px;height:21px}
  body.hivis .tabbar a{font-size:12.5px;letter-spacing:0}
  body.hivis .tabbar a svg{width:24px;height:24px}
}
@media (max-width:330px){
  .tabbar a{font-size:10px;letter-spacing:0}
}
/* O RÓTULO DA TAB BAR MORA NUM <span>, e é por isso que esta regra
   existe. O aperto de ≤600px acima acerta `.tabbar a span` DIRETO,
   enquanto todo o A+ mira o <a> e conta com a HERANÇA para chegar ao
   rótulo — e valor herdado perde para qualquer regra que acerte o
   <span>, por menos específica que seja. Resultado: no A+ o rótulo
   voltava a 9px e a letra do modo de baixa visão ficava MENOR que a do
   modo normal. Devolver a herança aqui conserta isso. Enquanto o `9px`
   morava no <style> de aulagame e percepção, o que salvava era esta
   camada carregar depois; agora os dois são vizinhos e quem decide é a
   especificidade, que não depende de ordem de carregamento nenhuma.
   (Fica fora de @media de propósito: o A+ vale em toda largura.) */
body.hivis .tabbar a span{font-size:inherit}

/* ============================================================
   MODO A+ (alta visibilidade) — das QUATRO páginas
   O botão nasceu no aulagame; aqui ele vira camada comum. Regras
   escritas para NÃO regredir o aulagame: onde o seletor coincide
   com o que ele já tinha no <style> inline (este arquivo carrega
   depois e venceria por ordem), os valores são os mesmos.
   Princípio: tipografia maior, borda mais grossa, alvo maior e
   tinta no contraste máximo — sem trocar o fundo das páginas.
   ============================================================ */
/* CORREÇÃO DE UM COMENTÁRIO QUE ERA FALSO: até aqui este bloco
   afirmava que, onde o seletor coincidia com o do <style> do aulagame,
   "os valores são os mesmos". Não eram. O aulagame trazia um segundo
   jogo de --ink-dim/--ink-faint/--line/--line-2, e medindo com a curva
   sRGB de verdade ele perdia em TODO par cor×fundo. O que decidiu foi
   o filete do tema claro: o azul-acinzentado #9aa1a8 do aulagame ficava
   entre 2.08:1 e 2.61:1 sobre papel, painel e poço — abaixo dos 3:1 de
   componente não-textual —, enquanto o #8b8271 terroso daqui fica entre
   3.02:1 e 3.80:1 e passa em todas as superfícies. Como este arquivo
   carrega depois, o jogo bom já era o que o aluno via; a cópia só
   esperava alguém editar o arquivo errado. Agora há um dono só.
   (Fica registrado o que NÃO foi resolvido aqui: no tema escuro
   --line rende ~2.0:1 e --line-2 ~2.8:1 sobre o painel do A+ nos dois
   jogos. Mexer nisso é decisão de paleta do dono, não faxina de
   duplicação, e por isso não foi feito de carona.) */
.msk-hivis{
  flex:none;min-width:48px;min-height:44px;padding:0 12px;border-radius:10px;
  border:2px solid var(--line-2);background:var(--well);
  font-size:16px;font-weight:800;letter-spacing:.02em;color:var(--ink-dim);
  transition:color .15s,background .15s,border-color .15s;
}
.msk-hivis:hover{color:var(--ink);border-color:var(--phosphor)}
.msk-hivis[aria-pressed="true"]{
  background:var(--phosphor);border-color:var(--phosphor);color:var(--on-hue);
}

/* tinta e filete no contraste máximo de cada tema (medido ≥9:1 sobre
   panel, panel-2 e well nos dois temas) */
body.hivis{
  --ink:#ffffff;--ink-dim:#dfe4e8;--ink-faint:#c2c9cf;
  --line:#454e56;--line-2:#5a646d;
}
:root[data-theme="light"] body.hivis{
  --ink:#000000;--ink-dim:#1f242a;--ink-faint:#333a41;
  --line:#8b8271;--line-2:#6b6353;
}
body.hivis :focus-visible{outline:3px solid var(--phosphor);outline-offset:2px}

/* cabeçalho e componentes compartilhados */
body.hivis .brand b{font-size:16px}
body.hivis .brand span{font-size:12px}
body.hivis .msk-chip{font-size:13px;border-width:2px}
body.hivis .msk-badge{font-size:10.5px}
body.hivis .msk-sel-box{border-width:2px}
body.hivis .msk-sel-box h2{font-size:19px}
body.hivis .msk-sel-hint{font-size:13.5px}
body.hivis .msk-pcard{border-width:2px;min-height:66px}
body.hivis .msk-pcard .msk-pmeta b{font-size:17px}
body.hivis .msk-pcard .msk-pmeta small{font-size:13px}
body.hivis .msk-add{font-size:13px;border-width:2px}

/* controles de página (percepção, afinador, aulagame usam as mesmas classes) */
body.hivis .seg{border-width:2px}
body.hivis .seg button{font-size:13px;border-right-width:2px}
body.hivis .seg button[aria-pressed="true"]{color:var(--on-hue);background:var(--phosphor)}
body.hivis .btn{font-size:15px;border-width:3px;min-height:56px}
body.hivis .hint{font-size:15px;color:var(--ink-dim)}
body.hivis .prompt{font-size:clamp(18px,5.2vw,23px);border-width:3px;color:var(--ink-dim)}
body.hivis .result{font-size:16.5px;border-width:3px}
body.hivis .status{font-size:13px}
body.hivis .ivgrid button{min-height:64px;border-width:3px;color:var(--ink-dim)}
body.hivis .ivgrid button b{font-size:19px}
body.hivis .ivgrid button small{font-size:11.5px}
body.hivis #voiceOut{font-size:16px}

/* A+ no MURAL (index) */
body.hivis .msec{border-width:2px}
body.hivis .msec > h2{font-size:13px;color:var(--ink-dim)}
body.hivis .me-id b{font-size:21px}
body.hivis .me-id small{font-size:13px}
body.hivis .me-trocar{font-size:12.5px;border-width:2px}
body.hivis .me-stat{border-width:2px}
body.hivis .me-stat span{font-size:12px}
body.hivis .me-stat b{font-size:21px}
body.hivis .me-lugar,body.hivis .me-instalar{border-width:2px}
body.hivis .me-lugar-pos{font-size:22px}
body.hivis .me-lugar-tit,body.hivis .me-instalar-tit{font-size:15px}
body.hivis .me-lugar-sub,body.hivis .me-instalar-sub{font-size:13.5px}
body.hivis .me-instalar-ico{width:26px;height:26px}
body.hivis .msk-ios-share{width:13px;height:17px}
body.hivis .oitem .ometa b{font-size:16px}
body.hivis .oitem .ometa small{font-size:13.5px}
body.hivis .ovazio{font-size:14.5px}
body.hivis .avcard{border-width:2px}
body.hivis .avhead b{font-size:15px}
body.hivis .avhead time{font-size:12.5px}
body.hivis .avtxt{font-size:15.5px}
body.hivis .avform textarea{font-size:16px;border-width:2px}
body.hivis .avform .avsend{font-size:12.5px;border-width:2px}

/* A+ na PERCEPÇÃO
   As abas viraram ícone + rótulo miúdo (cinco numa fileira só). No A+
   quem cresce é o ÍCONE e o alvo; o rótulo sobe só um fio, senão
   "ARMADURA" estoura a coluna de ~50px que sobra em 320px. */
body.hivis .tabs{padding:3px}
body.hivis .tabs [role=tab]{font-size:13.5px;min-height:52px;border-width:2px}
body.hivis .tabs [role=tab] svg{width:26px;height:26px;stroke-width:2}
/* em 320px nem 9,5px cabe: "ARMADURA" sairia reticente. Abaixo de 360
   o rótulo do A+ volta ao corpo normal — quem cresce continua sendo o
   ÍCONE e o alvo, que é o que o A+ existe para fazer. (A regra tem de
   morar aqui: este arquivo carrega depois do <style> da percepção e
   venceria uma cópia lá.) */
@media (max-width:360px){
  body.hivis .tabs [role=tab]{font-size:12px;letter-spacing:0;padding:8px 10px}
}
/* rótulo maior precisa de coluna maior: em 92px "FUNDAMENTAL" invadia o controle */
body.hivis .config .field > span{font-size:12px;width:118px;color:var(--ink-dim)}
body.hivis .score{font-size:13.5px}
body.hivis .foot{font-size:12.5px}

/* A+ no AFINADOR — letras do círculo e leitura central maiores
   (é o pedido do professor: no celular o mostrador ficava pequeno) */
body.hivis .note-btn{font-size:clamp(17px,5.2vmin,25px);font-weight:800}
@supports (font-size:1cqw){
  body.hivis .note-btn{font-size:clamp(17px,5.9cqw,26px)}
}
body.hivis #noteName{font-size:clamp(58px,21vmin,96px);font-weight:800}
body.hivis #noteOct{font-size:clamp(17px,4.6vmin,23px)}
body.hivis #solfege{font-size:14px;color:var(--ink-dim)}
body.hivis #cents{font-size:clamp(20px,5.6vmin,27px)}
body.hivis #okTag{font-size:13px}
body.hivis #freq{font-size:14.5px}
body.hivis #target{font-size:13px;color:var(--ink-dim)}
body.hivis .a4ctl button,body.hivis .a4ctl output{font-size:14px}
body.hivis .timbre button{font-size:13px}

/* ---------- AVATAR (círculo com inicial, matiz derivado do nome) ----------
   --av-h  matiz (JS)   --av-ink  tinta legível sobre o matiz (JS mede
   a luminância e escolhe preto ou branco: >=4.5:1 em qualquer hue) */
.msk-av{
  display:inline-grid;place-items:center;flex:none;
  width:var(--av-size,36px);height:var(--av-size,36px);border-radius:50%;
  background:hsl(var(--av-h,150) var(--hue-s) var(--hue-l));
  color:var(--av-ink,var(--on-hue));
  font-family:var(--font-ui);
  font-size:calc(var(--av-size,36px) * .44);font-weight:800;line-height:1;
  text-transform:uppercase;
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.16);
}

/* ============================================================
   CABEÇALHO ÚNICO das quatro páginas (musikaiApp.cabecalho)
   Uma linha só, nesta ordem: logo · BOM DE OUVIDO · ⚙ · ? · bolinha.
   Antes cada página trazia a sua cópia deste bloco no <style> — e as
   quatro divergiram. Aqui é uma vez só.

   O tamanho é ditado por 320×568 SEM ROLAGEM: a linha inteira mede
   30px de altura (41px com o respiro e o filete), e em largura os
   cinco itens com gap de 14px cabem em 296px (320 menos o respiro do
   chassi) com folga. O gap de 14px não é estético: com botões de 30px
   ele é exatamente o que faz as áreas de toque de 44px encostarem sem
   se sobrepor.
   ============================================================ */
/* O CABEÇALHO FICA FIXO EM TODAS AS PÁGINAS. Ele carrega a marca, o ⚙, o ?
   e o crachá do perfil — os quatro controles permanentes do app. Com ele
   rolando junto, numa trilha de 9.000px o aluno perde os quatro de vista e
   precisa voltar ao topo de um percurso inteiro para alcançar qualquer um.
   `sticky` e não `fixed`: ele continua ocupando o lugar dele no fluxo,
   então nada embaixo precisa de compensação de altura. O fundo é o do
   painel porque o conteúdo passa POR BAIXO dele ao rolar. */
/* ---------- A ESCALA DO DESKTOP ----------
   O app foi desenhado para o celular: cada medida é px literal, e o painel
   é `min(94vw,560px)`. Num monitor de 1920 isso vira uma coluna estreita
   de letra pequena no meio de um vazio — o dono testou e a palavra dele
   foi "muito pequeno tudo".

   `zoom` E NÃO UMA TABELA DE TAMANHOS. Trocar as centenas de px por uma
   escala em cada regra seria reabrir todas as contas de layout do app —
   inclusive a de 320×568, que é medida e coberta por teste. `zoom` amplia
   o painel INTEIRO por igual: as proporções já medidas continuam valendo,
   e o que muda é só quantos pixels de tela cada uma ocupa.

   NO PAINEL, E NUNCA NA RAIZ. Zoom na raiz muda a largura de viewport que
   a própria media query lê: a 1,18 um viewport de 1200 vira 1017, a regra
   deixa de valer, o zoom sai, o viewport volta a 1200 — e a tela pisca
   entre os dois estados para sempre. Aplicado a um filho, a media query
   continua lendo a largura real.

   A TAB BAR E A BARRA FLUTUANTE FICAM DE FORA de propósito: as duas são
   `position:fixed` e pertencem à janela, não ao painel. Ampliá-las junto
   comeria altura de tela em vez de dar.

   E NÃO AMPLIA EM JANELA BAIXA: tela larga e curta é o formato de quem já
   ampliou o navegador (ou de um notebook 16:9 apertado), e ali a altura é
   o recurso escasso — ampliar tiraria linhas em vez de dar tamanho. */
@media (min-width: 1200px){ .unit{zoom:1.18;--zoom:1.18} }
@media (min-width: 1600px){ .unit{zoom:1.34;--zoom:1.34} }
@media (min-width: 1200px) and (max-height: 760px){ .unit{zoom:1;--zoom:1} }

/* o painel entrega o respiro de cima ao cabecalho — ver o comentario dele */
/* QUEM MONTA O CABEÇALHO AVISA O PAINEL, com uma classe. Isto era
   `.unit:has(> .msk-topo)`, e `:has()` não existe em WebView antiga (Chrome
   < 105) — que é o que roda dentro do app da loja. Sem `:has()` a regra
   INTEIRA cai, o painel mantém o respiro de cima e sobra uma faixa acima do
   cabeçalho: foi o que o dono viu no aparelho (print de 08/09/2026), e o que
   eu reproduzi no Chrome apagando as regras com `:has` (o cabeçalho desceu
   17px). Seletor de classe qualquer navegador entende. */
.unit.msk-com-topo{padding-top:0}
.msk-topo{
  display:flex;flex-wrap:nowrap;align-items:center;gap:14px;
  padding-bottom:10px;border-bottom:1px solid var(--line);
  position:sticky;top:0;z-index:30;background:var(--panel);
  /* O FUNDO TEM DE ATRAVESSAR O RESPIRO DO PAINEL. O cabeçalho vive dentro
     do `.unit`, que tem `padding:clamp(16px,3.4vw,26px)`: com o fundo
     parando na caixa de conteúdo, o texto que rola por baixo aparecia nas
     duas calhas laterais e no cantinho arredondado — foi o que o dono viu.
     Margem negativa do tamanho do respiro, e o respiro devolvido como
     padding: o fundo vai de borda a borda sem mover nada de lugar. */
  /* O RESPIRO VEM DO PAINEL, por `--unit-pad`, e nao de um `clamp` repetido
     aqui. Repetido, ele so acertava nas paginas cujo respiro por acaso
     batia com este numero — o mural e o afinador usam 28px e ficavam com o
     cabecalho afundado, com uma faixa de painel por cima dele. */
  /* NO EIXO VERTICAL NAO HA MARGEM NEGATIVA, e isso e decisao e nao
     esquecimento: ela funcionava em duas paginas e nao nas outras duas, e
     eu nao consegui provar por que — margem negativa de topo em elemento
     `sticky` depende de coisas demais (contexto de formatacao do pai,
     colapso, ordem das folhas). Em vez de insistir num mecanismo que eu
     nao sei explicar, o painel PERDE o respiro de cima e quem o carrega e
     o cabecalho: o resultado e o mesmo, vale em qualquer pagina, e nao
     tem como ser derrotado por um reset.
     No eixo horizontal a margem negativa fica, porque ali ela funciona nas
     quatro (medido) e nao ha alternativa tao simples. */
  margin-inline:calc(-1 * var(--unit-pad, 16px));
  padding-inline:var(--unit-pad, 16px);
  padding-top:var(--unit-pad, 16px);
}
.msk-topo-logo{
  /* o gap da linha é 14px porque é isso que separa duas áreas de toque
     de 44px. Logo e nome, porém, não são dois controles: são UMA marca,
     e 14px os desmancharia. A margem negativa os aproxima para 7px sem
     precisar de um wrapper só para isso — wrapper que faria a primeira
     linha ter quatro caixas em vez das cinco coisas que o dono listou. */
  /* 20px ERA POUCO, e o motivo é que a marca mudou de desenho debaixo desta
     regra: o disco tracejado antigo era uma silhueta simples, que se lia em
     qualquer tamanho; a marca nova é uma fruta com bico, raminho e olho —
     em 20px o bico some e sobra uma bolinha. 28px é o menor tamanho em que
     as quatro partes ainda se distinguem no celular.
     NÃO ESTOURA A PRIMEIRA LINHA: ela é medida (320px com ⚙, ? e o crachá,
     todos de 44px), e a marca não é alvo de toque — os 8px que ela cresce
     saem do espaço que o nome já cedia por `clamp`. */
  width:28px;height:28px;flex:none;display:block;margin-right:-7px;
}
/* o viewBox da marca é 106×90 (mais larga que alta): sem isto ela seria
   espremida numa caixa quadrada e o bico encurtaria */
.msk-topo-logo{aspect-ratio:106/90;height:auto}
.msk-topo-nome{
  /* margin-right:auto empurra ⚙ · ? · bolinha para a direita sem um
     wrapper só para isso. O corpo do nome encolhe com a tela (clamp)
     porque "BOM DE OUVIDO" tem 13 caracteres e é ele, não os botões,
     quem decide se a linha cabe em 320px: os três controles ocupam
     118px fixos (3 alvos de 44px), o que sobra é dele.
     .06em, e não os .18em de antes: com o cabeçalho em UMA linha cada
     0.01em custa ~1.4px, e medido em 320px o nome era cortado. */
  margin-right:auto;min-width:0;
  font-size:clamp(11px,3.2vw,14px);font-weight:800;letter-spacing:.06em;
  text-transform:uppercase;color:var(--ink);white-space:nowrap;
}
.msk-topo-nome em{font-style:normal;color:var(--phosphor)}
/* A borda é --ink-faint, e não --line-2 como no cabeçalho antigo: medido
   contra o chassi, --line-2 dá 1.58:1 no escuro e 2.52:1 no claro, e
   reprova o mínimo de 3:1 de limite de componente (WCAG 1.4.11). O
   comentário que estava nas páginas afirmava ">=3:1 nos dois temas" sem
   nunca ter sido medido. --ink-faint já é ≥4.5:1 sobre panel/well nos
   dois temas — é o token que o repositório mantém para isso. */
.msk-topo-btn{
  position:relative;flex:none;display:inline-grid;place-items:center;
  width:30px;height:30px;border-radius:9px;
  border:1.5px solid var(--ink-faint);background:var(--well);color:var(--ink);
  font-size:17px;font-weight:800;line-height:1;
  transition:border-color .15s,color .15s,box-shadow .15s;
}
.msk-topo-btn::after{content:"";position:absolute;inset:-7px}  /* alvo de 44px */
.msk-topo-btn:hover{border-color:var(--phosphor);color:var(--phosphor)}
.msk-topo-btn svg{
  width:18px;height:18px;display:block;
  fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;
}
/* A bolinha do perfil: a mesma caixa de 30px dos botões, redonda.
   Foi um chip com o nome do aluno por extenso e comia a linha inteira. */
.msk-chip{
  position:relative;flex:none;display:inline-grid;place-items:center;
  width:30px;height:30px;padding:0;border-radius:50%;
  border:1.5px solid var(--ink-faint);background:var(--well);
  transition:border-color .15s;
}
.msk-chip::after{content:"";position:absolute;inset:-7px}      /* alvo de 44px */
.msk-chip[hidden]{display:none}
.msk-chip:hover{border-color:var(--phosphor)}
.msk-chip .msk-av{--av-size:26px}
/* ---------- O MENU DO AVATAR (rodada 2 da reforma) ----------
   Marca à esquerda, bolinha à direita, e o resto atrás dela: Minha conta,
   Configurações (o #configBtn de sempre) e o "?" (o #helpBtn de sempre).
   O menu pendura do cabeçalho, que é sticky — por isso o absolute cola
   nele e acompanha a rolagem. Os dois botões perdem a caixa de 30px e
   viram linhas de 48px com rótulo; o alvo falso de 44px (::after) sai
   porque a linha inteira já é o alvo. */
.msk-menu{
  position:absolute;right:var(--unit-pad,16px);top:calc(100% - 4px);z-index:31;
  min-width:224px;padding:6px;border:1px solid var(--line);border-radius:12px;
  background:var(--panel);box-shadow:0 12px 32px rgba(0,0,0,calc(.35 * var(--shadow-k,1)));
}
.msk-menu[hidden]{display:none}
/* o "⋯" escondido pela bolinha: o inline-grid do botão venceria o [hidden]
   das páginas que não o declaram com !important (o mural) */
.msk-topo-btn[hidden]{display:none}
.msk-menu .msk-topo-btn,.msk-menu .msk-menu-item{
  position:static;display:flex;align-items:center;gap:10px;width:100%;height:auto;min-height:48px;
  padding:8px 12px;border:0;border-radius:8px;background:none;color:var(--ink);
  font-size:14px;font-weight:600;letter-spacing:0;line-height:1.2;text-align:left;
}
.msk-menu .msk-topo-btn::after{content:none}
.msk-menu .msk-topo-btn:hover,.msk-menu .msk-menu-item:hover{background:var(--well);color:var(--ink)}
.msk-menu .msk-topo-btn svg{width:20px;height:20px;flex:none;color:var(--ink-dim)}
.msk-menu-glifo{flex:none;width:20px;text-align:center;font-size:17px;font-weight:800;color:var(--ink-dim)}
.msk-menu-rotulo{flex:1;min-width:0}
body.hivis .msk-menu .msk-topo-btn,body.hivis .msk-menu .msk-menu-item{font-size:16px;min-height:52px}
/* A+ (baixa visão): os glifos engordam, mas a CAIXA de 30px não muda —
   crescer a linha aqui é rolagem garantida no 320px. O nome do app
   também não cresce em tela estreita (o piso do clamp é o mesmo): A+
   existe para o aluno ler o EXERCÍCIO, e a marca é a única coisa da
   tela que ele já sabe de cor. Onde sobra largura, ele cresce. */
body.hivis .msk-topo-nome{font-size:clamp(11px,3.6vw,16px)}
body.hivis .msk-topo-btn{border-width:2px;font-size:19px}
body.hivis .msk-chip{border-width:2px}
body.hivis .msk-topo-btn svg{stroke-width:2.4}

.msk-badge{
  font-size:10px;letter-spacing:.1em;text-transform:uppercase;font-weight:700;
  padding:3px 6px;border-radius:6px;
  background:rgba(var(--phos-rgb),.14);color:var(--phosphor);
  border:1px solid rgba(var(--phos-rgb),.35);
}
/* selo do plano no cartão da conta: "Completo" usa a cor da marca (o de
   cima); "Grátis" é neutro, para não parecer conquista. Quando é botão
   (só no aulagame), herda a fonte e ganha a mãozinha. */
.msk-selo-plano{margin-left:auto;font-family:inherit;white-space:nowrap}
.msk-selo-gratis{background:transparent;color:inherit;border-color:currentColor;opacity:.85}
button.msk-selo-plano{cursor:pointer}

/* ---------- SPLASH de entrada (anel cromático + carregamento) ----------
   z-index 100: acima de tudo, inclusive da tab bar. Sem JS o splash
   nunca aparece (a classe .msk-entrada é posta por script). */
#mskSplash{display:none}
:root.msk-entrada #mskSplash{
  display:flex;position:fixed;inset:0;z-index:100;
  flex-direction:column;align-items:center;justify-content:center;gap:18px;
  background:
    radial-gradient(900px 600px at 50% -10%, var(--glow-1) 0%, transparent 60%),
    var(--bg);
  text-align:center;color:var(--ink);font-family:var(--font-ui);cursor:pointer;
  padding:24px;
}
#mskSplash svg{width:116px;height:116px}
#mskSplash b{font-size:19px;font-weight:800;letter-spacing:.3em;text-transform:uppercase}
#mskSplash b em{font-style:normal;color:var(--phosphor)}
#mskSplash > span{font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--ink-dim)}
.msk-load{display:flex;flex-direction:column;align-items:center;gap:9px;width:min(240px,70vw)}
.msk-load-track{
  width:100%;height:4px;border-radius:99px;overflow:hidden;
  background:var(--well);border:1px solid var(--line);
}
.msk-load-bar{
  display:block;height:100%;width:8%;border-radius:99px;
  background:var(--phosphor);transition:width .25s ease-out;
}
.msk-load-txt{font-size:10.5px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-dim)}
@media (prefers-reduced-motion:no-preference){
  :root.msk-entrada #mskSplash{animation:mskFade .3s ease-out}
  /* a marca NAO gira: ela tem bico, e bico girando vira cata-vento.
     Quem carrega o movimento da tela de carga e a barra. */
  #mskSplash .spring{animation:mskPulso 2.4s ease-in-out infinite;transform-origin:50% 50%}
  @keyframes mskPulso{0%,100%{transform:scale(1)}50%{transform:scale(1.06)}}
  @keyframes mskFade{from{opacity:0}to{opacity:1}}
  @keyframes mskSpin{to{transform:rotate(360deg)}}
}

/* ---------- SELEÇÃO "Quem está tocando?" ----------
   A ESCADA DE EMPILHAMENTO DO APP, INTEIRA — conferida elemento a
   elemento no navegador, não de memória (a versão curta que ficou aqui
   antes é o que deixou passar o defeito de baixo):

      20  .tabbar ............. as quatro páginas
      30  .msk-topo ........... cabeçalho (sticky, FUNDO OPACO)
          .install-banner ..... afinador
      40  .barra-trilha/.ao-pe  aulagame
          .help-overlay ....... afinador (ajuda própria, mais antiga)
   →  45  #mskSel ............. ESTE diálogo
      55  #confete ............ aulagame
      60  #toasts ............. aulagame · .msk-dica
      70  #picker ............. aulagame
      80  .config-modal-overlay percepção
      85  .msk-ajuda .......... a ajuda explica o diálogo, então o cobre
      90  .inapp-banner ....... aulagame
     100  #mskSplash · #splash

   POR QUE 45, E NÃO ISOLAR O <main>. O `<main>` NÃO cria contexto de
   empilhamento (position:static, transform:none, opacity:1, isolation:auto
   — medido), então o cabeçalho (30) e a barra da trilha (40), que moram
   DENTRO dele, competem no mesmo contexto raiz que este diálogo. Isolar o
   <main> resolveria os dois de uma vez, mas rebaixaria de um golpe TUDO o
   que está dentro dele para o nível 0 do raiz — inclusive abaixo da tab
   bar (20), que está FORA. Mover um número para a faixa vazia entre 40 e
   55 não altera a ordem relativa de nenhum outro par do app.

   ERA 15, depois 25, e as duas estavam erradas pelo mesmo motivo: o
   diálogo se declara modal e ficava ABAIXO de coisa opaca. Em 15 a tab bar
   recebia o clique; em 25 o cabeçalho — que é `position:sticky`, tem fundo
   OPACO (var(--panel)) e z-index 30 — pintava sobre 317 x 22px do <h2> do
   diálogo, ou seja 100% do título. E o título não é enfeite: é onde o foco
   pousa ao abrir e é o alvo do `aria-labelledby`.

   O `inert` do fundo (musikai-app.js) NÃO resolvia isso e nunca resolveria:
   inert tira do foco, do leitor e do hit-test — mas o elemento CONTINUA
   PINTANDO. Tanto que `elementsFromPoint` deixa de acusar o cabeçalho
   assim que ele fica inerte, enquanto os olhos continuam vendo a barra
   opaca por cima do título. Medir cobertura por hit-test sozinho esconde
   exatamente este defeito; o que prova é a geometria mais a ordem de
   pintura (é o que teste-acessibilidade.mjs cobra).

   DUAS SESSÕES ACHARAM ESTE MESMO DEFEITO, e o merge das duas mora aqui.
   A outra (que entrou no main primeiro) diagnosticou igual — "o cabeçalho
   virou sticky em 30 e passou a flutuar por cima da tela de login" — e
   escolheu 35: acima do cabeçalho, abaixo do painel de ajuda do afinador
   (40), com o argumento de que a ajuda é modal e tem de vencer todo mundo.

   FICA 45, e não 35, porque 35 ainda perde para a BARRA DA TRILHA
   (`.barra-trilha`/`.ao-pe`, 40, aulagame): ela é cromo de página, tem
   fundo opaco e continuaria pintando sobre o diálogo — o mesmo defeito de
   antes, um degrau mais acima. E o argumento da ajuda continua honrado sem
   custo: a ajuda da CAMADA (`.msk-ajuda`, 85), que é a que explica os
   controles do próprio diálogo, sempre esteve acima dele. A ajuda do
   afinador (40) é painel local e mais antigo, e quando o diálogo abre o
   fundo inteiro fica `inert` — inclusive ela. Modal que inertiza o fundo
   tem de estar ACIMA do que inertizou, senão promete o que não cumpre. */
#mskSel{
  position:fixed;inset:0;z-index:45;overflow-y:auto;
  background:
    radial-gradient(1100px 700px at 50% -10%, var(--glow-1) 0%, transparent 60%),
    var(--bg);
  padding:calc(24px + env(safe-area-inset-top,0px)) 12px calc(var(--tabbar-h) + 24px);
  display:flex;align-items:flex-start;justify-content:center;
}
#mskSel[hidden]{display:none}
.msk-sel-box{
  width:min(94vw,560px);
  border-radius:22px;border:1px solid var(--line);
  background:linear-gradient(178deg,var(--panel) 0%,var(--panel-2) 70%);
  box-shadow:0 32px 90px rgba(0,0,0,calc(.6 * var(--shadow-k)));
  padding:clamp(16px,3.4vw,26px);
}
.msk-sel-box h2{
  font-size:17px;font-weight:800;letter-spacing:.16em;text-transform:uppercase;color:var(--ink);
}
.msk-sel-box h2:focus-visible{outline:2px solid var(--phosphor);outline-offset:4px}
.msk-sel-hint{margin-top:8px;font-size:12px;line-height:1.5;color:var(--ink-dim)}
.msk-sel-hint b{color:var(--ink)}
.msk-plist{display:flex;flex-direction:column;gap:10px;margin-top:16px}
.msk-pcard{
  display:flex;align-items:center;gap:12px;width:100%;min-height:60px;
  padding:10px 14px;border-radius:14px;text-align:left;
  border:1px solid var(--line);background:var(--well);color:var(--ink);
  transition:border-color .15s,background .15s;
}
.msk-pcard:hover{border-color:var(--line-2);background:var(--hover-fill,var(--well))}
.msk-pcard .msk-av{--av-size:42px}
.msk-pcard .msk-pmeta{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}
.msk-pcard .msk-pmeta b{font-size:15px;font-weight:700;line-height:1.2}
.msk-pcard .msk-pmeta small{font-size:11px;color:var(--ink-dim)}
.msk-pcard.msk-resume{
  border-color:rgba(var(--phos-rgb),.5);
  background:rgba(var(--phos-rgb),.07);
}
.msk-pcard .msk-cont{
  font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--phosphor);font-weight:700;
}
.msk-add{
  margin-top:14px;min-height:44px;width:100%;padding:12px;border-radius:12px;
  border:1px dashed var(--line-2);background:none;color:var(--ink-dim);
  font-size:11px;letter-spacing:.16em;text-transform:uppercase;
}
.msk-add:hover{color:var(--ink);border-color:var(--phosphor)}
/* ---------- AS TRES PORTAS DA ENTRADA (28/09/2026) ----------
   Escola | Professor | Aluno, no topo da tela de entrar. A porta ativa leva
   `aria-selected="true"` e e isso que a pinta — sem :has(), que a WebView do
   app nao tem. Alvo de toque de 44px. */
.msk-portas{display:flex;gap:6px;margin:0 0 14px}
.msk-portas button{
  flex:1 1 0;min-width:0;min-height:44px;padding:0 6px;cursor:pointer;
  border:1px solid var(--line-2);background:none;color:var(--ink-dim);
  font-size:12px;letter-spacing:.12em;text-transform:uppercase;
}
.msk-portas button[aria-selected="true"]{color:var(--ink);border-color:var(--phosphor)}
/* ---------- OS ADENDOS DA ENTRADA, LADO A LADO ----------
   Três botões de largura inteira empilhados eram o que empurrava a tela de
   entrada para além da dobra. Aqui eles pareiam quando cabem e voltam a
   ocupar a linha quando não cabem — `flex-basis` em porcentagem com
   `flex-wrap`, sem media query, porque quem decide é a largura real do
   texto e não um limiar que eu chutaria.
   O alvo de toque não muda: `min-height:44px` continua em `.msk-add`. */
.msk-adendos{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
.msk-adendos .msk-add{
  flex:1 1 44%;width:auto;margin-top:0;
  /* SEM `min-width:0` O PAREAMENTO NÃO ACONTECE: um item de flex não
     encolhe abaixo da largura do próprio conteúdo, e "escolher um nome
     lembrado" em caixa alta com 0,16em de espaçamento é mais largo que
     meia coluna — então cada botão continuava tomando a linha inteira,
     que foi o que a medição mostrou. Com o piso liberado e o texto podendo
     quebrar em duas linhas, eles pareiam. */
  min-width:0;white-space:normal;line-height:1.25;
  letter-spacing:.1em;padding:10px 8px;
}
/* ---------- botão VISITANTE (a porta de quem não tem turma) ----------
   É um .msk-add — mesma caixa, mesma largura de coluna, o mesmo alvo de
   44px — com DUAS diferenças e só duas: traço inteiro em vez de
   tracejado e a tinta do fósforo. O tracejado é o dialeto de "adendo"
   desta tela ("usar outro nome", "adicionar aluno", "continuar sem
   entrar"), e o visitante não é adendo: é uma das portas de entrada.

   O ALFA DE .75 É MEDIDO, não escolhido no olho. O rótulo em --phosphor
   sobre --panel rende 5.34:1 no escuro e 6.57:1 no claro (curva sRGB de
   verdade, conta no teste-visitante) e passa em AA com folga. A BORDA é
   o que precisava de conta: a 45% — o alfa que o botão "entrar" usa —
   ela rende 2.03:1 e 2.06:1, abaixo dos 3:1 que a WCAG 1.4.11 pede para
   o contorno de um controle. A 75% rende 3.52:1 e 3.76:1, e passa nos
   dois temas. Nenhuma cor nova entrou: só o alfa mudou.
   (Fica registrado o que NÃO foi resolvido aqui: o mesmo 2:1 vale para
   o .msk-addform button de todos os formulários desta tela, e mexer
   nisso é decisão de paleta do dono — não carona desta entrega.) */
.msk-add.msk-visita{
  border-style:solid;border-color:rgba(var(--phos-rgb),.75);color:var(--phosphor);
}
.msk-add.msk-visita:hover{border-color:var(--phosphor);color:var(--phosphor)}
/* ---------- A LINHA DA TURMA na porta de entrada ----------
   Também é um .msk-add — mesma caixa, mesma largura de coluna, os mesmos
   44px de alvo — e nenhuma cor nova entrou: o rótulo segue em --ink-dim,
   como os vizinhos, e só o CÓDIGO sobe para --ink, exatamente o par que a
   .msk-sel-hint já usa para destacar o que importa dentro de um texto
   apagado. O que muda é a CAIXA das letras: text-transform:uppercase serve
   a rótulo ("USAR OUTRO NOME"), mas metade deste é um código que o aluno
   compara caractere a caractere com o que o professor mandou no WhatsApp —
   trocá-lo por maiúsculas é perder fidelidade justamente no dado que
   precisa ser conferido. O letter-spacing cai junto pelo mesmo motivo (um
   código espaçado se lê letra a letra, não como palavra) e é ele que
   mantém o rótulo inteiro em UMA linha a 320px, que é o que segura a
   altura desta tela. */
.msk-add.msk-turma{text-transform:none;letter-spacing:.01em;font-size:12.5px}
.msk-add.msk-turma b{color:var(--ink);font-weight:700}
/* ---------- ENTRAR COM GOOGLE: o único botão em destaque da entrada ----------
   Coluna inteira, 52px de alto e o texto no tamanho de título de cartão:
   é a ação principal e tem de parecer a principal — o PIN e a senha, que
   ficaram atrás dos adendos, continuam com o traço fino do .msk-add. As
   cores são as do resumo (.msk-pcard.msk-resume): o fósforo da casa em
   fundo translúcido, nenhum tom novo. `.msk-google-gis` é o lugar do botão
   OFICIAL do Google quando o One Tap não abre — ele vem com o desenho dele
   (o Google exige), então aqui só se centra. */
.msk-google-slot{display:flex;justify-content:center;margin-top:16px;min-height:52px}
.msk-google{
  width:100%;min-height:52px;padding:12px 16px;border-radius:14px;
  border:1px solid rgba(var(--phos-rgb),.6);background:rgba(var(--phos-rgb),.12);
  color:var(--ink);font-size:15px;font-weight:700;letter-spacing:.02em;
  transition:border-color .15s,background .15s;
}
.msk-google:hover{border-color:var(--phosphor);background:rgba(var(--phos-rgb),.2)}
.msk-google:disabled{opacity:.7}
.msk-google-gis{display:flex;justify-content:center;width:100%}
body.hivis .msk-google{border-width:2px;min-height:56px}
.msk-addform{display:flex;gap:8px;margin-top:14px;flex-wrap:wrap}
.msk-addform[hidden]{display:none}   /* display:flex venceria o [hidden] do navegador */
.msk-addform input{
  flex:1 1 160px;min-height:44px;padding:10px 12px;border-radius:12px;
  border:1px solid var(--line);background:var(--well);color:var(--ink);font-size:14px;
}
.msk-addform button{
  min-height:44px;padding:10px 16px;border-radius:12px;
  border:1px solid rgba(var(--phos-rgb),.45);background:var(--well);color:var(--phosphor);
  font-size:11px;letter-spacing:.14em;text-transform:uppercase;
}

/* ---------- OLHO: ver o segredo que se está digitando ----------
   O alternador mora DENTRO da moldura do campo, e o campo abre uma calha
   de 52px à direita só para ele. Sobrepor o botão ao texto é o erro
   clássico desta peça em tela estreita: a 375px o último dígito do PIN
   ficaria debaixo do dedo justamente na hora de conferir.

   O ALVO tem 44x44 de verdade (WCAG 2.5.5) mesmo com o desenho de 20px —
   quem toca toca no botão, não no traço. Ele cabe sem estourar porque o
   input do .msk-addform já tem min-height:44px.

   CONTRASTE MEDIDO (curva sRGB de verdade, traço sobre --well):
     escuro  ink-dim 7.29   phosphor (à mostra/hover) 5.74
     claro   ink-dim 6.40   phosphor (à mostra/hover) 5.50
     A+ escuro 15.12        A+ claro 12.43
   Todos acima dos 4.5:1 de texto normal, que é a régua mais dura das
   duas — para ícone a WCAG 1.4.11 pediria 3:1.

   Vem DEPOIS de `.msk-addform input` de propósito: as duas regras têm a
   mesma especificidade e é a ordem que decide quem ganha o padding.

   O botão é escrito como `.msk-segredo .msk-olho` (duas classes) e não
   como `.msk-olho`: no mesmo formulário mora `.msk-addform button`, que
   com uma classe + um elemento ganharia de uma classe só e devolveria ao
   olho a borda cor-de-fósforo e o fundo do botão "entrar". */
.msk-segredo{position:relative;display:block;flex:1 1 100%}
.msk-segredo input{width:100%;padding-right:52px}
.msk-segredo .msk-olho{
  position:absolute;top:50%;right:2px;transform:translateY(-50%);
  width:44px;height:44px;min-height:44px;display:grid;place-items:center;
  padding:0;border:0;background:none;color:var(--ink-dim);
  border-radius:12px;cursor:pointer;
}
.msk-segredo .msk-olho:hover{color:var(--phosphor)}
/* estado LIGADO tem duas marcas, não uma: o risco no desenho e a cor —
   cor sozinha não serve para quem não distingue as duas */
.msk-segredo .msk-olho[aria-pressed="true"]{color:var(--phosphor)}
/* o anel de foco global mora fora da caixa; aqui ele entraria por baixo
   da borda do campo, então é puxado para dentro */
.msk-segredo .msk-olho:focus-visible{outline-offset:-2px}
body.hivis .msk-segredo .msk-olho svg{width:24px;height:24px;stroke-width:2.2}

.msk-err{margin-top:10px;font-size:12.5px;color:var(--err-ink)}
.msk-err[hidden]{display:none}

/* APAGAR A CONTA: a única ação sem volta do app, e a única que se pinta com a
   cor de erro. Ela mora na segunda etapa (o botão só existe depois de a pessoa
   abrir o aviso), e a cor não vem sozinha — o rótulo já diz "para sempre". */
.msk-addform .msk-apagar{
  border-color:var(--err-ink);color:var(--err-ink);
}
.msk-addform .msk-apagar:hover{background:var(--well)}
.msk-addform .msk-apagar:disabled{opacity:.7}
body.hivis .msk-addform .msk-apagar{border-width:2px}

/* ---------- MURAL ---------- */
.mural{display:flex;flex-direction:column;gap:18px;margin-top:18px}
.msec{
  border-radius:16px;border:1px solid var(--line);background:var(--panel-2);
  padding:16px;
}
.msec > h2{
  font-size:11px;font-weight:700;letter-spacing:.2em;text-transform:uppercase;
  color:var(--ink-dim);margin-bottom:12px;
}

/* (a) cartão do meu perfil */
.me{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.me .msk-av{--av-size:64px}
.me-id{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1 1 100px}
.me-id b{font-size:19px;font-weight:800;line-height:1.15;color:var(--ink)}
.me-id small{font-size:11.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-dim)}
.me-trocar{
  flex:0 0 auto;margin-left:auto;
  min-height:44px;padding:10px 12px;border-radius:12px;
  border:1px solid var(--line);background:var(--well);color:var(--ink-dim);
  font-size:11px;letter-spacing:.1em;text-transform:uppercase;white-space:nowrap;
}
.me-trocar:hover{color:var(--ink);border-color:var(--line-2)}
.me-stats{display:flex;gap:10px;flex-wrap:wrap;width:100%;margin-top:4px}
.me-stat{
  flex:1 1 92px;min-width:92px;padding:10px 12px;border-radius:12px;
  border:1px solid var(--line);background:var(--well);
}
.me-stat span{display:flex;align-items:center;font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-dim)}
/* ícone do rótulo (a chama da ofensiva) — SVG, nunca emoji: emoji não
   herda currentColor, não acompanha o modo A+ e cada sistema desenha o
   seu. Mesma linguagem de traço da tab bar. */
.me-stat span .ico-flame{
  width:15px;height:15px;flex:none;margin-right:5px;
  fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;
}
body.hivis .me-stat span .ico-flame{width:18px;height:18px}
.me-stat b{
  display:block;margin-top:3px;font-family:var(--font-mono);
  font-size:19px;font-weight:700;color:var(--ink);
}
.me-stat b small{
  font-family:var(--font-ui);font-size:12px;font-weight:600;
  color:var(--ink-dim);margin-left:5px;letter-spacing:.04em;
}

/* (a2) meu lugar no placar — leva ao placar da turma (aulagame).
   Mora DENTRO do cartão do perfil, e não numa seção nova: seção nova
   custaria mais 44px de moldura (título + respiro do cartão) numa tela
   de 568px que já é disputada.

   DUAS FAIXAS, e não duas colunas de texto. Medido em 320px: com o
   número, a seta e os respiros, sobra uma coluna de 121px para a
   frase — e "faltam 180 XP para alcançar Duda" virava DUAS linhas, o
   primeiro lugar virava TRÊS. A frase desceu para uma faixa própria,
   de largura cheia (212px em 320px), e volta a caber numa linha.
   O bloco inteiro é o alvo de toque, e ele mede 60px — acima dos 44
   mínimos. O número usa a fonte mono: é dado, como o XP ao lado.

   A MESMA CAIXA serve à linha "Instalar no celular" (a4): é <button> no
   Chrome/Edge e texto no Safari do iPhone, e por isso a regra também zera
   o que o botão herda do agente (text-align, font). Um seletor só, e não
   uma cópia: duas caixas iguais escritas duas vezes divergem caladas.
   `.me-lugar{` fica no COMEÇO da linha de propósito — o teste do placar
   do mural lê a caixa ancorado ali. */
.me-instalar,
.me-lugar{
  display:flex;flex-wrap:wrap;align-items:center;gap:4px 12px;width:100%;
  margin-top:12px;min-height:52px;padding:9px 12px;border-radius:12px;
  border:1px solid var(--line);background:var(--well);
  color:var(--ink);text-decoration:none;text-align:left;font:inherit;
  transition:border-color .15s,background-color .15s;
}
.me-lugar:hover,button.me-instalar:hover{border-color:var(--line-2);background:var(--hover-fill)}
.me-lugar-pos{
  flex:none;min-width:34px;
  font-size:20px;font-weight:700;color:var(--phosphor);line-height:1.15;
}
.me-instalar-tit,
.me-lugar-tit{flex:1 1 auto;min-width:0;font-size:13px;font-weight:700;color:var(--ink);line-height:1.25}
/* base 100%: a frase quebra para a linha de baixo SEMPRE, e não só
   quando não couber — o que mantém a altura previsível */
.me-instalar-sub,
.me-lugar-sub{
  flex:1 0 100%;font-size:11.5px;color:var(--ink-dim);line-height:1.35;overflow-wrap:anywhere;
}
/* mesma língua de traço da tab bar: 24×24, sem preenchimento, currentColor */
.me-lugar-seta{
  flex:none;width:20px;height:20px;color:var(--ink-faint);
  fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;
}
/* (a4) instalar no celular — o que é SÓ dela: o celular com a seta na
   ponta esquerda (no lugar do número do placar), o negrito da instrução
   e o "quadrado com a seta" do iOS no meio da frase. O ícone do iOS é da
   camada (musikaiApp.instalacao.iconeCompartilhar) e mora aqui com o
   prefixo msk-, como o resto do que a camada desenha. */
.me-instalar-ico{
  flex:none;width:22px;height:22px;color:var(--phosphor);
  fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;
}
.me-instalar-sub b{color:var(--ink);font-weight:600}
.msk-ios-share{width:11px;height:14px;vertical-align:-2px;fill:currentColor;color:var(--phosphor)}

/* (a3) o que a turma já te mandou — UMA LINHA dentro do cartão do perfil.
   Sem moldura própria de propósito: o bloco do lugar, logo acima, já é uma
   caixa, e duas caixas empilhadas no mesmo cartão viram lista. Aqui é texto
   com números — a informação é curta e não é alvo de toque (nada acontece ao
   tocar), então não precisa dos 44px nem da borda.
   `flex-wrap` porque com os seis emojis a linha passa dos 268px úteis de um
   visor de 320 e quebra em duas — que é o pior caso e continua barato.
   O RÓTULO OCUPA A LINHA DELE (flex-basis:100%): no celular (12/09/2026) o
   rótulo cabia com dois chips na primeira linha e os outros quatro caíam na
   segunda, com vãos desiguais. Com o rótulo em cima, os seis chips ficam
   numa fileira só, no mesmo vão, e cada chip é indivisível (nowrap). */
.me-reacoes{
  display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px;width:100%;
  margin-top:10px;font-size:11.5px;line-height:1.45;color:var(--ink-dim);
}
.me-reacoes[hidden]{display:none}
.me-reac-rot{flex:0 0 100%;letter-spacing:.06em}
.me-reac-item{display:inline-flex;align-items:center;gap:3px;white-space:nowrap}
/* o emoji é o próprio objeto que o colega mandou (o mesmo do picker e do
   toast), e não ícone de rótulo — por isso continua emoji e não vira SVG.
   Tamanho explícito para ele acompanhar o modo A+ como o resto da linha. */
.me-reac-item i{font-style:normal;font-size:14px;line-height:1}
.me-reac-item b{font-family:var(--font-mono);font-weight:700;font-variant-numeric:tabular-nums;color:var(--ink)}
/* o zero é informação, mas é a menos importante da linha: --ink-faint (≥4.5:1
   sobre --panel-2 nos dois temas, medido em teste-reacoes) e peso normal, para
   o olho ir direto ao que a turma mandou de verdade. */
.me-reac-item.me-reac-zero b{font-weight:500;color:var(--ink-faint)}
body.hivis .me-reacoes{font-size:13.5px}
body.hivis .me-reac-item i{font-size:16px}

/* (b) quem está online */
.olist{display:flex;flex-direction:column;gap:10px}
.oitem{display:flex;align-items:center;gap:11px;min-height:48px}
.oitem .msk-av{--av-size:34px}
.oitem .ometa{display:flex;flex-direction:column;gap:1px;min-width:0}
.oitem .ometa b{font-size:14px;font-weight:700;color:var(--ink);line-height:1.2}
.oitem .ometa small{font-size:11.5px;color:var(--ink-dim);line-height:1.35}
.odot{
  width:9px;height:9px;border-radius:50%;flex:none;margin-left:auto;
  background:var(--phosphor);box-shadow:0 0 0 3px rgba(var(--phos-rgb),.16);
}
@media (prefers-reduced-motion:no-preference){
  .odot{animation:mskPulse 1.9s ease-in-out infinite}
  @keyframes mskPulse{
    0%,100%{box-shadow:0 0 0 2px rgba(var(--phos-rgb),.10)}
    50%    {box-shadow:0 0 0 6px rgba(var(--phos-rgb),.20)}
  }
}
.ovazio{font-size:12.5px;line-height:1.5;color:var(--ink-dim)}
.ooff{
  margin-top:10px;font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-faint);
}
.ooff[hidden]{display:none}

/* "ir para o mesmo exercício" do colega — linha extra dentro do PRÓPRIO
   `.ometa` (que já é coluna), e não uma caixa nova: o `.oitem` é compacto
   de propósito (48px por linha da turma inteira) e uma moldura própria
   custaria a régua toda de novo, por item da lista. `.oalvo-txt` herda o
   tamanho de `.oitem .ometa small` acima — só o espaçamento muda. */
.oitem .ometa .oalvo-txt{margin-top:2px}
.oalvo-ir{
  align-self:flex-start;display:inline-flex;align-items:center;
  min-height:30px;margin-top:3px;padding:4px 10px;border-radius:10px;
  border:1px solid rgba(var(--phos-rgb),.5);color:var(--phosphor);
  font-size:11px;font-weight:700;text-decoration:none;white-space:nowrap;
}
.oalvo-ir:hover{border-color:var(--phosphor);background:rgba(var(--phos-rgb),.08)}
body.hivis .oalvo-txt{font-size:13.5px}
body.hivis .oalvo-ir{font-size:12.5px;border-width:2px;min-height:36px}

/* (c) avisos do professor */
.avlist{display:flex;flex-direction:column;gap:12px}
.avcard{
  display:flex;gap:11px;padding:12px;border-radius:12px;
  border:1px solid var(--line);background:var(--well);
}
.avcard .msk-av{--av-size:34px}
.avbody{display:flex;flex-direction:column;gap:4px;min-width:0;flex:1}
.avhead{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}
.avhead b{font-size:13px;font-weight:700;color:var(--ink)}
.avhead time{font-size:11px;color:var(--ink-faint)}
.avtxt{font-size:13.5px;line-height:1.5;color:var(--ink);overflow-wrap:anywhere}
.avdel{
  align-self:flex-start;min-height:44px;min-width:44px;padding:8px 10px;border-radius:10px;
  font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-dim);
  border:1px solid transparent;
}
.avdel:hover{color:var(--err-ink);border-color:var(--line)}
.avform{display:flex;flex-direction:column;gap:10px;margin-bottom:14px}
.avform[hidden]{display:none}        /* idem: só o professor vê o campo de aviso */
.avform textarea{
  width:100%;min-height:74px;padding:11px 12px;border-radius:12px;resize:vertical;
  border:1px solid var(--line);background:var(--well);color:var(--ink);
  font-family:var(--font-ui);font-size:14px;line-height:1.5;
}
.avform .avsend{
  align-self:flex-end;min-height:44px;padding:11px 20px;border-radius:12px;
  border:1px solid rgba(var(--phos-rgb),.45);background:var(--well);color:var(--phosphor);
  font-size:11px;letter-spacing:.14em;text-transform:uppercase;
}
.avform .avsend:hover{border-color:rgba(var(--phos-rgb),.8)}
.avform .avsend:disabled{opacity:.5;cursor:default}

/* ============================================================
   DESPOLUIÇÃO — componentes que nasceram desta faxina
   Tudo aqui fala a MESMA língua de desenho da tab bar:
   viewBox 0 0 24 24, fill:none, stroke:currentColor, 1.7,
   linecap/linejoin round. Nenhum emoji, nenhum gradiente,
   nenhuma sombra sem função e nenhum `transition:all`.
   ============================================================ */

/* ---------- botão de AÇÃO em ícone ----------
   Compõe com .btn (que já dá borda, fundo e cores): esta classe só
   troca a caixa — quadrado de alvo cheio em vez de retângulo com
   rótulo. Vale para "nova rodada", "ouvir de novo" e "microfone",
   que se repetem dezenas de vezes por sessão. O aria-label continua
   dizendo por extenso o que o ícone desenha.

   A altura é EXATAMENTE a do .btn com rótulo (48px, e 56px no A+): a
   tela da lição do aulagame cabe em 320×568 por poucos pixels, e um
   botão quadrado mais alto que o retangular que substituiu roubaria
   justamente esses pixels. 48px continua acima dos 44 de alvo mínimo. */
.iconact{
  flex:0 0 auto;width:48px;min-width:48px;height:48px;min-height:48px;
  display:inline-grid;place-items:center;padding:0;
}
/* mesma defesa que .row e .field já fazem nesta base: o `display` de
   autor venceria o [hidden] em navegador cujo UA sheet não marca aquela
   regra como !important — e o botão do microfone é escondido por
   atributo no modo "conferir sozinho". */
.iconact[hidden]{display:none}
.iconact svg{
  width:22px;height:22px;display:block;
  fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round;
}
/* o A+ cresce o alvo e o traço junto — quem liga o A+ mira pior */
body.hivis .iconact{width:56px;min-width:56px;height:56px;min-height:56px}
body.hivis .iconact svg{width:26px;height:26px;stroke-width:2}
/* estado "ligado" do microfone: o ícone vira mic cortado e o botão
   pega a cor de parar (a classe .stop já existe nas páginas) */
.iconact .ico-alt{display:none}
.iconact.stop .ico-on{display:none}
.iconact.stop .ico-alt{display:block}

/* ---------- BOLINHA DO MICROFONE (três canais, nenhum indispensável) ----------
   Verde contra vermelho SEM texto é o pior par possível para
   protanopia/deuteranopia (~8% dos homens). Por isso o estado é dito
   três vezes:
     · PREENCHIMENTO — ligado é cheio, desligado é anel vazado;
     · COR — --ok contra --ember;
     · MOVIMENTO — ligado pulsa devagar, desligado fica parado.
   Quem não distingue a cor lê pelo movimento; quem desligou animação
   lê pelo preenchimento; quem usa leitor de tela lê o texto que
   continua no DOM (.sr-only ao lado da bolinha).
   O ciclo é de 2 s e a opacidade varia pouco: o aluno está tentando
   ouvir um intervalo, e um ponto piscando rápido no canto do olho
   compete com a atenção dele. */
/* a bolinha mora NO PRÓPRIO botão do microfone (canto superior direito).
   Sozinha numa linha, embaixo de uma fileira de três botões, ela virava
   um ponto órfão que não dizia de quem era o estado — e ficava, ainda por
   cima, debaixo do botão errado. É o mesmo lugar em que o metrônomo desta
   página já põe o LED dele. */
.iconact{position:relative}
.iconact .led{position:absolute;top:6px;right:6px}
body.hivis .iconact .led{top:7px;right:7px}
.led.mic-on,.led.mic-off{width:11px;height:11px}
.led.mic-on{background:var(--ok);box-shadow:0 0 10px var(--ok-glow)}
.led.mic-off{background:transparent;box-shadow:inset 0 0 0 2px var(--ember)}
body.hivis .led.mic-on,body.hivis .led.mic-off{width:14px;height:14px}
body.hivis .led.mic-off{box-shadow:inset 0 0 0 3px var(--ember)}
@media (prefers-reduced-motion:no-preference){
  .led.mic-on{animation:mskMicPulso 2s ease-in-out infinite}
  @keyframes mskMicPulso{0%,100%{opacity:1}50%{opacity:.5}}
}

/* ============================================================
   FILEIRA DE BOTÕES — a largura da tela dividida em partes iguais

   Decisão do dono: um botão ocupa a largura toda, dois ocupam metade
   cada, três um terço, quatro um quarto, e assim por diante. O critério
   mora AQUI, uma vez só: largura decidida página a página foi como as
   quatro telas divergiram da última vez.

   `flex:1 1 0` — base ZERO, e não o `1 1 auto` que as páginas traziam.
   Com base `auto` cada botão parte do tamanho do próprio rótulo e o que
   se divide é apenas a SOBRA: "Ouvir o gabarito" saía com o dobro da
   largura de "Errei". Com base zero divide-se o espaço INTEIRO, e a
   divisão é exata.
   `min-width:0` porque o mínimo automático de um item flex é o conteúdo
   dele: sem isso um rótulo comprido se recusa a encolher e a fileira
   estoura a tela em vez de repartir.

   TETO E CENTRO. Um botão esticado por 1100px de monitor é ridículo: a
   fileira para em 460px e fica no meio da coluna — que é também o
   "centraliza os botões" que o dono pediu.

   `nowrap`, e não o `wrap` que estava lá: com a largura repartida não
   sobra o que quebrar, e era justamente o `wrap` que fazia a fileira de
   três virar duas fileiras num celular estreito.

   PISO DE TOQUE de 48px (o mínimo exigido é 44). É o MESMO valor que o
   `.btn` das páginas já declara — de propósito: escrever aqui um número
   menor apertaria o alvo de todo mundo, porque este arquivo carrega
   depois do <style> de cada página e venceria o empate de
   especificidade. O A+ continua mandando, com `body.hivis .btn`, que
   tem uma classe a mais.
   ============================================================ */
.row{
  flex-wrap:nowrap;align-items:stretch;
  width:100%;max-width:var(--msk-fileira-max,460px);
  margin-left:auto;margin-right:auto;
}
/* filho DIRETO, nunca `.row button`: a grade de intervalos e a linha do
   ditado moram dentro de caixas próprias, com largura própria */
.row > *{flex:1 1 0;min-width:0;min-height:48px}
/* o botão de ícone perde a caixa quadrada ao repartir a fileira: o alvo
   continua com 48px de altura, a largura é que passa a ser a fatia */
.row > .iconact{width:auto;min-width:0}

/* ---------- ÍCONE COM RÓTULO MIÚDO ----------
   Para o botão que se repete a cada rodada mas NÃO tem desenho que se
   explique sozinho — "acertei", "errei", "ouvir o gabarito". Só o ícone
   seria adivinhação; só o texto não cabe numa fatia de 88px (nem de
   64px, quando a fileira tem quatro). Então o ícone manda e o rótulo
   fica embaixo, miúdo, exatamente como nas abas da percepção e na tab
   bar — a mesma solução, e não uma terceira. O aria-label continua
   dizendo a frase inteira. */
.iconact.comrot{
  grid-auto-flow:row;gap:3px;width:auto;min-width:0;padding:0 3px;
}
.iconact.comrot svg{width:19px;height:19px}
.iconact.comrot small{
  max-width:100%;font-size:9px;line-height:1;font-weight:700;
  letter-spacing:.02em;text-transform:uppercase;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
body.hivis .iconact.comrot svg{width:22px;height:22px}
body.hivis .iconact.comrot small{font-size:10.5px}

/* ---------- FUSÃO ENTRE TELAS ----------
   ~200 ms de opacidade, e só. Transição é enfeite, não informação:
   sob prefers-reduced-motion não existe (a regra inteira mora dentro
   do no-preference), e o app fica instantâneo. */
@media (prefers-reduced-motion:no-preference){
  .msk-tela:not([hidden]){animation:mskFundir .2s ease-out}
  @keyframes mskFundir{from{opacity:0}to{opacity:1}}
}

/* ---------- DICA DE BOTÃO (só onde o resultado é INVISÍVEL) ----------
   "Ouvir de novo" você escuta na hora e "nova rodada" você vê a tela
   mudar — dica ali seria ruído sobre o óbvio. Ligar o microfone muda
   um estado que não se vê, e é o único caso que ganha dica. Aparece
   nas duas primeiras vezes e some para sempre: da terceira em diante
   o aluno passaria a ignorar as dicas justamente quando uma importar.
   z-index 60 = mesma faixa dos toasts do aulagame. */
.msk-dica{
  position:fixed;left:50%;transform:translateX(-50%);
  bottom:calc(var(--tabbar-h,58px) + 12px);z-index:60;
  max-width:min(88vw,340px);padding:10px 14px;border-radius:12px;
  border:1px solid var(--line-2);background:var(--panel);color:var(--ink);
  font-family:var(--font-ui);font-size:13px;line-height:1.4;text-align:center;
  box-shadow:0 14px 40px rgba(0,0,0,calc(.5 * var(--shadow-k)));
  pointer-events:none;
}
body.hivis .msk-dica{font-size:15px;border-width:2px}
@media (prefers-reduced-motion:no-preference){
  .msk-dica{animation:mskDicaIn .2s ease-out}
  @keyframes mskDicaIn{from{opacity:0;transform:translate(-50%,8px)}to{opacity:1;transform:translate(-50%,0)}}
}

/* ---------- O "?" QUE PISCA NA HESITAÇÃO ----------
   Visível e discreto SEMPRE. Pisca só quando é a primeira vez naquele
   tipo de exercício E o aluno demorou sem agir depois de entrar; visto
   uma vez, nunca mais. Sob prefers-reduced-motion não pisca: ganha o
   mesmo destaque, parado. */
#helpBtn.msk-chama{border-color:var(--phosphor);color:var(--phosphor)}
@media (prefers-reduced-motion:no-preference){
  #helpBtn.msk-chama{animation:mskChama 1.4s ease-in-out 3}
  @keyframes mskChama{
    0%,100%{box-shadow:0 0 0 0 rgba(var(--phos-rgb),0)}
    50%    {box-shadow:0 0 0 6px rgba(var(--phos-rgb),.22)}
  }
}

/* ---------- PAINEL DE AJUDA (o que fazer NESTE exercício) ----------
   z-index 85: ACIMA da modal de configuração (80) — a ajuda explica os
   ajustes e precisa poder cobri-la — e ABAIXO do aviso de navegador
   embutido (90) e do splash (100). */
.msk-ajuda{
  position:fixed;inset:0;z-index:85;
  display:flex;align-items:flex-end;justify-content:center;
  background:var(--scrim);
  -webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);
}
.msk-ajuda[hidden]{display:none}
.msk-ajuda-cx{
  width:min(100%,520px);max-height:88dvh;overflow-y:auto;
  border:1px solid var(--line);border-radius:18px 18px 0 0;
  background:var(--panel);color:var(--ink);font-family:var(--font-ui);
  padding:18px 18px calc(18px + env(safe-area-inset-bottom,0px));
  box-shadow:0 -12px 40px rgba(0,0,0,calc(.5 * var(--shadow-k)));
}
@media (min-width:560px){
  .msk-ajuda{align-items:center}
  .msk-ajuda-cx{border-radius:18px;padding-bottom:18px}
}
.msk-ajuda-top{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding-bottom:12px;margin-bottom:12px;border-bottom:1px solid var(--line);
}
.msk-ajuda-top h2{
  font-size:15px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:var(--ink);
}
.msk-ajuda-top button{
  flex:none;width:44px;height:44px;border-radius:10px;
  border:1px solid var(--line);background:var(--well);color:var(--ink-dim);
  display:inline-grid;place-items:center;font-size:18px;line-height:1;
  transition:color .15s,border-color .15s;
}
.msk-ajuda-top button:hover{color:var(--phosphor);border-color:var(--phosphor)}
.msk-ajuda h3{
  font-size:11px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;
  color:var(--phosphor);margin:14px 0 5px;
}
.msk-ajuda h3:first-of-type{margin-top:0}
.msk-ajuda p{font-size:13.5px;line-height:1.55;color:var(--ink-dim)}
body.hivis .msk-ajuda h3{font-size:12.5px}
body.hivis .msk-ajuda p{font-size:15.5px;color:var(--ink)}

/* ---------- PREFERÊNCIAS DO APP dentro do perfil / da conta ----------
   Tema e A+ saíram do cabeçalho: com "BOM DE OUVIDO" (13 letras) no
   lugar de "MUSIKAI" (7) não havia mais espaço lá. Ficam aqui, e o A+
   fica TAMBÉM na porta de entrada (login e "quem está tocando?"), que
   é a primeira tela que todo mundo vê — enterrá-lo só no perfil criaria
   o beco de precisar enxergar para chegar ao que faz enxergar. */
.msk-prefs{margin-top:16px;padding-top:14px;border-top:1px solid var(--line)}
.msk-prefs > h3{
  font-size:11px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;
  color:var(--ink-faint);margin-bottom:10px;
}
.msk-pref{display:flex;align-items:center;gap:10px;margin-top:10px;flex-wrap:wrap}
.msk-pref > span{
  flex:1 1 120px;min-width:0;font-size:12px;line-height:1.4;color:var(--ink-dim);
}
.msk-pref > span b{display:block;color:var(--ink);font-size:13px;font-weight:700}
.msk-pref .seg{flex:0 1 auto}
.msk-pref kbd{
  font-family:var(--font-mono);font-size:10.5px;
  border:1px solid var(--line-2);border-radius:5px;padding:1px 5px;color:var(--ink-faint);
}
body.hivis .msk-prefs > h3{font-size:12px;color:var(--ink-dim)}
body.hivis .msk-pref > span{font-size:13.5px}
body.hivis .msk-pref > span b{font-size:15px}
/* os botões do par usam o mesmo desenho do .seg das páginas; como este
   arquivo é compartilhado e o .seg mora no <style> de cada página, o
   par ganha aqui o mínimo para nunca depender daquilo */
.msk-prefseg{
  display:flex;flex:none;border:1px solid var(--line);border-radius:12px;
  background:var(--well);overflow:hidden;
}
.msk-prefseg button{
  min-height:44px;padding:8px 12px;
  font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-dim);border-right:1px solid var(--line);
  transition:color .15s,background .15s;white-space:nowrap;
}
.msk-prefseg button:last-child{border-right:0}
.msk-prefseg button:hover{color:var(--ink)}
.msk-prefseg button[aria-pressed="true"]{color:var(--phosphor);background:rgba(var(--phos-rgb),.09)}
body.hivis .msk-prefseg{border-width:2px}
body.hivis .msk-prefseg button{font-size:13px;min-height:50px}
/* ---------- A FESTA DA UNIDADE, ATRÁS DA TELA DE FIM ----------
   Mora aqui pelo mesmo motivo que o Sabiá mora: ele é personagem do app
   inteiro, não componente de uma página. A cena é FUNDO — o texto e os
   botões ficam na frente, clicáveis desde o primeiro quadro (pedido do
   dono, 08/09/2026).

   O TEXTO CONTINUA MEDIDO: a cena vive atrás de um véu na cor do painel,
   e por isso o contraste do título, do XP e dos botões é o de sempre —
   nenhuma tabela de contraste precisa ser refeita.

   PARALLAX SEM ROLAGEM: três camadas com deslocamentos diferentes (céu
   6px, árvore 14px, frente 26px) numa panorâmica de 8s. E o QUADRO FINAL
   é o estado base das regras: os keyframes só dizem DE ONDE a cena vem,
   então quem pede menos movimento recebe a cena pronta, parada, em vez
   de uma tela vazia.

   A SAGA DA PLANTA (pedido do dono, 09/09/2026, depois de aprovar a cena
   da semente: "deu certo, mas está repetitivo, dá pra fazer uma animação
   diferente por unidade?"). A MESMA planta avança UM estágio por unidade
   da trilha — semente, broto, tronco, galhos, botões, frutos —, e a conta
   que mapeia unidade → estágio mora no aulagame (`estagioDaFesta`). Aqui
   fica o que MUDA por estágio, e é sempre a mesma dupla: a classe `est-N`
   na caixa da cena escolhe (1) o gesto do Sabiá e (2) o compasso do
   desenho.

   O RELÓGIO É UM SÓ, para os seis estágios, e cada tempo tem o seu
   keyframe:
     voo (0–2,0s) → o selo é carimbado (2,4s) → a planta sobe do chão
     (2,8–4,6s) → a copa abre, quando existe (4,5–5,4s) → a PEÇA NOVA do
     estágio entra (5,2s, escalonada) → e o gesto do Sabiá acompanha a
     peça nova, sempre.
   Nos extremos há dois desvios, e os dois são de história: no estágio 0 a
   semente cai do bico (2,2–3,4s) e a casca se abre DEPOIS dela sumir
   (3,5–4,7s) — árvore/casca que nasce ao lado da semente conta o
   contrário de "saiu dela"; no 5 a fruta madura sobe da árvore ao bico
   (5,9–7,3s), que é a colheita.

   A fruta é a MESMA peça da espécie da trilha, e a árvore que a dá é da
   mesma espécie: é isso que faz a cena ser da trilha dele e não um
   enfeite genérico. Nada disto é imagem nova — é SVG montado no aulagame
   com os tokens `--cena-*`. */
#fim{position:relative;isolation:isolate}
/* O QUADRO DA FESTA TEM PISO (09/09/2026), E ELE QUASE NÃO MORDE — que é
   exatamente o ponto.
   A HISTÓRIA QUE ESTE COMENTÁRIO CONTAVA NÃO REPRODUZ, e some daqui: ele
   cravava que "com poucas linhas a altura caía para ~201px, quadro de
   ~221px". MEDIDO em 10/09/2026 nas telas de 320, 375 e 812px de largura ×
   375, 568, 640 e 768px de altura, com e sem `hivis`, a altura NATURAL
   mínima de `#fim` com festa é 309,8px em TODAS — o piso de 310px morde por
   0,2px. O quadro de 221px era conta de uma versão anterior do texto do fim
   (título, XP e tempo com outro tamanho e outro padding), não medida deste
   código.
   O PISO FICA MESMO ASSIM, e não por inércia: ele não existe para consertar
   um quadro baixo observado, existe para GARANTIR que ele não apareça. A
   altura de `#fim` é AUTOMÁTICA — ela depende do texto do fim, e esse texto
   muda (uma linha a mais de resultado, uma fonte maior no aparelho do aluno,
   um `hivis` novo). Se ela cair abaixo de 310px o `max-height:calc(100% -
   30px)` da planta morde, o `aspect-ratio` encolhe a caixa junto e as
   âncoras em PX de `.f-gota` e `.f-semente` erram o pé em ~30px — defeito
   que não tem conserto de dentro do CSS da cena, porque a âncora horizontal
   precisaria saber a ALTURA do quadro e `100%` num `right:` mede largura.
   O piso é o contrato que deixa a cena inteira ser medida em px. 0,2px de
   folga hoje é o que prova que ele está no lugar certo: alto o bastante para
   travar, baixo o bastante para não inventar quadro nenhum.
   E O QUE ENGORDA A TELA COM FESTA NÃO É ELE: são os `padding:6px 14px` que
   o título e as linhas de resultado ganham lá embaixo (o fundo próprio que
   substituiu o véu) — 12px em cada um dos dois, 24px de altura a mais que a
   lição comum não tem. Quem for cortar altura corta ali, não aqui. */
#fim.com-festa{min-height:310px}
#fim > .celebrate{position:relative;z-index:1}
/* OS BOTÕES CHEGAM NOS 2 SEGUNDOS FINAIS (pedido do dono): a cena tem a
   tela por 3,2s e então a fileira sobe para a frente. `pointer-events`
   entra no keyframe de propósito — botão invisível que aceita toque é
   armadilha. Sem festa (lição comum) nada disto vale: a fileira aparece
   com a tela, como sempre.
   ANOTADO, NÃO CONSERTADO (10/09/2026): em PAISAGEM 812×375 os botões já
   ficam abaixo da dobra SEM festa (74–129px), e com festa vão a 115–182px.
   O deslocamento é pré-existente e não é regressão deste PR — mas some com
   os 6,4s de `fBotoes`: quem está de lado rola atrás de um botão que ainda
   não apareceu, e não tem como saber que ele vem. Fica registrado aqui
   porque o conserto é decisão do dono (encurtar o atraso em paisagem, ou
   deixar a fileira entrar com a tela quando a altura é pequena), não
   escolha de quem estava mexendo na cena. */
#fim.com-festa > .celebrate > .row{animation:fBotoes 1.8s cubic-bezier(.3,.7,.3,1) 6.4s both}
@keyframes fBotoes{
  from{opacity:0;transform:translateY(18px) scale(.98);pointer-events:none}
  to{opacity:1;transform:none;pointer-events:auto}
}
@media (prefers-reduced-motion:reduce){
  /* sem movimento, mas o palco continua: a cena aparece pronta e os
     botões não ficam presos atrás de uma animação que não vai rodar */
  #fim.com-festa > .celebrate > .row{animation:none}
}
.festa-un{position:absolute;inset:-10px -14px;z-index:0;overflow:hidden;border-radius:16px;pointer-events:none}
/* ---------- REVER A FESTA (pedido do dono, 09/09/2026) ----------
   A MESMA cena, agora numa camada por cima da trilha. Nada aqui redesenha
   a festa: só dá a ela uma moldura de tela inteira e um jeito de sair. */
/* A CENA NÃO VIRA TELA CHEIA, e é só isso que esta moldura garante.
   Esticar o quadro para a tela inteira não mostra a mesma cena maior —
   mostra outra, com o povoado espalhado e a árvore perdida no meio.
   O QUE ESTE COMENTÁRIO PROMETIA E NÃO ENTREGAVA (corrigido em
   09/09/2026): "mesma proporção, mesmo quadro". Não há como: a altura do
   quadro da tela de fim é AUTOMÁTICA (~362×329 com o texto de sempre,
   ~362×221 com mais linhas), então proporção fixa nenhuma o acompanha — a
   de 3/4 daqui é 0,75 contra os 1,10 de lá. O que faz a cena ser A MESMA
   nos dois usos é outra coisa, e é por isso que ela vale a regra: TODAS as
   peças são medidas em PIXELS e ancoradas em porcentagem — Sabiá 148×163,
   flor 40, selo 50, fruta 42, semente 22, gota 8×12 e, desde a saga, a
   planta em 296px de altura. Enquanto isso valer, a composição é a mesma
   em qualquer moldura; a planta em % da altura já quebrou isso uma vez (a
   razão planta/Sabiá saía 0,85 aqui e 1,18 lá). E o que faz o PIXEL
   bastar é o piso de `#fim.com-festa`: sem quadro baixo, o `max-height`
   da planta não morde em nenhum quadro de uso, e px que não é reescalado
   é px exato.
   (O véu que este comentário citava não existe mais: ele era o
   esmaecimento de que o dono reclamou em 09/09/2026, e o contraste do
   texto passou para o fundo do próprio texto — ver o bloco
   `#fim.com-festa > .celebrate`.) */
.festa-rever{
  position:fixed;inset:0;z-index:60;background:var(--bg);
  display:flex;align-items:center;justify-content:center;padding:16px;
}
.festa-rever .festa-un{
  position:relative;inset:auto;width:min(92vw,420px);aspect-ratio:3/4;
  max-height:76vh;border-radius:16px;border:1px solid var(--line);
}
.festa-sair{
  position:absolute;right:12px;top:12px;z-index:2;
  padding:8px 14px;border-radius:999px;border:1px solid var(--line);
  background:var(--panel);color:var(--ink);font:inherit;font-size:13px;cursor:pointer;
}
/* o botão no marco da unidade: discreto, do lado do nome — quem está
   subindo a trilha não pode tropeçar nele */
.marco-rever{
  margin-left:auto;padding:4px 10px;border-radius:999px;
  border:1px solid var(--line);background:transparent;color:var(--ink-dim);
  font:inherit;font-size:11px;cursor:pointer;
}
.marco-rever:hover,.marco-rever:focus-visible{color:var(--ink);border-color:var(--phosphor)}
/* O VÉU SAIU, E O TEXTO DO FIM GANHOU FUNDO PRÓPRIO (09/09/2026).
   Até aqui a legibilidade vinha de UM véu na cor do painel (`.festa-un`
   com `::after`, `opacity:.9` e máscara) por cima da cena inteira. Ele
   fazia dois trabalhos com um controle só, e por isso não tinha como
   acertar os dois: com o véu comprido o dono viu a cena "muito esmaecida"
   (a copa da árvore nova nascia dentro da faixa 100% opaca e aparecia com
   10% da própria cor); com o véu encurtado para soltar a cena, o tronco
   passou a correr por trás da linha de XP e o contraste caiu para
   5,09 / 4,42 / 3,77 no tema claro — duas medidas abaixo dos 4,5:1 que
   texto normal de 13px exige. Um controle, dois donos, briga garantida.

   Agora são dois controles. O PALCO não tem véu nenhum: a cena aparece
   com a cor cheia, do céu ao pé da árvore, nos dois usos (tela de fim e
   "rever"). E o TEXTO carrega o próprio fundo: título e linhas de
   resultado desenham a cor do painel OPACA sob si mesmos, o que torna o
   contraste independente do que passa atrás.

   MEDIDO com os tokens reais sobre painel opaco (09/09/2026):
     tema claro  — título (--ink) 16,68:1 · XP (--ink-dim) 7,46:1 ·
                   tempo (--ink-faint) 5,46:1 · o <b> do XP (--phosphor) 6,41:1
     tema escuro — título 14,60:1 · XP 6,77:1 · tempo 5,05:1 · <b> 5,34:1
   Todas passam AA de texto normal (4,5:1) — e com mais folga do que os
   6,25–6,50 que o véu de `opacity:.9` rendia, porque .9 de painel sobre
   céu não é painel. Os botões nunca dependeram do véu: `.btn` tem fundo
   próprio desde sempre. */
#fim.com-festa > .celebrate > h2,
#fim.com-festa > .celebrate > p{
  background:var(--panel);border-radius:12px;padding:6px 14px;max-width:100%;
}
/* a flor do nó só ganha cor dentro da trilha (`.boli .np-*`): na festa ela
   é a mesma peça, então a paleta vem de novo dos tokens da cena */
.festa-un{--cena-fruta-festa:var(--cena-flor)}
.festa-un.esp-pitanga{--cena-fruta-festa:var(--cena-pitanga)}
.festa-un.esp-jabuticaba{--cena-fruta-festa:var(--cena-jabuticaba)}
/* a fruta que ele colhe é a MESMA peça, então herda a mesma paleta */
.f-flor .np-bractea,.f-fruta .np-bractea{fill:var(--cena-folha);stroke:var(--cena-folha-traco);stroke-width:1.6;stroke-linejoin:round}
.f-flor .np-casca,.f-fruta .np-casca{fill:var(--panel);stroke:var(--cena-traco);stroke-width:3}
.f-flor .np-polpa,.f-fruta .np-polpa{fill:var(--cena-fruta-festa)}
.f-flor .np-aro,.f-flor .np-gomo,.f-flor .np-cicatriz,
.f-fruta .np-aro,.f-fruta .np-gomo,.f-fruta .np-cicatriz{stroke:var(--cena-traco);fill:none}
.f-flor .np-cabo,.f-fruta .np-cabo{stroke:var(--cena-folha)}
.festa-un > span{position:absolute;inset:0;display:block}
.f-ceu{background:linear-gradient(180deg,var(--cena-ceu-alto),var(--cena-ceu-meio) 55%,var(--cena-ceu-baixo))}
/* AS FLORES E O SABIÁ VOLTARAM A 1 (dono, 09/09/2026: "está muito
   esmaecido"). Elas nasceram a .9 e ele a .85 para "assentar no fundo",
   mas somados ao véu davam desenho lavado — quem afasta a cena do texto
   é o véu com máscara, que já faz esse trabalho sozinho. Personagem de
   história não entra translúcido. */
.f-flor{position:absolute;width:40px;height:40px}
.f-flor svg,.f-fruta svg,.f-semente svg,.f-gota svg,.f-arvore-nova svg,.f-sabia svg{width:100%;height:100%;display:block}
/* O SABIÁ VOLTOU A 148×163 (09/09/2026, mesmo dia). Ele tinha encolhido para
   120×132 por causa de uma conta de largura que EU inventei: "planta e Sabiá
   lado a lado, com 4px de folga, tudo dentro da moldura". Nessa conta a
   moldura mais estreita não fechava e o pássaro tinha de ceder.
   A conta estava certa; a PREMISSA estava errada. O dono nunca pediu os dois
   lado a lado — desde o primeiro pedido ele diz que a semente "vira uma
   árvore que CRESCE NA FRENTE DELE", e o comentário de `.f-arvore-nova`
   sempre terminou afirmando isso ("Ela fica NA FRENTE do Sabiá"). Zero
   sobreposição era regra minha, não dele, e era ela — não a moldura — que
   somava 148 + 4 + largura-da-planta na mesma linha.
   Dissolvida a premissa, a largura deixa de ser o teto: a planta passa POR
   CIMA do pássaro e cresce até onde a moldura aguenta sem cortar a copa.
   Então o Sabiá volta ao tamanho aprovado (148×163 é a proporção exata do
   viewBox 150×165, logo é o mesmo desenho, sem tarja) e a árvore fica MAIOR
   do que ficaria: 296px, razão 1,82 contra os 1,20 que o encolhimento tinha
   conseguido no quadro baixo.
   `right` FOI DE 4% PARA 9% (09/09/2026): a camada `.f-frente` termina a
   panorâmica com `translate3d(26px,…)`, e a 4% de um quadro de 362px de
   largura a borda direita dele caía 11px FORA da cena — a alça da
   escaleta desaparecia no quadro final (medido na bancada). 9% dá 32,6px
   de folga contra os 26px do empurrão, nos dois quadros da cena. */
.f-sabia{position:absolute;right:9%;bottom:2%;width:148px;height:163px}
/* NA CENA O SABIÁ É DESENHO, NÃO O BOTÃO DA LIÇÃO. A peça reusada traz a
   classe `.sabia-licao`, e lá embaixo ela é `position:fixed` no canto da
   tela do exercício, some abaixo de 640px de altura e encolhe no modo A+.
   Dentro do quadro isso dá desastre: sem transform no antebraço da camada
   (é o caso de quem pede MENOS MOVIMENTO, onde a panorâmica não roda) o
   `fixed` passa a se medir pela janela e o pássaro vira tela cheia — visto
   na bancada. Aqui ele volta a ser um desenho dentro do próprio quadro. */
.festa-un .f-sabia svg.sabia-licao{
  position:static;width:100%;height:100%;opacity:1;pointer-events:none;
}
/* CADA DECLARAÇÃO ACIMA MATA UMA REGRA DIFERENTE de `.sabia-licao`, e é
   por isso que nenhuma delas é enfeite:
     `position:static`  — desfaz o `position:fixed` no canto da tela;
     `width/height:100%`— desfazem o `body.hivis .sabia-licao{92px×101px}`,
                          que no modo A+ vence pelo `body.hivis` e encolhia
                          o pássaro da cena de 120×132 para 92×101;
     `opacity:1`        — desfaz o esmaecido do estado "dormindo";
     `pointer-events:none` — a cena é fundo e não intercepta toque.
   `display:block` NÃO entra: `.f-sabia svg{display:block}`, três regras
   acima, já ganha essa disputa — declaração que não muda nada é ruído que
   o próximo leitor tem de conferir. */
/* ---------- A FRUTA, A SEMENTE E A ÁRVORE QUE NASCE ----------
   O ESTADO BASE É O FIM DA HISTÓRIA, como no resto da cena: a fruta já
   virou semente (logo, `opacity:0`) e a árvore está crescida, com fruto.
   Quem pede MENOS MOVIMENTO recebe esse quadro pronto — e recebe também a
   casca da semente aberta no chão, que é o bloco `reduce` lá embaixo:
   árvore parada sem a casca no pé dela seria só um cenário, não a
   história de que ELE plantou.

   A FRUTA E A SEMENTE NASCEM ANCORADAS NO BICO, e isso é aritmética, não
   olhômetro. O Sabiá é peça de tamanho FIXO (148×163) presa a
   `right:9%;bottom:2%`, e no viewBox 150×165 dele a ponta do bico
   (`.sb-bico`) está em (124,58). Convertido: 122,3px da esquerda e 57,3px
   do topo da caixa dele. Logo o bico fica a `9% + 25,65px` da direita do
   quadro e a `2% + 105,7px` do fundo — em QUALQUER quadro, porque a parte
   em px não escala com ele. Centrar a peça nesse ponto é só descontar
   metade dela.
   E A FRUTA VOLTOU A 42px. Ela tinha caído para 40 com uma justificativa
   que era FALSA em dois números: dizia que 42px pediria
   `right:calc(9% − 1,2px)` e nasceria "fora do lado direito". Com o bico
   antigo (20,8px) a conta dava −0,2px, não −1,2px; e `9% − 0,2px` é 30,7px
   DENTRO de um quadro de 343 (9% dele são 30,9px), porque o que sai da
   moldura é a parte em px, não a porcentagem inteira. Com o bico de volta a
   25,65px a questão nem se levanta: 42px centrados ali dão
   `right:calc(9% + 4,65px)`, folgado.
   ISSO ERA O DEFEITO: com `bottom:34%` a fruta encolhia 42px ACIMA do
   bico no quadro do "rever" (bico a ~24% do fundo ali) e a semente nascia
   57px acima dele — a cena mostrava a fruta desaparecendo no ar, e
   encolher NO BICO é justamente o que lê como "virou semente".

   E O MOVIMENTO É SÓ `transform`. A primeira versão animava `right` e
   `bottom`: propriedade de LAYOUT, ~4s de reflow por quadro na WebView do
   app, e a única cena do arquivo fora da convenção que fPan/fPan2/fPan3/
   fVoo/fCresce já seguiam. */
/* A FRUTA FICA VISÍVEL NO ESTADO BASE desde a saga (09/09/2026), e a
   inversão é de história: ela era a fruta que virava semente, e por isso
   o quadro final não a tinha (`opacity:0`); agora ela é a COLHEITA do
   estágio 5 — ele arranca da árvore e fica com ela no bico. Quadro final
   sem a fruta seria a cena que apaga justamente o que o aluno acabou de
   conquistar. Nos outros cinco estágios este elemento não existe: quem
   decide é `cenaDaFesta`. */
/* E A FRUTA SEGURADA NAO SAI DA MOLDURA (10/09/2026). Desde o desenho
   articulado a ponta do bico fica a 2px da borda da caixa do Sabia, entao
   a fruta centrada nela ja nasce 19px FORA dessa caixa — e a panoramica
   (`fPan3`) ainda empurra a camada 26px para a direita no ultimo quadro.
   Medido em Chrome headless, no fim da panoramica: a fruta saia 10,7px
   pela direita no quadro de 362px, 15,8px no de 306px (tela de 320) e
   16,8px no "rever" de 320px — a colheita, que e o que o aluno acabou de
   ganhar, aparecia cortada em TODO celular. O `max()` e o guarda-corpo:
   a ancora continua sendo o bico (`9% + -19,03px`, a conta do teste), e
   o piso de 25px e o minimo para a caixa dela — 42px, que no bico viram
   38,5px de bbox (escala ,86 girada 4°) — ficar dentro com o empurrao de
   26px (26 − (42 − 38,5)/2 = 24,25). O piso so morde onde a conta pura
   sairia da moldura (quadros de menos de 490px, ou seja todo celular), e
   morde o minimo: 11,5px no quadro de 362, 17,5px no mais estreito — o
   centro dela entra pelo bico em vez de ficar na ponta, e ela e desenhada
   POR CIMA dele, entao le como fruta na boca. */
.f-fruta{
  position:absolute;right:max(calc(9% + -19.03px),25px);bottom:calc(2% + 95.57px);
  width:42px;height:42px;opacity:1;
}
.f-semente{
  position:absolute;right:calc(9% + -9.03px);bottom:calc(2% + 105.57px);
  width:22px;height:22px;opacity:0;
}
/* AS GOTAS SÃO ANCORADAS NA PLANTA, E NÃO NO BICO — e a inversão é o
   conserto de um defeito medido: em t=4500ms, num quadro de 362×329, a
   gota caía em x=283 e o broto estava em x=206,7 (os torrões de 190,8 a
   221,9). Erro de 76,3px, 21% da largura do quadro: a rega caía ao LADO da
   planta, no vazio. A causa era o ponto de repouso — o keyframe terminava
   em `translate(-26px,96px)` a partir do bico, e o bico ficava ~76px à
   direita da planta.

   POR QUE A PLANTA, E NÃO O BICO. Só um dos dois pontos pode ser exato: os
   dois são ancorados por `right:X%` mais px, e a distância entre
   porcentagens diferentes muda com a largura do quadro. O que a cena tem
   de acertar é onde a água CAI, então o repouso (`translate(0,0)`) é o pé
   da planta: `9% + 134,1px` mais os 4px de meia-gota dão `9% + 138,1px`, que
   é o CENTRO da caixa dela (ela abre em `9% + 26px` e tem 224,2px), e
   `1% + 9,1px` é a linha de chão do viewBox
   (y=128 de 132, numa planta de 296px). Isso é exato em qualquer largura.
   O NASCIMENTO no bico continua sendo aritmética, agora dentro do
   keyframe: o bico está a `9% + 25,65px` da direita e a `2% + 105,7px` do
   fundo, logo a 112,4px à direita e ~94px acima do repouso — e é só a
   parte em % da ALTURA que varia entre os quadros, 1,3px do quadro mais
   baixo ao mais alto. A gota nasce no bico com folga de um pixel e cai no
   pé da planta com folga nenhuma, que é a troca certa.
   OS DOIS NÚMEROS ANDARAM JUNTOS quando a árvore passou para a FRENTE do
   Sabiá: a borda direita da caixa dela saltou de `9% + 124px` para
   `9% + 26px` (é a sobreposição) e a largura foi de 181,8 para 224,2px, então
   o centro andou 72,8px para a direita. A distância até o bico caiu de 194
   para 112px pelo mesmo motivo, e a linha de chão subiu de 7,3 para 9,1px
   porque a planta ficou mais alta.

   Elas somem ao encharcar o chão — água que fica poçada no quadro final
   não é história, é sujeira — e quem carrega a prova da rega no estado
   base é o desenho: os torrões empurrados no pé do broto. */
.f-gota{
  position:absolute;right:calc(9% + 134.1px);bottom:calc(1% + 9.1px);
  width:8px;height:12px;opacity:0;
}
/* A ÁGUA É ÁGUA. Ela saía `var(--phosphor)` — `rgb(204,119,34)` no escuro
   e `#834d16` no claro: água LARANJA/marrom, quatro linhas abaixo de um
   comentário cravando "nenhuma cor literal, só tokens `--cena-*`".
   `--phosphor` é o acento de UI (o mesmo do botão e do foco), não token de
   cena, e a cena inteira é pintada por `--cena-*` justamente para
   acompanhar o tema. Entrou `--cena-agua`, azul nos dois temas e escolhido
   contra o céu de cada um (o céu é o que passa atrás da gota). */
.f-gota .gt-corpo{fill:var(--cena-agua);stroke:none}
.f-semente .sm-corpo{fill:var(--cena-casca-t);stroke:var(--cena-traco);stroke-width:1.5}
.f-semente .sm-risco{fill:none;stroke:var(--cena-casca);stroke-width:1.4;stroke-linecap:round}
/* A PLANTA CRESCE NA FRENTE DO SABIÁ — e é literal, não figura de
   linguagem. Foram três versões até chegar aqui, e as duas primeiras
   erraram por lados opostos.

   1 · ELA MORAVA DENTRO DO PÁSSARO, e isso era defeito. Com `right:28%` a
   planta ocupava x 167–287 num quadro de 362px e o Sabiá (148px a
   `right:9%`) ocupava 181–329: 106 dos 120px de largura dela caíam sobre
   ele — de 78% a 99% da ÁREA da planta, por estágio. Nos quadros 0–3 a
   planta desenha de 18 a 60px de tinta, e uma tinta dessas atrás da
   escaleta é raminho: o passo novo de cada unidade, que é a razão da saga
   existir, ficava ilegível.
   A correção de então foi `right:calc(9% + 124px)`, que põe a borda direita
   da planta 4px à ESQUERDA da borda esquerda do Sabiá. Zero sobreposição —
   e isso foi longe demais. O dono nunca pediu os dois lado a lado; ele pediu
   uma árvore que cresce NA FRENTE dele, e era essa regra inventada que
   forçava o pássaro a encolher para a árvore caber.
   Agora a âncora é `right:calc(9% + 26px)`: 26px é 0,35px mais do que os
   25,65px em que está a ponta do bico, ou seja a caixa da planta começa
   imediatamente atrás do bico e cruza o pássaro inteiro. O que a
   sobreposição NÃO pode fazer é cobrir o rosto — é ele que planta, rega e
   colhe. PARADO ele não cobre: na faixa de altura da cabeça (y 68–92 do
   viewBox da planta) a tinta mais à direita é a ponta de um galho, em x=74,
   a `9% + 84,3px` da borda — 13px à esquerda do EIXO da cabeça (`9% + 71px`).
   E ERA SÓ ISSO QUE ESTAVA PROVADO, o que não bastava: esta conta é ESTÁTICA,
   e os gestos TRANSLADAM o pássaro — todos para a esquerda, ou seja para
   dentro da copa. Medido na bancada em 10/09/2026, na versão anterior deste
   PR: no estágio 4 a tinta de `.ar-copa` entrava no disco do olho entre 4,88s
   e 5,40s (29% da área dele aos 5,05s) e no estágio 3 o traço de `.ar-galho`
   raspava a borda do olho entre 3,97s e 4,11s. Os 13px daqui não eram folga
   nenhuma: eram 13px que o gesto gastava e ainda sobrava dívida.
   O CONSERTO FOI NA AMPLITUDE DOS GESTOS, não aqui (ver os `@keyframes fG*`),
   e a folga passou a ser COBRADA AO LONGO DA LINHA DO TEMPO pelo bloco 9i do
   teste: em cada pose de cada um dos seis gestos, em cada um dos seis quadros
   da moldura, o olho e o bico ficam a pelo menos 4px da tinta da planta (o
   pior caso real é 5,3px). O que cruza o pássaro continua sendo o tronco e a
   barriga da copa, e cruzar por ali é justamente a profundidade que a cena
   quer.

   2 · E ELA VOLTOU A SER MEDIDA EM PIXELS, ao contrário do que o
   comentário anterior prometia. A versão em % da ALTURA (`height:48%`)
   consertava um problema e criava outro: com o Sabiá em 148×163 FIXOS, a
   razão planta/Sabiá saía 0,85 no quadro da tela de fim (362×329) e 1,18
   no do "rever" (343×458) — a árvore era mais baixa que o pássaro num
   quadro e mais alta no outro, 39% de variação, com o comentário de
   `.festa-rever .festa-un` jurando "mesma proporção, mesmo quadro". Não
   há como as duas coisas serem constantes: os dois quadros têm proporções
   diferentes (1,10 contra 0,75) e a altura do da tela de fim é
   AUTOMÁTICA. Entre "a planta enche a mesma fração do quadro" e "a cena é
   a mesma composição", a segunda é a que o dono descreveu — e é a que
   todo o resto da cena já seguia: Sabiá 120×132, flor 40px, selo 50px,
   fruta 40px, semente 22px, gota 8×12, todos fixos. A planta era a única
   fora da convenção.

   3 · A ÁRVORE ADULTA TEM DE PARECER ADULTA (dono, 09/09/2026: "o sabiá é
   maior que a arvore? aumenta mais a arvore. bem mais"). Ela estava em 160px
   contra os 163px do Sabiá — razão 0,98, um EMPATE, e empate lê como arbusto.
   Agora são 296px contra 163px: razão 1,82, e a MESMA em todos os quadros de
   uso, porque nenhum deles morde o teto (é o que o piso de `#fim.com-festa`
   garante). Quem manda no limite é a ALTURA, e não mais a largura: 296px é o
   que cabe nos 330px do quadro mais baixo depois do 1% de chão e dos 30px de
   céu. A largura deixou de ser o teto quando a planta passou para a frente do
   pássaro — os 296px viram 224,2px de caixa (viewBox 100/132), e a tinta mais
   à esquerda da copa (x=10,0 de 100, achada DERIVANDO a bézier e não pelo nó
   em x=18) sobra 14,2px da borda na tela de 320px, o quadro mais estreito que
   existe, já descontados os 26px da panorâmica — e 7,5px durante o overshoot
   de `fCresce`. O teste cobre 320, 344, 360 e 375, que é a faixa em que isto
   aperta.
   ESSES DOIS NÚMEROS SÃO PESSIMISTAS DE PROPÓSITO, e vale saber por quê: eles
   somam a panorâmica CHEIA (26px de `fPan3`) com a árvore CHEIA, e as duas
   nunca coexistem. `fPan3` sai de −26px e cruza o zero no meio dos 8s, e a
   planta só chega ao tamanho cheio depois dos ~4,6s (`fCresce`), quando o
   empurrão já é para o outro lado. Medida a folga real, instante a instante,
   o pior caso é 47,8px de borda esquerda (e 49px pela direita, 68,8px pelo
   topo). O teste continua cobrando a soma pessimista porque folga que sobra
   não custa nada e conta que depende de dois relógios baterem juntos é
   armadilha — mas ninguém precisa achar que a copa passa a 7px da moldura.

   4 · O TETO É REDE, NÃO RÉGUA. `max-height:calc(100% - 30px)` promete
   "pelo menos 30px de céu acima da caixa", e a diferença em relação à versão
   anterior é o que ele NÃO é mais: um número que morde em quadro de uso.
   Antes eram 62px e o quadro baixo (~221px) mordia sempre — 159px de planta,
   razão 1,20, e as âncoras em px da gota e da casca errando o pé por ~30px,
   porque o `preserveAspectRatio` encolhe o desenho e o centro do pé anda com
   ele. Isso não se conserta aqui: a âncora horizontal precisaria saber a
   ALTURA do quadro, e `100%` num `right:` mede largura. Foi consertado na
   CAUSA, com o piso de `#fim.com-festa` — sem quadro baixo, o teto não morde
   e a razão é constante.
   Ele fica porque moldura estranha existe (o "rever" é `76vh` numa tela
   deitada, por exemplo): aí a planta degrada encolhendo pelo LADO, com o pé
   no centro da caixa — menor, nunca deslocada —, e a copa continua sem subir
   por cima da linha de XP nem da fila de botões. 30px de caixa são ~57px de
   TINTA de céu, porque o desenho só começa em y=12 de 132.

   Ela fica NA FRENTE do Sabiá (é o que o dono descreveu) e por isso vem
   depois dele no DOM, dentro da mesma camada `.f-frente` — e a fruta
   colhida vem depois DELA, senão a copa cobriria justamente o que ele
   acabou de arrancar. */
.f-arvore-nova{
  position:absolute;right:calc(9% + 26px);bottom:1%;
  height:296px;max-height:calc(100% - 30px);aspect-ratio:100/132;
  transform-origin:bottom center;
}
.f-arvore-nova .ar-chao{fill:var(--cena-chao);opacity:.55}
.f-arvore-nova .ar-tronco{fill:var(--cena-casca);stroke:var(--cena-casca-t);stroke-width:2}
.f-arvore-nova .ar-galho{fill:none;stroke:var(--cena-casca);stroke-width:2.6;stroke-linecap:round}
.f-arvore-nova .ar-copa{transform-box:fill-box;transform-origin:center bottom}
.f-arvore-nova .ar-copa path{fill:var(--cena-folha);stroke:var(--cena-folha-traco);stroke-width:2.5}
/* O FRUTO DA ÁRVORE SEGUE A ESPÉCIE DA TRILHA, e não é mais um
   `<circle>` para toda trilha. A cena afirma que "a árvore dá fruto da
   mesma espécie", e em Intervalo a peça colhida era uma flor de 6 pétalas
   enquanto a árvore dava bolinha — afirmação e desenho discordavam. O
   desenho por espécie sai de `frutoDaArvoreSvg(fruta, x, y)` no aulagame
   (a porta do quadro inteiro é `plantaDaFestaSvg(fruta, estagio)`; a
   `arvoreDaFestaSvg` que este comentário citava não existe desde a saga);
   aqui ficam só as três tintas, todas de token. */
.f-arvore-nova .ar-polpa{fill:var(--cena-fruta-festa);stroke:var(--cena-traco);stroke-width:1.6}
.f-arvore-nova .ar-bractea{fill:var(--cena-folha);stroke:var(--cena-folha-traco);stroke-width:1}
.f-arvore-nova .ar-detalhe{fill:none;stroke:var(--cena-traco);stroke-width:1;opacity:.65}
.f-arvore-nova .ar-cabo{fill:none;stroke:var(--cena-folha-traco);stroke-width:2;stroke-linecap:round}
/* AS PEÇAS DOS CINCO PRIMEIROS QUADROS. Mesma regra de sempre: nenhuma
   cor literal, só tokens `--cena-*` — cor literal não acompanha o tema. */
.f-arvore-nova .ar-casca{fill:var(--cena-casca-t);stroke:var(--cena-traco);stroke-width:1.2}
.f-arvore-nova .ar-brotinho{fill:none;stroke:var(--cena-folha);stroke-width:2;stroke-linecap:round}
.f-arvore-nova .ar-torrao{fill:none;stroke:var(--cena-casca-t);stroke-width:2.4;stroke-linecap:round}
.f-arvore-nova .ar-broto{fill:none;stroke:var(--cena-folha-traco);stroke-width:3;stroke-linecap:round}
.f-arvore-nova .ar-folha{fill:var(--cena-folha);stroke:var(--cena-folha-traco);stroke-width:1.4}
/* O BOTÃO É PEÇA PRÓPRIA, e não "a copa outra vez". Ele nasceu com a MESMA
   tinta (`--cena-folha`) e o MESMO contorno (`--cena-folha-traco`) da copa:
   o único separador era o contorno de 1,2 unidades, que a escala do grupo
   devolvia a 1,06px CSS, com 2,01:1 de contraste contra a folhagem —
   abaixo dos 3:1 que WCAG 1.4.11 pede para peça gráfica. Na tela, a
   unidade dos botões lia como "a mesma copa de novo", e a diferença entre
   os quadros 4 e 5 virava só a cor da fruta.
   Agora são TRÊS diferenças: a cor do que ele vai virar
   (`--cena-botao`, um claro só dele desde que a cereja tomou o lugar da
   flor do ipê, 21/09/2026), um contorno grosso de
   `--cena-traco` (3,31:1 contra a copa no escuro, 4,63:1 no claro — a
   conta roda no teste, contra os tokens reais) e a SÉPALA verde na base,
   que a copa não tem. */
.f-arvore-nova .ar-casulo{fill:var(--cena-botao);stroke:var(--cena-traco);stroke-width:2.5}
.f-arvore-nova .ar-sepala{fill:none;stroke:var(--cena-folha-traco);stroke-width:1.6;stroke-linecap:round}
/* O TRAÇO NÃO PODE AFINAR COM A ESCALA, E ISSO VALE PARA TODAS AS PEÇAS.
   O grupo `.ar-planta` é escalado a partir do pé (é isso que faz o tronco
   do estágio 2 ser o MESMO tronco do 5, e não outro desenho), e o SVG
   escala o contorno junto — fio de cabelo na WebView do app, onde o
   contorno é o que separa a planta do céu.
   A tabela compensava SÓ tronco e galho, e as outras afinavam em silêncio:
   medida, a folha saía a 0,78 no estágio 2 e 0,95 no 3, que é exatamente o
   "fio de cabelo" que o comentário chamava de fatal. Agora toda peça com
   contorno dentro do grupo tem a sua linha, e o valor é uma CONTA — traço
   base ÷ escala do estágio —, então o teste enumera as peças do desenho
   gerado e confere uma por uma, a 0,05 de tolerância. Quem acrescentar
   peça com contorno e esquecer a compensação vê o teste vermelho.
   A COPA É O CASO DUPLO: no estágio 4 ela é escalada duas vezes (o grupo,
   .79, e o `<g class="ar-copa-abre">`, .80), logo a divisão é por .632. */
.est-2 .f-arvore-nova .ar-tronco{stroke-width:3.45}
.est-3 .f-arvore-nova .ar-tronco{stroke-width:2.7}
.est-4 .f-arvore-nova .ar-tronco{stroke-width:2.53}
.est-3 .f-arvore-nova .ar-galho{stroke-width:3.51}
.est-4 .f-arvore-nova .ar-galho{stroke-width:3.29}
.est-2 .f-arvore-nova .ar-folha{stroke-width:2.41}
.est-3 .f-arvore-nova .ar-folha{stroke-width:1.89}
.est-4 .f-arvore-nova .ar-copa path{stroke-width:3.96}
.est-4 .f-arvore-nova .ar-cabo{stroke-width:2.53}
.est-4 .f-arvore-nova .ar-casulo{stroke-width:3.16}
.est-4 .f-arvore-nova .ar-sepala{stroke-width:2.03}
/* o selo mora no ALTO À ESQUERDA, fora do miolo: no meio da tela ele caía
   em cima das estrelas, e à direita encostava nelas — visto na tela */
.f-selo{
  position:absolute;left:5%;top:5%;width:50px;height:50px;
  border-radius:50%;display:grid;place-items:center;
  background:hsl(var(--uh,150) var(--hue-s) var(--hue-l));color:var(--on-hue);
  font-size:22px;font-weight:800;line-height:1;opacity:.92;
}
/* AS NOTINHAS GANHARAM CONTORNO (09/09/2026). Elas sobem do lado do Sabiá
   (56%-77%), e desde que a árvore passou NA FRENTE dele a copa ocupa esse
   pedaço do quadro: nota em `--phosphor` sobre `--cena-folha` é acento de
   UI sobre folhagem, e o contraste depende do tema. O halo na cor do céu
   devolve a separação sem tirar a nota do lugar em que ela faz sentido —
   ela sai de quem canta. */
.f-nota{position:absolute;bottom:16%;font-size:20px;color:var(--phosphor);opacity:0;
  text-shadow:0 0 3px var(--cena-ceu-meio),0 0 6px var(--cena-ceu-meio)}
/* o anúncio do nível: só existe na unidade em que ele SOBE, e fica embaixo,
   fora da faixa do texto do resultado */
.f-nivel{
  position:absolute;left:0;right:0;bottom:4%;text-align:center;
  font-size:13px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;
  color:var(--phosphor);
}
@media (prefers-reduced-motion:no-preference){
  .f-nivel{animation:fSelo .8s cubic-bezier(.34,1.5,.64,1) 5.8s both}
}
/* MENOS MOVIMENTO NÃO É MENOS HISTÓRIA (09/09/2026). O "ele faz alguma
   coisa" que o dono cobrou morava inteiro nos keyframes, então quem pede
   `reduce` via exatamente a cena de que ele reclamou: pássaro parado e
   nada mais. O estado base de CADA estágio é o FIM daquele estágio, e ele
   se lê como frase: a casca aberta com o fio saindo (0), o broto e os
   torrões empurrados (1), o tronco firme (2), os galhos abertos (3), a
   copa com botões (4), os frutos e a fruta colhida no bico (5).
   O único que precisa de ajuda é o 0, e é este bloco: a semente que caiu
   do bico fica VISÍVEL no pé, no mesmíssimo ponto em que o keyframe
   `fSemente` a deixa, para as duas leituras coincidirem — casca abrindo
   sozinha seria cenário, não "ele plantou". Nos outros estágios o
   elemento nem existe, e a regra não tem em quem pegar. */
@media (prefers-reduced-motion:reduce){
  .f-semente{opacity:1;transform:translate(-136px,108px) scale(.55) rotate(300deg)}
}
@media (prefers-reduced-motion:no-preference){
  /* 8s de panorâmica: é o tempo que a HISTÓRIA leva (voo, fruta, semente,
     árvore) mais um respiro. Já foi 10s quando a cena era só travessia, e
     5s antes disso — a 5s ela passava antes de ser vista (dono, 08/09).
     A panorâmica é `ease-in-out` porque linear, numa distância tão curta,
     lê como deslize de máquina. */
  .f-ceu{animation:fPan 8s ease-in-out both}
  .f-arvore{animation:fPan2 8s ease-in-out both}
  .f-frente{animation:fPan3 8s ease-in-out both}
  @keyframes fPan{from{transform:translate3d(-6px,0,0)}to{transform:translate3d(6px,0,0)}}
  @keyframes fPan2{from{transform:translate3d(-14px,5px,0)}to{transform:translate3d(14px,-5px,0)}}
  @keyframes fPan3{from{transform:translate3d(-26px,7px,0)}to{transform:translate3d(26px,-7px,0)}}
  .f-flor{animation:fFlor .85s cubic-bezier(.34,1.35,.64,1) both;animation-delay:calc(.3s + var(--i,0) * .28s)}
  @keyframes fFlor{from{opacity:0;transform:scale(.3)}to{opacity:1;transform:none}}
  /* O VOO ENCURTOU DE 3,6s PARA 2,2s e passou a começar no primeiro quadro:
     ele precisa POUSAR antes de colher a fruta, senão a história começa
     pelo fim. */
  .f-sabia{animation:fVoo 2.2s cubic-bezier(.25,.6,.25,1) both}
  @keyframes fVoo{from{transform:translate(-96vw,34px) rotate(-9deg)}to{transform:none}}
  /* 1 · A FRUTA. Ela começa a amadurecer na videira COM ELE AINDA NO AR
     (antes eram 1,9s de voo e mais nada na tela: tempo morto no começo da
     cena), fica ali para ser vista enquanto ele pousa, sobe num arco até
     o bico e ali ENCOLHE — encolher no bico é o que lê como "virou
     semente". Os tempos vivem num keyframe só de propósito: duas
     animações na mesma propriedade brigam pelo mesmo `opacity` e a fila
     do CSS resolve pela última, o que apagaria o nascimento da fruta.
     O deslocamento é `translate` MEDIDO DO BICO, que é a posição base, e
     `translate(0,0)` é o bico — daí o encolhimento acontecer no lugar certo
     em qualquer quadro.
     E O 0% É A COPA, o que não era o caso até agora: ele tinha ficado em
     `translate(-140px,35px)`, que é o ponto de quando o bico e a planta eram
     outros. Convertido para o viewBox da planta, isso caía em (79,8 · 102,4) —
     32 unidades ABAIXO do centro da copa, na faixa do tronco e do chão: a
     cena mostrava o Sabiá colhendo do PÉ da árvore. O centro da copa é
     (50,70) do viewBox, e no quadro de 330px isso é `9% + 138,1px` da direita
     e `1% + 139,0px` do fundo — 112,4px à esquerda e 30px ACIMA do bico. A
     semente e a gota já tinham sido reancoradas pela conta nova; a fruta
     tinha ficado para trás. */
  .f-fruta{animation:fFruta 1.4s ease-in-out 5.9s both}
  @keyframes fFruta{
    0%  {opacity:0;transform:translate(-136px,-19px) scale(.3)}
    14% {opacity:1;transform:translate(-136px,-19px) scale(1)}
    34% {opacity:1;transform:translate(-136px,-19px) scale(1)}
    62% {opacity:1;transform:translate(-74px,-8px) scale(1) rotate(-8deg)}
    100%{opacity:1;transform:translate(0,0) scale(.86) rotate(4deg)}
  }
  /* 2 · A SEMENTE. Sai do bico (o mesmo ponto em que a fruta encolheu),
     cai num arco, quica uma vez e para no PÉ DA ÁRVORE que vai nascer.
     Ela se abre e SOME AOS ~4,70s, antes dos 4,9s em que `fCresce`
     começa: na primeira versão ela só sumia aos ~5,66s e havia ~760ms de
     árvore crescendo AO LADO da semente — que é a história errada, porque
     a árvore tem de sair DELA. */
  /* E ELA PARA NO PÉ DA PLANTA DE VERDADE, o que não era o caso até
     09/09/2026: o repouso era `translate(-120px,90px)` a partir do bico, e o
     pé da planta ficava a 186,9px dele — a semente parava 67px à DIREITA da
     árvore que ia nascer dela, fora até do torrão de chão (raio de 31,5px).
     Era o mesmo defeito que a rega já tinha levado (a gota caía 76,3px ao
     lado), e ele aparece também em quem pede MENOS MOVIMENTO, porque a regra
     `reduce` congela a casca exatamente neste ponto. Agora o deslocamento é a
     conta: do bico (`9% + 20,8px` / `2% + 85,6px`) ao centro do pé da planta
     (`9% + 210,9px` / `1% + 7,3px`) são 194px à esquerda e ~82px para baixo.
     Os quicos foram reescalados junto, para o arco continuar sendo o mesmo
     arco. */
  .f-semente{animation:fSemente 1.2s cubic-bezier(.4,.1,.7,1) 2.2s both}
  @keyframes fSemente{
    0%  {opacity:0;transform:translate(0,0) scale(.5) rotate(0)}
    10% {opacity:1;transform:translate(-8px,2px) scale(1) rotate(30deg)}
    52% {opacity:1;transform:translate(-118px,108px) scale(1) rotate(200deg)}
    66% {opacity:1;transform:translate(-126px,89px) scale(1) rotate(260deg)}
    80% {opacity:1;transform:translate(-136px,108px) scale(1) rotate(300deg)}
    92% {opacity:0;transform:translate(-136px,108px) scale(.55) rotate(300deg)}
    100%{opacity:0;transform:translate(-136px,108px) scale(.55) rotate(300deg)}
  }
  /* 3 · A ÁRVORE. Ela sobe do ponto onde a semente parou: primeiro fina e
     alta (o broto), depois abre em largura. A copa e os frutos entram
     depois, para o crescimento ter DUAS etapas visíveis em vez de um
     inflar só. Pronta aos 7,0s — dentro dos 6 a 8s que o dono pediu. */
  .f-arvore-nova{animation:fCresce 1.8s cubic-bezier(.3,.9,.3,1) 2.8s both}
  @keyframes fCresce{
    0%  {opacity:0;transform:scale(.06,.02)}
    12% {opacity:1;transform:scale(.1,.12)}
    45% {transform:scale(.34,.72)}
    78% {transform:scale(1.06,1.03)}
    100%{opacity:1;transform:none}
  }
  .f-arvore-nova .ar-copa{animation:fCopa .9s cubic-bezier(.34,1.4,.64,1) 4.5s both}
  @keyframes fCopa{from{opacity:0;transform:scale(.25)}to{opacity:1;transform:none}}
  /* A PEÇA NOVA DO ESTÁGIO entra sempre aos 5,2s, e as três classes dividem
     a mesma regra porque é o MESMO beat: folha (estágios 1, 2 e 3), botão
     (4) e fruto (5). Ter três tempos diferentes para o mesmo momento da
     cena era o caminho curto para uma cena andar fora do compasso da
     outra. `--i` só existe no botão e no fruto, que são três; a folha cai
     no `var(--i,0)` e abre junto. */
  .f-arvore-nova .ar-folha,.f-arvore-nova .ar-botao,.f-arvore-nova .ar-fruta{
    transform-box:fill-box;transform-origin:center;
    animation:fArFruta .5s cubic-bezier(.34,1.5,.64,1) both;
    animation-delay:calc(5.2s + var(--i,0) * .18s);
  }
  @keyframes fArFruta{from{opacity:0;transform:scale(0)}to{opacity:1;transform:none}}
  /* O ESTÁGIO 0 NÃO CRESCE, ele ABRE: `fCresce` sobe uma planta do chão, e
     no primeiro quadro não há planta — há uma semente que se abre. Por isso
     o override, e por isso ele começa aos 3,5s: a semente do bico só some
     aos ~3,30s, e casca abrindo ao lado da semente ainda inteira conta o
     contrário de "saiu dela". */
  .est-0 .f-arvore-nova{animation:fAbreCasca 1.2s cubic-bezier(.34,1.4,.64,1) 3.5s both}
  @keyframes fAbreCasca{from{opacity:0;transform:scale(.45)}to{opacity:1;transform:none}}
  /* AS GOTAS caem escalonadas a partir dos 3,4s, junto com o gesto de regar
     (`fGRegar`, 44% dos 8s = 3,52s). Elas somem ao chegar no chão. */
  .f-gota{animation:fGota 1.1s ease-in both;animation-delay:calc(3.4s + var(--i,0) * .22s)}
  /* o repouso (`translate(0,0)`) é o PÉ DA PLANTA; o 0% é o bico, 194,1px
     à direita e ~76px acima dele. A gota desce e vem para a esquerda, ou
     seja: y sobe de -76 para 0 e x cai de 194 para 0.
     Os números foram de (194,-76) para (112,-94) porque a planta passou para
     a FRENTE do Sabiá (o pé dela andou 73px para a direita, encurtando o
     caminho horizontal) e o Sabiá voltou a 148×163 (o bico subiu de novo:
     `2% + 105,7px` em vez de `2% + 85,6px`). */
  @keyframes fGota{
    0%  {opacity:0;transform:translate(136px,-105px) scale(.6)}
    16% {opacity:1;transform:translate(129px,-91px) scale(1)}
    84% {opacity:1;transform:translate(17px,-13px) scale(1)}
    100%{opacity:0;transform:translate(0px,0px) scale(.5)}
  }
  /* ---------- O GESTO DO SABIÁ, UM POR ESTÁGIO ----------
     Ele é o AUTOR da ação nos seis quadros — o dono foi explícito que o
     pássaro não pode ficar só parado. A CHEGADA (`fVoo`) é a mesma nos
     seis e vive em `.f-sabia`; o GESTO vive no <svg> DE DENTRO, e é por
     isso que os dois não brigam: duas animações na mesma propriedade do
     MESMO elemento se atropelam (a fila do CSS resolve pela última), em
     elementos diferentes elas se somam.
     Todos duram os 8s da cena e não têm atraso, de propósito: assim a
     porcentagem do keyframe É o tempo da cena (44% = 3,52s) e o gesto
     pode ser lido contra a peça que ele causa, sem contas.
     E todos terminam em `transform:none` — gesto que acaba fora do lugar
     deixa o pássaro torto no quadro final, que é exatamente o quadro que
     quem pede menos movimento recebe.

     OS GESTOS ERAM MAGROS, E A MEDIDA ESTÁ AQUI. Fração do tempo em
     `transform:none` na primeira versão: est-0 79% · est-4 75% · est-5 72%
     (e só depois de 5,44s PARADO) · est-2 70% · est-1 67% · est-3 58%. Ou
     seja: em cinco dos seis quadros o pássaro passava a maior parte da
     cena imóvel, o que é a reclamação original do dono ("ele quase não faz
     nada") sobrevivendo dentro da correção dela. E o est-4 não tinha
     translação NENHUMA — só `scale(1,07)` e 4° de giro, que a 148px de
     desenho é quase nada.
     Agora nenhum gesto passa de 30% parado, todos TRANSLADAM de verdade, e
     o gesto começa perto do fim do pouso em vez de esperar o meio da cena.
     AS AMPLITUDES SÃO DE UM SABIÁ DE 148px, e é por isso que elas não
     mudaram quando ele voltou a esse tamanho: foi nele que foram calibradas.
     Enquanto ele esteve em 120px o mesmo px lia 23% maior, e o gesto ficava
     exagerado sem que nada acusasse. Quem mexer no tamanho do pássaro revisa
     esta tabela junto.
     E QUEM MANDA NA AMPLITUDE HORIZONTAL É O ROSTO (10/09/2026). A frase que
     estava aqui — "keyframe não entra em conta de sobreposição nenhuma, só o
     CSS estático entra" — descrevia um buraco, e o dono achou o buraco no
     celular: a planta cresce NA FRENTE do pássaro, então todo px que o gesto
     anda para a ESQUERDA é px para dentro da copa. Medido na bancada, os
     gestos 3 e 4 cobriam o OLHO (estágio 4: 29% da área do olho aos 5,05s;
     estágio 3: o traço do galho raspando a borda entre 3,97s e 4,11s), e o
     rosto é o que conta a cena — ele planta, rega e colhe.
     Os três gestos que chegavam perto foram recalibrados, e cada um pelo
     motivo da sua história:
       · 3 (`fGVoltear`) volteia MAIS CURTO: −46px viraram −16px. Ele
         continua dando a volta (o laço em y vai de −18 a +12), só não entra
         no meio das folhas — voltear é passar POR ENTRE os galhos, não por
         dentro deles;
       · 4 (`fGCantar`) canta PARA O OUTRO LADO: era −12px, agora é +6px. Ele
         "sobe ao lado da copa e canta", e o lado é escolha — a subida (que é
         a história) está toda no y, e virar a subida para a direita afasta o
         rosto da copa em vez de enfiá-lo nela;
       · 5 (`fGColher`) se inclina MENOS e MAIS BAIXO: −24px viraram −19px, e
         o arco desceu 4px. Ele colhe da copa, e colher é esticar o pescoço
         por baixo dela, não atravessá-la.
     A folga não é mais promessa de comentário: o bloco 9i do teste varre a
     linha do tempo dos SEIS gestos, em cada quadro da moldura, e exige 4px
     entre a tinta da planta e o olho/o bico. Pior caso hoje: 5,3px.
     Cada um tem uma segunda metade também: o bico volta, mas balançando —
     é o "ele fez e ficou satisfeito", e é o que tira a impressão de
     boneco que congela. O teste MEDE a fração parada e exige translação;
     contar passos não bastava (três passos todos `none` compilam, rodam e
     não movem nada). */
  /* 0 · ele abaixa o bico e SOLTA a semente na terra (ela sai aos 2,2s) */
  .est-0 .f-sabia svg.sabia-licao{animation:fGPlantar 8s ease-in-out both}
  @keyframes fGPlantar{
    0%,20%{transform:none}
    26%{transform:translate(2px,7px) rotate(8deg)}
    32%{transform:translate(1px,11px) rotate(12deg)}
    38%{transform:translate(-1px,7px) rotate(7deg)}
    46%{transform:translate(-6px,-4px) rotate(-4deg)}
    56%{transform:translate(-9px,-9px) rotate(-6deg)}
    66%{transform:translate(-4px,-3px) rotate(-1deg)}
    76%{transform:translate(2px,-7px) rotate(3deg)}
    88%{transform:translate(1px,-2px) rotate(1deg)}
    100%{transform:none}
  }
  /* 1 · ele tomba três vezes e REGA (as gotas caem aos 3,4s) */
  .est-1 .f-sabia svg.sabia-licao{animation:fGRegar 8s ease-in-out both}
  @keyframes fGRegar{
    0%,18%{transform:none}
    26%{transform:translate(-7px,-4px) rotate(-5deg)}
    34%{transform:translate(-4px,3px) rotate(-12deg)}
    42%{transform:translate(-3px,5px) rotate(-15deg)}
    50%{transform:translate(-4px,2px) rotate(-8deg)}
    58%{transform:translate(-3px,5px) rotate(-15deg)}
    68%{transform:translate(-6px,-2px) rotate(-4deg)}
    80%{transform:translate(3px,-6px) rotate(4deg)}
    90%{transform:translate(1px,-2px) rotate(1deg)}
    100%{transform:none}
  }
  /* 2 · ele BATE AS ASAS ao lado do tronco que firma — e desde 09/09/2026
     a ASA é que bate. O gesto do quadro 2 era o corpo subindo 10px, e o
     bater de asa que se via era o `sbAsa` OCIOSO, o mesmo que roda nos
     seis quadros: o "gesto próprio" do estágio era 10px contra os 4px de
     `sbBob` que já rodavam sempre. Nada no CSS acusava, porque a asa
     mexia — só não por causa deste estágio. `fGAsa` sobrepõe o ocioso (5
     classes contra 2, ganha por especificidade) e bate FORTE, 34° contra
     os 7° do repouso, na janela em que ele sobe. */
  .est-2 .f-sabia svg.sabia-licao{animation:fGBater 8s ease-in-out both}
  @keyframes fGBater{
    0%,16%{transform:none}
    24%{transform:translate(-4px,-14px) rotate(-4deg) scale(1.03)}
    32%{transform:translate(-8px,-4px) rotate(2deg) scale(1)}
    40%{transform:translate(-5px,-18px) rotate(-5deg) scale(1.04)}
    48%{transform:translate(-10px,-6px) rotate(3deg) scale(1)}
    58%{transform:translate(-6px,-16px) rotate(-3deg) scale(1.03)}
    70%{transform:translate(-2px,-5px) rotate(1deg) scale(1)}
    84%{transform:translate(1px,-8px) rotate(-1deg) scale(1.01)}
    100%{transform:none}
  }
  .est-2 .f-sabia svg.sabia-licao .sb-asa{animation:fGAsa 8s ease-in-out both}
  @keyframes fGAsa{
    0%,14%{transform:none}
    20%{transform:rotate(-34deg)}
    26%{transform:rotate(4deg)}
    32%{transform:rotate(-34deg)}
    38%{transform:rotate(4deg)}
    44%{transform:rotate(-34deg)}
    50%{transform:rotate(2deg)}
    58%{transform:rotate(-30deg)}
    66%{transform:rotate(2deg)}
    74%{transform:rotate(-18deg)}
    86%{transform:rotate(-4deg)}
    100%{transform:none}
  }
  /* 3 · ele VOLTEIA por entre os galhos que acabaram de abrir */
  .est-3 .f-sabia svg.sabia-licao{animation:fGVoltear 8s ease-in-out both}
  @keyframes fGVoltear{
    0%,18%{transform:none}
    26%{transform:translate(-6px,-12px) rotate(-7deg)}
    34%{transform:translate(-11px,-16px) rotate(-9deg)}
    44%{transform:translate(-16px,-4px) rotate(5deg)}
    54%{transform:translate(-15px,12px) rotate(8deg)}
    64%{transform:translate(-8px,6px) rotate(3deg)}
    74%{transform:translate(-5px,-9px) rotate(-4deg)}
    86%{transform:translate(-2px,-4px) rotate(-1deg)}
    100%{transform:none}
  }
  /* 4 · ele sobe ao lado da copa e CANTA, e os botões abrem no canto (as
     notinhas saem aos 5,5s, do lado dele — é ele que canta) */
  .est-4 .f-sabia svg.sabia-licao{animation:fGCantar 8s ease-in-out both}
  @keyframes fGCantar{
    0%,28%{transform:none}
    36%{transform:translate(2px,-14px) rotate(-4deg)}
    46%{transform:translate(6px,-22px) rotate(-6deg) scale(1.04)}
    56%{transform:translate(3px,-17px) rotate(-2deg) scale(1.07)}
    64%{transform:translate(5px,-21px) rotate(-5deg) scale(1.03)}
    72%{transform:translate(2px,-15px) rotate(-1deg) scale(1.07)}
    82%{transform:translate(1px,-8px) rotate(-3deg) scale(1.02)}
    92%{transform:translate(0px,-3px) rotate(-1deg)}
    100%{transform:none}
  }
  /* 5 · ele se INCLINA para a árvore e COLHE (a fruta sobe aos 5,9s). O
     movimento começa aos 2,4s e não aos 5,44s: cinco segundos e meio de
     pássaro parado antes do clímax era o quadro mais imóvel da saga. */
  .est-5 .f-sabia svg.sabia-licao{animation:fGColher 8s ease-in-out both}
  @keyframes fGColher{
    0%,30%{transform:none}
    40%{transform:translate(-8px,-2px) rotate(-4deg)}
    50%{transform:translate(-16px,3px) rotate(-6deg)}
    60%{transform:translate(-13px,8px) rotate(-3deg)}
    70%{transform:translate(-19px,12px) rotate(-9deg)}
    78%{transform:translate(-11px,9px) rotate(-6deg)}
    86%{transform:translate(-5px,3px) rotate(-2deg)}
    94%{transform:translate(-2px,-3px) rotate(1deg)}
    100%{transform:none}
  }
  /* O SELO SAIU DA AGLOMERAÇÃO: estava aos 5,4s, no meio dos 5,4–7,3s em
     que já aconteciam a árvore, a copa, três frutos e quatro notas ao
     mesmo tempo. Aos 2,6s ele preenche o vão entre o pouso e a subida da
     fruta ao bico, e o fim da cena fica com uma coisa por vez. */
  .f-selo{animation:fSelo .8s cubic-bezier(.34,1.5,.64,1) 2.6s both}
  @keyframes fSelo{from{opacity:0;transform:scale(2.3) rotate(-12deg)}to{opacity:1;transform:none}}
  /* as notinhas saem do lado ESQUERDO (eram 58%–79%, por dentro da árvore
     nova) e com passo maior entre elas, para não virarem uma rajada só */
  .f-nota{animation:fNota 1.8s ease-out both;animation-delay:calc(5.5s + var(--i,0) * .3s)}
  @keyframes fNota{from{opacity:0;transform:none}20%{opacity:.9}to{opacity:0;transform:translate(20px,-118px)}}
}

/* ---------- O SWITCH, AS ABAS E O "i" DO ⚙ (rodada 1 da reforma) ----------
   Binário é interruptor: 48×28 com bola de 22, alvo de 44 de altura, e o
   ligado na cor de marca. A linha vira rótulo à esquerda e interruptor à
   direita, em letra de corpo (14px) — o rótulo de 10px em caixa alta que
   a página usa no exercício é pequeno demais para uma lista de ajustes.
   As dicas ficam recolhidas atrás do "i" (28px, alvo de 44 pelo padding).
   `:has` é o que deixa a regra vencer o `#ajustesTrilha .field` da
   página sem mexer nela. */
.msk-switch{
  flex:none;width:48px;height:28px;margin:8px 0;padding:0;border-radius:999px;
  border:1.5px solid var(--line-2);background:var(--well);position:relative;
  transition:background .15s,border-color .15s;
}
.msk-switch-bola{
  position:absolute;top:2px;left:2px;width:21px;height:21px;border-radius:50%;
  background:var(--ink-faint);transition:transform .15s,background .15s;
}
.msk-switch[aria-checked="true"]{background:var(--phosphor);border-color:var(--phosphor)}
.msk-switch[aria-checked="true"] .msk-switch-bola{transform:translateX(20px);background:var(--on-hue)}
.msk-switch:focus-visible{outline-offset:3px}
@media (prefers-reduced-motion:reduce){.msk-switch,.msk-switch-bola{transition:none}}
/* `.msk-tem-switch` é posto por switchDeSeg() na linha que ganhou o
   interruptor — pelo mesmo motivo do `.msk-com-topo` acima: com `:has()`
   estas quatro regras sumiam na WebView do app e os ajustes voltavam a ser
   rótulo em cima, controle embaixo. */
#mskSel .field.msk-tem-switch,.msk-pref.msk-tem-switch{
  flex-direction:row;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:6px 10px;
}
#mskSel .field.msk-tem-switch > span,.msk-pref.msk-tem-switch > span{
  flex:1 1 160px;width:auto;min-width:0;display:flex;align-items:center;gap:6px;
  font-size:14px;letter-spacing:0;text-transform:none;color:var(--ink);line-height:1.3;
}
#mskSel .field.msk-tem-switch > span > b,.msk-pref > span b{font-size:14px}
body.hivis #mskSel .field.msk-tem-switch > span,body.hivis .msk-pref.msk-tem-switch > span{font-size:15.5px}
#mskSel .fieldhint,.msk-hint{display:none;flex:1 1 100%;margin:0;font-size:12.5px;line-height:1.45;color:var(--ink-dim)}
#mskSel .fieldhint.aberta,.msk-hint.aberta{display:block}
.msk-info{
  flex:none;width:28px;height:28px;margin:-8px 0;border-radius:50%;
  border:1.5px solid var(--line-2);color:var(--ink-dim);background:var(--panel);
  font-family:var(--font-mono);font-size:13px;font-weight:700;line-height:1;
}
.msk-info[aria-expanded="true"]{color:var(--phosphor);border-color:var(--phosphor)}
/* o par escondido pelo switch: `display:flex` do próprio par venceria o
   [hidden] das páginas que não o declaram com !important (o mural) */
.seg[hidden],.msk-prefseg[hidden]{display:none}
.msk-abas{width:100%;margin:0 0 12px}
.msk-abas button{flex:1}
/* as linhas de 3+ opções: rótulo e "i" numa linha, o par inteiro na
   linha de baixo — e o rótulo em 12px, não nos 10px em caixa alta
   espaçada do exercício */
#mskSel .field:not(.msk-tem-switch){flex-direction:row;flex-wrap:wrap;align-items:center;gap:6px 8px}
#mskSel .field:not(.msk-tem-switch) > span{flex:0 1 auto;width:auto;font-size:12px;letter-spacing:.06em;color:var(--ink-dim)}
#mskSel .field:not(.msk-tem-switch) > .seg{flex:1 1 100%;margin:0}

/* ================================================================
   PAUTA E METRÔNOMO — a casca do que musikai-app.js desenha

   Mora aqui pelo mesmo motivo que o desenho: havia uma cópia do bloco
   no fim de cada página, e regra de estilo com dois donos vira duas
   regras. A ALTURA da pauta é a decisão de layout que importa — ela é
   alta por natureza (duas pautas empilhadas) e a tela do exercício não
   pode rolar, então o teto acompanha a tela em vez de ser fixo.
   ================================================================ */
/* ---------------- A PAUTA É UMA FOLHA, NOS DOIS TEMAS ----------------

   POR QUE A PAUTA NÃO SEGUE O TEMA. Partitura é preto sobre branco há
   cinco séculos, e é assim que o aluno vê a mesma música no caderno, na
   apostila e na prova. Notação clara sobre fundo escuro existe (alguns
   editores fazem), mas ela troca o que o olho treinou: a cabeça de nota
   deixa de ser a mancha ESCURA sobre o claro e vira o contrário, e num
   vão de 5 a 11 px isso custa leitura. Então a pauta é uma FOLHA — clara
   nos dois temas — e o resto da tela continua seguindo o tema do app.

   O PAPEL NÃO É BRANCO. No tema escuro uma folha #fff a 100% de
   luminância no meio de um fundo #0a0c0e é um facho na cara de quem
   estuda à noite. O papel do tema escuro é quente e rebaixado (#ded4bf,
   ~60% de luminância): continua lendo como papel, e a tinta ainda tira
   12:1 em cima dele — muito acima do 4,5:1 exigido. No tema claro o
   papel é quase branco (#fdfbf4), um tom ACIMA do bege da página, que é
   o que faz a folha se destacar da mesa em vez de sumir nela.

   A BORDA É A FRONTEIRA. Uma ilha clara sem borda dentro do escuro
   parece bug de tema. Com uma borda de papel envelhecido e uma sombra
   curta por baixo, ela parece uma folha POUSADA — que é o que é. */
.pauta-box,
.pauta-folha,
.pauta-opcoes button{
  --folha-papel:#ded4bf;   /* papel quente, rebaixado para não ofuscar  */
  --folha-tinta:#17140e;   /* nanquim: cabeças, claves, acidentes       */
  --folha-linha:#3d372b;   /* pentagrama, suplementares, chave e barra  */
  --folha-ok:#0b5c38;      /* veredito certo, escurecido para o papel   */
  --folha-erro:#9e2a14;    /* veredito errado, idem                     */
  --folha-acento:#7a4310;  /* a letra A/B/C da opção aberta              */
  /* AVISO, E NÃO VEREDITO. O fantasma da voz fora da margem NÃO pode usar
     `--folha-erro`: enquanto o aluno ainda canta não houve julgamento
     nenhum, e pintar de vermelho o que está em curso é dar por perdida
     uma nota que ele ainda pode acertar segurando mais um pouco. Âmbar é
     a cor de "atenção" no app inteiro; aqui ela desce ao papel. Medido
     sobre o papel: 4,03:1 no escuro e 5,72:1 no claro — acima dos 3:1 que
     a WCAG pede para objeto gráfico, com folga. */
  --folha-alerta:#8a5a00;
  --folha-borda:#b3a68a;
  --folha-sombra:rgba(0,0,0,.45);
}
:root[data-theme="light"] .pauta-box,
:root[data-theme="light"] .pauta-folha,
:root[data-theme="light"] .pauta-opcoes button{
  --folha-papel:#fdfbf4;
  --folha-tinta:#14120c;
  --folha-linha:#3a342a;
  --folha-acento:#834d16;
  /* A FRONTEIRA NO TEMA CLARO é o caso difícil: papel quase branco sobre
     mesa bege dá 1,09:1, e não existe borda "de papel" que chegue aos
     3:1 do WCAG para elemento não-textual sem virar contorno grosso
     (medi: nem #a89772 passa de 2,54:1). Quem separa a folha da mesa
     aqui é a SOMBRA — que é o que separa papel de mesa no mundo também.
     A borda fica em 2,03:1, visível, e no modo A+ ela sobe para 3,44:1,
     que é onde a exigência realmente morde. A notação em si, que é o que
     precisa ser lido, está em 18:1. */
  --folha-borda:#b9aa88;
  --folha-sombra:rgba(70,60,40,.22);
}
/* A+ sobre a folha: a tinta vai a preto e as linhas engrossam (regra em
   .pauta-linha). O A+ global mexe em --ink/--line, que a folha não usa —
   por isso ela precisa do seu próprio reforço, senão o modo não faz nada
   justamente onde mais importa, que é a leitura. */
body.hivis .pauta-box,
body.hivis .pauta-folha,
body.hivis .pauta-opcoes button{
  --folha-tinta:#000000;
  --folha-linha:#1e1a13;
  --folha-ok:#08462a;
  --folha-erro:#7d1f0e;
  --folha-acento:#5e3208;
  --folha-alerta:#7d4f00;  /* 4,77:1 no papel escuro, 6,78:1 no claro */
  --folha-borda:#8d8064;
}
/* A CIFRA SOB O GRUPO DE NOTAS DO ACORDE (17/09/2026). Pedido do prof.
   Danilo: "sempre escreva a cifra, C, G, A, F, etc." De manhã foi um rótulo
   HTML acima da `.pauta-box`; o dono testou no celular (print de 17/09) e
   viu o "F" solto no canto, sem nota nenhuma embaixo. Decisão dele: uma
   cifra POR ACORDE, dentro do SVG da pauta, centrada sob o grupo de notas
   daquele acorde (desenharPauta, musikai-app.js). É tinta da FOLHA, como a
   cabeça e a clave — a pauta é papel nos dois temas —, e o tamanho mora no
   próprio SVG (font-size 9 do viewBox), para escalar com a pauta. */
.pauta-cifra{fill:var(--folha-tinta);font-weight:700;letter-spacing:.02em}
body.hivis .pauta-cifra{font-weight:800}
/* A CIFRA NA COR DO GRUPO (foto do prof. Danilo, 17/09/2026, lição 2 da
   unidade 5): sobre as notas-alvo (verdes, `tinta:'alvo'`) a cifra do
   relativo é verde — o "F♯m" da foto; fechado o grupo (todas ✓) segue na
   tinta de acerto, que no papel é o mesmo verde; parado nele (algum ✗),
   vermelho. A classe vem de `desenharPauta`, no próprio <text>. */
.pauta-cifra-alvo,.pauta-cifra-ok{fill:var(--folha-ok)}
.pauta-cifra-erro{fill:var(--folha-erro)}
.pauta-box{
  margin-top:12px;padding:8px 6px;
  border:1px solid var(--folha-borda);border-radius:10px;
  background:var(--folha-papel);
  box-shadow:0 1px 3px var(--folha-sombra);
}
/* ALTURA DECLARADA, e não `height:auto`. Com largura de 100% e altura
   automática, o SVG assume a PROPORÇÃO do viewBox: a pauta do ditado
   (74 por 54 de viewBox) virava 216px de altura numa coluna de 296px e
   sozinha estourava a tela do exercício. Com as duas medidas dadas, o
   preserveAspectRatio="meet" desenha a pauta tão grande quanto couber
   DENTRO da caixa e centraliza — que é o comportamento que se quer de
   uma partitura: ela ocupa o espaço que há, sem empurrar nada. */
/* 79px e não 74px: a caixa da pauta simples passou de 54 para 58
   unidades de viewBox para caber a clave da Bravura inteira (ver
   desenharPauta). Sem esses 5px o vão entre as linhas encolheria 7% —
   a mesma pauta, 7% mais difícil de ler. Com eles, o vão medido em
   375px continua igual ao de antes. */
.pauta-svg{display:block;margin:0 auto;width:100%;height:79px;overflow:visible}
/* AS CLAVES são PREENCHIMENTO, não traço. Enquanto eram desenhadas à
   mão fazia sentido tratá-las como os outros ícones do app (traço de
   espessura única, currentColor, pontas redondas) — mas glifo de
   partitura de verdade tem espessura MODULADA: grosso no eixo da
   espiral, fino nas pontas. É essa modulação que faz a clave parecer
   impressa em vez de rabiscada, e traço de espessura constante não a
   reproduz. Agora o contorno vem da Bravura e é a área que pinta.

   A clave é tão escura quanto a nota: numa lição de leitura ela não é
   enfeite, é o que diz se a linha é sol ou fá. */
.pauta-clave{fill:var(--folha-tinta);stroke:none}
/* os acidentes da ARMADURA são a mesma tinta da clave e da nota: numa
   armadura eles não são enfeite, são o que o aluno tem de contar */
.pauta-arm{fill:var(--folha-tinta);stroke:none}
/* pentagrama, linhas suplementares, chave e barra da pauta dupla */
.pauta-linha{stroke:var(--folha-linha);stroke-width:1.05}
.pauta-sup{stroke:var(--folha-linha);stroke-width:1.3;stroke-linecap:round}
.pauta-chave{fill:var(--folha-linha);stroke:none}
.pauta-barra{stroke:var(--folha-linha);stroke-width:1.6}
/* A CABEÇA DE NOTA É A SEMIBREVE DA BRAVURA (noteheadWhole), desde
   03/09/2026. O miolo vazado vem do próprio glifo (segundo subcaminho),
   então a linha da pauta atravessa por dentro sem `fill:none` nenhum —
   e a espessura das laterais é a da fonte, não um traço nosso (o traço
   de 36 unidades que engrossava o noteheadBlack foi embora com ele).
   Quem pinta é o JS, pelo `fill`, porque a cor carrega o veredito
   (tinta / verde / vermelho); aqui fica só o que é fixo. */
.pauta-cabeca{stroke:none}
/* o ✓/✗ do veredito: traço, não preenchimento, para ficar nítido a 8px */
.pauta-sinal{fill:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
body.hivis .pauta-sinal{stroke-width:2.1}

/* ---------- O SENSOR DA VOZ: A FITA, OS PONTOS E A CABEÇA FANTASMA (04/09/2026) ----------
   O híbrido "pauta viva + fita de afinação" aprovado pelo dono; substitui
   o halo de 27/08/2026 (opção 4 do docs/sensor-captacao-opcoes.html). O
   desenho e a geometria estão em `desenharPauta`/`pintarSensorDaPauta`
   (musikai-app.js); aqui ficam só a TINTA e o movimento.

   A COR É A DISTÂNCIA, e nunca o veredito: verde (`--folha-ok`) quando a
   voz está dentro da margem do rigor, âmbar (`--folha-alerta`) até meio
   tom, neutro (`--folha-linha`) além disso. `--folha-erro` NÃO entra aqui
   (ver o porquê junto do token): enquanto o aluno canta não houve
   julgamento nenhum. A faixa chega como classe do grupo (`dentro` /
   `perto` / `longe`), publicada pelo pintor — e a bolinha da fita e a
   cabeça fantasma leem a MESMA faixa, porque são a mesma voz. Nenhum
   dos desenhos é a ÚNICA pista: a `.gravtxt` diz a fase por escrito
   ("cantando…", "segure!"), a posição ("2ª de 4") e o desvio. */
.pauta-fita-trilho{fill:var(--folha-linha);fill-opacity:.28}
.pauta-fita-tique{stroke:var(--folha-linha);stroke-width:.8;opacity:.6}
/* os rótulos das pontas (AGUDO em cima, GRAVE embaixo): caixa alta, letras
   espaçadas, na tinta da linha — discretos como no protótipo aprovado */
.pauta-fita-rot{font:600 4.6px var(--font-ui);fill:var(--folha-linha);letter-spacing:.08em}
/* a faixa verde é a TOLERÂNCIA do rigor, desenhada — pintada leve para a
   bolinha continuar legível por cima dela */
.pauta-fita-verde{fill:var(--folha-ok);fill-opacity:.16;stroke:var(--folha-ok);stroke-opacity:.55;stroke-width:.8}
.pauta-fita-alvo{stroke:var(--folha-tinta);stroke-width:1.4}
/* A BOLINHA DA VOZ tem dois estados de desenho. SEM FAIXA (ninguém está
   cantando) ela é o ANEL do protótipo: vazia, tracejada, parada no alvo —
   "é aqui que a sua voz vai encaixar". COM FAIXA ela se enche na cor da
   distância, com contorno de papel para se destacar da faixa e do trilho.
   As duas regras de cor logo abaixo são as que os testes cravam ao pé da
   letra; o resto do preenchimento fica nesta regra composta. */
.pauta-fita-voz{fill:none;stroke:var(--folha-linha);stroke-width:1;stroke-dasharray:1.6 1.6}
.pauta-fita.dentro .pauta-fita-voz,.pauta-fita.perto .pauta-fita-voz,.pauta-fita.longe .pauta-fita-voz{
  stroke:var(--folha-papel);stroke-width:.8;stroke-dasharray:none
}
.pauta-fita.longe .pauta-fita-voz{fill:var(--folha-linha)}
.pauta-fita.perto .pauta-fita-voz{fill:var(--folha-alerta)}
.pauta-fita.dentro .pauta-fita-voz{fill:var(--folha-ok)}
/* O FIO PONTILHADO da bolinha até a cabeça fantasma: a mesma voz em duas
   escalas. Segue a faixa da fita (o grupo é o mesmo). O JS acende e apaga
   pela opacidade; aqui só a tinta. */
.pauta-fita-liga{stroke:var(--folha-linha);stroke-width:.7;stroke-dasharray:1.2 1.8;opacity:.6}
.pauta-fita.perto .pauta-fita-liga{stroke:var(--folha-alerta)}
.pauta-fita.dentro .pauta-fita-liga{stroke:var(--folha-ok)}
/* A SETA, A PALAVRA E AS DUAS LEGENDAS SAÍRAM (rodada "tela limpa",
   05/09/2026): a mesma direção e o mesmo nome apareciam três vezes na
   tela ao mesmo tempo. Quem diz a direção agora é só a frase do
   rodapé; o nome mora na instrução do exercício ou no veredito. */
/* A PAUTA COM SENSOR É MAIS ALTA (110px, e não os 79 de toda pauta): o
   viewBox ganhou a faixa de legenda embaixo, e a fita — "quase da altura
   do papel", como no protótipo — precisa de papel para ser lida. Só a
   pauta de canto paga isso; leitura, ditado e percepção ficam nos 79px. */
.pauta-svg.pauta-canto{height:110px}
body.hivis .pauta-fita-rot{font-weight:700}
/* OS TRÊS PONTOS (`.pauta-ponto`) SAÍRAM DO PAPEL em 17/09/2026 — pedido do
   dono: "as 3 bolinhas e a setinha de acerto devem ficar ABAIXO da pauta".
   O relógio da sustentação agora é `.gravpontos`, no pé da escuta (regra
   mais abaixo, junto de `.gravdir`); o SVG guarda só o contador. */
body.hivis .pauta-fita-alvo{stroke-width:2}
body.hivis .pauta-fita-voz{stroke-width:1.3}
body.hivis .pauta-fita.dentro .pauta-fita-voz,body.hivis .pauta-fita.perto .pauta-fita-voz,body.hivis .pauta-fita.longe .pauta-fita-voz{stroke-width:1.1}
/* A CABEÇA FANTASMA é onde a voz ESTÁ — vazada e tracejada porque não é
   notação: é medição. O tracejado é o mesmo da nota oculta do ditado
   (`3 2.4`), pelo motivo de sempre: dois jeitos de desenhar "isto não é
   uma nota escrita" acabariam divergindo. */
.pauta-fantasma-cabeca{
  fill:none;stroke:var(--folha-linha);stroke-width:1.6;stroke-dasharray:3 2.4;
}
.pauta-fantasma.perto .pauta-fantasma-cabeca{stroke:var(--folha-alerta)}
.pauta-fantasma.dentro .pauta-fantasma-cabeca{stroke:var(--folha-ok)}
body.hivis .pauta-fantasma-cabeca{stroke-width:2.1}
/* O MOVIMENTO É SUAVIZAÇÃO, NUNCA INFORMAÇÃO: quem manda é o quadro de
   áudio (`pintarGravacao`), que reescreve estes valores 60 vezes por
   segundo. A transição só tira o serrilhado entre um quadro e outro — por
   isso ela pode sumir inteira sem custar nada a quem pediu movimento
   reduzido, ao contrário da barra, que sem animação precisava dos cinco
   degraus para continuar contando o tempo. Os pontos não têm transição:
   acender é um fato, não um deslizar. */
@media (prefers-reduced-motion:no-preference){
  .pauta-fita-voz{transition:transform .09s linear,opacity .15s}
  .pauta-fantasma{transition:transform .09s linear,opacity .15s}
  /* O "AINDA ESTOU AQUI" DA CONFERÊNCIA DE SÍLABA (pedido do dono,
     21/08/2026) veio dos três blocos, passou pelo halo e agora é a
     bolinha da fita, com o MESMO keyframe (`conferindoPulso`, declarado
     mais abaixo, junto da barra) — inventar um segundo pulso na mesma
     tela seria pedir ao aluno dois vocabulários para a mesma espera. A
     classe só é ligada quando o app não está em movimento reduzido: essa
     preferência é lida em JS (ver `pintarGravacao`), e media query
     sozinha não a enxerga. */
  .pauta-fita.conferindo .pauta-fita-voz{animation:conferindoPulso .6s ease-in-out infinite}
}
body.hivis .pauta-linha{stroke-width:1.5}
body.hivis .pauta-sup{stroke-width:1.8}
body.hivis .pauta-barra{stroke-width:2.2}
/* TETO ADAPTATIVO, no mesmo espírito do veredito do aulagame: a pauta
   dupla cresce até onde a tela deixa e para ali. Os 470px descontados
   são o resto da tela do exercício (topo, progresso, enunciado, ajustes
   do canto, botões, veredito e a tab bar). svh, e não vh, porque em
   celular o vh não desconta a barra de endereço. */
.pauta-dupla{
  height:min(134px, max(90px, calc(100svh - 480px)));
}
/* ---------- AS TRÊS OPÇÕES DA LEITURA: EMPILHADAS ----------
   Eram três pautas LADO A LADO, e quem as apertava era a LARGURA: numa
   coluna de 88px o SVG cai para 0,6 do tamanho e o vão entre as linhas
   da pauta fica em 4,8px — medido. Largura, ao contrário de altura, não
   melhora com o aparelho: aquele 4,8px seria o mesmo num tablet.
   Empilhadas, cada pauta tem a coluna inteira e quem passa a limitar é a
   ALTURA, que cresce com a tela.

   AS REGRAS APONTAM PARA `.pauta-svg`, E NÃO PARA `.pauta-dupla`. Desde
   17/08/2026 as opções da leitura são pauta SIMPLES (a ata do prof.
   Danilo tirou a pauta dupla de cena, por enquanto), e `.pauta-dupla` é
   uma classe que só a dupla recebe — deixar as alturas amarradas a ela
   fazia as três opções nascerem com os 79px do padrão e a tela estourar.
   `.pauta-svg` está nas duas, então isto continua valendo no dia em que
   a pauta dupla voltar.

   SÓ UMA ABERTA POR VEZ. Três pautas inteiras em coluna não cabem em
   320×568 — nem perto. A aberta fica em tamanho cheio e as outras duas
   viram TIRAS: ainda se vê que são três e qual é qual, e um toque abre a
   que se quer ler. Quem responde é o toque na que JÁ está aberta.

   O ALVO DE TOQUE da tira é 44px, e não os 30px da caixa: mesmo recurso
   dos botões do cabeçalho (::after com inset negativo). O gap de 14px é
   exatamente o que faz dois alvos de 44px encostarem sem se sobrepor —
   a mesma conta do .msk-topo. */
.pauta-opcoes{display:flex;flex-direction:column;gap:14px;margin-top:12px}
/* cada opção da leitura é uma folhinha — mesma regra da pauta grande,
   senão a mesma notação apareceria em papel numa tela e no fundo do tema
   na outra */
.pauta-opcoes button{
  position:relative;display:flex;align-items:center;gap:8px;
  padding:4px 8px;border:1px solid var(--folha-borda);border-radius:10px;
  background:var(--folha-papel);
  box-shadow:0 1px 3px var(--folha-sombra);
  transition:border-color .15s,background .15s,box-shadow .15s;
}
.pauta-opcoes button::after{content:"";position:absolute;inset:-7px 0}
.pauta-opcoes button[aria-expanded="true"]::after{content:none}  /* a aberta já é alta */
/* a letra fica SOBRE o papel, então é tinta de papel — com --ink-faint
   (cinza claro, feito para fundo escuro) ela sumia na folha */
.pauta-opcoes .pauta-letra{
  flex:none;width:14px;text-align:center;line-height:1;
  font-family:var(--font-ui);font-size:11px;font-weight:800;color:var(--folha-linha);
}
.pauta-opcoes .pauta-corpo{flex:1;min-width:0}
.pauta-opcoes button[aria-expanded="true"]{
  border-color:var(--folha-acento);box-shadow:0 0 0 1px var(--folha-acento),0 2px 6px var(--folha-sombra);
}
.pauta-opcoes button[aria-expanded="true"] .pauta-letra{color:var(--folha-acento)}
body.hivis .pauta-opcoes .pauta-letra{font-size:13px}
/* a tira: 20px de pauta dentro de uma caixa de 30px */
.pauta-opcoes .pauta-svg{height:20px}
/* a ABERTA leva tudo o que a tela deixar. Os 485px descontados são o
   resto da tela do exercício MENOS a reserva do veredito, que enquanto
   ninguém respondeu está vazia e é emprestada à pauta (ver
   `body.pauta-escolha` no aulagame). O teto passou de 200 para 140px
   quando a opção virou pauta SIMPLES: a caixa dela tem 58 unidades de
   viewBox contra as 120 da dupla, então com `meet` tudo acima de ~131px
   (medido a 330px de coluna) já é margem vazia — altura reservada que
   não vira vão entre linhas nenhum. */
.pauta-opcoes button[aria-expanded="true"] .pauta-svg{
  height:min(140px, max(52px, calc(100svh - 485px)));
}
/* RESPONDIDA: ninguém precisa mais LER as opções — as três viram tira e
   a altura volta para o veredito, que é o que se lê agora. */
.pauta-opcoes.respondida{gap:6px}
.pauta-opcoes.respondida .pauta-svg,
.pauta-opcoes.respondida button[aria-expanded="true"] .pauta-svg{height:12px}
.pauta-opcoes button:hover:not(:disabled){border-color:var(--line-2)}
.pauta-opcoes button:disabled{opacity:.45;cursor:default}
/* MESMA REGRA DAS NOTAS: a opção certa e a errada não podem diferir só
   pela cor da borda. Cada uma ganha um selo no canto — ✓ ou ✗ —, como
   SVG embutido em data-URI: self-contained, sem requisição e sem
   depender de fonte, igual ao resto da notação.

   O SELO ENTRA COMO MÁSCARA, não como imagem de fundo. Cor não atravessa
   data-URI (nem `currentColor` nem `var()` são resolvidos lá dentro), e
   com a cor escrita no desenho o selo ficaria surdo ao modo A+, que é
   justamente quem mais precisa dele. Com máscara, o desenho é só a
   forma e quem pinta é o `background-color` — daí ele acompanha
   --folha-ok/--folha-erro em qualquer tema e no A+. */
.pauta-opcoes button.right,
.pauta-opcoes button.wrong{position:relative;opacity:1}
.pauta-opcoes button.right{border-color:var(--folha-ok);box-shadow:0 0 0 1px var(--folha-ok)}
.pauta-opcoes button.wrong{border-color:var(--folha-erro);box-shadow:0 0 0 1px var(--folha-erro)}
/* ---------- MOSTRADOR DE VOZ (aulagame · percepção) ----------
   A leitura ao vivo do que está saindo da voz. A MESMA declaração estava
   escrita à mão nas duas páginas, palavra por palavra (#voiceOut no
   aulagame, #voiceOut e #voice4Out na percepção) — duas cópias que
   precisavam concordar e que ninguém lembraria de mudar juntas.

   #voiceCont é o contador ("· ouvindo 12") do aulagame, IRMÃO da região
   viva e nunca filho (o porquê está na marcação da página e no cabeçalho
   do sw.js): dentro dela, `aria-atomic` reapresentaria tudo a cada quadro
   de áudio. `<i>` só para casar com o aria-hidden — a inclinação não faz
   parte do acordo, daí o font-style:normal. Vazio ele SOME, senão o `gap`
   de 8px da linha descolaria o mostrador da borda direita à toa. */
#voiceOut,#voice4Out,#voiceCont{
  font-size:13px;text-transform:none;letter-spacing:.01em;color:var(--ink);
}
#voiceOut,#voice4Out{margin-left:auto}
#voiceCont{font-style:normal}
#voiceCont:empty{display:none}
/* MUDANÇA DECLARADA, e não efeito colateral: a variante A+ existia SÓ no
   aulagame. Juntando os seletores, os dois mostradores da percepção
   (#voiceOut e #voice4Out) passam a crescer no A+ também — 13px → 16px,
   nos dois temas. Antes ficavam em 13px com o modo ligado, o que era um
   furo silencioso: o A+ existe para baixa visão, e a leitura ao vivo da
   voz é justamente o que o aluno fica olhando enquanto canta.
   Fica registrado porque a regra BASE era idêntica nas duas páginas e a
   VARIANTE não — mover regra faz elemento passar a casar outra regra, e o
   comparar-estilo.mjs não vê isso (docs/plano-componentizacao.md). */
body.hivis #voiceOut,body.hivis #voice4Out,body.hivis #voiceCont{font-size:16px}

/* O MESMO SELO SERVE A DUAS SUPERFÍCIES: a folha da pauta (aqui desde o
   v41) e a GRADE DE RESPOSTA do aulagame (.choices), que ganhou o selo na
   rodada da auditoria WCAG. Nas duas o problema é o mesmo — a opção certa
   e a que o aluno marcou não podem diferir só pela cor da borda (1.4.1).
   Elas moram juntas para o desenho do ✓ e do ✗ existir UMA vez: quando a
   grade tinha a própria cópia, eram duas máscaras iguais para manter em
   sincronia, e é assim que duas viram três. Muda só a COR (a folha tem
   paleta própria, de papel) e o TAMANHO (a grade é mais apertada). */
.pauta-opcoes button.right::before,
.pauta-opcoes button.wrong::before,
.choices button.right::before,
.choices button.wrong::before{
  content:"";position:absolute;top:3px;right:4px;width:15px;height:15px;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;
}
/* o botão da grade é o berço do selo; a folha já era `relative` */
.choices button{position:relative}
/* 13px e não 15: a caixa da grade tem 56px de altura e as três opções
   disputam a largura a 320px — o selo não pode roubar a palavra */
.choices button.right::before,
.choices button.wrong::before{width:13px;height:13px}
.pauta-opcoes button.right::before,
.choices button.right::before{
  -webkit-mask-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M3.4 8.4 L6.6 11.6 L12.6 4.8" fill="none" stroke="black" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>');
  mask-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M3.4 8.4 L6.6 11.6 L12.6 4.8" fill="none" stroke="black" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>');
}
.pauta-opcoes button.wrong::before,
.choices button.wrong::before{
  -webkit-mask-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M4.4 4.4 L11.6 11.6 M11.6 4.4 L4.4 11.6" fill="none" stroke="black" stroke-width="2.4" stroke-linecap="round"/></svg>');
  mask-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M4.4 4.4 L11.6 11.6 M11.6 4.4 L4.4 11.6" fill="none" stroke="black" stroke-width="2.4" stroke-linecap="round"/></svg>');
}
.pauta-opcoes button.right::before{background-color:var(--folha-ok)}
.pauta-opcoes button.wrong::before{background-color:var(--folha-erro)}
.choices button.right::before{background-color:var(--ok)}
.choices button.wrong::before{background-color:var(--amber)}
/* SÓ `.choices`, e NÃO `.ivgrid`: a ivgrid do aulagame é o TECLADO de
   notas do ditado (12 botões que chamam dictAdd), não uma grade de
   resposta — nenhum `marcarResposta` a alcança, e o veredito dela mora na
   fita #mline. Selo ali seria desenho para um estado que não acontece. */
body.hivis .choices button.right::before,
body.hivis .choices button.wrong::before{width:17px;height:17px}
body.hivis .pauta-opcoes button.right::before,
body.hivis .pauta-opcoes button.wrong::before{width:19px;height:19px;background-size:19px 19px}
body.hivis .pauta-box{border-width:2px}
body.hivis .pauta-opcoes button{border-width:2px}
body.hivis .pauta-opcoes .pauta-letra{color:var(--folha-tinta)}

.metro-box{
  display:flex;align-items:center;gap:10px;margin-top:12px;padding:10px 12px;
  border:1px solid var(--line);border-radius:16px;background:var(--well);
}
.metro-box .metro-rot{
  font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-faint);
}
.metro-box input[type="range"]{flex:1;min-width:0;accent-color:var(--phosphor)}
.metro-box .metro-val{font-size:14px;width:30px;text-align:right;color:var(--ink)}
.metro-bt{
  position:relative;flex:none;width:40px;height:40px;border-radius:50%;
  border:1px solid var(--line);background:var(--panel-2);color:var(--ink-dim);
  display:flex;align-items:center;justify-content:center;
}
.metro-bt svg{width:20px;height:20px;fill:currentColor}
.metro-bt[aria-pressed="true"]{color:var(--phosphor);border-color:rgba(var(--phos-rgb),.5)}
.metro-led{
  position:absolute;top:-2px;right:-2px;width:10px;height:10px;border-radius:50%;
  background:var(--line-2);transition:background .09s,box-shadow .09s;
}
.metro-led.on{background:var(--ok);box-shadow:0 0 10px rgba(var(--ok-rgb),.7)}
body.hivis .metro-box{border-width:2px}
body.hivis .metro-bt{width:48px;height:48px;border-width:2px}
body.hivis .metro-box .metro-val{font-size:17px;width:36px}

/* ================================================================
   A LINHA DE ESCUTA — "é a sua vez, e falta tanto"

   Subiu de aulagame.html na rodada da janela de escuta (19/08/2026),
   e está declarada em DONOS: esta folha é a dona única, e nenhuma
   página pode redeclarar estas classes.

   POR QUE SUBIU. O trabalho inteiro dessa rodada foi tirar cópias de
   circulação: uma função monta a linha para os três exercícios que
   pedem voz, um `inicioDaEscuta()` diz quando a janela abre, um
   `revelaNome` diz quem pode dizer o nome da nota. O desenho morando
   numa página e o comportamento em outra era a cópia que sobrava — e
   foi por uma divergência dessas (a fita velando o nome enquanto a
   barra o revelava) que esta rodada quase entregou a resposta do
   exercício de leitura.

   Só o aulagame a usa hoje. Isso não a torna local: o percepcao mede a
   mesma voz com as mesmas constantes, e quando ele ganhar a linha ela
   já estará aqui, com um dono, em vez de nascer como segunda cópia.
   ================================================================ */
/* ---------- A BARRA DA NOTA DA VEZ: "está gravando, e falta tanto" ----
   O PEDIDO É DO PROFESSOR DANILO ("uma barra vermelha que preenche
   mostrando que está gravando e quanto tempo falta em cada nota pode
   resolver") e a necessidade dele é medida: o app só decidia no fim de
   1,5 s de voz contínua e não dizia isso em lugar nenhum. Ele sustentava
   ~0,8 s, seguia para a nota seguinte, e a mediana da amostra — que
   emendava as DUAS notas — reprovava a primeira dizendo o nome da
   segunda. Sem relógio à vista não havia como descobrir a regra a não
   ser errando, e ele errou todos.

   A COR NÃO É VERMELHA, e a recusa é deliberada. Vermelho é a convenção
   universal de "REC", mas NESTE app vermelho e âmbar são ERRO: --ember e
   --amber são as vidas, o veredito de erro e a casa da nota errada nesta
   mesma fita. Uma barra vermelha correndo durante uma tentativa CERTA
   poria a cor do erro na tela exatamente no momento que este trabalho
   existe para desconfundir. O terra da marca (--phosphor) é o que o app
   já usa para ESTADO DE INTERFACE — foco, aba ativa, botão marcado — e,
   de mais perto, é o fim do gradiente do MEDIDOR DE NÍVEL logo acima:
   "o app está te ouvindo" já é desenhado em terra aqui. A barra herda um
   significado em vez de atropelar outro.

   MEDIDO NA TELA RENDERIZADA, não calculado no papel — preenchimento
   contra o trilho: 5,74:1 no tema escuro e 5,50:1 no claro; contra o
   fundo da tela: 5,81 e 6,13, e 5,09 no pior caso (escuro com A+). O
   piso da WCAG para objeto gráfico é 3:1, e o texto ao lado fica todo
   acima de 4,5 nos quatro estados (tema × A+).

   E A INFORMAÇÃO NÃO DEPENDE DA COR. Ao lado da barra corre o texto, que
   diz as três coisas por escrito: a posição ("2ª de 4"), o nome da nota
   ("Mi♭") e o relógio ("falta 0,9 s"). Quem não vê cor nenhuma continua
   sabendo o quê e por quanto tempo. */
/* A LINHA TEM PISO DE ALTURA, por razão gêmea da barra logo abaixo: o
   texto da ESPERA pode quebrar em duas linhas (A+, telas estreitas) e,
   sem piso, a `.gravlinha` só crescia NESSE instante — empurrando a
   pauta pra baixo e de volta, um pulo visível a cada troca de fase (o
   dono via "a pauta quebrando de linha" sem nenhuma linha ter mudado
   nela mesma). Medido no Chrome a 320px: uma linha ≈ 20,8px (23,2px em
   A+), duas linhas ≈ 35,7px (40,8px em A+) — o piso reserva o pior caso
   sempre, pra nunca sobrar salto. */
.gravlinha{display:flex;align-items:center;gap:8px;margin-top:5px;min-height:36px}
.gravlinha[hidden]{display:none}
/* A BARRA TEM PISO, e o piso é o conserto de um defeito medido: com o
   texto da ESPERA (mais longo que os outros, porque carrega o motivo da
   espera E o relógio) a linha inteira encolhia em cima da barra — ela é
   a única coisa aqui que sabe crescer, e por isso era a única que
   apagava. Media 2px a 320 e a 375, com e sem A+: virava um traço, e a
   fase que este trabalho existe para mostrar era justamente a que ficava
   sem barra.

   O PISO SOZINHO NÃO BASTA: se o texto não souber encolher, o que ele
   passar do piso vaza para fora da tela (medi 66,7px de vazamento e 45px
   de rolagem horizontal a 320 com A+). Por isso ele vem em par com o
   `flex:0 1 auto` + `min-width:0` da `.gravtxt` logo abaixo — o texto
   quebra em duas linhas antes de empurrar a barra ou a tela. Nada é
   truncado: elipse aqui esconderia o relógio, que é a informação. */
.gravbar{
  flex:1 1 auto;min-width:72px;height:6px;border-radius:999px;
  background:var(--well);border:1px solid var(--line-2);overflow:hidden;
}
.gravbar i{display:block;height:100%;width:0;border-radius:999px;background:var(--phosphor)}
@media (prefers-reduced-motion:no-preference){
  .gravbar i{transition:width .1s linear}
}
/* AQUI MORAVAM OS TRÊS BLOCOS do Solfejo (`.gravbar.blocos`, 21/08/2026):
   três degraus grandes desenhados por cima desta barra, para aquele motor
   só. Saíram em 27/08 — a sustentação do Solfejo passou a ser contada
   pelo HALO em volta da cabeça da nota, e a `.gravbar` inteira fica
   escondida ali pela regra `.gravlinha.halo` logo abaixo. O que resta é a
   barra fina de sempre, UMA só, para sing e arpejo.
   ---------- A BARRA SAI QUANDO O HALO ENTRA (27/08/2026) ----------
   Manter a barra aqui embaixo seria o segundo relógio da mesma contagem,
   e o primeiro a discordar num quadro perdido teria razão sozinho. O que
   FICA da linha é o TEXTO — a fase por escrito, a posição na cadeia, o
   desvio e a sílaba ouvida —, que é exatamente o que o protótipo mostra
   ao lado da pauta e o que sustenta quem não vê o desenho.
   A `.gravmarca` continua: ela é a pista de fase que não depende de cor
   nem de forma, e tirá-la junto seria perder um canal de graça. */
.gravlinha.halo .gravbar{display:none}
/* com a barra fora, o texto herda a linha inteira — e é isso que faz
   caber o desvio ("+12¢") sem estourar os 320px do piso medido */
.gravlinha.halo .gravtxt{flex:1 1 auto}
/* .gravcents SAIU (rodada "tela limpa", 05/09/2026): o desvio ao vivo
   não aparece mais no rodapé — só o veredito mostra o número, pequeno. */
/* MOVIMENTO REDUZIDO NÃO PODE VIRAR AUSÊNCIA DE INFORMAÇÃO — a regra da
   casa (é a mesma do contador de XP e da barra do avanço automático).
   Aqui a barra deixa de deslizar: o preenchimento anda em CINCO degraus
   de 20% e o número passa a contar de meio em meio segundo, os dois
   quantizados no JS, que é quem sabe do relógio. Perde-se a animação,
   nunca o relógio. Medido no navegador, sustentando uma nota inteira:
   0 → 20 → 40 → 60 → 80 → 100%, com o texto em 1,5 → 1,0 → 0,5 s — e a
   nota aceita no fim, como no movimento normal.

   A CLASSE É IRMÃ DA MEDIA QUERY, e de propósito. A media query é o
   lugar certo da regra; a classe existe porque quem PINTA a barra já
   consultou a preferência (é ele que quantiza os degraus) e porque
   preferência que só vive em CSS não se mede no navegador — e barra é
   justamente o tipo de coisa em que "verifiquei lendo o código" não
   prova nada. Com a classe, o degrau e a ausência de deslizamento saem
   no mesmo getComputedStyle. */
.gravlinha.sem-anim .gravbar i{transition:none}
@media (prefers-reduced-motion:reduce){
  .gravbar i{transition:none}
}
/* `0 1 auto` e não `0 0 auto`: o texto pede o tamanho natural, mas ACEITA
   encolher quando não cabe — e com `min-width:0` ele pode encolher abaixo
   do que a maior palavra pediria, que é o que o autoriza a quebrar linha
   em vez de estourar a caixa. É esta linha que faz o piso da barra valer
   sem custar rolagem horizontal. */
.gravtxt{
  flex:0 1 auto;min-width:0;font-size:11px;line-height:1.35;letter-spacing:.02em;
  color:var(--ink-dim);font-variant-numeric:tabular-nums;
}
/* o relógio nunca quebra no meio: "0,9" numa linha e "s" na outra seria
   pior do que a linha a mais que a quebra evita. E o NEGRITO CRESCE
   sozinho (21/08/2026): a queixa era a palavra de estado
   ("cantando…"/"segure!") pequena e apagada demais para o papel que ela
   passou a ter — mas a linha inteira (a posição "1ª de 3 —") já mede o
   piso de 320px com A+ na seção 8 de teste-escuta.mjs, e crescer tudo
   arriscaria esse orçamento. O negrito é sempre a palavra que importa
   nesta linha — o resto é conector. */
.gravtxt b{color:var(--ink);font-weight:700;font-size:13px;white-space:nowrap}
body.hivis .gravbar{height:9px}
body.hivis .gravtxt{font-size:13px;color:var(--ink)}
body.hivis .gravtxt b{font-size:15px}
body.hivis .gravlinha{min-height:41px}

/* ---------- A MARCA DA FASE: o sinal que NÃO é cor ----------
   Queixa do dono (19/08/2026): "o aluno não sabe que começou a gravação
   e não canta; só depois o app fala que errou". A barra respondia
   "quanto falta" mas nunca "AGORA", e quem não sabe que a janela abriu
   fica esperando um convite que a tela não dava.

   A MARCA É UM GLIFO, e é ela que carrega a fase para quem não vê cor
   nenhuma (WCAG 1.4.1, a mesma regra que acabou de entrar nas grades de
   resposta): `·` esperando, `▶` a vez de cantar, `●` gravando. São os
   MESMOS glifos da fita soletrada (`▶` é a nota da vez em pintarCadeia),
   e não símbolos novos: o aluno já aprendeu essa língua uma tela antes.
   O texto ao lado diz a mesma coisa por extenso — a marca é a terceira
   pista, nunca a única. */
.gravmarca{
  flex:0 0 auto;width:1em;text-align:center;
  font-size:11px;line-height:1;color:var(--ink-faint);
}
.gravlinha.vez .gravmarca,.gravlinha.gravando .gravmarca{color:var(--phosphor)}
body.hivis .gravmarca{font-size:13px;color:var(--ink)}

/* O INSTANTE DA ABERTURA PISCA UMA VEZ — e é UMA vez mesmo, no padrão do
   `.avancou` da fita logo acima: pulso infinito durante um exercício de
   concentração é justamente o que esta tela evita desde que o veredito
   ganhou lugar fixo. Quem pediu movimento reduzido não perde nada: o
   glifo `▶` e o texto "SUA VEZ" já estavam lá antes do pisca e continuam
   depois dele — a animação é o reforço, nunca o recado. */
@media (prefers-reduced-motion:no-preference){
  .gravlinha.abriu .gravbar{animation:escutaAbriu .7s ease-out}
}
@keyframes escutaAbriu{
  0%{border-color:var(--phosphor);background:rgba(var(--phos-rgb),.18)}
  100%{border-color:var(--line-2);background:var(--well)}
}
/* irmã da media query, pelo mesmo motivo da `.sem-anim` logo acima: a
   preferência tem de se MEDIR no navegador, e regra que só vive em CSS
   não sai no getComputedStyle de quem pinta. */
.gravlinha.sem-anim .gravbar{animation:none}

/* ---------- CONFERINDO A SÍLABA (só o solfejo) ----------
   A altura fecha rápido; a fala do reconhecedor chega até 1,2s depois
   (ver `esperaSilaba` em aulagame.html). Sem sinal nenhum aqui, essa
   espera lia como a tela tendo voltado ao início — a barra cheia, mas
   pulsando, é o "ainda estou aqui" que falta. O PULSO, não um relógio
   novo: `ESPERA_SILABA_MS` é curto e fixo, e prometer um número que
   ninguém tem tempo de ler seria pior que não prometer nada. */
.gravlinha.conferindo .gravbar i{background:var(--ink-dim)}
@media (prefers-reduced-motion:no-preference){
  /* 0,6s e não 1s (achado 21/08/2026, ver aulagame.html): a espera dura
     até 1,2s — num ciclo de 1s o aluno via o pulso descer UMA vez só, e
     um fade lento e único lê como travado, não como vivo. Em 0,6s cabem
     dois pulsos inteiros dentro do pior caso, e o contraste (1 → 0,25,
     não 1 → 0,4) é o que faz o "ainda estou aqui" ser visto de relance. */
  .gravlinha.conferindo .gravbar i{animation:conferindoPulso .6s ease-in-out infinite}
}
@keyframes conferindoPulso{
  0%,100%{opacity:1}
  50%{opacity:.25}
}
/* o TOGGLE de movimento reduzido do próprio app (`.sem-anim`) tem de
   parar o pulso também, e não só o `@media` do sistema — é o mesmo
   motivo do `.sem-anim .gravbar i{transition:none}` logo acima: a
   preferência lida em JS não se mede num media query sozinho. Linha
   PRÓPRIA, e não somada à de cima: aquela é vigiada por teste-arpejo.mjs
   ao pé da letra (a media query "irmã da classe"), e mexer nela quebraria
   uma prova que não é sobre este pulso. */
.gravlinha.conferindo.sem-anim .gravbar i{animation:none}
/* O PULSO DO SOLFEJO MUDOU DE ENDEREÇO (21/08 → 27/08/2026). O pedido do
   dono — "quando estiver conferindo a sílaba as barrinhas podem pulsar em
   verde" — continua de pé; o que mudou é ONDE. Era `.gravbar.blocos
   .gravbloco.on`, aqui no rodapé; passou ao halo em 27/08 e desde
   04/09/2026 é `.pauta-fita.conferindo .pauta-fita-voz`, lá em cima na
   pauta (regra junto do sensor, mais acima neste arquivo), com o MESMO
   keyframe. As regras dos blocos saíram junto com eles.
   A do `.sem-anim` também não tem par aqui: o sensor mora na pauta, fora
   do alcance da `.gravlinha`, e quem corta o pulso dele é o JS — que já
   lê a preferência e simplesmente não pede a classe. */
/* O RECADO DO MEIO DA CADEIA ocupa o MESMO lugar do lembrete do
   microfone, em vez de somar uma linha: a tela do arpejo já nasceu no
   limite dos 320×568 e uma linha a mais aqui a faria rolar. Quando há
   recado, o lembrete se esconde — nunca os dois juntos. */
.arpstatus{margin-top:4px}
.arpstatus:empty{display:none}
/* ---------- O PÉ DA ESCUTA (04/09/2026, rodada de ajustes do híbrido) ----------
   A linha de escuta desceu para BAIXO do papel e virou o rodapé do
   protótipo aprovado: os TRÊS PONTOS da sustentação, o texto ao lado
   ("1,0 de 1,5 s" / "o ponto feito fica") e o desvio pequeno à direita;
   acima deles, a FRASE DE DIREÇÃO ("▲ Um pouco grave. Sobe um tiquinho." /
   "Aí. Segura assim."). A barra fina (`.gravbar`) continua no markup e
   escondida por `.gravlinha.halo` — o relógio é os pontos.

   OS PONTOS DAQUI SÃO OS MESMOS DA PAUTA: o JS lê `feitos` do pintor do
   sensor e só escreve `data-feitos` aqui — nenhuma segunda contagem. Dois
   desenhos do mesmo número, como a bolinha e a cabeça fantasma.

   ESTAS REGRAS FICAM DEPOIS DE `.arpstatus` DE PROPÓSITO: a fatia
   `.gravlinha{` → `.arpstatus{` é a que os testes vasculham por cor de
   erro na barra (âmbar/vermelho nunca correm numa tentativa certa). A
   frase de direção usa o âmbar do app, mas só quando a voz está FORA, e
   por isso mora aqui, fora daquela fatia. */
.gravlinha{flex-wrap:wrap}
/* O PÉ DA ESCUTA (`.gravpe`) É UMA LINHA PRÓPRIA SOB A PAUTA, com os TRÊS
   PONTOS da sustentação e a frase de direção (a setinha) lado a lado.
   Pedido do dono (17/09/2026): "as 3 bolinhas e a setinha de acerto devem
   ficar ABAIXO da pauta". Os pontos tinham saído daqui em 05/09 (rodada
   "tela limpa") por duplicarem os que a pauta desenhava sobre a nota da
   vez; agora é o contrário — o papel só conta (`.pauta-pontos`), e quem
   mostra é este pé. Cada ponto é um terço da janela PASSADO NO VERDE:
   verde aqui é "tempo conquistado", não veredito — e ponto feito fica.
   A classe `feito` é posta pelo JS no próprio <i> (a WebView do app não
   tem `:has()`: quem monta o filho põe classe). */
.gravpe{flex:1 1 100%;display:flex;align-items:center;gap:10px;min-width:0}
.gravpe[hidden]{display:none}
.gravpontos{display:inline-flex;gap:5px;flex:0 0 auto}
.gravpontos[hidden]{display:none}
.gravpontos i{display:block;width:11px;height:11px;border-radius:50%;box-sizing:border-box;border:1.5px solid var(--ink-dim);background:transparent}
.gravpontos i.feito{background:var(--ok);border-color:var(--ok)}
body.hivis .gravpontos i{width:13px;height:13px;border-width:2px}
.gravdir{flex:1 1 auto;min-width:0;font-size:15px;font-weight:600;line-height:1.3;color:var(--ink)}
.gravdir:empty{display:none}
.gravdir .vale{color:var(--ok);font-weight:700}
.gravdir .ajuste{color:var(--amber);font-weight:700}
/* a setinha da frase é um triângulo de CSS, o mesmo desenho da seta na pauta */
.gravdir .seta{display:inline-block;width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;vertical-align:middle;margin:0 4px 2px 0}
.gravdir .seta.sobe{border-bottom:9px solid var(--amber)}
.gravdir .seta.desce{border-top:9px solid var(--amber)}
body.hivis .gravdir{font-size:17px}

/* AQUI MORAVA O `#capNota` (21/08 → 27/08/2026), o círculo da captura:
   um anel de 32px no rodapé, com o nome da nota dentro, que desenhava os
   400ms da `esperaCaptura`. Saiu porque a sustentação inteira virou um
   anel EM CIMA da nota (o halo de 27/08; desde 04/09/2026 são os três
   pontos `.pauta-ponto`, mais acima neste arquivo) — e aquela espera
   passou a ser mostrada pelo sensor parado, com "capturando…" ao lado. Dois círculos para a mesma nota na mesma tela era a dívida que
   o próprio #capNota tinha sido criado para pagar, quando quem fazia esse
   papel era o `#nivel` reaproveitado. O perímetro cravado que ele
   declarava aqui (81,68, o par do `CAPNOTA_C` no aulagame) saiu junto: o
   halo publica o dele no `data-c` do elemento, com um dono só. */

/* ---------- O SABIÁ ASSISTINDO A LIÇÃO ----------
   ELE NÃO CUSTA ALTURA. A lição é a tela mais apertada do app (320×568
   com o cromo já cortado, e há teste cobrando a conta): qualquer coisa que
   entre no fluxo empurra o exercício para fora. Ele é `absolute` no canto,
   `pointer-events:none`, e some por completo em janela baixa — onde não
   sobra espaço, quem sai é o enfeite, nunca o exercício.

   OS ESTADOS SÃO OS DO ESBOÇO, com o mesmo vocabulário que já estava
   fechado: atento (padrão), tocando (acertou), confere (errou), dormindo
   (o aluno sumiu). */
/* `fixed` E NÃO `absolute`. Preso à seção da lição, ele ficava no rodapé
   dela — e a seção é mais alta que a janela, então o bicho pousava abaixo
   da dobra e só a cabeça aparecia por trás da tab bar. Foi o que o dono
   viu. Preso à JANELA, ele fica sempre no mesmo canto, logo acima da barra
   de navegação, que é o único pedaço de tela que a lição nunca usa. */
/* FIXO NO CANTO DE BAIXO, que é o único pedaço da tela da lição sem
   controle nenhum: o X fica em cima, as respostas no meio e o continuar
   embaixo ao centro. Ele recebe toque (`pointer-events:auto`) só para
   responder com uma pose — é a única coisa desta tela que não é resposta e
   aceita toque, e é por isso que ela é pequena e encostada no canto.
   À DIREITA desde 19/09/2026: é por ali que o ramo da trilha entra na
   lição (ver A LIÇÃO DENTRO DO RAMO, no fim deste arquivo), e o Sabiá
   passa a ficar EM PÉ nesse galho, virado para o exercício — o desenho é
   espelhado no `.risco-vivo` só nesta tela (`body.em-licao`), para o
   clone do voo e a cena do fim continuarem como sempre foram. */
.sabia-licao{
  /* mesmo piso da barra flutuante, e pelo mesmo motivo: o
     `env(safe-area-inset-bottom)` volta zero em Android com barra de
     gestos, e sem piso o Sabia encosta na beira */
  position:fixed;right:8px;bottom:max(54px, calc(var(--tabbar-h) + 22px));
  width:76px;height:84px;z-index:12;
  pointer-events:auto;cursor:pointer;
  opacity:.96;
}
.sabia-licao:active{transform:scale(.94)}
body.em-licao .sabia-licao .risco-vivo{transform:translate(150px,0) scaleX(-1)}   /* 150 = largura do viewBox */
body.hivis .sabia-licao{width:92px;height:101px}
/* ---------- O SABIÁ DA LIÇÃO É O MAIS VIVO (pedido do dono) ----------
   Ele passa a lição inteira ao lado do aluno, então é ali que o
   movimento vale a pena: respira mais fundo, mexe a asa mais vezes e
   olha em volta de tempos em tempos. Enquanto TOCA, a escaleta balança
   no tempo — é o único momento em que ele está fazendo algo, e é isso
   que o movimento diz. Nada disto vale para o pássaro-guia da trilha,
   que é pequeno e está de fundo. */
@media (prefers-reduced-motion:no-preference){
  .sabia-licao .sb-corpo{animation:sbBob 2.1s ease-in-out infinite}
  .sabia-licao .sb-asa{animation:sbAsa 2.6s ease-in-out infinite}
  .sabia-licao .sb-cabeca{animation:sbOlhada 6.5s ease-in-out infinite}
  @keyframes sbOlhada{0%,74%,100%{transform:none}80%{transform:rotate(-5deg)}88%{transform:rotate(3deg)}}
  /* `assobia` é a mesma olhada, só que a partir da cabeça ERGUIDA (-8°):
     a animação vence o `transform` parado, então o ângulo do estado tem
     de morar dentro do keyframe */
  .sabia-licao.assobia .sb-cabeca{animation:sbAssobia 6.5s ease-in-out infinite}
  @keyframes sbAssobia{0%,74%,100%{transform:rotate(-8deg)}80%{transform:rotate(-13deg)}88%{transform:rotate(-4deg)}}
  .sabia-licao.tocando .sb-escaleta{animation:sbToca .46s ease-in-out infinite alternate}
  @keyframes sbToca{from{transform:none}to{transform:translateY(-2px) rotate(-1.2deg)}}
  /* tocando, a cabeça acompanha o balanço da escaleta, no mesmo compasso */
  .sabia-licao.tocando .sb-cabeca{animation:sbTocaCab .46s ease-in-out infinite alternate}
  @keyframes sbTocaCab{from{transform:rotate(9deg)}to{transform:rotate(12deg)}}
  .sabia-licao.confere .sb-corpo{animation:sbBob 1.2s ease-in-out infinite}
}
@media (max-height:640px){ .sabia-licao{display:none} }
.sabia .sb-corpo{transform-origin:70px 100px;animation:sbBob 2.6s ease-in-out infinite}
.sabia .sb-olho{transform-origin:112px 40px;animation:sbPisca 4.2s ease-in-out infinite}
.sabia .sb-asa{transform-origin:52px 70px;animation:sbAsa 3.4s ease-in-out infinite}
/* O PIVÔ É O PESCOÇO (104,66), e não o centro da cabeça: é o único ponto
   em volta do qual ela gira sem descolar do corpo — o corte do ombro fica
   escondido até 18° para cada lado. Cada estado é um ângulo daqui. */
.sabia .sb-cabeca{transform-origin:104px 66px}
/* ---------- SEU TREINO: A SEMANA EM BARRAS (09/09/2026) ----------
   Sete colunas de altura proporcional ao maior dia. Mora aqui, e nao no
   <style> do index, pelo mesmo motivo do resto desta rodada: o orcamento
   de estilo das paginas so desce. */
.treino{margin-top:14px;width:100%}
.treino h3{
  margin:0 0 8px;font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-dim);font-weight:700;
}
.treino-grade{
  display:grid;grid-template-columns:repeat(7,1fr);gap:6px;
  align-items:end;height:72px;
}
.treino-col{display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:5px;height:100%}
.treino-col i{
  display:block;width:100%;max-width:26px;border-radius:5px;
  background:var(--phosphor);opacity:.75;
}
/* dia sem treino continua com um traco: sumir a barra faria o buraco na
   semana parecer coluna que nao existe, e o buraco E a informacao */
.treino-col i.vazio{background:var(--ink-faint);opacity:.35}
.treino-col.hoje i{opacity:1}
.treino-col span{font-size:10px;color:var(--ink-faint);line-height:1}
.treino-col.hoje span{color:var(--ink-dim);font-weight:700}
.treino-hoje{margin:8px 0 0;font-size:12px;color:var(--ink-dim)}
body.hivis .treino-col span{font-size:12px}
body.hivis .treino-hoje{font-size:14px}
/* AS BARRAS DO MEU TREINO SAO BOTOES (13/09/2026, "quando clica"): a coluna
   inteira e o alvo (72px de altura, mais que os 44 do toque), o botao nasce
   sem casca (o desenho e o da barra), e o dia aberto fica marcado por
   aria-pressed — classe posta por quem monta, nunca :has() (WebView do app) */
.treino-btn{appearance:none;-webkit-appearance:none;border:0;background:none;padding:0;margin:0;min-height:44px;cursor:pointer;color:inherit;font:inherit;border-radius:8px}
.treino-btn:focus-visible{outline:2px solid var(--phosphor);outline-offset:2px}
.treino-btn[aria-pressed="true"] i{outline:2px solid var(--phosphor);outline-offset:2px;opacity:1}
/* o detalhe do dia, dentro do dialogo: uma linha por tentativa — licao em
   negrito, subtitulo miudo, e a meta (estrelas, tempo, erros, hora) */
.msk-dia-resumo{font-weight:700}
.msk-dia{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px}
.msk-dia li{display:flex;flex-direction:column;gap:2px;padding:8px 10px;border-radius:9px;background:var(--well)}
.msk-dia b{font-size:14px}
.msk-dia .lic-sub{font-size:12px;color:var(--ink-dim)}
.msk-dia-meta{font-size:12px;color:var(--ink-dim)}
.msk-dia-meta em{font-style:normal;color:var(--gold);letter-spacing:.06em}

/* o cartao de um colega: o Sabia dele grande, o nivel ao lado e os numeros
   que o mural ja mostrava, agora com rosto */
.msk-colega{display:flex;align-items:center;gap:14px;margin:6px 0 10px}
.msk-colega b{display:block;font-size:16px}
.msk-colega small{display:block;font-size:12px;color:var(--ink-dim)}
.msk-colega-nums{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}
.msk-colega-nums li{
  display:flex;justify-content:space-between;gap:12px;
  padding:7px 10px;border-radius:9px;background:var(--well);
}
.msk-colega-nums span{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-dim)}
.msk-colega-nums b{font-size:14px}
/* o perfil completo do colega (12/09/2026): o MESMO .me-stats e .treino do
   "Meu perfil", so que dentro do dialogo — a coluna e o respiro sao daqui,
   as caixinhas e as barras continuam com o dono de sempre */
.msk-colega-perfil{display:flex;flex-direction:column;align-items:stretch;margin:0 0 12px}
.msk-colega-perfil .treino{margin-top:12px}
/* o colega logo a frente, no bloco do lugar: botao irmao do link, com o
   Sabia dele — mesma lingua do .oquem (borda, foco visivel, nada de <a>) */
.me-lugar-colega{
  display:inline-flex;align-items:center;gap:8px;margin-top:8px;padding:5px 12px 5px 6px;
  border-radius:999px;border:1px solid var(--line);background:var(--well);
  color:var(--ink);font:inherit;font-size:12.5px;cursor:pointer;
}
.me-lugar-colega:hover{border-color:var(--line-2);background:var(--hover-fill)}
.me-lugar-colega:focus-visible{outline:2px solid var(--phosphor);outline-offset:2px}
body.hivis .me-lugar-colega{border-width:2px;font-size:14px}
/* o nome do colega no mural vira BOTAO — mesmo desenho de antes, so que
   alcancavel pelo teclado e anunciado como porta */
.oquem{
  padding:0;border:0;background:none;text-align:left;font:inherit;
  font-weight:700;color:inherit;cursor:pointer;
}
.oquem:hover,.oquem:focus-visible{color:var(--phosphor)}

/* ---------- O TOPO DA LICAO, EM UM BLOCO SO (09/09/2026) ----------
   Duas fileiras: X + titulo + foguinho + coracoes; barra de ponta a ponta
   embaixo. O `flex-wrap` e a barra com `flex:1 0 100%` sao o mecanismo —
   nada de segundo container, para o <h2> continuar sendo o titulo da tela
   e o `intro-aberta` continuar reconhecendo `.lesson-top`. */
.msk-tela .lesson-top{flex-wrap:wrap;gap:8px 10px;padding-bottom:8px}
/* O X ERA DE 44px, do tamanho de um botao de acao, para uma coisa que se
   toca uma vez por licao — e roubava a largura do titulo. Vira 32, que e o
   desenho; o ALVO continua com 44 pelo ::after invisivel, que e a regra de
   toque que este app segue em toda parte. */
.msk-tela .lesson-top .iconbtn{
  position:relative;width:32px;height:32px;font-size:16px;border-radius:9px;
}
.msk-tela .lesson-top .iconbtn::after{content:"";position:absolute;inset:-6px}
/* o titulo toma a largura que sobra e, desde 17/09/2026, QUEBRA linha em
   vez de cortar com reticencias: o dono viu "3 · Dominantes · Cadencia
   invertida" cortado no celular e nao dava para ler. O `min-width:0` e o
   que impede o nome de empurrar os coracoes para fora — a caixa encolhe e o
   texto quebra dentro dela. O espacamento entre letras cai de .16em para
   .06em SO AQUI — o titulo deixou de ser uma faixa propria e passou a
   disputar a linha com os coracoes; cada centesimo de em era uma letra a
   menos do nome. */
.msk-tela .lesson-top .ltitle{
  flex:1;min-width:0;padding:0;margin:0;letter-spacing:.06em;line-height:1.2;
  overflow-wrap:anywhere;
}
/* O NOME DA UNIDADE, na linha de cima do titulo (correcao do dono,
   17/09/2026): um <small> em bloco dentro do <h2>, menor e mais apagado —
   diz ONDE o aluno esta sem disputar com o nome da licao. Classe no proprio
   filho, sem :has() — a WebView do app da loja nao tem :has(). */
.ltitle-unidade{
  display:block;font-size:10px;font-weight:600;letter-spacing:.12em;
  color:var(--ink-dim);margin-bottom:1px;
}
body.hivis .ltitle-unidade{font-size:12px}
/* a regua colorida da unidade sai daqui: numa linha de 32px ela encostava
   no texto. Quem carrega a cor da unidade nesta tela passa a ser a barra de
   progresso, logo abaixo — mesma informacao, sem apertar nada. */
.msk-tela .lesson-top .ltitle.unidade::after{display:none}
.msk-tela .lesson-top .progress{flex:1 0 100%}
.msk-tela .lesson-top .progress #progFill,
.msk-tela .lesson-top .progress > div{
  background:linear-gradient(90deg,
    hsl(var(--uh,210) var(--hue-s) var(--hue-l) / .45),
    hsl(var(--uh,210) var(--hue-s) var(--hue-l)));
  box-shadow:none;
}

/* O FOGUINHO DA OFENSIVA NO TOPO DA LICAO (pedido do dono, 09/09/2026).
   Chip discreto ao lado das vidas: quem esta no meio do exercicio nao pode
   ter mais um numero disputando a atencao com o som — ele existe para ser
   visto de canto de olho, entre uma questao e outra. Mora aqui, e nao no
   <style> do aulagame, porque la o orcamento de estilo so desce. */
.lesson-fogo{
  flex:none;display:inline-flex;align-items:center;gap:3px;
  padding:4px 9px;border-radius:999px;
  border:1px solid rgba(var(--ember-rgb),.4);color:var(--ember);
  font-size:12px;font-weight:800;line-height:1;
}
.lesson-fogo[hidden]{display:none}
.lesson-fogo .ico-flame{
  width:14px;height:14px;fill:none;stroke:currentColor;
  stroke-width:2;stroke-linecap:round;stroke-linejoin:round;
}
body.hivis .lesson-fogo{font-size:14px}
body.hivis .lesson-fogo .ico-flame{width:17px;height:17px}

/* ---------- A TELA DA LICAO: O ESTIMULO E O HEROI (09/09/2026) ----------
   O dono comparou o app com os concorrentes da Play e apontou o mesmo
   defeito em todos os prints: "hoje tem muito texto no nosso app; para quem
   ja e aluno, o Sabia basta". O enunciado era uma CAIXA — borda, fundo e
   18px — ocupando o lugar mais nobre da tela, e o exercicio de verdade
   cabia num botao de 48px embaixo dela.

   O enunciado nao sumiu, e nao podia: quem chega na segunda semana ainda o
   le. Virou LINHA — mesmo texto, dito baixinho — e quem quer a explicacao
   longa toca no "?". O lugar que ele desocupou e do OUVIR, que e a acao
   principal de quase toda licao daqui.

   O enunciado em si continua no <style> do aulagame: `.prompt` tambem e da
   percepcao, e trazer o nome para ca obrigaria a mover as regras dela na
   mesma tacada — faxina de outro dia. O que sobe aqui e so o que NASCEU
   nesta rodada: a linha do estimulo. */
/* OUVIR TEM PESO 3, e nao largura total. A primeira versao usava
   `flex:1 1 100%` — e no celular do Danilo isso EMPURROU o "dar o do" e o
   microfone para fora da tela (dois prints, 10/09/2026). A causa esta a
   poucas linhas daqui: `.row` e `flex-wrap:nowrap` por decisao da reforma
   anterior, entao `basis:100%` nao quebra linha; ele espreme os irmaos
   contra a borda.

   Peso resolve sem brigar com aquela decisao: o OUVIR fica ~3x maior que
   os vizinhos, continua sendo o heroi da linha, e a fileira segue cabendo
   inteira com um, dois ou tres botoes. */
.row.estimulo #btnAgain{flex:3 1 0;min-height:60px}
.row.estimulo #btnAgain svg{width:28px;height:28px}
/* Na tela alta o vao entre o estimulo e a resposta era espaco MORTO: a
   resposta desce para o polegar (`#answerArea{margin-top:auto}`, no
   aulagame) e no meio nao sobrava nada. Agora e o estimulo que cresce e se
   centra nesse vao. Mesma condicao de altura da regra de la. */
@media (min-height:700px){
  #licao > .row.estimulo{flex:1;align-items:center;min-height:0}
  #licao > .row.estimulo #btnAgain{min-height:72px}
}
.sabia .sb-notas,.sabia .sb-tubo,.sabia .sb-zzz{opacity:0}
.sabia .sb-dorme{display:none}
/* ---------- OS NÍVEIS DO SABIÁ (pedido do dono, 08/09/2026) ----------
   O desenho aprovado não é refeito: cada nível ACRESCENTA uma peça, e
   ela nasce escondida. Quem manda é `vestirSabia`, que acumula as
   classes — nv5 traz nv2, nv3 e nv4 junto, porque medalha sem gravata
   seria um pássaro que desaprendeu. */
.sabia .sb-gravata,.sabia .sb-oculos,.sabia .sb-chapeu,.sabia .sb-medalha{opacity:0}
/* o avatar do "Meu perfil" é o mesmo pássaro recortado na cabeça: a cor de
   fundo continua sendo a do nome (pintaAvatar), e é ela que distingue um
   aluno do outro quando dois estiverem no mesmo nível */
/* a escada de níveis: uma linha por degrau, o pássaro daquele degrau à
   esquerda. O de agora ganha moldura — e o texto diz "você está aqui"
   junto, porque cor e borda sozinhas não contam nada a quem não vê. */
.msk-niveis{list-style:none;margin:8px 0 0;padding:0;display:flex;flex-direction:column;gap:6px}
.msk-niveis li{display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:10px}
.msk-niveis li.agora{background:var(--surface-2,rgba(127,127,127,.12));outline:2px solid var(--phosphor)}
.msk-niveis b{display:block;font-size:14px}
.msk-niveis small{display:block;font-size:12px;color:var(--ink-dim)}
/* a caixinha do nível no mural é um BOTÃO: o dono pediu que dê para tocar
   nela e ver a evolução. Botão de verdade, para o teclado e o leitor de
   tela também chegarem — o visual continua sendo o da caixinha. */
.me-stat-btn{cursor:pointer;text-align:left;font:inherit;color:inherit}
.me-stat-btn:hover,.me-stat-btn:focus-visible{border-color:var(--phosphor)}
.msk-av-sabia{overflow:hidden;padding:0}
.msk-av-sabia svg{width:118%;height:118%;display:block;margin:-9% 0 0 -9%}
.sabia.nv2 .sb-gravata,.sabia.nv3 .sb-oculos,.sabia.nv4 .sb-chapeu,.sabia.nv5 .sb-medalha{opacity:1}
.sabia.tocando .sb-tubo{opacity:1}
.sabia.tocando .sb-asa{animation:sbDedilha .5s ease-in-out infinite}
/* NOTA SÓ SAI COM A MANGUEIRA NA BOCA. As duas coisas andavam separadas e
   o resultado era incoerente nos dois lados: na árvore ele estava com a
   mangueira e mudo, e na lição soltava notinha sem mangueira nenhuma. Agora
   `tocando` liga as duas e é o único estado que solta som; `assobia` é o
   bicho parado, pensando junto com o aluno. */
.sabia.tocando .sb-notas{opacity:1}
.sabia.tocando .sb-notas .n{animation:sbSobe 1.6s linear infinite}
/* O BICO NÃO PULSA MAIS NO ESTADO PARADO. `assobia` virou o padrão da
   lição — o bicho esperando o aluno responder —, e um bico que incha e
   desincha o tempo todo não é "parado": pior, a escala saía do CENTRO do
   próprio bico, então cada pulso o afastava da cabeça e abria uma fresta.
   Com o traço sujo por cima, a fresta lia como bico solto. Foi o que o
   dono viu.
   O pulso fica guardado para quando houver um estado de assobio de
   verdade; a origem agora é a BASE dele (a borda da cabeça), que é o único
   ponto a partir do qual crescer não descola nada. */
.sabia .sb-bico{transform-origin:124px 47px}
/* OS ESTADOS SÃO ÂNGULOS DA CABEÇA em volta do pescoço (o desenho
   articulado de 10/09/2026): assobia ergue (-8°), tocando baixa para o
   teclado (+10°, é o "tocando" do esboço A4 — e vale para o guia da
   trilha, que é `tocando` fixo), confere inclina (+12°, com a balançada
   de quem repara), ouvindo debruça (+8°). Quem pede menos movimento
   recebe o mesmo ângulo, parado. */
.sabia.assobia .sb-cabeca{transform:rotate(-8deg)}
.sabia.tocando .sb-cabeca{transform:rotate(10deg)}
.sabia.confere .sb-cabeca{transform:rotate(12deg);animation:sbConfere 1.4s ease-in-out infinite}
/* DORMINDO NÃO É ÂNGULO, É OUTRA POSTURA: a cabeça vai para debaixo da
   asa e o que sobra é "uma bola com rabo" (A2 do esboço, correção do
   Danilo). O acordado inteiro (`.sb-corpo`) sai e a bola (`.sb-dorme`)
   entra, respirando devagar; os zês sobem de onde a cabeça se escondeu. */
.sabia.dormindo .sb-corpo{display:none}
.sabia.dormindo .sb-dorme{display:block;animation:sbBob 4.6s ease-in-out infinite}
.sabia.dormindo .sb-zzz{opacity:1}
.sabia.dormindo .sb-zzz .z{animation:sbZ 3.2s linear infinite}
/* `ouvindo` — sugestão do Danilo: "conferir na escaleta, fazer algum
   movimento". A mangueira aparece (como em `tocando`), mas SEM as
   notas subindo — nada foi confirmado ainda, soltar nota aqui
   prometeria acerto antes da hora. O movimento é a cabeça debruçando
   sobre a escaleta, mais lento que o `sbConfere` do erro (este não é
   um erro) e sem o giro largo dele. */
.sabia.ouvindo .sb-tubo{opacity:1}
.sabia.ouvindo .sb-cabeca{transform:rotate(8deg);animation:sbOuvindo 1s ease-in-out infinite}
@keyframes sbBob{0%,100%{transform:translateY(0)}50%{transform:translateY(-4px)}}
@keyframes sbPisca{0%,93%,100%{transform:scaleY(1)}96%{transform:scaleY(.08)}}
@keyframes sbAsa{0%,100%{transform:rotate(0)}50%{transform:rotate(-7deg)}}
@keyframes sbDedilha{0%,100%{transform:rotate(-2deg) translateY(0)}50%{transform:rotate(3deg) translateY(2px)}}
@keyframes sbBico{0%,100%{transform:scale(1)}50%{transform:scale(1.12)}}
@keyframes sbConfere{0%,100%{transform:rotate(12deg)}30%{transform:rotate(4deg)}60%{transform:rotate(15deg)}}
@keyframes sbOuvindo{0%,100%{transform:rotate(6deg) translateY(0)}50%{transform:rotate(10deg) translateY(1px)}}
@keyframes sbSobe{0%{opacity:0;transform:translate(0,0) scale(.5)}15%{opacity:1}
                  100%{opacity:0;transform:translate(-26px,-70px) scale(1.05)}}
@keyframes sbZ{0%{opacity:0;transform:translate(0,0) scale(.5)}20%{opacity:1}
               100%{opacity:0;transform:translate(26px,-62px) scale(1.2)}}
/* QUEM PEDE MENOS MOVIMENTO CONTINUA VENDO O ESTADO, e não a animação: o
   olho fechado do sono, as notas do acerto e a cabeça inclinada do confere
   são INFORMAÇÃO. Só o que se repete para sempre é que sai. */
@media (prefers-reduced-motion:reduce){
  .sabia .sb-corpo,.sabia .sb-dorme,.sabia .sb-olho,.sabia .sb-asa,.sabia .sb-cabeca,
  .sabia .sb-bico,.sabia .sb-notas .n,.sabia .sb-zzz .z,
  .sabia.confere .sb-cabeca,.sabia.ouvindo .sb-cabeca{animation:none}
  .sabia.ouvindo .sb-cabeca{transform:rotate(8deg)}
}
/* ---------- O SABIÁ ANIMADO (12/09/2026): o rig e a chave `sb-viva` ----------
   As peças NOVAS do rig (ver SABIA_CABECA/SABIA_CORPO em musikai-app.js)
   têm pivô no ponto de CONTATO, em unidades do viewBox: a pálpebra desce
   do alto do olho, a mandíbula de baixo gira na base do bico (o mesmo
   pivô do de cima), a cauda gira onde encaixa no corpo, os pés recolhem a
   partir do alto das pernas, a escaleta balança pelo centro. Os adereços
   têm pivô onde POUSAM — é de lá que a peça quica ao cair na cabeça.
   A pálpebra nasce invisível: piscar é o animador acendê-la por um
   instante. */
.sabia .sb-palpebra{opacity:0;transform-origin:112px 33px}
.sabia .sb-pupila{transform-origin:113px 40px}
.sabia .sb-bico-baixo{transform-origin:124px 47px}
.sabia .sb-cauda{transform-origin:48px 94px}
.sabia .sb-pes{transform-origin:90px 106px}
.sabia .sb-escaleta{transform-origin:81px 112px}
.sabia .sb-gravata{transform-origin:104px 66px}
.sabia .sb-medalha{transform-origin:113px 80px}
.sabia .sb-oculos{transform-origin:112px 40px}
.sabia .sb-chapeu{transform-origin:108px 26px}
/* `sb-viva` É A CHAVE: o SVG ligado ao animador (musikaiApp.sabia.ligar)
   ganha a classe, e nele os laços de keyframe das peças que o animador
   dirige são DESLIGADOS — senão a respiração do CSS e a do WAAPI se
   somariam, e a pose de mola brigaria com o `sbAssobia`. As notinhas e os
   zês continuam por CSS: são enfeite do estado, não gesto. Sem a classe,
   nada aqui existe e o pássaro é o de sempre — é o que a festa usa, e o
   que qualquer navegador sem `Element.animate` recebe. Fica DEPOIS das
   regras de estado de propósito: mesma especificidade, o último vence. */
.sabia.sb-viva .sb-corpo,.sabia.sb-viva .sb-dorme,.sabia.sb-viva .sb-olho,.sabia.sb-viva .sb-asa,
.sabia.sb-viva .sb-cabeca,.sabia.sb-viva .sb-escaleta,.sabia.sb-viva .sb-bico{animation:none}
/* ---------- RODADA 2 DO SABIÁ ANIMADO (12/09/2026): balão, voo, poleiro do fim ----------
   `#sabiaVoo` é a camada FIXA do voo entre telas: por cima de tudo, sem
   toque, e o clone dentro dela é posicionado pelo JS (left/top no ponto de
   partida, `transform` a cada quadro — ver VOAR ENTRE TELAS em
   musikai-app.js). `.sb-balao` é o balão de fala, HTML fixo ancorado no
   bico pelo JS; a seta é o `::after` e troca de lado com `.esq` quando não
   há lugar à direita do bico. `.sabia-fim` é o poleiro da tela de
   resultado do aulagame — o guia da trilha, pequeno, para o Sabiá ter onde
   pousar ao sair da lição. Nada aqui tem cor própria: painel e tinta são
   os do tema, como o resto do personagem. */
#sabiaVoo{position:fixed;inset:0;z-index:80;pointer-events:none;overflow:hidden}
#sabiaVoo svg.sb-voo{position:absolute;left:0;top:0;display:block;transform-origin:50% 50%;will-change:transform;opacity:.98}
.sb-balao{
  position:fixed;z-index:81;pointer-events:none;
  max-width:min(62vw,240px);padding:6px 11px;border-radius:12px;
  background:var(--panel,#fbf6ea);color:var(--ink,#1b1710);
  border:1.5px solid var(--ink,#1b1710);
  font:600 13px/1.3 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
  transform-origin:0 100%;
  box-shadow:0 2px 6px rgba(0,0,0,.18);
}
.sb-balao.esq{transform-origin:100% 100%}
.sb-balao::after{
  content:"";position:absolute;left:8px;bottom:-6px;width:9px;height:9px;
  background:inherit;border-right:1.5px solid var(--ink,#1b1710);border-bottom:1.5px solid var(--ink,#1b1710);
  transform:rotate(45deg);
}
.sb-balao.esq::after{left:auto;right:8px}
/* o POLEIRO do Sabiá nas páginas que não tinham pássaro (percepcao,
   13/09/2026): fica à direita, encostado nas abas; vazio não ocupa nada.
   Mora aqui, e não na página, porque o Sabiá é personagem do app inteiro
   (ver DONOS em teste-estilo.mjs) — e porque o <style> da percepcao já
   está no teto da catraca. */
.sb-poleiro{display:flex;justify-content:flex-end;margin:10px 4px -6px 0;min-height:0}
.sb-poleiro:empty{display:none}
.sabia-fim{display:block;width:64px;height:70px;margin:-4px auto -8px}
.sabia-fim svg{width:100%;height:100%;display:block}

/* O TRAÇO SUJO. É o filtro do esboço, e é ele que separa "o desenho do
   protótipo" de "um vetor liso parecido com ele" — a primeira versão desta
   tela saiu lisa e foi reprovada por isso. Ele NÃO se mexe: a fervura (a
   troca de semente) mora só no esboço, e aqui seria animação permanente
   numa tela de leitura. */
.risco{filter:url(#rabisco)}
/* o Sabiá usa o filtro VIVO: é o único que ferve. Fervura na planta
   inteira seria a tela toda tremendo o tempo todo — no esboço a página é
   uma vitrine, aqui é onde o aluno lê o nome da lição. */
.risco-vivo{filter:url(#rabiscoVivo)}

/* o 0,42 é o encosto: a flor ocupa o nó inteiro (o viewBox dela vai de
   -42 a 42), então parar a 42% do centro deixa o pássaro colado nela sem
   subir por cima das pétalas.

   ELE POUSA SEMPRE À ESQUERDA DA FLOR, em qualquer lado da curva — e isso
   é conserto, não gosto. No nó da DIREITA o "lado de fora" é a beira da
   tela: o pássaro empurrava o documento (medido: 442px de conteúdo num
   painel de 393), a página inteira andava para o lado e o cabeçalho
   sticky ia junto. Foi o que o dono viu no aparelho em 09/09/2026, em dois
   prints. À esquerda ele cabe nos dois lados, e o bico — que nasce virado
   para a direita — passa a olhar para a flor SEMPRE, sem espelho nenhum. */
.boli .sabia-guia{right:calc(50% + var(--noflor,84px) * 0.42);left:auto}

/* ---------- O BICO OLHA SEMPRE PARA A FLOR ----------
   O desenho nasce virado para a DIREITA e o pássaro pousa sempre à
   ESQUERDA da flor (regra acima): ele já olha para ela em qualquer nó.
   O `scaleX(-1)` que existia para o nó da direita saiu junto com a
   posição que o criava — e com ele saiu o par de animações espelhadas,
   que só existia porque `transform` é uma propriedade só e a respiração
   apagava o espelho no primeiro quadro. */
@media (prefers-reduced-motion:no-preference){
  /* A RESPIRAÇÃO SEGUE O PÁSSARO, E NÃO A LIÇÃO ATUAL. Estes seletores
     eram `.boli.atual .sabia-guia` porque, até 27/08/2026, o Sabiá só
     existia dentro da `.atual` — as duas coisas eram a mesma. Agora ele
     pousa no poleiro (a última lição aberta), que costuma ser uma bolinha
     `.done`: preso ao `.atual`, o pássaro ficaria congelado. `.boli` basta
     e não depende do nome da classe nova, que mora na outra página. */
  .boli .sabia-guia{animation:sabiaRespira 2.8s ease-in-out infinite;transform-origin:50% 100%}
  @keyframes sabiaRespira{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
}

/* ---------- TELA CHEIA NA ÁRVORE E NA LIÇÃO ----------
   Pedido do dono. A tab bar sai das duas: na trilha ela cortava o pé do
   desenho, e na lição ela é navegação que ninguém deveria usar no meio de
   um exercício.

   NINGUÉM FICA PRESO, e isso foi conferido antes de tirar: da trilha, o
   chip flutuante leva à LISTA das trilhas, e lá a tab bar está de volta —
   um toque a mais, não um beco. Da lição, o X fecha para a trilha.

   O PINO É `--tabbar-h`, e não uma lista de regras. Tudo que precisa
   desviar da barra (o respiro do body, a barra flutuante, o Sabiá da
   lição) já lê essa variável; zerá-la faz os três acompanharem sozinhos.
   O que fica é o `env(safe-area-inset-bottom)`, porque o risco do iPhone
   continua lá mesmo sem a barra. */
body.na-trilha,body.em-licao{--tabbar-h:env(safe-area-inset-bottom, 0px)}
body.na-trilha .tabbar,body.em-licao .tabbar{display:none}

/* ---------- TOKENS DE TIPO E RAIO (rodada 5 da reforma, 08/09/2026) ----------
   Contado no CSS das quatro páginas: 28 tamanhos de fonte (de 8,5 a 36px)
   e 8 raios. A regra para CSS NOVO é uma escala de seis tamanhos, dois
   pesos (500 e 700) e três raios — controle, cartão, pílula. O piso é
   12px para corpo e 11px para rótulo em caixa alta (com tracking curto:
   cada .01em custa ~1,4px numa linha de celular). O que já existe sobe ao
   piso aos poucos, regra a regra, sem redesenhar; esta rodada subiu a
   tab bar, os rótulos do perfil e os títulos dos painéis. Ficam em `body`
   porque os tokens de cor moram no :root de cada página, e este arquivo
   não redeclara o :root delas. */
body{
  --fs-xs:12px;--fs-s:14px;--fs-m:16px;--fs-l:20px;--fs-xl:24px;--fs-xxl:32px;
  --r-ctl:8px;--r-card:16px;--r-pill:999px;
}

/* ---------- TRILHA DO PROFESSOR (12/09/2026) ----------
   A tela nova do aulagame: para o aluno, a lista das lições que o
   professor liberou para a turma; para o professor, as turmas dele e o
   interruptor lição a lição. Mora AQUI, e não no <style> da página, pela
   catraca do teste-estilo (o orçamento do aulagame só desce) e porque é
   componente com dono único — registrado em DONOS como `.tprof-*`.
   Só CLASSE, nenhum `:has()`: o WebView do app da loja não o entende e a
   regra cairia inteira. Tudo em token de tema, para o claro e o escuro
   saírem do mesmo lugar que o resto. A bola do interruptor ligado usa
   `--on-hue` (a tinta sobre a cor de marca), como o `.msk-switch` acima. */
.tprof-box{padding:10px 0 4px}
.tprof-box.ocupado{opacity:.6;pointer-events:none}
.tprof-trilha{font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-dim);margin:14px 0 6px}
.tprof-trilha:first-child{margin-top:0}
.tprof-uni{border:1px solid var(--line);border-radius:14px;background:var(--well);padding:8px 12px 2px;margin-bottom:8px}
.tprof-uni-top{display:flex;align-items:center;gap:8px;padding-bottom:6px;border-bottom:1px solid var(--line)}
.tprof-uni-top b{flex:1;font-size:14px;color:var(--ink)}
.tprof-uni-top small{font-size:11px;color:var(--ink-faint)}
/* min-height 44: "todas"/"nenhuma" e "aplicar" mediam 28px de alto — abaixo
   do alvo de toque (revisão de UX de 12/09/2026) */
.tprof-atalho{font-size:11px;min-height:44px;padding:6px 10px;border:1px solid var(--line-2);border-radius:8px;background:transparent;color:var(--ink-dim);letter-spacing:.04em}
.tprof-atalho:hover{color:var(--ink);border-color:var(--ink-dim)}
.tprof-lic{display:flex;align-items:center;gap:10px;width:100%;text-align:left;min-height:44px;padding:6px 0;border:0;border-top:1px solid var(--line);background:transparent;color:var(--ink);font-size:14px}
.tprof-lic.primeira{border-top:0}
.tprof-lic span{flex:1}
.tprof-lic small{display:block;font-size:11px;color:var(--ink-faint)}
.tprof-lic:disabled{opacity:.45;cursor:default}
.tprof-lic .seta{flex:none;color:var(--ink-dim)}
.tprof-sw{flex:none;width:38px;height:22px;border-radius:11px;border:1px solid var(--line-2);background:var(--panel-2);position:relative;transition:background .15s,border-color .15s}
.tprof-sw::after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--ink-dim);transition:transform .15s,background .15s}
/* o interruptor NÃO estica: `.tprof-lic span{flex:1}` (o nome da lição)
   vencia o `flex:none` de .tprof-sw por especificidade, e o botãozinho de
   38px saía com 180px de largura (visto na revisão de UX de 12/09/2026) */
.tprof-lic .tprof-sw{flex:none}
.tprof-lic[aria-checked="true"] .tprof-sw{background:var(--phosphor);border-color:var(--phosphor)}
.tprof-lic[aria-checked="true"] .tprof-sw::after{transform:translateX(16px);background:var(--on-hue)}
.tprof-turma{margin-bottom:8px;width:100%}
.tprof-turma .meta{flex:1}
.tprof-turma .meta b{display:block;font-size:15px;color:var(--ink)}
.tprof-turma .meta small{font-size:11px;color:var(--ink-dim);font-family:var(--font-mono)}
.tprof-vazio{font-size:13px;line-height:1.5;color:var(--ink-dim);padding:12px 0}
/* ---------- MINHA TRILHA (12/09/2026) ----------
   A trilha que o próprio usuário monta, e as duas árvores novas (o
   Jequitibá da trilha do professor, a Goiabeira da Minha Trilha). Mora AQUI
   pela mesma catraca do teste-estilo que trouxe a trilha do professor para
   cá — o <style> do aulagame está no teto e só desce — e é componente com
   dono único, registrado em DONOS como `.mt-*`/`.mtdes*`. Só CLASSE, nenhum
   `:has()`: o WebView do app da loja não o entende. O seletor reaproveita
   as classes `.tprof-*` do editor do professor: é a mesma pergunta "entra
   ou sai", e duas linguagens para ela envelheceriam separadas.

   AS DUAS ESPÉCIES NÃO GANHARAM TOKEN NOVO, de propósito: o :root do
   aulagame também está no teto, e as cores que já existem dizem a coisa
   certa — a goiaba vermelha é polpa da cor da pitanga (`--cena-pitanga`,
   medida nos dois temas), e o pixídio do jequitibá é MADEIRA
   (`--cena-casca`, a cor do tronco). Quem distingue as árvores é o desenho
   do nó (noEspecieSvg), não a tinta. */
.mt-montar{width:100%;margin:0 auto 8px}
.mt-sug{border:1px solid var(--phosphor);border-radius:14px;padding:8px 12px 4px;margin-bottom:10px;background:var(--well)}
.mt-sug .tprof-trilha{margin-top:0}
.mt-sug-item{display:flex;align-items:center;gap:10px;padding:6px 0;border-top:1px solid var(--line)}
.mt-sug-item.primeira{border-top:0}
.mt-sug-item > span{flex:1;font-size:13px;color:var(--ink);line-height:1.35}
.mt-sug-item small{display:block;font-size:11px;color:var(--ink-dim)}
.mt-salvar{width:100%;margin-top:12px}
.mt-conv{font-size:14px;line-height:1.5;color:var(--ink);padding:8px 0}
.mt-conv p{margin:0 0 10px}
.mt-conv .btn{width:100%}
.mtdes{width:100%;text-align:left;display:flex;flex-direction:column;gap:6px}
.mtdes .ltit{margin:4px 0 0;font-size:11px;font-weight:800;letter-spacing:.2em;text-transform:uppercase;color:var(--ink-dim)}
.mtdes-linhas{display:flex;gap:6px}
.mtdes-linhas > span{flex:1;display:flex;flex-direction:column;align-items:center;padding:6px 4px;border:1px solid var(--line);border-radius:10px;background:var(--well)}
.mtdes-linhas b{font-size:15px;color:var(--ink)}
.mtdes-linhas small{font-size:10px;color:var(--ink-dim);letter-spacing:.04em}
.mtdes-frase{margin:0;font-size:13px;font-weight:700;color:var(--ink)}
.mtdes-frase small{display:block;font-weight:400;color:var(--ink-dim)}
.mtdes-frase.nivel-bem,.mtdes-frase.nivel-quase{color:var(--phosphor)}
.mtdes .mt-sug-item{border:1px solid var(--phosphor);border-radius:12px;padding:6px 10px;background:var(--well)}
body.hivis .mtdes .ltit{font-size:13px}
body.hivis .mtdes-frase{font-size:15px}
/* as duas espécies novas, nos três lugares em que a espécie pinta: a polpa
   do nó feito (.trail), a fruta solta da cena (.ucena-svg) e a festa */
.trail.esp-goiaba{--cena-fruta-no:var(--cena-pitanga)}
.trail.esp-jequitiba{--cena-fruta-no:var(--cena-casca)}
.ucena-svg.esp-goiaba{--cena-fruta-solta:var(--cena-pitanga)}
.ucena-svg.esp-jequitiba{--cena-fruta-solta:var(--cena-casca)}
.festa-un.esp-goiaba{--cena-fruta-festa:var(--cena-pitanga)}
.festa-un.esp-jequitiba{--cena-fruta-festa:var(--cena-casca)}
/* ---------- ESCOLHA DA TRILHA — O CARROSSEL (pedido do Cabello, 12/09/2026) ----------
   "Ao invés de botão abaixo, mais 2 trilhas ao lado direito de mesmo
   tamanho, com seta para ir deslizando para o lado — a trilha de destaque na
   frente e as bordas das outras ao lado." A fileira dos cartões (.tlist) e o
   cartão-árvore (.tcard, com .tnome/.tside e os nós da .arvore-trilha)
   SUBIRAM do <style> do aulagame para cá nesta rodada, com dono registrado
   em DONOS: a grade de três colunas virou um trilho que rola de lado, e o
   CSS novo não cabia no teto da catraca da página.

   COMO O TRILHO CENTRALIZA SEM SCRIPT: o cartão tem `scroll-snap-align:
   center` e o trilho `scroll-snap-type: x mandatory` — o navegador encaixa o
   cartão mais próximo no centro ao soltar o dedo. Os dois espaçadores
   (::before/::after) valem metade do que sobra da largura, para o PRIMEIRO
   e o ÚLTIMO cartão também poderem ficar no centro; são pseudo-elementos e
   não padding porque padding no fim de um trilho rolável é engolido por
   motor antigo, e porque `flex-basis` em % se resolve contra a caixa de
   CONTEÚDO — padding lateral em % entraria em círculo com a largura do
   cartão. O cartão mede METADE do trilho (teto de 170px, para o desktop não
   desenhar uma árvore de 260px de largura): é o que deixa as bordas dos
   vizinhos à mostra dos dois lados e ainda dá à árvore uma coluna em pé
   (a 375px: 160×~380, cinco nós pela conta do povoarArvore). Os vizinhos
   ficam em .9 e meio apagados; quem marca o `ativa` é o script (o cartão
   mais perto do centro — ver o bloco do carrossel no aulagame). As setas
   são botões de 44px sobre as laterais e somem no extremo; os pontinhos
   ficam abaixo do trilho. Só classe, nenhum :has(); só token, nenhuma cor
   escrita. `body.hivis` só engorda o nome, como antes. */
.tcarrossel{position:relative;flex:1;min-height:0;display:flex;flex-direction:column}
.tlist{
  flex:1;min-height:280px;display:flex;align-items:stretch;gap:8px;margin-top:4px;
  --tcard-w:min(50%,170px);
  overflow-x:auto;overflow-y:hidden;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;
  scrollbar-width:none;-ms-overflow-style:none;outline:none;border-radius:16px;
}
.tlist::-webkit-scrollbar{display:none}
.tlist::before,.tlist::after{content:"";flex:0 0 calc((100% - var(--tcard-w)) / 2 - 8px)}
.tlist:focus-visible{box-shadow:0 0 0 2px var(--phosphor)}
.card.tcard{
  flex:0 0 var(--tcard-w);width:var(--tcard-w);scroll-snap-align:center;scroll-snap-stop:always;
  display:block;min-height:0;padding:0;overflow:hidden;isolation:isolate;
  border-width:1.5px;border-color:hsl(var(--uh,150) var(--hue-s) var(--hue-l));
  transform:scale(.9);opacity:.72;transition:transform .25s ease,opacity .25s ease,border-color .15s;
}
.card.tcard.ativa{transform:none;opacity:1}
.card.tcard:hover:not(:disabled){border-color:hsl(var(--uh,150) var(--hue-s) var(--hue-l))}
.card.tcard:hover:not(:disabled) .tnome{background:var(--well)}
/* a árvore é FUNDO do botão, e o botão é o alvo de toque inteiro. A máscara
   que desvanece as pontas da cena da trilha sai aqui: a coluna tem borda,
   e o chão tem de encostar nela */
.card.tcard .arvore-trilha{position:absolute;inset:0;width:100%;height:100%;z-index:-1;-webkit-mask-image:none;mask-image:none}
/* nome em cima e contador embaixo, em plaquinhas na cor do painel: o texto
   fica sobre a cor em que o contraste já foi medido, nunca sobre céu e
   tronco desenhados */
.card.tcard .tnome,.card.tcard .tside{
  position:absolute;left:5px;right:5px;z-index:1;margin:0;
  background:var(--panel);border:1px solid var(--line);border-radius:8px;
  padding:4px 5px;text-align:center;align-items:center;
}
.card.tcard .tnome{
  top:6px;font-size:11.5px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;
  color:var(--ink);line-height:1.15;border-color:hsl(var(--uh,150) var(--hue-s) var(--hue-l));
}
body.hivis .card.tcard .tnome{font-size:13.5px}
.card.tcard .tside{bottom:6px;flex-direction:column;gap:1px}
.card.tcard .tside b{font-size:13px;color:hsl(var(--uh,150) var(--hue-s) var(--hue-lt))}
.card.tcard .tside small{font-size:clamp(9.5px,3.2vw,11px);letter-spacing:.02em}
/* o CONVITE do cartão extra sem cópia ("só o que o professor separou para a
   turma") é frase, não rótulo: sai das caixas altas e do cinza apagado que
   a lateral do cartão de perfil herda — em duas linhas de 11px, em caixa
   alta e ink-faint, ninguém lia (visto na revisão de UX de 12/09/2026) */
.card.tcard .tside small.conv{text-transform:none;letter-spacing:.01em;color:var(--ink-dim);line-height:1.3}
/* o nó da coluna é o mesmo desenho do nó da trilha (noEspecieSvg), sem o
   número: aqui a flor é contagem, não endereço. O anel pontilhado marca
   onde o aluno parou, no matiz da trilha */
.arvore-trilha .np-num{display:none}
.arvore-trilha .no.vazio{opacity:.62}
.arvore-trilha .no.cheio .np-polpa{fill:var(--cena-fruta-solta)}
.arvore-trilha .no.atual .np-casca{stroke-width:4.5}
.arvore-trilha .np-anel{stroke:hsl(var(--uh,150) var(--hue-s) var(--hue-l))}
/* as setas: alvo de 44px, sobre as laterais do trilho; somem no extremo */
.tseta{
  position:absolute;top:50%;left:2px;z-index:2;width:44px;height:44px;margin-top:-22px;padding:0 0 3px;
  border-radius:50%;border:1px solid var(--line-2);background:var(--panel);color:var(--ink);
  font-size:26px;line-height:1;font-weight:700;display:flex;align-items:center;justify-content:center;cursor:pointer;
}
.tseta-dir{left:auto;right:2px}
.tseta[hidden]{display:none}
.tseta:hover{border-color:var(--ink-dim)}
.tseta:focus-visible{outline:2px solid var(--phosphor);outline-offset:2px}
.tpontos{display:flex;justify-content:center;gap:6px;padding:8px 0 0;min-height:14px}
.tpontos i{display:block;width:6px;height:6px;border-radius:3px;background:var(--line-2);transition:width .2s,background .2s}
.tpontos i.ativa{width:16px;background:var(--phosphor)}
/* o subtítulo derivado da descrição (subtituloDaLicao), nas três listas de
   lição e no título da lição na tela de fim */
.lic-sub{display:block;font-size:11px;line-height:1.35;color:var(--ink-dim);font-weight:400;letter-spacing:0;text-transform:none}
.mtdes-licao{margin:0;font-size:13px;color:var(--ink)}
.mtdes-licao small{display:block;font-size:11px;color:var(--ink-dim)}
/* "▶ Ver como fazer" (13/09/2026): o link externo para o vídeo curto da lição,
   na tela de entrada do aulagame. Mora na camada porque o <style> do aulagame
   está no teto (ver DONOS em teste-estilo.mjs). Alvo de 44px: é tocado no
   celular, antes de começar. */
.lic-video{display:flex;align-items:center;justify-content:center;gap:8px;min-height:44px;margin:0 0 12px;padding:10px 14px;border-radius:12px;border:1px solid var(--line);background:var(--panel);color:var(--ink);font-weight:600;text-decoration:none}
.lic-video:hover,.lic-video:focus-visible{border-color:var(--phosphor);color:var(--phosphor)}
@media (prefers-reduced-motion:reduce){.card.tcard,.tpontos i{transition:none}}
/* ---------- PLACAR DA LIÇÃO NA TURMA (pedido do dono, 12/09/2026) ----------
   O top 3 desta lição e a linha do próprio aluno, na tela de fim do
   aulagame; a medalha grande no anel das estrelas; a medalhinha na bolinha
   da trilha. Reaproveita `.prow` do placar geral — duas linguagens de
   placar na mesma tela envelheceriam separadas.

   MORA AQUI, e não no <style> do aulagame, por causa da catraca do
   teste-estilo: o orçamento daquele <style> só desce, e é este arquivo
   que a régua aponta como destino de componente novo. Registrado em DONOS.
   Só seletor de CLASSE: a WebView do app da loja não tem :has(). */
.lplacar{width:100%;display:flex;flex-direction:column;gap:6px;text-align:left}
.lplacar .ltit{margin:4px 0 0;font-size:11px;font-weight:800;letter-spacing:.2em;text-transform:uppercase;color:var(--ink-dim)}
.lplacar .lmed{margin:0;font-size:13px;font-weight:700;color:var(--gold)}
.lplacar .llista{display:flex;flex-direction:column;gap:6px}
.lplacar .lrow{min-height:44px;padding:8px 12px}
.lplacar .letc{text-align:center;color:var(--ink-faint);line-height:1;font-size:16px}
.lplacar .pnums i{color:var(--ink-dim);font-size:12px}
body.hivis .lplacar .ltit{font-size:13px}
body.hivis .lplacar .lmed{font-size:15px}
/* a medalha GRANDE, no canto do anel das estrelas: só para quem ficou no top 3 */
.medalhona{position:absolute;right:-10px;bottom:-6px;font-size:36px;line-height:1;
  filter:drop-shadow(0 0 8px rgba(var(--gold-rgb),.5))}
.ring.fail .medalhona{display:none}
/* medalhinha do top 3 na bolinha da trilha: mesma faixa do recorde (.brec),
   pelo mesmo motivo — não custa altura nem vira contador. É a posição
   guardada na última volta em que o servidor respondeu; sem sessão ela
   nunca existe. */
.bmed{font-style:normal;font-size:10px;margin-left:3px;line-height:1}
body.hivis .bmed{font-size:12px}

/* ---------- A GRADE DE QUINZE (relativos, 17/09/2026) ----------
   A variante `.quinze` da grade de resposta — a `.choices` do aulagame e
   a `.ivgrid` da percepção, UMA regra para as duas telas: quinze cifras
   (Am, A♯m, B♭m, Bm, Cm…) em CINCO colunas fixas, três fileiras. Eram
   doze em quatro colunas até o prof. Danilo mandar separar os
   enarmônicos (17/09/2026); cinco de três (e não três de cinco) porque a
   tela da lição não rola e cinco fileiras não cabem com o veredito. A
   320px sobram ~53px por coluna, e a cifra mais larga ("G♯m", 15px bold)
   tem ~35px — cabe sem truncar. Mora aqui, e não no <style> da página,
   por causa da catraca do teste-estilo: o orçamento daquele <style> só
   desce, e é este arquivo que a régua aponta como destino de componente
   novo. Registrado em DONOS. Só seletor de CLASSE: a WebView do app da
   loja não tem :has().
   `text-transform:none` de propósito: `.choices button small` e `.ivgrid
   button small` são uppercase, e foi assim que o "m" de menor virou "M"
   no aparelho do professor. O botão hoje só tem <b>, mas a grade proíbe
   a caixa alta em qualquer filho — o "m" minúsculo é o significado. */
.choices.quinze,.ivgrid.quinze{grid-template-columns:repeat(5,1fr);gap:6px}
.choices.quinze button,.ivgrid.quinze button{min-height:48px;padding:6px 2px}
.choices.quinze button *,.ivgrid.quinze button *{text-transform:none}
/* na lição as grades sobem para 64px (ver o <style> do aulagame); três
   fileiras de 64 são 200px, então a de quinze fica em 50px por fileira */
@media (min-height:700px){
  body.em-licao .choices.quinze button{min-height:50px}
}

/* ---------- "MUITO ALTO" no medidor de nível (17/09/2026) ----------
   O outro lado do aviso de "está baixo" do aulagame: rastro do Cabello
   (SM-S931B, app da loja) — com o microfone saturado a clareza do SPICE
   despenca e a voz some; ver o bloco "aviso de muito alto" no JS da
   página. Em --ember, e não em --amber, porque aqui o app JÁ está
   perdendo a voz: "baixo" é "ainda não te ouvi", "alto" é "parei de te
   ouvir". Mora aqui, e não no <style> da página, por causa da catraca do
   teste-estilo (o orçamento daquele <style> só desce); registrado em
   DONOS como `.alto`. A classe vai NO PRÓPRIO #nivel e NO PRÓPRIO
   #baixo: a WebView do app da loja não tem :has(), e regra com :has cai
   inteira no aparelho. */
.nivel.alto i{background:var(--ember)}
.baixo.alto{color:var(--ember);border-left-color:var(--ember)}

/* AS CIFRAS DO RELATIVO CANTADO (unidade 5, lição 1; 17/09/2026).
   Pedido do prof. Danilo: "na unidade cinco não é necessária a clave de
   Sol e as notas na pauta. Pode escrever só os acordes. Escreve o acorde
   maior e o acorde menor relativo em vermelho [ou outra cor]. Quando ele
   acerta, muda a cor." A caixa da pauta fica escondida (`hidden`: o
   sensor ainda desenha nela) e no lugar entram as duas cifras grandes.
   O alvo é ÂMBAR — a cor de alvo da casa (mira do ciclo, faixa da fita),
   e não o vermelho, que no app é erro; acerto em --ok, erro em --ember,
   as duas tintas de significado. Só a lição 1: a lição 2 (arpejo) voltou
   à pauta pela FOTO do professor na mesma noite (ver `.pauta-cifra-alvo`,
   no bloco da pauta). Classes NO PRÓPRIO elemento: a WebView do app da
   loja não tem :has(). Dono: musikai-app.css (DONOS). */
.pauta-box[hidden]{display:none}
.cifras-relativo{
  display:grid;grid-template-columns:1fr auto 1fr;align-items:center;
  gap:2px 12px;margin-top:8px;padding:12px 6px 10px;
  border:1px solid var(--line);border-radius:10px;background:var(--well);
  font-family:var(--font-ui);
}
.cifras-relativo .cifra-soou,
.cifras-relativo .cifra-alvo{font-size:46px;line-height:1;font-weight:700;letter-spacing:.01em}
.cifras-relativo .cifra-soou{justify-self:end;color:var(--ink)}
.cifras-relativo .cifra-seta{font-size:22px;color:var(--ink-dim)}
.cifras-relativo .cifra-alvo{justify-self:start;color:var(--amber);transition:color .25s}
.cifras-relativo .cifra-alvo.acertou{color:var(--ok)}
.cifras-relativo .cifra-alvo.errou{color:var(--ember)}
body.hivis .cifras-relativo .cifra-alvo{text-decoration:underline;text-underline-offset:6px}

/* ---------- A LIÇÃO DENTRO DO RAMO (19/09/2026) ----------
   Feedback do Cabello e do Cosa: "a vida está lúdica na trilha; entra na
   lição e parece um hospital — o Sabiá fica solto no meio dos botões,
   voando e batendo a cara no vidro". Decisão do dono: a árvore da trilha
   ENTRA na lição pela margem, e o Sabiá ganha um poleiro de verdade.

   A COMPOSIÇÃO é a do encarte de LP que o Cosa mandou: o texto ao centro
   e, na MARGEM DIREITA (um pouco no topo), um ramo que emoldura sem
   invadir. O DESENHO é o da árvore da própria trilha (Intervalo, Melodia,
   Acorde — cada uma com seu galho, folha e fruta), reaproveitado: as
   mesmas classes `cm-*` e o mesmo `esp-*` da cena da unidade
   (`.ucena-svg`), gerados pelas mesmas funções do aulagame (bloco LIÇÃO NO
   RAMO). Galho e folhas em contorno de opacidade baixa; a FRUTA é o único
   ponto de cor cheia, e vive na margem.

   `.licao-cena` mora ATRÁS de tudo: `z-index:-1` dentro de um contexto de
   empilhamento próprio de `#licao` (`isolation:isolate`), sem toque
   (`pointer-events:none`). O CSS vive aqui, e não no <style> do aulagame,
   porque aquele está no teto (TETOS em teste-estilo.mjs). A cena cobre o
   `.unit` inteiro (o inset negativo desfaz o respiro `--unit-pad`) para o
   ramo entrar de verdade pela borda.

   DISCRETO DE PROPÓSITO: céu, nuvens e galhos ao longe são pano de fundo
   em opacidade baixa. Cada opacidade foi MEDIDA contra --ink e --ink-dim
   nos dois temas (teste-licao-ramo.mjs, ≥ 4,5:1): subir qualquer uma
   reprova o teste. O parallax é `--lc-x/--lc-y` gravados pelo JS e
   multiplicados por camada aqui; só `transform` e `opacity` se mexem,
   nenhum filtro animado. */
#licao{position:relative;isolation:isolate}
.licao-cena{position:absolute;inset:calc(-1 * var(--unit-pad,0px));z-index:-1;overflow:hidden;pointer-events:none;border-radius:15px}
.licao-cena>*{position:absolute}
/* as camadas de fundo nascem 12% maiores que a caixa: o deslocamento nunca mostra borda */
.licao-cena .lc-ceu,.licao-cena .lc-galhos,.licao-cena .lc-nuvens{inset:-12%;width:auto;height:auto}
.licao-cena .lc-ceu{opacity:.26;background:linear-gradient(180deg,var(--cena-ceu-alto) 0,var(--cena-ceu-meio) 58%,var(--cena-ceu-chao) 100%);transform:translate3d(calc(var(--lc-x,0px) * .15),calc(var(--lc-y,0px) * .15),0)}
/* o céu do horário, SÓ com as paradas do céu da trilha: de manhã o meio
   claro sobe às pontas; à noite e de madrugada é o chão (o tom mais
   escuro do céu) que sobe. Nuvem no topo e casca-t reprovavam o contraste
   de --ink-dim (medido em 19/09/2026) */
.licao-cena.lc-manha .lc-ceu{background:linear-gradient(180deg,var(--cena-ceu-meio) 0,var(--cena-ceu-alto) 50%,var(--cena-ceu-meio) 100%)}
.licao-cena.lc-noite .lc-ceu,.licao-cena.lc-madrugada .lc-ceu{background:linear-gradient(180deg,var(--cena-ceu-chao) 0,var(--cena-ceu-alto) 50%,var(--cena-ceu-chao) 100%)}
/* os SVGs reaproveitam `.ucena-svg` pelas cores; a máscara de ponta e o
   `width:100%;height:auto` da trilha não servem numa camada que enche a caixa */
.licao-cena svg{display:block;width:100%;height:100%;-webkit-mask-image:none;mask-image:none}
.licao-cena .lc-galhos{opacity:.12;transform:translate3d(calc(var(--lc-x,0px) * .35),calc(var(--lc-y,0px) * .35),0)}
.licao-cena .lc-nuvens{opacity:.24;transform:translate3d(calc(var(--lc-x,0px) * .7),calc(var(--lc-y,0px) * .7),0)}
/* O RAMO DA MARGEM: 160px colados à direita, do topo ao pé da caixa; o
   viewBox é alto (1000) e `slice` corta o que sobra embaixo. Galho e folhas
   (`.lc-verde`) em opacidade baixa; a fruta (`.lc-fruta`) cheia. */
.licao-cena .lc-ramo{top:0;right:0;bottom:0;width:160px;height:auto}
.licao-cena .lc-verde{opacity:.3}
.licao-cena svg .lc-fino{stroke-width:3.2}
/* O POLEIRO: fixo no mesmo canto do Sabiá da lição (`.sabia-licao`), com
   o galho passando sob os pés dele (y=148 de 165 no SVG do pássaro → 8 px
   acima do pé da caixa). Fixo para não sair do lugar quando a lição rola;
   some junto com o pássaro nas telas baixas. */
.licao-cena .lc-poleiro{position:fixed;right:0;bottom:calc(max(54px, var(--tabbar-h) + 22px) - 8px);width:150px;height:70px;opacity:.92;transform-origin:100% 100%}
body.hivis .licao-cena .lc-poleiro{width:180px;height:84px;bottom:calc(max(54px, var(--tabbar-h) + 22px) - 10px)}
@media (max-height:640px){.licao-cena .lc-poleiro{display:none}}
/* a pequena flexão do galho quando o Sabiá pousa (o JS liga `.flexao` no
   fim do voo); enfeite — sob movimento reduzido não existe */
@media (prefers-reduced-motion:no-preference){
  .licao-cena .lc-poleiro.flexao{animation:lcFlexao .55s ease-out}
  @keyframes lcFlexao{0%{transform:none}35%{transform:rotate(-1.8deg) translateY(2px)}70%{transform:rotate(.5deg)}100%{transform:none}}
}
/* ---------- O COMPLETO (freemium, F1 — 28/09/2026) ----------
   Dono único (DONOS em teste-estilo.mjs): nasceu na camada porque o
   <style> do aulagame está no teto. Três peças: a etiqueta da bolinha
   além do recorte grátis (`.bcompra` — o cadeado de COMPRA, diferente do
   disco bloqueado da progressão), o convite no lugar do vídeo "como fazer"
   e a tela do Completo. Nada disso aparece com a cobrança desligada. Sem
   :has() (a WebView do app não tem): quem monta põe a classe. */
.bcompra{display:inline-flex;align-items:center;gap:3px;font-style:normal;font-size:10px;font-weight:700;
  padding:1px 6px;border-radius:999px;border:1px solid var(--amber);color:var(--ink);background:var(--panel)}
.bcompra svg{width:10px;height:10px;fill:none;stroke:var(--amber);stroke-width:2.4;stroke-linecap:round}
body.hivis .bcompra{font-size:11.5px}
.completo-convite{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;min-height:44px;margin:0 0 12px;
  padding:10px 14px;border-radius:12px;border:1px dashed var(--amber);background:var(--panel);color:var(--ink);
  font:inherit;font-weight:600;cursor:pointer}
.completo-convite:hover,.completo-convite:focus-visible{border-style:solid}
.completo-capa{position:fixed;inset:0;z-index:60;background:var(--bg);display:flex;align-items:center;justify-content:center;padding:16px}
.completo-capa .completo-cartao{width:min(92vw,420px);max-height:86vh;overflow:auto;padding:20px;border-radius:16px;
  border:1px solid var(--line);background:var(--panel);color:var(--ink)}
.completo-capa h3{margin:0 0 6px}
.completo-capa .completo-sub,.completo-capa .completo-breve,.completo-capa .completo-turma{color:var(--ink-dim);margin:0 0 10px}
.completo-capa .completo-lista{margin:0 0 12px;padding-left:20px;line-height:1.5}
.completo-capa .completo-preco{margin:0 0 6px;font-size:16px}
.completo-capa .completo-ok{width:100%;min-height:44px}
.completo-capa .completo-assinar{display:flex;flex-direction:column;gap:8px;margin:4px 0 8px}
.completo-capa .completo-assinar .btn{width:100%;min-height:44px}
.completo-capa .completo-msg{color:var(--ink-dim);margin:0 0 8px;min-height:1em}
