# Ficha de proyecto

> La hoja tipo App Store: la tarjeta no saca al visitante a otra pestaña en el primer clic.

Paquete: `@pantherkit/design-system` · Archivo: `src/components/ficha.css`

Se importa con:

```css
@import "@pantherkit/design-system/tokens.css";  /* siempre antes */
@import "@pantherkit/design-system/components.css";
```

La pieza se entrega como CSS, no como componente React: se aplica poniendo
las clases en tu propio marcado. No hay import de JavaScript que hacer.

## Qué es

El primer clic sobre un proyecto no debería echar al visitante del sitio. La ficha resuelve eso: captura entera, descripción completa, todo el stack y **una sola acción**, que es la que sí lleva fuera.

En escritorio se parte en dos columnas a partir de 64rem. Apilado —captura arriba, texto debajo— es la forma de un teléfono; en una pantalla de 1500px eso deja la ficha estrecha y larga con medio monitor vacío a los lados.

La ficha entra con un fundido y una escala corta: 1.5% de escala, 6px, y la curva frena largo. Lo que se percibe es que la ficha **se posa**, no que salta.

## Clases

| Clase | Qué hace |
| --- | --- |
| `.caso-overlay` | La capa fija. Se sirve con `hidden` y la abre el guion. |
| `.caso-fondo` | El velo. Tinta del sistema al 72% con desenfoque: la ficha se despega de la página en vez de fundirse con ella. |
| `.caso-panel` | La hoja. `role="dialog" aria-modal="true"`. |
| `.caso-cerrar` | Flota sobre la captura, no sobre una superficie del sistema: por eso lleva velo propio con desenfoque. |
| `.caso-panel__media` | La captura. `object-position: top center`, porque recortar una captura por el centro decapita justo lo que identifica el proyecto. Con `hidden`, el texto ocupa la ficha entera. |
| `.caso-panel__meta` | Tag e industria, en mono. La mono es señal técnica —metadata de documento—, nunca prosa. |
| `.caso-panel__titulo / __desc / __chips / __cta` | El cuerpo. `__chips` es solo el contenedor; `__cta` es solo la posición. |

## El marcado

Esto es exactamente lo que se pinta en la página `/componentes/ficha`:
el mismo texto se inyecta ahí como pieza viva y se imprime aquí. No pueden
separarse.

### La ficha completa

El disparador y la hoja van juntos en el mismo fragmento porque no se entienden por separado: sin JavaScript la ficha no abre, y lo único que salva la degradación es que la tarjeta sea un enlace real al proyecto.

*Nota: El guion de abrir/cerrar es de la vitrina. `ficha.css` no trae JavaScript: solo pinta.*

```html
<button type="button" class="fbtn" data-ficha-abrir="ficha-demo">Abrir la ficha</button>

<div class="caso-overlay" id="ficha-demo" hidden>
  <div class="caso-fondo" data-ficha-cerrar></div>
  <div class="caso-panel" role="dialog" aria-modal="true" aria-labelledby="ficha-demo-titulo">
    <button type="button" class="caso-cerrar" data-ficha-cerrar aria-label="Cerrar la ficha">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>
    </button>
    <!-- En producción aquí va <img src="/captura.png" alt="…">. La vitrina pinta
         un armazón para no traerse una imagen que no documenta nada. -->
    <div class="caso-panel__media">
      <svg viewBox="0 0 320 200" class="h-full w-full" role="img" aria-label="Armazón de una captura de pantalla" opacity="0.35">
        <g fill="currentColor">
          <rect x="0" y="0" width="320" height="22" opacity="0.5"/>
          <rect x="0" y="22" width="72" height="178" opacity="0.28"/>
          <rect x="88" y="40" width="140" height="12"/>
          <rect x="88" y="62" width="200" height="8" opacity="0.6"/>
          <rect x="88" y="78" width="176" height="8" opacity="0.6"/>
          <rect x="88" y="108" width="90" height="60" opacity="0.4"/>
          <rect x="190" y="108" width="98" height="60" opacity="0.4"/>
        </g>
      </svg>
    </div>
    <div class="caso-panel__cuerpo">
      <p class="caso-panel__meta">Plataforma · Logística</p>
      <h3 class="caso-panel__titulo" id="ficha-demo-titulo">Un proyecto de ejemplo</h3>
      <p class="caso-panel__desc">La descripción completa cabe aquí porque la ficha no es un tooltip: es la parada intermedia entre la tarjeta y el sitio real. Quien llega hasta abajo ya decidió.</p>
      <div class="caso-panel__chips">
        <span class="rounded-sm border border-border px-2 py-1 font-mono text-[0.65rem] uppercase tracking-[0.12em] text-text-muted">Astro</span>
        <span class="rounded-sm border border-border px-2 py-1 font-mono text-[0.65rem] uppercase tracking-[0.12em] text-text-muted">AWS</span>
      </div>
      <a href="/componentes" class="caso-panel__cta fbtn fbtn--primario">Ver el proyecto</a>
    </div>
  </div>
</div>
```

### Sin captura

Con `hidden` en `.caso-panel__media` la columna izquierda desaparece y el texto ocupa la ficha entera. Media caja vacía sería peor que no partirla, y por eso está previsto en el CSS en vez de dejarse al consumidor.

```html
<button type="button" class="fbtn" data-ficha-abrir="ficha-sin-media">Abrir sin captura</button>

<div class="caso-overlay" id="ficha-sin-media" hidden>
  <div class="caso-fondo" data-ficha-cerrar></div>
  <div class="caso-panel" role="dialog" aria-modal="true" aria-labelledby="ficha-sin-media-titulo">
    <button type="button" class="caso-cerrar" data-ficha-cerrar aria-label="Cerrar la ficha">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>
    </button>
    <div class="caso-panel__media" hidden></div>
    <div class="caso-panel__cuerpo">
      <p class="caso-panel__meta">Infraestructura · Interno</p>
      <h3 class="caso-panel__titulo" id="ficha-sin-media-titulo">Un proyecto sin captura</h3>
      <p class="caso-panel__desc">No todo proyecto tiene una pantalla que enseñar. La ficha se adapta en vez de dejar medio panel vacío.</p>
      <a href="/componentes" class="caso-panel__cta fbtn fbtn--primario">Ver el proyecto</a>
    </div>
  </div>
</div>
```

## Cuándo NO usarla

La parte que más valor tiene. Cada una está aquí porque costó algo.

### Como diálogo de confirmación o formulario.

Es una hoja de lectura con una acción. Un formulario dentro hereda `max-height: 80vh` con scroll interno y un botón de cerrar que flota sobre el contenido: exactamente lo que no se quiere cuando hay que rellenar campos.

### Con dos CTA.

`.caso-panel__cta` está en singular a propósito. La ficha existe para que el visitante no se vaya en el primer clic; darle dos salidas devuelve el problema que la ficha vino a resolver.

### Con la tarjeta disparadora hecha con un `<div>` o un `<button>`.

Sin JavaScript la ficha no abre. La única degradación que hay es que la tarjeta sea un `<a href>` real al proyecto: entonces el visitante va directo al sitio en vez de a la hoja. Con un `<div>` no queda nada.

### Encima del navbar de cristal.

`z-index: 90` está escrito literal para quedar por debajo del 100 del navbar. El sistema no tiene escala de z-index; si la ficha tiene que tapar la barra, eso es una decisión del sistema, no un número que se sube en el consumidor.

### Con morph desde la tarjeta (View Transitions).

Se probó y se descartó: «no me gusta ese pedo». Una tarjeta vertical y una hoja apaisada siempre se leen como una caja que se deforma, por bien afinada que esté la transición.

## Tokens de los que depende

Si `tokens.css` no está importado antes, estas variables no existen y la
pieza se pinta con los valores iniciales del navegador —que no es un fallo
visible, es una pieza silenciosamente rota.

- `--color-bg-primary`
- `--color-bg-surface-1`
- `--color-text-primary`
- `--color-text-secondary`
- `--color-text-muted`
- `--color-border`
- `--radius-xl`
- `--radius-full`
- `--font-mono`
- `--dur-base`
- `--dur-medium`
- `--ease-out-quint`

## Qué queda cuando algo falla

- Sin JavaScript la ficha no se abre: se sirve con `hidden` y la rellena el guion. La rejilla de tarjetas sigue funcionando porque cada tarjeta es un `<a>` al proyecto real. El CSS no finge nada por su cuenta.
- Sin `backdrop-filter` el velo sube a 92% de tinta: sin desenfoque, la página de detrás competiría con la ficha.
- Con `prefers-reduced-motion` la ficha aparece de golpe y el botón de cerrar deja de crecer, pero su fondo sigue aclarándose. El estado se queda.

## Accesibilidad

- El `<dialog>` nativo no se usa: la pieza es `role="dialog" aria-modal="true"` sobre un `<div>`, así que el foco, el `Esc` y el retorno del foco al disparador **los pone el consumidor**. No vienen en el CSS.
- `aria-labelledby` apuntando a `.caso-panel__titulo`: el diálogo tiene que anunciarse con el nombre del proyecto.
- `.caso-cerrar` es un icono sin texto: necesita `aria-label`.
- El bloqueo del scroll del cuerpo al abrir también es del consumidor —y de su scroll virtual, si lo tiene—, no de esta hoja.

## Lo que el sistema todavía no resuelve

Valores literales y huecos conocidos. Se escriben para que nadie los
confunda con una decisión.

- El `letter-spacing: -0.018em` del título está literal: cae entre `--tracking-heading` (-0.012em) y `--tracking-display` (-0.028em), que es justo donde está su tamaño. Falta el eslabón intermedio de la escala.
- El `z-index: 90` y el `blur(10px)` también son literales: no hay escala de capas ni escala de desenfoque en los tokens.

---

Versión viva: <https://design.wearefaber.com/componentes/ficha>
Índice de componentes: </componentes.md>
