{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "nav-drawer",
  "title": "Nav Drawer",
  "author": "Visant Labs <visantsupply@gmail.com>",
  "description": "O menu mobile feito uma vez direito, em dois desenhos: gaveta lateral ou página inteira brutalista com imagem. Foco preso, Escape, retorno do foco e trava de rolagem vindos do Radix Dialog.",
  "dependencies": [
    "@radix-ui/react-dialog"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/visant/ui/nav-drawer.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as Dialog from \"@radix-ui/react-dialog\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Nav Drawer — o menu mobile feito uma vez, direito.\n *\n * ── Por que isto é um item do registry ────────────────────────────────────\n *\n * Num audit real de site entregue, \"Experience Design\" tirou **2 de 4**, e a\n * causa foi uma só: o botão de menu no mobile abria uma `<div>` escrita à mão.\n * Sem foco preso, sem `Escape`, sem `aria-expanded`, com o corpo da página\n * rolando por baixo. É o mesmo defeito toda vez, em todo projeto, porque menu\n * mobile é a peça que se escreve às onze da noite antes do deploy.\n *\n * Nada disso se resolve com CSS bonito, e nada disso é original: o\n * **Radix Dialog** já entrega prisão de foco, retorno do foco ao gatilho,\n * `Escape`, trava de rolagem, `aria-modal` e o par `aria-expanded`/`aria-controls`\n * no gatilho. O trabalho da casa aqui é a superfície e a entrada — não\n * reimplementar acessibilidade que já existe testada.\n *\n * ── Por que a animação é CSS e não Framer Motion ──────────────────────────\n *\n * Saída animada num portal exige `forceMount` + `AnimatePresence` + estado\n * espelhado — três peças pra manter sincronizadas, e é exatamente aí que o\n * drawer some antes da hora ou fica preso aberto. O Radix expõe\n * `data-state=\"open|closed\"` e **espera a animação CSS terminar** antes de\n * desmontar. Uma `@keyframes` faz o trabalho, sem estado nenhum. É o motivo de\n * a animação morar no bloco `css` do item: instalar sem ela é instalar um\n * drawer que aparece a seco.\n *\n * ── Toda entrada tem uma saída, e saída não é entrada de trás pra frente ───\n *\n * Véu, painel e itens têm animação nos DOIS estados. Faltar a saída é o erro\n * clássico: a peça entra com capricho e depois desaparece a seco, e o gesto\n * inteiro lê como interrompido.\n *\n * A primeira versão economizava keyframes com `animation-direction: reverse` —\n * a mesma `@keyframes` da entrada, tocada de trás pra frente. **Não funciona, e\n * o motivo é estrutural**: o Radix decide se espera a saída comparando o\n * `animation-name` antes e depois da troca de estado. Mesmo nome, ele conclui\n * que nenhuma animação nova começou e desmonta o painel no mesmo quadro.\n * Medido: 40ms depois do clique de fechar, os nós já estavam fora do documento.\n * Ou seja, a regra de desenho (\"saída é gesto próprio, não entrada invertida\")\n * está gravada no framework — quem tenta a economia perde a saída inteira.\n *\n * Então cada estado tem keyframe com nome próprio, e as diferenças são de\n * intenção, não de sinal trocado:\n *\n * · **Painel**: entra em 420ms com `--ease-drawer` (chega macio), sai em 240ms\n *   com curva de aceleração (vai embora rápido). Ninguém quer esperar pra sair.\n * · **Véu**: some em 140ms, mais rápido que o painel — a página de trás\n *   reaparece antes de o painel terminar de sair, e é isso que faz a saída\n *   parecer leve em vez de arrastada.\n * · **Itens**: entram de cima pra baixo em 460ms com 14px de percurso; saem de\n *   BAIXO PRA CIMA, rápido, com metade do percurso. Invertida, a cascata sairia\n *   do primeiro pro último e a lista pareceria se desfazendo pelo topo com o\n *   painel ainda na tela. Ver o `--io` no `NavDrawerList`.\n *\n * A saída dos itens tem que caber DENTRO da saída do painel — o Radix espera a\n * animação do painel, não a dos filhos, e item que ainda anima quando o painel\n * desmonta some no meio do gesto. Por isso ela não é um número: é uma FRAÇÃO de\n * `--dur-base`, 0,6 de duração e 0,1 por degrau de atraso. Com teto de 3 degraus\n * o pior caso dá 0,9 do painel, em qualquer marca.\n *\n * A primeira tentativa foi com número fixo (140ms + 22ms por degrau = 206ms) e\n * já nascia errada: `--dur-base` neste tema é 200ms, não os 240ms do fallback\n * que eu tinha usado pra fazer a conta. Timing amarrado a token se escreve em\n * token — número fixo aqui é um acerto que dura até a próxima marca.\n *\n * ── A entrada escalonada, e por que o índice não vem do consumidor ─────────\n *\n * Os itens sobem em cascata de ~45ms. O `--i` de cada um é escrito pelo\n * `NavDrawerList`, que embrulha cada filho num `<li>` — pedir `index` na prop\n * seria transferir pro consumidor uma contabilidade que quebra em silêncio no\n * dia em que alguém reordena o menu.\n *\n * O teto do escalonamento é deliberado: a partir do 8º item o atraso congela.\n * Um menu de 14 links com cascata linear leva 630ms pra terminar de aparecer, e\n * nessa altura já não é elegância, é a interface fazendo a pessoa esperar.\n *\n * ── O que a peça NÃO faz ──────────────────────────────────────────────────\n *\n * · **Não decide o breakpoint.** Onde o menu vira drawer é decisão da navbar do\n *   projeto (`md:hidden` no gatilho). Cravar aqui obrigaria fork no primeiro\n *   cliente com menu curto.\n * · **Não sabe qual rota está ativa.** `active` entra por prop — a peça não\n *   importa o router de ninguém.\n * · **Não fecha sozinha ao navegar.** Em app com router de cliente, o clique no\n *   link não desmonta nada; use `NavDrawerClose asChild` em volta do link (o\n *   caminho normal) ou controle `open` por fora.\n */\n\nexport interface NavDrawerContentProps\n  extends Omit<React.ComponentPropsWithoutRef<typeof Dialog.Content>, \"title\"> {\n  /** De onde o painel entra. `right` é o padrão do polegar destro. */\n  side?: \"right\" | \"left\" | \"top\";\n  /**\n   * Obrigatório: é o nome do diálogo pro leitor de tela. Fica invisível a menos\n   * que `showTitle`. Sem ele o Radix reclama no console — e com razão, porque um\n   * diálogo sem nome é um buraco na navegação por voz.\n   */\n  title: React.ReactNode;\n  showTitle?: boolean;\n  /**\n   * Slot da marca no topo do painel — logo, wordmark, monograma.\n   *\n   * Existe porque o drawer costuma cobrir o header inteiro: sem ele, o momento\n   * em que o menu abre é o único momento da navegação em que a marca some da\n   * tela. Entra por slot e não por prop de `src` porque cada projeto tem o\n   * componente de logo dele, com o SVG e as variantes de contraste da marca.\n   */\n  brand?: React.ReactNode;\n  /** Rodapé do painel: idioma, telefone, redes. Fica colado embaixo. */\n  footer?: React.ReactNode;\n  /**\n   * O desenho do painel.\n   *\n   * · `panel` (padrão): a gaveta de 26rem que entra pela borda, com cantos do tema.\n   * · `full`: página inteira e brutalista. Sem raio, régua de 1px entre os\n   *   links, tipo enorme em caixa alta, e o hover inverte a linha inteira. Com\n   *   `media`, a tela divide: imagem à esquerda, menu à direita.\n   */\n  variant?: \"panel\" | \"full\";\n  /**\n   * A imagem da metade esquerda no `variant=\"full\"`. Slot pelo mesmo motivo do\n   * `brand`: cada projeto tem o seu componente de imagem otimizada. Some abaixo\n   * de `md`, onde não há largura pra dividir a tela. Ignorado no `panel`.\n   */\n  media?: React.ReactNode;\n}\n\n/** O desenho do painel chega aos itens por contexto: o item não pede prop. */\nconst VarianteCtx = React.createContext<\"panel\" | \"full\">(\"panel\");\n\nexport const NavDrawer = Dialog.Root;\nexport const NavDrawerTrigger = Dialog.Trigger;\nexport const NavDrawerClose = Dialog.Close;\nexport const NavDrawerPortal = Dialog.Portal;\n\nexport const NavDrawerContent = React.forwardRef<\n  React.ElementRef<typeof Dialog.Content>,\n  NavDrawerContentProps\n>(({ side = \"right\", title, showTitle = false, brand, footer, variant = \"panel\", media, className, children, ...props }, ref) =>\n  variant === \"full\" ? (\n    <PaginaInteira ref={ref} side={side} title={title} brand={brand} footer={footer} media={media} className={className} {...props}>\n      {children}\n    </PaginaInteira>\n  ) : (\n  <Dialog.Portal>\n    {/*\n     * O véu não é só escurecer: o `backdrop-blur` é o que separa o painel de uma\n     * página de fundo claro, onde sombra sozinha não aparece. E ele é o alvo de\n     * clique-fora — que o Radix já liga sem precisar de `onClick` aqui.\n     */}\n    <Dialog.Overlay className=\"nav-drawer-overlay fixed inset-0 z-50 bg-foreground/25 backdrop-blur-[2px]\" />\n    <Dialog.Content\n      ref={ref}\n      data-side={side}\n      className={cn(\n        \"nav-drawer-panel fixed z-50 flex flex-col gap-6 bg-card text-foreground\",\n        \"[box-shadow:var(--e-modal,0_24px_64px_-24px_rgb(0_0_0/0.45))]\",\n        \"outline-none focus-visible:[box-shadow:var(--focus)]\",\n        // `100dvh` e não `100vh`: no Safari do iPhone o `vh` conta a barra de\n        // endereço que já sumiu, e o rodapé do painel fica atrás do polegar.\n        side === \"top\"\n          ? \"inset-x-0 top-0 max-h-[85dvh] rounded-b-[var(--r-surface,0.875rem)] px-6 pb-8 pt-6\"\n          : cn(\n              \"inset-y-0 h-dvh w-[min(92vw,26rem)] px-7 pb-8 pt-6\",\n              side === \"right\"\n                ? \"right-0 rounded-l-[var(--r-surface,0.875rem)]\"\n                : \"left-0 rounded-r-[var(--r-surface,0.875rem)]\",\n            ),\n        className,\n      )}\n      {...props}\n    >\n      <div className=\"flex items-start justify-between gap-4\">\n        <div className=\"min-w-0 flex-1\">\n          {brand && <div className=\"mb-1\">{brand}</div>}\n          <Dialog.Title\n            className={cn(\n              \"text-[length:var(--text-rotulo,0.6875rem)] uppercase tracking-[0.14em] text-muted-foreground\",\n              !showTitle && \"sr-only\",\n            )}\n          >\n            {title}\n          </Dialog.Title>\n        </div>\n        <Dialog.Close\n          aria-label=\"Fechar menu\"\n          className={cn(\n            \"-mr-2 -mt-1 grid size-9 shrink-0 place-items-center rounded-[var(--r-control,0.5rem)]\",\n            \"text-muted-foreground transition-colors [transition-duration:var(--dur-fast,140ms)]\",\n            \"hover:bg-muted hover:text-foreground\",\n            \"outline-none focus-visible:[box-shadow:var(--focus)]\",\n          )}\n        >\n          {/* Ícone desenhado, nunca um caractere: \"×\" muda de forma e de peso a\n              cada fonte de marca, e some quando a fonte não carrega. */}\n          <svg viewBox=\"0 0 16 16\" className=\"size-4\" aria-hidden=\"true\" fill=\"none\">\n            <path\n              d=\"M3.5 3.5l9 9M12.5 3.5l-9 9\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.4\"\n              strokeLinecap=\"round\"\n            />\n          </svg>\n        </Dialog.Close>\n      </div>\n\n      {/*\n       * `overflow-y-auto` + `overscroll-contain`: com o menu longo o painel rola\n       * sozinho, e o `contain` impede que a rolagem \"vaze\" pra página atrás\n       * quando chega no fim da lista — o efeito de corrente que denuncia\n       * overlay mal feito no iOS.\n       */}\n      <div className=\"-mx-2 flex-1 overflow-y-auto overscroll-contain px-2\">{children}</div>\n\n      {footer && (\n        <div className=\"border-t border-border pt-5 text-[length:var(--text-apoio,0.8125rem)] text-muted-foreground\">\n          {footer}\n        </div>\n      )}\n    </Dialog.Content>\n  </Dialog.Portal>\n  ),\n);\nNavDrawerContent.displayName = \"NavDrawerContent\";\n\n/**\n * A variante `full`: o menu vira a página.\n *\n * Brutalista no sentido de mostrar a estrutura em vez de escondê-la: régua de\n * 1px na cor do texto entre todos os links, nenhum canto arredondado, nenhuma\n * sombra, tipo grande o bastante pra o menu ser o assunto da tela. O hover não\n * desliza um rótulo: inverte a linha inteira, que é o gesto mais seco possível\n * e por isso o que combina.\n *\n * A acessibilidade é a mesma do painel, porque o `Dialog` é o mesmo: foco preso,\n * Escape, retorno do foco e trava de rolagem. Só a superfície muda.\n */\nconst PaginaInteira = React.forwardRef<\n  React.ElementRef<typeof Dialog.Content>,\n  Omit<NavDrawerContentProps, \"variant\" | \"showTitle\">\n>(({ side = \"right\", title, brand, footer, media, className, children, ...props }, ref) => (\n  <Dialog.Portal>\n    <Dialog.Overlay className=\"nav-drawer-overlay fixed inset-0 z-50 bg-background\" />\n    <Dialog.Content\n      ref={ref}\n      data-side={side}\n      data-variant=\"full\"\n      className={cn(\n        \"nav-drawer-panel fixed inset-0 z-50 grid h-dvh w-screen bg-background text-foreground outline-none\",\n        media ? \"md:grid-cols-2\" : \"grid-cols-1\",\n        className,\n      )}\n      {...props}\n    >\n      {media && (\n        <div className=\"relative hidden min-h-0 overflow-hidden border-r border-foreground md:block [&>*]:h-full [&>*]:w-full\">\n          {media}\n        </div>\n      )}\n      <div className=\"flex min-h-0 flex-col\">\n        <div className=\"flex h-16 shrink-0 items-stretch justify-between border-b border-foreground\">\n          <div className=\"flex min-w-0 items-center px-6\">\n            {brand}\n            <Dialog.Title className=\"sr-only\">{title}</Dialog.Title>\n          </div>\n          <Dialog.Close\n            aria-label=\"Fechar menu\"\n            className={cn(\n              \"grid w-16 shrink-0 place-items-center border-l border-foreground\",\n              \"transition-colors [transition-duration:var(--dur-fast,140ms)]\",\n              \"hover:bg-foreground hover:text-background\",\n              \"outline-none focus-visible:[box-shadow:var(--focus)]\",\n            )}\n          >\n            <svg viewBox=\"0 0 16 16\" className=\"size-5\" aria-hidden=\"true\" fill=\"none\">\n              <path d=\"M2.5 2.5l11 11M13.5 2.5l-11 11\" stroke=\"currentColor\" strokeWidth=\"1.5\" />\n            </svg>\n          </Dialog.Close>\n        </div>\n        <VarianteCtx.Provider value=\"full\">\n          <div className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain\">{children}</div>\n        </VarianteCtx.Provider>\n        {footer && (\n          <div className=\"shrink-0 border-t border-foreground px-6 py-4 font-mono text-[length:var(--text-rotulo,0.6875rem)] uppercase tracking-[0.12em]\">\n            {footer}\n          </div>\n        )}\n      </div>\n    </Dialog.Content>\n  </Dialog.Portal>\n));\nPaginaInteira.displayName = \"NavDrawerFullPage\";\n\n/**\n * Embrulha cada filho num `<li>` e escreve os DOIS índices da cascata.\n *\n * `--i` é a ordem de entrada, de cima pra baixo. `--io` é a ordem de saída, de\n * baixo pra cima — e não é capricho: a saída não é a entrada tocada de trás pra\n * frente. Se ela fosse, o primeiro item sumiria primeiro e a lista pareceria se\n * desfazendo pelo topo enquanto o painel ainda está na tela. Saindo do fim pro\n * começo, o olho acompanha a borda do painel indo embora — a lista sai junto\n * com o painel, e não apesar dele.\n */\nexport function NavDrawerList({\n  className,\n  children,\n  ...props\n}: React.HTMLAttributes<HTMLUListElement>) {\n  const itens = React.Children.toArray(children).filter(React.isValidElement);\n  return (\n    <ul className={cn(\"nav-drawer-list flex flex-col\", className)} {...props}>\n      {itens.map((child, i) => (\n        <li\n          key={(child as React.ReactElement).key ?? i}\n          style={\n            {\n              // Teto em 7 na entrada: ver a nota sobre menu de 14 links.\n              \"--i\": Math.min(i, 7),\n              // Teto em 3 na saída, e mais apertado de propósito: a cascata de\n              // saída inteira tem que caber DENTRO da saída do painel. O Radix\n              // espera a animação do painel, não a dos filhos — item que ainda\n              // está animando quando o painel desmonta simplesmente some no meio\n              // do gesto.\n              \"--io\": Math.min(itens.length - 1 - i, 3),\n            } as React.CSSProperties\n          }\n        >\n          {child}\n        </li>\n      ))}\n    </ul>\n  );\n}\n\nexport interface NavDrawerItemProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {\n  /** Marca a rota atual. A peça não descobre isso sozinha — ver a nota. */\n  active?: boolean;\n  /** Número/rótulo à direita: contagem, ano, \"novo\". Nunca decoração. */\n  meta?: React.ReactNode;\n  /** Renderiza no filho — `<Link>` do Next, `<NavLink>` do router. */\n  asChild?: boolean;\n}\n\n// `forwardRef` não é zelo: `<NavDrawerClose asChild>` em volta do item é o uso\n// normal (fechar ao navegar), e o `Slot` do Radix passa uma ref. Sem encaminhar,\n// o React avisa no console de todo projeto que usar a peça do jeito certo.\nexport const NavDrawerItem = React.forwardRef<HTMLAnchorElement, NavDrawerItemProps>(function NavDrawerItem(\n  { active = false, meta, asChild = false, className, children, ...props },\n  ref,\n) {\n  const cheia = React.useContext(VarianteCtx) === \"full\";\n  /*\n   * `asChild` aqui não pode ser o `Slot` puro: o Slot funde props no filho mas\n   * mantém os filhos DELE, e a peça precisa reparentar — o texto do link tem que\n   * entrar dentro do `<span>` que carrega a régua. Com Slot, o `<Link>` do Next\n   * viria com dois filhos (rótulo e meta) e estouraria no `Children.only`.\n   * Clonar é o que faz o `<Link>` continuar sendo a âncora de verdade, com o\n   * prefetch e o roteamento dele, e ainda assim receber a anatomia daqui.\n   */\n  const label = (\n    <>\n      {/*\n       * O rótulo anda 6px no hover e a régua cresce por baixo. O deslocamento é\n       * do SPAN e não do `<a>`: mover a âncora inteira move junto o alvo de\n       * clique, e o item foge do cursor no instante em que a pessoa mira.\n       */}\n      <span className=\"nav-drawer-label relative inline-block\">\n        {asChild && React.isValidElement(children)\n          ? (children as React.ReactElement<{ children?: React.ReactNode }>).props.children\n          : children}\n        <span\n          aria-hidden=\"true\"\n          className=\"nav-drawer-rule absolute -bottom-0.5 left-0 block h-px w-full origin-left bg-[var(--brand,currentColor)]\"\n        />\n      </span>\n      {meta && (\n        <span className=\"nav-drawer-meta font-mono text-[length:var(--text-rotulo,0.6875rem)] tabular-nums tracking-[0.1em] text-muted-foreground\">\n          {meta}\n        </span>\n      )}\n    </>\n  );\n\n  const shared = {\n    ref,\n    \"data-active\": active || undefined,\n    // `data-active` pinta; `aria-current` INFORMA. Sem ele o leitor de tela lê\n    // quatro links iguais e a pessoa não descobre em qual página está — que é\n    // justamente a pergunta que um menu existe pra responder. São dois atributos\n    // porque são dois públicos, e nenhum dos dois substitui o outro.\n    \"aria-current\": active ? (\"page\" as const) : undefined,\n    className: cn(\n      \"nav-drawer-item group/nav relative flex items-baseline justify-between gap-4\",\n      cheia\n        ? [\n            // Página inteira: a linha é a régua, o hover inverte a linha toda.\n            \"border-b border-foreground px-6 py-5 text-foreground\",\n            \"text-[clamp(2rem,5.5vw,4.5rem)] font-bold uppercase leading-[0.9] tracking-[-0.045em]\",\n            \"transition-colors [transition-duration:var(--dur-fast,140ms)]\",\n            \"hover:bg-foreground hover:text-background data-[active]:bg-foreground data-[active]:text-background\",\n            \"[&_.nav-drawer-rule]:hidden [&_.nav-drawer-meta]:text-current\",\n            \"outline-none focus-visible:[box-shadow:var(--focus)]\",\n          ]\n        : [\n            \"py-3 text-[length:var(--text-titulo,1.375rem)] font-medium tracking-[-0.02em]\",\n            \"text-muted-foreground transition-colors [transition-duration:var(--dur-fast,140ms)]\",\n            \"hover:text-foreground data-[active]:text-foreground\",\n            \"outline-none focus-visible:[box-shadow:var(--focus)] focus-visible:rounded-[var(--r-control,0.5rem)]\",\n          ],\n      className,\n    ),\n    ...props,\n  };\n\n  if (asChild && React.isValidElement(children)) {\n    const child = children as React.ReactElement<Record<string, unknown>>;\n    return React.cloneElement(\n      child,\n      { ...shared, className: cn(shared.className, child.props.className as string) },\n      label,\n    );\n  }\n\n  return <a {...shared}>{label}</a>;\n});\n\n/**\n * O gatilho desenhado: três traços que viram X quando o painel abre.\n *\n * Lê o `data-state` que o próprio `Dialog.Trigger` escreve, então não existe um\n * segundo estado pra manter em sincronia — o defeito clássico é o ícone ficar em\n * X depois de fechar por `Escape`, porque quem fechou não foi o clique.\n *\n * ── Por que três traços, e por que o botão tem caixa ──────────────────────────\n *\n * A versão anterior desenhava DOIS traços soltos num quadrado transparente. Duas\n * coisas quebravam ao mesmo tempo: dois traços não são o sinal universal de menu\n * (o olho lê \"igual\", não \"menu\"), e sem borda nem fundo em repouso o ícone\n * ficava boiando no header — nada dizia onde termina o alvo de clique, então\n * ninguém sabia que aquilo era um botão até passar o mouse.\n *\n * Agora são três traços dentro de uma caixa de botão de verdade: 44px de alvo (o\n * mínimo de toque), borda e superfície do tema em repouso. O traço do meio some\n * encolhendo pela esquerda quando abre — se ele apenas apagasse, o X apareceria\n * com um fantasma no centro no meio da transição.\n *\n * A geometria é o que faz o X fechar: caixa de 10px, traços em 0 / 4,25 / 8,5.\n * O CSS do item leva o primeiro traço 4,25px pra baixo e o último 4,25px pra\n * cima — os dois pousam exatamente no meio, que é onde o X cruza.\n *\n * Use dentro de `<NavDrawerTrigger asChild>`.\n */\nexport const NavDrawerBurger = React.forwardRef<\n  HTMLButtonElement,\n  React.ButtonHTMLAttributes<HTMLButtonElement> & { label?: string }\n>(({ label = \"Menu\", className, ...props }, ref) => (\n  <button\n    ref={ref}\n    type=\"button\"\n    aria-label={label}\n    className={cn(\n      \"nav-drawer-burger group/burger grid size-11 place-items-center\",\n      // Caixa: o botão precisa ter borda em repouso, senão o ícone boia.\n      \"rounded-[var(--r-control,0.5rem)] border border-border bg-card text-foreground\",\n      \"transition-[background-color,border-color] [transition-duration:var(--dur-fast,140ms)]\",\n      \"hover:bg-muted hover:border-foreground/25\",\n      \"outline-none focus-visible:[box-shadow:var(--focus)]\",\n      className,\n    )}\n    {...props}\n  >\n    <span aria-hidden=\"true\" className=\"relative block h-[10px] w-[22px]\">\n      <span className=\"burger-bar absolute left-0 top-0 block h-[1.5px] w-full rounded-full bg-current\" />\n      {/*\n       * O traço do meio não usa `:first-child`/`:last-child`, então o CSS do item\n       * não o alcança — a saída dele mora aqui, e encolhe pela esquerda em vez de\n       * só apagar, pra não deixar fantasma no cruzamento do X.\n       */}\n      <span\n        className={cn(\n          \"burger-bar absolute left-0 top-[4.25px] block h-[1.5px] w-full origin-left rounded-full bg-current\",\n          \"transition-[transform,opacity] [transition-duration:var(--dur-fast,140ms)] [transition-timing-function:var(--ease-out,ease)]\",\n          \"group-data-[state=open]/burger:scale-x-0 group-data-[state=open]/burger:opacity-0\",\n          \"motion-reduce:transition-none\",\n        )}\n      />\n      <span className=\"burger-bar absolute bottom-0 left-0 block h-[1.5px] w-full rounded-full bg-current\" />\n    </span>\n  </button>\n));\nNavDrawerBurger.displayName = \"NavDrawerBurger\";\n",
      "type": "registry:ui"
    }
  ],
  "css": {
    "@keyframes nav-drawer-veil-in": {
      "from": {
        "opacity": "0"
      },
      "to": {
        "opacity": "1"
      }
    },
    "@keyframes nav-drawer-veil-out": {
      "from": {
        "opacity": "1"
      },
      "to": {
        "opacity": "0"
      }
    },
    "@keyframes nav-drawer-from-right": {
      "from": {
        "transform": "translate3d(100%, 0, 0)"
      },
      "to": {
        "transform": "translate3d(0, 0, 0)"
      }
    },
    "@keyframes nav-drawer-to-right": {
      "from": {
        "transform": "translate3d(0, 0, 0)"
      },
      "to": {
        "transform": "translate3d(100%, 0, 0)"
      }
    },
    "@keyframes nav-drawer-from-left": {
      "from": {
        "transform": "translate3d(-100%, 0, 0)"
      },
      "to": {
        "transform": "translate3d(0, 0, 0)"
      }
    },
    "@keyframes nav-drawer-to-left": {
      "from": {
        "transform": "translate3d(0, 0, 0)"
      },
      "to": {
        "transform": "translate3d(-100%, 0, 0)"
      }
    },
    "@keyframes nav-drawer-from-top": {
      "from": {
        "transform": "translate3d(0, -100%, 0)"
      },
      "to": {
        "transform": "translate3d(0, 0, 0)"
      }
    },
    "@keyframes nav-drawer-to-top": {
      "from": {
        "transform": "translate3d(0, 0, 0)"
      },
      "to": {
        "transform": "translate3d(0, -100%, 0)"
      }
    },
    "@keyframes nav-drawer-item-in": {
      "from": {
        "opacity": "0",
        "transform": "translate3d(0, 14px, 0)"
      },
      "to": {
        "opacity": "1",
        "transform": "translate3d(0, 0, 0)"
      }
    },
    "@keyframes nav-drawer-item-out": {
      "from": {
        "opacity": "1",
        "transform": "translate3d(0, 0, 0)"
      },
      "to": {
        "opacity": "0",
        "transform": "translate3d(0, 8px, 0)"
      }
    },
    ".nav-drawer-overlay[data-state=open]": {
      "animation": "nav-drawer-veil-in var(--dur-base, 240ms) var(--ease-out, cubic-bezier(0.22, 0.61, 0.36, 1)) both"
    },
    ".nav-drawer-overlay[data-state=closed]": {
      "animation": "nav-drawer-veil-out var(--dur-fast, 140ms) cubic-bezier(0.5, 0, 0.9, 0.4) both"
    },
    ".nav-drawer-panel[data-side=right][data-state=open]": {
      "animation": "nav-drawer-from-right var(--dur-slow, 420ms) var(--ease-drawer, cubic-bezier(0.32, 0.72, 0, 1)) both"
    },
    ".nav-drawer-panel[data-side=right][data-state=closed]": {
      "animation": "nav-drawer-to-right var(--dur-base, 240ms) cubic-bezier(0.5, 0, 0.9, 0.4) both"
    },
    ".nav-drawer-panel[data-side=left][data-state=open]": {
      "animation": "nav-drawer-from-left var(--dur-slow, 420ms) var(--ease-drawer, cubic-bezier(0.32, 0.72, 0, 1)) both"
    },
    ".nav-drawer-panel[data-side=left][data-state=closed]": {
      "animation": "nav-drawer-to-left var(--dur-base, 240ms) cubic-bezier(0.5, 0, 0.9, 0.4) both"
    },
    ".nav-drawer-panel[data-side=top][data-state=open]": {
      "animation": "nav-drawer-from-top var(--dur-slow, 420ms) var(--ease-drawer, cubic-bezier(0.32, 0.72, 0, 1)) both"
    },
    ".nav-drawer-panel[data-side=top][data-state=closed]": {
      "animation": "nav-drawer-to-top var(--dur-base, 240ms) cubic-bezier(0.5, 0, 0.9, 0.4) both"
    },
    ".nav-drawer-panel[data-state=open] .nav-drawer-list > li": {
      "animation": "nav-drawer-item-in 460ms var(--ease-out, cubic-bezier(0.22, 0.61, 0.36, 1)) both",
      "animation-delay": "calc(120ms + var(--i, 0) * 45ms)"
    },
    ".nav-drawer-panel[data-state=closed] .nav-drawer-list > li": {
      "animation": "nav-drawer-item-out calc(var(--dur-base, 240ms) * 0.6) cubic-bezier(0.5, 0, 0.9, 0.4) both",
      "animation-delay": "calc(var(--io, 0) * var(--dur-base, 240ms) * 0.1)"
    },
    ".nav-drawer-rule": {
      "transform": "scaleX(0)",
      "transition": "transform var(--dur-base, 240ms) var(--ease-out, cubic-bezier(0.22, 0.61, 0.36, 1))"
    },
    ".nav-drawer-item:hover .nav-drawer-rule, .nav-drawer-item:focus-visible .nav-drawer-rule, .nav-drawer-item[data-active] .nav-drawer-rule": {
      "transform": "scaleX(1)"
    },
    ".nav-drawer-label": {
      "transition": "transform var(--dur-base, 240ms) var(--ease-out, cubic-bezier(0.22, 0.61, 0.36, 1))"
    },
    ".nav-drawer-item:hover .nav-drawer-label, .nav-drawer-item:focus-visible .nav-drawer-label": {
      "transform": "translate3d(6px, 0, 0)"
    },
    ".nav-drawer-burger .burger-bar": {
      "transition": "transform var(--dur-base, 240ms) var(--ease-drawer, cubic-bezier(0.32, 0.72, 0, 1))"
    },
    ".nav-drawer-burger[data-state=open] .burger-bar:first-child": {
      "transform": "translateY(4.25px) rotate(45deg)"
    },
    ".nav-drawer-burger[data-state=open] .burger-bar:last-child": {
      "transform": "translateY(-4.25px) rotate(-45deg)"
    },
    "@media (prefers-reduced-motion: reduce)": {
      ".nav-drawer-overlay, .nav-drawer-panel, .nav-drawer-panel[data-state=open] .nav-drawer-list > li, .nav-drawer-panel[data-state=closed] .nav-drawer-list > li": {
        "animation-duration": "1ms",
        "animation-delay": "0ms"
      },
      ".nav-drawer-rule, .nav-drawer-label, .nav-drawer-burger .burger-bar": {
        "transition": "none"
      }
    }
  },
  "meta": {
    "uso": {
      "quando": "Menu mobile de site ou landing, em gaveta lateral ou página inteira com imagem",
      "evite": "Navegação lateral fixa de app no desktop: side-nav; atalhos de ícone, magnify-dock"
    }
  },
  "categories": [
    "controles",
    "menu",
    "mobile",
    "drawer",
    "navegacao",
    "acessibilidade"
  ],
  "type": "registry:ui"
}