Communitygithub.com

yuidvg/lp-hero-video

랜딩 페이지에 프로덕트 데모를 통합하는 스킬입니다. CSS 애니메이션을 사용한 프로덕트 UI 목업, 히어로 비디오, 카드 내 비디오 등 웹사이트 데모 구현 패턴을 제공합니다.

lp-hero-video란 무엇인가요?

이 스킬은 랜딩 페이지 및 웹사이트에 프로덕트 데모 비디오를 효과적으로 통합하기 위한 포괄적인 가이드입니다. 히어로 배경 비디오, 프로덕트 카드 비디오, 기기 목업 비디오, 라이트박스 모달 비디오 등 다양한 패턴을 React 컴포넌트 예제와 함께 제공합니다. 특히 동영상 파일 없이 CSS/SVG/Canvas 애니메이션으로 프로덕트 UI 목업을 구현하는 Pattern 5를 강력히 추천하며, 이는 Stripe, Linear, Vercel과 같은 최신 SaaS 제품에서 사용하는 접근 방식입니다. React, Next.js, TypeScript 환경에서 사용할 수 있으며, framer-motion을 활용한 스크롤 트리거 애니메이션, 반응형 디자인, 성능 최적화(MP4 + WebM 듀얼 포맷, faststart 적용), 접근성(prefers-reduced-motion 대응) 등의 세부 구현 지침을 포함하고 있어 개발자가 바로 활용할 수 있습니다.

지원 대상~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/yuidvg/skills/tree/HEAD/skills/lp-hero-video

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

LP 히어로 비디오 스킬

랜딩 페이지 및 웹사이트에 프로덕트 데모 비디오를 효과적으로 통합하기 위한 가이드입니다.

지원 패턴

  1. 히어로 배경 비디오 — 페이지 상단의 전체 화면 배경 비디오
  2. 프로덕트 데모 섹션 — 프로덕트 소개 섹션에 인라인 비디오
  3. 플로팅 기기 목업 — 기기 프레임 내에 비디오 표시
  4. 라이트박스 / 모달 비디오 — 클릭 시 확대 재생

0단계: 사전 확인 체크리스트

비디오를 통합하기 전에 다음을 확인하십시오:

비디오 자료 확인

  • 비디오 파일이 준비되었습니까? (아직 없다면 먼저 플레이스홀더 UI를 생성하십시오)
  • 포맷: .mp4 (H.264) + .webm (VP9) 두 가지 형식이 이상적입니다.
  • 해상도: 히어로용은 1920x1080 권장, 카드 내부는 1280x720이면 충분합니다.
  • 파일 크기: 히어로용은 5-15MB, 카드 내부는 2-5MB를 목표로 합니다.
  • 길이: 자동 재생은 10-30초 루프가 가장 적합합니다.

비디오 자료 제작 방법 (Screen Studio 등으로 녹화하는 경우)

  1. 제품의 가장 인상적인 조작 흐름을 15-30초 동안 녹화합니다.
  2. 해상도는 1920x1080 이상으로 녹화합니다.
  3. 내보내기 시 다음을 설정합니다:
    • MP4 (H.264): 품질 중간-높음, 비트레이트 4-8 Mbps
    • WebM (VP9): 동등한 품질로 추가 내보내기 (도구에서 지원하는 경우)
  4. ffmpeg 변환 명령어:
# MP4 최적화 (웹용, faststart 포함)
ffmpeg -i input.mov -c:v libx264 -crf 23 -preset slow \
  -movflags +faststart -an -vf scale=1920:-2 output.mp4

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

# 카드용 (더 작게)
ffmpeg -i input.mov -c:v libx264 -crf 26 -preset slow \
  -movflags +faststart -an -vf scale=1280:-2 output-card.mp4

호스팅

  • 로컬 (/public/videos/): 소규모 사이트, Vercel / Netlify 배포 대상
  • CDN / 오브젝트 스토리지: 대규모, 여러 비디오의 경우 (Cloudflare R2, AWS S3 + CloudFront)
  • YouTube / Vimeo 임베드: SEO 중요, 외부 공유가 필요한 경우

패턴 1: 히어로 배경 비디오

페이지 최상단에 전체 화면으로 프로덕트 데모 비디오를 표시합니다. 텍스트 오버레이가 포함됩니다.

React 컴포넌트

interface HeroVideoProps {
  /** MP4 비디오 경로 */
  videoSrc: string;
  /** WebM 비디오 경로 (선택 사항) */
  videoSrcWebm?: string;
  /** 비디오 로드 전에 표시할 포스터 이미지 */
  posterSrc?: string;
  /** 오버레이 자식 요소 (제목 등) */
  children?: React.ReactNode;
  /** 오버레이 어두움 (0-1, 기본값 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">
      {/* 배경 비디오 */}
      <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>

      {/* 어두운 오버레이 */}
      <div
        className="absolute inset-0"
        style={{ backgroundColor: `rgba(0, 0, 0, ${overlayOpacity})` }}
      />

      {/* 페이지 배경으로의 그라데이션 페이드 */}
      <div className="absolute inset-x-0 bottom-0 h-32 bg-gradient-to-t from-background to-transparent" />

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

사용 예시

<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">
    기술로 대화를 재발명합니다.
  </h1>
</HeroVideo>

Plural Reality LP 적용 위치

현재 LifeGameHero 컴포넌트를 대체하거나 함께 사용하는 형태:

// Home.tsx 에서의 교체 패턴
// 옵션 A: 완전 교체
<HeroVideo videoSrc="/videos/hero-demo.mp4" posterSrc="/images/IMG_5298.jpeg">
  <h1 ...>{h.heroTitle}</h1>
</HeroVideo>

// 옵션 B: LifeGameHero 유지 + 아래에 비디오 섹션 추가
<LifeGameHero>...</LifeGameHero>
<Section className="py-0">
  <HeroVideo videoSrc="/videos/product-overview.mp4" overlayOpacity={0.2}>
    <h2>프로덕트 소개</h2>
  </HeroVideo>
</Section>

패턴 2: 프로덕트 카드 비디오

프로덕트 목록의 각 카드에서 정적 이미지 대신 데모 비디오를 호버 시 재생합니다.

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

사용 예시 (기존 카드 이미지 교체)

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

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

패턴 3: 기기 목업 비디오

브라우저나 스마트폰 프레임 내에 데모 비디오를 표시합니다 (신뢰도 향상).

function BrowserMockupVideo({
  videoSrc,
  posterSrc,
  title,
}: {
  videoSrc: string;
  posterSrc?: string;
  title?: string;
}) {
  return (
    <div className="border border-border bg-background overflow-hidden">
      {/* 브라우저 크롬 */}
      <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>
      {/* 비디오 콘텐츠 */}
      <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>
  );
}

패턴 4: 라이트박스 모달 비디오

썸네일 클릭 시 비디오를 모달로 표시합니다. 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" />
        {/* 재생 버튼 오버레이 */}
        <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>

      {/* 모달 */}
      {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>
      )}
    </>
  );
}

구현 체크리스트

비디오를 추가할 때는 다음을 확인하십시오:

성능

  • autoPlay muted loop playsInline 4가지 속성 설정 (iOS 대응 필수)
  • poster 속성으로 초기 프레임 이미지 지정 (로딩 중 경험 향상)
  • preload="metadata"로 카드 내 비디오의 초기 로드를 가볍게 함
  • 비디오 파일에 -movflags +faststart 적용 완료 (프로그레시브 재생)
  • MP4 + WebM 두 가지 형식을 <source>로 제공 (브라우저 호환성)

반응형

  • object-cover / object-contain으로 적절히 맞춤
  • 모바일에서는 비디오 대신 정적 이미지를 표시하는 옵션도 고려
    const isMobile = window.innerWidth < 768;
    // 모바일에서는 poster 이미지만, 비디오는 로드하지 않음
    

접근성

  • 자동 재생 비디오는 반드시 muted (사용자 조작 없이 오디오 재생은 차단됨)
  • prefers-reduced-motion에 대응, 애니메이션 비활성화 시 정적 이미지로 폴백
    @media (prefers-reduced-motion: reduce) {
      video { display: none; }
      .video-poster-fallback { display: block; }
    }
    

디자인 (Plural Reality 준수)

  • 모서리 둥글게 없음 (border-radius: 0px)
  • 테두리: 1px solid var(--border)
  • 오버레이는 검은색 그라데이션 (단색 유지)
  • 재생 버튼 아이콘은 Lucide React의 Play 사용

파일 배치 규칙

client/
  public/
    videos/
      hero-demo.mp4           # 히어로 비디오 (MP4)
      hero-demo.webm           # 히어로 비디오 (WebM)
      hero-poster.jpg           # 히어로 비디오 포스터 이미지
      kouchou-ai-demo.mp4      # 광청AI 데모 비디오
      baisoku-survey-demo.mp4  # 배속 서베이 데모 비디오
      baisoku-kaigi-demo.mp4   # 배속 회의 데모 비디오
  src/
    components/
      HeroVideo.tsx             # 히어로 전체 화면 비디오 컴포넌트
      ProductCardVideo.tsx      # 카드 내 호버 재생 컴포넌트
      BrowserMockupVideo.tsx    # 브라우저 프레임 비디오
      VideoLightbox.tsx         # 모달 비디오 컴포넌트

일반적인 구성 예시

SaaS / 프로덕트 LP (권장 구성)

히어로 전체 화면 비디오 (15-30초 루프, 음소거 자동 재생)
  ↓
텍스트 섹션 (미션, 가치)
  ↓
브라우저 목업 비디오 (메인 프로덕트 데모)
  ↓
프로덕트 카드 (호버 시 개별 데모 비디오 재생)
  ↓
사례 연구 / 실적
  ↓
CTA

기업 사이트 (절제된 구성)

히어로 이미지 + 재생 버튼 (클릭 시 라이트박스 비디오)
  ↓
텍스트 섹션
  ↓
프로덕트 카드 (정적 이미지, 상세 페이지에 비디오)

패턴 5: CSS 애니메이션을 통한 프로덕트 UI 목업 (권장)

비디오 파일 없이 CSS 애니메이션 / SVG / Canvas로 프로덕트의 조작감을 직접 렌더링합니다. Stripe, Linear, Vercel에서 채택한 접근 방식입니다.

장점

  • 비디오 파일 불필요 (제로 추가 용량)
  • 완전 반응형 (모든 화면 크기에서 깨지지 않음)
  • 네이티브 렌더링으로 선명함 (압축 아티팩트 없음)
  • 인터랙티브 대응 가능 (호버, 클릭 시 변화)
  • 다크 모드 / 라이트 모드 자동 대응

전제: 기존 애니메이션 자산

Plural Reality LP는 SolutionAnimations.tsx에 다음과 같은 고품질 애니메이션이 구현되어 있습니다:

컴포넌트내용기술
NetworkOntologyAnimation3계층 온톨로지 (DATA→ONTOLOGY→APPLICATION)SVG + framer-motion
DataFlowAnimation수집→정리→연결 파이프라인SVG + framer-motion
ClusterAnimation의견 클러스터링 시각화SVG + framer-motion
ConversationBranchAnimation심층 인터뷰 분기 트리SVG + framer-motion
FeedbackLoopAnimation지속적 대화 사이클SVG + framer-motion
IntegrationMeshAnimation플랫폼 통합 다이어그램SVG + framer-motion
ParticleFieldAnimation파티클 네트워크Canvas
IsometricLayersAnimation등각 투영 레이어 구조SVG + framer-motion

또한 LifeGameHero.tsx에 지구 → 일본 Canvas 애니메이션이 있습니다.

공통 디자인 원칙

- 배경: 검은색 (#000000), 흰색 요소로 렌더링
- 단색 (색상 사용 안 함, 브랜드 틸만 강조 색상으로 사용)
- 폰트: var(--font-mono)로 테크 레이블, var(--font-sans)로 UI 요소
- 모서리 둥글게 없음: rx={0} / border-radius: 0px
- 애니메이션: framer-motion의 useInView + motion.*로 스크롤 트리거
- 그리드 배경: 얇은 흰색 선 격자 패턴 (GridBg 유틸리티)
- 글로우: feDropShadow로 절제된 발광 (GlowDef 유틸리티)

패턴 5A: 브라우저 프레임 + 애니메이션 UI

브라우저 크롬 (타이틀바 + 도트) 안에 애니메이션으로 움직이는 목업 UI를 표시합니다.

function ProductDemoMock({
  title,
  children,
  className,
}: {
  title: string;
  children: React.ReactNode;
  className?: string;
}) {
  return (
    <div className={cn("border border-border bg-black overflow-hidden", className)}>
      {/* 브라우저 크롬 */}
      <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>
      {/* 애니메이션 콘텐츠 */}
      <div className="aspect-video relative overflow-hidden">
        {children}
      </div>
    </div>
  );
}

패턴 5B: 광청AI 데모 애니메이션

텍스트 데이터가 흘러들어오고 → AI가 클러스터링 → 결과가 표시되는 흐름입니다.

// 개념 설계 (구현 시 SolutionAnimations의 ClusterAnimation 참조)
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">
        {/* 단계 1: 텍스트 줄이 왼쪽에서 슬라이드 인 */}
        {phase === "input" && (
          <g>
            {["교통이 불편해서 곤란해요", "육아 지원을 강화해 주세요", "방재 대책이 걱정돼요"].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>
        )}

        {/* 단계 2: 파티클이 중앙으로 모임 */}
        {phase === "processing" && (
          <motion.text x={400} y={225} textAnchor="middle" fill="white" fontSize={12} fontFamily="var(--font-mono)"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
          >
            CLUSTERING...
          </motion.text>
        )}

        {/* 단계 3: 클러스터된 결과 표시 */}
        {phase === "result" && (
          <g>
            {[
              { label: "교통 인프라", count: 42, x: 200, y: 150 },
              { label: "육아 지원", count: 38, x: 400, y: 100 },
              { label: "방재", count: 27, x: 600, y: 180 },
            ].map((cluster, i) => (
              <motion.g
                key={cluster.label}
                initial={{ scale: 0, opacity: 0 }}
                animate={{ scale: 1, opacity: 1 }}
                transition={{ delay: i * 0.3, type: "spring", stiffness: 100 }}
              >
                <circle cx={cluster.x} cy={cluster.y} r={60 + cluster.count} fill="white" fillOpacity={0.03} stroke="white" strokeWidth={0.5} />
                <text x={cluster.x} y={cluster.y - 5} textAnchor="middle" fill="white" fontSize={12} fontFamily="var(--font-sans)" fontWeight="bold">{cluster.label}</text>
                <text x={cluster.x} y={cluster.y + 15} textAnchor="middle" fill="white" fontSize={10} fontFamily="var(--font-mono)" opacity={0.6}>{cluster.count}건</text>
              </motion.g>
            ))}
          </g>
        )}
      </svg>
    </ProductDemoMock>
  );
}

보충: 목업 해상도 및 더미 데이터 변경

ProductDemoMock 내부에서 aspect-video를 유지하는 한, SVG viewBox와 내부 좌표를 조정하여 다양한 비율을 지원할 수 있습니다. 또한 더미 텍스트 데이터는 실제 프로덕트 도메인에 맞게 자유롭게 변경 가능합니다. 예: 의료, 금융, 교육 등.


라이선스

이 스킬은 Plural Reality의 LP 디자인 가이드라인을 따릅니다. 상업적 및 개인 프로젝트에서 자유롭게 사용/수정하십시오.

관련 스킬