/* 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-negar .fz-bloco-caixa { fill: hsl(var(--destructive) / 0.12); stroke: hsl(var(--destructive)); }
.fz-bloco-fim .fz-bloco-caixa { fill: hsl(var(--muted)); stroke: hsl(var(--muted-foreground)); }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/* A animação de saída dos painéis. O build congelado só trouxe a variante
 * `data-[swipe=end]`, então TODO painel do portal — diálogo, popover, select —
 * emitia `data-[state=closed]:animate-out` sem regra nenhuma por trás e sumia
 * seco, enquanto as classes irmãs de fade e zoom já existiam e não tinham a que
 * se somar. */
.data-\[state\=closed\]\:animate-out[data-state='closed'] {
  animation-name: exit;
  animation-duration: 0.15s;
  --tw-exit-opacity: initial;
  --tw-exit-scale: initial;
  --tw-exit-rotate: initial;
  --tw-exit-translate-x: initial;
  --tw-exit-translate-y: initial;
}

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