Saltar al contenido
motionframe

Buscar piezas

Categorías

La pieza

Qué hace y dónde encaja

Una portada a pantalla completa con un titular gigante en el centro y, detrás, dos carriles de tarjetas que nacen como un punto, se abren hacia los lados y crecen de forma geométrica hasta salirse por los bordes, girando de 6° a 28° como las paredes de un pasillo. Las fotos van en escala de grises y solo una pastilla de color vivo rompe el blanco y negro. Una tasa de reproducción única controla todo: arranca rápido y se asienta, la rueda y el arrastre aceleran o invierten el sentido con inercia, y al pasar el cursor por una tarjeta el corredor se detiene, la foto recupera su color y aparece un cursor propio con su título. Trae tres colores de acento, fondo oscuro o claro, botón imantado y pausa. Encaja en portadas de estudios creativos, arquitectura e interiorismo, fotografía, moda o portfolios. Sin librerías: 28 KB de HTML.

Prompt
≈11.698 tokens · con Lite
Librerías
ninguna
Demo
27,8 KB · ligero
Prueba en frío
pendiente

Antes de copiar

Tu marca, dentro del prompt

Cambia lo que quieras y copia: el prompt sale ya con el nombre, los colores y los textos de tu cliente. Lo que dejes en blanco se queda como en la demo.

Colores

Sin cambios: se copia con los valores de la demo.

Gratis y entero

El prompt, entero y a la vista

Qué trae el prompt, por apartados

  1. 01Qué es
  2. 02Librerías y esqueleto
  3. 03Tokens de diseño
  4. 04Estructura y contenido
  5. 05Movimiento
  6. 06Código clave
  7. 07Interacción
  8. 08Adaptación a móvil y tablet
  9. 09Accesibilidad y movimiento reducido
  10. 10Rendimiento
  11. 11Personalización
  12. 12Comprobación final
Completo
5.802 palabras · ≈11.698 tokens
Lite
1.581 palabras · ≈2.839 tokens
Librerías
ninguna
Peso de la demo
27,8 KB (ligero)
Resolución máxima (DPR)
2
Se pausa fuera de pantalla
sí
Respeta «menos movimiento»
sí

Tokens estimados para español (≈3,6 caracteres por token): sirven para orientarte, no son exactos.

prompt.md
# Recrea en un único archivo HTML: Corredor de imágenes

Una sección a pantalla completa en blanco y negro con un color vivo: dos carriles de tarjetas con fotos en escala de grises vienen desde el fondo hacia el espectador en perspectiva, crecen de golpe y se van por los bordes mientras un titular gigante se queda en el centro; el cursor las detiene y las pinta de color.

Eres un desarrollador front-end creativo senior. Genera un único `index.html` autocontenido que
reproduzca exactamente la pieza descrita: mismo layout, textos, colores, movimiento e interacción.
HTML, CSS y JS en un solo archivo, sin build ni frameworks. Librerías solo por CDN y en las versiones
indicadas. Todos los valores numéricos de este documento son definitivos: úsalos tal cual.
Responde solo con el archivo completo.

## Qué es

La portada de **Umbral**, un estudio de imagen de Aldenor. Estética de estudio contemporáneo: fondo casi negro (o papel claro), tipografía grotesca condensada muy pesada, una sola pastilla de color vivo (lima ácido por defecto) y fotografías en blanco y negro. No hay cursivas serif, ni letra manuscrita, ni pasteles, ni degradados.

Cómo se comporta:

1. **Dos carriles.** 10 tarjetas horizontales (16:10) en el carril izquierdo y 10 tarjetas verticales (3:4) en el derecho. Cada tarjeta recorre un ciclo de 30 s en bucle infinito: nace como un punto (≈2,6 % del ancho del escenario) cruzando un poco el eje central, se abre enseguida hacia su carril y crece de forma **geométrica** hasta ≈46 % del ancho (horizontales) o ≈34 % (verticales). Gira sobre el eje Y de 6° a 28°, siempre con el borde exterior más cerca del espectador (como las paredes de un pasillo).
2. **Retardos negativos.** Las 20 tarjetas son la misma animación con retardos negativos repartidos de forma uniforme (el carril derecho va desfasado medio paso), así que el corredor está lleno desde el primer fotograma. Los keyframes se **muestrean por JS** (80 muestras) en función del tamaño real del escenario, no se escriben a mano.
3. **Titular en el centro** con una palabra sobre una pastilla de color de acento, subtítulo y un botón. Un velo radial muy suave detrás del texto mantiene el contraste cuando una tarjeta pasa por detrás.
4. **Velocidad viva.** Una única tasa de reproducción compartida por todas las animaciones: arranca a ×7 y se asienta en ×1 (efecto «de golpe a velocidad»); la rueda y el arrastre la aceleran o la invierten con inercia, y al pasar el cursor por una tarjeta el corredor se frena hasta parar.
5. **Tarjeta en foco.** La tarjeta bajo el cursor se queda quieta, recupera su color original, se rodea de un filete de acento y su pie se pinta de acento; las demás se oscurecen un 55 %. Un cursor propio, un punto de acento que se convierte en pastilla con el nombre del proyecto, sigue al puntero con un retardo suave.
6. **Controles.** Pausa, tres colores de acento (lima, naranja, azul eléctrico) y un conmutador de fondo oscuro/claro.

## Librerías y esqueleto

Sin librerías. Todo es HTML, CSS y la Web Animations API del navegador.

Fuentes (Google Fonts, `display=swap`; ambas tienen ñ, tildes, ¿ y ¡):

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wdth,wght@12..96,75..100,200..800&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
```

Cabecera exacta:

```html
<!doctype html>
<html lang="es" data-tema="oscuro" data-acento="lima">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Umbral — Estudio de imagen en Aldenor</title>
<meta name="description" content="Identidades, espacios y producto. Veinte proyectos de Umbral en un solo corredor de imágenes.">
```

**Imágenes.** Son fotografías del banco de la pieza, en WebP y AVIF, a dos tamaños (`<archivo>-800.webp`, de 800 px de ancho, y `<archivo>.webp`, del tamaño original), siempre bajo `https://motionframe-two.vercel.app/assets/banco`. Verticales (1080×1440): `https://motionframe-two.vercel.app/assets/banco/v01-cromo-espiral.webp` (escultura de cromo), `v02-vidrio-prisma`, `v03-ceramica-apilada`, `v04-arco-escalera`, `v05-duna-nocturna`, `v06-orbe-obsidiana`, `v07-frasco-ambar`, `v08-columnata-hormigon`. Horizontales (1600×1000): `https://motionframe-two.vercel.app/assets/banco/h01-interior-arco.webp`, `h02-cordilleras-niebla`, `h03-cromo-liquido`, `h04-muralla-pastel`, `h05-lago-luna`, `h06-seda-granate`, `h07-piedras-equilibrio`, `h08-pabellon-agua`. Cada una existe también como `.avif` y `-800.avif`. Se pintan siempre en escala de grises, así que su color original no importa. Con otras fotos vale cualquier WebP/AVIF vertical 3:4 y horizontal 16:10.

## Tokens de diseño

```css
:root {
  --fondo: #0a0a0a;
  --texto: #f3f3ef;
  --suave: rgba(243, 243, 239, .66);
  --linea: rgba(243, 243, 239, .18);
  --acento: #d4ff00;
  --sobre-acento: #0a0a0a;
  --f-display: "Bricolage Grotesque", "Helvetica Neue", Arial, sans-serif;
  --f-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --gutter: clamp(16px, 2.2vw, 36px);
  --e-salida: cubic-bezier(.16, 1, .3, 1);
  --e-suave: cubic-bezier(.65, 0, .35, 1);
  color-scheme: dark;
}
[data-tema="claro"] { --fondo: #f4f4f0; --texto: #0a0a0a; --suave: rgba(10, 10, 10, .64); --linea: rgba(10, 10, 10, .18); color-scheme: light; }
[data-acento="naranja"] { --acento: #ff4d00; --sobre-acento: #0a0a0a; }
[data-acento="azul"]    { --acento: #2a4bff; --sobre-acento: #ffffff; }
```

Reglas de estilo: blanco y negro con **un** color vivo; nada de pasteles. Titular en Bricolage Grotesque 800 con `font-stretch: 78%` y `opsz` 96; textos en la misma familia; etiquetas, contadores y pies en Geist Mono en mayúsculas. Esquinas de 6 px en las tarjetas, pastillas redondas en botones. Sin sombras.

## Estructura y contenido

Árbol de la página (textos literales; el `<script>` va después, ver «Código clave»):

```html
<a class="saltar" href="#contenido">Saltar al contenido</a>

<main class="stage" id="stage">
  <div class="lanes" id="lanes" aria-hidden="true"></div>
  <div class="velo" aria-hidden="true"></div>

  <header class="top">
    <a class="logo" href="#contenido" aria-label="Umbral, inicio"><i></i>Umbral</a>
    <nav class="nav" aria-label="Principal">
      <a href="#contenido">Proyectos</a>
      <a href="#contenido">Estudio</a>
      <a href="#contenido">Contacto</a>
    </nav>
  </header>

  <section class="centro" id="contenido" aria-labelledby="titulo">
    <h1 id="titulo"><span class="l"><span>Lo mejor</span></span><span class="l"><span><span class="hl">viene</span> de frente.</span></span></h1>
    <p class="sub">Veinte proyectos de Umbral en un solo pasillo: identidades, espacios y producto. Pasa el cursor por uno y el corredor se detiene.</p>
    <a class="cta" id="cta" href="#contenido">Ver los proyectos
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
    </a>
  </section>

  <footer class="hud">
    <p class="ayuda" style="margin:0"><span class="teclas"><kbd>←</kbd> <kbd>→</kbd></span><span>Arrastra o gira la rueda para acelerar</span></p>
    <div class="medidor" aria-hidden="true"><span>Vel.</span><span class="barra"><i id="barra"></i></span><output id="vel">×1,0</output></div>
    <div class="ctrl">
      <button class="btn btn-pausa" id="pausa" type="button" aria-pressed="false" aria-label="Pausar el corredor">
        <svg class="i-pause" viewBox="0 0 14 14" aria-hidden="true"><rect x="2" y="1" width="3.4" height="12" rx=".6"/><rect x="8.6" y="1" width="3.4" height="12" rx=".6"/></svg>
        <svg class="i-play" viewBox="0 0 14 14" aria-hidden="true"><path d="M3 1.2v11.6a.6.6 0 0 0 .9.5l9-5.8a.6.6 0 0 0 0-1L3.9.7A.6.6 0 0 0 3 1.2z"/></svg>
      </button>
      <span role="group" aria-label="Color de acento" style="display:flex;align-items:center">
        <button class="sw" type="button" data-ac="lima" style="--c:#d4ff00" aria-pressed="true" aria-label="Acento lima"></button>
        <button class="sw" type="button" data-ac="naranja" style="--c:#ff4d00" aria-pressed="false" aria-label="Acento naranja"></button>
        <button class="sw" type="button" data-ac="azul" style="--c:#2a4bff" aria-pressed="false" aria-label="Acento azul eléctrico"></button>
      </span>
      <button class="btn tema" id="tema" type="button" aria-pressed="false" aria-label="Cambiar a fondo claro">
        <svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="6.5" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="M8 1.5a6.5 6.5 0 0 1 0 13z" fill="currentColor"/></svg>
      </button>
    </div>
  </footer>

  <div class="cur" id="cur" aria-hidden="true"><span id="curtxt">Pausa</span></div>
  <ul class="sr" id="lista"></ul>
</main>
```

CSS completo (copiar tal cual dentro de `<style>`):

```css
:root {
  --fondo: #0a0a0a;
  --texto: #f3f3ef;
  --suave: rgba(243, 243, 239, .66);
  --linea: rgba(243, 243, 239, .18);
  --acento: #d4ff00;
  --sobre-acento: #0a0a0a;
  --f-display: "Bricolage Grotesque", "Helvetica Neue", Arial, sans-serif;
  --f-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --gutter: clamp(16px, 2.2vw, 36px);
  --e-salida: cubic-bezier(.16, 1, .3, 1);
  --e-suave: cubic-bezier(.65, 0, .35, 1);
  color-scheme: dark;
}
[data-tema="claro"] {
  --fondo: #f4f4f0;
  --texto: #0a0a0a;
  --suave: rgba(10, 10, 10, .64);
  --linea: rgba(10, 10, 10, .18);
  color-scheme: light;
}
[data-acento="naranja"] { --acento: #ff4d00; --sobre-acento: #0a0a0a; }
[data-acento="azul"]    { --acento: #2a4bff; --sobre-acento: #ffffff; }
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; height: 100%; overflow: hidden; }
body {
  background: var(--fondo);
  color: var(--texto);
  font-family: var(--f-display);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  transition: background-color .6s var(--e-suave), color .6s var(--e-suave);
}
a { color: inherit; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
:focus-visible { outline: 2px solid var(--acento); outline-offset: 3px; border-radius: 3px; }
.sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; list-style: none; }
.saltar { position: absolute; left: var(--gutter); top: -60px; z-index: 40; background: var(--acento); color: var(--sobre-acento); padding: 10px 16px; border-radius: 999px; text-decoration: none; font-weight: 600; }
.saltar:focus { top: 12px; }

/* ——— Escenario ——— */
.stage { position: relative; height: 100svh; min-height: 480px; overflow: hidden; isolation: isolate; touch-action: pan-y; user-select: none; -webkit-user-select: none; }
.lanes { position: absolute; inset: 0; will-change: transform; }
.card {
  position: absolute; left: 50%; top: 50%;
  margin: 0; overflow: hidden; border-radius: 6px;
  background: #161616;
  outline: 1px solid rgba(255, 255, 255, .16); outline-offset: -1px;
  will-change: transform, opacity;
  backface-visibility: hidden;
  opacity: 0;
}
.card picture, .card img { display: block; width: 100%; height: 100%; }
.card img { object-fit: cover; filter: grayscale(1) contrast(1.14) brightness(1.06); transition: filter .45s var(--e-suave); pointer-events: none; }
.card .vel { position: absolute; inset: 0; background: var(--fondo); opacity: .7; pointer-events: none; }
.card::after { content: ""; position: absolute; inset: 0; background: var(--fondo); opacity: 0; transition: opacity .45s var(--e-suave); pointer-events: none; }
.card .cap {
  position: absolute; left: 14px; bottom: 14px; right: 14px;
  display: flex; justify-content: space-between; gap: 10px; align-items: flex-end;
  font: 500 12px/1.25 var(--f-mono); letter-spacing: .02em; text-transform: uppercase; color: #fff;
  pointer-events: none; z-index: 2;
}
.card .cap b { font-weight: 500; background: #0a0a0a; padding: 5px 8px; border-radius: 3px; }
.card .cap i { font-style: normal; opacity: .85; text-shadow: 0 0 8px rgba(0, 0, 0, .8); }
.is-hold .card:not(.is-on)::after { opacity: .55; }
.card.is-on { outline: 2px solid var(--acento); outline-offset: -2px; }
.card.is-on img { filter: none; }
.card.is-on .vel { visibility: hidden; }
.card.is-on .cap b { background: var(--acento); color: var(--sobre-acento); }
.velo {
  position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(ellipse 40% 36% at 50% 50%, color-mix(in srgb, var(--fondo) 74%, transparent) 0, color-mix(in srgb, var(--fondo) 34%, transparent) 55%, transparent 100%);
}

/* ——— Texto y controles ——— */
.top, .hud { position: absolute; left: 0; right: 0; z-index: 5; display: flex; align-items: center; justify-content: space-between; padding: 0 var(--gutter); pointer-events: none; }
.top { top: 0; height: 76px; }
.hud { bottom: 0; height: 84px; gap: 16px; }
.top > *, .hud > * { pointer-events: auto; }
.logo { text-transform: lowercase; display: inline-flex; align-items: center; gap: 9px; font-weight: 700; font-size: 20px; letter-spacing: -.03em; text-decoration: none; }
.logo i { width: 11px; height: 11px; border-radius: 50%; background: var(--acento); display: block; }
.nav { display: flex; gap: clamp(14px, 2.4vw, 34px); font: 500 12px var(--f-mono); letter-spacing: .04em; text-transform: uppercase; }
.nav a { text-decoration: none; position: relative; padding: 6px 0; }
.nav a::after { content: ""; position: absolute; left: 0; right: 0; bottom: 1px; height: 1px; background: var(--acento); transform: scaleX(0); transform-origin: right; transition: transform .5s var(--e-salida); }
.nav a:hover::after, .nav a:focus-visible::after { transform: scaleX(1); transform-origin: left; }

.centro { position: absolute; inset: 0; z-index: 4; display: grid; place-content: center; justify-items: center; text-align: center; padding: 76px var(--gutter) 84px; pointer-events: none; }
h1 {
  margin: 0; font-weight: 800; font-stretch: 78%;
  font-size: clamp(54px, 8.4vw, 156px); line-height: .9; letter-spacing: -.032em; word-spacing: .06em;
  font-variation-settings: "opsz" 96;
}
h1 .l { display: block; overflow: hidden; padding: .06em .12em .1em; margin: -.06em -.12em -.1em; }
h1 .l > span { display: inline-block; transform: translateY(112%); }
h1 .hl { position: relative; display: inline-block; color: var(--sobre-acento); padding: 0 .14em; margin: 0 .02em 0 -.14em; }
h1 .hl::before { content: ""; position: absolute; inset: .06em 0 .04em; background: var(--acento); border-radius: .06em; transform: scaleX(0); transform-origin: left; z-index: -1; }
.sub { margin: clamp(18px, 2.6vw, 34px) 0 0; max-width: 34ch; font-size: clamp(15px, 1.25vw, 18px); line-height: 1.45; color: var(--suave); opacity: 0; transform: translateY(14px); }
.cta {
  --mx: 50%; --my: 50%;
  position: relative; display: inline-flex; align-items: center; gap: 12px; overflow: hidden; isolation: isolate;
  margin-top: clamp(20px, 2.4vw, 30px); padding: 15px 22px 15px 24px; border-radius: 999px;
  border: 1px solid var(--texto); font: 600 15px var(--f-display); letter-spacing: -.01em; text-decoration: none;
  pointer-events: auto; opacity: 0; transform: translateY(14px);
  transition: color .45s var(--e-salida), border-color .45s var(--e-salida);
  will-change: transform;
}
.cta::before { content: ""; position: absolute; z-index: -1; left: var(--mx); top: var(--my); width: 420px; height: 420px; margin: -210px 0 0 -210px; border-radius: 50%; background: var(--acento); transform: scale(0); transition: transform .6s var(--e-salida); }
.cta:hover, .cta:focus-visible { color: var(--sobre-acento); border-color: var(--acento); }
.cta:hover::before, .cta:focus-visible::before { transform: scale(1); }
.cta svg { width: 18px; height: 18px; transition: transform .5s var(--e-salida); }
.cta:hover svg { transform: translateX(4px); }

.ayuda { font: 400 12px/1.4 var(--f-mono); color: var(--suave); text-transform: uppercase; letter-spacing: .04em; display: flex; align-items: center; gap: 10px; }
.ayuda kbd { font: 500 11px var(--f-mono); border: 1px solid var(--linea); border-radius: 4px; padding: 2px 6px; color: var(--texto); }
.medidor { display: flex; align-items: center; gap: 12px; font: 500 12px var(--f-mono); letter-spacing: .04em; text-transform: uppercase; }
.medidor .barra { position: relative; width: clamp(60px, 9vw, 130px); height: 2px; background: var(--linea); }
.medidor .barra i { position: absolute; left: 0; top: -1px; height: 4px; width: 100%; background: var(--acento); transform-origin: 0 50%; transform: scaleX(.2); }
.medidor output { min-width: 5.4ch; text-align: right; font-variant-numeric: tabular-nums; }
.ctrl { display: flex; align-items: center; gap: 8px; }
.btn { width: 38px; height: 38px; border-radius: 50%; border: 1px solid var(--linea); display: grid; place-items: center; transition: background-color .3s, border-color .3s, transform .5s var(--e-salida); }
.btn:hover { border-color: var(--texto); transform: scale(1.08); }
.btn svg { width: 14px; height: 14px; fill: currentColor; }
.btn .i-play { display: none; }
.btn[aria-pressed="true"] .i-play { display: block; }
.btn[aria-pressed="true"] .i-pause { display: none; }
.sw { width: 18px; height: 18px; border-radius: 50%; background: var(--c); position: relative; transition: transform .45s var(--e-salida); margin: 0 6px; }
.sw::after { content: ""; position: absolute; inset: -5px; border-radius: 50%; border: 1px solid var(--texto); opacity: 0; transform: scale(.7); transition: opacity .3s, transform .45s var(--e-salida); }
.sw:hover { transform: scale(1.18); }
.sw[aria-pressed="true"]::after { opacity: 1; transform: scale(1); }
.tema { margin-left: 4px; }
.tema svg { width: 16px; height: 16px; }

/* ——— Cursor ——— */
.cur { position: absolute; left: 0; top: 0; z-index: 8; pointer-events: none; opacity: 0; will-change: transform; }
.cur span {
  display: block; transform: translate(-50%, -50%) scale(.2);
  min-width: 56px; padding: 9px 15px; border-radius: 999px; white-space: nowrap; color: transparent;
  background: var(--acento);
  font: 500 12px/1.1 var(--f-mono); letter-spacing: .03em; text-transform: uppercase; text-align: center;
  transition: transform .5s var(--e-salida), color .3s;
}
.cur.on span { transform: translate(-50%, -50%) scale(1); color: var(--sobre-acento); }
.cur.vis { opacity: 1; }
@media (hover: hover) and (pointer: fine) { .stage.fino, .stage.fino .card { cursor: none; } .stage.fino a, .stage.fino button { cursor: pointer; } }

/* ——— Entrada ——— */
.listo h1 .l > span { transform: none; transition: transform 1.15s var(--e-salida); }
.listo h1 .l:nth-child(2) > span { transition-delay: .09s; }
.listo h1 .hl::before { transform: scaleX(1); transition: transform .9s var(--e-salida) .55s; }
.listo .sub { opacity: 1; transform: none; transition: opacity .9s var(--e-salida) .6s, transform .9s var(--e-salida) .6s; }
.listo .cta { opacity: 1; transform: none; transition: opacity .9s var(--e-salida) .75s, transform .9s var(--e-salida) .75s, color .45s var(--e-salida), border-color .45s var(--e-salida); }
.top, .hud { opacity: 0; transition: opacity 1s var(--e-suave) .9s; }
.listo .top, .listo .hud { opacity: 1; }

/* ——— Móvil ——— */
@media (max-width: 760px) {
  .nav a:not(:last-child) { display: none; }
  .ayuda kbd, .ayuda .teclas { display: none; }
  .hud { height: 76px; }
  .medidor .barra { display: none; }
  .sub { max-width: 30ch; }
  .tema { margin-left: 0; }
}
@media (max-width: 420px) { .medidor { display: none; } }
@media (max-height: 560px) { .sub { display: none; } }
@media (max-aspect-ratio: 9/10) { h1 { font-size: clamp(48px, 15.4vw, 120px); } .centro { padding-bottom: 96px; } }
@media (hover: none) { .cur { display: none; } }

/* ——— Movimiento reducido ——— */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: .01ms !important; transition-delay: 0s !important; }
  h1 .l > span { transform: none; }
  h1 .hl::before { transform: scaleX(1); }
  .sub, .cta, .top, .hud { opacity: 1; transform: none; }
  .btn-pausa, .medidor, .ayuda { display: none; }
}
```

## Movimiento

| Elemento | Propiedad | De → a | Duración | Easing | Retardo | Disparador |
|---|---|---|---|---|---|---|
| Tarjeta (20) | `transform` | punto de ≈2,6 % del ancho cruzando el eje → 46 % (horizontales) o 34 % (verticales) en su carril; rotateY 6°→28° | 30 s por vuelta, infinito | lineal entre 80 muestras (el crecimiento es geométrico: `s = s0^(1−t)`) | −(i/10)·30 s (derecha: −((i+.5)/10)·30 s) | al cargar |
| Tarjeta | `opacity` | 0 → 1 en t 0–.05; 1 hasta t .93; 1 → 0 en t .93–1 | idem | lineal | idem | idem |
| Tarjeta | `z-index` | round(t·100)+1 | idem | lineal | idem | idem |
| Velo de profundidad `.vel` | `opacity` | .7 → 0 hasta t .8 | idem | lineal | idem | idem |
| Pie `.cap` | `opacity` | 0 → 1 entre t .55 y .80 (ease-out cúbico) | idem | lineal | idem | idem |
| Tasa de reproducción | `playbackRate` | arranca con impulso +6 que decae con τ = 0,9 s; la tasa sigue a la meta con τ = 0,32 s (0,22 s al frenar) | continuo | exponencial | 0 | rAF |
| Titular (cada línea) | `translateY` | 112 % → 0 (máscara por línea) | 1,15 s | `--e-salida` | línea 2: +0,09 s | al cargar fuentes (máx. 1,2 s) |
| Pastilla de acento | `scaleX` | 0 → 1 (origen izquierda) | 0,9 s | `--e-salida` | 0,55 s | idem |
| Subtítulo y botón | `opacity`, `translateY` | 0, 14 px → 1, 0 | 0,9 s | `--e-salida` | 0,6 s / 0,75 s | idem |
| Cabecera y pie (HUD) | `opacity` | 0 → 1 | 1 s | `--e-suave` | 0,9 s | idem |
| Conjunto de carriles | `translate3d` | paralaje de −18·x px, −12·y px según el puntero (−1…1) | continuo | lerp τ = 0,18 s | 0 | puntero |
| Tarjeta en foco | `filter` de la imagen | gris → color | 0,45 s | `--e-suave` | 0 | cursor encima |
| Resto de tarjetas | `opacity` de `::after` | 0 → .55 | 0,45 s | `--e-suave` | 0 | cursor encima |
| Cursor propio | `transform` | sigue al puntero (lerp τ = 0,07 s); punto escala .2 → pastilla escala 1 | 0,5 s | `--e-salida` | 0 | cursor sobre tarjeta |
| Botón principal | relleno | círculo de 420 px que crece desde el punto de entrada | 0,6 s | `--e-salida` | 0 | hover/foco |
| Botón principal | `translate3d` | imán: .22·dx, .3·dy | inmediato (rAF) | — | 0 | hover |

## Código clave

Los pilares son tres: (1) el **muestreo de keyframes** en JS con retardos negativos, (2) la **tasa de reproducción única** que acelera, invierte o frena todo, y (3) el **foco** que detiene el corredor. Script completo (copiar tal cual al final del `<body>`):

```html
<script>
(() => {
  'use strict';
  const BANCO = 'https://motionframe-two.vercel.app/assets/banco';
  const REDUCIDO = matchMedia('(prefers-reduced-motion: reduce)').matches;

  // [archivo, ancho del original, proyecto, tipo]
  const H = [
    ['h03-cromo-liquido', 'Metal líquido', 'Campaña'], ['h01-interior-arco', 'Casa Arco', 'Interiorismo'],
    ['h06-seda-granate', 'Seda', 'Packaging'], ['h08-pabellon-agua', 'Pabellón de agua', 'Arquitectura'],
    ['h02-cordilleras-niebla', 'Sierra blanca', 'Identidad'], ['h05-lago-luna', 'Lago luna', 'Fotografía'],
    ['h07-piedras-equilibrio', 'Equilibrio', 'Editorial'], ['h04-muralla-pastel', 'Terrazas del faro', 'Arquitectura'],
    ['h06-seda-granate', 'Seda', 'Packaging'], ['h03-cromo-liquido', 'Metal líquido', 'Campaña']
  ].map(a => ({ f: a[0], n: a[1], t: a[2], w: 1600, h: 1000 }));
  const V = [
    ['v01-cromo-espiral', 'Espiral', 'Escultura'], ['v04-arco-escalera', 'Puerta azul', 'Fotografía'],
    ['v06-orbe-obsidiana', 'Obsidiana', 'Producto'], ['v03-ceramica-apilada', 'Torno', 'Cerámica'],
    ['v08-columnata-hormigon', 'Columnata', 'Arquitectura'], ['v02-vidrio-prisma', 'Prisma', 'Producto'],
    ['v05-duna-nocturna', 'Duna', 'Dirección de arte'], ['v07-frasco-ambar', 'Ámbar', 'Packaging'],
    ['v02-vidrio-prisma', 'Prisma', 'Producto'], ['v08-columnata-hormigon', 'Columnata', 'Arquitectura']
  ].map(a => ({ f: a[0], n: a[1], t: a[2], w: 1080, h: 1440 }));

  const T = 30;            // segundos por vuelta completa de un carril
  const MUESTRAS = 80;     // keyframes por tarjeta
  const stage = document.getElementById('stage');
  const lanes = document.getElementById('lanes');

  const cartas = [];
  [[-1, H, 0], [1, V, .5]].forEach(([lane, lista, desfase]) => {
    lista.forEach((d, i) => {
      const el = document.createElement('figure');
      el.className = 'card';
      el.innerHTML =
        '<picture><source type="image/avif" srcset="' + BANCO + '/' + d.f + '-800.avif 800w, ' + BANCO + '/' + d.f + '.avif ' + d.w + 'w" sizes="46vw">' +
        '<img alt="" width="' + d.w + '" height="' + d.h + '" decoding="async" srcset="' + BANCO + '/' + d.f + '-800.webp 800w, ' + BANCO + '/' + d.f + '.webp ' + d.w + 'w" sizes="46vw" src="' + BANCO + '/' + d.f + '-800.webp"></picture>' +
        '<span class="vel"></span><figcaption class="cap"><b>' + String(cartas.length + 1).padStart(2, '0') + '</b><i>' + d.n + ' · ' + d.t + '</i></figcaption>';
      el.dataset.nombre = d.n + ' · ' + d.t;
      lanes.appendChild(el);
      cartas.push({
        el, vel: el.querySelector('.vel'), cap: el.querySelector('.cap'), lane, ar: d.h / d.w,
        ancho: lane < 0 ? .46 : .34,                       // ancho final como fracción del del escenario
        fase: (i + desfase) / lista.length,                // reparto uniforme en el ciclo (retardo negativo)
        yb: (((i * 37 + (lane > 0 ? 11 : 3)) % 9) - 4) / 4 * .16,   // desvío vertical propio de cada tarjeta
        j: (((i * 53) % 7) - 3) / 3 * .018                 // holgura del pasillo propia de cada tarjeta
      });
    });
  });

  const lista = document.getElementById('lista');
  [...H.slice(0, 8), ...V.slice(0, 8)].forEach(d => {
    const li = document.createElement('li'); li.textContent = d.n + ', ' + d.t; lista.appendChild(li);
  });

  // —— Geometría: keyframes muestreados por JS ——
  const suavizado = x => { x = Math.min(1, Math.max(0, x)); return 1 - Math.pow(1 - x, 3); };
  let W = 0, Hh = 0, K = 1;
  function fotogramas(c) {
    const P = Math.max(900, W * .9);
    const w1 = c.ancho * K * W;                 // ancho final en px
    const s0 = (.026 * W) / w1;                 // escala inicial: ≈2,6 % del ancho
    const hueco = (.17 + c.j) * W * (K > 1 ? .5 : 1);
    const cruce = .035 * W;
    const f = [], g = [], h = [];
    for (let i = 0; i <= MUESTRAS; i++) {
      const t = i / MUESTRAS;
      const s = Math.pow(s0, 1 - t);            // crecimiento geométrico
      const abre = suavizado(t / .34);          // los carriles se abren pronto y se mantienen
      const borde = hueco * abre - cruce * (1 - abre);   // arranca cruzando el eje
      const x = c.lane * (borde + (w1 * s) / 2);
      const y = Hh * (c.yb * abre - c.lane * .05 * Math.pow(t, 1.6));
      const giro = -c.lane * (6 + 22 * t);      // 6° → 28°
      const op = t < .05 ? t / .05 : t > .93 ? (1 - t) / .07 : 1;
      f.push({
        offset: t, easing: 'linear', opacity: +op.toFixed(3), zIndex: Math.round(t * 100) + 1,
        transform: 'perspective(' + P.toFixed(0) + 'px) translate3d(' + x.toFixed(1) + 'px,' + y.toFixed(1) + 'px,0) rotateY(' + giro.toFixed(2) + 'deg) scale(' + s.toFixed(5) + ')'
      });
      g.push({ offset: t, easing: 'linear', opacity: +(.7 * (1 - Math.min(1, t / .8))).toFixed(3) });
      h.push({ offset: t, easing: 'linear', opacity: +suavizado((t - .55) / .25).toFixed(3) });
    }
    return [f, g, h];
  }

  const anims = [];
  function medir() {
    W = stage.clientWidth; Hh = stage.clientHeight;
    K = W / Hh < .9 ? 1.6 : 1;
    cartas.forEach(c => {
      const w = c.ancho * K * W;
      Object.assign(c.el.style, { width: w + 'px', height: (w * (c.lane < 0 ? .625 : c.ar)) + 'px', marginLeft: (-w / 2) + 'px', marginTop: (-(w * (c.lane < 0 ? .625 : c.ar)) / 2) + 'px' });
    });
  }
  function construir() {
    medir();
    const primera = !anims.length;
    cartas.forEach((c, i) => {
      const [f, g, h] = fotogramas(c);
      if (primera) {
        const opc = { duration: T * 1000, iterations: Infinity, delay: -c.fase * T * 1000 };
        const a = c.el.animate(f, opc), b = c.vel.animate(g, opc), d = c.cap.animate(h, opc);
        a.currentTime = b.currentTime = d.currentTime = T * 1000 * 20;   // margen para poder ir marcha atrás
        anims.push(a, b, d);
      } else {
        anims[i * 3].effect.setKeyframes(f);
        anims[i * 3 + 1].effect.setKeyframes(g);
        anims[i * 3 + 2].effect.setKeyframes(h);
      }
    });
    if (REDUCIDO) anims.forEach(a => a.pause());
  }
  construir();
  let rt; addEventListener('resize', () => { clearTimeout(rt); rt = setTimeout(construir, 120); });

  // —— Bucle: velocidad, paralaje y cursor ——
  const lerp = (a, b, k) => a + (b - a) * k;
  const velOut = document.getElementById('vel'), barra = document.getElementById('barra');
  const cur = document.getElementById('cur'), curtxt = document.getElementById('curtxt');
  const fino = matchMedia('(hover: hover) and (pointer: fine)').matches;
  if (fino) stage.classList.add('fino');
  else document.querySelector('.ayuda span:last-child').textContent = 'Desliza para acelerar · toca una imagen';
  let tasa = 0, boost = REDUCIDO ? 0 : 6, retener = false, pausado = false, visible = true, ultimo = 0, ultimoTxt = '';
  const p = { x: 0, y: 0, tx: 0, ty: 0, cx: -100, cy: -100, tcx: -100, tcy: -100 };

  function tick(now) {
    if (!visible) return;
    requestAnimationFrame(tick);
    const dt = Math.min(.05, ((now - ultimo) / 1000) || .016); ultimo = now;
    boost *= Math.exp(-dt / .9);
    const meta = (pausado || retener) ? 0 : 1 + boost;
    tasa = lerp(tasa, meta, 1 - Math.exp(-dt / (meta === 0 ? .22 : .32)));
    if (Math.abs(tasa) < .002 && meta === 0) tasa = 0;
    if (!REDUCIDO) {
      for (let i = 0; i < anims.length; i++) anims[i].playbackRate = tasa;
      if (anims[0].currentTime < T * 4000) anims.forEach(a => { a.currentTime += T * 10000; });   // vueltas completas: no se nota
    }
    // lectura de velocidad
    const txt = '×' + Math.abs(tasa).toFixed(1).replace('.', ',');
    if (txt !== ultimoTxt) { ultimoTxt = txt; velOut.textContent = (tasa < -.05 ? '−' : '') + txt; }
    barra.style.transform = 'scaleX(' + Math.min(1, Math.abs(tasa) / 8).toFixed(3) + ')';
    // paralaje del conjunto + cursor
    const k = 1 - Math.exp(-dt / .18);
    p.x = lerp(p.x, p.tx, k); p.y = lerp(p.y, p.ty, k);
    lanes.style.transform = 'translate3d(' + (-p.x * 18).toFixed(2) + 'px,' + (-p.y * 12).toFixed(2) + 'px,0)';
    p.cx = lerp(p.cx, p.tcx, 1 - Math.exp(-dt / .07)); p.cy = lerp(p.cy, p.tcy, 1 - Math.exp(-dt / .07));
    cur.style.transform = 'translate3d(' + p.cx.toFixed(1) + 'px,' + p.cy.toFixed(1) + 'px,0)';
  }
  requestAnimationFrame(t => { ultimo = t; tick(t); });

  new IntersectionObserver(es => {
    const v = es[0].isIntersecting && !document.hidden;
    if (v !== visible) { visible = v; if (v) { ultimo = performance.now(); requestAnimationFrame(tick); } }
  }).observe(stage);
  document.addEventListener('visibilitychange', () => {
    const v = !document.hidden;
    if (v !== visible) { visible = v; if (v) { ultimo = performance.now(); requestAnimationFrame(tick); } }
  });

  // —— Interacción ——
  let tarjeta = null, fijada = null, arrastre = null;
  function foco(el) {
    if (tarjeta === el) return;
    if (tarjeta) tarjeta.classList.remove('is-on');
    tarjeta = el;
    if (el) { el.classList.add('is-on'); curtxt.textContent = el.dataset.nombre; }
    stage.classList.toggle('is-hold', !!el);
    cur.classList.toggle('on', !!el);
    if (!el) curtxt.textContent = 'Pausa';
    retener = !!el && !REDUCIDO;
  }
  const rect = () => stage.getBoundingClientRect();
  stage.addEventListener('pointermove', e => {
    const r = rect();
    p.tx = (e.clientX - r.left) / r.width * 2 - 1; p.ty = (e.clientY - r.top) / r.height * 2 - 1;
    p.tcx = e.clientX - r.left; p.tcy = e.clientY - r.top;
    if (e.pointerType === 'mouse') cur.classList.add('vis');
    if (arrastre) {
      const dx = e.clientX - arrastre.x; arrastre.x = e.clientX;
      if (Math.abs(e.clientX - arrastre.x0) > 6) arrastre.mov = true;
      boost = Math.max(-9, Math.min(9, boost + dx * .035));
    }
  });
  stage.addEventListener('pointerleave', () => { cur.classList.remove('vis'); if (!fijada) foco(null); p.tx = p.ty = 0; });
  stage.addEventListener('pointerover', e => {
    if (e.pointerType !== 'mouse' || arrastre) return;
    foco(e.target.closest('.card'));
  });
  stage.addEventListener('pointerdown', e => {
    if (e.target.closest('a,button')) return;
    arrastre = { x: e.clientX, x0: e.clientX, mov: false, card: e.target.closest('.card') };
    if (e.pointerType === 'mouse') foco(null);
  });
  const fin = e => {
    if (!arrastre) return;
    const a = arrastre; arrastre = null;
    if (e.pointerType !== 'mouse' && !a.mov) {              // toque: fija o suelta una tarjeta
      const c = a.card && a.card !== fijada ? a.card : null;
      fijada = c; foco(c);
    } else if (e.pointerType === 'mouse') {
      foco(document.elementFromPoint(e.clientX, e.clientY)?.closest('.card') || null);
    }
  };
  addEventListener('pointerup', fin); addEventListener('pointercancel', fin);
  stage.addEventListener('wheel', e => {
    boost = Math.max(-9, Math.min(9, boost + (e.deltaY + e.deltaX) * .006));
  }, { passive: true });
  addEventListener('keydown', e => {
    if (e.key === 'ArrowRight') boost = Math.min(9, boost + 2);
    else if (e.key === 'ArrowLeft') boost = Math.max(-9, boost - 2);
  });

  // —— Controles ——
  const bPausa = document.getElementById('pausa');
  bPausa.addEventListener('click', () => {
    pausado = !pausado;
    bPausa.setAttribute('aria-pressed', pausado);
    bPausa.setAttribute('aria-label', pausado ? 'Reanudar el corredor' : 'Pausar el corredor');
  });
  document.querySelectorAll('.sw').forEach(b => b.addEventListener('click', () => {
    document.documentElement.dataset.acento = b.dataset.ac;
    document.querySelectorAll('.sw').forEach(x => x.setAttribute('aria-pressed', x === b));
  }));
  const bTema = document.getElementById('tema');
  bTema.addEventListener('click', () => {
    const claro = document.documentElement.dataset.tema !== 'claro';
    document.documentElement.dataset.tema = claro ? 'claro' : 'oscuro';
    bTema.setAttribute('aria-pressed', claro);
    bTema.setAttribute('aria-label', claro ? 'Cambiar a fondo oscuro' : 'Cambiar a fondo claro');
  });

  // Botón imantado con relleno desde el punto de entrada
  const cta = document.getElementById('cta');
  let cx = 0, cy = 0, rafCta = 0;
  cta.addEventListener('pointerenter', e => { const r = cta.getBoundingClientRect(); cta.style.setProperty('--mx', (e.clientX - r.left) + 'px'); cta.style.setProperty('--my', (e.clientY - r.top) + 'px'); });
  cta.addEventListener('pointerleave', e => { const r = cta.getBoundingClientRect(); cta.style.setProperty('--mx', (e.clientX - r.left) + 'px'); cta.style.setProperty('--my', (e.clientY - r.top) + 'px'); cta.style.transform = ''; });
  cta.addEventListener('pointermove', e => {
    if (e.pointerType !== 'mouse' || REDUCIDO) return;
    const r = cta.getBoundingClientRect();
    cx = (e.clientX - r.left - r.width / 2) * .22; cy = (e.clientY - r.top - r.height / 2) * .3;
    cancelAnimationFrame(rafCta); rafCta = requestAnimationFrame(() => { cta.style.transform = 'translate3d(' + cx.toFixed(1) + 'px,' + cy.toFixed(1) + 'px,0)'; });
  });
  document.querySelectorAll('a[href="#contenido"]').forEach(a => a.addEventListener('click', e => e.preventDefault()));

  // Entrada
  const arrancar = () => requestAnimationFrame(() => requestAnimationFrame(() => document.body.classList.add('listo')));
  (document.fonts && document.fonts.ready ? Promise.race([document.fonts.ready, new Promise(r => setTimeout(r, 1200))]) : Promise.resolve()).then(arrancar);
})();
</script>
```

Detalles del muestreo que no se pueden perder:

- La perspectiva va **dentro de cada transform** (`perspective(P) translate3d(x,y,0) rotateY(a) scale(s)`, con `P = max(900, ancho·.9)`): el punto de fuga de cada tarjeta es el centro del escenario porque todas están centradas antes de trasladarse.
- El escenario se mide con `clientWidth` y `clientHeight`; en pantallas verticales (ancho/alto < .9) las tarjetas se multiplican por 1,6 y el hueco central se reduce a la mitad.
- Los keyframes se recalculan al redimensionar con `effect.setKeyframes`, sin recrear las animaciones: el corredor no salta.
- `currentTime` arranca en 20 ciclos para poder ir marcha atrás sin llegar a 0; cuando baja de 4 ciclos se suman 10 ciclos exactos (imperceptible).
- Las tarjetas verticales usan su propia proporción (alto/ancho de la foto) y las horizontales, 0,625.

## Interacción

- **Puntero (ratón):** el corredor se frena hasta parar al entrar en una tarjeta (la tarjeta pasa a color y recibe el filete de acento) y arranca al salir. El cursor propio muestra «Nombre · Tipo». El conjunto se desplaza ligeramente con el puntero.
- **Rueda:** acelera hacia delante (deltaY positivo) o invierte (negativo); el impulso decae solo. No se hace `preventDefault`: la página puede seguir haciendo scroll.
- **Arrastre:** horizontal, suma impulso según el desplazamiento.
- **Teclado:** las flechas izquierda y derecha restan o suman 2 al impulso. El botón de pausa es un `button` con `aria-pressed`.
- **Táctil:** el arrastre horizontal acelera; un toque sobre una tarjeta la fija (color y parada) y otro toque en otro sitio la suelta. `touch-action: pan-y` deja libre el scroll vertical.
- Botón principal: imán sutil y relleno de acento que nace donde entra el puntero.

## Adaptación a móvil y tablet

- Hasta 760 px: solo queda visible el último enlace del menú («Contacto»), se ocultan las teclas del texto de ayuda y la barra de velocidad.
- Hasta 420 px: se oculta también el medidor de velocidad.
- Pantallas verticales (ancho/alto < .9): titular `clamp(48px, 15.4vw, 120px)`, tarjetas ×1,6, relleno inferior del bloque central de 96 px.
- Altura inferior a 560 px: se oculta el subtítulo.
- En dispositivos sin puntero fino el cursor propio no se muestra y el texto de ayuda pasa a «Desliza para acelerar · toca una imagen».

## Accesibilidad y movimiento reducido

- El corredor es decorativo: `aria-hidden="true"`. Los proyectos se listan en una lista visualmente oculta (`.sr`) con su nombre y tipo.
- Enlace «Saltar al contenido», foco visible de 2 px en el color de acento, botones con `aria-label` y `aria-pressed` que cambian con el estado.
- Contraste AA: texto sobre el fondo del tema; texto de la pastilla (`--sobre-acento`) negro sobre lima y naranja, blanco sobre azul.
- `prefers-reduced-motion: reduce`: las animaciones de las tarjetas se **pausan** en su estado inicial (composición estática con las 20 tarjetas repartidas), no hay impulso de arranque ni parada por hover, las transiciones duran 0,01 ms, el titular aparece ya completo y se ocultan pausa, medidor y ayuda. El color al pasar el cursor se mantiene.

## Rendimiento

- Todo el movimiento va por `transform` y `opacity` en capas propias (`will-change`), animadas con WAAPI: sin layout en cada fotograma. El único trabajo por fotograma en JS es fijar la tasa de reproducción de 60 animaciones y las transformaciones del paralaje y del cursor.
- Imágenes con `srcset` (800 px y original) y AVIF con WebP de respaldo; `decoding="async"`. Las repeticiones de cada carril reutilizan archivos ya descargados.
- El bucle se detiene cuando la pieza sale de pantalla (`IntersectionObserver`) o la pestaña está oculta, y se reanuda al volver.
- No usa WebGL: no hay DPR que limitar ni fallback.

## Personalización

- **Marca y textos:** `Umbral`, `Aldenor`, `Lo mejor`, `viene` (la palabra sobre la pastilla), `de frente.` y `Ver los proyectos`.
- **Colores:** `#0a0a0a`, `#f3f3ef` y `#d4ff00`. Para otro color vivo cambia también `--sobre-acento` (negro sobre colores claros, blanco sobre oscuros). Variantes que funcionan: rojo `#ff2d20`, verde ácido `#7cff00`, amarillo `#ffe600`.
- **Fotos:** sustituye los nombres de las listas `H` (horizontales 1600×1000) y `V` (verticales 1080×1440) y sus títulos.
- **Ritmo:** `T` (30 s por vuelta; más alto, más lento). **Densidad:** número de elementos de `H` y `V`.
- **Tamaños:** el `ancho` final de cada carril (.46 y .34) y el hueco central `.17` de `fotogramas()`.
- **Fotos en color siempre:** quita el `filter` del `img`.

## Comprobación final

1. Sin errores en consola; todas las imágenes cargan desde `https://motionframe-two.vercel.app/assets/banco`.
2. Al cargar ya hay tarjetas de todos los tamaños en los dos carriles (retardos negativos) y el corredor arranca rápido y se asienta en ×1,0 en unos 2,5 s.
3. Las tarjetas nacen pequeñas cerca del centro, se abren a su carril y crecen sin saltos; las del carril izquierdo tienen el borde derecho más lejano y las del derecho, el izquierdo.
4. El titular queda siempre legible, también cuando una tarjeta pasa por detrás.
5. Al pasar el cursor por una tarjeta el corredor se detiene en medio segundo, la tarjeta pasa a color y las demás se oscurecen; al salir, reanuda.
6. La rueda acelera y, hacia arriba, invierte el sentido; el medidor «Vel.» refleja el valor y vuelve a ×1,0.
7. Los tres colores de acento y el modo claro funcionan; el texto de la pastilla se lee en los tres.
8. Con `prefers-reduced-motion` todo queda quieto, legible y sin saltos.
9. Sin scroll horizontal entre 360 y 1920 px; a 390×844 el titular ocupa casi todo el ancho.
10. El enlace «Saltar al contenido», el botón de pausa y los colores se manejan con teclado.
11. Con la pieza fuera de pantalla o la pestaña oculta, el bucle de JS se detiene.
12. El HTML pesa menos de 40 KB.

Prueba en frío

La prueba en frío

Aún sin publicar para esta pieza

En la prueba en frío pegamos el prompt en Claude Sonnet 5.5, sin ayuda ni contexto, y comparamos lo que genera con la demo. Cuando esté hecha, aquí verás la captura, el veredicto y la fecha.

Paso a paso

Cómo usarlo en 4 pasos

  1. 01

    Personaliza

    Rellena el formulario de arriba con el nombre, los colores y los textos de tu cliente. Se meten en el prompt al copiar.
  2. 02

    Copia el Completo o el Lite

    El Completo, para Claude, ChatGPT, Gemini o Cursor. El Lite, más corto, para Lovable, Bolt o v0, que aceptan menos texto.
  3. 03

    Pega e itera

    Pégalo en una conversación nueva. Si quieres cambiar algo, pídelo con una frase corta:
    • «Pon el acento en rojo y deja el fondo en blanco»
    • «Haz el corredor el doble de lento»
    • «Deja solo el carril de fotos verticales»
    • «Quita el filtro de gris para que las fotos se vean siempre en color»
  4. 04

    Publica

    Guarda el resultado como HTML (o en tu proyecto) y súbelo a Vercel, Netlify o tu hosting de siempre. Tienes los pasos en la guía.

Guías por herramientaClaudeChatGPTGeminiCursorLovableBoltv0

Licencia en claro

Qué puedes hacer con esta pieza

Todas las piezas incluyen licencia de uso comercial. Usa el resultado tal cual en tus productos, encargos freelance o webs de clientes, sin atribución.

Puedes

  • Usarlo en tus webs y en las de tus clientes, también si cobras por ellas.
  • Cambiarlo todo lo que quieras.
  • Publicar el resultado sin citarnos.

No puedes

  • Revender o regalar el prompt, la demo o el código como plantilla o biblioteca.
  • Compartir tu cuenta Pro.

Es la misma licencia para lo gratis y lo Pro. La tienes entera en licencia de uso.

Preguntas frecuentes

Lo que suele preguntarse

¿Puedo usar mis propias fotos?
Sí. Sustituye los nombres de las listas de fotos horizontales (16:10) y verticales (3:4) y sus títulos. Cada carril usa 10 elementos y puedes repetir fotos si tienes menos; las repetidas se descargan una sola vez.
¿Funciona en el móvil?
Sí. En pantallas verticales las tarjetas se agrandan y el titular ocupa casi todo el ancho; el arrastre horizontal acelera, un toque fija una tarjeta y el scroll vertical sigue libre.
¿Y si el usuario tiene el movimiento reducido?
El corredor se queda parado en una composición estática con las tarjetas repartidas, el titular aparece completo y se ocultan los controles de velocidad. Al pasar el cursor, la foto sigue recuperando su color.
¿Puedo ponerlo debajo de otras secciones de mi web?
Sí. Es una sección de 100svh que se pausa cuando sale de pantalla y no bloquea el scroll de la página: la rueda solo acelera el corredor mientras el cursor está encima y no se cancela el desplazamiento.
¿Qué diferencia hay entre el prompt Completo y el Lite?
El Completo (≈11698 tokens) describe la pieza con todo detalle y es el que mejor resultado da en Claude, ChatGPT, Gemini o Cursor. El Lite (≈2839 tokens) resume lo esencial para herramientas que aceptan menos texto, como Lovable, Bolt o v0.

Versión 1.0 · publicada el 01-10-2026

Cópialo desde aquí

Tu navegador no nos deja escribir en el portapapeles. El texto ya está seleccionado: pulsa Ctrl + C (o ⌘ + C) o el botón.