Ir al contenido
Evaluar mi proyecto
Componentes · TarjetasDisponible

Tarjeta fotograma

Tarjeta con el formato de un fotograma de 35 mm: perforaciones, grano, indicador y código de tiempo.

REC01:24 / 02:10

Atardecer en el puerto

LIVE00:18 / 01:30

Bodega norte

Código completo

Es exactamente el archivo que se descarga. Cópialo a tu proyecto y úsalo.

tarjeta-cinema.tsx82 líneas
Descargar
/**
 * 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.

terminal.sh1 líneas
npx shadcn@latest add https://www.gassierconsulting.com/r/tarjeta-cinema

Ejemplo de uso de Tarjeta fotograma

Después de copiar tarjeta-cinema.tsx a tu proyecto, impórtalo y colócalo donde lo necesites.

ejemplo.tsx5 líneas
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.

PropiedadTipoDescripción
title?stringOpcional.
meta?stringCódigo de tiempo o dato corto en la esquina inferior derecha.
badge?stringTexto del indicador de la esquina superior izquierda.
ratio?"16/9" | "4/3" | "1/1" | "21/9"Opcional.
image?stringFotografía que sustituye la escena dibujada.
imageAlt?stringOpcional.
className?stringOpcional.
style?CSSPropertiesOpcional.

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.

VariableValor por defecto
--gcci-line#26272c
--gcci-bg#0e0e10
--gcci-ratio16 / 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-*.
Licencia
MIT
Dependencias
React >=18

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