LP 히어로 비디오 스킬
랜딩 페이지 및 웹사이트에 프로덕트 데모 비디오를 효과적으로 통합하기 위한 가이드입니다.
지원 패턴
- 히어로 배경 비디오 — 페이지 상단의 전체 화면 배경 비디오
- 프로덕트 데모 섹션 — 프로덕트 소개 섹션에 인라인 비디오
- 플로팅 기기 목업 — 기기 프레임 내에 비디오 표시
- 라이트박스 / 모달 비디오 — 클릭 시 확대 재생
0단계: 사전 확인 체크리스트
비디오를 통합하기 전에 다음을 확인하십시오:
비디오 자료 확인
- 비디오 파일이 준비되었습니까? (아직 없다면 먼저 플레이스홀더 UI를 생성하십시오)
- 포맷:
.mp4(H.264) +.webm(VP9) 두 가지 형식이 이상적입니다. - 해상도: 히어로용은 1920x1080 권장, 카드 내부는 1280x720이면 충분합니다.
- 파일 크기: 히어로용은 5-15MB, 카드 내부는 2-5MB를 목표로 합니다.
- 길이: 자동 재생은 10-30초 루프가 가장 적합합니다.
비디오 자료 제작 방법 (Screen Studio 등으로 녹화하는 경우)
- 제품의 가장 인상적인 조작 흐름을 15-30초 동안 녹화합니다.
- 해상도는 1920x1080 이상으로 녹화합니다.
- 내보내기 시 다음을 설정합니다:
- MP4 (H.264): 품질 중간-높음, 비트레이트 4-8 Mbps
- WebM (VP9): 동등한 품질로 추가 내보내기 (도구에서 지원하는 경우)
- 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 playsInline4가지 속성 설정 (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에 다음과 같은 고품질 애니메이션이 구현되어 있습니다:
| 컴포넌트 | 내용 | 기술 |
|---|---|---|
NetworkOntologyAnimation | 3계층 온톨로지 (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 디자인 가이드라인을 따릅니다. 상업적 및 개인 프로젝트에서 자유롭게 사용/수정하십시오.