Communitygithub.com

yuidvg/lp-hero-video

ランディングページにプロダクトデモを組み込むスキル。 CSSアニメーションによるプロダクトUIモック、ヒーロー動画、カード内動画など、 Webサイトへのデモ演出パターンを実装する。 トリガー例: "動画を入れて", "ヒーロー動画", "デモ動画を埋め込み", "hero video", "product video", "LP動画", "ランディングページに動画", "プロダクトデモ", "アニメーションで見せたい", "UIモック"

lp-hero-video 是什么?

lp-hero-video is a Cursor agent skill that ランディングページにプロダクトデモを組み込むスキル。 CSSアニメーションによるプロダクトUIモック、ヒーロー動画、カード内動画など、 Webサイトへのデモ演出パターンを実装する。 トリガー例: "動画を入れて", "ヒーロー動画", "デモ動画を埋め込み", "hero video", "product video", "LP動画", "ランディングページに動画", "プロダクトデモ", "アニメーションで見せたい", "UIモック".

兼容平台~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/yuidvg/skills/tree/HEAD/skills/lp-hero-video

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

LP Hero Video Skill

A guide for effectively integrating product demo videos into landing pages and websites.

Supported Patterns

  1. Hero Background Video — Full-screen background video at the top of the page
  2. Product Demo Section — Inline video in a product introduction section
  3. Floating Device Mockup — Display video inside a device frame
  4. Lightbox / Modal Video — Click to expand and play

Step 0: Pre-Implementation Checklist

Before adding a video, verify the following:

Video Asset Check

  • Is the video file ready? (If not, create a placeholder UI first)
  • Formats: Ideally both .mp4 (H.264) and .webm (VP9)
  • Resolution: 1920x1080 recommended for Hero, 1280x720 sufficient for card videos
  • File size: 5-15 MB for Hero, 2-5 MB for card videos
  • Duration: 10-30 seconds loop best for autoplay

Creating Video Assets (e.g., recording with Screen Studio)

  1. Record the most impressive product workflow in 15-30 seconds
  2. Record at 1920x1080 or higher resolution
  3. Set the following during export:
    • MP4 (H.264): Quality Medium-High, bitrate 4-8 Mbps
    • WebM (VP9): Export additional copy at equivalent quality (if supported by the tool)
  4. ffmpeg conversion commands:
# MP4 optimization (Web-ready, with faststart)
ffmpeg -i input.mov -c:v libx264 -crf 23 -preset slow \
  -movflags +faststart -an -vf scale=1920:-2 output.mp4

# WebM conversion
ffmpeg -i input.mov -c:v libvpx-vp9 -crf 30 -b:v 0 \
  -an -vf scale=1920:-2 output.webm

# For smaller card videos
ffmpeg -i input.mov -c:v libx264 -crf 26 -preset slow \
  -movflags +faststart -an -vf scale=1280:-2 output-card.mp4

Hosting

  • Local (/public/videos/): For small sites, Vercel / Netlify deployments
  • CDN / Object Storage: For large scale, multiple videos (Cloudflare R2, AWS S3 + CloudFront)
  • YouTube / Vimeo Embed: When SEO is a priority, or you want to share externally

Pattern 1: Hero Background Video

Display a full-screen product demo video at the top of the page, with a text overlay.

React Component

interface HeroVideoProps {
  /** Path to the MP4 video */
  videoSrc: string;
  /** Path to the WebM video (optional) */
  videoSrcWebm?: string;
  /** Poster image to display before the video loads */
  posterSrc?: string;
  /** Child elements for overlay (e.g., heading) */
  children?: React.ReactNode;
  /** Overlay darkness (0-1, default 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">
      {/* Background Video */}
      <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>

      {/* Dark Overlay */}
      <div
        className="absolute inset-0"
        style={{ backgroundColor: `rgba(0, 0, 0, ${overlayOpacity})` }}
      />

      {/* Gradient fade to page background */}
      <div className="absolute inset-x-0 bottom-0 h-32 bg-gradient-to-t from-background to-transparent" />

      {/* Content */}
      {children && (
        <div className="absolute inset-0 z-10 flex items-end">
          <div className="container pb-16 md:pb-24">{children}</div>
        </div>
      )}
    </div>
  );
}

Usage Example

<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">
    Reinventing conversation with technology.
  </h1>
</HeroVideo>

Application Point on Plural Reality LP

Replace or co-exist with the current LifeGameHero component:

// Home.tsx replacement pattern
// Option A: Full replacement
<HeroVideo videoSrc="/videos/hero-demo.mp4" posterSrc="/images/IMG_5298.jpeg">
  <h1 ...>{h.heroTitle}</h1>
</HeroVideo>

// Option B: Keep LifeGameHero and add a video section below
<LifeGameHero>...</LifeGameHero>
<Section className="py-0">
  <HeroVideo videoSrc="/videos/product-overview.mp4" overlayOpacity={0.2}>
    <h2>Product Introduction</h2>
  </HeroVideo>
</Section>

Pattern 2: Product Card Video

Replace static images with demo videos on product cards, playing on hover.

React Component

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>
  );
}

Usage Example (Replacing existing card image)

// Before:
<ParallaxImage src="/images/kouchoai.png" alt={t.products.kouchouAI} />

// After:
<ProductCardVideo
  videoSrc="/videos/kouchou-ai-demo.mp4"
  posterSrc="/images/kouchoai.png"
  alt={t.products.kouchouAI}
/>

Pattern 3: Device Mockup Video

Display demo videos inside browser or smartphone frames (increases credibility).

function BrowserMockupVideo({
  videoSrc,
  posterSrc,
  title,
}: {
  videoSrc: string;
  posterSrc?: string;
  title?: string;
}) {
  return (
    <div className="border border-border bg-background overflow-hidden">
      {/* Browser chrome */}
      <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>
      {/* Video content */}
      <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>
  );
}

Pattern 4: Lightbox Modal Video

Display the video in a modal after clicking a thumbnail. Supports YouTube embeds.

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" />
        {/* Play button overlay */}
        <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>
      )}
    </>
  );
}

Implementation Checklist

When adding a video, check the following:

Performance

  • Set the autoPlay, muted, loop, playsInline attributes (mandatory for iOS compatibility)
  • Specify a poster attribute for an initial frame image (improves loading experience)
  • Use preload="metadata" to keep initial loading light for card videos
  • Apply -movflags +faststart to video files (progressive playback)
  • Provide both MP4 and WebM formats via <source> (browser compatibility)

Responsive

  • Use object-cover / object-contain for proper fitting
  • Consider showing a static image instead of a video on mobile devices
    const isMobile = window.innerWidth < 768;
    // On mobile, load only the poster image, not the video
    

Accessibility

  • Autoplay videos must include muted (audio playback without user interaction is blocked)
  • Respect prefers-reduced-motion and fall back to a static image when animations are disabled
    @media (prefers-reduced-motion: reduce) {
      video { display: none; }
      .video-poster-fallback { display: block; }
    }
    

Design (Plural Reality Compliant)

  • No rounded corners (border-radius: 0px)
  • Border: 1px solid var(--border)
  • Overlays are black gradients (maintaining monochromatic scheme)
  • Use Lucide React's Play icon for the play button

File Organization Convention

client/
  public/
    videos/
      hero-demo.mp4           # Hero video (MP4)
      hero-demo.webm           # Hero video (WebM)
      hero-poster.jpg           # Hero video poster image
      kouchou-ai-demo.mp4      # Kouchou AI demo video
      baisoku-survey-demo.mp4  # Baisoku Survey demo video
      baisoku-kaigi-demo.mp4   # Baisoku Kaigi demo video
  src/
    components/
      HeroVideo.tsx             # Hero full-screen video component
      ProductCardVideo.tsx      # Card hover-to-play component
      BrowserMockupVideo.tsx    # Browser-framed video
      VideoLightbox.tsx         # Modal video component

Common Layout Examples

SaaS / Product LP (Recommended Layout)

Hero full-screen video (15-30 second loop, muted autoplay)
  ↓
Text section (Mission & Values)
  ↓
Browser mockup video (main product demo)
  ↓
Product cards (hover to play individual demo videos)
  ↓
Case studies / Testimonials
  ↓
CTA

Corporate Website (Subtle Layout)

Hero image + play button (click to open Lightbox video)
  ↓
Text section
  ↓
Product cards (static images, video on detail page)

Pattern 5: CSS Animation Product UI Mockup (Recommended)

Render the product's operational feel directly with CSS animations / SVG / Canvas, without video files. This is the approach adopted by Stripe, Linear, and Vercel.

Benefits

  • No video files required (zero byte addition)
  • Fully responsive (works on any screen size)
  • Native rendering for sharpness (no compression artifacts)
  • Supports interactivity (hover, click for changes)
  • Automatically adapts to dark/light mode

Prerequisites: Existing Animation Assets

The Plural Reality LP already has the following high-quality animations implemented in SolutionAnimations.tsx:

ComponentDescriptionTechnology
NetworkOntologyAnimation3-layer ontology (DATA→ONTOLOGY→APPLICATION)SVG + framer-motion
DataFlowAnimationCollect→Organize→Connect pipelineSVG + framer-motion
ClusterAnimationOpinion clustering visualizationSVG + framer-motion
ConversationBranchAnimationIn-depth interview branch treeSVG + framer-motion
FeedbackLoopAnimationContinuous dialogue cycleSVG + framer-motion
IntegrationMeshAnimationPlatform integration diagramSVG + framer-motion
ParticleFieldAnimationParticle networkCanvas
IsometricLayersAnimationIsometric projection layer structureSVG + framer-motion

Also, LifeGameHero.tsx has a Canvas animation from Earth to Japan.

Common Design Principles

- Background: Black (#000000), drawn with white elements
- Monochromatic (no color, or only Brand Teal as accent)
- Font: var(--font-mono) for tech labels, var(--font-sans) for UI elements
- No rounded corners: rx={0} / border-radius: 0px
- Animation: framer-motion's useInView + motion.* for scroll-triggered animation
- Grid background: Subtle white line grid pattern (GridBg utility)
- Glow: feDropShadow for subtle glow (GlowDef utility)

Pattern 5A: Browser Frame + Animated UI

Display an animated mock UI inside a browser chrome (title bar + dots).

function ProductDemoMock({
  title,
  children,
  className,
}: {
  title: string;
  children: React.ReactNode;
  className?: string;
}) {
  return (
    <div className={cn("border border-border bg-black overflow-hidden", className)}>
      {/* Browser chrome */}
      <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>
      {/* Animated content */}
      <div className="aspect-video relative overflow-hidden">
        {children}
      </div>
    </div>
  );
}

Pattern 5B: Kouchou AI Demo Animation

An animation showing text data flowing in → AI clustering → results being displayed.

// Conceptual design (refer to ClusterAnimation in SolutionAnimations for implementation)
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">
        {/* Phase 1: Text lines slide in from the left */}
        {phase === "input" && (
          <g>
            {["Traffic is inconvenient", "I want better childcare support", "I'm worried about disaster measures"].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>
        )}

        {/* Phase 2: Particles converge to the center */}
        {phase === "processing" && (
          <motion.text x={400} y={225} textAnchor="middle" fill="white" fontSize={12} fontFamily="var(--font-mono)"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
          >
            Processing...
          </motion.text>
        )}

        {/* Phase 3: Clustering result display */}
        {phase === "result" && (
          <g>
            <motion.circle cx={200} cy={150} r={60} fill="none" stroke="white" strokeWidth={0.5}
              initial={{ r: 0 }}
              animate={{ r: 60 }}
              transition={{ duration: 0.8 }}
            />
            <motion.text x={200} y={155} textAnchor="middle" fill="white" fontSize={10} fontFamily="var(--font-sans)">
              Urban Development
            </motion.text>
            <motion.circle cx={500} cy={180} r={50} fill="none" stroke="white" strokeWidth={0.5}
              initial={{ r: 0 }}
              animate={{ r: 50 }}
              transition={{ duration: 0.8, delay: 0.3 }}
            />
            <motion.text x={500} y={185} textAnchor="middle" fill="white" fontSize={10} fontFamily="var(--font-sans)">
              Childcare & Education
            </motion.text>
            <motion.circle cx={350} cy={320} r={55} fill="none" stroke="white" strokeWidth={0.5}
              initial={{ r: 0 }}
              animate={{ r: 55 }}
              transition={{ duration: 0.8, delay: 0.6 }}
            />
            <motion.text x={350} y={325} textAnchor="middle" fill="white" fontSize={10} fontFamily="var(--font-sans)">
              Disaster Prevention
            </motion.text>
          </g>
        )}
      </svg>
    </ProductDemoMock>
  );
}

Pattern 5C: Baisoku Survey Demo (Animated Form)

function BaisokuSurveyDemoAnimation() {
  return (
    <ProductDemoMock title="baisoku-survey.plural-reality.com">
      <svg viewBox="0 0 800 450" className="w-full h-full">
        {/* Animated fill bar */}
        <motion.rect
          x={50} y={50} width={700} height={20}
          fill="white" fillOpacity={0.1}
          rx={0}
        />
        <motion.rect
          x={50} y={50} height={20}
          fill="white" fillOpacity={0.3}
          initial={{ width: 0 }}
          animate={{ width: 700 }}
          transition={{ duration: 3, ease: "easeInOut", repeat: Infinity, repeatDelay: 1 }}
          rx={0}
        />
        {/* Question text appearing one by one */}
        {["Q1: Age group?", "Q2: Occupation?", "Q3: Needs?"].map((q, i) => (
          <motion.text
            key={q}
            x={70}
            y={120 + i * 60}
            fill="white"
            fontSize={14}
            fontFamily="var(--font-sans)"
            initial={{ opacity: 0, x: -20 }}
            animate={{ opacity: 1, x: 0 }}
            transition={{ delay: i * 1.2, duration: 0.5, repeat: Infinity, repeatDelay: 4 - i * 1.2 }}
          >
            {q}
          </motion.text>
        ))}
        {/* Progress percentage */}
        <motion.text
          x={730} y={64} textAnchor="end" fill="white" fontSize={11} fontFamily="var(--font-mono)"
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          transition={{ duration: 0.5, repeat: Infinity, repeatDelay: 4 }}
        >
          100%
        </motion.text>
      </svg>
    </ProductDemoMock>
  );
}

Pattern 5D: Baisoku Kaigi Demo (Dialog Box Animation)

function BaisokuKaigiDemoAnimation() {
  return (
    <ProductDemoMock title="baisoku-kaigi.plural-reality.com">
      <svg viewBox="0 0 800 450" className="w-full h-full">
        {/* Person A's speech bubble */}
        <motion.g
          initial={{ opacity: 0, x: -50 }}
          animate={{ opacity: 1, x: 0 }}
          transition={{ duration: 0.6, repeat: Infinity, repeatDelay: 5 }}
        >
          <rect x={40} y={60} width={280} height={50} fill="white" fillOpacity={0.1} stroke="white" strokeWidth={0.5} rx={0} />
          <text x={56} y={90} fill="white" fontSize={13} fontFamily="var(--font-sans)">
            Should we allocate more budget?
          </text>
        </motion.g>
        {/* Person B's speech bubble */}
        <motion.g
          initial={{ opacity: 0, x: 50 }}
          animate={{ opacity: 1, x: 0 }}
          transition={{ duration: 0.6, delay: 1.5, repeat: Infinity, repeatDelay: 4.5 }}
        >
          <rect x={480} y={160} width={280} height={50} fill="white" fillOpacity={0.1} stroke="white" strokeWidth={0.5} rx={0} />
          <text x={496} y={190} fill="white" fontSize={13} fontFamily="var(--font-sans)">
            Let's check the survey results.
          </text>
        </motion.g>
        {/* AI summary displayed on the left */}
        <motion.g
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          transition={{ duration: 0.5, delay: 3, repeat: Infinity, repeatDelay: 3 }}
        >
          <rect x={40} y={280} width={280} height={80} fill="white" fillOpacity={0.05} stroke="white" strokeWidth={0.5} strokeDasharray="3 3" rx={0} />
          <text x={56} y={300} fill="white" fontSize={10} fontFamily="var(--font-mono)" opacity={0.6}>
            AI Analysis
          </text>
          <text x={56} y={320} fill="white" fontSize={11} fontFamily="var(--font-sans)">
            72% of opinions support the budget increase.
          </text>
          <text x={56} y={340} fill="white" fontSize={11} fontFamily="var(--font-sans)">
            Key concern: impact on other projects.
          </text>
        </motion.g>
      </svg>
    </ProductDemoMock>
  );
}

Summary of Best Practices

  1. Start with an animation mockup (Pattern 5) and switch to video only when you need specific footage
  2. Provide multiple formats — MP4 (H.264) + WebM (VP9) for maximum compatibility
  3. Optimize heavily — Compress videos with ffmpeg and add -movflags +faststart
  4. Do not autoplay audio — Always set muted attribute
  5. Always set a poster — A static image to show before the video loads
  6. Respect reduced motion — Provide a fallback for users who prefer reduced animation
  7. Handle mobile device constraints — Consider conditionally loading static images instead of videos on less capable devices

相关技能