/* Nexxos — aparência (gerado; edite pelo painel em Admin → Aparência) */
:root {
  color-scheme: dark;
  /* Cor da frente para as camadas com opacidade (bordas, texto apagado,
     preenchimento de hover). Ver o comentário de CLARO em theme.js. */
  --zh-fg-rgb: 255,255,255;
  --zh-shadow-rgb: 0,0,0;
  --zh-accent: #7C3AED;
  --zh-accent-rgb: 124,58,237;
  --zh-accent2: #A78BFA;
  --zh-accent2-rgb: 167,139,250;
  --zh-grad1: #C4B5FD;
  --zh-grad1-rgb: 196,181,253;
  --zh-grad2: #7C3AED;
  --zh-grad2-rgb: 124,58,237;
  --zh-bg: #0B0614;
  --zh-bg-rgb: 11,6,20;
  --zh-surface: #231A36;
  --zh-surface-rgb: 35,26,54;
  --zh-text: #ffffff;
  --zh-text-rgb: 255,255,255;
  --zh-success: #73be59;
  --zh-success-rgb: 115,190,89;
  --zh-danger: #ff867b;
  --zh-danger-rgb: 255,134,123;
  --zh-warning: #ff895e;
  --zh-warning-rgb: 255,137,94;
  --zh-info: #A78BFA;
  --zh-info-rgb: 167,139,250;
  --zh-radius: 12px;
  --zh-glow: 1;
  /* Fundo do herói. Vale "none" quando o dono não enviou nada — e none é
     um valor válido de camada em background-image, então a pilha da faixa
     não precisa saber se existe imagem ou não. O caminho já passou por
     validar(), que só aceita /caminho-local ou https:// (nada de data: ou
     javascript:) e cujo regex não permite aspas — por isso o url() aqui
     não tem como ser escapado.
     (Sem crases neste comentário: ele vive dentro de um template literal.) */
  --zh-hero-fundo: url("/uploads/heroFundo-mtjf0a7s.jpg");
  /* Mesma ideia para a tela de carregamento do preview: "none" quando o dono
     nao enviou nada, e ai vale so o papel claro com a logo. */
  --zh-carregando-fundo: none;

  /* ── Rampa de texto ────────────────────────────────────────────────
     Quatro níveis, cada um com um piso de contraste MEDIDO contra o fundo
     acima. Antes disto a interface escrevia texto secundário como "a cor da
     frente a 25%" ou "a 30%" — e isso dava 1,2:1 e 2,7:1, ou seja, texto que
     boa parte das pessoas simplesmente não lê. Os números no fim de cada
     linha são o contraste real que cada nível entrega neste tema.

     Regra de uso: t4 NUNCA recebe texto. É linha, separador, borda. */
  --zh-t1: rgba(255,255,255,0.93);   /* 11.1:1 */
  --zh-t2: rgba(255,255,255,0.7);   /* 7.0:1 */
  --zh-t3: rgba(255,255,255,0.515);   /* 4.6:1 */
  --zh-t4: rgba(255,255,255,0.255);   /* 2.2:1 */

  /* ── Planos ────────────────────────────────────────────────────────
     s1 é o cartão em repouso, s2 é o cartão sob o cursor. Existem como
     token porque "fundo + 3% de branco" estava a 1,13:1 do fundo e sumia.

     O alvo do hover não é mais um número escrito aqui: é
     alvoHover(superficie, fundo), que mantém o SALTO percebido em 1,31:1
     acima do cartão qualquer que seja a paleta. Ver o comentário da
     função — foi um número fixo duas vezes, e quebrou nas duas. */
  --zh-s1: #231A36;
  --zh-s2: #3a2b59;

  /* ── Fio ───────────────────────────────────────────────────────────
     O fio deixou de ser "a cor da frente a 9%" — um valor escolhido, que
     medido dava 1,12:1 e não desenhava linha nenhuma. Agora é medido:
     1,25:1, que é onde uma linha de 1px se lê sem virar moldura.

     Medido contra o QUÊ é a parte que custou. A primeira versão media
     contra a página, e funcionava só porque naquela paleta o cartão estava
     a 1,06:1 da página — os dois planos eram praticamente o mesmo. Numa
     paleta onde o cartão está 1,21:1 ACIMA da página, um fio ancorado na
     página cai dentro do valor do próprio cartão: medido, dava 1,04:1 em
     dez componentes de uma vez. O fio contorna o cartão, não a página.

     Então a âncora é o plano MAIS CLARO entre os dois — o mais difícil de
     se separar. Sobre ele o fio entrega os 1,25:1 pedidos; sobre o outro,
     que é mais escuro, ele entrega mais ainda. Um valor, os dois planos
     atendidos, em qualquer paleta que o dono escolher em Aparência. */
  --zh-linha:       #382956;
  --zh-linha-soft:  #30244a;
  --zh-linha-forte: #433267;

  /* Rótulo sobre superfície de marca — medido, ver sobreAcento(). Vale nos
     dois temas: a marca não troca de cor quando a luz troca de lado. */
  --zh-sobre-acento: #ffffff;

  /* A superfície de marca (botão primário, avatar). Existe como token porque
     o produto pintava as duas peças com "linear-gradient(--zh-accent,
     --zh-accent2)" — e --zh-accent2 é uma cor de TEXTO, que no tema claro
     escurece para se ler no papel: o botão ia de acento a acento-escuro e o
     rótulo caía para 2,6:1 na metade direita.
     Aqui a peça é resolvida por medição — gradiente das cores de marca
     quando a tinta se lê nas duas pontas, acento sólido quando não se lê.
     Ver superficieMarca(). */
  --zh-gradiente-acento: linear-gradient(135deg, var(--zh-accent), var(--zh-accent));

  /* ── Vidro ─────────────────────────────────────────────────────────
     Um só par de tokens para as cinco camadas flutuantes do produto
     (barra superior, menu suspenso, gaveta, sobreposição de modal,
     torrada). O desfoque é o que separa o que flutua do que está fixo;
     escrito à mão em cada lugar, ele estava entre 4px e 20px na mesma
     tela. O -webkit- ainda é necessário no Safari. */
  --zh-vidro:      rgba(var(--zh-surface-rgb), .8);
  --zh-vidro-fino: rgba(var(--zh-surface-rgb), .62);
  --zh-desfoque:   blur(12px) saturate(1.15);

  /* Brilho de acento. É a terceira sombra do sistema: elev diz altura,
     realce diz material, esta diz "isto está ACESO". Só para o controle
     primário e para o estado de foco — brilho em tudo é brilho em nada. */
  --zh-brilho: 0 0 20px rgba(var(--zh-accent-rgb), calc(.3 * var(--zh-glow)));

  /* ── Escala de tipografia ──────────────────────────────────────────
     A interface usava 24 tamanhos diferentes, com passos de meio pixel
     (11 · 11,5 · 12 · 12,5 · 13 · 13,5). Meio pixel não cria hierarquia —
     ninguém enxerga a diferença — mas garante que nada alinhe com nada.
     E 58% das declarações ficavam abaixo de 12px, com mínimo de 9px.

     Oito degraus, cada um com salto perceptível, e piso de 12px. O 12 é
     reservado a rótulo em versalete com entreletra aberta, que é onde
     tamanho pequeno ainda funciona. */
  /* Duas famílias de degraus, porque o produto tem dois regimes de leitura
     e misturá-los foi o que produziu as 24 medidas anteriores:

     REGIME DENSO (o aplicativo: painel, workspace, tabelas). Continua em
     px e continua em 13/14/15. Subir o corpo do painel para 16px, como a
     especificação pede, tiraria uma linha inteira de cada tabela e uma
     seção inteira da barra lateral — a especificação escreve a escala de
     uma página de conteúdo, não a de uma ferramenta.

     REGIME EDITORIAL (herói, seções, texto corrido: home, comprar, vazios).
     Aqui valem os degraus da especificação, em rem, com entrelinha própria:
     2,5 / 2 / 1,5 / 1 / 0,875 / 0,75rem. */
  --zh-fs-rotulo: .75rem;    /* 12px — XS: distintivo, versalete */
  --zh-fs-meta:   13px;      /* legenda, data, contagem */
  --zh-fs-corpo:  14px;      /* o padrão da interface densa */
  --zh-fs-realce: 15px;      /* corpo com ênfase */
  --zh-fs-cartao: 18px;      /* título de cartão */
  --zh-fs-small:  .875rem;   /* 14px — Small da especificação */
  --zh-fs-texto:  1rem;      /* 16px — Body: texto de LEITURA */
  --zh-fs-secao:  1.5rem;    /* 24px — H3 */
  --zh-fs-tela:   2rem;      /* 32px — H2 */
  --zh-fs-hero:   2.5rem;    /* 40px — H1 */
  --zh-lh-rotulo: 1rem;
  --zh-lh-small:  1.25rem;
  --zh-lh-texto:  1.5rem;
  --zh-lh-secao:  2rem;
  --zh-lh-tela:   2.5rem;
  --zh-lh-hero:   3rem;
  --zh-ls-rotulo: .08em;

  /* ── Grade ─────────────────────────────────────────────────────────
     Base 4px. Substitui 18 valores de gap e 22 raios improvisados. */
  --zh-e-1: 4px;  --zh-e-2: 8px;  --zh-e-3: 12px; --zh-e-4: 16px;
  --zh-e-5: 24px; --zh-e-6: 32px; --zh-e-7: 48px;
  /* Os degraus de raio são 6 · 8 · 12 · 16, e não 6 · 10 · 14 · 20, porque
     esses são exatamente os valores do Tailwind (rounded-md/lg/xl/2xl) que
     metade do produto já usa direto na marcação. Escolher outros números
     obrigaria a reescrever 82 botões para ganhar 2px de diferença que
     ninguém enxerga — e deixaria a escala brigando com o framework para
     sempre. A escala serve o código que existe. */
  --zh-r-sm: 6px; --zh-r-md: 8px; --zh-r-lg: 12px; --zh-r-xl: 16px; --zh-r-full: 999px;
  /* Alvo mínimo de toque. 202 controles do produto estão abaixo disto. */
  --zh-alvo: 40px;

  /* ── Face de display ───────────────────────────────────────────────
     Bricolage Grotesque nos títulos: grotesca de largura e tamanho óptico
     variáveis, contemporânea, que não se confunde com a Inter nem com a
     Jakarta de nenhum outro produto. É o traço próprio da interface, e por
     isso ela vale SÓ nos títulos — o corpo fica na Plus Jakarta Sans, que é
     onde uma face de texto é boa.

     A faixa de peso é 600..800 e não duas instâncias soltas: cada nível de
     título pede um peso diferente, e 800 num título de 18px vira bloco.
     "font-optical-sizing: auto" (na regra de h1/h2 mais abaixo) deixa a
     própria fonte afinar o desenho conforme o tamanho — é o que essa
     família tem de melhor, e é o motivo de ela valer a segunda requisição.

     O fallback é a Jakarta e não a Inter: enquanto a Bricolage não chega, o
     título é desenhado pela mesma face do corpo, e o salto de layout na
     troca fica menor do que com uma terceira métrica no meio. */
  --zh-font-display: 'Bricolage Grotesque', 'Plus Jakarta Sans', Inter, ui-sans-serif, system-ui, sans-serif;

  /* ── Peso como eixo de hierarquia ──────────────────────────────────
     O produto hierarquizava quase só por tamanho, e por isso precisava de
     24 tamanhos. Com peso entrando como segundo eixo, oito degraus bastam:
     "corpo" e "corpo em ênfase" passam a ser a mesma medida em pesos
     diferentes, em vez de 13px e 13,5px. */
  --zh-peso-corpo: 400;
  --zh-peso-ui: 500;
  --zh-peso-acao: 600;
  /* 800 é o topo da faixa variável da Bricolage, e é o peso que ela foi
     desenhada para exibir. Tinha caído para 700 quando a face virou Jakarta
     (onde 800 fecha os contraformas); volta com ela. */
  --zh-peso-display: 800;

  /* ── Marca ─────────────────────────────────────────────────────────
     Três paradas próprias, deliberadamente NÃO as do CrieFy: o roxo é o
     acento do produto e os dois quentes fecham a varredura sem repetir o
     magenta e o dourado da paleta deles.
     Ela é DECORATIVA (a régua, a barra de progresso): não carrega texto, e
     por isso pode varrer matizes livremente — ao contrário de
     --zh-gradiente-acento, que é superfície de rótulo e tem de passar no
     contraste. São duas coisas diferentes com nomes parecidos. */
  --zh-marca-1: #7C3AED;
  --zh-marca-2: #F43F5E;
  --zh-marca-3: #FB923C;
  --zh-marca-gradiente: linear-gradient(90deg, var(--zh-marca-1), var(--zh-marca-2) 55%, var(--zh-marca-3));

  --zh-font: 'Plus Jakarta Sans', Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
  --zh-font-mono: 'Roboto Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --zh-tracking-titulo: -.025em;

  /* ── Elevação ──────────────────────────────────────────────────────
     Cor e tipografia dizem o que a interface É; a elevação diz o que está
     ACIMA do quê. Sem ela tudo divide o mesmo plano e a tela lê como um
     desenho — foi o que sobrou faltando depois da paleta.

     Cada nível são DUAS sombras: uma curta e fechada (o contato do objeto
     com a superfície, que define a borda) e uma longa e aberta (a luz
     ambiente contornando o objeto, que dá a distância). Uma sombra só
     escolhe entre parecer colada ou parecer flutuando borrada; as duas
     juntas é o que o olho lê como altura.

     O raio negativo de espalhamento (o terceiro número) encolhe a sombra
     para dentro da silhueta: sem ele a sombra longa vaza pelas laterais e
     o cartão ganha um halo de neblina em vez de peso. */
  --zh-elev-1: 0 1px 2px -1px rgba(var(--zh-shadow-rgb),.28), 0 2px 6px -2px rgba(var(--zh-shadow-rgb),.22);
  --zh-elev-2: 0 2px 4px -2px rgba(var(--zh-shadow-rgb),.30), 0 8px 18px -6px rgba(var(--zh-shadow-rgb),.34);
  --zh-elev-3: 0 4px 8px -3px rgba(var(--zh-shadow-rgb),.34), 0 18px 40px -10px rgba(var(--zh-shadow-rgb),.46);
  --zh-elev-4: 0 8px 18px -6px rgba(var(--zh-shadow-rgb),.40), 0 40px 80px -20px rgba(var(--zh-shadow-rgb),.60);

  /* ── Realce de material ────────────────────────────────────────────
     No escuro a sombra quase não aparece: preto sobre um fundo já escuro
     não separa nada. Quem separa é a QUINA DE LUZ — um fio claro de 1px na
     aresta de cima, como se a superfície pegasse a luz que vem de cima.
     É o que faz o cartão parecer um objeto com espessura em vez de um
     retângulo mais claro. No claro ela quase some (ver o bloco do tema
     claro) porque lá quem faz o trabalho é a sombra. */
  --zh-realce: inset 0 1px 0 rgba(255,255,255,.055);
  --zh-realce-forte: inset 0 1px 0 rgba(255,255,255,.10);

  /* ── Movimento ─────────────────────────────────────────────────────
     Três durações, e não uma por componente: o que muda a percepção não é
     o número em si, é TODA a interface concordar sobre ele. A curva é
     assimétrica de propósito (sai rápido, chega devagar) — é o que faz o
     movimento parecer resposta a um gesto e não uma animação tocando. */
  /* 100 · 150 · 300 são os degraus do Tailwind (duration-100/150/300), que
     74 controles do produto já usam por classe utilitária. Manter 110/180
     deixaria três durações convivendo na mesma tela sem que ninguém pudesse
     alinhá-las — a diferença entre 150ms e 180ms não se percebe isolada, mas
     se percebe quando dois botões vizinhos reagem em tempos diferentes. */
  --zh-t-rapido: 100ms;   /* pressão, troca de cor, estados de toque */
  --zh-t: 150ms;          /* hover, elevação, o padrão */
  --zh-t-lento: 300ms;    /* painéis, gavetas, entrada de modal */
  --zh-ease: cubic-bezier(.32,.72,0,1);
  --zh-ease-entrada: cubic-bezier(.16,1,.3,1);
  --zh-ease-saida: cubic-bezier(.4,0,.9,.4);

  /* ── Foco e pressão ────────────────────────────────────────────────
     O anel de foco é acessibilidade, não enfeite: sem ele quem navega por
     teclado não sabe onde está. Fica no acento porque precisa vencer
     qualquer fundo, e recuado 2px para não ser confundido com a borda. */
  /* A cor do anel MUDA com o tema, e nao por gosto: ela e primeiro plano
     sobre um fundo que vira. Medido contra os tres planos de cada tema:

       roxo do acento (#7C3AED)  escuro 3,5 / 2,9 / 2,2   claro 5,2 / 5,7
       roxo claro (accent2)      escuro 7,4 / 6,1 / 4,6   claro 7,5 / 8,2

     O acento cheio reprovava os 3:1 que a WCAG pede para indicador de foco
     em DOIS dos tres planos escuros — inclusive sobre o cartao, que e onde
     a maioria dos campos vive. accent2 ja e a cor de texto ajustada por
     tema (ver acentoTexto), entao ela vira sozinha e passa com folga nos
     seis casos. Uma cor, medida, sem versao por tema escrita a mao. */
  --zh-anel: var(--zh-accent2);
  --zh-anel-largura: 2px;
  --zh-anel-recuo: 2px;
  /* Encolhimento no clique. Discreto de propósito: o mesmo valor vale para
     um botão de 28px e para um cartão de 320px, e o que é sutil no pequeno
     vira deformação no grande. Controles pequenos pedem mais (.96) na
     própria regra. */
  --zh-pressao: .985;
}

/* A entreletra dos títulos vale nas doze telas de uma vez — cada uma declara o
   próprio tamanho de fonte, mas nenhuma declarava tracking. É o ajuste de
   tipografia com maior efeito por linha escrita. */
/* ═══ Direção de arte ═══════════════════════════════════════════════════
   Os títulos passam a ser desenhados pela face de display, e o peso e a
   entreletra escalam POR NÍVEL. Um só valor para os três daria 800 num
   título de 18px (que vira bloco) ou 600 num de 36px (que fica mole) —
   entreletra fechada só funciona quando a letra é grande. */
h1, h2, .titulo-grande, .logo-title, .zh-display {
  font-family: var(--zh-font-display);
  font-optical-sizing: auto;
}
/* O prefixo html não é enfeite: o preflight do Tailwind declara
   "h1,h2 { font-weight: inherit }" e é carregado DEPOIS do /theme.css, então
   um seletor de elemento puro perde e o título cai para o peso do corpo.
   Com html na frente a especificidade sobe o suficiente para o token valer. */
html h1, html .titulo-grande, html .logo-title, html .zh-display {
  font-weight: var(--zh-peso-display); letter-spacing: -.035em;
}
html h2 { font-weight: 700; letter-spacing: -.025em; }
/* h3 herda só a entreletra: é nome de projeto, não título de seção. */
h3 { letter-spacing: -.012em; }

/* A régua de marca. O gradiente vive AQUI, num objeto de 3px com lugar
   definido — e não como lavagem no fundo de uma tela inteira. Uma marca
   se reconhece por repetição num lugar constante; um fundo colorido só
   deixa tudo mais difícil de ler. */
.zh-regua {
  display: block; height: 3px; width: 112px; border-radius: 2px;
  background: var(--zh-marca-gradiente);
}
.zh-regua.curta { width: 64px; }
.zh-regua.larga { width: 100%; max-width: 260px; }

/* ═══ Camada de interação ═══════════════════════════════════════════════
   Regras de comportamento que valem em TODAS as telas de uma vez. Estão
   aqui, no CSS gerado, e não em cada página, porque interação inconsistente
   é pior que interação ausente: um botão que afunda ao lado de outro que
   não afunda faz a tela parecer quebrada.

   Tudo escrito com :where(), que tem especificidade ZERO. Isso é a
   diferença entre uma camada-base e uma camada que atropela: qualquer
   regra de componente ganha destas sem precisar de !important. O anel de
   foco é a única exceção, e o comentário dele explica por quê. */

/* Foco visível. Só :focus-visible — o navegador já sabe distinguir o
   teclado do mouse, e é por isso que ninguém precisa mais apagar o anel
   no clique.
   O !important existe porque as telas estão cheias de "outline:none" e
   "outline-none" do Tailwind, herdados da época em que apagar o foco era
   prática comum. Sem ele, a navegação por teclado continuaria cega em
   metade do produto — e essa é a única regra do sistema que prefiro ver
   vencendo o autor a ver perdendo. */
:where(a[href], button, input, select, textarea, summary, [tabindex]:not([tabindex="-1"]),
       [role="button"], [role="tab"], [role="link"], [role="menuitem"], [role="switch"],
       [role="option"], [contenteditable="true"]):focus-visible {
  outline: var(--zh-anel-largura) solid var(--zh-anel) !important;
  outline-offset: var(--zh-anel-recuo) !important;
}

/* ── Quem desenha o proprio foco ─────────────────────────────────────
   Quatro componentes do produto sao ENVELOPES: uma peca arredondada, com
   borda e fundo, embrulhando um <input> transparente e sem raio (a busca
   de projetos, o compositor do workspace, o prompt do heroi, o chat do
   editor). Neles o anel generico caia no campo interno — caixa errada, e
   sem raio proprio ele saia com quinas retas dentro de uma peca
   arredondada, por cima do realce que o componente ja fazia. Dois
   indicadores de foco na mesma peca, e o de cima feio.

   A regra: quem se declara "zh-foco-proprio" recebe o anel na SUA caixa e
   silencia o generico dentro dela. O "outline" acompanha o border-radius
   do elemento, entao ele sai arredondado de gracxa.

   ":has(:focus-visible)" e nao ":focus-within" de proposito: o anel e para
   quem navega por teclado. No clique de mouse continua valendo so o
   realce suave que cada componente ja tinha — dois estados distintos para
   duas intencoes distintas. */
.zh-foco-proprio:has(:focus-visible) {
  outline: var(--zh-anel-largura) solid var(--zh-anel);
  outline-offset: var(--zh-anel-recuo);
}
:where(.zh-foco-proprio) :focus-visible { outline: none !important; }

/* Pressão. O clique precisa ter um instante próprio: sem ele a interface
   só tem "antes" e "depois", e o toque não parece ter chegado.
   Fica de fora quem já carrega transform no atributo style — uma barra de
   rolagem centrada com translateX(-50%), por exemplo, saltaria para o
   canto no clique se o scale sobrescrevesse aquilo. */
:where(button, [role="button"], [role="tab"], [role="menuitem"], [role="option"]):not([style*="transform"]) {
  transition: transform var(--zh-t-rapido) var(--zh-ease);
}
:where(button, [role="button"], [role="tab"], [role="menuitem"], [role="option"]):not([style*="transform"]):not(:disabled):not([aria-disabled="true"]):active {
  transform: scale(var(--zh-pressao));
}

/* ── Botoes de caixa de texto (heroi e compositor) ───────────────────
   Os dois lugares onde se escreve para a IA — o prompt do /inicio e o
   compositor do workspace — tinham a mesma linha de acoes desenhada duas
   vezes, com medidas diferentes: 32px de um lado, 36px do outro, pilulas
   com borda em um e sem borda no outro. Lado a lado pareciam telas de
   produtos diferentes.

   Aqui fica a familia unica: circulo nas pontas, pilula no meio, mesma
   altura, mesma borda. Mora na folha compartilhada porque as duas telas
   sao arquivos HTML separados e nao tem outro CSS em comum — duplicar o
   bloco era garantir que um dos dois ficasse para tras na proxima mexida.

   Regra de cor: so o ENVIAR e preenchido. Cor cheia e o que diz "aperte
   aqui", e ela nao pode estar em tres lugares da mesma linha. */
/* A linha das acoes: o espacador empurra o grupo da direita, e e ele que
   mantem anexar de um lado e enviar do outro em qualquer largura. */
.pb-linha { display: flex; align-items: center; gap: 8px; padding: 0 12px 12px; }
.pb-vao { flex: 1; }
/* SEM CAIXA por padrao. A primeira versao deu borda e fundo a todos os
   botoes da linha: quatro molduras lado a lado viraram parede, e a caixa de
   texto — que e o assunto — virou o fundo delas. Contorno e um recurso caro;
   gasto em tudo, nao destaca nada.

   Aqui o normal e o botao NU: so icone e rotulo, com o retangulo aparecendo
   no hover. O que sobra de contorno vai para UMA peca (.pb-destaque) e para
   o enviar. */
.pb-botao {
  height: 34px; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  border-radius: var(--zh-r-full); border: 1px solid transparent;
  background: none; color: var(--zh-t2);
  font-family: inherit; font-size: 13px; font-weight: 500; line-height: 1; cursor: pointer;
  padding: 0 8px;
  transition: background-color var(--zh-t) var(--zh-ease), border-color var(--zh-t) var(--zh-ease),
              color var(--zh-t) var(--zh-ease);
}
.pb-botao:hover { color: var(--zh-t1); background: rgba(var(--zh-fg-rgb),.07); }
.pb-redondo { width: 34px; padding: 0; flex: none; }
.pb-pilula { padding: 0 9px; }
/* O seletor que decide o que acontece com a mensagem e o unico com moldura:
   ele nao e um atalho, e uma escolha que muda o resultado. */
.pb-destaque {
  border-color: var(--zh-linha); background: rgba(var(--zh-fg-rgb),.04);
  color: var(--zh-t1); font-weight: 600; padding: 0 11px;
}
.pb-destaque:hover { border-color: var(--zh-linha-forte); background: rgba(var(--zh-fg-rgb),.08); }
.pb-seta { opacity: .45; flex: none; transition: transform var(--zh-t) var(--zh-ease); }
.pb-aberto .pb-seta, .pb-botao[aria-expanded="true"] .pb-seta { transform: rotate(180deg); }
.pb-botao[aria-expanded="true"] { color: var(--zh-t1); background: rgba(var(--zh-fg-rgb),.07); }

.pb-enviar {
  width: 34px; height: 34px; flex: none; padding: 0; border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  color: #fff; background: var(--zh-accent);
  transition: filter var(--zh-t) var(--zh-ease), background-color var(--zh-t) var(--zh-ease),
              color var(--zh-t) var(--zh-ease);
}
/* brightness em vez de uma segunda cor: o acento acompanha a paleta que o
   dono escolhe no painel, e uma cor de hover cravada descolaria da marca
   dele no primeiro tema novo. */
.pb-enviar:hover:not(:disabled) { filter: brightness(1.12); }
/* Desligado nao e "roxo apagado": e outra peca, sem cor de acao. O
   compositor nasce assim (sem texto, nada a enviar), entao este estado
   aparece mais do que o ligado. */
.pb-enviar:disabled { background: rgba(var(--zh-fg-rgb),.06); color: var(--zh-t3); box-shadow: none; cursor: default; filter: none; }
/* Parar: mesma peca, funcao oposta. Circulo igual, cor de perigo — o que
   muda e o significado, nao a forma. */
.pb-parar {
  width: 34px; height: 34px; flex: none; padding: 0; border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  color: var(--zh-bg); background: var(--zh-danger);
  transition: filter var(--zh-t) var(--zh-ease);
}
.pb-parar:hover { filter: brightness(1.1); }
/* O produto esconde botao com a classe .hidden do Tailwind. Como .pb-enviar
   e .pb-parar declaram display, um seletor de classe SO nao venceria por
   ordem de folha (o CSS do Tailwind entra em runtime, depois deste). Duas
   classes no mesmo seletor ganham por especificidade, sem !important. */
.pb-botao.hidden, .pb-enviar.hidden, .pb-parar.hidden { display: none; }

/* A lista abre PARA CIMA: as duas caixas ficam no pe da tela, e um menu
   para baixo cairia fora no telefone. */
.pb-modo { position: relative; }
.pb-menu {
  position: absolute; bottom: calc(100% + 8px); right: 0; z-index: 30;
  min-width: 244px; padding: 6px; border-radius: 14px;
  background: rgba(var(--zh-surface-rgb),.98); border: 1px solid var(--zh-linha);
  box-shadow: var(--zh-elev-3); backdrop-filter: blur(18px);
  opacity: 0; visibility: hidden; transform: translateY(6px) scale(.98);
  transform-origin: bottom right;
  transition: opacity var(--zh-t) var(--zh-ease), transform var(--zh-t) var(--zh-ease), visibility var(--zh-t);
}
.pb-modo.aberto .pb-menu { opacity: 1; visibility: visible; transform: translateY(0) scale(1); }
.pb-modo.aberto .pb-botao { color: var(--zh-t1); background: rgba(var(--zh-fg-rgb),.07); }
.pb-modo.aberto .pb-destaque { border-color: var(--zh-linha-forte); }
.pb-modo.aberto .pb-seta { transform: rotate(180deg); }
/* Planejar aceso: o modo muda o que vai acontecer com o pedido, entao ele
   nao pode parecer igual a Criar depois de escolhido. */
.pb-modo.planejando .pb-botao { color: var(--zh-accent2); border-color: rgba(var(--zh-accent-rgb),.45); background: rgba(var(--zh-accent-rgb),.10); }
.pb-opcao {
  width: 100%; display: flex; align-items: flex-start; gap: 10px; padding: 9px 10px;
  border: 0; background: none; border-radius: 10px; cursor: pointer; text-align: left;
  color: var(--zh-t2); font-family: inherit;
  transition: background-color var(--zh-t-rapido) var(--zh-ease);
}
.pb-opcao:hover { background: rgba(var(--zh-fg-rgb),.06); }
.pb-opcao > svg { flex: none; margin-top: 1px; }
.pb-opcao-txt { display: flex; flex-direction: column; min-width: 0; }
.pb-opcao-tit { font-size: 13px; font-weight: 600; color: var(--zh-t1); }
.pb-opcao-sub { font-size: 12px; color: var(--zh-t3); margin-top: 2px; line-height: 1.45; }
.pb-check { margin-left: auto; flex: none; opacity: 0; color: var(--zh-accent2); transition: opacity var(--zh-t) var(--zh-ease); }
.pb-opcao.escolhida .pb-check { opacity: 1; }

/* ── Menus da barra do workspace ─────────────────────────────────────
   Tres menus vizinhos na mesma barra — telas do app, conexoes e tamanho
   do dispositivo — estavam escritos tres vezes, cada um com estilo
   embutido: 300px de largura contra 230 e 210; fio divisorio entre os
   itens em um e nenhum nos outros; o "check" desenhado com o caractere ✓
   em dois deles, que cada fonte desenha de um jeito e nao alinha com
   nada. Abertos um ao lado do outro, pareciam de produtos diferentes.

   Aqui fica UM menu, com as pecas que os tres precisam: cabecalho de
   busca, corpo rolavel, item com icone/titulo/subtitulo/valor e marca de
   escolhido. Quem quiser um menu novo amanha nao desenha outro. */
.zh-menu {
  position: absolute; z-index: 130; min-width: 220px;
  padding: 6px; border-radius: 14px;
  background: rgba(var(--zh-surface-rgb),.98);
  border: 1px solid var(--zh-linha);
  box-shadow: var(--zh-realce), var(--zh-elev-3);
}
/* SEM backdrop-filter aqui, de proposito. O fundo do painel ja e 98% opaco:
   o desfoque atras dele nao aparecia em nada. O que ele fazia era obrigar o
   navegador a rasterizar o menu numa camada propria — e camada que cai em
   coordenada fracionaria sai com o texto BORRADO. Aparecia na lista de
   telas, que e a que tem mais texto, ainda por cima em fonte mono. */
/* A busca e um CAMPO, nao uma faixa. Antes ela encostava nas bordas do
   painel, e o anel de foco do teclado — que e um retangulo de quinas retas —
   era desenhado por fora dela, batendo na borda arredondada do menu logo
   acima. Dava aquele aro roxo torto no topo, com cara de erro de CSS.
   Agora ela tem margem, fundo e raio proprios: o anel acompanha o raio do
   campo (ver .zh-foco-proprio) e cai dentro do painel, onde deveria. */
.zh-menu-busca {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 6px; padding: 0 10px; height: 36px;
  border-radius: 10px;
  background: rgba(var(--zh-fg-rgb),.04);
  border: 1px solid var(--zh-linha-soft);
  transition: border-color var(--zh-t) var(--zh-ease), background-color var(--zh-t) var(--zh-ease);
}
.zh-menu-busca:focus-within { border-color: var(--zh-linha-forte); background: rgba(var(--zh-fg-rgb),.06); }
.zh-menu-busca > svg { flex: none; color: var(--zh-t3); }
.zh-menu-busca input {
  flex: 1; min-width: 0; background: none; border: 0; outline: none;
  color: var(--zh-text); font-size: 13px; font-family: var(--zh-font-mono);
}
.zh-menu-busca input::placeholder { color: var(--zh-t3); font-family: var(--zh-font); }
/* Barra de rolagem discreta. A do sistema desenha um trilho claro de ponta
   a ponta dentro de um menu escuro — era o que mais chamava atencao na
   lista de telas, mais que a propria lista. */
.zh-menu-corpo {
  max-height: 300px; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain;
  scrollbar-width: thin; scrollbar-color: rgba(var(--zh-fg-rgb),.18) transparent;
}
.zh-menu-corpo::-webkit-scrollbar { width: 10px; }
.zh-menu-corpo::-webkit-scrollbar-track { background: transparent; }
.zh-menu-corpo::-webkit-scrollbar-thumb {
  background: rgba(var(--zh-fg-rgb),.16); border-radius: 99px;
  border: 3px solid transparent; background-clip: content-box;
}
.zh-menu-corpo::-webkit-scrollbar-thumb:hover { background: rgba(var(--zh-fg-rgb),.30); background-clip: content-box; }

.zh-menu-item {
  width: 100%; display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; border: 0; background: none; border-radius: 10px;
  cursor: pointer; text-align: left; color: var(--zh-t2);
  font-family: inherit; font-size: 13px; line-height: 1.35;
  transition: background-color var(--zh-t-rapido) var(--zh-ease), color var(--zh-t-rapido) var(--zh-ease);
}
.zh-menu-item + .zh-menu-item { margin-top: 2px; }
.zh-menu-item:hover { background: rgba(var(--zh-fg-rgb),.06); color: var(--zh-t1); }
/* Escolhido nao e so "mais claro": ganha o acento. Sem cor, a diferenca
   entre o item sob o mouse e o item ativo era de 1% de fundo. */
.zh-menu-item.ativo { background: rgba(var(--zh-accent-rgb),.12); color: var(--zh-t1); }
.zh-menu-item > svg:first-child { flex: none; opacity: .7; }
.zh-menu-item.ativo > svg:first-child { opacity: 1; color: var(--zh-accent2); }
.zh-menu-item.alinha-topo { align-items: flex-start; }
.zh-menu-item.alinha-topo > svg:first-child { margin-top: 1px; }
.zh-menu-txt { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.zh-menu-tit { font-size: 13px; font-weight: 600; color: var(--zh-t1); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.zh-menu-sub { font-size: 12px; color: var(--zh-t3); margin-top: 2px; line-height: 1.45; }
.zh-menu-dica { flex: none; font-size: 12px; color: var(--zh-t3); font-variant-numeric: tabular-nums; }
.zh-menu-check { flex: none; opacity: 0; color: var(--zh-accent2); }
.zh-menu-item.ativo .zh-menu-check { opacity: 1; }
/* Roboto Mono vem em 400 e 500 — e so isso: o <link> das telas pede
   wght@400;500. Pedir 600 (que e o peso do .zh-menu-tit) fazia o navegador
   FALSIFICAR o negrito: ele engrossa o traco por conta propria, sem mudar a
   largura de avanco, e o texto sai com cara de desfocado. Medido lado a
   lado: 400 e 500 nitidos, 600 e 700 borrados e com a MESMA largura.
   Item em fonte mono usa 500, que existe de verdade. */
.zh-menu-mono { font-family: var(--zh-font-mono); }
.zh-menu-tit.zh-menu-mono { font-weight: 500; }
.zh-menu-nota {
  margin: 6px 2px 2px; padding: 9px 8px 2px;
  border-top: 1px solid var(--zh-linha-soft);
  font-size: 12px; color: var(--zh-t3); line-height: 1.6;
}
.zh-menu-vazio { padding: 16px 12px; text-align: center; font-size: 13px; color: var(--zh-t3); line-height: 1.7; }

/* ── Diálogo de decisão ──────────────────────────────────────────────
   O modal de excluir projeto era desenhado inteiro em style embutido, com
   tudo centralizado no topo e os botoes alinhados a esquerda logo abaixo —
   dois eixos de leitura na mesma caixa. E a opcao destrutiva tinha o mesmo
   peso visual da inofensiva: mesma altura, mesma borda, so a cor mudava.

   Aqui a caixa tem UMA coluna: emblema e titulo no topo, as escolhas como
   cartoes, e o cancelar discreto no pe. A escolha que apaga arquivo e a
   unica em vermelho, e ela carrega no proprio texto o tamanho do que vai
   ser movido — o aviso mora na consequencia, nao num quadro separado. */
.zh-dialogo-fundo {
  position: fixed; inset: 0; z-index: 100000;
  display: flex; align-items: center; justify-content: center; padding: 20px;
  background: rgba(var(--zh-shadow-rgb),.55);
  /* Aqui o desfoque E o efeito: o que esta atras aparece de proposito. */
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  animation: zhFundoEntra .14s var(--zh-ease);
}
@keyframes zhFundoEntra { from { opacity: 0 } to { opacity: 1 } }
@keyframes zhDialogoEntra { from { opacity: 0; transform: translateY(10px) scale(.985) } to { opacity: 1; transform: none } }
.zh-dialogo {
  width: min(420px, 100%); padding: 22px; border-radius: 18px;
  background: var(--zh-surface); border: 1px solid var(--zh-linha);
  box-shadow: var(--zh-realce), var(--zh-elev-4);
  font-family: var(--zh-font); text-align: left;
  animation: zhDialogoEntra .18s var(--zh-ease);
}
@media (prefers-reduced-motion: reduce) {
  .zh-dialogo-fundo, .zh-dialogo { animation: none; }
}
.zh-dialogo-topo { display: flex; gap: 12px; align-items: flex-start; margin-bottom: 16px; }
.zh-dialogo-emblema {
  width: 38px; height: 38px; flex: none; border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(var(--zh-danger-rgb),.10); color: var(--zh-danger);
}
/* Nem todo dialogo e de perigo: o de desconectar nao apaga nada, e o
   emblema vermelho ali mentia sobre o que estava para acontecer. */
.zh-dialogo-emblema.calmo { background: rgba(var(--zh-accent-rgb),.12); color: var(--zh-accent2); }
.zh-dialogo-tit { font-size: 16px; font-weight: 600; color: var(--zh-text); line-height: 1.3; }
.zh-dialogo-sub { font-size: 13px; color: var(--zh-t3); margin-top: 4px; line-height: 1.5; }
.zh-dialogo-opcao {
  width: 100%; display: flex; gap: 11px; align-items: flex-start;
  padding: 12px; border-radius: 12px;
  border: 1px solid var(--zh-linha); background: rgba(var(--zh-fg-rgb),.03);
  cursor: pointer; text-align: left; font-family: inherit; color: var(--zh-t2);
  transition: border-color var(--zh-t) var(--zh-ease), background-color var(--zh-t) var(--zh-ease);
}
.zh-dialogo-opcao + .zh-dialogo-opcao { margin-top: 8px; }
.zh-dialogo-opcao:hover { border-color: var(--zh-linha-forte); background: rgba(var(--zh-fg-rgb),.06); }
.zh-dialogo-opcao > svg { flex: none; margin-top: 1px; opacity: .8; }
.zh-dialogo-opcao-txt { display: flex; flex-direction: column; min-width: 0; }
.zh-dialogo-opcao-tit { font-size: 13px; font-weight: 600; color: var(--zh-t1); }
.zh-dialogo-opcao-sub { font-size: 12px; color: var(--zh-t3); margin-top: 3px; line-height: 1.5; }
/* A que apaga arquivo e a unica vermelha da caixa. Vermelho em duas opcoes
   nao avisa nada: so pinta a tela de perigo. */
.zh-dialogo-opcao.perigo { border-color: rgba(var(--zh-danger-rgb),.30); background: rgba(var(--zh-danger-rgb),.06); color: var(--zh-danger); }
.zh-dialogo-opcao.perigo:hover { border-color: rgba(var(--zh-danger-rgb),.55); background: rgba(var(--zh-danger-rgb),.11); }
.zh-dialogo-opcao.perigo .zh-dialogo-opcao-tit { color: var(--zh-danger); }
.zh-dialogo-opcao.perigo > svg { opacity: 1; }
.zh-dialogo-aviso {
  margin-top: 10px; padding: 10px 12px; border-radius: 10px;
  background: rgba(var(--zh-danger-rgb),.08); border: 1px solid rgba(var(--zh-danger-rgb),.22);
  font-size: 12px; color: var(--zh-danger); line-height: 1.55;
}
/* Campo de texto do dialogo (o "digite o nome para confirmar"). Era o
   <input> do prompt() do navegador, com a borda azul do sistema. */
.zh-dialogo-campo {
  width: 100%; height: 38px; margin-top: 12px; padding: 0 12px;
  border-radius: 10px; border: 1px solid var(--zh-linha);
  background: rgba(var(--zh-fg-rgb),.04); color: var(--zh-text);
  font-family: inherit; font-size: 13px; outline: none;
  transition: border-color var(--zh-t) var(--zh-ease), background-color var(--zh-t) var(--zh-ease);
}
.zh-dialogo-campo:focus { border-color: rgba(var(--zh-accent-rgb),.55); background: rgba(var(--zh-fg-rgb),.06); }
.zh-dialogo-campo::placeholder { color: var(--zh-t3); }

.zh-dialogo-pe { display: flex; justify-content: flex-end; align-items: center; gap: 8px; margin-top: 16px; }
/* A acao do dialogo: cheia de cor, como o enviar do compositor. Perigo troca
   o acento pelo vermelho — e so ele fica vermelho na caixa inteira. */
.zh-dialogo-acao {
  height: 34px; padding: 0 16px; border-radius: var(--zh-r-full); border: 0;
  background: var(--zh-accent); color: #fff;
  font-family: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
  transition: filter var(--zh-t) var(--zh-ease);
}
.zh-dialogo-acao:hover { filter: brightness(1.12); }
/* --zh-danger e cor de TEXTO: salmao claro no tema escuro, vinho no
   claro. Como FUNDO, o rotulo precisa ser o inverso dele — e --zh-bg e
   esse inverso nos dois temas (quase preto no escuro, quase branco no
   claro). Branco fixo aqui dava 2:1 sobre o salmao. */
.zh-dialogo-acao.perigo { background: var(--zh-danger); color: var(--zh-bg); }
.zh-dialogo-cancelar {
  height: 34px; padding: 0 14px; border-radius: var(--zh-r-full);
  border: 0; background: none; color: var(--zh-t3);
  font-family: inherit; font-size: 13px; font-weight: 500; cursor: pointer;
  transition: background-color var(--zh-t) var(--zh-ease), color var(--zh-t) var(--zh-ease);
}
.zh-dialogo-cancelar:hover { color: var(--zh-t1); background: rgba(var(--zh-fg-rgb),.07); }

/* ── Alvos de toque ──────────────────────────────────────────────────
   O produto tem ~200 controles abaixo de 40px por tela. Isso é um problema
   de DEDO, não de mouse: com ponteiro, um alvo de 28px é confortável, e
   inflar tudo para 40px só afastaria os controles uns dos outros sem ganho.
   Por isso a regra inteira mora atrás de (pointer: coarse) — em desktop ela
   não existe, e em telefone e tablet todo controle passa a ter onde bater. */
@media (pointer: coarse) {
  :where(button, [role="button"], summary, .sidebar-link, .z-nav-item, .z-btn,
         .z-icone-btn, .z-fechar, .tab-link, .z-aba, .z-filtro, .proj-menu-item) {
    min-height: var(--zh-alvo);
    min-width: var(--zh-alvo);
  }
  /* O <a> ficava de fora, e a medição no telefone mostrou o preço: o link
     de voltar do /comprar com 17px de altura, o "ver todos" do painel com
     20px, os ícones de navegação de /corte e /pesquisa com 20x20. Todos
     controles de verdade, todos impossíveis de acertar com o polegar.

     Mas "a" inteiro NÃO pode entrar na regra: um link dentro de um
     parágrafo com 40px de altura mínima abre buracos no meio do texto.
     A distinção é o CONTEÚDO — um link que embrulha só um ícone é botão;
     um link com texto corrido ao redor é link. ":has(> svg:only-child)"
     pega exatamente o primeiro caso. */
  :where(a[href]:has(> svg:only-child), a[href]:has(> img:only-child)) {
    min-height: var(--zh-alvo);
    min-width: var(--zh-alvo);
    display: inline-flex; align-items: center; justify-content: center;
  }
  /* Link que já se comporta como bloco/linha própria (botão de voltar,
     "ver todos", ação de rodapé) ganha altura sem afetar texto corrido. */
  :where(a.voltar, a.hero-alternativa, a.painel-vertodos, a.z-link-acao) {
    min-height: var(--zh-alvo);
    display: inline-flex; align-items: center;
  }
  /* Código embutido caía para 11,2px no telefone porque herda de um
     contêiner com "font-size: .8em". Piso próprio, em token. */
  :where(code, kbd, samp) { font-size: max(var(--zh-fs-meta), .85em); }
  /* Campo de 30px de altura é ruim de acertar e pior de digitar. Checkbox e
     radio ficam de fora: eles são desenhados pelo próprio sistema e esticar
     a caixa deforma o controle em vez de aumentá-lo. */
  :where(input:not([type="checkbox"]):not([type="radio"]), select, textarea) {
    min-height: var(--zh-alvo);
  }
}

/* Para o botão só de ícone que precisa CONTINUAR pequeno no desenho — os da
   barra superior, onde treze controles dividem 48px de altura. A área
   clicável cresce por um pseudo-elemento transparente, que não ocupa espaço
   no fluxo: o botão segue com 28px na tela e passa a ter 40px de alvo.
   É o jeito de ganhar alvo sem redesenhar a barra. */
.zh-alvo-invisivel { position: relative; }
.zh-alvo-invisivel::after {
  content: ''; position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 100%; height: 100%;
  min-width: var(--zh-alvo); min-height: var(--zh-alvo);
}

/* Desabilitado tem que ser legível pelo CURSOR, não só pela opacidade:
   quem está com o ponteiro em cima descobre antes de clicar. */
:where(button, [role="button"], input, select, textarea):disabled,
:where([aria-disabled="true"]) { cursor: not-allowed; }

/* Nada de arrastar o texto de rótulos e botões: seleção acidental em
   interface de aplicativo lê como falha, não como recurso. */
:where(button, [role="button"], [role="tab"], .z-nav-item, .sidebar-link) { user-select: none; }

/* ═══ Botão canônico ════════════════════════════════════════════════════
   O produto tem 26 classes de botão em cinco arquivos, e três delas se
   chamam ".btn-primary" com três aparências diferentes. Reescrever a
   marcação de centenas de botões — muitos montados dentro de strings de
   JavaScript — seria caro e arriscado sem nenhum ganho para quem usa.

   Então a unificação acontece no CSS: as classes continuam existindo (nada
   de JS que as consulta quebra), mas TODAS passam a herdar daqui o
   comportamento, a geometria de base e os estados. O que sobra em cada
   classe é só o que a distingue de verdade: o tom.

   Tudo em :where() — especificidade zero. A base nunca atropela uma regra
   de página; ela preenche o que a página não disse. O que as páginas
   declaravam de forma divergente (raio, padding, transição) foi
   normalizado no próprio arquivo, porque ali :where() perderia. */
:where(.btn, .btn-primary, .btn-danger, .btn-ghost, .btn-success, .btn-xs,
       .z-btn, .zcm-btn, .zcm-btn-primary, .zcm-btn-ghost, .zcm-copy-btn,
       .ic-btn, .ta-btn, .ta-label-btn, .tab-btn, .back-btn,
       .skills-filter-btn, .skill-install-btn, .int-filter-btn, .int-connect-btn) {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--zh-e-2);
  font-family: inherit; font-size: var(--zh-fs-meta); font-weight: 500;
  line-height: 1.2; white-space: nowrap;
  border: 1px solid transparent; border-radius: var(--zh-r-md);
  cursor: pointer; user-select: none;
  transition: background-color var(--zh-t) var(--zh-ease),
              border-color var(--zh-t) var(--zh-ease),
              color var(--zh-t) var(--zh-ease),
              box-shadow var(--zh-t) var(--zh-ease),
              transform var(--zh-t-rapido) var(--zh-ease);
}
/* Os estados valem para os 26 de uma vez. Antes, alguns subiam no hover,
   outros não reagiam a nada, e quase nenhum respondia ao clique — era o que
   fazia a interface parecer montada por pessoas diferentes. */
:where(.btn, .btn-primary, .btn-danger, .btn-ghost, .btn-success, .btn-xs,
       .z-btn, .zcm-btn, .zcm-copy-btn, .ic-btn, .ta-btn, .ta-label-btn,
       .skills-filter-btn, .skill-install-btn, .int-filter-btn,
       .int-connect-btn):not(:disabled):not([aria-disabled="true"]):active {
  transform: scale(.97);
}
:where(.btn, .btn-primary, .btn-danger, .btn-ghost, .btn-success, .btn-xs,
       .z-btn, .zcm-btn, .zcm-copy-btn, .ic-btn, .ta-btn,
       .skills-filter-btn, .skill-install-btn, .int-filter-btn,
       .int-connect-btn):disabled {
  opacity: .45; cursor: not-allowed; transform: none; box-shadow: none;
}

/* ── Superfície de controle ──────────────────────────────────────────
   O workspace pintava as pílulas com "branco a 5%". No escuro isso é uma
   superfície discreta; no claro é branco sobre papel branco, ou seja,
   superfície nenhuma — a pílula sumia e o botão perdia a afordância.
   Com a cor da frente do tema, os dois lados funcionam. */
.zh-sup { background: rgba(var(--zh-fg-rgb), .055); }
.zh-sup:hover, .zh-sup-hover:hover { background: rgba(var(--zh-fg-rgb), .10); }

/* O primário do workspace. "Publicar" é o objetivo de quem está ali, e era
   um botão BRANCO — genérico, sem marca, no mesmo peso de "Recarregar
   preview". É o lugar da tela onde o acento deve aparecer mais forte. */
/* O rótulo NÃO é mais "#fff" cravado. Com o roxo escuro anterior o branco
   dava 5,7:1 e passava; sobre a esmeralda ele mede 2,54:1 — o botão mais
   importante do produto ficaria ilegível, e ficaria assim em silêncio.
   Agora ele vem de --zh-sobre-acento, que é medido contra o acento vigente
   (ver sobreAcento() em theme.js) e portanto continua certo se o dono
   trocar a cor em Admin → Aparência.
   A dupla classe existe para vencer a utilitária do Tailwind, que carrega
   depois do /theme.css. */
.zh-publicar.zh-publicar {
  background: var(--zh-accent); color: var(--zh-sobre-acento);
  font-weight: var(--zh-peso-acao);
  box-shadow: var(--zh-realce-forte), 0 2px 8px -2px rgba(var(--zh-accent-rgb), .5);
}
.zh-publicar:hover {
  background: var(--zh-accent); filter: brightness(1.1);
  box-shadow: var(--zh-realce-forte), var(--zh-brilho);
}

/* ── Utilidades de profundidade ──────────────────────────────────────
   Para as telas que ainda não têm classe própria, ou para não repetir a
   dupla realce+elevação em cada componente. */
.zh-elev-1 { box-shadow: var(--zh-elev-1); }
.zh-elev-2 { box-shadow: var(--zh-elev-2); }
.zh-elev-3 { box-shadow: var(--zh-elev-3); }
.zh-elev-4 { box-shadow: var(--zh-elev-4); }
.zh-superficie { box-shadow: var(--zh-realce), var(--zh-elev-1); }
.zh-flutua    { box-shadow: var(--zh-realce), var(--zh-elev-3); }

/* Objeto que responde: sobe um fio no hover, volta e afunda no clique.
   A volta ao nível 1 no :active é o detalhe que vende o gesto — o objeto
   é empurrado PARA BAIXO, contra a superfície, e a sombra encurta junto. */
.zh-interativo {
  transition: transform var(--zh-t) var(--zh-ease),
              box-shadow var(--zh-t) var(--zh-ease),
              border-color var(--zh-t) var(--zh-ease),
              background-color var(--zh-t) var(--zh-ease);
}
.zh-interativo:hover { transform: translateY(-2px); box-shadow: var(--zh-realce-forte), var(--zh-elev-3); }
.zh-interativo:active { transform: translateY(0); box-shadow: var(--zh-elev-1); transition-duration: var(--zh-t-rapido); }

/* Borda de luz: um fio de 1px em degradê, mais claro no topo e sumindo na
   base. Faz a mesma coisa que o realce, mas contorna a peça inteira —
   é o acabamento de cartão grande, painel e modal.
   O truque das duas máscaras com composite:exclude recorta o miolo do
   degradê e deixa só o anel de 1px; é a única forma de ter borda em
   degradê sem um elemento extra no HTML. */
.zh-borda-luz { position: relative; }
.zh-borda-luz::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px; pointer-events: none;
  background: linear-gradient(180deg, rgba(255,255,255,.14), rgba(255,255,255,.02) 45%, rgba(255,255,255,0));
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
          mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
}
:root[data-tema="claro"] .zh-borda-luz::before {
  background: linear-gradient(180deg, rgba(255,255,255,.9), rgba(0,0,0,.06));
}


/* == Vidro ==============================================================
   O que FLUTUA é translúcido e desfoca o que passa por baixo; o que está
   fixo é opaco. Essa é a única regra que o produto precisa para o usuário
   entender, sem legenda, o que é camada e o que é página.
   O fallback importa: sem suporte a backdrop-filter (Firefox com a flag
   desligada, WebView antiga) um fundo a 80% deixa o texto de baixo
   atravessando o menu. Por isso o translúcido só entra DENTRO do @supports. */
.zh-vidro { background: var(--zh-surface); }
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .zh-vidro {
    background: var(--zh-vidro);
    -webkit-backdrop-filter: var(--zh-desfoque); backdrop-filter: var(--zh-desfoque);
  }
  .zh-vidro.fino { background: var(--zh-vidro-fino); }
}
.zh-brilho { box-shadow: var(--zh-brilho); }

/* == Pular para o conteudo ==============================================
   Sem isto, quem navega por teclado atravessa a barra lateral inteira
   (23 links no painel) antes de chegar na tela. É invisível até receber
   foco — e aí precisa aparecer ACIMA de tudo, inclusive do cabeçalho fixo. */
.zh-pular {
  position: fixed; top: var(--zh-e-2); left: var(--zh-e-2); z-index: 9999;
  transform: translateY(-200%);
  background: var(--zh-accent); color: var(--zh-sobre-acento);
  font-weight: var(--zh-peso-acao); font-size: var(--zh-fs-corpo);
  padding: var(--zh-e-2) var(--zh-e-4); border-radius: var(--zh-r-md);
  box-shadow: var(--zh-elev-3);
  transition: transform var(--zh-t) var(--zh-ease-entrada);
}
.zh-pular:focus { transform: translateY(0); }

/* == Distintivo =========================================================
   Pílula de estado. O fundo é o acento a 20% e o texto é o acento de
   TEXTO (--zh-accent2), não o acento cheio: acento sobre acento a 20% é o
   par que mais reprovava contraste no produto, e --zh-accent2 já nasce
   medido contra exatamente esse fundo (ver acentoTexto()). */
.zh-selo {
  display: inline-flex; align-items: center; gap: var(--zh-e-1);
  padding: var(--zh-e-1) var(--zh-e-3);
  border-radius: var(--zh-r-full);
  font-size: var(--zh-fs-rotulo); line-height: var(--zh-lh-rotulo);
  font-weight: var(--zh-peso-acao); white-space: nowrap;
  background: rgba(var(--zh-accent-rgb), .2); color: var(--zh-accent2);
}
.zh-selo.ok    { background: rgba(var(--zh-success-rgb), .18); color: var(--zh-success); }
.zh-selo.erro  { background: rgba(var(--zh-danger-rgb), .18);  color: var(--zh-danger); }
.zh-selo.aviso { background: rgba(var(--zh-warning-rgb), .18); color: var(--zh-warning); }
.zh-selo.info  { background: rgba(var(--zh-info-rgb), .18);    color: var(--zh-info); }
.zh-selo.neutro{ background: rgba(var(--zh-fg-rgb), .08);      color: var(--zh-t2); }

/* == Balao de dica ======================================================
   "title=" do navegador leva ~1,5s para aparecer, não é estilizável e não
   abre no foco por teclado. Este abre em hover E em foco, em 150ms.
   Deliberadamente só em CSS: uma dica que depende de JS é uma dica que
   some quando a tela está carregando, que é justo quando ela é mais útil.
   Limitação assumida: não vira para baixo quando falta espaço em cima —
   por isso vale para rótulo curto de controle, que é onde o espaço existe.
   Para conteúdo longo, use um popover de verdade, não isto. */
[data-dica] { position: relative; }
[data-dica]::after, [data-dica]::before {
  position: absolute; bottom: calc(100% + 8px); left: 50%;
  opacity: 0; pointer-events: none;
  transition: opacity var(--zh-t) var(--zh-ease), transform var(--zh-t) var(--zh-ease);
  z-index: 200;
}
[data-dica]::after {
  content: attr(data-dica);
  transform: translate(-50%, 4px);
  background: var(--zh-linha-forte); color: var(--zh-t1);
  font-size: var(--zh-fs-rotulo); line-height: var(--zh-lh-rotulo);
  font-weight: var(--zh-peso-ui); white-space: nowrap;
  padding: var(--zh-e-2) var(--zh-e-3); border-radius: var(--zh-r-sm);
  box-shadow: var(--zh-elev-2);
}
[data-dica]::before {
  content: ''; bottom: calc(100% + 3px);
  transform: translate(-50%, 4px);
  border: 5px solid transparent; border-top-color: var(--zh-linha-forte);
}
[data-dica]:hover::after,  [data-dica]:focus-visible::after,
[data-dica]:hover::before, [data-dica]:focus-visible::before {
  opacity: 1; transform: translate(-50%, 0);
}
[data-dica=""]::after, [data-dica=""]::before { content: none; }

/* == Progresso e espera ================================================= */
.zh-progresso {
  height: 4px; border-radius: var(--zh-r-full); overflow: hidden;
  background: rgba(var(--zh-fg-rgb), .09);
}
.zh-progresso > i {
  display: block; height: 100%; width: 0;
  border-radius: inherit; background: var(--zh-marca-gradiente);
  transition: width var(--zh-t-lento) var(--zh-ease);
}
/* Sem valor conhecido: a barra vira um vaivém em vez de mentir uma
   porcentagem. "indeterminado" é estado, não decoração. */
.zh-progresso.indeterminado > i {
  width: 35%; animation: zh-vaivem 1.4s var(--zh-ease) infinite;
}
@keyframes zh-vaivem { 0% { margin-left: -35%; } 100% { margin-left: 100%; } }

.zh-girando {
  display: inline-block; width: 1em; height: 1em; flex-shrink: 0;
  border: 2px solid currentColor; border-right-color: transparent;
  border-radius: 50%; animation: zh-girar .7s linear infinite;
  vertical-align: -.125em;
}
@keyframes zh-girar { to { transform: rotate(360deg); } }

/* == Carga de tela inteira ==============================================
   O produto tinha CINCO idiomas de "carregando" (animate-spin do Tailwind,
   .tl-spinner, .tb-spin, .spinner, .prompt-border-spin) e o da abertura do
   workspace era o mais datado: um disco cheio de 64px com gradiente a 50%,
   um arco branco generico girando dentro e — o detalhe que denuncia a
   heranca — "shadow-purple-500/25", um roxo do Tailwind CRAVADO. Trocar a
   cor da marca em Aparencia nao alcancava aquele brilho: ele ficaria roxo
   para sempre, em qualquer instalacao.

   No lugar: um ANEL fino com a varredura da marca girando, e a LOGO parada
   no centro. Duas razoes, nenhuma estetica:

   · o anel usa --zh-marca-*, entao ele acompanha o acento configurado;
   · a logo amarra a espera a identidade DA INSTALACAO, e faz esta tela
     falar a mesma lingua da tela de carregamento do preview, que tambem
     e centrada na logo. Um produto, um jeito de esperar.

   A logo e IRMA do anel, nao filha: filha giraria junto.

   O @supports nao e zelo excessivo. Sem "mask", o conic-gradient pinta um
   disco cheio — pior que o que estava aqui antes. O ramo de reserva e o
   anel classico de borda, que funciona em qualquer navegador. */
.zh-carga { position: relative; width: 84px; height: 84px; margin: 0 auto; }
.zh-carga::before {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  border: 3px solid rgba(var(--zh-fg-rgb), .10);
  border-top-color: var(--zh-accent2);
  animation: zh-girar 1.15s linear infinite;
}
@supports (mask: radial-gradient(#000, #000)) or (-webkit-mask: radial-gradient(#000, #000)) {
  .zh-carga::before {
    border: 0;
    background: conic-gradient(from 0turn,
      transparent 0 12%, var(--zh-marca-1) 42%, var(--zh-marca-2) 68%,
      var(--zh-marca-3) 88%, transparent 100%);
    -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
            mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
  }
}
.zh-carga > img, .zh-carga > svg {
  position: absolute; inset: 0; margin: auto;
  width: 38px; height: 38px; object-fit: contain; display: block;
}
/* Sobre PAPEL CLARO a varredura da marca nao transfere. Medido sobre
   #F6F4FA: acento 5,22 · rosa 3,36 · laranja 2,07 · lavanda 1,69. As duas
   ultimas simplesmente somem — um terco do anel ficaria invisivel. No claro
   ele usa so a metade escura da propria varredura. */
.zh-carga.no-claro::before {
  background: conic-gradient(from 0turn, transparent 0 10%,
    var(--zh-marca-1) 46%, var(--zh-marca-2) 80%, transparent 100%);
}

/* Estado CALMO: nada novo chegou do motor ha mais de 6s.
   Isto nao e enfeite. A tela de carregamento recebe DOIS tipos de texto —
   o real, extraido dos eventos do motor ("Criando index.html…"), e o
   generico, de um rotador que preenche o silencio ("Montando a
   estrutura…"). O codigo sempre soube a diferenca (_loadingRealTs); quem
   olhava, nao: as duas frases tinham exatamente a mesma aparencia.
   Agora o anel carrega essa diferenca. Girando firme = chegou coisa nova.
   Desacelerado, com a logo respirando = de pe, esperando. Sem legenda, sem
   jargao, e honesto nos dois sentidos. */
/* A primeira versao distinguia os estados por OPACIDADE (calmo a 50%).
   Medido sobre o papel: o acento a 50% da 2,19:1 e a rosa 1,91:1 — abaixo
   dos 3:1 que a WCAG 1.4.11 pede para componente nao-textual QUE TRANSMITE
   ESTADO. E este anel passou a transmitir estado, entao a regra vale para
   ele; nem a 80% a rosa chegava la (2,77).
   Os dois estados ficam em opacidade cheia, e o que os separa e a
   VELOCIDADE e a riqueza da varredura: calmo gira devagar em uma cor so
   (acento, 5,22:1); ativo gira rapido e ganha a segunda parada (rosa,
   3,36:1). Mais legivel que o escurecimento, e passa medido. */
.zh-carga.calmo::before {
  animation-duration: 3.4s;
  background: conic-gradient(from 0turn, transparent 0 22%,
    var(--zh-marca-1) 58%, transparent 92%);
}
.zh-carga.calmo > img { animation: zh-respirar 3.4s ease-in-out infinite; }
@keyframes zh-respirar {
  0%, 100% { opacity: .7; transform: scale(.965); }
  50%      { opacity:  1; transform: scale(1); }
}

/* Quem pede menos movimento perde o giro — e ai o texto e o cronometro
   abaixo do anel sao os unicos portadores da informacao, que e o motivo de
   nenhum dos dois ser opcional. */
@media (prefers-reduced-motion: reduce) {
  .zh-carga::before, .zh-carga > img { animation: none; opacity: .55; }
}
/* Botao em espera: o rótulo fica, o clique não passa. Trocar o texto por
   "Carregando..." muda a largura do botão e a linha inteira dá um salto. */
:where(button, [role="button"])[aria-busy="true"] {
  pointer-events: none; opacity: .7;
}

/* == Esqueleto ==========================================================
   Ele existe para o layout não SALTAR quando o dado chega — por isso a
   caixa precisa ter a altura da coisa que vai substituí-la, e não a de um
   retângulo genérico. O brilho atravessa da esquerda para a direita
   porque ler no sentido do texto é o que dá a sensação de "vindo". */
.zh-esqueleto {
  position: relative; overflow: hidden;
  border-radius: var(--zh-r-sm);
  background: rgba(var(--zh-fg-rgb), .07);
}
.zh-esqueleto::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(var(--zh-fg-rgb), .07), transparent);
  animation: zh-varrer 1.5s ease-in-out infinite;
}
@keyframes zh-varrer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } }

/* == Entrada por rolagem ================================================
   Escrito com "animation-timeline: view()" e não com IntersectionObserver:
   o observador precisa de JS em cada página, roda na thread principal e —
   o que mais importa — deixa o conteúdo INVISÍVEL se o script falhar ou
   demorar. Aqui o estado padrão é visível; o esconde-e-revela só existe
   dentro do @supports, ou seja, só onde o navegador garante que vai
   terminar a animação. Conteúdo nunca fica refém de efeito. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .zh-revelar {
      animation: zh-subir linear both;
      animation-timeline: view();
      animation-range: entry 5% cover 26%;
    }
  }
}
@keyframes zh-subir {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* Cascata. O atraso vem do índice do filho e não de uma variável escrita
   à mão em cada item, para uma lista renderizada por JS não precisar
   calcular nada. Doze degraus: acima disso a cascata deixa de ser ritmo e
   vira espera. */
@media (prefers-reduced-motion: no-preference) {
  .zh-escalonar > * { animation: zh-subir var(--zh-t-lento) var(--zh-ease-entrada) both; }
  .zh-escalonar > :nth-child(1)  { animation-delay:   0ms; }
  .zh-escalonar > :nth-child(2)  { animation-delay:  50ms; }
  .zh-escalonar > :nth-child(3)  { animation-delay: 100ms; }
  .zh-escalonar > :nth-child(4)  { animation-delay: 150ms; }
  .zh-escalonar > :nth-child(5)  { animation-delay: 200ms; }
  .zh-escalonar > :nth-child(6)  { animation-delay: 250ms; }
  .zh-escalonar > :nth-child(7)  { animation-delay: 300ms; }
  .zh-escalonar > :nth-child(8)  { animation-delay: 350ms; }
  .zh-escalonar > :nth-child(9)  { animation-delay: 400ms; }
  .zh-escalonar > :nth-child(10) { animation-delay: 450ms; }
  .zh-escalonar > :nth-child(11) { animation-delay: 500ms; }
  .zh-escalonar > :nth-child(n+12) { animation-delay: 550ms; }
}

/* == Regime editorial ===================================================
   Para texto de LEITURA (herói, seções de conteúdo, estados vazios), onde
   14px é apertado. O "max-width" em ch e não em px: a medida confortável
   de linha é contada em caracteres, e em px ela muda toda vez que o
   tamanho da fonte muda. */
.zh-leitura {
  font-size: var(--zh-fs-texto); line-height: var(--zh-lh-texto);
  color: var(--zh-t2); max-width: 68ch;
}
.zh-h1 { font-family: var(--zh-font-display); font-size: var(--zh-fs-hero); line-height: var(--zh-lh-hero); font-weight: var(--zh-peso-display); letter-spacing: -.035em; }
.zh-h2 { font-family: var(--zh-font-display); font-size: var(--zh-fs-tela); line-height: var(--zh-lh-tela); font-weight: 700; letter-spacing: -.025em; }
.zh-h3 { font-size: var(--zh-fs-secao); line-height: var(--zh-lh-secao); font-weight: var(--zh-peso-acao); letter-spacing: -.012em; }

/* == O raiz nunca rola de lado ==========================================
   Medido: /corte a 320px rolava 94px na horizontal, /pesquisa 43px. A causa
   não era layout largo — era a GAVETA. Um painel "position: fixed" fechado
   com "translateX(100%)" fica inteiro à direita da janela (borda direita
   medida em 702px numa tela de 320), e "body { overflow: hidden }" não o
   contém: elemento fixo tem a janela como bloco contêiner, não o body. Ele
   escapa do recorte e estica a área rolável do documento.

   "clip" e não "hidden", e a diferença importa: "hidden" transforma o raiz
   em contêiner de rolagem, o que muda o elemento que "position: sticky"
   observa e quebra barras grudadas em várias telas. "clip" recorta sem
   criar contêiner de rolagem — é a versão desta regra que não tem efeito
   colateral. O @supports cobre navegador sem "clip", onde "hidden" ainda é
   melhor que barra de rolagem lateral.

   Isto é uma REDE, não um conserto: cada estouro real foi corrigido na
   origem (ver a gaveta em corte/pesquisa, o compositor do workspace e o
   painel do editor). A rede existe para a próxima gaveta que alguém
   adicionar não reintroduzir o defeito em silêncio. A auditoria continua
   medindo por geometria de elemento, não por scrollWidth, justamente para
   não ser cega a um estouro que a rede escondeu. */
html { overflow-x: clip; }
@supports not (overflow: clip) { html { overflow-x: hidden; } }

/* == Midia nunca estoura o pai =========================================
   O produto nao tinha NENHUMA regra disto — medido: zero ocorrencias de
   "max-width" em img/video em qualquer folha. Enquanto as imagens vem de
   upload do dono (logo, favicon, fundo do heroi) isso passa despercebido,
   porque ele escolhe imagens pequenas. Uma foto de 3000px num cartao de
   projeto arrebenta a coluna inteira.

   O par com "height: auto" e o que impede a distorcao: sem ele, uma imagem
   com atributo height=600 no HTML fica com 320 de largura e 600 de altura.
   Como isto esta em :where() (especificidade zero), qualquer altura
   declarada em CSS pela propria tela continua vencendo — o auto so derruba
   o atributo do HTML, que e exatamente o caso problematico.

   O SVG fica de FORA de proposito: os icones do produto declaram width e
   height proprios e vivem dentro de botoes de 16px; um max-width neles nao
   resolve nada e um height:auto colapsaria icone com viewBox sem altura. */
:where(img, video, canvas) { max-width: 100%; }
:where(img, video) { height: auto; }

/* == Camada flutuante cabe na janela ===================================
   Menu suspenso, popover e balao sao posicionados por JS a partir do
   elemento que os abriu. No desktop sobra espaco; a 320px o mesmo calculo
   joga metade do menu para fora, e o pedaco de fora e inalcancavel porque
   o raiz agora recorta.
   Um teto em "max-width" resolve a classe inteira sem tocar em nenhum dos
   27 calculos de posicao espalhados pelo produto. "left: auto" nao da para
   consertar aqui, mas largura contida ja evita o corte na maioria dos
   casos, e os que sobram aparecem na auditoria. */
:where([role="menu"], [role="listbox"], [role="dialog"], .zh-menu, .zh-popover,
       .proj-menu, .mode-menu, .model-menu, .z-menu) {
  max-width: calc(100vw - var(--zh-e-4));
}

/* No dedo, 13px e 14px sao pequenos demais para texto corrido — e o piso
   de 16px é o que impede o Safari do iPhone de dar zoom sozinho ao focar
   um campo. Sobe o TOKEN, não cada regra: quem já usa var() acompanha,
   quem cravou px continua como estava, e nada de layout denso quebra. */
@media (pointer: coarse) {
  :root { --zh-fs-corpo: 16px; --zh-fs-meta: 14px; --zh-fs-realce: 16px; }
  /* Este !important é o segundo (e último) do sistema, pela mesma razão do
     anel de foco: sem ele a regra não chega ao produto. Campo abaixo de 16px
     faz o Safari do iPhone dar ZOOM na página inteira ao receber foco, e o
     usuário fica com a interface deslocada e sem jeito óbvio de voltar. As
     telas cravam "font-size: 13px" em classe (.z-in, .form-input, .chat-input),
     então qualquer seletor de elemento perde. O escopo é estreito de propósito:
     só ponteiro grosso, só campo de texto. */
  input, select, textarea { font-size: max(16px, 1em) !important; }
}

/* Rolagem suave onde ela é pedida por âncora. Fica atrás da preferência de
   movimento — o bloco de prefers-reduced-motion logo abaixo devolve "auto". */
html { scroll-behavior: smooth; }

/* Quem pediu menos movimento recebe menos movimento. A interface continua
   inteira — o que morre é a duração, não o estado final. As decorativas
   infinitas (as estrelas do hero, o giro do prompt) param de vez, que é o
   ponto todo da preferência. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Tema claro. Vem depois de :root e ganha por especificidade — só troca os
   neutros; accent, gradientes e as cores de estado seguem iguais.
   Quem escreve o atributo é o script de tema, antes da primeira pintura. */
:root[data-tema="claro"] {
  color-scheme: light;
  --zh-bg: #F6F4FA;
  --zh-bg-rgb: 246,244,250;
  --zh-surface: #FFFFFF;
  --zh-surface-rgb: 255,255,255;
  --zh-text: #1B1725;
  --zh-text-rgb: 27,23,37;
  --zh-fg-rgb: 0,0,0;
  --zh-shadow-rgb: 42,32,60;

  /* A rampa recalculada contra o papel claro. As proporções NÃO são as mesmas
     do escuro: branco sobre quase-preto rende muito mais que preto sobre
     quase-branco, então t3 aqui precisa de 54% onde no escuro bastavam 45%.
     É exatamente por copiar as proporções de um tema para o outro que o claro
     estava pior que o escuro. */
  --zh-t1: rgba(0,0,0,0.88);   /* 11.1:1 */
  --zh-t2: rgba(0,0,0,0.715);   /* 7.1:1 */
  --zh-t3: rgba(0,0,0,0.575);   /* 4.5:1 */
  --zh-t4: rgba(0,0,0,0.335);   /* 2.2:1 */
  --zh-s1: #FFFFFF;
  --zh-s2: #d0cadd;

  /* O fio NÃO pode ser medido a partir do fundo aqui: no claro o cartão é
     branco PURO e a página é lavanda clara, então um degrau "acima do
     fundo" andaria na direção errada e o fio sumiria dentro do cartão. No
     papel quem desenha quina é tinta, e tinta é a cor da frente com pouca
     opacidade — o mesmo mecanismo do resto do tema claro. */
  --zh-linha:       rgba(var(--zh-fg-rgb), .13);
  --zh-linha-soft:  rgba(var(--zh-fg-rgb), .08);
  --zh-linha-forte: rgba(var(--zh-fg-rgb), .22);

  --zh-vidro:      rgba(var(--zh-surface-rgb), .82);
  --zh-vidro-fino: rgba(var(--zh-surface-rgb), .66);

  /* O acento de texto TROCA no claro. #A78BFA sobre papel dá 2,5:1 — todo
     link e todo ícone de realce do produto era ilegível no tema claro. O
     acento cheio (fundo de botão) segue o mesmo, porque ali o texto é branco
     em cima e quem precisa passar é esse par. */
  --zh-accent2: #5909e3;
  --zh-accent2-rgb: 89,9,227;

  /* Cores de estado escurecidas até se lerem no papel. Antes atravessavam sem
     mudar e reprovavam TODAS (1,6:1 a 2,4:1): no tema claro, toda mensagem de
     erro e de sucesso do produto era ilegível. */
  --zh-success: #216b07;
  --zh-success-rgb: 33,107,7;
  --zh-danger: #b21607;
  --zh-danger-rgb: 178,22,7;
  --zh-warning: #a83207;
  --zh-warning-rgb: 168,50,7;
  --zh-info: #5e2af6;
  --zh-info-rgb: 94,42,246;
  /* Os brilhos roxos foram desenhados para acender no escuro; no claro eles
     viram borrão. Ficam de pé, mas com a metade da força. */
  --zh-glow: 0.5;

  /* A profundidade troca de mecanismo aqui, não de intensidade.
     No escuro a sombra é preto sobre quase-preto: mal aparece, e quem
     separa os planos é a quina de luz. No claro é o contrário — a sombra
     tem para onde escurecer, e a quina de luz sobre um cartão quase branco
     não tem contraste nenhum. Então os alfas das sombras CAEM (no claro
     .30 de preto vira uma mancha suja, não uma sombra) e o realce quase
     desaparece. Mesmos nomes, mesma leitura de altura, física invertida. */
  --zh-elev-1: 0 1px 2px -1px rgba(var(--zh-shadow-rgb),.10), 0 2px 6px -2px rgba(var(--zh-shadow-rgb),.08);
  --zh-elev-2: 0 2px 4px -2px rgba(var(--zh-shadow-rgb),.10), 0 8px 18px -6px rgba(var(--zh-shadow-rgb),.12);
  --zh-elev-3: 0 4px 8px -3px rgba(var(--zh-shadow-rgb),.11), 0 18px 40px -10px rgba(var(--zh-shadow-rgb),.16);
  --zh-elev-4: 0 8px 18px -6px rgba(var(--zh-shadow-rgb),.13), 0 40px 80px -20px rgba(var(--zh-shadow-rgb),.20);
  --zh-realce: inset 0 1px 0 rgba(255,255,255,.75);
  --zh-realce-forte: inset 0 1px 0 rgba(255,255,255,1);
}
