/* 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)); }
/* As duas declarações são paralelogramos (entrada/saída do fluxograma
 * clássico), cada uma com a sua cor: o campo do request em ciano, o parâmetro
 * em roxo. */
.fz-bloco-entrada .fz-bloco-caixa { fill: hsl(187 85% 43% / 0.12); stroke: hsl(187 85% 43%); }
.fz-bloco-parametro .fz-bloco-caixa { fill: hsl(262 83% 58% / 0.12); stroke: hsl(262 83% 58%); }

/* 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);
}

/* O quadro do gráfico: ancora os números das barras e reserva, no topo, a folga
 * em que cabe o número da barra mais alta (ela encosta na borda do desenho). */
.fz-spark-quadro {
  position: relative;
  padding-top: 1.125rem;
}

/* A fatia inteira da hora responde ao mouse — o alvo da dica não pode ser uma
 * barra de dois pixels. `pointer-events: all` porque o preenchimento é
 * transparente, e sem ele o SVG não considera a área como atingível. */
.fz-spark-alvo {
  fill: transparent;
  pointer-events: all;
}

.fz-spark-hora:hover .fz-spark-alvo {
  fill: hsl(var(--muted-foreground) / 0.1);
}

/* A camada dos números cobre exatamente o SVG (mesma altura, colada na base do
 * quadro), para a porcentagem de `bottom` significar altura de barra. */
.fz-spark-valores {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 8rem;
  pointer-events: none;
}

.fz-spark-valor {
  position: absolute;
  transform: translateX(-50%);
  padding-bottom: 0.125rem;
  color: hsl(var(--muted-foreground));
  font-size: 0.6875rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Abaixo desta largura cada fatia de hora é mais estreita que um número de
 * três dígitos, e os totais se sobreporiam. O valor de cada hora continua na
 * dica, que no toque fica aberta. */
@media (max-width: 900px) {
  .fz-spark-valores {
    display: none;
  }

  .fz-spark-quadro {
    padding-top: 0;
  }
}

.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);
}

/* A barra do rótulo em lote das avaliações só aparece com caixa marcada. O
 * atributo nasce no HTML (escondida é o estado que sobrevive ao script morto,
 * em que as caixas nem servem) e é o formulario.js que o tira. */
[data-fz-lote-barra][data-fz-vazio] {
  display: none;
}

/* ------------------------------------------------- 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;
  /* Uma dica pode ter mais de uma linha (a de uma barra do gráfico por hora
   * traz a hora, o total e cada faixa): a quebra vem como "\n" no atributo. */
  white-space: pre-line;
  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;
}

/* ------------------------------------------------------- cor do painel --
 * A amostra do seletor de cor (page.ColorPicker) e a faixa de acento de um
 * painel e de um widget (Dashboard.Color e a opção `color` do widget). As
 * cores em si são as classes `bg-gradient-to-* from-<cor>-500 to-<cor>-600`
 * do build congelado — a paleta fechada do domínio é exatamente o conjunto de
 * cores que tem as duas. Aqui só entram a forma e o realce do escolhido.
 *
 * O realce é CSS puro sobre o rádio (`:checked + .fz-swatch`): o rádio é
 * `sr-only` e continua no teclado, então o foco também ganha anel. Nenhuma
 * classe vem de JavaScript — o css-check não a enxergaria. */
.fz-swatch {
  display: block;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 9999px;
  box-shadow: inset 0 0 0 1px hsl(var(--border));
  transition: box-shadow 150ms ease, transform 150ms ease;
}

.fz-swatch-sm {
  display: inline-block;
  width: 0.75rem;
  height: 0.75rem;
  flex-shrink: 0;
}

/* "Sem cor": um círculo vazado, cortado na diagonal. */
.fz-swatch-none {
  background: linear-gradient(135deg,
    transparent calc(50% - 1px),
    hsl(var(--muted-foreground) / 0.6) calc(50% - 1px),
    hsl(var(--muted-foreground) / 0.6) calc(50% + 1px),
    transparent calc(50% + 1px));
  box-shadow: inset 0 0 0 1px hsl(var(--muted-foreground) / 0.5);
}

label:hover > .fz-swatch {
  transform: scale(1.1);
}

.fz-swatch-input:checked + .fz-swatch {
  box-shadow: 0 0 0 2px hsl(var(--background)), 0 0 0 4px hsl(var(--foreground));
}

.fz-swatch-input:focus-visible + .fz-swatch {
  box-shadow: 0 0 0 2px hsl(var(--background)), 0 0 0 4px hsl(var(--ring));
}

.fz-accent-bar {
  height: 0.25rem;
  border-radius: 9999px;
}

/* --------------------------------------------- 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. */
/* A MOLDURA (1.6.0, Bug001): área de altura FIXA com a largura do diálogo.
 * O mapa de dentro é enquadrado e movido pelo fila-avaliacoes.js (transform
 * na .fz-mapa); a moldura nunca cresce com ele — 28 vínculos cabem na mesma
 * caixa que 3. Sem JavaScript ela rola, como antes. A altura é
 * page.GraphFrameHeight; menor em telas pequenas. */
.fz-mapa-moldura {
  position: relative;
  height: 360px;
  border: 1px solid hsl(var(--border));
  border-radius: 0.5rem;
  background:
    radial-gradient(hsl(var(--border) / 0.6) 1px, transparent 1px) 0 0 / 16px 16px,
    hsl(var(--muted) / 0.3);
  overflow: auto;
}

@media (max-width: 640px) {
  .fz-mapa-moldura {
    height: 260px;
  }
}

/* A tela: o que recorta e recebe o arrasto. */
.fz-mapa-tela {
  position: absolute;
  inset: 0;
  overflow: auto; /* sem JavaScript, rola — como a moldura fazia antes */
  cursor: grab;
  touch-action: none;
}

/* Com o JavaScript montado, a tela recorta e o mapa anda por transform. */
.fz-mapa-tela[data-fz-mapa-ativa] {
  overflow: hidden;
}

.fz-mapa-tela[data-fz-arrastando] {
  cursor: grabbing;
}

/* A barra de controles, no canto superior direito, acima da tela. */
.fz-mapa-controles {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 0.125rem;
  padding: 0.125rem;
  border-radius: 0.375rem;
  border: 1px solid hsl(var(--border));
  background-color: hsl(var(--popover));
  box-shadow: 0 2px 8px hsl(0 0% 0% / 0.15);
}

.fz-mapa-botao {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.75rem;
  height: 1.75rem;
  padding: 0 0.375rem;
  border-radius: 0.25rem;
  font-size: 12px;
  font-weight: 600;
  color: hsl(var(--foreground));
  background: transparent;
  border: 0;
  cursor: pointer;
}

.fz-mapa-botao:hover,
.fz-mapa-botao:focus-visible {
  background-color: hsl(var(--accent));
  outline: none;
}

.fz-mapa-nivel {
  min-width: 3rem;
  font-variant-numeric: tabular-nums;
}

/* O mapa em TELA CHEIA (1.6.1, ponto 4 do Rafael): uma CAMADA própria no
 * <body> (data-fz-mapa-camada, criada pelo fila-avaliacoes.js), por cima do
 * diálogo, para onde a moldura é MOVIDA. A versão anterior esticava o
 * diálogo inteiro (:has no painel) e fixava a moldura dentro dele: o resto do
 * diálogo ficava espalhado por trás e o mapa num retângulo pequeno, porque
 * um ancestral com transform é o bloco contentor do fixo. Movendo o nó, não
 * há ancestral nenhum — e o diálogo fica intacto para a volta. Atributos, e
 * não classes, porque o css-check não enxerga classe vinda de JavaScript. */
[data-fz-mapa-camada] {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  background-color: hsl(var(--background));
}

[data-fz-mapa-camada-topo] {
  padding: 0.625rem 1rem;
  border-bottom: 1px solid hsl(var(--border));
  font-size: 0.875rem;
  font-weight: 600;
  color: hsl(var(--foreground));
}

[data-fz-mapa-camada] .fz-mapa-moldura {
  flex: 1;
  height: auto;
  border: 0;
  border-radius: 0;
}

.fz-mapa {
  position: relative;
  margin: 0 auto; /* sem JavaScript: menor que a moldura, 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;
}

/* As novidades do modal "Sobre" e da /novidades (page/about.templ).
 *
 * `uppercase` é do Tailwind e o build congelado nunca a emitiu: os cabeçalhos
 * de seção de uma release (Atenção / Novidades / Correções) são a primeira
 * superfície a usá-la.
 *
 * `.fz-novidade` é o item comprido recolhido num <details> nativo: o título
 * no <summary>, o corpo embaixo. O marcador padrão do navegador (o triângulo)
 * fica, porque é o que diz "isto abre" sem JavaScript nenhum; só sai o
 * contorno que o Chrome desenha em volta do summary inteiro ao CLICAR, e o
 * foco por teclado continua visível. */
.uppercase {
  text-transform: uppercase;
}
.fz-novidade > summary {
  outline: none;
}
.fz-novidade > summary:focus-visible {
  outline: 2px solid hsl(var(--ring));
  outline-offset: 2px;
  border-radius: 0.25rem;
}

/* A área do script AMPLIADA (page/rules.templ, fzAbasDeRegra.ampliar): a raiz
 * do componente vira um painel do tamanho da janela, acima do diálogo (que é
 * z-50), e o editor de texto e a tela do fluxograma crescem com ela. É o
 * MESMO nó, só posicionado: o formulário, o CodeMirror e o canvas seguem
 * sendo os mesmos. O `.CodeMirror` do editor tem altura fixa no tema; aqui
 * ela vira a altura que sobra. */
[data-fz-ampliado] {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  overflow: auto;
  padding: 1rem;
  background: hsl(var(--background));
}
[data-fz-ampliado] .CodeMirror {
  height: calc(100vh - 12rem);
}
[data-fz-ampliado] .fz-canvas-tela {
  height: calc(100vh - 20rem);
  min-height: 420px;
}

/* Com a área do script AMPLIADA, o PAINEL do diálogo que a contém vira a
 * janela inteira. O `position: fixed` do nó ampliado não escapa do painel:
 * ele é `translate(-50%, -50%)`, e transform faz de um ancestral o bloco
 * contentor dos descendentes fixos — o painel "ampliado" saía do tamanho do
 * diálogo. Sem transform e ocupando a viewport, o nó fixo cobre a janela. O
 * `:has()` é o que faz isto valer só enquanto há um nó ampliado dentro. */
[aria-modal="true"]:has([data-fz-ampliado]) {
  transform: none;
  left: 0;
  top: 0;
  width: 100vw;
  max-width: none;
  height: 100vh;
  max-height: none;
  padding: 0;
  overflow: hidden;
  border-radius: 0;
}

/* O seletor de tipo do input (API × arquivo CSV): o realce do cartão escolhido
   é CSS puro, pelo rádio sr-only irmão — o mesmo desenho do seletor de cor
   dos painéis. Sem JavaScript, sem classe trocada em runtime. */
.fz-tipo-input:checked + .fz-tipo-cartao {
  border-color: hsl(var(--primary));
  background-color: hsl(var(--primary) / 0.06);
}
.fz-tipo-input:focus-visible + .fz-tipo-cartao {
  outline: 2px solid hsl(var(--ring));
  outline-offset: 2px;
}

/* -----------------------------------------------------------------------------
 * O Select: opção sob o mouse, título de grupo e campo de pesquisa
 * -----------------------------------------------------------------------------
 * A opção é um <div>, e a classe dela só tem `focus:bg-accent` — herança do
 * Radix, onde quem foca o item é o teclado. Sob o mouse nada mudava, e a lista
 * não dizia o que era clicável. */
[data-fz-select] [role='option'] {
  cursor: pointer;
}

[data-fz-select] [role='option']:hover {
  background: hsl(var(--accent));
  color: hsl(var(--accent-foreground));
}

/* O título de um grupo (a CATEGORIA, na lista de regras). Ele não é escolhível
 * e não pode parecer que é: caixa alta miúda, cinza, com ícone, a contagem do
 * grupo à direita e uma linha que o separa do grupo anterior. As opções ficam
 * no recuo de sempre (pl-8), abaixo dele. */
.fz-select-grupo {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.5rem 0.5rem 0.25rem;
  color: hsl(var(--muted-foreground));
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  line-height: 1rem;
  text-transform: uppercase;
  cursor: default;
  user-select: none;
}

.fz-select-grupo ~ .fz-select-grupo {
  margin-top: 0.25rem;
  padding-top: 0.625rem;
  border-top: 1px solid hsl(var(--border));
}

.fz-select-grupo-nome {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fz-select-grupo-total {
  margin-left: auto;
  padding: 0 0.375rem;
  border-radius: 9999px;
  background: hsl(var(--muted));
  font-weight: 500;
  letter-spacing: 0;
}

/* O campo de pesquisa fica no topo do painel, FORA da área que rola. */
.fz-select-busca {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0 0.75rem;
  border-bottom: 1px solid hsl(var(--border));
}

.fz-select-busca input {
  flex: 1;
  min-width: 0;
  height: 2.5rem;
  border: 0;
  outline: none;
  background: transparent;
  color: inherit;
  font-size: 0.875rem;
}

.fz-select-busca input::placeholder {
  color: hsl(var(--muted-foreground));
}

.fz-select-vazio {
  padding: 1.5rem 0.5rem;
  color: hsl(var(--muted-foreground));
  font-size: 0.875rem;
  text-align: center;
}

/* `sm:mr-auto` não existe no build congelado. É o que empurra para a ESQUERDA
 * o primeiro botão de um rodapé de diálogo (`sm:justify-end`): a ação que
 * desfaz — remover o agendamento de um relatório — longe das que confirmam. */
@media (min-width: 640px) {
  .sm\:mr-auto {
    margin-right: auto;
  }
}

/* O indicador de "carregando" de um botão TOMA O LUGAR do ícone durante o
 * envio (registro 036 da homologação da 1.3.0). O `.htmx-indicator` só zera a
 * opacidade, e invisível ele continuava ocupando 24px antes do ícone: era o
 * vão à esquerda de "Enviar requisição", "Avaliar o arquivo" e "Testar
 * conexão". Fora de requisição ele não gera caixa; durante, o htmx põe
 * `htmx-request` nele e o ícone de repouso, que vem logo depois, some. */
.fz-indicador-botao {
  display: none;
}

.fz-indicador-botao.htmx-request {
  display: inline-flex;
}

.fz-indicador-botao.htmx-request + .fz-icone-repouso {
  display: none;
}

/* ---------------------------------------------------------------------------
 * O marketplace por categoria e com versões (1.4.0).
 *
 * A cor de uma categoria é UM número, o matiz, que o servidor escreve no
 * atributo style como `--h` (page.CategoryStyleOf): o resto — saturação e luz
 * de cada tema — mora aqui. "Sem categoria" (`data-fz-sem-categoria`) tira a
 * saturação em vez de inventar um matiz.
 * ------------------------------------------------------------------------- */
.fz-mkt-card {
  --h: 220;
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  border-radius: var(--radius);
  border: 1px solid hsl(var(--border));
  background: hsl(var(--card));
  transition: transform .25s, box-shadow .25s, border-color .25s;
}
.fz-mkt-card:hover {
  transform: translateY(-3px);
  border-color: hsl(var(--primary) / .5);
  box-shadow: 0 12px 28px -12px hsl(var(--primary) / .25);
}
.fz-mkt-card-abrir {
  display: flex;
  flex: 1;
  flex-direction: column;
  width: 100%;
  text-align: left;
}
.fz-mkt-capa,
.fz-mkt-heroi {
  --h: 220;
  --s: 70%;
  --l: 48%;
  position: relative;
  display: flex;
  align-items: flex-end;
  width: 100%;
  height: 84px;
  padding: 12px 16px;
  overflow: hidden;
  color: #fff;
  background: hsl(var(--h) var(--s) var(--l));
}
.fz-mkt-capa[style],
.fz-mkt-heroi[style] {
  background: hsl(var(--h) var(--s) var(--l));
}
.dark .fz-mkt-capa,
.dark .fz-mkt-heroi {
  --s: 50%;
  --l: 36%;
}
.fz-mkt-capa[data-fz-sem-categoria],
.fz-mkt-heroi[data-fz-sem-categoria] {
  --s: 10%;
  --l: 45%;
}
.fz-mkt-heroi {
  height: 104px;
  margin: -24px -24px 8px;
  width: calc(100% + 48px);
  border-radius: var(--radius) var(--radius) 0 0;
}
.fz-mkt-capa-icone {
  position: absolute;
  right: -14px;
  top: -14px;
  width: 116px;
  height: 116px;
  opacity: .22;
  stroke-width: 1.5;
}
/* Categoria e versão são TEXTO informativo (revisão final da 1.4.0, Bug008):
 * com fundo e borda elas pareciam botões, e ninguém sabia o que clicar. */
.fz-mkt-capa-rotulo {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
}
.fz-mkt-versao {
  position: absolute;
  top: 10px;
  left: 16px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  opacity: .9;
}
/* O corpo é uma coluna: a descrição reserva duas linhas e o bloco de quem
 * publicou desce até o rodapé (Bug013), para os cards de uma linha da grade
 * terem as mesmas alturas por dentro. */
.fz-mkt-corpo {
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1;
  padding: 16px;
}
.fz-mkt-titulo-texto {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.fz-mkt-descricao {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: 2.5rem;
}
.fz-mkt-ver {
  font-size: 12px;
  font-weight: 500;
  color: hsl(var(--primary));
}
.fz-mkt-ver:hover {
  text-decoration: underline;
}
/* O estado (publicada, fora da vitrine, adquirida) é texto com um ponto de
 * cor, e não selo: selo com fundo lia como botão. */
.fz-mkt-status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 500;
  color: hsl(var(--muted-foreground));
}
.fz-mkt-status::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: currentColor;
}
.fz-mkt-status:has(svg)::before {
  display: none;
}
[data-fz-estado="published"].fz-mkt-status,
[data-fz-estado="acquired"].fz-mkt-status {
  color: hsl(142 71% 35%);
}
.dark [data-fz-estado="published"].fz-mkt-status,
.dark [data-fz-estado="acquired"].fz-mkt-status {
  color: hsl(142 60% 55%);
}
.fz-mkt-rodape-acoes {
  flex-wrap: wrap;
  justify-content: flex-start;
}
.fz-mkt-titulo {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
}
.fz-mkt-selo {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: 4px;
  padding: 2px 8px 2px 6px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  background: hsl(var(--primary) / .1);
  color: hsl(var(--primary));
}
.fz-mkt-pub {
  display: grid;
  gap: 6px;
  margin-top: auto;
  padding-top: 10px;
  border-top: 1px solid hsl(var(--border));
  font-size: 12px;
  color: hsl(var(--muted-foreground));
}
.fz-mkt-org {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  font-weight: 500;
  color: hsl(var(--foreground));
}
.fz-mkt-avatar {
  display: grid;
  flex: none;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 6px;
  font-size: 10px;
  font-weight: 700;
  background: hsl(var(--muted));
  color: hsl(var(--muted-foreground));
}
.fz-mkt-datas {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
}
.fz-mkt-datas > span {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-variant-numeric: tabular-nums;
}
[data-fz-tom="selo"],
[data-fz-tom="parametro"] {
  color: hsl(var(--primary));
}
[data-fz-tom="perdido"] {
  color: hsl(25 95% 45%);
}
.dark [data-fz-tom="perdido"] {
  color: hsl(25 95% 62%);
}
.fz-mkt-rodape {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0 16px 16px;
  margin-top: auto;
}

/* O selo de estado da versão pendente e do histórico. */
.fz-mkt-pendente,
.fz-mkt-estado {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  background: hsl(var(--muted));
  color: hsl(var(--muted-foreground));
}
/* No CARD o estado pendente é texto (Bug008); no histórico continua selo. */
.fz-mkt-pendente {
  padding: 0;
  background: none;
}
[data-fz-estado="review"].fz-mkt-pendente {
  color: hsl(25 90% 42%);
}
[data-fz-estado="approved"].fz-mkt-pendente {
  color: hsl(var(--primary));
}
[data-fz-estado="rejected"].fz-mkt-pendente {
  color: hsl(0 65% 48%);
}
[data-fz-estado="review"].fz-mkt-estado,
[data-fz-estado="changed"].fz-mkt-estado {
  background: hsl(25 95% 53% / .14);
  color: hsl(25 90% 42%);
}
[data-fz-estado="approved"].fz-mkt-estado,
[data-fz-estado="verified"].fz-mkt-estado,
[data-fz-estado="added"].fz-mkt-estado {
  background: hsl(var(--primary) / .1);
  color: hsl(var(--primary));
}
[data-fz-estado="rejected"].fz-mkt-estado,
[data-fz-estado="removed"].fz-mkt-estado {
  background: hsl(0 72% 51% / .12);
  color: hsl(0 65% 48%);
}
.fz-mkt-caixa {
  padding: 12px 14px;
  border-radius: calc(var(--radius) - 2px);
  border: 1px solid hsl(var(--border));
}
.fz-mkt-contagem {
  display: inline-grid;
  place-items: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  background: hsl(var(--primary));
  color: hsl(var(--primary-foreground));
}

/* A escolha (rádio) de incremento e de validação: o cartão inteiro é o alvo. */
.fz-mkt-escolha {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 8px 10px;
  border-radius: calc(var(--radius) - 2px);
  border: 1px solid hsl(var(--border));
  cursor: pointer;
}
.fz-mkt-escolha:has(input:checked) {
  border-color: hsl(var(--primary));
  background: hsl(var(--primary) / .06);
}
.fz-mkt-escolha input {
  margin-top: 3px;
  accent-color: hsl(var(--primary));
}
.fz-mkt-escolha b {
  display: block;
  font-size: 13px;
  font-weight: 500;
}
.fz-mkt-escolha small {
  display: block;
  font-size: 12px;
  color: hsl(var(--muted-foreground));
}

/* O histórico de versões: uma linha do tempo com a atual em destaque. */
.fz-mkt-historico {
  display: grid;
  margin: 0;
  padding: 0 0 0 16px;
  list-style: none;
  border-left: 2px solid hsl(var(--border));
}
.fz-mkt-historico li {
  position: relative;
  display: grid;
  gap: 4px;
  padding: 0 0 12px;
}
.fz-mkt-historico li::before {
  content: "";
  position: absolute;
  left: -22px;
  top: 4px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 2px solid hsl(var(--border));
  background: hsl(var(--card));
}
.fz-mkt-historico li[data-fz-atual]::before {
  border-color: hsl(var(--primary));
  background: hsl(var(--primary));
}

/* O menu de categorias: um <details> nativo, sem JavaScript. */
.fz-mkt-filtro {
  position: relative;
}
.fz-mkt-filtro-botao {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 12px;
  border-radius: calc(var(--radius) - 2px);
  border: 1px solid hsl(var(--input));
  background: hsl(var(--background));
  font-size: 14px;
  cursor: pointer;
  list-style: none;
}
.fz-mkt-filtro-botao::-webkit-details-marker {
  display: none;
}
.fz-mkt-filtro[open] .fz-mkt-filtro-botao {
  border-color: hsl(var(--primary));
}
.fz-mkt-filtro-painel {
  position: absolute;
  z-index: 20;
  top: calc(100% + 6px);
  left: 0;
  width: 290px;
  max-width: calc(100vw - 32px);
  max-height: 60vh;
  overflow-y: auto;
  padding: 6px;
  border-radius: calc(var(--radius) - 2px);
  border: 1px solid hsl(var(--border));
  background: hsl(var(--popover));
  color: hsl(var(--popover-foreground));
  box-shadow: 0 12px 28px -12px hsl(222 47% 11% / .3);
}
.fz-mkt-filtro-lista {
  display: grid;
  gap: 1px;
}
.fz-mkt-filtro-separador {
  height: 1px;
  margin: 4px 0;
  background: hsl(var(--border));
}
.fz-mkt-opcao {
  --h: 220;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 6px;
  font-size: 13px;
  cursor: pointer;
}
.fz-mkt-opcao:hover {
  background: hsl(var(--muted));
}
.fz-mkt-opcao input {
  accent-color: hsl(var(--primary));
}
.fz-mkt-tile {
  display: grid;
  flex: none;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 7px;
  background: hsl(var(--h) 85% 94%);
  color: hsl(var(--h) 65% 40%);
}
.dark .fz-mkt-tile {
  background: hsl(var(--h) 45% 22%);
  color: hsl(var(--h) 85% 72%);
}
[data-fz-sem-categoria] .fz-mkt-tile,
[data-fz-sem-categoria].fz-mkt-tag {
  filter: saturate(.1);
}
.fz-mkt-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.fz-mkt-tag {
  --h: 220;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 6px 3px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  background: hsl(var(--h) 85% 94%);
  color: hsl(var(--h) 65% 38%);
}
.dark .fz-mkt-tag {
  background: hsl(var(--h) 45% 22%);
  color: hsl(var(--h) 85% 72%);
}
.fz-mkt-tag-remover {
  display: inline-grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border-radius: 999px;
}
.fz-mkt-tag-remover:hover {
  background: hsl(0 0% 0% / .08);
}

/* O diff da revisão de versão: uma linha por linha do script. */
.fz-diff {
  overflow-x: auto;
  border-radius: calc(var(--radius) - 2px);
  border: 1px solid hsl(var(--border));
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  line-height: 1.6;
}
.fz-diff-linha {
  display: flex;
  gap: 8px;
  padding: 0 10px;
  white-space: pre;
}
.fz-diff-linha[data-fz-diff="+"] {
  background: hsl(142 70% 45% / .12);
}
.fz-diff-linha[data-fz-diff="-"] {
  background: hsl(0 72% 51% / .12);
}
.fz-diff-marca {
  flex: none;
  width: 10px;
  color: hsl(var(--muted-foreground));
  user-select: none;
}
@media (prefers-reduced-motion: reduce) {
  .fz-mkt-card {
    transition: none;
  }
  .fz-mkt-card:hover {
    transform: none;
  }
}

/* ===== /documentacao: o documento técnico dentro da moldura do portal =====
   O documento embutido traz estilo próprio, que fica de fora; este é o dele
   escrito sobre os tokens do portal (--card, --border, --muted-foreground…),
   então o tema claro/escuro e a tipografia são os da plataforma. TUDO é
   escopado em .fz-doc: as classes do documento (.sec, .pill, .n, .c…) são
   nomes curtos, e soltas brigariam com o resto do portal. Os 6.5rem de
   top/scroll-margin-top são o cabeçalho fixo da moldura, que cobriria o
   sumário e o título da seção ao saltar para uma âncora. */
.fz-doc { display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: 1.5rem; align-items: start; }
@media (max-width: 960px) { .fz-doc { grid-template-columns: minmax(0, 1fr); } }
.fz-doc-rail { position: sticky; top: 6.5rem; max-height: calc(100vh - 8rem); overflow-y: auto;
  border: 1px solid hsl(var(--border)); border-radius: var(--radius); background: hsl(var(--card)); box-shadow: var(--shadow-sm); padding: 1rem .75rem; }
@media (max-width: 960px) { .fz-doc-rail { position: static; max-height: none; } }
.fz-doc-toc summary { display: none; }
.fz-doc-toc-title { margin: 0 0 .5rem; padding: 0 .5rem; font-size: .875rem; font-weight: 600; }
@media (max-width: 960px) {
  .fz-doc-toc summary { display: block; cursor: pointer; padding: 0 .5rem; font-size: .875rem; font-weight: 600; }
  .fz-doc-toc[open] summary { margin-bottom: .5rem; }
  .fz-doc-toc-title { display: none; }
}
.fz-doc-rail ol { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.fz-doc-rail nav a { display: grid; grid-template-columns: 1.5rem minmax(0, 1fr); gap: .5rem; align-items: baseline; padding: .375rem .5rem;
  border-radius: calc(var(--radius) - 4px); font-size: .8125rem; line-height: 1.4; color: hsl(var(--muted-foreground)); text-decoration: none; transition: background-color .15s, color .15s; }
.fz-doc-rail nav a:hover { background: hsl(var(--muted)); color: hsl(var(--foreground)); }
.fz-doc-rail nav a.atual { background: hsl(var(--primary) / .1); color: hsl(var(--primary)); font-weight: 600; }
.fz-doc-rail nav a .n { font-size: .75rem; text-align: right; font-variant-numeric: tabular-nums; opacity: .8; }

.fz-doc-body { min-width: 0; display: flex; flex-direction: column; gap: 1.5rem; color: hsl(var(--foreground)); line-height: 1.65; }
.fz-doc-body a { color: hsl(var(--primary)); text-underline-offset: 2px; }
.fz-doc-body .hero, .fz-doc-body section.sec, .fz-doc-body footer {
  border: 1px solid hsl(var(--border)); border-radius: var(--radius); background: hsl(var(--card)); box-shadow: var(--shadow-sm); padding: 1.5rem; }
.fz-doc-body section.sec { scroll-margin-top: 6.5rem; }
.fz-doc-body .hero { background: var(--gradient-card); }
.fz-doc-body .olho { margin: 0 0 .5rem; font-size: .875rem; font-weight: 500; color: hsl(var(--primary)); }
.fz-doc-body h1 { margin: 0 0 .75rem; font-size: 1.875rem; line-height: 1.2; font-weight: 700; letter-spacing: -.02em; }
.fz-doc-body .lede { margin: 0; color: hsl(var(--muted-foreground)); max-width: 75ch; }
.fz-doc-body .lede strong { color: hsl(var(--foreground)); }
.fz-doc-body .resumo { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: .75rem; margin: 1.5rem 0 0; }
.fz-doc-body .resumo div { border: 1px solid hsl(var(--border)); border-radius: calc(var(--radius) - 2px); background: hsl(var(--muted) / .5); padding: .75rem 1rem; }
.fz-doc-body .resumo dt { margin: 0 0 .125rem; font-size: .75rem; color: hsl(var(--muted-foreground)); }
.fz-doc-body .resumo dd { margin: 0; font-size: .875rem; font-weight: 600; }

.fz-doc-body .sec-topo { display: flex; align-items: center; gap: .75rem; margin-bottom: .25rem; }
.fz-doc-body section.sec h2 { flex: 1; min-width: 0; margin: 0; font-size: 1.25rem; font-weight: 600; line-height: 1.3; letter-spacing: -.01em; }
.fz-doc-body .selo { flex: none; display: grid; place-items: center; width: 2.25rem; height: 2.25rem; border-radius: calc(var(--radius) - 2px);
  background: hsl(var(--primary) / .1); color: hsl(var(--primary)); font-size: .8125rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.fz-doc-body section.sec > p, .fz-doc-body section.sec > ul, .fz-doc-body section.sec > ol { max-width: 75ch; }
.fz-doc-body section.sec p { margin: .875rem 0; }
.fz-doc-body section.sec ul, .fz-doc-body section.sec ol { margin: .875rem 0; padding-left: 1.375rem; }
.fz-doc-body section.sec ul { list-style: disc; }
.fz-doc-body section.sec ol { list-style: decimal; }
.fz-doc-body section.sec li { margin: .375rem 0; }
.fz-doc-body section.sec li > p { margin: .25rem 0; }
.fz-doc-body h3 { margin: 1.75rem 0 .125rem; font-size: 1rem; font-weight: 600; scroll-margin-top: 6.5rem; }
.fz-doc-body strong { font-weight: 600; }
.fz-doc-body code:not(pre code) { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .85em;
  background: hsl(var(--muted)); padding: .1em .375em; border-radius: .25rem; overflow-wrap: anywhere; }

.fz-doc-body .bloco { margin: 1rem 0; }
.fz-doc-body .bloco pre { margin: 0; padding: .875rem 1rem; overflow-x: auto; border-radius: calc(var(--radius) - 2px);
  background: hsl(222 47% 9%); color: hsl(210 20% 90%); border: 1px solid hsl(222 47% 16%);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: .8125rem; line-height: 1.7; tab-size: 2; }
.fz-doc-body .bloco pre code { font: inherit; background: none; padding: 0; }

.fz-doc-body .aviso { margin: 1.25rem 0; padding: .875rem 1.125rem; max-width: 75ch; border: 1px solid hsl(var(--warning) / .35);
  border-left: 3px solid hsl(var(--warning)); border-radius: calc(var(--radius) - 2px); background: hsl(var(--warning) / .06); }
.fz-doc-body .aviso > .rot { display: block; margin-bottom: .25rem; font-size: .8125rem; font-weight: 600; color: hsl(var(--warning)); }
.fz-doc-body .aviso p { margin: .5rem 0 0; font-size: .9375rem; }
.fz-doc-body .aviso p:first-of-type { margin-top: 0; }
.fz-doc-body .aviso ul { margin: .5rem 0 0; font-size: .9375rem; }
.fz-doc-body .aviso.nota { border-color: hsl(var(--border)); border-left-color: hsl(var(--muted-foreground)); background: hsl(var(--muted) / .5); }
.fz-doc-body .aviso.nota > .rot { color: hsl(var(--muted-foreground)); }
.fz-doc-body .aviso.ok { border-color: hsl(var(--success) / .35); border-left-color: hsl(var(--success)); background: hsl(var(--success) / .06); }
.fz-doc-body .aviso.ok > .rot { color: hsl(var(--success)); }

/* As tabelas no desenho das do portal (ui.Table): cabeçalho em
   muted-foreground, linha com borda e hover. */
.fz-doc-body .rolagem { overflow-x: auto; margin: 1rem 0; border: 1px solid hsl(var(--border)); border-radius: calc(var(--radius) - 2px); }
.fz-doc-body table { width: 100%; min-width: 480px; border-collapse: collapse; font-size: .875rem; }
.fz-doc-body th, .fz-doc-body td { padding: .625rem 1rem; text-align: left; vertical-align: top; border-bottom: 1px solid hsl(var(--border)); }
.fz-doc-body th { font-weight: 500; color: hsl(var(--muted-foreground)); background: hsl(var(--muted) / .5); }
.fz-doc-body tbody tr { transition: background-color .15s; }
.fz-doc-body tbody tr:hover { background: hsl(var(--muted) / .5); }
.fz-doc-body tbody tr:last-child td { border-bottom: 0; }
.fz-doc-body td code { white-space: nowrap; }

.fz-doc-body .passos { list-style: none; counter-reset: passo; margin: 1rem 0; padding: 0; max-width: 75ch; }
.fz-doc-body .passos > li { position: relative; margin: 0; padding: 0 0 1.125rem 2.75rem; counter-increment: passo; }
.fz-doc-body .passos > li::before { content: counter(passo); position: absolute; left: 0; top: 0; display: grid; place-items: center;
  width: 1.75rem; height: 1.75rem; border-radius: 9999px; background: hsl(var(--primary) / .1); color: hsl(var(--primary)); font-size: .8125rem; font-weight: 600; }
.fz-doc-body .passos > li > strong { display: block; margin-bottom: .25rem; }
.fz-doc-body .passos > li p { margin: .375rem 0; }

.fz-doc-body figure { margin: 1rem 0; max-width: 100%; }
.fz-doc-body figure svg { display: block; width: 100%; height: auto; border: 1px solid hsl(var(--border)); border-radius: calc(var(--radius) - 2px); background: hsl(var(--background)); }
.fz-doc-body figcaption { margin-top: .5rem; max-width: 75ch; font-size: .8125rem; color: hsl(var(--muted-foreground)); }
.fz-doc-body .dg-box { fill: hsl(var(--card)); stroke: hsl(var(--border)); stroke-width: 1.2; }
.fz-doc-body .dg-acc { fill: hsl(var(--primary) / .1); stroke: hsl(var(--primary)); stroke-width: 1.2; }
.fz-doc-body .dg-ok { fill: hsl(var(--success) / .1); stroke: hsl(var(--success)); stroke-width: 1.2; }
.fz-doc-body .dg-warn { fill: hsl(var(--accent) / .1); stroke: hsl(var(--accent)); stroke-width: 1.2; }
.fz-doc-body .dg-t { fill: hsl(var(--foreground)); font-size: 13px; font-weight: 600; }
.fz-doc-body .dg-s { fill: hsl(var(--muted-foreground)); font: 400 11px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
.fz-doc-body .dg-x { fill: hsl(var(--muted-foreground)); font-size: 10.5px; }
.fz-doc-body .dg-l, .fz-doc-body .dg-ld { fill: none; stroke: hsl(var(--muted-foreground)); stroke-width: 1.3; }
.fz-doc-body .dg-ld { stroke-dasharray: 4 3; }

.fz-doc-body .chk { list-style: none; margin: 0 0 1rem; padding: 0; max-width: 75ch; }
.fz-doc-body .chk li { display: flex; align-items: flex-start; gap: .625rem; margin: 0 0 .5rem; padding: .5rem .75rem; font-size: .875rem;
  border: 1px solid hsl(var(--border)); border-radius: calc(var(--radius) - 4px); background: hsl(var(--card)); }
.fz-doc-body .chk input { flex: none; width: 1rem; height: 1rem; margin: .2rem 0 0; accent-color: hsl(var(--primary)); }
.fz-doc-body .chk label { cursor: pointer; }
.fz-doc-body .chk li.feito { opacity: .55; }
.fz-doc-body .chk li.feito label { text-decoration: line-through; }
.fz-doc-body .btn { display: inline-flex; align-items: center; height: 2.25rem; padding: 0 .75rem; font-size: .875rem; font-weight: 500; cursor: pointer;
  border: 1px solid hsl(var(--input)); border-radius: calc(var(--radius) - 2px); background: hsl(var(--background)); color: hsl(var(--foreground)); transition: background-color .15s; }
.fz-doc-body .btn:hover { background: hsl(var(--accent)); color: hsl(var(--accent-foreground)); }

/* As pílulas no desenho do ui.Badge do portal. */
.fz-doc-body .pill { display: inline-flex; align-items: center; padding: .125rem .625rem; border-radius: 9999px; font-size: .75rem; font-weight: 600; white-space: nowrap;
  border: 1px solid hsl(var(--border)); color: hsl(var(--muted-foreground)); background: hsl(var(--muted) / .5); }
.fz-doc-body .pill.alta { color: hsl(var(--destructive)); border-color: hsl(var(--destructive) / .3); background: hsl(var(--destructive) / .1); }
.fz-doc-body .pill.media { color: hsl(var(--warning)); border-color: hsl(var(--warning) / .3); background: hsl(var(--warning) / .1); }
.fz-doc-body .pill.baixa { color: hsl(var(--success)); border-color: hsl(var(--success) / .3); background: hsl(var(--success) / .1); }
.fz-doc-body .pill.copy { color: hsl(var(--primary)); border-color: hsl(var(--primary) / .3); background: hsl(var(--primary) / .1); }

.fz-doc-body footer { font-size: .8125rem; color: hsl(var(--muted-foreground)); }
.fz-doc-body footer p { margin: .5rem 0; }

@media print {
  .fz-doc { display: block; }
  .fz-doc-rail { display: none; }
  .fz-doc-body .hero, .fz-doc-body section.sec, .fz-doc-body footer { box-shadow: none; break-inside: auto; }
}

/* As opções de uma seção DESMARCADA do relatório (fzSecao): apagadas e sem
 * clique. O `disabled` dos campos é escrito pelo mesmo componente; o atributo
 * já vem do servidor, então o bloco nasce apagado antes do JavaScript. */
.fz-secao-opcoes[data-fz-inativa] {
  opacity: .45;
  pointer-events: none;
}

/* ===================================================================
 * Comunicação, fase 2 (1.4.0): o editor rico do e-mail, os modelos e a
 * pré-visualização. O "papel" do editor é branco (ou a cor do modelo) nos
 * dois temas: é o e-mail como chega, e não um pedaço do portal.
 * =================================================================== */
.fz-editor { border: 1px solid hsl(var(--border)); border-radius: calc(var(--radius) - 2px); overflow: hidden; }
.fz-editor-barra { display: flex; flex-wrap: wrap; align-items: center; gap: .25rem; padding: .375rem; border-bottom: 1px solid hsl(var(--border)); background: hsl(var(--muted) / .5); }
.fz-editor-sep { width: 1px; align-self: stretch; margin: 0 .25rem; background: hsl(var(--border)); }
.fz-editor-botao { display: inline-flex; align-items: center; justify-content: center; height: 2rem; min-width: 2rem; padding: 0 .375rem; border-radius: calc(var(--radius) - 4px);
  font-size: .8125rem; color: hsl(var(--foreground)); background: transparent; border: 1px solid transparent; cursor: pointer; list-style: none; }
.fz-editor-botao:hover { background: hsl(var(--accent)); }
.fz-editor-botao::-webkit-details-marker { display: none; }
.fz-editor-texto { padding: 0 .5rem; }
.fz-editor-campo { height: 2rem; padding: 0 .375rem; font-size: .8125rem; border: 1px solid hsl(var(--input)); border-radius: calc(var(--radius) - 4px);
  background: hsl(var(--background)); color: hsl(var(--foreground)); }
.fz-campo-curto { width: 5.5rem; }
.fz-editor-cor { position: relative; }
.fz-editor-cor-painel { position: absolute; z-index: 30; top: calc(100% + 4px); left: 0; display: grid; gap: .5rem; padding: .5rem; min-width: 12rem;
  border: 1px solid hsl(var(--border)); border-radius: calc(var(--radius) - 2px); background: hsl(var(--popover)); color: hsl(var(--popover-foreground));
  box-shadow: 0 8px 24px rgb(0 0 0 / .15); }
.fz-cor-seletor { width: 2.25rem; height: 2rem; padding: 0; border: 1px solid hsl(var(--input)); border-radius: calc(var(--radius) - 4px); background: transparent; cursor: pointer; }
.fz-amostra { display: inline-block; width: 1.25rem; height: 1.25rem; border-radius: 9999px; border: 1px solid hsl(var(--border)); cursor: pointer; padding: 0; }
.fz-editor-corpo { display: grid; grid-template-columns: minmax(0, 1fr); }
@media (min-width: 768px) {
  .fz-editor-corpo { grid-template-columns: minmax(0, 1fr) 15rem; }
}
.fz-editor-tela { position: relative; max-height: 32rem; overflow: auto; color: #18181b; }
.fz-editor-cabecalho { display: flex; align-items: center; gap: .75rem; padding: 1rem 1.75rem; font-size: 1.125rem; font-weight: 700; }
.fz-editor-cabecalho img { display: block; height: auto; }
.fz-editor-area { min-height: 14rem; padding: 1.75rem; line-height: 1.6; outline: none; }
.fz-editor-area:focus { box-shadow: inset 0 0 0 2px hsl(var(--ring) / .4); }
.fz-editor-area img { max-width: 100%; height: auto; cursor: pointer; }
.fz-editor-area img[data-fz-selecionada] { outline: 2px solid hsl(var(--primary)); outline-offset: 2px; }
.fz-editor-area h1 { font-size: 1.75em; font-weight: 700; margin: .5em 0; }
.fz-editor-area h2 { font-size: 1.4em; font-weight: 700; margin: .5em 0; }
.fz-editor-area h3 { font-size: 1.15em; font-weight: 700; margin: .5em 0; }
.fz-editor-area p { margin: 0 0 .75em; }
.fz-editor-area a { text-decoration: underline; }
/* O preflight do build congelado zera o marcador de TODA lista (list-style:
   none; padding 0): dentro do editor a lista do e-mail aparecia como linhas
   soltas, embora o e-mail saia com marcadores — os clientes de e-mail usam o
   padrão do navegador. */
.fz-editor-area ul { list-style: disc; padding-left: 1.5em; margin: 0 0 .75em; }
.fz-editor-area ol { list-style: decimal; padding-left: 1.5em; margin: 0 0 .75em; }
.fz-editor-area li { margin: 0 0 .25em; }
.fz-editor-rodape { padding: 1rem 1.75rem; border-top: 1px solid #e4e4e7; font-size: .75rem; line-height: 1.5; }
.fz-editor-descadastro { padding: .75rem 1.75rem; border-top: 1px solid #e4e4e7; font-size: .75rem; color: #71717a; background: #fafafa; }
.fz-editor-alca { position: absolute; width: 12px; height: 12px; border-radius: 2px; background: hsl(var(--primary)); border: 2px solid #ffffff; cursor: nwse-resize; touch-action: none; }
.fz-editor-painel { display: grid; align-content: start; gap: .75rem; padding: .75rem; border-left: 1px solid hsl(var(--border)); background: hsl(var(--muted) / .3); }
.fz-modelo-logo { display: block; max-height: 2.5rem; width: auto; border-radius: 4px; border: 1px solid hsl(var(--border)); }
.fz-previa-modelo { width: 100%; height: 34rem; border: 1px solid hsl(var(--border)); border-radius: calc(var(--radius) - 2px); background: #f4f4f5; }
.fz-previa { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: flex-start; }
.fz-previa-quadro { display: grid; gap: .5rem; margin: 0; max-width: 100%; }
.fz-previa-desktop { width: 640px; max-width: 100%; height: 36rem; border: 1px solid hsl(var(--border)); border-radius: calc(var(--radius) - 2px); background: #f4f4f5; }
.fz-previa-celular { width: 375px; max-width: 100%; height: 36rem; border: 8px solid hsl(var(--foreground) / .85); border-radius: 1.5rem; background: #f4f4f5; }
.fz-quebra { white-space: pre-line; }

/* ------------------------------------------- a fila de investigação (1.6.0) --
 * Utilidades que a fila emite e o build congelado não trouxe: larguras dos
 * controles do filtro, o truncamento das colunas de texto, o divisor da lista
 * do diálogo de colunas e o X dos selos de filtro. Mesma grafia do Tailwind,
 * para a classe emitida casar com a regra. */
.w-24 { width: 6rem; }
.w-28 { width: 7rem; }
.w-32 { width: 8rem; }
.w-44 { width: 11rem; }
.w-64 { width: 16rem; }
.max-w-\[220px\] { max-width: 220px; }
.min-w-\[200px\] { min-width: 200px; }
.min-w-\[280px\] { min-width: 280px; }
.italic { font-style: italic; }
.items-baseline { align-items: baseline; }
.underline-offset-2 { text-underline-offset: 2px; }
.divide-y > :not([hidden]) ~ :not([hidden]) { border-top: 1px solid hsl(var(--border)); }
.hover\:bg-muted-foreground\/20:hover { background-color: hsl(var(--muted-foreground) / 0.2); }

/* ------------------------------------------------- a fila cabe na caixa --
 * (1.6.1, pontos 2, 3 e 6 do Rafael) A tabela de avaliações é
 * `table-layout: fixed` com a largura da caixa: cada coluna tem a largura do
 * colgroup (page.columnWidth) e as de texto dividem o que sobra, cortadas com
 * reticências — o texto inteiro está no title e no detalhe. Sem isto as dez
 * colunas passavam da borda e o que sobrava só aparecia com uma rolagem
 * lateral sem barra. A coluna da caixa de marcação, fixa no colgroup, deixou
 * de receber a sobra da tabela. */
.fz-fila {
  table-layout: fixed;
  width: 100%;
}

.fz-fila th,
.fz-fila td {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0.5rem 0.75rem;
}

.fz-fila th {
  height: 2.5rem;
}

/* As células que carregam dois selos (risco + rótulo) ou duas linhas
 * (input: nome e número) quebram em vez de cortar. */
.fz-fila .fz-fila-quebra {
  white-space: normal;
}

/* Os selos da coluna de risco (risco, rótulo, demonstração): lado a lado
 * enquanto cabem, e com um vão quando quebram. Inline com um espaço entre
 * eles, um selo (22px) era mais alto que a entrelinha da célula (20px), e
 * dois selos em linhas diferentes se sobrepunham. */
.fz-fila .fz-fila-selos {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem;
}

.fz-fila .fz-fila-sel {
  padding: 0;
  text-align: center;
}

.fz-fila .fz-fila-sel label {
  padding: 0.75rem 0;
}

.fz-fila .fz-fila-sel input {
  vertical-align: middle;
}

/* A célula do botão de abrir: o `.fz-fila td` acima vence um `p-1` do
 * Tailwind por especificidade, e com 0.75rem de cada lado o botão de 2rem
 * não cabia numa coluna de 3rem — o olho saía cortado. */
.fz-fila .fz-fila-acao {
  padding: 0.25rem;
  text-align: right;
}

/* --------------------------------------------- a faixa de números da fila --
 * (1.6.1, ponto 5 do Rafael) Os doze cartões que vinham antes da fila viraram
 * chips: o ícone, o rótulo, o valor e, nos operacionais, a proporção. O que
 * recorta é um link; o que já está filtrando fica aceso. */
.fz-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
}

.fz-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  border: 1px solid hsl(var(--border));
  border-radius: 9999px;
  background-color: hsl(var(--muted) / 0.3);
  padding: 0.2rem 0.65rem;
  font-size: 12.5px;
  line-height: 1.25;
  color: hsl(var(--foreground));
  text-decoration: none;
  white-space: nowrap;
}

a.fz-chip:hover,
a.fz-chip:focus-visible {
  border-color: hsl(var(--primary) / 0.6);
  outline: none;
}

.fz-chip-ativo,
a.fz-chip-ativo:hover {
  border-color: hsl(var(--primary));
  background-color: hsl(var(--primary) / 0.12);
}

.fz-chip-rotulo {
  color: hsl(var(--muted-foreground));
}

.fz-chip-valor {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.fz-chip-parte {
  font-size: 11px;
  color: hsl(var(--muted-foreground));
}

/* ------------------------------------------------- o ranking recolhível --
 * (1.6.1, pontos 1 e 5) "Regras que mais pontuaram" DEPOIS da fila, num
 * <details> nativo de lugar fixo; o carimbo da hora no canto diz que o bloco
 * está vivo, e fica laranja num ciclo em que a consulta falhou. */
.fz-ranking {
  border: 1px solid hsl(var(--border));
  border-radius: 0.5rem;
  background-color: hsl(var(--muted) / 0.2);
}

.fz-ranking-titulo {
  cursor: pointer;
  padding: 0.625rem 1rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: hsl(var(--muted-foreground));
}

.fz-ranking-carimbo {
  float: right;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.fz-ranking-carimbo-falha {
  color: hsl(38 92% 50%);
}

.fz-ranking-corpo {
  display: grid;
  gap: 0.5rem;
  padding: 0 1rem 0.75rem;
}

/* ------------------------------------- as conexões de uma avaliação (1.7.0) --
 * O grafo da aba Conexões reaproveita a moldura, a tela, os fios e os
 * círculos do mapa do caminho (.fz-mapa-*). O que é só dele: a CAIXA de uma
 * chave (campo em cima, valor embaixo), a pílula da contagem sobre o fio, o
 * fio mais forte entre o centro e a chave, o HALO da transação marcada e a
 * cor pelo RÓTULO — que vem DEPOIS das cores de risco do mapa, para vencer
 * por ordem na mesma especificidade. Os sufixos são contrato com
 * page.connLeafClass. */
.fz-conexao-chave {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 0 0.5rem;
  border: 1.5px solid hsl(var(--border));
  border-radius: 0.5rem;
  background-color: hsl(var(--card));
  color: hsl(var(--foreground));
  text-decoration: none;
  overflow: hidden;
}

a.fz-conexao-chave:hover,
a.fz-conexao-chave:focus-visible {
  border-color: hsl(var(--primary));
  outline: none;
}

.fz-conexao-campo {
  font-size: 10px;
  line-height: 1;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: hsl(var(--muted-foreground));
}

.fz-conexao-valor {
  max-width: 100%;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  line-height: 1.2;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fz-conexao-contagem {
  position: absolute;
  transform: translate(-50%, -50%);
  padding: 1px 0.375rem;
  border: 1px solid hsl(var(--border));
  border-radius: 9999px;
  background-color: hsl(var(--popover));
  color: hsl(var(--muted-foreground));
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 10px;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

.fz-conexao-fio-chave {
  stroke: hsl(var(--muted-foreground) / 0.6);
  stroke-width: 2;
}

.fz-conexao-no {
  cursor: pointer;
}

.fz-conexao-fraude { background-color: hsl(var(--destructive) / 0.22); border-color: hsl(var(--destructive)); }
.fz-conexao-chargeback { background-color: hsl(271 81% 56% / 0.2); border-color: hsl(271 81% 56%); }
.fz-conexao-legitima { background-color: hsl(142 71% 45% / 0.18); border-color: hsl(142 71% 45%); }

/* O halo da transação MARCADA: a caixa é sr-only dentro do <label>, e o
 * estado é o :checked dela — nada vem de JavaScript. */
.fz-conexao-no:has(input:checked) {
  box-shadow: 0 0 0 3px hsl(var(--background)), 0 0 0 5px hsl(var(--primary));
}

.fz-conexao-no:has(input:focus-visible) {
  border-color: hsl(var(--primary));
}

/* O rodapé do detalhe de uma regra do marketplace (bug 45, 1.7.0). O
 * ui.DialogFooter é `sm:flex-row sm:justify-end sm:space-x-2`, sem quebra:
 * numa regra adquirida são quatro itens (reportar, sugerir, o selo de
 * adquirida e cancelar a aquisição), mais largos que o max-w-2xl, e o
 * justify-end empurrava o excesso para fora pela ESQUERDA — o "Reportar um
 * problema" saía cortado. Aqui a linha quebra e o espaço vira `gap`; a
 * margem do space-x-2 sai, senão o primeiro item de cada linha nova nasceria
 * deslocado. ⚠️ O nome não é fz-mkt-rodape: esse é o rodapé do CARD
 * (space-between), e a primeira versão desta correção o reaproveitou sem
 * querer — os itens se espalharam pela largura. O seletor repete a classe para vencer o
 * `.sm\:space-x-2 > :not([hidden]) ~ :not([hidden])` do build congelado. */
@media (min-width: 640px) {
  .fz-mkt-detalhe-rodape {
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
  }

  .fz-mkt-detalhe-rodape.fz-mkt-detalhe-rodape > :not([hidden]) ~ :not([hidden]) {
    margin-left: 0;
    margin-right: 0;
  }
}

@media (max-width: 639px) {
  .fz-mkt-detalhe-rodape {
    gap: 0.5rem;
  }
}

/* ------------------------------------ os filtros da fila de Avaliações (1.8.0)
 * Decisões do Rafael de 29/10/2026: a caixa inteira dos filtros é
 * RECOLHÍVEL (um <details> lembrado por navegador), os campos se alinham em
 * duas linhas de colunas de largura previsível com o rótulo em cima, e "De"
 * e "Até" viraram um campo de período só (.fz-periodo). */
.fz-filtros {
  border: 1px solid hsl(var(--border));
  border-radius: 0.5rem;
  background-color: hsl(var(--card));
}

.fz-filtros-titulo {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.625rem 1rem;
  font-size: 0.875rem;
  font-weight: 600;
}

.fz-filtros-titulo::-webkit-details-marker,
.fz-mais-filtros-botao::-webkit-details-marker {
  display: none;
}

.fz-filtros-seta {
  color: hsl(var(--muted-foreground));
  transform: rotate(-90deg);
  transition: transform 0.15s;
}

.fz-filtros[open] > .fz-filtros-titulo .fz-filtros-seta {
  transform: none;
}

.fz-filtros-corpo {
  display: grid;
  gap: 0.875rem;
  padding: 0.25rem 1rem 1rem;
}

.fz-filtros-linha {
  display: grid;
  gap: 0.75rem;
  align-items: end;
}

/* Input · Período · Busca · botões.
 *
 * ⚠️ A linha 1 alinha pelo TOPO, e não pela base como as outras. O Período
 * é o único bloco com três andares — rótulo, campo e a fileira de atalhos —
 * e, alinhado pela base, era a fileira de atalhos que encostava no chão da
 * linha: o campo subia 28px e o rótulo "Período" ficava acima de "Input" e
 * "Buscar por ID" (apontado em 2026-10-29, 1.8.2). Pelo topo, rótulos e
 * campos nivelam e os atalhos pendem sozinhos debaixo do campo. O preço é o
 * bloco dos botões, que não tem rótulo e subiria para a linha dos rótulos:
 * ele ganha o recuo de UM rótulo (fonte de .fz-filtro-rotulo × entrelinha
 * 1.5 + o gap de .fz-filtro), o mesmo espaço que os vizinhos gastam com o
 * texto de cima. Mexer no rótulo ou no gap exige mexer aqui.
 *
 * A coluna do Período leva 1.7 partes para o período ABSOLUTO (duas datas
 * com hora e o selo de fuso, ~470px) caber inteiro nas larguras comuns; o
 * campo de ID cede, que não precisa dessa largura. */
.fz-filtros-linha-1 {
  grid-template-columns: minmax(12rem, 1fr) minmax(16rem, 1.7fr) minmax(12rem, 1fr) auto;
  align-items: start;
}

.fz-filtros-linha-1 > .fz-filtros-acoes {
  padding-top: calc(0.75rem * 1.5 + 0.25rem);
}

/* Risco · Estado · Regra, colunas iguais */
.fz-filtros-linha-2,
.fz-filtros-linha-mais {
  grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
}

.fz-filtro {
  display: grid;
  gap: 0.25rem;
  min-width: 0;
  align-content: start;
}

.fz-filtro-rotulo {
  font-size: 0.75rem;
  color: hsl(var(--muted-foreground));
}

.fz-filtros-acoes {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.fz-filtro-faixa {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 0.375rem;
  align-items: center;
}

.fz-filtro-caixa {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.875rem;
  min-height: 2.5rem;
}

.fz-mais-filtros {
  display: grid;
  gap: 0.75rem;
}

.fz-mais-filtros-botao {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  width: max-content;
  height: 2.25rem;
  padding: 0 0.875rem;
  border: 1px solid hsl(var(--border));
  border-radius: calc(var(--radius) - 2px);
  font-size: 0.875rem;
  font-weight: 500;
}

.fz-mais-filtros-botao:hover {
  background-color: hsl(var(--accent));
}

.fz-mais-filtros[open] > .fz-mais-filtros-botao {
  border-color: hsl(var(--primary));
}

.fz-mais-filtros-contagem {
  display: inline-grid;
  place-items: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.375rem;
  border-radius: 999px;
  background-color: hsl(var(--primary));
  color: hsl(var(--primary-foreground));
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.fz-ranking-periodo {
  margin-left: 0.5rem;
  font-weight: 400;
}

.fz-ranking-periodo::before {
  content: "· ";
}

/* --- o campo de período ---------------------------------------------------- */
.fz-periodo {
  position: relative;
}

.fz-periodo-botao {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  /* ⚠️ min-width: 0 porque o botão é item de GRADE (.fz-filtro é grid), e a
   * grade não deixa um item ficar mais estreito que o próprio conteúdo: com
   * o período absoluto o botão passava 21px da coluna e invadia "Buscar por
   * ID" — o `width: 100%` não segura isso. Com o mínimo zerado ele para na
   * borda da coluna e o .fz-periodo-texto ganha as reticências que já tinha. */
  min-width: 0;
  height: 2.5rem;
  padding: 0 0.75rem;
  border: 1px solid hsl(var(--input));
  border-radius: calc(var(--radius) - 2px);
  background-color: hsl(var(--background));
  font-size: 0.875rem;
  text-align: left;
}

.fz-periodo-botao:hover {
  border-color: hsl(var(--muted-foreground) / 0.6);
}

.fz-periodo-botao[aria-expanded="true"] {
  border-color: hsl(var(--primary));
  box-shadow: 0 0 0 3px hsl(var(--primary) / 0.15);
}

.fz-periodo-botao:focus-visible,
.fz-periodo-atalhos button:focus-visible,
.fz-periodo-opcoes button:focus-visible,
.fz-periodo-cal button:focus-visible,
.fz-periodo-abas button:focus-visible {
  outline: 2px solid hsl(var(--ring));
  outline-offset: 2px;
}

.fz-periodo-texto {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fz-periodo-fuso {
  flex: none;
  padding: 1px 6px;
  border: 1px solid hsl(var(--border));
  border-radius: 4px;
  font-size: 11px;
  font-weight: 600;
  color: hsl(var(--muted-foreground));
  font-variant-numeric: tabular-nums;
}

.fz-periodo-atalhos {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}

.fz-periodo-atalhos button {
  height: 1.5rem;
  padding: 0 0.5rem;
  border: 1px solid transparent;
  border-radius: 0.375rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: hsl(var(--muted-foreground));
}

.fz-periodo-atalhos button:hover {
  color: hsl(var(--foreground));
}

.fz-periodo-atalhos button[aria-pressed="true"] {
  color: hsl(var(--primary));
  border-color: hsl(var(--primary) / 0.6);
  background-color: hsl(var(--primary) / 0.1);
}

.fz-periodo-painel {
  position: absolute;
  z-index: 40;
  /* logo abaixo do botão: o rótulo (1rem), o vão e o botão (2.5rem) */
  top: 4rem;
  left: 0;
  width: 40rem;
  max-width: calc(100vw - 2rem);
  border: 1px solid hsl(var(--border));
  border-radius: 0.75rem;
  background-color: hsl(var(--popover));
  color: hsl(var(--popover-foreground));
  box-shadow: 0 18px 50px rgb(0 0 0 / 0.35);
}

.fz-periodo-corpo {
  display: grid;
  gap: 0.875rem;
  padding: 0.875rem;
  max-height: min(70vh, 36rem);
  overflow-y: auto;
}

.fz-periodo-abas {
  display: inline-flex;
  width: max-content;
  padding: 2px;
  border: 1px solid hsl(var(--border));
  border-radius: 999px;
}

.fz-periodo-abas button {
  height: 1.875rem;
  padding: 0 1rem;
  border-radius: 999px;
  font-size: 0.875rem;
  font-weight: 600;
  color: hsl(var(--muted-foreground));
}

.fz-periodo-abas button[aria-selected="true"] {
  background-color: hsl(var(--primary));
  color: hsl(var(--primary-foreground));
}

.fz-periodo-grade {
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  gap: 0.5rem 0.75rem;
  align-items: center;
}

.fz-periodo-unidade {
  font-size: 0.8125rem;
  color: hsl(var(--muted-foreground));
}

.fz-periodo-opcoes {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
}

.fz-periodo-opcoes button {
  min-width: 3.25rem;
  height: 2rem;
  padding: 0 0.625rem;
  border: 1px solid hsl(var(--border));
  border-radius: 0.375rem;
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
}

.fz-periodo-opcoes button:hover {
  background-color: hsl(var(--accent));
}

.fz-periodo-opcoes button[aria-pressed="true"] {
  border-color: hsl(var(--primary));
  background-color: hsl(var(--primary) / 0.12);
}

.fz-periodo-duracao {
  display: grid;
  grid-template-columns: 7rem 11rem 1fr;
  gap: 0.75rem;
  align-items: end;
  margin-top: 0.875rem;
}

.fz-periodo-entrada {
  width: 100%;
  height: 2.25rem;
  padding: 0 0.625rem;
  border: 1px solid hsl(var(--input));
  border-radius: calc(var(--radius) - 2px);
  background-color: hsl(var(--background));
  color: hsl(var(--foreground));
  font-size: 0.875rem;
}

.fz-periodo-dica {
  font-size: 0.75rem;
  color: hsl(var(--muted-foreground));
}

.fz-periodo-erro {
  font-size: 0.8125rem;
  color: hsl(var(--destructive));
}

.fz-periodo-meses {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.25rem;
}

.fz-periodo-mes-topo {
  display: grid;
  grid-template-columns: 1.75rem 1fr 1.75rem;
  align-items: center;
  text-align: center;
  margin-bottom: 0.375rem;
  font-size: 0.875rem;
}

.fz-periodo-nav {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 0.375rem;
  font-size: 1rem;
}

.fz-periodo-nav:hover:not(:disabled) {
  background-color: hsl(var(--accent));
}

.fz-periodo-nav:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

.fz-periodo-cal {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 2px;
  font-variant-numeric: tabular-nums;
}

.fz-periodo-cal [data-fz-periodo-semana] {
  padding: 2px 0;
  text-align: center;
  font-size: 11px;
  color: hsl(var(--muted-foreground));
}

.fz-periodo-cal button {
  height: 2rem;
  border: 1px solid transparent;
  border-radius: 0.375rem;
  font-size: 0.8125rem;
}

.fz-periodo-cal button:hover:not(:disabled) {
  border-color: hsl(var(--border));
}

.fz-periodo-cal button[data-hoje] {
  border-color: hsl(var(--muted-foreground) / 0.7);
}

.fz-periodo-cal button[data-meio] {
  border-radius: 0;
  background-color: hsl(var(--primary) / 0.14);
}

.fz-periodo-cal button[data-ponta] {
  background-color: hsl(var(--primary));
  color: hsl(var(--primary-foreground));
}

.fz-periodo-cal button:disabled {
  color: hsl(var(--muted-foreground) / 0.4);
  cursor: not-allowed;
}

.fz-periodo-datas {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.625rem;
  margin-top: 0.75rem;
}

.fz-periodo-rodape {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.625rem 0.875rem;
  border-top: 1px solid hsl(var(--border));
}

.fz-periodo-fuso-campo {
  display: grid;
  gap: 0.25rem;
  min-width: 14rem;
}

.fz-periodo-botoes {
  display: flex;
  gap: 0.5rem;
}

@media (max-width: 1023px) {
  .fz-filtros-linha-1 {
    grid-template-columns: 1fr 1fr;
  }
}

@media (max-width: 639px) {
  .fz-filtros-linha-1 {
    grid-template-columns: 1fr;
  }
  .fz-periodo-meses {
    grid-template-columns: 1fr;
  }
  .fz-periodo-datas,
  .fz-periodo-duracao {
    grid-template-columns: 1fr 1fr;
  }
  .fz-periodo-grade {
    grid-template-columns: 1fr;
    gap: 0.25rem;
  }
  .fz-periodo-fuso-campo {
    min-width: 0;
    width: 100%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .fz-filtros-seta {
    transition: none;
  }
}

/* O ranking com mais de dez regras (1.8.0): a caixa tem a altura de dez
 * linhas da .fz-fila (2.25rem + a borda) mais o cabeçalho, e o resto rola
 * DENTRO dela, com o cabeçalho parado em cima. O invólucro do ui.Table tem
 * overflow próprio; sem soltá-lo, o sticky grudaria nele, que não rola. */
.fz-ranking-rolagem {
  max-height: 26rem;
  overflow-y: auto;
}

.fz-ranking-rolagem > div {
  overflow: visible;
}

.fz-ranking-rolagem thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background-color: hsl(var(--card));
}

/* Os avisos de uma regra no rastro da avaliação (1.8.1): a lista de
 * mensagens da unidade dentro do callout. */
.fz-avisos-lista {
  list-style: disc;
  padding-left: 1rem;
  margin-top: 0.25rem;
}
