{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "eye-tracker",
  "title": "Agent Bot",
  "author": "Visant Labs <visantsupply@gmail.com>",
  "description": "Avatar de bot ou agente de IA: a cabeça é um shader vivo na cor da marca, com visor e antena, e os olhos seguem o cursor e o foco, piscam e cumprimentam no clique. Três peles (neon, voronoi, túnel) e quatro formas.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "@visant/css-color",
    "@visant/motion"
  ],
  "files": [
    {
      "path": "registry/visant/ui/eye-tracker.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  AnimatePresence,\n  animate,\n  motion,\n  useMotionValue,\n  useReducedMotion,\n  useSpring,\n  useTransform,\n} from \"motion/react\";\n\nimport { DUR } from \"@/lib/motion\";\nimport { cn } from \"@/lib/utils\";\nimport { onThemeChange, resolveCssColor } from \"@/lib/css-color\";\n\n/**\n * Eye Tracker: o avatar de um bot, agente ou assistente de IA. Dois olhos que\n * acompanham o cursor e o foco, piscam sozinhos e cumprimentam no clique.\n *\n * Mora onde o agente aparece: cabeçalho de chat, onboarding, estado vazio de\n * um assistente. O valor está em parecer atento, então ele olha pra onde a\n * pessoa está agindo, não só pra onde o mouse passa.\n *\n * ── Decisões que não são óbvias ────────────────────────────────────────────\n *\n * 1. **O corpo é material vivo, o fundo não.** Com `body=\"shader\"` (o padrão)\n *    a cabeça do bot é preenchida por um fragment shader recortado pela forma\n *    do corpo, e os olhos ficam num visor por cima. É isso que separa o bot do\n *    mascote bolinha genérico: a silhueta tem antena e visor, e a pele mexe.\n *    O shader é o mesmo das peças `shader-neon`, `shader-voronoi` e\n *    `shader-tunnel`, enxuto e com os parâmetros fixos pro tamanho de avatar.\n * 2. **A cor sai da marca.** As três paradas da rampa são tokens resolvidos\n *    pelo navegador (`resolveCssColor`), relidos quando o tema troca.\n * 3. **O shader sente o cursor.** Mexer o mouse injeta energia, que escoa em\n *    ~1 s; o cumprimento dá um pulso. Fora da tela não há quadro nenhum, com\n *    movimento reduzido fica um quadro parado, e o contexto WebGL é devolvido\n *    no unmount (o navegador só aguenta uns 16 vivos).\n * 4. **Segue o foco do teclado também.** Quem navega com Tab não move mouse\n *    nenhum; sem isso o rosto fica parado pra essa pessoa.\n * 5. **Os olhos andam juntos e a cabeça inclina.** Olho independente vira\n *    estrábico de perto. O par anda como um bloco, o visor acompanha com\n *    menos curso (paralaxe) e a cabeça gira um pouco em 3D.\n * 6. **Distância vira intensidade, com teto.** A partir de ~260 px de tela o\n *    olhar está no máximo. Só a direção vem do retângulo de tela, então a\n *    escala da vitrine não distorce nada.\n * 7. **Piscada em intervalo sorteado** (2,5 a 5,5 s). Intervalo fixo é o que\n *    denuncia o robô.\n * 8. **Clique ou Enter cumprimenta.** Os olhos apertam num sorriso, a antena\n *    acende e um balão escreve o `greeting`; um `role=\"status\"` lê a frase.\n */\n\nexport interface EyeTrackerProps\n  extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"onAnimationStart\" | \"onDrag\" | \"onDragEnd\" | \"onDragStart\" | \"onAnimationEnd\"> {\n  /** Lado da cabeça, em px. */\n  size?: number;\n  /** Forma dos olhos. */\n  eyes?: \"slant\" | \"dots\" | \"bars\" | \"pupil\";\n  /** Forma do corpo. Com `body=\"solid\"`, `glass` foi feito pra ficar por cima de um fundo vivo. */\n  face?: \"card\" | \"orb\" | \"glass\" | \"brand\";\n  /** Pele do corpo: shader vivo com visor e antena, ou o rosto liso de antes. */\n  body?: \"shader\" | \"solid\";\n  /** Qual shader preenche o corpo quando `body=\"shader\"`. */\n  shader?: \"neon\" | \"voronoi\" | \"tunnel\";\n  /** Quanto os olhos andam, de 0 a 1 (fração do meio rosto). */\n  follow?: number;\n  /** Pisca sozinho de vez em quando. */\n  blink?: boolean;\n  /** O que o balão escreve no clique. Vazio desliga o cumprimento. */\n  greeting?: string;\n}\n\nconst MOLA = { stiffness: 260, damping: 22, mass: 0.6 };\nconst ALCANCE = 260;\n\nconst OLHO = {\n  slant: \"h-[14%] w-[7%] rounded-[var(--r-pill)] -rotate-[18deg]\",\n  dots: \"size-[10%] rounded-[var(--r-pill)]\",\n  bars: \"h-[16%] w-[6%] rounded-[2px]\",\n  pupil: \"grid size-[22%] place-items-center rounded-full\",\n} as const;\n\nconst ROSTO = {\n  card: \"rounded-[var(--r-surface)] border border-border bg-card [box-shadow:var(--e-raised)]\",\n  orb: \"rounded-full border border-border bg-card [background-image:radial-gradient(circle_at_32%_28%,color-mix(in_oklab,var(--foreground)_12%,transparent),transparent_60%)] [box-shadow:var(--e-raised),inset_0_-10px_24px_-12px_color-mix(in_oklab,var(--foreground)_30%,transparent)]\",\n  glass:\n    \"rounded-[var(--r-surface)] border border-[color-mix(in_oklab,var(--foreground)_18%,transparent)] bg-[color-mix(in_oklab,var(--card)_45%,transparent)] backdrop-blur-md [box-shadow:var(--e-raised),inset_0_1px_0_color-mix(in_oklab,var(--foreground)_14%,transparent)]\",\n  brand: \"rounded-[var(--r-surface)] bg-[var(--brand)] [box-shadow:var(--e-raised)]\",\n} as const;\n\n/** Silhueta do bot: cabeça em squircle largo ou redonda. */\nconst CABECA = {\n  card: \"rounded-[30%]\",\n  orb: \"rounded-full\",\n  glass: \"rounded-[30%]\",\n  brand: \"rounded-[30%]\",\n} as const;\n\n// ── Pele em shader ────────────────────────────────────────────────────────\n// Os três fragment shaders vêm das peças shader-*, com os knobs fixados em\n// constantes de avatar. Todos falam a mesma língua: uRes, uTime, uPulse e uma\n// rampa de três cores (uA fundo, uB meio, uC núcleo).\n\nconst VERT = `attribute vec2 a_pos;void main(){gl_Position=vec4(a_pos,0.0,1.0);}`;\nconst CABECALHO = `precision mediump float;uniform vec2 uRes;uniform float uTime;uniform float uPulse;uniform vec3 uA;uniform vec3 uB;uniform vec3 uC;`;\n\nconst FRAG = {\n  neon: `${CABECALHO}\nvoid main(){\n  vec2 uv=(gl_FragCoord.xy-.5*uRes)/uRes.y;\n  float d=length(uv);\n  float g=0.;\n  for(float i=0.;i<4.;i++){\n    float f=(1.+i*.8)*.45;\n    float amp=(.15+uPulse*.2-i*.02);\n    float phase=uTime*(1.5+i*.3)+i*1.2;\n    float wave=sin(uv.x*f*12.+phase)*amp;\n    wave+=cos(uv.x*f*6.-phase*.7)*amp*.5*(.4+uPulse);\n    float dist=abs(uv.y-wave);\n    g+=(.006+uPulse*.004)/(dist+.003)*1.5*exp(-d*.5)*(.3+uPulse*.7);\n  }\n  vec3 c=mix(uA,uB,clamp(g,0.,1.));\n  c=mix(c,uC,smoothstep(1.2,4.,g));\n  gl_FragColor=vec4(c,1.);\n}`,\n  voronoi: `${CABECALHO}\nvec2 hash(vec2 p){\n  p=vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3)));\n  return fract(sin(p)*43758.5453);\n}\nvoid main(){\n  vec2 uv=gl_FragCoord.xy/uRes.y;\n  float t=uTime;\n  vec2 p=uv*3.2+vec2(t*.2);\n  vec2 ip=floor(p);\n  vec2 fp=fract(p);\n  float md=8.;\n  vec2 mc=vec2(0.);\n  for(int y=-1;y<=1;y++)\n  for(int x=-1;x<=1;x++){\n    vec2 g=vec2(float(x),float(y));\n    vec2 o=hash(ip+g);\n    o=.5+.5*sin(t+6.28*o+uPulse*2.);\n    vec2 r=g+o-fp;\n    float d=dot(r,r);\n    if(d<md){md=d;mc=ip+g;}\n  }\n  float dist=sqrt(md);\n  float tom=hash(mc+3.7).x;\n  vec3 c=mix(uA,uB,(1.-smoothstep(0.,.9,dist))*(.35+.65*tom));\n  c=mix(c,uC,1.-smoothstep(0.,.08+uPulse*.06,dist-.02));\n  gl_FragColor=vec4(c,1.);\n}`,\n  tunnel: `${CABECALHO}\nvoid main(){\n  vec2 uv=(gl_FragCoord.xy-.5*uRes)/uRes.y;\n  float t=uTime;\n  float a=atan(uv.y,uv.x);\n  float d=length(uv);\n  float tunel=.5/max(d,.001);\n  float espiral=a*3.+tunel+t*.4;\n  float padrao=sin(tunel*8.-t*2.)*.5+.5;\n  padrao*=sin(espiral)*.5+.5;\n  padrao+=sin(tunel*4.+a*2.+tunel*.5-t)*.3;\n  float aneis=(sin(tunel*12.-t*3.)*.5+.5)*exp(-d*1.5);\n  float luz=padrao*(1.-d)*.9+aneis*.35*(.5+uPulse);\n  luz+=exp(-d*2.)*.25*(1.+uPulse);\n  luz*=smoothstep(.02,.22,d);\n  vec3 c=mix(uA,uB,clamp(luz,0.,1.));\n  c=mix(c,uC,smoothstep(.9,1.6,luz));\n  gl_FragColor=vec4(c,1.);\n}`,\n} as const;\n\n/** Rampa da pele: fundo, meio, núcleo. Tudo token. */\nconst RAMPA = {\n  base: [\n    \"color-mix(in oklab, var(--brand) 30%, var(--card))\",\n    \"var(--brand)\",\n    \"color-mix(in oklab, var(--brand) 40%, var(--foreground))\",\n  ],\n  brand: [\n    \"var(--brand)\",\n    \"color-mix(in oklab, var(--brand) 45%, var(--background))\",\n    \"var(--background)\",\n  ],\n};\n\nfunction compilar(gl: WebGLRenderingContext, frag: string): WebGLProgram | null {\n  const shader = (tipo: number, src: string) => {\n    const s = gl.createShader(tipo);\n    if (!s) return null;\n    gl.shaderSource(s, src);\n    gl.compileShader(s);\n    return gl.getShaderParameter(s, gl.COMPILE_STATUS) ? s : null;\n  };\n  const vs = shader(gl.VERTEX_SHADER, VERT);\n  const fs = shader(gl.FRAGMENT_SHADER, frag);\n  const p = gl.createProgram();\n  if (!vs || !fs || !p) return null;\n  gl.attachShader(p, vs);\n  gl.attachShader(p, fs);\n  gl.linkProgram(p);\n  gl.deleteShader(vs);\n  gl.deleteShader(fs);\n  return gl.getProgramParameter(p, gl.LINK_STATUS) ? p : null;\n}\n\n/**\n * Pinta um shader dentro de `wrapRef`. Mesmo contrato das peças shader-*:\n * canvas criado no efeito, laço dormindo fora da tela e com movimento\n * reduzido, contexto devolvido no unmount. `energia` é um ref que o\n * componente alimenta com o ponteiro e o cumprimento.\n */\nfunction usePeleShader(\n  wrapRef: React.RefObject<HTMLSpanElement | null>,\n  ligado: boolean,\n  tipo: keyof typeof FRAG,\n  cores: string[],\n  energia: React.MutableRefObject<number>,\n) {\n  const coresChave = cores.join(\"|\");\n  React.useEffect(() => {\n    const wrap = wrapRef.current;\n    if (!ligado || !wrap) return;\n    const canvas = document.createElement(\"canvas\");\n    canvas.setAttribute(\"aria-hidden\", \"true\");\n    canvas.className = \"absolute inset-0 block h-full w-full\";\n    wrap.prepend(canvas);\n    const gl = canvas.getContext(\"webgl\", { antialias: false, alpha: false });\n    const prog = gl && compilar(gl, FRAG[tipo]);\n    if (!gl || !prog) {\n      canvas.remove();\n      return;\n    }\n\n    const buf = gl.createBuffer();\n    gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);\n    gl.useProgram(prog);\n    const aPos = gl.getAttribLocation(prog, \"a_pos\");\n    gl.enableVertexAttribArray(aPos);\n    gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);\n    const u = (n: string) => gl.getUniformLocation(prog, n);\n    const U = { res: u(\"uRes\"), time: u(\"uTime\"), pulse: u(\"uPulse\"), a: u(\"uA\"), b: u(\"uB\"), c: u(\"uC\") };\n\n    const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    let reduzido = mq.matches;\n    let visivel = false;\n    let raf = 0;\n    let ultimo = 0;\n    let tempo = 3; // começa com a pele já aberta, não num ponto de fase nula\n\n    const pintarCores = () => {\n      const [a, b, c] = coresChave.split(\"|\").map((cor) => resolveCssColor(cor, wrap));\n      gl.uniform3fv(U.a, a);\n      gl.uniform3fv(U.b, b ?? a);\n      gl.uniform3fv(U.c, c ?? b ?? a);\n    };\n    const medir = () => {\n      // offsetWidth é a medida local: a vitrine escala o palco com transform.\n      const dpr = Math.min(2, window.devicePixelRatio || 1);\n      canvas.width = Math.max(1, Math.round(wrap.offsetWidth * dpr));\n      canvas.height = Math.max(1, Math.round(wrap.offsetHeight * dpr));\n      gl.viewport(0, 0, canvas.width, canvas.height);\n    };\n    const desenhar = () => {\n      const pulso = reduzido ? 0.35 : 0.3 + 0.12 * Math.sin(tempo * 0.8) + energia.current;\n      gl.uniform2f(U.res, canvas.width, canvas.height);\n      gl.uniform1f(U.time, tempo);\n      gl.uniform1f(U.pulse, Math.min(1.2, pulso));\n      gl.drawArrays(gl.TRIANGLES, 0, 3);\n    };\n    const quadro = (agora: number) => {\n      const dt = ultimo ? Math.min(0.05, (agora - ultimo) / 1000) : 0;\n      ultimo = agora;\n      tempo += dt * (0.8 + energia.current);\n      energia.current *= Math.pow(0.04, dt); // escoa em ~1s\n      desenhar();\n      raf = visivel && !reduzido ? requestAnimationFrame(quadro) : 0;\n    };\n    const ligar = () => {\n      if (!raf && visivel && !reduzido) {\n        ultimo = 0;\n        raf = requestAnimationFrame(quadro);\n      }\n    };\n    const desligar = () => {\n      cancelAnimationFrame(raf);\n      raf = 0;\n    };\n    const trocarMovimento = () => {\n      reduzido = mq.matches;\n      if (reduzido) {\n        desligar();\n        desenhar();\n      } else ligar();\n    };\n\n    pintarCores();\n    medir();\n    desenhar();\n\n    const ro = new ResizeObserver(() => {\n      medir();\n      desenhar();\n    });\n    ro.observe(wrap);\n    const io = new IntersectionObserver(([e]) => {\n      visivel = e.isIntersecting;\n      if (visivel) ligar();\n      else desligar();\n    });\n    io.observe(wrap);\n    const offTema = onThemeChange(() => {\n      pintarCores();\n      desenhar();\n    });\n    mq.addEventListener(\"change\", trocarMovimento);\n\n    return () => {\n      desligar();\n      ro.disconnect();\n      io.disconnect();\n      offTema();\n      mq.removeEventListener(\"change\", trocarMovimento);\n      gl.deleteBuffer(buf);\n      gl.deleteProgram(prog);\n      gl.getExtension(\"WEBGL_lose_context\")?.loseContext();\n      canvas.remove();\n    };\n  }, [wrapRef, ligado, tipo, coresChave, energia]);\n}\n\nexport function EyeTracker({\n  size = 112,\n  eyes = \"slant\",\n  face = \"card\",\n  body = \"shader\",\n  shader = \"neon\",\n  follow = 0.6,\n  blink = true,\n  greeting = \"Oi, tudo bem?\",\n  className,\n  onClick,\n  style,\n  ...props\n}: EyeTrackerProps) {\n  const reduzido = useReducedMotion() ?? false;\n  const vivo = body === \"shader\";\n  const rosto = React.useRef<HTMLButtonElement>(null);\n  const pele = React.useRef<HTMLSpanElement>(null);\n  const energia = React.useRef(0);\n  const alvoX = useMotionValue(0);\n  const alvoY = useMotionValue(0);\n  const molaX = useSpring(alvoX, MOLA);\n  const molaY = useSpring(alvoY, MOLA);\n  const olhoX = reduzido ? alvoX : molaX;\n  const olhoY = reduzido ? alvoY : molaY;\n  const giroY = useTransform(olhoX, (v) => v * 0.5);\n  const giroX = useTransform(olhoY, (v) => -v * 0.5);\n  // A pupila anda mais um tanto dentro do globo; o visor anda menos (paralaxe).\n  const pupilaX = useTransform(olhoX, (v) => v * 0.35);\n  const pupilaY = useTransform(olhoY, (v) => v * 0.35);\n  const visorX = useTransform(olhoX, (v) => v * 0.4);\n  const visorY = useTransform(olhoY, (v) => v * 0.4);\n  const palpebra = useMotionValue(1);\n  const [falando, setFalando] = React.useState(false);\n  const [escrito, setEscrito] = React.useState(\"\");\n  const falandoRef = React.useRef(false);\n\n  usePeleShader(pele, vivo, shader, face === \"brand\" ? RAMPA.brand : RAMPA.base, energia);\n\n  // Meio rosto vezes `follow`: o teto do passeio em px locais.\n  const teto = (size / 2) * Math.min(1, Math.max(0, follow)) * 0.55;\n\n  const olharPara = React.useCallback(\n    (px: number, py: number) => {\n      const el = rosto.current;\n      if (!el || falandoRef.current) return;\n      const r = el.getBoundingClientRect();\n      const dx = px - (r.left + r.width / 2);\n      const dy = py - (r.top + r.height / 2);\n      const d = Math.hypot(dx, dy);\n      if (d < 1) return;\n      const forca = Math.min(1, d / ALCANCE) * teto;\n      alvoX.set((dx / d) * forca);\n      alvoY.set((dy / d) * forca);\n    },\n    [alvoX, alvoY, teto],\n  );\n\n  React.useEffect(() => {\n    let ux = -1;\n    let uy = -1;\n    const mover = (e: PointerEvent) => {\n      olharPara(e.clientX, e.clientY);\n      // O cursor andando agita a pele.\n      if (ux >= 0) energia.current = Math.min(0.9, energia.current + Math.hypot(e.clientX - ux, e.clientY - uy) * 0.0025);\n      ux = e.clientX;\n      uy = e.clientY;\n    };\n    const focar = (e: FocusEvent) => {\n      const t = e.target;\n      if (!(t instanceof Element) || t === rosto.current) return;\n      const r = t.getBoundingClientRect();\n      olharPara(r.left + r.width / 2, r.top + r.height / 2);\n    };\n    const sair = () => {\n      ux = uy = -1;\n      if (falandoRef.current) return;\n      alvoX.set(0);\n      alvoY.set(0);\n    };\n    window.addEventListener(\"pointermove\", mover, { passive: true });\n    document.addEventListener(\"focusin\", focar);\n    document.documentElement.addEventListener(\"pointerleave\", sair);\n    return () => {\n      window.removeEventListener(\"pointermove\", mover);\n      document.removeEventListener(\"focusin\", focar);\n      document.documentElement.removeEventListener(\"pointerleave\", sair);\n    };\n  }, [olharPara, alvoX, alvoY]);\n\n  // Piscada: um quadro curto fechando e abrindo, em intervalo sorteado.\n  React.useEffect(() => {\n    if (!blink || reduzido) return;\n    let timer = 0;\n    const agendar = () => {\n      timer = window.setTimeout(() => {\n        animate(palpebra, [1, 0.1, 1], { duration: DUR.fast, times: [0, 0.4, 1], ease: \"easeInOut\" });\n        agendar();\n      }, 2500 + Math.random() * 3000);\n    };\n    agendar();\n    return () => window.clearTimeout(timer);\n  }, [blink, reduzido, palpebra]);\n\n  // O balão escreve uma letra por vez e some depois de um respiro.\n  React.useEffect(() => {\n    if (!falando) return;\n    let i = 0;\n    const passo = reduzido ? greeting.length : 1;\n    const tic = window.setInterval(() => {\n      i = Math.min(greeting.length, i + passo);\n      setEscrito(greeting.slice(0, i));\n      if (i >= greeting.length) window.clearInterval(tic);\n    }, 55);\n    const fim = window.setTimeout(() => {\n      falandoRef.current = false;\n      setFalando(false);\n    }, greeting.length * 55 + 1600);\n    return () => {\n      window.clearInterval(tic);\n      window.clearTimeout(fim);\n    };\n  }, [falando, greeting, reduzido]);\n\n  function cumprimentar() {\n    if (!greeting || falandoRef.current) return;\n    falandoRef.current = true;\n    energia.current = 0.9;\n    alvoX.set(0);\n    alvoY.set(0);\n    setEscrito(\"\");\n    setFalando(true);\n  }\n\n  // Falando, os olhos apertam: o sorriso que um rosto sem boca consegue dar.\n  React.useEffect(() => {\n    if (!falando) return;\n    if (reduzido) return;\n    animate(palpebra, 0.45, { duration: DUR.fast, ease: \"easeOut\" });\n    return () => {\n      animate(palpebra, 1, { duration: DUR.fast, ease: \"easeOut\" });\n    };\n  }, [falando, reduzido, palpebra]);\n\n  // No visor o olho é sempre tinta do tema; no rosto liso da marca, contraste da marca.\n  const marcaLisa = !vivo && face === \"brand\";\n  const corOlho = marcaLisa ? \"bg-[var(--brand-foreground)]\" : \"bg-foreground\";\n  const olho = (\n    <span className={cn(\"block\", corOlho, OLHO[eyes])}>\n      {eyes === \"pupil\" && (\n        <motion.span\n          className={cn(\"block size-[46%] rounded-full\", marcaLisa ? \"bg-[var(--brand)]\" : \"bg-background\")}\n          style={{ x: pupilaX, y: pupilaY }}\n        />\n      )}\n    </span>\n  );\n\n  return (\n    <span\n      className=\"relative inline-flex [perspective:600px]\"\n      style={vivo ? { paddingTop: Math.round(size * 0.2) } : undefined}\n    >\n      <motion.button\n        {...props}\n        ref={rosto}\n        type=\"button\"\n        aria-label={greeting ? `Assistente. Clique pra ele dizer \"${greeting}\"` : \"Assistente\"}\n        onClick={(e) => {\n          onClick?.(e);\n          cumprimentar();\n        }}\n        style={{ ...style, width: size, height: size, rotateX: giroX, rotateY: giroY }}\n        className={cn(\n          \"relative grid cursor-pointer place-items-center outline-none [font:inherit] focus-visible:[box-shadow:var(--focus)]\",\n          vivo\n            ? cn(\n                CABECA[face],\n                \"[box-shadow:var(--e-raised),inset_0_0_0_1px_color-mix(in_oklab,var(--foreground)_12%,transparent)]\",\n              )\n            : ROSTO[face],\n          className,\n        )}\n      >\n        {vivo && (\n          <>\n            {/* Antena: o traço que diz \"bot\" antes de qualquer olho. Acende no cumprimento. */}\n            <span\n              aria-hidden=\"true\"\n              className=\"pointer-events-none absolute bottom-full left-1/2 flex -translate-x-1/2 flex-col items-center\"\n            >\n              <span\n                className={cn(\n                  \"block rounded-full bg-[var(--brand)] transition-[box-shadow] [transition-duration:var(--dur-fast)] [transition-timing-function:var(--ease-out)]\",\n                  falando && \"[box-shadow:0_0_0_4px_color-mix(in_oklab,var(--brand)_35%,transparent)]\",\n                )}\n                style={{ width: size * 0.09, height: size * 0.09 }}\n              />\n              <span\n                className=\"block w-[2px] bg-[color-mix(in_oklab,var(--foreground)_28%,transparent)]\"\n                style={{ height: size * 0.11 }}\n              />\n            </span>\n\n            {/* Pele: o canvas entra aqui, recortado pela forma da cabeça. */}\n            <span\n              ref={pele}\n              aria-hidden=\"true\"\n              className={cn(\n                \"pointer-events-none absolute inset-0 overflow-hidden rounded-[inherit] bg-[var(--brand)]\",\n                // Vidro: o shader desfocado por baixo de uma película. Blur no\n                // próprio canvas, não backdrop-filter: este sob rotação 3D vaza o recorte.\n                face === \"glass\" && \"[&>canvas]:scale-110 [&>canvas]:blur-md\",\n              )}\n            >\n              {face === \"glass\" && (\n                <span className=\"absolute inset-0 bg-[color-mix(in_oklab,var(--card)_30%,transparent)]\" />\n              )}\n              <span className=\"absolute inset-0 rounded-[inherit] [box-shadow:inset_0_-14px_28px_-16px_color-mix(in_oklab,var(--foreground)_40%,transparent)]\" />\n            </span>\n\n            {/* Visor: a tela escura onde os olhos acendem. */}\n            <motion.span\n              aria-hidden=\"true\"\n              className=\"pointer-events-none absolute inset-x-[13%] top-[30%] h-[40%] rounded-[var(--r-pill)] border border-[color-mix(in_oklab,var(--foreground)_12%,transparent)] bg-[color-mix(in_oklab,var(--background)_84%,transparent)] backdrop-blur-sm [box-shadow:inset_0_2px_8px_color-mix(in_oklab,var(--foreground)_18%,transparent)]\"\n              style={{ x: visorX, y: visorY }}\n            />\n          </>\n        )}\n\n        <motion.span\n          aria-hidden=\"true\"\n          className=\"absolute inset-0 flex items-center justify-center gap-[16%]\"\n          style={{ x: olhoX, y: olhoY, scaleY: palpebra }}\n        >\n          {olho}\n          {olho}\n        </motion.span>\n      </motion.button>\n\n      <AnimatePresence>\n        {falando && (\n          <motion.span\n            aria-hidden=\"true\"\n            className=\"pointer-events-none absolute left-[78%] top-[72%] whitespace-nowrap rounded-[var(--r-pill)] bg-[var(--brand)] px-3 py-1 text-xs font-medium text-[var(--brand-foreground)] [box-shadow:var(--e-raised)]\"\n            initial={reduzido ? { opacity: 0 } : { opacity: 0, scale: 0.6, y: -4 }}\n            animate={{ opacity: 1, scale: 1, y: 0 }}\n            exit={{ opacity: 0, transition: { duration: DUR.fast } }}\n            transition={reduzido ? { duration: DUR.fast } : { type: \"spring\", visualDuration: 0.25, bounce: 0.3 }}\n            style={{ transformOrigin: \"0% 0%\" }}\n          >\n            {escrito || \" \"}\n          </motion.span>\n        )}\n      </AnimatePresence>\n\n      <span className=\"sr-only\" role=\"status\">\n        {falando ? greeting : \"\"}\n      </span>\n    </span>\n  );\n}\n\nexport default EyeTracker;\n",
      "type": "registry:ui"
    }
  ],
  "meta": {
    "uso": {
      "quando": "Avatar de agente ou assistente de IA: cabeçalho e estado vazio de chat, onboarding guiado por bot, tela de espera enquanto o agente pensa",
      "evite": "Interface séria ou densa, foto de perfil de pessoa (use avatar) e mais de um bot vivo na mesma tela"
    }
  },
  "categories": [
    "efeitos",
    "bot",
    "agent",
    "agente",
    "ia",
    "assistente",
    "avatar",
    "mascote",
    "chat",
    "shader",
    "cursor",
    "estado-vazio"
  ],
  "type": "registry:ui"
}