@be-enlighten/beni-avatar
v0.4.0
Published
Avatar Vue 3 animado (mascote Beni) — squircle que olha pra você, pisca, pula e comemora. Estados, humores e acessórios, animações a 60fps com GSAP.
Downloads
21
Maintainers
Readme
@be-enlighten/beni-avatar
Avatar Vue 3 animado, pequeno e simpático. Um squircle que olha pra você, pisca, pula e comemora. Solte-o em qualquer app Vue pra dar personalidade a estados de carregamento, páginas vazias, fluxos de onboarding, ou só por diversão.
- 🎭 8 estados:
idle,working,error,speaking,loading,sleep,branding,celebrate - 💬 4 humores:
happy,sad,angry,surprised(sobrepostos a qualquer estado) - 🎩 6 acessórios de cabeça: beanie, coroa, capacete, chapéu de festa, gorro de papai noel, gorro stan
- 🖱️ Olhos que seguem o mouse, opcional
- 🎉 Easter eggs por clique: duplo, triplo, quíntuplo
- 🎨 Tema via props: cores do corpo e dos olhos controladas pelo componente pai
- ⚡ ~14 KB gzipped, sem dependências de runtime além de
gsapevue
Animações a 60fps com GSAP.
Demo
A playground em examples/beni-avatar é um estúdio interativo: à esquerda, os 8 estados como cards clicáveis; no centro, o Beni grande controlado pela seleção; à direita, os 4 humores. Logo abaixo, um painel de configuração com acessório, tema (5 presets), toggle "seguir mouse" e slider de intervalo de piscada. Os cards reagem ao tema e ao acessório ativos, com borda gradiente destacada no item selecionado. É também a página hospedada na Vercel.
Instalação
pnpm add @be-enlighten/beni-avatar gsap
# ou
npm install @be-enlighten/beni-avatar gsap
# ou
yarn add @be-enlighten/beni-avatar gsapPeer dependency:
vue≥ 3.5. Ogsap(≥ 3.15) é dependency do pacote — instalado automaticamente; declare-o também no app se quiser pinar a mesma cópia.
Início rápido
O avatar é distribuído como um SFC Vue acompanhado de um arquivo CSS. Importe o stylesheet uma vez no app, em main.ts, para que as variáveis CSS dos acessórios fiquem disponíveis:
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import '@be-enlighten/beni-avatar/style.css'
createApp(App).mount('#app')<!-- App.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import { Beni, type BeniState, type MoodName } from '@be-enlighten/beni-avatar'
const state = ref<BeniState>('idle')
const mood = ref<MoodName | null>(null)
</script>
<template>
<Beni
v-model:state="state"
v-model:mood="mood"
/>
</template>Pronto. Você tem um squircle de ~160×160 que pisca, respira e segue o cursor.
Props
| Prop | Tipo | Padrão | Descrição |
| --- | --- | --- | --- |
| state* | BeniState | 'idle' | Estado atual. Bind via v-model:state. |
| mood* | MoodName \| null | null | Humor sobreposto. Bind via v-model:mood. |
| headItem | HeadItemKey \| null | null | Acessório: 'beanie', 'crown', 'helmet', 'partyhat', 'santahat', 'stanhat'. |
| bodyBaseColor | string | '#60a5fa' | Cor inicial do gradiente do corpo. |
| bodyBaseColorTo | string | '#2563eb' | Cor final do gradiente do corpo. |
| bodyBaseModel | BodyModelName | 'squircle' | Forma base, antes de overrides de estado. |
| bodyScale | number | 1 | Escala geral do corpo. |
| bodyOffsetX | number | 0 | Deslocamento horizontal, em unidades SVG. |
| bodyOffsetY | number | 0 | Deslocamento vertical, em unidades SVG. |
| eyeBaseColor | string | '#00f0ff' | Cor padrão dos olhos (estados podem sobrescrever). |
| eyeBaseBlinkInterval | number | 4000 | Intervalo médio entre piscadas, em ms. 0 desativa. |
| eyeGap | number | 2 | Distância entre os olhos. |
| eyeOffsetX | number | 0 | Deslocamento horizontal dos olhos. |
| eyeOffsetY | number | 2 | Deslocamento vertical dos olhos. |
| eyeLookAtMouse | boolean | true | Olhos seguem o cursor. |
| eyeLookAtTarget | { x, y } \| null | null | Fixa os olhos em um ponto (sobrepõe o mouse). |
| eyeLookDuration | number | 0.15 | Duração do tween de movimento, em segundos. |
| eyeLookEase | string | 'power2.out' | Ease do GSAP. |
| eyeLookOvershoot | number | 0.6 | Overshoot quando os olhos se acomodam no alvo. |
| bodyFollowMouse | boolean | true | Quando os olhos atingem as bordas, a cabeça vira levemente na direção do cursor. |
| bodyFollowMaxRotation | number | 4 | Rotação máxima da cabeça, em graus. |
| bodyFollowDeadzone | number | 5 | Limiar (em unidades do offset dos olhos) abaixo do qual a cabeça relaxa. |
| bodyFollowDuration | number | 0.45 | Duração do tween de rotação da cabeça, em segundos. Mais lento que os olhos cria o efeito de "pescoço acompanha". |
| clickRipple | boolean | true | Mostra uma onda no clique. |
| clickRippleColor | string \| null | null | Cor da onda. Usa a cor atual do corpo por padrão. |
| clickRippleDuration | number | 0.4 | Duração da onda, em segundos. |
* bind bidirecional via v-model.
Eventos
| Evento | Payload | Quando |
| --- | --- | --- |
| update:state | BeniState | O estado muda (via v-model). |
| update:mood | MoodName \| null | O humor muda. |
| click | BeniClickPayload | O avatar é clicado. |
| interaction | BeniInteractionPayload | Uma interação interna de clique dispara. |
Estados
| Estado | Visual | Melhor uso |
| --- | --- | --- |
| idle | Balanço suave, olhos passeando | Padrão. |
| working | Corpo girando, olhos frenéticos | Carregamento/processamento. |
| error | Corpo vermelho, tremor com glitch | Estados de erro. |
| speaking | Cabeça balançando, sílabas na boca | Assistentes de IA. |
| loading | Quadrado arredondado girando | Loaders. |
| sleep | Respiração lenta, olhos fechados, "Z"s | Ocioso / segundo plano. |
| branding | Magenta→índigo, olhos "EN" | Splash / marketing. |
| celebrate | Pulando, explosão de confete | Sucesso. |
<Beni :state="currentState" />const currentState = ref<BeniState>('working')
watch(someTask, (task) => {
currentState.value = task.status === 'loading' ? 'working' : 'celebrate'
})Humores
Os humores se sobrepõem a um estado. Mudam olhos, boca e geram pequenos elementos SVG (lágrima, veia de raiva).
| Humor | Efeito |
| --- | --- |
| happy | Olhos arqueados, sorriso largo. |
| sad | Olhos fechados, tom azulado, lágrima. |
| angry | Olhos arqueados pra baixo, tom vermelho, veia. |
| surprised | Olhos circulares, sem boca. |
<Beni v-model:state="state" v-model:mood="mood" />Você pode derivar o humor a partir do estado. O avatar já dispara happy no clique duplo em idle (veja "Interações internas").
Acessórios de cabeça
Seis itens prontos. Cada um acompanha o pulo/escala/rotação do corpo, mas se mantém firme em cima da cabeça — não inclina com a inclinação do corpo, na prática parecia instável demais.
<Beni :head-item="accessory" />import { type HeadItemKey, headItemNames, headItemLabels } from '@be-enlighten/beni-avatar'
import { ref } from 'vue'
const accessory = ref<HeadItemKey | null>('crown')Em working, loading e error o acessório é escondido automaticamente. Esses estados giram tanto o corpo que o chapéu voaria.
Tematizando os acessórios: as cores vêm de variáveis CSS. Sobrescreva-as no seu stylesheet:
:root { --beni-items-head-crown: #ffd700; --beni-items-head-crown-shade: #b8860b; --beni-items-head-beanie: #8b5cf6; /* ... */ }Importe
@be-enlighten/beni-avatar/style.cssantes e sobrescreva as variáveis depois.
Interações internas por clique
Clique rápido pra disparar easter eggs. Já vêm de fábrica, sem flag.
| Padrão | Estado inicial | Dispara |
| --- | --- | --- |
| Clique duplo | idle | Humor happy por 2,2s |
| 5 cliques | idle | Estado branding por 3s |
| Clique duplo | sleep | Piscada de volta por 1,5s |
| 3 cliques | sleep | Acorda (persistente) |
Cada interação é anunciada pelo evento interaction.
Eventos de clique
<Beni @click="onClick" @interaction="onInteraction" />import type { BeniClickPayload, BeniInteractionPayload } from '@be-enlighten/beni-avatar'
function onClick(p: BeniClickPayload) {
console.log(p.target) // 'body' | 'eye-left' | 'eye-right'
console.log(p.clickIndex) // 1 no primeiro clique, 2 no segundo dentro de 800ms, ...
console.log(p.state) // estado atual
console.log(p.mood) // humor atual (ou null)
}
function onInteraction(p: BeniInteractionPayload) {
console.log(p.id) // ex.: 'happy-on-double-click'
console.log(p.label) // legível por humanos
console.log(p.triggerClickCount)
}Tematizando o avatar
<script setup lang="ts">
const theme = {
body: '#f59e0b',
bodyTo: '#ef4444',
eye: '#fef3c7',
}
</script>
<template>
<Beni
:body-base-color="theme.body"
:body-base-color-to="theme.bodyTo"
:eye-base-color="theme.eye"
/>
</template>Para um seletor de temas completo, use computed:
import { computed } from 'vue'
import type { BeniState, MoodName } from '@be-enlighten/beni-avatar'
const theme = computed(() => themes[currentTheme.value])
const state = ref<BeniState>('idle')
const mood = ref<MoodName | null>(null)O avatar é totalmente reativo: cada mudança de prop entra com tween (sem piscar), e estados/humores fazem crossfade suave.
Exemplo: estado de carregamento de um assistente de IA
<script setup lang="ts">
import { ref, watch } from 'vue'
import { Beni, type BeniState, type MoodName } from '@be-enlighten/beni-avatar'
const status = ref<'idle' | 'thinking' | 'done'>('idle')
const state = ref<BeniState>('idle')
const mood = ref<MoodName | null>(null)
watch(status, (s) => {
if (s === 'thinking') {
state.value = 'working'
mood.value = null
} else if (s === 'done') {
state.value = 'celebrate'
mood.value = 'happy'
} else {
state.value = 'idle'
mood.value = null
}
})
</script>
<template>
<div class="assistant">
<Beni
v-model:state="state"
v-model:mood="mood"
:head-item="status === 'done' ? 'partyhat' : null"
body-base-color="#fbbf24"
body-base-color-to="#ec4899"
eye-base-color="#fef3c7"
/>
<p v-if="status === 'thinking'">Pensando…</p>
<p v-else-if="status === 'done'">Pronto!</p>
</div>
</template>TypeScript
Pacote totalmente tipado. Exports mais úteis:
import type {
BeniState,
MoodName,
HeadItemKey,
BodyModelName,
EyeModelName,
BeniClickPayload,
BeniInteractionPayload,
} from '@be-enlighten/beni-avatar'
// Constantes
import {
beniStateNames, // ['idle', 'working', ...]
beniMoods,
headItemNames,
headItemLabels,
} from '@be-enlighten/beni-avatar'Suporte a navegadores
Qualquer navegador com suporte a CSS moderno (custom properties, filter) e SVG, ou seja, todos os navegadores evergreen. O GSAP exige requestAnimationFrame, que é universal.
O pacote respeita prefers-reduced-motion: reduce: as animações pausam no frame final quando o usuário tem essa preferência ativa.
Desenvolvimento
Este pacote vive no monorepo en-sdk (packages/beni-avatar). A demo/playground fica em examples/beni-avatar e consome o dist/ deste pacote via workspace — rode o build antes (e após cada mudança no src/).
pnpm install
pnpm --filter @be-enlighten/beni-avatar build # gera dist/ (pacote)
pnpm --filter @be-enlighten/beni-avatar typecheck # só type-check
pnpm --filter @be-enlighten/enspace-sdk-example-beni dev # playground em http://localhost:5173Publicando
Versão própria — este pacote não entra no lockstep dos 4 packages enspace-sdk-*. O versionamento é gerenciado por changesets (.changeset/ no root do monorepo): adicione um changeset com pnpm changeset a cada mudança user-facing e publique pelo fluxo unificado do monorepo:
pnpm version-packages # bumpa versão + gera CHANGELOG a partir dos changesets
pnpm release # valida, builda e publica o que estiver pendenteDetalhes na seção Publicação do AGENTS.md do monorepo. Sobe no npm público (access: public). Precisa de login npm local (npm login) com acesso ao escopo @be-enlighten.
Licença
MIT.
