Skill de Vídeo Héroe para LP
Guía para integrar vídeos de demostración de producto en landing pages y sitios web de forma efectiva.
Patrones compatibles
- Hero Background Video — Vídeo de fondo a pantalla completa en la parte superior de la página
- Product Demo Section — Vídeo en línea dentro de una sección de presentación del producto
- Floating Device Mockup — Vídeo mostrado dentro de un marco de dispositivo
- Lightbox / Modal Video — Reproducción ampliada al hacer clic
Paso 0: Lista de verificación previa
Antes de incorporar el vídeo, verifica lo siguiente:
Verificación del material de vídeo
- ¿El archivo de vídeo está listo? (Si no, crear primero una interfaz de usuario con marcador de posición)
- Formato: ideal tener dos formatos
.mp4(H.264) +.webm(VP9) - Resolución: para Hero se recomienda 1920x1080, para tarjetas 1280x720 es suficiente
- Tamaño de archivo: para Hero entre 5-15 MB, para tarjetas entre 2-5 MB
- Duración: para reproducción automática, lo óptimo es un bucle de 10-30 segundos
Cómo crear material de vídeo (grabando con Screen Studio, etc.)
- Graba el flujo de operación más impactante del producto en 15-30 segundos
- Graba con una resolución de 1920x1080 o superior
- Al exportar, configura:
- MP4 (H.264): calidad Media-Alta, tasa de bits 4-8 Mbps
- WebM (VP9): exportación adicional con calidad equivalente (si la herramienta lo permite)
- Comandos de conversión con ffmpeg:
# Optimización MP4 (para web, con faststart)
ffmpeg -i input.mov -c:v libx264 -crf 23 -preset slow \
-movflags +faststart -an -vf scale=1920:-2 output.mp4
# Conversión WebM
ffmpeg -i input.mov -c:v libvpx-vp9 -crf 30 -b:v 0 \
-an -vf scale=1920:-2 output.webm
# Para tarjetas (más pequeño)
ffmpeg -i input.mov -c:v libx264 -crf 26 -preset slow \
-movflags +faststart -an -vf scale=1280:-2 output-card.mp4
Alojamiento
- Local (
/public/videos/): para sitios pequeños, despliegues en Vercel / Netlify - CDN / Almacenamiento de objetos: para sitios grandes o múltiples vídeos (Cloudflare R2, AWS S3 + CloudFront)
- Incrustación de YouTube / Vimeo: para SEO, o si se desea compartir externamente
Patrón 1: Hero Background Video
Muestra un vídeo de demostración del producto a pantalla completa en la parte superior de la página. Con superposición de texto.
Componente React
interface HeroVideoProps {
/** Ruta del vídeo MP4 */
videoSrc: string;
/** Ruta del vídeo WebM (opcional) */
videoSrcWebm?: string;
/** Imagen de póster para mostrar antes de cargar el vídeo */
posterSrc?: string;
/** Elementos hijos de la superposición (títulos, etc.) */
children?: React.ReactNode;
/** Oscuridad de la superposición (0-1, por defecto 0.4) */
overlayOpacity?: number;
}
function HeroVideo({
videoSrc,
videoSrcWebm,
posterSrc,
children,
overlayOpacity = 0.4,
}: HeroVideoProps) {
const videoRef = useRef<HTMLVideoElement>(null);
return (
<div className="relative h-screen w-full overflow-hidden bg-black">
{/* Vídeo de fondo */}
<video
ref={videoRef}
autoPlay
muted
loop
playsInline
poster={posterSrc}
className="absolute inset-0 w-full h-full object-cover"
>
{videoSrcWebm && <source src={videoSrcWebm} type="video/webm" />}
<source src={videoSrc} type="video/mp4" />
</video>
{/* Superposición oscura */}
<div
className="absolute inset-0"
style={{ backgroundColor: `rgba(0, 0, 0, ${overlayOpacity})` }}
/>
{/* Degradado hacia el fondo de la página */}
<div className="absolute inset-x-0 bottom-0 h-32 bg-gradient-to-t from-background to-transparent" />
{/* Contenido */}
{children && (
<div className="absolute inset-0 z-10 flex items-end">
<div className="container pb-16 md:pb-24">{children}</div>
</div>
)}
</div>
);
}
Ejemplo de uso
<HeroVideo
videoSrc="/videos/product-demo.mp4"
videoSrcWebm="/videos/product-demo.webm"
posterSrc="/images/hero-poster.jpg"
overlayOpacity={0.35}
>
<h1 className="text-4xl md:text-6xl font-normal text-white tracking-tight">
Redescubriendo la conversación con tecnología.
</h1>
</HeroVideo>
Puntos de aplicación en Plural Reality LP
Sustituyendo o coexistiendo con el componente LifeGameHero actual:
// Home.tsx patrón de sustitución
// Opción A: Sustitución completa
<HeroVideo videoSrc="/videos/hero-demo.mp4" posterSrc="/images/IMG_5298.jpeg">
<h1 ...>{h.heroTitle}</h1>
</HeroVideo>
// Opción B: Mantener LifeGameHero y añadir sección de vídeo debajo
<LifeGameHero>...</LifeGameHero>
<Section className="py-0">
<HeroVideo videoSrc="/videos/product-overview.mp4" overlayOpacity={0.2}>
<h2>Introducción al producto</h2>
</HeroVideo>
</Section>
Patrón 2: Product Card Video
En cada tarjeta de la lista de productos, reproduce un vídeo de demostración al pasar el ratón, en lugar de una imagen estática.
Componente React
interface ProductCardVideoProps {
videoSrc: string;
posterSrc: string;
alt: string;
className?: string;
}
function ProductCardVideo({
videoSrc,
posterSrc,
alt,
className,
}: ProductCardVideoProps) {
const videoRef = useRef<HTMLVideoElement>(null);
const handleMouseEnter = () => {
videoRef.current?.play();
};
const handleMouseLeave = () => {
const video = videoRef.current;
if (video) {
video.pause();
video.currentTime = 0;
}
};
return (
<div
className={cn("relative aspect-video bg-muted overflow-hidden", className)}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<video
ref={videoRef}
muted
loop
playsInline
poster={posterSrc}
preload="metadata"
className="w-full h-full object-cover"
>
<source src={videoSrc} type="video/mp4" />
</video>
</div>
);
}
Ejemplo de uso (sustituir imagen en tarjeta existente)
// Antes:
<ParallaxImage src="/images/kouchoai.png" alt={t.products.kouchouAI} />
// Después:
<ProductCardVideo
videoSrc="/videos/kouchou-ai-demo.mp4"
posterSrc="/images/kouchoai.png"
alt={t.products.kouchouAI}
/>
Patrón 3: Device Mockup Video
Muestra el vídeo de demostración dentro del marco de un navegador o smartphone (aumenta la confianza).
function BrowserMockupVideo({
videoSrc,
posterSrc,
title,
}: {
videoSrc: string;
posterSrc?: string;
title?: string;
}) {
return (
<div className="border border-border bg-background overflow-hidden">
{/* Chrome del navegador */}
<div className="flex items-center gap-2 px-4 py-3 bg-secondary/30 border-b border-border">
<div className="flex gap-1.5">
<div className="w-3 h-3 rounded-full bg-muted-foreground/20" />
<div className="w-3 h-3 rounded-full bg-muted-foreground/20" />
<div className="w-3 h-3 rounded-full bg-muted-foreground/20" />
</div>
{title && (
<div className="flex-1 text-center">
<span className="text-xs text-muted-foreground font-mono">{title}</span>
</div>
)}
</div>
{/* Contenido del vídeo */}
<div className="aspect-video">
<video
autoPlay
muted
loop
playsInline
poster={posterSrc}
className="w-full h-full object-cover"
>
<source src={videoSrc} type="video/mp4" />
</video>
</div>
</div>
);
}
Patrón 4: Lightbox Modal Video
Muestra el vídeo en un modal al hacer clic en la miniatura. También compatible con incrustación de YouTube.
function VideoLightbox({
thumbnailSrc,
videoSrc,
youtubeId,
alt,
}: {
thumbnailSrc: string;
videoSrc?: string;
youtubeId?: string;
alt: string;
}) {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button
onClick={() => setIsOpen(true)}
className="relative group cursor-pointer aspect-video w-full overflow-hidden"
>
<img src={thumbnailSrc} alt={alt} className="w-full h-full object-cover" />
{/* Superposición del botón de reproducción */}
<div className="absolute inset-0 flex items-center justify-center bg-black/20 group-hover:bg-black/30 transition-colors">
<div className="w-16 h-16 flex items-center justify-center bg-white/90 group-hover:bg-white transition-colors">
<svg className="w-6 h-6 text-foreground ml-1" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z" />
</svg>
</div>
</div>
</button>
{/* Modal */}
{isOpen && (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/80"
onClick={() => setIsOpen(false)}
>
<div className="w-full max-w-5xl aspect-video" onClick={(e) => e.stopPropagation()}>
{youtubeId ? (
<iframe
src={`https://www.youtube.com/embed/${youtubeId}?autoplay=1`}
className="w-full h-full"
allow="autoplay; encrypted-media"
allowFullScreen
/>
) : (
<video
src={videoSrc}
autoPlay
controls
className="w-full h-full object-contain bg-black"
/>
)}
</div>
</div>
)}
</>
);
}
Lista de verificación de implementación
Al añadir vídeos, verifica lo siguiente:
Rendimiento
- Establecer los 4 atributos
autoPlay muted loop playsInline(obligatorio para compatibilidad con iOS) - Especificar imagen de
posterpara el fotograma inicial (mejora la experiencia durante la carga) - Usar
preload="metadata"para aligerar la carga inicial de vídeos en tarjetas - Aplicar
-movflags +faststartal archivo de vídeo (reproducción progresiva) - Proporcionar los dos formatos MP4 + WebM con
<source>(compatibilidad con navegadores)
Diseño responsive
- Usar
object-cover/object-containpara un ajuste adecuado - Considerar la opción de mostrar una imagen estática en lugar del vídeo en dispositivos móviles
const isMobile = window.innerWidth < 768; // En móvil, mostrar solo la imagen de poster, no cargar el vídeo
Accesibilidad
- Los vídeos de reproducción automática deben ir siempre con
muted(la reproducción de audio sin interacción del usuario está bloqueada) - Respetar
prefers-reduced-motiony mostrar una imagen estática como alternativa cuando las animaciones estén desactivadas@media (prefers-reduced-motion: reduce) { video { display: none; } .video-poster-fallback { display: block; } }
Diseño (conforme a Plural Reality)
- Sin bordes redondeados (
border-radius: 0px) - Borde:
1px solid var(--border) - Superposición con degradado negro (mantener monocromático)
- El icono del botón de reproducción debe ser
Playde Lucide React
Convención de ubicación de archivos
client/
public/
videos/
hero-demo.mp4 # Vídeo de héroe (MP4)
hero-demo.webm # Vídeo de héroe (WebM)
hero-poster.jpg # Imagen de póster del vídeo de héroe
kouchou-ai-demo.mp4 # Vídeo de demostración de Kouchou AI
baisoku-survey-demo.mp4 # Vídeo de demostración de Baisoku Survey
baisoku-kaigi-demo.mp4 # Vídeo de demostración de Baisoku Kaigi
src/
components/
HeroVideo.tsx # Componente de vídeo a pantalla completa de héroe
ProductCardVideo.tsx # Componente de vídeo con reproducción al pasar el ratón en tarjeta
BrowserMockupVideo.tsx # Vídeo con marco de navegador
VideoLightbox.tsx # Componente de vídeo modal
Ejemplos de configuración comunes
LP de SaaS / Producto (configuración recomendada)
Vídeo a pantalla completa de héroe (bucle de 15-30 segundos, reproducción automática silenciada)
↓
Sección de texto (misión, valores)
↓
Vídeo en mockup de navegador (demostración del producto principal)
↓
Tarjetas de producto (reproducción de vídeo individual al pasar el ratón)
↓
Casos de estudio / Logros
↓
CTA
Sitio corporativo (configuración comedida)
Imagen de héroe + botón de reproducción (clic para abrir vídeo en Lightbox)
↓
Sección de texto
↓
Tarjetas de producto (imagen estática, vídeo en página de detalle)
Patrón 5: Mockup de UI de producto con animaciones CSS (recomendado)
Sin necesidad de archivos de vídeo, dibuja directamente la sensación de interacción del producto con animaciones CSS / SVG / Canvas. Es el enfoque adoptado por Stripe, Linear y Vercel.
Ventajas
- No requiere archivos de vídeo (cero bytes adicionales)
- Completamente responsive (no se rompe en ningún tamaño de pantalla)
- Renderizado nativo y nítido (sin artefactos de compresión)
- Posible interacción (cambios al pasar el ratón, al hacer clic)
- Compatibilidad automática con modo oscuro / modo claro
Requisito previo: Activos de animación existentes
Plural Reality LP ya tiene implementadas las siguientes animaciones de alta calidad en SolutionAnimations.tsx:
| Componente | Contenido | Tecnología |
|---|---|---|
NetworkOntologyAnimation | Ontología de 3 capas (DATA→ONTOLOGY→APPLICATION) | SVG + framer-motion |
DataFlowAnimation | Pipeline de recoger→ordenar→conectar | SVG + framer-motion |
ClusterAnimation | Visualización de agrupación de opiniones | SVG + framer-motion |
ConversationBranchAnimation | Árbol de ramificación de entrevistas en profundidad | SVG + framer-motion |
FeedbackLoopAnimation | Ciclo de diálogo continuo | SVG + framer-motion |
IntegrationMeshAnimation | Diagrama de integración de plataformas | SVG + framer-motion |
ParticleFieldAnimation | Red de partículas | Canvas |
IsometricLayersAnimation | Estructura de capas en proyección isométrica | SVG + framer-motion |
Además, LifeGameHero.tsx contiene una animación en Canvas de la Tierra → Japón.
Principios de diseño comunes
- Fondo: negro (#000000), dibujar con elementos blancos
- Monocromático (no usar color, o solo Brand Teal como acento)
- Fuente: var(--font-mono) para etiquetas técnicas, var(--font-sans) para elementos de UI
- Sin bordes redondeados: rx={0} / border-radius: 0px
- Animación: usar useInView + motion.* de framer-motion para activación por scroll
- Fondo de cuadrícula: patrón de cuadrícula con líneas blancas tenues (utilidad GridBg)
- Resplandor: feDropShadow para un brillo sutil (utilidad GlowDef)
Patrón 5A: Marco de navegador + UI animada
Muestra una interfaz de usuario simulada con animación dentro del cromo de un navegador (barra de título + puntos).
function ProductDemoMock({
title,
children,
className,
}: {
title: string;
children: React.ReactNode;
className?: string;
}) {
return (
<div className={cn("border border-border bg-black overflow-hidden", className)}>
{/* Chrome del navegador */}
<div className="flex items-center gap-2 px-4 py-3 bg-[#1c1c1e] border-b border-border">
<div className="flex gap-1.5">
<div className="w-2.5 h-2.5 rounded-full bg-white/10" />
<div className="w-2.5 h-2.5 rounded-full bg-white/10" />
<div className="w-2.5 h-2.5 rounded-full bg-white/10" />
</div>
<div className="flex-1 text-center">
<span className="text-[10px] text-white/40 font-mono">{title}</span>
</div>
</div>
{/* Contenido animado */}
<div className="aspect-video relative overflow-hidden">
{children}
</div>
</div>
);
}
Patrón 5B: Animación de demostración de Kouchou AI
Flujo donde los datos de texto entran → la IA los agrupa en clusters → se muestran los resultados.
// Diseño conceptual (para la implementación, consultar ClusterAnimation en SolutionAnimations)
function KouchouAIDemoAnimation() {
const [phase, setPhase] = useState<"input" | "processing" | "result">("input");
useEffect(() => {
const timers = [
setTimeout(() => setPhase("processing"), 2000),
setTimeout(() => setPhase("result"), 4000),
];
const loop = setInterval(() => {
setPhase("input");
timers.push(setTimeout(() => setPhase("processing"), 2000));
timers.push(setTimeout(() => setPhase("result"), 4000));
}, 8000);
return () => { timers.forEach(clearTimeout); clearInterval(loop); };
}, []);
return (
<ProductDemoMock title="kouchou-ai.plural-reality.com">
<svg viewBox="0 0 800 450" className="w-full h-full">
{/* Fase 1: Líneas de texto se deslizan desde la izquierda */}
{phase === "input" && (
<g>
{["El transporte es incómodo", "Necesitamos más apoyo para la crianza", "Me preocupa la prevención de desastres"].map((text, i) => (
<motion.g
key={text}
initial={{ opacity: 0, x: -40 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: i * 0.4 }}
>
<rect x={40} y={60 + i * 50} width={350} height={36} fill="white" fillOpacity={0.05} stroke="white" strokeWidth={0.5} />
<text x={56} y={82 + i * 50} fill="white" fontSize={11} fontFamily="var(--font-sans)" opacity={0.8}>
{text}
</text>
</motion.g>
))}
</g>
)}
{/* Fase 2: Partículas se reúnen en el centro */}
{phase === "processing" && (
<motion.text x={400} y={225} textAnchor="middle" fill="white" fontSize={12} fontFamily="var(--font-mono)"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
Procesando...
</motion.text>
)}
{/* Fase 3: Clústeres de resultados */}
{phase === "result" && (
<g>
<motion.circle cx={200} cy={225} r={60} fill="white" fillOpacity={0.1} stroke="white" strokeWidth={0.5}
initial={{ r: 0 }} animate={{ r: 60 }} transition={{ type: "spring", damping: 10 }}
/>
<text x={200} y={225} textAnchor="middle" fill="white" fontSize={11} fontFamily="var(--font-sans)">
Infraestructura
</text>
<motion.circle cx={400} cy={225} r={60} fill="white" fillOpacity={0.1} stroke="white" strokeWidth={0.5}
initial={{ r: 0 }} animate={{ r: 60 }} transition={{ type: "spring", damping: 10, delay: 0.3 }}
/>
<text x={400} y={225} textAnchor="middle" fill="white" fontSize={11} fontFamily="var(--font-sans)">
Bienestar
</text>
<motion.circle cx={600} cy={225} r={60} fill="white" fillOpacity={0.1} stroke="white" strokeWidth={0.5}
initial={{ r: 0 }} animate={{ r: 60 }} transition={{ type: "spring", damping: 10, delay: 0.6 }}
/>
<text x={600} y={225} textAnchor="middle" fill="white" fontSize={11} fontFamily="var(--font-sans)">
Seguridad
</text>
</g>
)}
</svg>
</ProductDemoMock>
);
}