# Cajas animadas

> Las cajitas de Ghosty para tu sitio o tu app — estados, formas, pantallas, colores, temas y un resumen que se reordena arrastrando. Paquete @ghostystudio/boxes.

URL: https://www.ghosty.studio/docs/referencia/cajas

Cada caja de Ghosty es un robot con cabeza de tele: su cara dice qué hace (dormida, escuchando, trabajando, con error), sigue al mouse y brinca si le das clic. Es el mismo que ves en **Cajas del espacio**, y lo publicamos como paquete libre (MIT) para que lo uses en tus propias páginas y apps: `@ghostystudio/boxes`.

## Instalación

:::tabs
```html tab=HTML
<script src="https://unpkg.com/@ghostystudio/boxes/dist/boxes.iife.js"></script>

<ghosty-box state="happy"></ghosty-box>
```
```js tab=npm
// npm i @ghostystudio/boxes
import { defineGhostyBox, defineGhostyBoxes } from '@ghostystudio/boxes';
defineGhostyBox();   // <ghosty-box>
defineGhostyBoxes(); // <ghosty-boxes>
```
```tsx tab=React
// npm i @ghostystudio/boxes  (React 18 o 19)
import { RobotHead } from '@ghostystudio/boxes/react';

export function MiCaja() {
  return <RobotHead state="happy" size={120} />;
}
```
:::

Funciona en HTML pelón y en cualquier framework: es un custom element. En React hay dos componentes: `<RobotHead>` (una caja) y `<GhostyBoxes>` (el resumen). Ya traen `"use client"`, así que en Next.js (App Router) se usan tal cual dentro de un Server Component. Cada ejemplo de abajo tiene su pestaña **React**.

## Estados

Lo que la caja está haciendo. Cada estado tiene su cara, su movimiento y su luz.

:::boxes
<ghosty-box state="idle" size="96" title="idle"></ghosty-box>
<ghosty-box state="thinking" size="96" title="thinking"></ghosty-box>
<ghosty-box state="searching" size="96" title="searching"></ghosty-box>
<ghosty-box state="listening" size="96" title="listening"></ghosty-box>
<ghosty-box state="speaking" size="96" title="speaking"></ghosty-box>
<ghosty-box state="working" size="96" title="working"></ghosty-box>
<ghosty-box state="happy" size="96" title="happy"></ghosty-box>
<ghosty-box state="success" size="96" title="success"></ghosty-box>
<ghosty-box state="error" size="96" title="error"></ghosty-box>
<ghosty-box state="sleeping" size="96" title="sleeping"></ghosty-box>
:::

## Formas

:::boxes
<ghosty-box shape="rectangle" size="110" title="rectangle"></ghosty-box>
<ghosty-box shape="square" size="110" title="square"></ghosty-box>
<ghosty-box shape="circle" size="110" title="circle"></ghosty-box>
<ghosty-box shape="hexagon" size="110" title="hexagon"></ghosty-box>
:::

De temporada también hay `tree` (navidad) y `pumpkin` (halloween).

## Pantalla

`face` elige qué enseña: los ojos, una terminal o nada. Con la terminal, `screen-icon` pone un ícono: `mail`, `inbox`, `build`, `ci`, `preview`, `desktop` o `web` (y puedes registrar los tuyos con `registerScreenIcon`).

:::boxes
<ghosty-box face="eyes" size="110" title="eyes"></ghosty-box>
<ghosty-box face="terminal" state="listening" size="110" title="terminal"></ghosty-box>
<ghosty-box face="terminal" screen-icon="mail" size="110" title="mail"></ghosty-box>
<ghosty-box face="terminal" screen-icon="inbox" state="working" size="110" title="inbox · working"></ghosty-box>
<ghosty-box face="off" size="110" title="off"></ghosty-box>
:::

## Colores, temas y acabados

Un tema pinta la caja con una palabra; lo que pongas a mano le gana. `color` es la carcasa, `trim-color` las orejas y `screen-color` los LEDs. `texture` cambia el acabado y `wear` (0–1) le pone desgaste.

:::boxes
<ghosty-box theme="ghosty" size="110" title="ghosty"></ghosty-box>
<ghosty-box theme="mailmask" size="110" title="mailmask"></ghosty-box>
<ghosty-box theme="mailmaskGreen" size="110" title="mailmaskGreen"></ghosty-box>
<ghosty-box color="#7c3aed" trim-color="#fbbf24" screen-color="#fde68a" size="110" title="a mano"></ghosty-box>
<ghosty-box color="#8a8f98" texture="metal" size="110" title="metal"></ghosty-box>
<ghosty-box color="#a0522d" texture="wood" wear="0.6" size="110" title="wood · wear"></ghosty-box>
:::

Tu propio tema: `registerTheme('mimarca', { color: '#…', trimColor: '#…', screenColor: '#…' })` y luego `theme="mimarca"`.

## Resumen que se reordena arrastrando

`<ghosty-boxes>` es la fila de resumen: título, contador, una caja por elemento con su nombre, estado y puntitos, y lugares libres apagados. Agarra una caja: se levanta, se columpia y sonríe; al soltarla cae con un brinco. También con el teclado: Alt + ← / →.

:::boxes
<ghosty-boxes theme="mailmask" data='{"title":"Buzones","count":"4/6","sub":"12 sin leer · 1 importando","items":[{"id":"hola","name":"hola@","label":"8 sin leer","tone":"talk","dots":{"on":8,"cap":10},"head":{"state":"happy"}},{"id":"ventas","name":"ventas@","label":"importando","tone":"work","dots":{"on":3,"cap":10},"head":{"state":"working","screenIcon":"inbox"}},{"id":"soporte","name":"soporte@","label":"4 sin leer","tone":"talk","dots":{"on":4,"cap":10},"head":{"state":"listening"}},{"id":"facturas","name":"facturas@","label":"casi llena","tone":"error","dots":{"on":10,"cap":10},"head":{"state":"error"}},{"id":"l1","name":"libre","off":true},{"id":"l2","name":"libre","off":true}]}'></ghosty-boxes>
:::

Los datos también entran por código, y el orden nuevo sale en un evento:

:::tabs
```js tab=JavaScript
const row = document.querySelector('ghosty-boxes');
row.data = { title: 'Buzones', count: '4/6', items: [/* … */] };
row.addEventListener('box-reorder', (e) => guardar(e.detail.order)); // { id, from, to, order }
row.addEventListener('box-open', (e) => abrir(e.detail.id));
```
```tsx tab=React
import { useState } from 'react';
import { GhostyBoxes, type SummaryItem } from '@ghostystudio/boxes/react';

export function Buzones({ inicial }: { inicial: SummaryItem[] }) {
  const [items, setItems] = useState(inicial);
  return (
    <GhostyBoxes
      theme="mailmask"
      data={{ title: 'Buzones', count: `${items.length}/6`, items }}
      // { id, from, to, order }: deja el estado en el orden nuevo y guárdalo
      onReorder={({ order }) => {
        setItems(order.map((id) => items.find((it) => it.id === id)!));
        guardar(order);
      }}
      onOpen={({ id }) => abrir(id)}
    />
  );
}
```
:::

Cada elemento lleva `id`, `name`, y opcionales `label`, `tone` (`work`, `talk`, `error`, `sleep`, `calm`), `dots` (`{ on, cap }`), `off` (lugar libre, fijo), `group` (sólo se reordena dentro de su grupo) y `head` (las props de su caja). Los colores de la tarjeta salen de variables CSS sobre `.gbx`: `--gbx-bg`, `--gbx-border`, `--gbx-fg`, `--gbx-muted`, `--gbx-work`, `--gbx-talk`, `--gbx-error`, `--gbx-dot`, `--gbx-radius`.

## Atributos de `<ghosty-box>`

| Atributo | Valores | Default |
|---|---|---|
| `theme` | `ghosty`, `mailmask`, `mailmaskGreen` o uno tuyo | — |
| `state` | `idle`, `thinking`, `searching`, `listening`, `speaking`, `working`, `happy`, `success`, `error`, `sleeping` | `idle` |
| `shape` | `rectangle`, `square`, `circle`, `hexagon`, `tree`, `pumpkin` | `rectangle` |
| `size` | px | `160` |
| `color` · `trim-color` · `screen-color` | hex o `rgb()` | azul · acero · blanco |
| `face` | `eyes`, `terminal`, `off` | `eyes` |
| `screen-icon` | con terminal: `mail`, `inbox`, `build`, `ci`, `preview`, `desktop`, `web` | — |
| `texture` | `grain`, `smooth`, `paper`, `fabric`, `metal`, `leather`, `wood`, `ribs` | `grain` |
| `wear` | 0–1 | `0` |
| `speed` | multiplicador; `0` la congela | `1` |
| `seed` | 0–1, para que una fila no parpadee al unísono | al azar |
| `paused` · `interactive="false"` · `antenna="false"` · `floor-shadow="false"` | banderas | — |

En React son las mismas props en camelCase (`trimColor`, `screenIcon`, `floorShadow`) y los números y banderas van entre llaves (`size={96}`, `antenna={false}`). Respeta `prefers-reduced-motion`. Los cambios de atributo se ven al instante.

El robot es [robot-heads](https://github.com/fayazara/robot-heads) de Fayaz Ahmed (MIT), con estados, formas, pantallas, temas y acabados añadidos por Ghosty.
