# Docs @visant/ui

Instale uma peça, vista a sua marca e conecte o agente.

## Começar em 3 passos

### 1. Configure o registry

Projeto sem shadcn começa por `npx shadcn@latest init`. Depois, acrescente o `@visant` ao `components.json`.

```bash
npx shadcn@latest init
```

```json
{
  "registries": {
    "@visant": "https://ui.visantlabs.com/r/{name}.json"
  }
}
```

### 2. Instale uma peça

O arquivo vai pro seu projeto, em `src/components/ui`, e é seu pra editar. As dependências npm vêm junto.

```bash
npx shadcn@latest add @visant/slide-to-confirm
```

```tsx
import { SlideToConfirm } from "@/components/ui/slide-to-confirm"

export function Pagar() {
  return <SlideToConfirm label="Deslize pra pagar" onConfirm={() => console.log("pago")} />
}
```

### 3. Aplique um tema

O tema escreve as cores no seu CSS, e as peças leem dali.

```bash
npx shadcn@latest add @visant/theme-norte-verde
```

Temas prontos: `theme-visant`, `theme-norte-verde`, `theme-grao-editora`, `theme-vega-noturno`.

## Trocar de marca em runtime

Pra quando a marca só se decide com o app aberto: proposta, painel de vários clientes. O provider baixa o tema e troca uma folha de estilo, sem recarregar a página.

```bash
npx shadcn@latest add @visant/brand-theme
```

```tsx
import { BrandThemeProvider, useBrandTheme } from "@/lib/brand-theme"

function TrocaDeMarca() {
  const { brands, active, setBrand } = useBrandTheme()
  return brands.map((b) => (
    <button key={b.slug} aria-pressed={active?.slug === b.slug} onClick={() => setBrand(b.slug)}>
      {b.name}
    </button>
  ))
}

export default function App() {
  return (
    <BrandThemeProvider defaultBrand="norte-verde">
      <TrocaDeMarca />
    </BrandThemeProvider>
  )
}
```

## Conectar o agente

O servidor MCP recomenda e busca peças, sem chave. Quem não usa MCP lê o acervo em texto.

- Conector MCP, sem chave: `https://ui.visantlabs.com/mcp`
- Claude Code: `claude mcp add --transport http visant-ui https://ui.visantlabs.com/mcp`
- Acervo em texto: `https://ui.visantlabs.com/llms.txt`
- Esta página em Markdown: `https://ui.visantlabs.com/docs.md`
- Regras no agente: `npx shadcn@latest add @visant/agent-rules`
- Skills: `npx skills add pedrojaques99/visant-skills-public --skill visant-design`

## Ler

- [llms.txt](https://ui.visantlabs.com/llms.txt): índice por tarefa e eixo
- [r/index.json](https://ui.visantlabs.com/r/index.json): índice compacto
- [llms-full.txt](https://ui.visantlabs.com/llms-full.txt): tudo
- [Skills](https://ui.visantlabs.com/skills.md) e [Marca](https://ui.visantlabs.com/marca.md)

## Referência: libs, hooks, temas e regras

### Agent Rules

Regras pro seu agente de IA (Cursor, Claude Code, Copilot): procurar no @visant antes de escrever UI do zero e escolher a peça certa pela situação.

```bash
npx shadcn@latest add @visant/agent-rules
```

### Brand Theme Runtime

Provider e hook que aplicam em runtime o tema compilado de uma brand guideline. Uma folha de estilo só, trocada sem recarregar a página.

```bash
npx shadcn@latest add @visant/brand-theme
```

```tsx
import { BrandThemeProvider, useBrandTheme } from "@/lib/brand-theme"

function Atual() {
  const { active, setBrand } = useBrandTheme()
  return <button onClick={() => setBrand("vega-noturno")}>{active?.name}</button>
}

export const App = () => (
  <BrandThemeProvider defaultBrand="visant">
    <Atual />
  </BrandThemeProvider>
)
```

### Fold Motion

Hierarquia de entradas por dobra: rise, drift, mask, settle, climax, invert e mist em vez de trinta entradas iguais. O esconderijo é aplicado pelo componente, nunca por CSS estático.

```bash
npx shadcn@latest add @visant/fold-motion
```

```tsx
import { Fold } from "@/lib/fold-motion"

export const Secao = () => (
  <Fold variant="rise">
    <h2>Entra quando chega na tela</h2>
  </Fold>
)
```

### Motion Tokens

Curvas e durações da casa em JS, espelho de --ease-* e --dur-*: Motion, GSAP e Web Animations API importam daqui em vez de escrever o número na peça.

```bash
npx shadcn@latest add @visant/motion
```

```tsx
import { motion } from "motion/react"
import { DUR, EASE_OUT } from "@/lib/motion"

export function Entrada() {
  return <motion.div animate={{ opacity: 1 }} initial={{ opacity: 0 }} transition={{ duration: DUR.base, ease: EASE_OUT }} />
}
```

### Motion Layer

O ciclo de scroll do editor Shopify em GSAP/ScrollTrigger, com limpeza de instância.

```bash
npx shadcn@latest add @visant/motion-layer
```

```tsx
import { useEffect } from "react"
import { mount } from "@/lib/motion-layer"

export function Secao() {
  useEffect(() => mount(), [])
  return <h2 data-reveal="lines">O texto sobe de dentro de um corte</h2>
}
```

### SFX Bus

Cliques de objeto analógico em WebAudio puro, sem arquivo: botão de câmera, dente de knob, alavanca e agulha pousando, com throttle, contexto armado no primeiro gesto e mudo sob prefers-reduced-motion.

```bash
npx shadcn@latest add @visant/sfx-bus
```

```tsx
import { SfxProvider, playSfx } from "@/lib/sfx-bus"

export const App = () => (
  <SfxProvider>
    <button onClick={() => playSfx("complete")}>Salvar</button>
  </SfxProvider>
)
```

### Smooth Scroll Provider

Lenis + GSAP num relógio só, com bail-out em prefers-reduced-motion. A instância sai por callback.

```bash
npx shadcn@latest add @visant/smooth-scroll-provider
```

```tsx
import type { ReactNode } from "react"
import { SmoothScrollProvider } from "@/lib/smooth-scroll-provider"

export function Landing({ children }: { children: ReactNode }) {
  return <SmoothScrollProvider>{children}</SmoothScrollProvider>
}
```

### Tempo Clock

Relógio de BPM sem interface: passo na divisão, envelope de bumbo, tap tempo e troca de BPM sem pular o compasso.

```bash
npx shadcn@latest add @visant/tempo-clock
```

```tsx
import { createTempoClock } from "@/lib/tempo-clock"

const relogio = createTempoClock({ bpm: 120, div: 2 })

function quadro(agora: number) {
  const { beat, env } = relogio.tick(agora)
  document.body.style.opacity = String(beat ? 1 : env)
  requestAnimationFrame(quadro)
}

requestAnimationFrame(quadro)
```

### useDebounce

Atrasa um valor até ele parar de mudar. Só borda de saída, e o valor inicial passa sem atraso.

```bash
npx shadcn@latest add @visant/use-debounce
```

```tsx
import { useState } from "react"
import { useDebounce } from "@/hooks/use-debounce"

export function Busca() {
  const [texto, setTexto] = useState("")
  const termo = useDebounce(texto, 300)
  return <input value={texto} onChange={(e) => setTexto(e.target.value)} aria-label={termo} />
}
```

### useIdle

Chama uma função enquanto a peça está na tela e ninguém mexe nela, e cala com movimento reduzido. Vem com useInView.

```bash
npx shadcn@latest add @visant/use-idle
```

### useKnobDrag

O gesto de arrastar um knob isolado do desenho: pointer capture, trava de curso e passo fino com shift.

```bash
npx shadcn@latest add @visant/use-knob-drag
```

```tsx
import { useKnobDrag } from "@/hooks/use-knob-drag"

export function Ganho() {
  const { currentValue, rotation, handleMouseDown, handleKeyDown } = useKnobDrag({ min: 0, max: 10, value: 5 })
  return (
    <div role="slider" tabIndex={0} aria-valuenow={currentValue} onMouseDown={handleMouseDown} onKeyDown={handleKeyDown} style={{ rotate: `${rotation}deg` }} />
  )
}
```

### useLocalSearch

Busca em memória tolerante a acento: índice montado uma vez por lista, todos os termos batendo em qualquer ordem, e termo vazio devolvendo a lista inteira.

```bash
npx shadcn@latest add @visant/use-local-search
```

```tsx
import { useState } from "react"
import { useLocalSearch } from "@/hooks/use-local-search"

const MARCAS = ["Visant", "Norte Verde", "Grão Editora"]

export function Busca() {
  const [query, setQuery] = useState("")
  const achadas = useLocalSearch(MARCAS, { query, fields: (m) => [m] })
  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      {achadas.map((m) => <p key={m}>{m}</p>)}
    </>
  )
}
```

### useMediaQuery

Media query como estado de React, via useSyncExternalStore: leitura síncrona no primeiro render, sem piscar, e segura em SSR.

```bash
npx shadcn@latest add @visant/use-media-query
```

```tsx
import { useMediaQuery } from "@/hooks/use-media-query"

export function Menu() {
  const largo = useMediaQuery("(min-width: 1024px)")
  return largo ? <nav>Barra lateral</nav> : <button>Abrir menu</button>
}
```

### usePointerParallax

Escreve --px e --py uma vez por quadro e deixa o CSS decidir a profundidade de cada objeto. Só em ponteiro fino, volta ao repouso ao sair da janela.

```bash
npx shadcn@latest add @visant/use-pointer-parallax
```

```tsx
import { useRef } from "react"
import { usePointerParallax } from "@/hooks/use-pointer-parallax"

export function Cena() {
  const ref = useRef<HTMLDivElement>(null)
  usePointerParallax(ref)
  return <div ref={ref} style={{ transform: "translate3d(calc(var(--px, 0) * 8px), calc(var(--py, 0) * 8px), 0)" }} />
}
```

### useTilt

A inclinação lateral do celular, com o pedido de permissão do iOS dentro do gesto. Sem sensor, nada acontece.

```bash
npx shadcn@latest add @visant/use-tilt
```

### Vintage Base

A fundação do kit: variáveis de painel, texturas e as regras em @layer.

```bash
npx shadcn@latest add @visant/vintage-base
```

### Vintage Theme

O tema do kit de áudio, em claro e escuro: as chapas saem dos tokens do projeto e a luz sai da marca.

```bash
npx shadcn@latest add @visant/vintage-theme
```

### Tema Visant®

```bash
npx shadcn@latest add @visant/theme-visant
```

### Tema Norte Verde

```bash
npx shadcn@latest add @visant/theme-norte-verde
```

### Tema Grão Editora

```bash
npx shadcn@latest add @visant/theme-grao-editora
```

### Tema Vega Noturno

```bash
npx shadcn@latest add @visant/theme-vega-noturno
```
