Tarjeta fotograma
Tarjeta con el formato de un fotograma de 35 mm: perforaciones, grano, indicador y código de tiempo.
Atardecer en el puerto
Bodega norte
Código completo
Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.
/**
* Tarjeta con el formato de un fotograma de película de 35 mm: tiras laterales con
* perforaciones, grano fijo, un indicador de grabación y un código de tiempo. Sin imagen
* propia dibuja una escena gráfica con CSS (cielo en bandas, sol medio hundido, mar con
* reflejo); con `image` muestra tu fotografía dentro del mismo marco. Los colores se
* sobrescriben con --gcci-bg y --gcci-line.
*
* <TarjetaCinema title="Atardecer en el puerto" meta="01:24 / 02:10" badge="REC" />
* <TarjetaCinema title="Bodega norte" image="/bodega.jpg" imageAlt="Vista de la bodega" />
*/
import type { CSSProperties } from "react";
export type TarjetaCinemaProps = {
title?: string;
/** Código de tiempo o dato corto en la esquina inferior derecha. */
meta?: string;
/** Texto del indicador de la esquina superior izquierda. */
badge?: string;
ratio?: "16/9" | "4/3" | "1/1" | "21/9";
/** Fotografía que sustituye la escena dibujada. */
image?: string;
imageAlt?: string;
className?: string;
style?: CSSProperties;
};
const CSS = `
.gcci-root { --gcci-grain: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .6 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>"); --gcci-hole: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='26' height='30'><rect x='9' y='9' width='8' height='12' rx='2.5' fill='%2305050a'/></svg>"); box-sizing: border-box; overflow: hidden; border: 1px solid var(--gcci-line, #26272c); border-radius: 8px; background: var(--gcci-bg, #0e0e10); color: #ededee; font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
.gcci-strip { display: grid; grid-template-columns: 26px minmax(0, 1fr) 26px; background: #19191c; }
.gcci-strip::before, .gcci-strip::after { content: ""; background: var(--gcci-hole) center top / 26px 30px repeat-y; }
.gcci-frame { position: relative; overflow: hidden; aspect-ratio: var(--gcci-ratio, 16 / 9); background: #05050a; }
.gcci-scene { position: absolute; inset: 0; background: linear-gradient(180deg, #17120f 0%, #4d2a18 38%, #b5602c 62%, #f0a35a 74%); transition: transform .9s cubic-bezier(.16, 1, .3, 1); }
.gcci-sun { position: absolute; left: 50%; bottom: 36%; width: 24%; aspect-ratio: 1; transform: translate(-50%, 50%); border-radius: 50%; background: #ffe2b0; clip-path: inset(0 0 50% 0); }
.gcci-sea { position: absolute; left: 0; right: 0; bottom: 0; height: 36%; background: linear-gradient(180deg, #2a1a12, #0b0807); }
.gcci-sea::after { content: ""; position: absolute; left: 50%; top: 0; bottom: 0; width: 26%; transform: translateX(-50%); background: repeating-linear-gradient(180deg, rgba(255, 214, 160, .55) 0 1px, transparent 1px 7px); -webkit-mask-image: linear-gradient(180deg, #000, transparent 90%); mask-image: linear-gradient(180deg, #000, transparent 90%); }
.gcci-photo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transition: transform .9s cubic-bezier(.16, 1, .3, 1); }
.gcci-root:hover .gcci-scene, .gcci-root:hover .gcci-photo { transform: scale(1.04); }
.gcci-frame::after { content: ""; position: absolute; inset: 0; background: var(--gcci-grain); background-size: 160px 160px; opacity: .12; mix-blend-mode: overlay; pointer-events: none; }
.gcci-badge { position: absolute; z-index: 1; top: 10px; left: 12px; display: inline-flex; align-items: center; gap: 7px; font: 600 11px/1 ui-monospace, "SF Mono", Menlo, Consolas, monospace; letter-spacing: .12em; text-shadow: 0 1px 2px rgba(0, 0, 0, .6); }
.gcci-badge i { width: 7px; height: 7px; border-radius: 50%; background: #ff4a3d; animation: gcci-rec 1.6s steps(1, end) infinite; }
.gcci-time { position: absolute; z-index: 1; right: 12px; bottom: 10px; font: 12px/1 ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-variant-numeric: tabular-nums; text-shadow: 0 1px 2px rgba(0, 0, 0, .7); }
.gcci-title { margin: 0; padding: 14px 16px 16px; font-size: 15px; font-weight: 600; letter-spacing: -.01em; }
@keyframes gcci-rec { 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) { .gcci-badge i { animation: none; } .gcci-scene, .gcci-photo { transition: none; } }
`;
export function TarjetaCinema({
title = "Atardecer en el puerto",
meta = "01:24 / 02:10",
badge = "REC",
ratio = "16/9",
image,
imageAlt = "",
className,
style,
}: TarjetaCinemaProps) {
return (
<>
<style>{CSS}</style>
<article
className={["gcci-root", className].filter(Boolean).join(" ")}
style={{ ...style, ["--gcci-ratio" as string]: ratio.replace("/", " / ") }}
>
<div className="gcci-strip">
<div className="gcci-frame">
{image ? (
// eslint-disable-next-line @next/next/no-img-element
<img className="gcci-photo" src={image} alt={imageAlt} loading="lazy" />
) : (
<div className="gcci-scene" aria-hidden="true"><div className="gcci-sun" /><div className="gcci-sea" /></div>
)}
<span className="gcci-badge"><i aria-hidden="true" />{badge}</span>
<span className="gcci-time">{meta}</span>
</div>
</div>
<h3 className="gcci-title">{title}</h3>
</article>
</>
);
}
Úsalo en tu proyecto
Instálalo con la CLI de shadcn.
npx shadcn@latest add https://www.gassierconsulting.com/r/tarjeta-cinemaEjemplo de uso de Tarjeta fotograma
Después de copiar tarjeta-cinema.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.
import { TarjetaCinema } from "./tarjeta-cinema";
export default function Ejemplo() {
return <TarjetaCinema ratio="16/9" title="Ejemplo" />;
}Propiedades de TarjetaCinema
TarjetaCinema acepta 8 propiedades: todas son opcionales y tienen un valor por defecto, así que funciona sin configurar nada.
| Propiedad | Tipo | Descripción |
|---|---|---|
title? | string | Opcional. |
meta? | string | Código de tiempo o dato corto en la esquina inferior derecha. |
badge? | string | Texto del indicador de la esquina superior izquierda. |
ratio? | "16/9" | "4/3" | "1/1" | "21/9" | Opcional. |
image? | string | Fotografía que sustituye la escena dibujada. |
imageAlt? | string | Opcional. |
className? | string | Opcional. |
style? | CSSProperties | Opcional. |
Variantes disponibles
ratio:16/9,4/3,1/1,21/9
Personaliza Tarjeta fotograma con variables CSS
Define estas variables en el contenedor o en tu hoja de estilos para cambiar los colores sin tocar el componente. Si no las defines, se usa el valor por defecto.
| Variable | Valor por defecto |
|---|---|
--gcci-line | #26272c |
--gcci-bg | #0e0e10 |
--gcci-ratio | 16 / 9 |
Accesibilidad y rendimiento
El archivo tiene 82 líneas (5 KB) y define 1 animación con @keyframes. Esto es lo que hace Tarjeta fotograma por quien lo usa:
- Detecta prefers-reduced-motion y baja o quita el movimiento cuando la persona lo pide.
- Expone su estado a tecnologías de apoyo con atributos aria-*.
Acerca del producto
Sin imagen propia dibuja una escena gráfica en CSS; con image muestra tu fotografía dentro del mismo marco. Cuatro proporciones.
Incluye
Un solo archivo React + TypeScript con estilos dentro. Sin Tailwind ni dependencias externas.
- Tiras con perforaciones
- Escena CSS o fotografía propia
- Indicador y código de tiempo
- Cuatro proporciones
- Respeta prefers-reduced-motion