Ficha de proyecto

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

Leer la versión en texto src/components/ficha.css

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.

Cómo se importa

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

El marcado, vivo

Lo que se ve aquí abajo y lo que hay en el bloque de código son el mismo texto: se inyecta y se imprime desde una sola fuente. Si se separaran, el que se copia sería el que nadie mira.

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.

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

El marcado
<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.

El marcado
<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

Es la parte que más valor tiene de esta página. Cada línea está aquí porque costó algo: se probó, se retiró, o se perdieron horas.

Clases

.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.

Tokens de los que depende

Sin tokens.css importado antes, estas variables no existen: la pieza no falla, se pinta rota en silencio.

  • --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.