/* Complemento ao app.css (o build Tailwind congelado).
 *
 * Aqui entram duas coisas, e só elas:
 *
 *  1. Utilitários que o build congelado não tem, porque o Tailwind os geraria
 *     do texto-fonte e ninguém mais roda o Tailwind. Escrever à mão é o
 *     caminho normal agora — ver a seção "classe nova" no CLAUDE.md.
 *
 *  2. A substituição do framer-motion, que saiu junto com o Node.
 */

/* --------------------------------------------------- entrada em cascata ----
 * O React usa duas variantes do framer-motion, repetidas em 10 páginas:
 *   container: staggerChildren 0.05
 *   item:      opacity 0->1, y 10->0, 0.3s, cubic-bezier(.25,.46,.45,.94)
 * O atraso por item vem de um style inline (animation-delay), calculado no
 * servidor a partir do índice.
 */
@keyframes fz-item-in {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

.fz-stagger > * {
  opacity: 0;
  animation: fz-item-in 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}

/* O painel de estatísticas usa a OUTRA variante do framer-motion:
 *   container: staggerChildren 0.1
 *   item:      y 20->0, 0.5s
 * Depois de assentar as duas terminam iguais, mas reproduzir só uma faria a
 * página inteira entrar no ritmo errado — visível a olho, invisível no diff. */
@keyframes fz-item-in-lento {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: none; }
}

.fz-stagger-lento > * {
  opacity: 0;
  animation: fz-item-in-lento 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}

/* O framer-motion respeita prefers-reduced-motion sozinho; CSS não respeita.
 * Sem este bloco a migração introduziria uma regressão de acessibilidade que
 * não existia no React. */
@media (prefers-reduced-motion: reduce) {
  .fz-stagger > *,
  .fz-stagger-lento > * {
    animation: none;
    opacity: 1;
  }
}

/* ------------------------------------------------------- select próprio ----
 * O Radix expõe --radix-select-trigger-width e o select.tsx a usa para o painel
 * acompanhar a largura do campo. Aqui a variável é escrita pelo fzSelect, e a
 * classe correspondente não existe no build congelado — o Tailwind só a geraria
 * a partir do texto-fonte do React, que nunca teve este nome.
 */
.min-w-\[var\(--fz-select-trigger-width\)\] {
  min-width: var(--fz-select-trigger-width);
}

/* ================================================== construtor gráfico =====
 * O fluxograma de regras. Tudo aqui é classe NOVA: o build Tailwind congelado
 * foi gerado a partir do texto-fonte do SPA React, que nunca teve um construtor
 * visual — não há o que purgar nem o que reaproveitar.
 *
 * As cores saem das variáveis do tema, e não de valores fixos, para que o
 * diagrama acompanhe claro e escuro como o resto do portal.
 *
 * A classe da FORMA é sempre `fz-bloco-caixa`, seja o elemento um <rect>, um
 * <polygon> ou um <path>. É dela que penduram o preenchimento por tipo, o
 * contorno do selecionado e o do culpado: uma classe por forma multiplicaria
 * essas três regras por quatro e faria o losango esquecer de ficar vermelho no
 * dia em que alguém acrescentasse um estado.
 */
.fz-canvas-raiz {
  position: relative;
}

.fz-canvas-tela {
  position: relative;
  height: 30rem;
  border: 1px solid hsl(var(--border));
  border-radius: 0.5rem;
  overflow: hidden;
  background-color: hsl(var(--muted) / 0.3);
  /* A grade é o único indício de que o canvas se move: sem ela, arrastar o
   * fundo não parece fazer nada. */
  background-image:
    linear-gradient(hsl(var(--border) / 0.5) 1px, transparent 1px),
    linear-gradient(90deg, hsl(var(--border) / 0.5) 1px, transparent 1px);
  background-size: 20px 20px;
}

.fz-canvas-conteudo {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
}

.fz-canvas-svg {
  flex: 1 1 auto;
  width: 100%;
  min-height: 0;
  display: block;
  touch-action: none; /* o arrasto é nosso, não do navegador */
  cursor: grab;
}

/* ------------------------------------------------------------- as formas -- */

.fz-bloco {
  cursor: pointer;
}

.fz-bloco-caixa {
  fill: hsl(var(--card));
  stroke: hsl(var(--border));
  stroke-width: 1.5;
}

.fz-bloco-inicio .fz-bloco-caixa { fill: hsl(142 71% 45% / 0.12); stroke: hsl(142 71% 45%); }
.fz-bloco-condicao .fz-bloco-caixa { fill: hsl(217 91% 60% / 0.12); stroke: hsl(217 91% 60%); }
.fz-bloco-atribuir .fz-bloco-caixa { fill: hsl(38 92% 50% / 0.12); stroke: hsl(38 92% 50%); }
.fz-bloco-pontuar .fz-bloco-caixa { fill: hsl(var(--destructive) / 0.12); stroke: hsl(var(--destructive)); }
.fz-bloco-fim .fz-bloco-caixa { fill: hsl(var(--muted)); stroke: hsl(var(--muted-foreground)); }

/* A anotação é um colchete, não uma caixa: o lado direito está aberto de
 * propósito, e preencher a área implícita fecharia o símbolo. */
.fz-bloco-nota .fz-bloco-caixa {
  fill: none;
  stroke: hsl(var(--muted-foreground));
  stroke-dasharray: 4 3;
}

/* Os ESTADOS vêm depois das cores de tipo, e a ordem é o que os faz valer:
 * as duas famílias têm a mesma especificidade, então quem estiver por último
 * ganha. Com as cores de tipo em último, um bloco de cálculo selecionado
 * continuaria âmbar e o contorno de "este aqui é o culpado" nunca apareceria
 * — que é justamente o momento em que ele precisa aparecer. */
.fz-bloco-ativo .fz-bloco-caixa {
  stroke: hsl(var(--primary));
  stroke-width: 2.5;
}

/* O bloco culpado por um erro de compilação. O erro vem do servidor com o ID
 * do BLOCO, e não com linha e coluna — num desenho não existe linha. */
.fz-bloco-culpado .fz-bloco-caixa {
  stroke: hsl(var(--destructive));
  stroke-width: 2.5;
}

/* O bloco alcançado por dois caminhos diferentes, num desenho que não se
 * estrutura. Aparece uma vez só, tracejado, com a faixa de aviso por cima. */
.fz-bloco-repetido .fz-bloco-caixa {
  stroke-dasharray: 6 4;
}

.fz-bloco-titulo {
  font-size: 11px;
  font-weight: 600;
  fill: hsl(var(--muted-foreground));
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.fz-bloco-texto {
  font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  fill: hsl(var(--foreground));
}

/* O número do passo. É o mesmo número do statement no script compilado, e é o
 * que deixa o diagrama e o preview mutuamente legíveis. */
.fz-passo {
  fill: hsl(var(--background));
  stroke: hsl(var(--muted-foreground) / 0.6);
  stroke-width: 1;
}

.fz-passo-texto {
  font-size: 10px;
  font-weight: 700;
  fill: hsl(var(--muted-foreground));
}

/* ---------------------------------------------------------------- os fios -- */

/* Os fios não capturam o ponteiro: eles cruzam a tela toda, e um clique em cima
 * de uma seta precisa arrastar o fundo como qualquer outro ponto vazio. */
.fz-camada-fios {
  pointer-events: none;
}

.fz-fio {
  fill: none;
  stroke: hsl(var(--muted-foreground));
  stroke-width: 1.5;
  stroke-linejoin: round;
}

/* O fio da anotação não é fluxo de controle: é uma linha de chamada. */
.fz-fio-tracejado {
  stroke-dasharray: 3 3;
  stroke: hsl(var(--muted-foreground) / 0.6);
}

.fz-seta {
  fill: hsl(var(--muted-foreground));
}

.fz-rotulo-ramo {
  font-size: 11px;
  font-weight: 600;
  fill: hsl(var(--muted-foreground));
}

/* A junção: onde os dois ramos se reencontram. Só aparece quando os dois
 * continuam de verdade — ver canvas_desenho.go. */
.fz-juncao {
  fill: hsl(var(--muted-foreground));
  stroke: hsl(var(--background));
  stroke-width: 2;
}

/* ------------------------------------------------------------ a inserção -- */

/* O "+" no meio de cada seta é o ÚNICO lugar de onde nasce um bloco. Fica
 * discreto até o ponteiro chegar perto: um diagrama coberto de botões é um
 * diagrama que não se lê. */
.fz-inserir {
  cursor: pointer;
}

.fz-inserir-alvo {
  fill: hsl(var(--background));
  stroke: hsl(var(--muted-foreground) / 0.5);
  stroke-width: 1;
  opacity: 0.45;
  transition: opacity 0.12s ease-out;
}

.fz-inserir-cruz {
  stroke: hsl(var(--muted-foreground));
  stroke-width: 1.6;
  stroke-linecap: round;
  opacity: 0.45;
  transition: opacity 0.12s ease-out;
}

.fz-inserir:hover .fz-inserir-alvo,
.fz-inserir-aberto .fz-inserir-alvo {
  opacity: 1;
  fill: hsl(var(--primary));
  stroke: hsl(var(--primary));
}

.fz-inserir:hover .fz-inserir-cruz,
.fz-inserir-aberto .fz-inserir-cruz {
  opacity: 1;
  stroke: hsl(var(--primary-foreground));
}

/* O menu de tipos que o "+" abre. Fica na RAIZ do componente, e não dentro da
 * tela: a tela tem overflow:hidden para o SVG não vazar, e um menu criado lá
 * dentro seria cortado ao pé do diagrama. */
.fz-canvas-menu {
  position: absolute;
  z-index: 30;
  min-width: 11rem;
  border: 1px solid hsl(var(--border));
  border-radius: 0.5rem;
  background-color: hsl(var(--popover));
  color: hsl(var(--popover-foreground));
  padding: 0.25rem;
  box-shadow: 0 10px 25px -5px rgb(0 0 0 / 0.2);
}

.fz-canvas-menu button {
  display: block;
  width: 100%;
  border-radius: 0.375rem;
  padding: 0.375rem 0.5rem;
  text-align: left;
  font-size: 0.8125rem;
  background-color: transparent;
}

.fz-canvas-menu button:hover {
  background-color: hsl(var(--accent));
  color: hsl(var(--accent-foreground));
}

.fz-canvas-menu small {
  display: block;
  color: hsl(var(--muted-foreground));
  font-size: 0.6875rem;
}

/* ------------------------------------------------- a moldura e a legenda -- */

/* A faixa de aviso. Fica POR CIMA do desenho e não no lugar dele: quase todo
 * aviso aqui é sobre algo que o autor precisa ver enquanto olha o diagrama. */
.fz-canvas-aviso {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin: 0.5rem;
  border-radius: 0.375rem;
  background-color: hsl(38 92% 50% / 0.12);
  color: hsl(var(--foreground));
  padding: 0.5rem;
  font-size: 0.75rem;
  line-height: 1.35;
}

.fz-canvas-painel {
  align-self: start;
  max-height: 30rem;
  overflow-y: auto;
}

.fz-legenda-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.75rem;
  color: hsl(var(--muted-foreground));
}

.fz-legenda-forma {
  width: 2.75rem;
  height: 1.625rem;
  flex: none;
}

.fz-canvas-dica,
.fz-canvas-titulo {
  font-size: 0.8125rem;
  color: hsl(var(--muted-foreground));
}

.fz-canvas-dica {
  font-size: 0.6875rem;
  line-height: 1.35;
  margin-top: 0.5rem;
}

.fz-canvas-titulo {
  font-weight: 600;
  color: hsl(var(--foreground));
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: 0.6875rem;
}

.fz-canvas-campo {
  display: block;
  margin-top: 0.75rem;
  font-size: 0.8125rem;
}

.fz-canvas-campo > span {
  display: block;
  margin-bottom: 0.25rem;
  color: hsl(var(--muted-foreground));
}

.fz-canvas-campo-inline {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.fz-canvas-campo-inline > span {
  margin-bottom: 0;
}

.fz-canvas-campo input[type='text'],
.fz-canvas-campo select,
.fz-canvas-campo textarea {
  width: 100%;
  border: 1px solid hsl(var(--input));
  border-radius: 0.375rem;
  background-color: hsl(var(--background));
  padding: 0.375rem 0.5rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8125rem;
}

.fz-canvas-remover {
  margin-top: 1rem;
  width: 100%;
  border: 1px solid hsl(var(--destructive) / 0.4);
  border-radius: 0.375rem;
  padding: 0.375rem 0.5rem;
  font-size: 0.8125rem;
  color: hsl(var(--destructive));
  background-color: transparent;
}

.fz-canvas-remover:hover {
  background-color: hsl(var(--destructive) / 0.1);
}

/* Os botões da barra da tela (Enquadrar, Desfazer). */
.fz-canvas-barra {
  position: absolute;
  right: 0.5rem;
  bottom: 0.5rem;
  z-index: 10;
  display: flex;
  gap: 0.25rem;
}

.fz-canvas-barra button {
  border: 1px solid hsl(var(--border));
  border-radius: 0.375rem;
  background-color: hsl(var(--background) / 0.9);
  padding: 0.125rem 0.5rem;
  font-size: 0.6875rem;
  color: hsl(var(--muted-foreground));
}

.fz-canvas-barra button:hover {
  color: hsl(var(--foreground));
  background-color: hsl(var(--accent));
}

/* Duas classes que o construtor gráfico usa e o build congelado não tem — não
 * porque foram esquecidas, mas porque o SPA React, de onde o Tailwind purgou,
 * nunca teve um fluxograma. O `make css-check` reprova classe emitida sem
 * regra, então elas entram à mão, como manda a convenção. */
.p-0\.5 {
  padding: 0.125rem;
}

@media (min-width: 1024px) {
  /* paleta | tela | painel do bloco selecionado */
  .lg\:grid-cols-\[10rem_1fr_16rem\] {
    grid-template-columns: 10rem 1fr 16rem;
  }
}

/* O mapeamento de campos do marketplace alinha o rótulo à esquerda da própria
 * célula do grid. O build congelado não tem a classe porque o SPA React nunca
 * teve essa tela. */
.justify-self-start {
  justify-self: start;
}

/* O x-cloak do Alpine, que NÃO tinha regra nenhuma até aqui — e sem regra o
 * atributo não esconde coisa alguma. O editor de regras já o usava nas duas
 * abas (regras.templ), então até este commit as duas apareciam empilhadas
 * entre a carga do HTML e a hidratação do Alpine. O acordeão da ajuda tornaria
 * isso impossível de ignorar: doze tópicos abertos antes do primeiro clique. */
[x-cloak] {
  display: none !important;
}

/* O espaço do vídeo de cada tópico da ajuda. O build congelado não tem a
 * classe porque o SPA React nunca teve tela de ajuda. */
.aspect-video {
  aspect-ratio: 16 / 9;
}

/* O resumo de cada tópico da ajuda vive DENTRO do gatilho do acordeão, que o
 * shadcn deixa em font-medium. Sem desfazer isso, o subtítulo sai com o mesmo
 * peso do título e os dois se confundem. O build congelado não tem a classe
 * porque nenhuma tela do SPA React precisou desfazer um peso herdado. */
.font-normal {
  font-weight: 400;
}

/* Quatro utilitários do card do marketplace que o build congelado não tem — o
 * SPA React nunca teve essa tela. O mt-auto é o que alinha os rodapés entre
 * colunas: sem ele, um card com descrição de uma linha põe o botão Adquirir
 * acima do vizinho de duas linhas, e a grade fica serrilhada. */
.mt-auto {
  margin-top: auto;
}

.pt-3 {
  padding-top: 0.75rem;
}

/* ⚠️ AQUI MORAVA UMA CÓPIA DE `.data-[state=closed]:animate-out`, e ela era um
 * DEFEITO — a animação de saída de todo painel do portal saía errada por causa
 * dela. Não a traga de volta; a guarda é
 * TestTheExtraSheetDoesNotRedeclareTheAnimationVariables, em csscheck_test.go.
 *
 * O comentário que a acompanhava dizia que o build congelado só trouxera a
 * variante `data-[swipe=end]`. Isso é falso, e foi medido: o app.css traz
 * `.data-\[state\=closed\]\:animate-out[data-state=closed]` na posição CERTA da
 * cascata — antes de `fade-out-0`, `zoom-out-95`, `slide-out-to-left-1/2` e
 * `slide-out-to-top-[48%]`, que é o que faz esses quatro vencerem e escreverem
 * as variáveis `--tw-exit-*`.
 *
 * A cópia repetia o seletor com a MESMA especificidade (0,2,0) e devolvia as
 * quatro variáveis a `initial`. Empatada, ela vencia por ordem de arquivo — este
 * é o segundo <link> do base.templ —, e o `@keyframes exit` (que é `to`-only)
 * caía nos fallbacks de `var(--tw-exit-…, 0)`. O painel de um diálogo, que
 * repousa em `translate(-50%,-50%)`, animava até `translate(0,0)`: descia 50% da
 * própria altura e andava 50% da largura, em opacidade CHEIA, porque o
 * `fade-out-0` tinha sido zerado junto. Popover, tooltip e select perdiam o fade
 * e o zoom e sumiam secos.
 *
 * Nada disso falhava em lugar nenhum: o HTML é idêntico, o css-check é
 * unidirecional (classe emitida -> existe regra, nunca "a regra é a certa") e
 * golden não fotografa animação. */

/* O DIÁLOGO SOME AO FECHAR, em vez de sair animado. Painel e overlay, os dois.
 *
 * Consertada a cópia acima, o que sobra é a saída que o shadcn desenhou: 2% da
 * altura para baixo (de `translate-y-[-50%]` até o `--tw-exit-translate-y: -48%`
 * do `slide-out-to-top-[48%]`, porque o `@keyframes exit` é `to`-only e parte do
 * estado calculado), 5% de encolhimento e o fade. Num diálogo pequeno ninguém
 * repara; nos grandes do portal — o "Listar Inputs" tem 894px, o seletor de fuso
 * tem 1024 — o deslize é o que se vê, e ele é o que se pediu para tirar. Fechar
 * é fechar: some no quadro seguinte.
 *
 * ⚠️ Isto NÃO é o mesmo que apagar a cópia de cima. Aquilo devolveu o dono das
 * variáveis ao app.css e consertou popover, tooltip e select, que voltaram a
 * esmaecer. Esta regra é uma decisão de produto, e só sobre o diálogo.
 *
 * ⚠️ O gancho é `aria-modal`, e é de propósito: ele já é emitido por
 * `ui.DialogContent` e por `ui.AlertDialogContent`, então a regra não muda uma
 * linha de HTML. Uma classe marcadora nova seria mais explícita e custaria
 * SETENTA goldens regravados para não mudar comportamento nenhum.
 *
 * ⚠️ A regra alcança o OVERLAY também, e essa metade é a que passa despercebida:
 * ele é irmão do painel dentro do wrapper do x-show e tem `animate-out
 * fade-out-0` próprio. Apagando só a do painel, o painel some no ato e o fundo
 * escuro fica esmaecendo sozinho por 0,15s — pior que o defeito que se está
 * corrigindo. Como `DialogContent(id, class)` entrega o `class` ao painel e nada
 * ao overlay, o `:has()` no wrapper é o que chega nos dois sem mexer no
 * primitivo compartilhado. (`:has()` já é usado à mão aqui, nas regras do selo
 * do reCAPTCHA.)
 *
 * ⚠️ A ESPECIFICIDADE é o ponto todo, e já mordeu uma vez. O concorrente é
 * `.data-[state=closed]:animate-out[data-state=closed]` — classe MAIS atributo,
 * (0,2,0). Um `duration-300` do Tailwind é (0,1,0) e PERDE: medido no navegador,
 * ele ficou no HTML com o animation-duration computado continuando 0.15s. (É
 * pela mesma razão que o `duration-200` do `dialogContentClass` sempre foi
 * decorativo.) Este seletor é (0,2,1) — `div` (0,0,1) mais o argumento do
 * `:has()` (0,1,0) mais o atributo (0,1,0) — e ganha por MÉRITO, não por ordem
 * de arquivo. Ordem de arquivo foi exatamente o que produziu o defeito de cima.
 *
 * ⚠️ Quem transforma isto em "some no ato" é o hide() do fzDialog, que LÊ o
 * animation-duration computado: zero ali quer dizer desmontar agora. Com um
 * prazo fixo, o painel ficaria parado e inteiro na tela esperando uma animação
 * que não existe.
 *
 * A ENTRADA não é tocada — o seletor é [data-state='closed']. Abrir continua
 * com o fade e o zoom de sempre. */
div:has(> [aria-modal='true']) > [data-state='closed'] {
  animation: none;
}

.mt-3 {
  margin-top: 0.75rem;
}

/* O card do marketplace sobe 4px no hover. `-translate-y-1` existe no build
 * congelado só como variante `data-[side=top]:` (do posicionamento de popover),
 * nunca como utilitário solto — daí precisar da cadeia inteira de variáveis de
 * transform aqui, e não só do --tw-translate-y. */
.hover\:-translate-y-1:hover {
  --tw-translate-y: -0.25rem;
  transform: translate(var(--tw-translate-x), var(--tw-translate-y))
    rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y))
    scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

/* O badge do sino. Duas utilitárias que o build congelado não tem porque o SPA
 * React nunca teve a feature — e nenhuma classe existente serve no lugar:
 * o badge precisa ser um círculo de 20px com UM dígito e crescer para caber
 * "9+" com dois. `w-5` fixo cortaria o segundo caractere; `px-1` sozinho, sem
 * o piso de largura, deixaria o de um dígito oval em vez de redondo. */
.min-w-5 {
  min-width: 1.25rem;
}

.px-1 {
  padding-left: 0.25rem;
  padding-right: 0.25rem;
}

/* A landing empilha três seções com o mesmo respiro do herói, mas as duas de
 * baixo só precisam do espaço EMBAIXO — o de cima já veio da seção anterior.
 * `py-20` existe no build congelado; `pb-20` sozinho, não. */
.pb-20 {
  padding-bottom: 5rem;
}

/* ========================================================= a landing ======
 * A página pública do produto. Nada aqui passou pelo build congelado: o SPA
 * React não tinha porta de entrada nenhuma, então não há o que purgar nem o que
 * reaproveitar — é a página que mais introduz classe nova do projeto inteiro.
 *
 * As cores saem das variáveis do tema, nunca de valor fixo, pelo mesmo motivo
 * do construtor gráfico: a landing acompanha o claro e o escuro sem uma segunda
 * folha.
 *
 * Todo @keyframes daqui também entra no bloco de prefers-reduced-motion no fim
 * da seção. É o contrato deste arquivo, e uma página de marketing com fundo
 * pulsando é exatamente o que a preferência existe para desligar.
 */

/* ---------------------------------------- utilitários que faltavam ---------
 * Seis do build congelado. Cada um foi conferido contra o app.css antes de
 * entrar: o `md:grid-cols-2` e o `gap-6`, por exemplo, JÁ existiam e não estão
 * aqui. */
.mt-10 {
  margin-top: 2.5rem;
}

.mb-10 {
  margin-bottom: 2.5rem;
}

/* O pé de explicação do bloco de código alinha com o padding de 1.25rem do
 * <pre>, e `px-5` não existe no build congelado — só px-4 e px-6. */
.px-5 {
  padding-left: 1.25rem;
  padding-right: 1.25rem;
}

@media (min-width: 768px) {
  /* A navegação por âncora do cabeçalho, que só aparece a partir de md. */
  .md\:flex {
    display: flex;
  }
}

@media (min-width: 640px) {
  /* Os três mostradores do painel. */
  .sm\:grid-cols-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (min-width: 768px) {
  /* A largura de um widget de painel na faixa do meio (grade de 2 colunas).
   *
   * ⚠️ O `col-span-2` SEM prefixo existe no build congelado e seria a escolha
   * óbvia — e errada. A grade do painel é `grid-cols-1` no celular, e um item
   * que ocupa duas colunas onde só existe uma faz o navegador CRIAR a segunda
   * implicitamente: a grade inteira do celular sai com o dobro da largura e o
   * corpo passa a rolar de lado. O span precisa começar no breakpoint em que a
   * segunda coluna de fato existe. */
  .md\:col-span-2 {
    grid-column: span 2 / span 2;
  }
}

@media (min-width: 1024px) {
  /* O par "conteúdo largo + coluna de apoio" que repete em quatro seções. O
   * build congelado tem `lg:grid-cols-3` mas não o de 2, e tem `col-span-2`
   * só sem prefixo de breakpoint. */
  .lg\:grid-cols-2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .lg\:col-span-2 {
    grid-column: span 2 / span 2;
  }

  /* A largura cheia de um widget de painel: as três colunas da grade grande.
   * Na faixa do meio ele já vale 2, que é a largura cheia de lá. */
  .lg\:col-span-3 {
    grid-column: span 3 / span 3;
  }
}

/* -------------------------------------------------- a barra de topo -------
 * O `z-20` do cabeçalho. O build congelado só tem `z-10` e `z-50`: o 10 empata
 * com o `<main>` e perde o desempate por ordem de DOM — era esse o conteúdo
 * passando por cima da barra fixa —, e o 50 é a faixa dos diálogos e do toaster,
 * onde um cabeçalho não deve entrar. */
.z-20 {
  z-index: 20;
}

/* A margem de rolagem das seções ancoradas. Sem ela o link do menu leva a seção
 * para y=0, ou seja, para DEBAIXO da barra fixa: o título fica escondido e a
 * página parece ter rolado errado. O valor é a altura da barra (~4.5rem) mais
 * folga; o rootMargin do observador no fzCabecalho usa o mesmo desconto. */
.fz-ancora {
  scroll-margin-top: 5.5rem;
}

/* A barra descolada do topo. Fundo mais opaco e sombra: é o que a separa do
 * conteúdo que passa por baixo. A ordem de pintura já está correta pelo z-20 —
 * isto é o que faz a separação ser PERCEBIDA, que é coisa diferente.
 *
 * O seletor tem a mesma especificidade de `.bg-background\/80`, e vence porque o
 * app.extra.css é carregado depois do app.css. */
.fz-cabecalho {
  transition: box-shadow 0.2s ease, background-color 0.2s ease;
}

.fz-cabecalho-fixado {
  background-color: hsl(var(--background) / 0.92);
  box-shadow: var(--shadow-md);
}

/* O link da seção em cena. Quem acrescenta a classe é o fzCabecalho, no DOM —
 * um x-bind por link exigiria um getter por link, porque o build CSP do Alpine
 * não avalia expressão. */
.fz-nav-ativo {
  color: hsl(var(--foreground));
}

.fz-nav-ativo::after {
  content: '';
  display: block;
  height: 2px;
  margin-top: 0.25rem;
  border-radius: 9999px;
  background: hsl(var(--primary));
}

/* ------------------------------------------------- revelar ao rolar -------
 * A classe que ZERA a opacidade é `fz-revelar-armado`, e quem a acrescenta é o
 * fzRevelar no init(). Escrevê-la no HTML seria mais curto e seria um defeito:
 * um script bloqueado por extensão, uma falha de rede ou o Alpine que não sobe
 * deixariam a página INTEIRA invisível, sem nada indicando que havia conteúdo.
 * Deste jeito o pior caso é a página aparecer pronta, sem animação. */
@keyframes fz-revelar-in {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: none; }
}

.fz-revelar-armado {
  opacity: 0;
}

.fz-revelar-armado.fz-revelar-visivel {
  animation: fz-revelar-in 0.55s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
}

/* ----------------------------------------------------- fundo animado ------
 * Os dois borrões do fundoDecorativo, com deriva lenta. Duração longa e
 * deslocamento pequeno de propósito: o fundo tem de dar sinal de vida sem
 * puxar o olho para longe do texto.
 *
 * `will-change` está aqui porque são dois elementos com blur-3xl em área de
 * tela cheia — sem a promessa de transformação o compositor os repinta a cada
 * quadro. */
@keyframes fz-derivar {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  50%  { transform: translate3d(-3%, 2%, 0) scale(1.08); }
  100% { transform: translate3d(0, 0, 0) scale(1); }
}

.fz-blob-a,
.fz-blob-b {
  will-change: transform;
  animation: fz-derivar 24s ease-in-out infinite;
}

/* Meio ciclo de defasagem: em fase os dois inflam juntos e o fundo inteiro
 * parece respirar, que é chamativo demais. */
.fz-blob-b {
  animation-duration: 31s;
  animation-delay: -12s;
}

/* A malha de pontos atrás do herói. Dois gradientes, como faz a tela do
 * construtor gráfico — e com máscara radial, senão a grade encosta na borda da
 * seção e vira uma caixa desenhada em volta do texto. */
.fz-malha {
  background-image:
    radial-gradient(hsl(var(--muted-foreground) / 0.18) 1px, transparent 1px);
  background-size: 22px 22px;
  -webkit-mask-image: radial-gradient(ellipse 70% 60% at 50% 40%, #000 0%, transparent 100%);
  mask-image: radial-gradient(ellipse 70% 60% at 50% 40%, #000 0%, transparent 100%);
}

/* O escudo do herói, respirando. É o único elemento da página que anima sem
 * gatilho: está acima da dobra, onde não há rolagem para observar. */
@keyframes fz-pulsar {
  0%, 100% { box-shadow: 0 0 0 0 hsl(var(--primary) / 0.35); }
  50%      { box-shadow: 0 0 0 14px hsl(var(--primary) / 0); }
}

.fz-pulsar {
  animation: fz-pulsar 3.2s ease-out infinite;
}

/* O brilho no hover dos cards do marketplace. Usa --shadow-glow, que o tema
 * define nas duas variantes e que NENHUMA classe consumia até aqui. */
.fz-brilho {
  transition: var(--transition-smooth);
}

.fz-brilho:hover {
  box-shadow: var(--shadow-glow);
  border-color: hsl(var(--primary) / 0.4);
}

/* -------------------------------------------------- bloco de código ------
 * O <pre> da vitrine. As CORES não estão aqui: vêm do cm-fz-theme.css, cujas
 * regras `.cm-s-fz .cm-*` são seletores comuns e colorem os <span> que o
 * servidor emitiu, sem CodeMirror montado. Duplicá-las aqui criaria a segunda
 * paleta que o projeto evita.
 */
.fz-cod {
  margin: 0;
  padding: 1.25rem;
  overflow-x: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.8125rem;
  line-height: 1.7;
  tab-size: 2;
  /* A superfície é escura NAS DUAS temas, e isto é deliberado.
   *
   * As cores de token do cm-fz-theme.css são pastéis derivados da paleta ESCURA
   * do tema GitHub (está dito no cabeçalho daquele arquivo). No tema claro elas
   * caem sobre fundo quase branco e o contraste desaba — o rosa de `if` e o azul
   * da string ficam no limite do legível. O editor de regras tem esse mesmo
   * problema, porque o CodeMirror ali é `background: transparent`; é um defeito
   * preexistente dele, e consertá-lo é mexer no tema do editor, não na landing.
   *
   * O que não se pode é REPRODUZIR o defeito na página mais pública do produto.
   * Fixando a superfície no escuro, as cores caem no fundo para o qual foram
   * feitas, nos dois temas — e bloco de código escuro sobre página clara é a
   * convenção de qualquer documentação. O preço são os dois tokens abaixo, os
   * únicos que seguiam variável de tema. */
  color: hsl(213 31% 91%);
  background: hsl(222 47% 11%);
  /* Estica para ocupar a aba inteira, com o código apoiado no topo. O fundo
   * cobrindo toda a área é o que faz o painel parecer um editor em vez de um
   * trecho colado no meio de um card. */
  flex: 1 1 auto;
}

/* Os dois tokens que o tema escreve com variável — e que portanto viravam junto
 * com a página, invertendo-se contra a superfície agora fixa. No tema claro o
 * `cm-variable` ficava escuro sobre escuro: as variáveis sumiam do exemplo e
 * sobravam só as palavras-chave, o que é pior do que o contraste que se estava
 * consertando.
 *
 * O seletor precisa das DUAS classes do mesmo elemento. `.fz-cod .cm-variable`
 * empata em especificidade com o `.cm-s-fz .cm-variable` do tema, e o tema vence
 * o empate porque o cm-fz-theme.css é carregado DEPOIS — ele entra pelo Extras
 * da página, e o Extras vem abaixo das duas folhas da base no <head>. */
.fz-cod.cm-s-fz .cm-variable {
  color: hsl(213 31% 91%);
}

.fz-cod.cm-s-fz .cm-bracket {
  color: hsl(215 14% 65%);
}

/* As duas abas da seção de autoria têm a mesma altura — a do fluxograma, que é
 * quem precisa dela para o rótulo dentro das formas continuar legível. A coluna
 * flex é o que permite ao pé de explicação descer para a base com `mt-auto` em
 * vez de sobrar um vazio embaixo do código. */
.fz-aba-autoria {
  display: flex;
  flex-direction: column;
  min-height: 30rem;
}

/* O cursor do efeito de digitação. É um ::after do <code>, então acompanha o
 * fim do texto enquanto ele cresce — um elemento posicionado precisaria ser
 * movido a cada caractere. Fica piscando depois de terminar, de propósito: é
 * um editor, e um editor tem cursor. */
.fz-caret::after {
  content: '';
  display: inline-block;
  width: 0.5ch;
  height: 1.05em;
  vertical-align: text-bottom;
  background: hsl(var(--primary));
  animation: fz-piscar 1.1s step-end infinite;
}

@keyframes fz-piscar {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* A tela do fluxograma na landing herda `cursor: grab` do editor, e ali nada é
 * arrastável: o rule-canvas.js só se prende a [data-fz-canvas-raiz], que esta
 * página não emite. Um cursor de arrasto sobre um desenho parado é uma
 * afordância falsa. */
.fz-canvas-parado {
  height: 30rem;
}

.fz-canvas-parado .fz-canvas-svg {
  cursor: default;
}

/* ---------------------------------------------- gráfico e numeração ------
 * O gráfico de barras do painel simulado. A geometria vem do servidor (o SVG é
 * emitido pelo templ, não montado por JavaScript, para as classes ficarem sob o
 * make css-check); daqui sai só a cor e a subida.
 *
 * transform-origin na base é o que faz a barra CRESCER em vez de encolher a
 * partir do centro. O atraso por barra vem do style inline, calculado no
 * servidor — não há utilitário de delay no build congelado, e uma expressão no
 * HTML precisaria de eval. */
.fz-grafico {
  display: block;
  width: 100%;
  /* Casa com a altura do viewBox (160), então a escala vertical é 1:1. */
  height: 10rem;
}

/* O eixo ganha a linha de base do gráfico, e não uma borda própria: são a mesma
 * linha, e duas ficariam desalinhadas em algum zoom. */
.fz-eixo {
  padding-top: 0.5rem;
  border-top: 1px solid hsl(var(--border));
}

.fz-barra {
  fill: hsl(var(--primary) / 0.55);
  transform-origin: 50% 100%;
}

.fz-revelar-armado .fz-barra {
  transform: scaleY(0);
}

.fz-revelar-armado.fz-revelar-visivel .fz-barra {
  transform: scaleY(1);
  transition: transform 0.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* O número de cada passo da lista "Como o grupo avalia". É o mesmo círculo do
 * `.fz-passo` do fluxograma, em HTML em vez de SVG — as duas listas falam de
 * passos numerados e devem parecer a mesma coisa. */
.fz-passo-numero {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 9999px;
  border: 1px solid hsl(var(--muted-foreground) / 0.6);
  background: hsl(var(--background));
  font-size: 0.625rem;
  font-weight: 700;
  color: hsl(var(--foreground));
}

/* ------------------------------------------------ trilha de auditoria ----
 * A linha do tempo simulada. O fio vertical é uma borda no ::before, e não um
 * elemento à parte, para não precisar de um <span> por linha só para desenhar
 * um traço. */
.fz-trilha-linha {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.5rem 0 0.5rem 0.25rem;
}

.fz-trilha-linha:not(:last-child)::before {
  content: '';
  position: absolute;
  left: 0.5rem;
  top: 1.1rem;
  bottom: -0.4rem;
  width: 1px;
  background: hsl(var(--border));
}

.fz-trilha-linha > p {
  flex: 1 1 auto;
  min-width: 0;
}

.fz-trilha-ponto {
  position: relative;
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  margin-top: 0.35rem;
  border-radius: 9999px;
  background: hsl(var(--primary));
  box-shadow: 0 0 0 3px hsl(var(--background));
}

/* O framer-motion respeitava a preferência sozinho; CSS não respeita. Cada
 * animação da landing precisa aparecer aqui, ou a página passa a ter uma
 * regressão de acessibilidade que o portal não tinha.
 *
 * As duas primeiras regras não são só `animation: none`: o fzRevelar já
 * desiste sozinho quando a preferência está ligada, mas o CSS não pode
 * DEPENDER disso — se o JavaScript não rodar, o `armado` nunca sai e a página
 * ficaria invisível. */
@media (prefers-reduced-motion: reduce) {
  .fz-revelar-armado,
  .fz-revelar-armado.fz-revelar-visivel {
    animation: none;
    opacity: 1;
  }

  .fz-revelar-armado .fz-barra,
  .fz-revelar-armado.fz-revelar-visivel .fz-barra {
    transform: none;
    transition: none;
  }

  .fz-blob-a,
  .fz-blob-b,
  .fz-pulsar,
  .fz-caret::after {
    animation: none;
  }

  .fz-brilho,
  .fz-cabecalho {
    transition: none;
  }
}

/* ------------------------------------------- o gráfico da telemetria -----
 * A série por hora do /telemetry. A geometria vem do servidor (ui.BuildSpark),
 * pelas mesmas duas razões do fluxograma e do gráfico da landing; daqui sai só
 * a cor.
 *
 * preserveAspectRatio="none" no SVG faz o desenho esticar na largura do card e
 * manter a altura fixa daqui — é o que dispensa qualquer JavaScript de
 * responsividade. */
.fz-spark {
  display: block;
  width: 100%;
  height: 8rem;
}

/* As três faixas empilhadas. A aprovada reusa o .fz-barra do gráfico da
 * landing — é a mesma ideia de "volume normal" e deve ter a mesma cor. */
.fz-barra-negada {
  fill: hsl(var(--destructive) / 0.75);
}

/* O risco MÉDIO fica entre o baixo (a cor da landing) e o alto (destructive):
 * âmbar, a mesma família do selo da faixa na lista de avaliações. */
.fz-barra-media {
  fill: hsl(38 92% 50% / 0.55);
}

.fz-barra-erro {
  fill: hsl(38 92% 50% / 0.8);
}

/* A hora SEM tráfego. Ela precisa aparecer: sem o traço, "não passou nada" e
 * "esta hora não existe" ficariam graficamente idênticos, e a série passaria a
 * mentir sobre o tempo. */
.fz-spark-vazia {
  fill: hsl(var(--muted-foreground) / 0.35);
}

.fz-spark-eixo {
  position: relative;
  height: 1.25rem;
  margin-top: 0.25rem;
}

.fz-spark-rotulo {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  color: hsl(var(--muted-foreground));
  font-size: 0.75rem;
  line-height: 1rem;
  white-space: nowrap;
}

/* O quadradinho de cor da legenda. É a legenda que NOMEIA as faixas: o <title>
 * de cada barra sai da geometria, que é pura e não traduz. */
.fz-legenda {
  display: inline-block;
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 2px;
}

.fz-legenda-aprovada {
  background: hsl(var(--primary) / 0.55);
}

.fz-legenda-media {
  background: hsl(38 92% 50% / 0.55);
}

.fz-legenda-negada {
  background: hsl(var(--destructive) / 0.75);
}

.fz-legenda-erro {
  background: hsl(38 92% 50% / 0.8);
}

/* ------------------------------------------ o medidor de uso do host -----
 * A barra de CPU, memória e disco da telemetria. A largura vem de um style
 * inline calculado no servidor: o valor é contínuo, o build congelado só tem as
 * larguras discretas do Tailwind, e uma expressão no HTML precisaria de eval —
 * que a CSP não concede. */
.fz-medidor {
  height: 0.375rem;
  border-radius: 9999px;
  background: hsl(var(--muted));
  overflow: hidden;
}

.fz-medidor-preenchido {
  height: 100%;
  border-radius: 9999px;
  background: hsl(var(--primary));
  transition: width 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* ------------------------------------------ o log do painel (Sistema) -----
 * A caixa rolante da seção Sistema da telemetria. Ela precisa de altura fixa:
 * o fragmento é substituído a cada 5s, e uma lista que cresce sem teto empurra
 * o resto da página para baixo a cada atualização.
 *
 * Escrita à mão porque o build congelado do Tailwind não tem `max-h-[420px]` —
 * é variante arbitrária, e não há Tailwind aqui para gerá-la. */
.fz-log {
  max-height: 26rem;
  overflow-y: auto;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
  font-size: 0.75rem;
  line-height: 1.5;
}

.fz-log-linha {
  display: flex;
  gap: 0.75rem;
  padding: 0.25rem 0.75rem;
  border-bottom: 1px solid hsl(var(--border) / 0.5);
}

.fz-log-linha:last-child {
  border-bottom: 0;
}

/* Largura fixa para os níveis alinharem: DEBUG tem cinco letras e INFO tem
 * quatro, e sem a coluna a mensagem de cada linha começa num lugar diferente —
 * o que torna a lista ilegível justamente quando ela está cheia. */
.fz-log-nivel {
  flex: 0 0 3.25rem;
  font-weight: 600;
}

/* A mensagem quebra; o resto não. `break-word` porque um valor de atributo pode
 * ser uma URL ou um stack sem espaço nenhum, e sem isso ele estouraria a caixa
 * na horizontal. */
.fz-log-msg {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Recuo das opções de uma seção do relatório, para elas se alinharem com o
 * TÍTULO da seção em vez de com a borda do bloco.
 *
 * 1.75rem é exatamente a soma do checkbox (h-4 = 1rem) com o gap-3 (0.75rem)
 * que os separa — o mesmo cálculo que o `pl-10` do campo de busca faz com o
 * ícone. O build congelado do Tailwind tem `ml-1` e `ml-2` e mais nada, então
 * esta regra entra à mão: não há Tailwind para regenerar. */
.ml-7 {
  margin-left: 1.75rem;
}

/* ------------------------------------------ o selo do reCAPTCHA v3 --------
 * O selo flutuante NÃO é nosso: quem o cria é o `api.js` do Google, num
 * `<div class="grecaptcha-badge">` dentro de um `<div>` anônimo pendurado no
 * fim do `<body>`, com os estilos escritos INLINE. Estas são as duas únicas
 * regras do repositório sobre ele.
 *
 * ⚠️ O `!important` aqui não é preguiça: o concorrente é o atributo `style` do
 * próprio elemento, e nenhuma especificidade de seletor ganha de um estilo
 * inline. É o caso para o qual `!important` existe.
 *
 * ⚠️ E o combinador é DESCENDENTE, nunca `>`. O selo é neto do `<body>`, não
 * filho — medido no navegador, `.grecaptcha-badge < div < body`. Com `>` a
 * regra não casa com nada, e o modo de falha é silencioso: a folha carrega, o
 * seletor não bate, o selo fica onde estava e nada em lugar nenhum acusa.
 *
 * As duas regras se cobrem, e é de propósito:
 *
 *  - o `z-index` resolve QUEM PINTA POR CIMA. O selo do Google não declara
 *    z-index nenhum (medido: `auto`), e o header, o `<main>` e o `<footer>` das
 *    telas públicas são todos `relative z-10` — o selo empata em `auto` e
 *    perde, que é como ele foi parar ATRÁS da barra translúcida do rodapé. 20 é
 *    a faixa da barra de topo da landing: acima do conteúdo, abaixo dos
 *    diálogos (50) e do toaster (100). Ele é o que segura o estrago se a folga
 *    abaixo um dia não bastar — o selo fica na frente do rodapé em vez de
 *    sumir atrás dele.
 *  - o `bottom` resolve ONDE ELE FICA. O padrão do Google é 14px, que cai
 *    dentro do rodapé; estes valores o põem logo ACIMA dele.
 *
 * ⚠️ O `:has(footer)` é o que limita a subida a quem tem rodapé. Das três telas
 * com captcha — cadastro, login e esqueci-a-senha —, só o cadastro tem um. Sem
 * ele, o selo subiria também no login, onde nada o encobre, e entraria por cima
 * do cartão sem motivo.
 *
 * ⚠️ Os 640px são contrato com o `sm:` do Tailwind, que é onde o
 * `flex-col sm:flex-row` do rodapé para de empilhar. Os dois valores saem de
 * medição, não de conta (pt e es dão o mesmo número): o rodapé tem 125px a
 * 320px de largura, 105px até 639px, 73px entre 640 e ~699 — onde o copyright
 * ainda quebra linha — e 53px daí para cima. Cada valor é o pior caso da sua
 * faixa mais ~15px de folga, que é a mesma distância que o Google usa para a
 * borda da janela. Mexer no rodapé sem mexer aqui devolve a sobreposição, e o
 * `css-check` não alcança isto: ele cobra "classe emitida -> regra existe", e
 * esta classe nós nunca emitimos. */
.grecaptcha-badge {
  z-index: 20 !important;
}

body:has(footer) .grecaptcha-badge {
  bottom: 8.75rem !important;
}

@media (min-width: 640px) {
  body:has(footer) .grecaptcha-badge {
    bottom: 5.5rem !important;
  }
}

/* ------------------------------------------------ seletor de fuso horário ---
 * As classes do mapa. Escritas à mão porque o `make css-check` é UNIDIRECIONAL
 * e cego a JavaScript: o fuso.js cria o ponto e injeta o SVG, e nada reclamaria
 * se estas regras não existissem — a tela sairia sem estilo, só isso.
 *
 * ⚠️ A proporção é 2,5:1 e é CONTRATO com tools/worldmap: o viewBox do mapa é
 * "12 6 360 144", e o SVG é preserveAspectRatio="none" — a caixa e o mapa TÊM
 * de ser a mesma coisa, porque é isso que faz a conta ponteiro -> longitude do
 * fuso.js fechar. Divergindo, o desenho é esticado e a dica aponta para o lugar
 * errado, e nada falha.
 *
 * O CSS não tem como ler um viewBox, então este número é literal por força.
 * Quem guarda o contrato é TestOMapaEOCSSConcordamNaProporcao, em
 * internal/portal, que lê os dois arquivos e compara.
 */
/* A regra que apaga a animação de saída dos diálogos mora lá em cima, junto do
 * resto do assunto de animação — ela deixou de ser específica deste painel. */

.fz-fuso-mapa {
  aspect-ratio: 360 / 144;
  width: 100%;
  /* ⚠️ Teto de altura por MAX-WIDTH, e nunca por max-height.
   *
   * O diálogo não tem altura máxima: com o mapa livre, a soma (cabeçalho +
   * busca + lista + mapa + padding) passava dos ~750px úteis de um laptop de
   * 900px e as bordas do painel ficavam fora da tela. Um `max-height` aqui
   * cortaria a altura e deixaria a largura em 100% — a proporção quebra, e com
   * preserveAspectRatio="none" isso é literalmente o mapa esticado que este
   * arquivo existe para evitar. Limitando a LARGURA, a altura vem junto pela
   * aspect-ratio e o desenho continua fiel.
   *
   * O 2.5 é a mesma proporção do viewBox, e cai sob a mesma guarda. */
  max-width: calc(42vh * 2.5);
  margin-inline: auto;
  /* A "água". Um tom próprio, e não o bg-muted da caixa: é o contraste contra a
   * terra que faz o mapa ser legível, e ele precisa existir nos dois temas. */
  background: hsl(var(--muted) / 0.5);
}

.fz-fuso-svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* A terra e as fronteiras.
 *
 * ⚠️ São DUAS cores, e precisam ser: o <path> sai do gerador com
 * fill="currentColor" e stroke="currentColor", e enquanto os dois resolviam
 * para o mesmo `color` a divisória existia no SVG e era invisível na tela —
 * uma mancha cinza sem separação nenhuma entre países. Medido no navegador.
 *
 * ⚠️ A opacidade do preenchimento foi medida na tela, não escolhida no escuro:
 * a 0.35 a terra e a água ficavam quase do mesmo tom no tema escuro, e o mapa
 * lia como uma mancha. O contraste tem de vir daqui e não de uma cor fixa,
 * senão um dos dois temas fica errado.
 *
 * A largura de 0.8px é a que sobrevive: a 1px a Europa vira um borrão de
 * linhas, e abaixo de 0.6 a fronteira some no antialiasing. Ela só se comporta
 * assim porque o <path> traz vector-effect="non-scaling-stroke" — em unidades
 * do viewBox, a mesma linha que fica fina numa caixa de 800px engrossaria numa
 * de 1100. */
.fz-fuso-svg path {
  fill: hsl(var(--muted-foreground) / 0.55);
  stroke: hsl(var(--muted-foreground) / 0.9);
  stroke-width: 0.8px;
}

/* Os alfinetes das zonas são <circle> DO SVG, e não <div> por cima: o que se
 * estiliza é `fill`, nunca `background`. pointer-events: none porque sem isso o
 * mousemove da caixa morre ao passar por cima de um deles e a dica pisca. */
.fz-fuso-ponto {
  fill: hsl(var(--muted-foreground) / 0.55);
  stroke: none;
  pointer-events: none;
  transition: fill 120ms ease-out;
}

/* O alfinete sob o ponteiro. É o que dá o que confirmar quando a escolha
 * acontece — ver BATIDA_ANTES_DE_FECHAR no fuso.js. O raio não muda: mexer em
 * geometria a cada mousemove é o caminho que já custou caro neste componente,
 * e a cor sozinha resolve. */
.fz-fuso-ponto-ativo {
  fill: hsl(var(--primary));
}

.fz-fuso-dica {
  position: absolute;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  margin: -0.5rem 0 0 0.75rem;
  padding: 0.5rem 0.625rem;
  border-radius: var(--radius);
  background: hsl(var(--popover));
  color: hsl(var(--popover-foreground));
  border: 1px solid hsl(var(--border));
  box-shadow: 0 4px 12px hsl(0 0% 0% / 0.15);
  font-size: 0.75rem;
  line-height: 1.2;
  white-space: nowrap;
  pointer-events: none;
}

/* Perto da borda direita a dica troca de lado, senão ela sai da caixa nos fusos
 * do Pacífico — que são justamente os que ficam no canto. */
.fz-fuso-dica-esquerda {
  transform: translateX(-100%);
  margin-left: -0.75rem;
}

.fz-fuso-hora {
  color: hsl(var(--muted-foreground));
  font-variant-numeric: tabular-nums;
}

.fz-fuso-item:hover,
.fz-fuso-item[aria-selected='true'] {
  background: hsl(var(--accent));
  color: hsl(var(--accent-foreground));
}

/* O aviso de compatibilidade do diálogo de grupo.
 *
 * ⚠️ As classes têm NOME PRÓPRIO em vez de serem utilitárias do Tailwind, e não
 * é preferência: o build CSS é congelado e purgado, e `border-amber-500/40`,
 * `text-amber-700` e `pl-6` simplesmente não estão nele. Escrever à mão uma
 * regra nomeada diz o que ela é; escrever à mão `.pl-6 { padding-left: 1.5rem }`
 * seria remendar o Tailwind fora do Tailwind, e a próxima pessoa acharia que
 * dava para usar qualquer utilitário. */
.fz-aviso {
  border: 1px solid hsl(38 92% 50% / 0.4);
}

.fz-aviso-titulo {
  color: hsl(31 81% 39%);
}

.fz-aviso-lista {
  padding-left: 1.5rem;
}

/* O contêiner do de-para. A regra existe também para a classe casar o
 * css-check: ele exige que TODA classe emitida tenha regra, e as de
 * `.fz-mapa > summary` abaixo não contam pela classe do pai. */
.fz-mapa {
  background: hsl(var(--background));
}

/* O botão de REMOVER uma solicitação, empurrado para a ponta esquerda do rodapé
 * do diálogo — longe de "Aprovar", que é o vizinho de um clique de distância.
 *
 * ⚠️ Nome próprio em vez do `mr-auto` do Tailwind, pela mesma razão do
 * .fz-aviso acima: o build é congelado e purgado, e `mr-auto` não está nele.
 * Escrever `.mr-auto { margin-right: auto }` à mão seria remendar o Tailwind
 * fora do Tailwind, e a próxima pessoa suporia que qualquer utilitário serve. */
.fz-acao-a-esquerda {
  margin-right: auto;
}

/* A CHECAGEM AO VIVO DA SENHA (view/page/password_strength.templ).
 *
 * ⚠️ Todas as classes têm NOME PRÓPRIO, pela mesma razão do .fz-aviso acima: o
 * build Tailwind é congelado e purgado, e nem `w-1/3`, nem `bg-amber-500`, nem
 * `text-emerald-500` estão nele.
 *
 * ⚠️ E o estado é ATRIBUTO, não classe. Quem o escreve é o senha.js, e o
 * `make css-check` lê o HTML que o templ produz — uma classe acrescentada por
 * JavaScript passaria por fora da guarda e sairia sem regra, em silêncio. */
.fz-forca-trilha {
  flex: 1 1 auto;
  height: 0.375rem;
  border-radius: 9999px;
  background: hsl(var(--muted));
  overflow: hidden;
}

.fz-forca-barra {
  height: 100%;
  width: 0;
  border-radius: 9999px;
  background: hsl(var(--muted-foreground));
  transition: width 0.2s ease, background-color 0.2s ease;
}

.fz-forca-rotulo {
  color: hsl(var(--muted-foreground));
  min-width: 4.5rem;
  text-align: right;
}

/* Os três degraus. Ver nivelDe(), no senha.js: o medidor mede o quanto falta
 * para a senha ser ACEITÁVEL, e não uma entropia — o degrau cheio é a política
 * inteira cumprida, e não um palpite sobre quanto ela resiste. */
.fz-forca[data-fz-nivel='1'] .fz-forca-barra {
  width: 33%;
  background: hsl(var(--destructive));
}

.fz-forca[data-fz-nivel='1'] .fz-forca-rotulo {
  color: hsl(var(--destructive));
}

.fz-forca[data-fz-nivel='2'] .fz-forca-barra {
  width: 66%;
  background: hsl(38 92% 50%);
}

.fz-forca[data-fz-nivel='2'] .fz-forca-rotulo {
  color: hsl(31 81% 39%);
}

.fz-forca[data-fz-nivel='3'] .fz-forca-barra {
  width: 100%;
  background: hsl(142 71% 45%);
}

.fz-forca[data-fz-nivel='3'] .fz-forca-rotulo {
  color: hsl(142 71% 36%);
}

/* Cada requisito emite os DOIS ícones e o CSS esconde o que não vale: trocar o
 * nó por JavaScript exigiria montar SVG no cliente, e os ícones do portal são
 * componentes templ.
 *
 * O estado de PARTIDA é "por cumprir", e é ele que sobrevive ao JavaScript
 * morto — mesmo desenho do fz-revelar-armado da landing. */
.fz-regra {
  color: hsl(var(--muted-foreground));
}

.fz-regra .fz-regra-sim {
  display: none;
}

.fz-regra[data-fz-ok='1'] {
  color: hsl(142 71% 36%);
}

.fz-regra[data-fz-ok='1'] .fz-regra-sim {
  display: inline-block;
}

.fz-regra[data-fz-ok='1'] .fz-regra-nao {
  display: none;
}

/* A zona de upload de CSV, enquanto um arquivo é arrastado por cima dela.
 *
 * O atributo é escrito pelo formulario.js; a CLASSE nunca é — o make css-check
 * lê o HTML que o templ produz e não enxerga o que o JavaScript acrescenta, e
 * uma classe de lá sairia do build congelado sem regra nenhuma, em silêncio.
 *
 * As cores repetem o que `hover:border-primary/50 hover:bg-muted/50` já dá à
 * zona: arrastar por cima tem de parecer o mesmo estado que passar o mouse por
 * cima, senão o realce lê como erro em vez de convite. O `border-primary` cheio
 * (e não /50) é a diferença deliberada — é o único sinal de que o arquivo pode
 * ser SOLTO ali, e não só que o cursor passou. */
.fz-zona-upload[data-fz-arrastando] {
  border-color: hsl(var(--primary));
  background-color: hsl(var(--muted) / 0.5);
}

/* ------------------------------------------------- tela estreita (celular) --
 * O portal foi portado de um SPA desenhado para desktop, e o build congelado
 * do Tailwind só traz as variantes responsivas que o texto-fonte do React usava
 * — quase nenhuma. Tudo aqui é @media (max-width: …): em desktop nenhuma
 * destas regras se aplica, e é isso que mantém a baseline de paridade de
 * pixels (178 fotos que não podem ser regeradas) intacta.
 *
 * O que se corrige foi MEDIDO, não suposto: em 400px de largura o cluster da
 * direita do cabeçalho tinha 405px sozinho, o documento saía com 603px e toda
 * página autenticada rolava de lado — os cards do lançador cortados na borda.
 *
 * As classes `fz-topo*` e `fz-conteudo` são ganchos escritos no shell.templ
 * para as regras não dependerem da ordem das classes utilitárias do Tailwind,
 * que mudam quando alguém troca uma cor. */
@media (max-width: 1023px) {
  /* O chip do usuário vira só o avatar (o nome fica no title); o seletor de
   * conta vira só o ícone. Os dois textos são o que mais pesava no cluster.
   *
   * Até 1023px, e não até 767: numa tela com botão de ação no cabeçalho
   * ("Voltar para Inputs", "Atualizar") o cluster media 530px, e somado à
   * marca passava dos 768px de um tablet em pé por ~30px. A baseline de
   * pixels é medida a 1280px, fora desta faixa. */
  .fz-topo-usuario-texto,
  .fz-topo-conta-nome {
    display: none;
  }

  .fz-topo-usuario {
    padding-left: 0.375rem;
    padding-right: 0.375rem;
    padding-top: 0.375rem;
    padding-bottom: 0.375rem;
  }

  .fz-topo-conta {
    max-width: none;
  }

  .fz-topo-marca h1 {
    font-size: 1.25rem;
    line-height: 1.75rem;
  }
}

@media (max-width: 639px) {
  .fz-topo {
    padding-left: 1rem;
    padding-right: 1rem;
    padding-top: 0.75rem;
    padding-bottom: 0.75rem;
  }

  /* O cluster de ações desce para uma segunda linha, encostado à direita.
   * `margin-left: auto` é o que o mantém à direita quando ele é o único item
   * da linha — o `justify-between` do pai só distribui entre dois. */
  .fz-topo-linha {
    flex-wrap: wrap;
    row-gap: 0.5rem;
  }

  .fz-topo-acoes {
    margin-left: auto;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 0.25rem;
  }

  /* Medido a 400px: com o botão de ação da página ("Voltar para Inputs"),
   * o sino, o idioma, o avatar e o sair, o cluster passava de 368px e o
   * "sair" caía sozinho numa terceira linha. Os botões de ícone perdem 4px
   * de cada lado e o chip do avatar perde a borda de respiro: cabe em uma. */
  .fz-topo-acoes > button.h-10.w-10,
  .fz-topo-acoes > a.h-10.w-10,
  .fz-topo-acoes form > button.h-10.w-10 {
    height: 2.25rem;
    width: 2.25rem;
  }

  .fz-topo-usuario {
    padding: 0.125rem;
  }

  .fz-topo-marca {
    gap: 0.75rem;
    min-width: 0;
  }

  .fz-topo-marca p {
    display: none;
  }

  .fz-conteudo {
    padding-left: 1rem;
    padding-right: 1rem;
    padding-top: 1.5rem;
    padding-bottom: 1.5rem;
  }
}

@media (max-width: 639px) {
  /* Os formulários dos diálogos são grades de duas a quatro colunas
   * (`grid-cols-2` para "ID | Nome", `grid-cols-3` para as linhas de vínculo,
   * as arbitrárias para as linhas de campo e coluna). Num painel de ~350px
   * isso dá 100px por campo, com o valor de um <select> truncado na terceira
   * letra. Abaixo de sm tudo vira uma coluna — a mesma coisa que o
   * `grid-cols-1 sm:grid-cols-2` do shadcn faria, se o build congelado
   * tivesse as variantes.
   *
   * Só `div`: a <dl> de "rótulo | valor" dos diálogos de detalhe cabe em duas
   * colunas e lê melhor assim. E só dentro de um diálogo: as grades de página
   * já carregam os próprios breakpoints. */
  [role="dialog"] div.grid-cols-2,
  [role="dialog"] div.grid-cols-3,
  [role="dialog"] div.grid-cols-4,
  [role="dialog"] div.grid-cols-5,
  [role="dialog"] div.grid-cols-\[1fr_1fr_1fr_1fr_auto\],
  [role="dialog"] div.grid-cols-\[1fr_1fr_1fr_auto\],
  [role="dialog"] div.grid-cols-\[minmax\(0\,14rem\)_1fr\] {
    grid-template-columns: repeat(1, minmax(0, 1fr));
  }

  /* O painel do diálogo no celular: sem o `p-6` de desktop sobra mais para o
   * conteúdo, e o `max-w-*` deixa de importar porque a largura é a da tela. */
  [aria-modal="true"] {
    padding: 1rem;
  }
}

/* ------------------------------------------------------------ tooltip --
 * O tooltip de todo botão sem texto: `data-tooltip` é a frase, e o CSS a
 * desenha ao passar o mouse e ao focar pelo teclado. Sem JavaScript, sem
 * wrapper, sem estado — e portanto sem o nó órfão que o tooltip de Alpine
 * deixava a cada linha de tabela trocada pelo htmx. Quem o escreve é o
 * ui.Button (buttonAttrs), a partir do aria-label que o botão já tinha.
 *
 * As cores são as do popover, para casar com o ui.Tooltip que ainda existe.
 * Em cima por padrão; `data-tooltip-side="bottom"` para o cabeçalho, que não
 * tem espaço acima, e para toda linha de tabela, onde a primeira linha seria
 * cortada pelo overflow do contêiner. */
/* ⚠️ `:not(.absolute):not(.fixed)`, e não o seletor nu: com a mesma
 * especificidade de `.absolute` e vindo DEPOIS do app.css, o `relative` vencia
 * e tirava do lugar todo botão posicionado que tivesse dica — o X de todo
 * diálogo (`absolute right-4 top-4`) caía no fluxo do grid, no rodapé. */
[data-tooltip]:not(.absolute):not(.fixed) {
  position: relative;
}

/* Com o overlays.js de pé, a dica é o #fz-dica flutuante e o `::after` sai. */
html[data-fz-dicas] [data-tooltip]::after {
  display: none;
}

#fz-dica {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 100;
  max-width: 18rem;
  width: max-content;
  padding: 0.375rem 0.75rem;
  border-radius: 0.375rem;
  border: 1px solid hsl(var(--border));
  background: hsl(var(--popover));
  color: hsl(var(--popover-foreground));
  box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
  font-size: 0.75rem;
  line-height: 1rem;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 120ms ease;
}

#fz-dica[data-aberta] {
  opacity: 1;
  visibility: visible;
}

[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  left: 50%;
  bottom: calc(100% + 0.375rem);
  transform: translate(-50%, 0.25rem);
  z-index: 60;
  max-width: 18rem;
  width: max-content;
  padding: 0.375rem 0.75rem;
  border-radius: 0.375rem;
  border: 1px solid hsl(var(--border));
  background: hsl(var(--popover));
  color: hsl(var(--popover-foreground));
  box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
  font-size: 0.75rem;
  line-height: 1rem;
  font-weight: 400;
  text-align: center;
  white-space: normal;
  opacity: 0;
  pointer-events: none;
  transition: opacity 150ms ease, transform 150ms ease;
}

[data-tooltip]:hover::after,
[data-tooltip]:focus-visible::after {
  opacity: 1;
  transform: translate(-50%, 0);
}

[data-tooltip-side="bottom"]::after,
table [data-tooltip]::after {
  bottom: auto;
  top: calc(100% + 0.375rem);
  transform: translate(-50%, -0.25rem);
}

[data-tooltip-side="bottom"]:hover::after,
[data-tooltip-side="bottom"]:focus-visible::after,
table [data-tooltip]:hover::after,
table [data-tooltip]:focus-visible::after {
  transform: translate(-50%, 0);
}

/* Um botão desabilitado não recebe eventos de mouse (disabled:pointer-events-
 * none), então o tooltip dele nunca abriria: nada a desenhar. */
[data-tooltip]:disabled::after {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-tooltip]::after {
    transition: none;
  }
}

/* ------------------------------------------------------ carregando --
 * A barra fina no topo da página enquanto há requisição do htmx em voo. Quem
 * liga e desliga a classe é static/js/fz/carregamento.js; polling não conta. */
html.fz-carregando::before {
  content: "";
  position: fixed;
  top: 0;
  left: 0;
  height: 2px;
  width: 30%;
  z-index: 100;
  background: hsl(var(--primary));
  box-shadow: 0 0 8px hsl(var(--primary) / 0.6);
  animation: fz-carregando 1s ease-in-out infinite;
}

@keyframes fz-carregando {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(400%); }
}

@media (prefers-reduced-motion: reduce) {
  html.fz-carregando::before {
    animation: none;
    width: 100%;
  }
}

/* Botão travado pelo anti-duplo-envio: o mesmo aspecto do disabled do shadcn,
 * que já vem das classes do botão — nada a acrescentar além do cursor. */
button[aria-busy="true"] {
  cursor: progress;
}

/* Texto de quem escreveu num textarea: as quebras de linha são dele. O build
 * congelado não traz whitespace-pre-line. */
.fz-quebra-linhas {
  white-space: pre-line;
}

/* Duas classes da grade que o app.css congelado não trouxe, escritas à mão
 * como o Tailwind as escreveria: a página da avaliação põe a árvore numa
 * coluna e o fluxograma em duas, e o resumo dela em quatro colunas no
 * tablet. */
@media (min-width: 768px) {
  .md\:grid-cols-4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (min-width: 1024px) {
  .lg\:col-span-2 {
    grid-column: span 2 / span 2;
  }
}

/* O construtor de expressão por encaixe (ver parser/blocks.go): a expressão
 * de um bloco do fluxograma como blocos aninhados. Cada nó é uma cápsula;
 * um buraco é uma cápsula tracejada; o menu abre embaixo. */
.fz-canvas-blocos {
  margin-top: 0.375rem;
  border: 1px solid hsl(var(--border));
  border-radius: 0.375rem;
  padding: 0.25rem 0.5rem;
  font-size: 0.75rem;
  background-color: transparent;
  color: hsl(var(--muted-foreground));
  cursor: pointer;
}

.fz-canvas-blocos:hover {
  background-color: hsl(var(--accent));
  color: hsl(var(--foreground));
}

.fz-encaixe-caixa {
  margin-top: 0.5rem;
}

.fz-encaixe-raiz {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8125rem;
}

.fz-encaixe-arvore {
  padding: 0.5rem;
  border: 1px solid hsl(var(--border));
  border-radius: 0.375rem;
  background-color: hsl(var(--muted) / 0.4);
  line-height: 2;
}

.fz-encaixe {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin: 0.125rem;
  padding: 0.125rem 0.375rem;
  border: 1px solid hsl(var(--border));
  border-radius: 0.5rem;
  background-color: hsl(var(--background));
}

.fz-encaixe-op {
  border-color: hsl(var(--primary) / 0.5);
}

.fz-encaixe-chamada {
  border-color: hsl(var(--primary) / 0.3);
}

.fz-encaixe-var {
  border-color: #16a34a;
  background-color: rgba(22, 163, 74, 0.08);
}

.fz-encaixe-lista {
  border-style: dotted;
}

.fz-encaixe-literal-caixa {
  border-color: hsl(var(--border));
}

.fz-encaixe-palavra,
.fz-encaixe-nome {
  border: 0;
  background: transparent;
  padding: 0 0.125rem;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

.fz-encaixe-palavra {
  font-weight: 600;
  color: hsl(var(--primary));
}

.fz-encaixe-palavra:hover,
.fz-encaixe-nome:hover {
  text-decoration: underline;
}

.fz-encaixe-parentese {
  color: hsl(var(--muted-foreground));
}

.fz-encaixe-buraco {
  display: inline-block;
  min-width: 1.75rem;
  margin: 0.125rem;
  padding: 0 0.375rem;
  border: 1px dashed hsl(var(--muted-foreground));
  border-radius: 0.5rem;
  background: transparent;
  color: hsl(var(--muted-foreground));
  font: inherit;
  cursor: pointer;
}

.fz-encaixe-buraco:hover {
  border-color: hsl(var(--primary));
  color: hsl(var(--primary));
}

.fz-encaixe-literal {
  border: 0;
  border-bottom: 1px solid hsl(var(--border));
  background: transparent;
  padding: 0 0.125rem;
  font: inherit;
  color: hsl(var(--foreground));
  outline: none;
}

.fz-encaixe-texto .fz-encaixe-literal {
  color: #b45309;
}

.fz-encaixe-texto::before,
.fz-encaixe-texto::after {
  content: '"';
  color: #b45309;
}

.fz-encaixe-remover,
.fz-encaixe-mais {
  border: 0;
  background: transparent;
  padding: 0 0.125rem;
  font: inherit;
  line-height: 1;
  color: hsl(var(--muted-foreground));
  cursor: pointer;
}

.fz-encaixe-remover:hover {
  color: hsl(var(--destructive));
}

.fz-encaixe-mais:hover {
  color: hsl(var(--primary));
}

.fz-encaixe-erro {
  margin: 0 0 0.375rem;
  font-size: 0.75rem;
  color: hsl(var(--destructive));
}

.fz-encaixe-menu-caixa {
  margin-top: 0.375rem;
  max-height: 16rem;
  overflow-y: auto;
  border: 1px solid hsl(var(--border));
  border-radius: 0.375rem;
  background-color: hsl(var(--popover));
  padding: 0.375rem;
}

.fz-encaixe-busca {
  width: 100%;
  margin-bottom: 0.375rem;
  border: 1px solid hsl(var(--border));
  border-radius: 0.375rem;
  padding: 0.25rem 0.5rem;
  font: inherit;
  background-color: hsl(var(--background));
  color: hsl(var(--foreground));
}

.fz-encaixe-secao {
  margin: 0.375rem 0 0.125rem;
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: hsl(var(--muted-foreground));
}

.fz-encaixe-item {
  display: flex;
  width: 100%;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  border: 0;
  border-radius: 0.25rem;
  padding: 0.125rem 0.375rem;
  background: transparent;
  font: inherit;
  text-align: left;
  color: hsl(var(--foreground));
  cursor: pointer;
}

.fz-encaixe-item:hover {
  background-color: hsl(var(--accent));
}

.fz-encaixe-item small {
  color: hsl(var(--muted-foreground));
  font-size: 0.6875rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fz-encaixe-vazio {
  margin: 0.25rem;
  font-size: 0.75rem;
  color: hsl(var(--muted-foreground));
}

/* ------------------------------------------------- o script colorido --
 * O realce fora do editor (ui.Code / ui.CodeInline): os spans são os do
 * cm-fz-theme.css, e aqui só entra a moldura. O bloco preserva as linhas do
 * script; a linha entra no meio de um texto (o passo de uma avaliação). */
.fz-codigo {
  border-radius: 0.375rem;
  background-color: hsl(var(--muted));
  padding: 0.75rem;
  font-size: 0.75rem;
  line-height: 1.5;
  overflow-x: auto;
  white-space: pre;
}

.fz-codigo-linha {
  font-size: 0.75rem;
  white-space: pre-wrap;
}

/* -------------------------------------------- o balão do visor do fluxograma --
 * ui.FlowchartViewer: o clique num bloco abre a expressão inteira ao lado
 * dele. Absoluto dentro da tela (que já é position: relative) para o
 * rule-canvas.js posicionar por left/top a partir do retângulo do bloco. */
.fz-visor .fz-bloco {
  cursor: pointer;
}

.fz-visor-balao {
  position: absolute;
  z-index: 20;
  max-width: min(28rem, calc(100% - 1rem));
  border: 1px solid hsl(var(--border));
  border-radius: 0.375rem;
  background-color: hsl(var(--popover));
  color: hsl(var(--popover-foreground));
  box-shadow: 0 4px 12px hsl(0 0% 0% / 0.15);
  padding: 0.5rem 0.75rem;
}

.fz-visor-balao[hidden] {
  display: none;
}

/* Duas utilidades do Tailwind que o CSS congelado não trouxe, para a tabela
 * de parâmetros da regra: célula alinhada ao topo e o select do booleano. */
.align-top {
  vertical-align: top;
}

.w-40 {
  width: 10rem;
}

/* --------------------------------------------- o mapa de uma avaliação --
 * O caminho no diálogo do detalhe, desenhado como mapa mental de cima para
 * baixo: a transação no topo, as regras vinculadas como ramos lado a lado,
 * as alcançadas por rules.<Nome>() como sub-ramos. Os FIOS são um SVG de
 * fundo; cada NÓ é um <button> posicionado em absoluto por cima, com o
 * número centrado por flex — ver page.EvaluationGraph para o porquê de
 * não ser <text> de SVG.
 *
 * A cor de um nó é o SUFIXO da classe (-baixo/-medio/-alto/-erro/-memo), e
 * é contrato com page.graphRiskClass: as três faixas são as mesmas do selo
 * de risco, e o css-check é quem prova que cada sufixo tem regra. */
.fz-mapa-moldura {
  overflow-x: auto; /* o mapa sai no tamanho natural; largo demais, rola */
  /* O balão sobe acima da caixa: o respiro é para ele não ser cortado
   * pela rolagem, que também recorta na vertical. */
  padding-top: 3.5rem;
}

.fz-mapa {
  position: relative;
  margin: 0 auto; /* menor que o diálogo, fica no centro */
}

.fz-mapa-fios {
  position: absolute;
  inset: 0;
  display: block;
  pointer-events: none;
}

.fz-mapa-fio {
  fill: none;
  stroke: hsl(var(--border));
  stroke-width: 1.5;
}

.fz-mapa-no {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  border: 1.5px solid hsl(var(--border));
  background-color: hsl(var(--card));
  padding: 0;
  cursor: pointer;
  outline: none;
}

.fz-mapa-no-baixo { background-color: hsl(142 71% 45% / 0.18); border-color: hsl(142 71% 45%); }
.fz-mapa-no-medio { background-color: hsl(38 92% 50% / 0.2); border-color: hsl(38 92% 50%); }
.fz-mapa-no-alto { background-color: hsl(var(--destructive) / 0.2); border-color: hsl(var(--destructive)); }
.fz-mapa-no-erro { background-color: hsl(var(--destructive) / 0.1); border-color: hsl(var(--destructive)); border-style: dashed; }
.fz-mapa-no-memo { background-color: hsl(var(--muted)); border-color: hsl(var(--muted-foreground) / 0.6); border-style: dashed; }

/* O estado vem DEPOIS das cores, pela mesma razão do fluxograma: mesma
 * especificidade, ganha quem está por último. */
.fz-mapa-no:hover,
.fz-mapa-no:focus-visible {
  border-color: hsl(var(--primary) / 0.7);
  z-index: 1; /* o balão por cima dos irmãos */
}

.fz-mapa-no-ativo {
  border-color: hsl(var(--primary));
  border-width: 2.5px;
}

/* O número dentro do círculo: a ordem de execução (o score, na raiz). */
.fz-mapa-numero {
  font-size: 12px;
  line-height: 1;
  font-weight: 700;
  color: hsl(var(--foreground));
}

.fz-mapa-raiz .fz-mapa-numero {
  font-size: 13px;
}

/* O balão do nó: escondido, e mostrado pelo hover (ou foco) do botão —
 * puro CSS, sem JavaScript. Não captura o ponteiro, senão o balão de um nó
 * cobriria o círculo do vizinho de cima e roubaria o hover. */
.fz-mapa-balao {
  display: none;
  position: absolute;
  bottom: calc(100% + 0.5rem);
  left: 50%;
  transform: translateX(-50%);
  flex-direction: column;
  align-items: center;
  white-space: nowrap;
  padding: 0.375rem 0.625rem;
  border-radius: 0.375rem;
  border: 1px solid hsl(var(--border));
  background-color: hsl(var(--popover));
  color: hsl(var(--popover-foreground));
  box-shadow: 0 4px 12px hsl(0 0% 0% / 0.25);
  pointer-events: none;
}

.fz-mapa-no:hover .fz-mapa-balao,
.fz-mapa-no:focus-visible .fz-mapa-balao {
  display: flex;
}

.fz-mapa-balao-nome {
  font-size: 11px;
  font-weight: 600;
}

.fz-mapa-balao-sub {
  font-size: 10px;
  color: hsl(var(--muted-foreground));
}

/* O recuo de meia linha que alinha o número e o tipo de um passo com o
 * código colorido ao lado — outra utilidade que o CSS congelado não trouxe. */
.pt-0\.5 {
  padding-top: 0.125rem;
}

/* ------------------------------------------------------------ fechar --
 * O X de todo diálogo: o do ui.DialogContent e os escritos à mão nos
 * cabeçalhos. Era um ícone cinza a 70% de opacidade, e quem procurava como sair
 * do diálogo não o achava — pior no "Criar Regra", onde ele tinha caído no
 * rodapé (ver o `:not(.absolute)` do tooltip). Agora ele fica no topo e tem cor
 * própria: vermelho discreto, cheio ao passar o mouse. */
.fz-fechar {
  color: hsl(var(--destructive));
  background-color: hsl(var(--destructive) / 0.12);
  border-radius: 0.375rem;
  opacity: 1;
}

.fz-fechar.absolute {
  top: 0.75rem;
  right: 0.75rem;
  padding: 0.375rem;
}

.fz-fechar:hover,
.fz-fechar:focus-visible {
  color: hsl(var(--destructive-foreground));
  background-color: hsl(var(--destructive));
}

/* ------------------------------------------------------ estado da conta --
 * A frase ao lado do switch de status acompanha o clique sem JavaScript: as
 * duas vão no HTML e o `:has` mostra a que casa com o checkbox escondido do
 * ui.Switch (que o fzToggle mantém em sincronia). */
.fz-estado-conta:has(input[name="is_enabled"]:checked) [data-fz-quando="desligado"],
.fz-estado-conta:not(:has(input[name="is_enabled"]:checked)) [data-fz-quando="ligado"] {
  display: none;
}

/* ------------------------------------------------ preenchimento automático --
 * O Chrome pinta o campo que ele autocompletou (usuário e senha no login) com
 * fundo azul-claro e texto preto, por cima do tema — o campo "Conta", que tem
 * autocomplete="off", ficava escuro e os dois de baixo não. O fundo do UA não
 * aceita override por `background-color`; o que funciona é adiar a transição
 * dele para nunca, o que mantém o fundo do próprio campo (inclusive o
 * translúcido `bg-background/50`) nos dois temas. A cor do texto, essa sim,
 * se sobrescreve por `-webkit-text-fill-color`. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active {
  -webkit-text-fill-color: hsl(var(--foreground));
  caret-color: hsl(var(--foreground));
  transition: background-color 600000s 0s, color 600000s 0s;
}

/* A fonte também: o Chrome desenha o valor autocompletado com a fonte do
 * sistema dele, e o Usuário/Senha saíam com outra letra que a do Conta. O
 * `::first-line` é o único seletor pelo qual ele aceita a troca. */
input:-webkit-autofill::first-line {
  font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  font-size: inherit;
}
