Communitygithub.com

yuidvg/lp-hero-video

在登陸頁面中嵌入產品展示影片的技能。透過CSS動畫實現產品UI模擬、英雄影片、卡片內影片等多種網站展示模式。

lp-hero-video 是什麼?

這個技能提供了一個完整的指南,幫助開發者在登陸頁面或網站中有效地嵌入產品展示影片。它涵蓋了多種影片展示模式,包括全螢幕背景英雄影片、產品介紹區塊的內嵌影片、裝置框架內的浮動影片,以及點擊放大的燈箱模態影片。技能不僅說明各模式的React元件實作,也詳細介紹影片素材的準備要點,如格式(MP4 + WebM)、解析度、檔案大小建議,以及使用ffmpeg進行最佳化的指令。此外,它還包含了實作檢查清單,涵蓋效能(如iOS所需的屬性)、響應式設計(如行動裝置使用靜態圖片替代)和無障礙性(如支援`prefers-reduced-motion`)。此技能特別適合在SaaS產品頁、公司官網等情境中使用,並已針對Plural Reality LP的設計系統進行整合,包括黑暗模式相容和特定的CSS動畫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

在登陸頁面或網站中有效嵌入產品展示影片的指南。

支援模式

  1. Hero Background Video — 頁面頂部的全螢幕背景影片
  2. Product Demo Section — 產品介紹區塊中的內嵌影片
  3. Floating Device Mockup — 在裝置框架內顯示影片
  4. Lightbox / Modal Video — 點擊後放大播放

Step 0: 事前確認檢查清單

在嵌入影片前,請確認以下事項:

影片素材的確認

  • 是否已準備好影片檔案?(如果尚未準備,請先建立佔位符UI)
  • 格式:理想為 .mp4(H.264)+ .webm(VP9)兩種格式
  • 解析度:Hero建議使用 1920x1080,卡片內使用 1280x720 即可
  • 檔案大小:Hero建議 5-15MB,卡片內建議 2-5MB
  • 長度:自動播放以 10-30秒的循環為佳

影片素材的製作方法(使用Screen Studio等工具錄製時)

  1. 錄製產品最令人印象深刻的操作流程,長度 15-30秒
  2. 以 1920x1080 以上的解析度錄製
  3. 匯出時設定如下:
    • MP4 (H.264):品質 Medium-High、位元率 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 / Object Storage:適用於大型網站或多個影片(Cloudflare R2, AWS S3 + CloudFront)
  • YouTube / Vimeo embed:適合重視SEO或需要外部共享的場合

Pattern 1: Hero Background Video

在頁面頂部全螢幕顯示產品展示影片,並附帶文字疊加層。

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

使用範例

<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 中的置換模式
// Option A: 完全取代
<HeroVideo videoSrc="/videos/hero-demo.mp4" posterSrc="/images/IMG_5298.jpeg">
  <h1 ...>{h.heroTitle}</h1>
</HeroVideo>

// Option B: 保留 LifeGameHero,並在其下方新增影片區塊
<LifeGameHero>...</LifeGameHero>
<Section className="py-0">
  <HeroVideo videoSrc="/videos/product-overview.mp4" overlayOpacity={0.2}>
    <h2>產品介紹</h2>
  </HeroVideo>
</Section>

Pattern 2: Product Card Video

在產品列表的每個卡片中,以滑鼠懸停播放展示影片取代靜態圖片。

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

使用範例(取代現有卡片圖片)

// 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

在瀏覽器或手機框架內顯示展示影片(提升信賴感)。

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

點擊縮圖後以模態視窗顯示影片,也支援 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" />
        {/* 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>
      )}
    </>
  );
}

實作檢查清單

新增影片時請確認以下項目:

效能

  • 設定 autoPlay muted loop playsInline 四個屬性(iOS 必須)
  • 使用 poster 屬性指定初始幀圖片(提升載入體驗)
  • 卡片內影片使用 preload="metadata" 以減輕初始載入負擔
  • 影片檔案已套用 -movflags +faststart(支援漸進式播放)
  • 在 <source> 中提供 MP4 + WebM 兩種格式(瀏覽器相容性)

響應式

  • 使用 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 / 產品登陸頁(建議配置)

Hero全螢幕影片(15-30秒循環、靜音自動播放)
  ↓
文字區塊(使命與價值)
  ↓
瀏覽器模擬影片(主要產品展示)
  ↓
產品卡片(懸停時播放個別展示影片)
  ↓
案例研究 / 成果展示
  ↓
CTA

公司官網(低調配置)

英雄圖片 + 播放按鈕(點擊後以Lightbox播放影片)
  ↓
文字區塊
  ↓
產品卡片(靜態圖片,詳細頁面內有影片)

Pattern 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),以白色元素繪製
- 單色調(不使用彩色,或僅用 Brand Teal 作爲強調色)
- 字體:技術標籤使用 var(--font-mono),UI元素使用 var(--font-sans)
- 無圓角:rx={0} / border-radius: 0px
- 動畫:使用 framer-motion 的 useInView + motion.* 實現滾動觸發
- 網格背景:淺白線網格模式(GridBg 工具)
- 發光:使用 feDropShadow 實現低調發光(GlowDef 工具)

Pattern 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)}>
      {/* 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: 廣聽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">
        {/* Phase 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>
        )}

        {/* Phase 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 }}>
            正在分析中...
          </motion.text>
        )}

        {/* Phase 3: 顯示分群結果 */}
        {phase === "result" && (
          <g>
            {[{label: "交通議題", x: 200}, {label: "育兒議題", x: 400}, {label: "防災議題", x: 600}].map((cluster) => (
              <motion.g key={cluster.label}
                initial={{ scale: 0, opacity: 0 }}
                animate={{ scale: 1, opacity: 1 }}
                transition={{ type: "spring", stiffness: 100, damping: 15 }}
              >
                <circle cx={cluster.x} cy={225} r={60} fill="white" fillOpacity={0.05} stroke="white" strokeWidth={0.5} />
                <text x={cluster.x} y={225} textAnchor="middle" dominantBaseline="middle" fill="white" fontSize={12} fontFamily="var(--font-mono)">
                  {cluster.label}
                </text>
              </motion.g>
            ))}
          </g>
        )}
      </svg>
    </ProductDemoMock>
  );
}

提供建議時的注意事項

應提案使用CSS動畫(Pattern 5)的時機

  • 實現簡單的操作流程(點擊、輸入、結果顯示等2-3步驟)
  • 需要深色模式/淺色模式雙支援
  • 需要回應使用者互動(懸停、點擊)
  • 設計稿已準備好(尤其是使用Figama UI模擬)
  • 團隊對framer-motion有經驗

應提案使用影片的時機

  • 需要實際錄影的複雜UI操作(多次點擊、拖拽等)
  • 包含實際資料或地圖顯示
  • 需要在多個裝置(實際設備)上確認動作
  • 沒有設計動畫的時間,需要快速交付

設計的一致性

無論選擇哪種Pattern,以下要點務必統一:

  • 邊框、背景色遵循Design Token
  • 無圓角、單色調
  • 使用相同的瀏覽器框架元件
  • 所有動畫使用framer-motion(統一時間軸)

Pattern 5B 實作:廣聽AI 展示動畫(完整版)

以下是在 SolutionAnimations.tsx 中新增的實作方案。

// 在 SolutionAnimations.tsx 中新增

export function KouchouAIDemoAnimation() {
  const [phase, setPhase] = useState<0 | 1 | 2>(0);

  // 循環動畫
  useEffect(() => {
    const sequence = async () => {
      while (true) {
        setPhase(0);
        await new Promise((r) => setTimeout(r, 3000));
        setPhase(1);
        await new Promise((r) => setTimeout(r, 3000));
        setPhase(2);
        await new Promise((r) => setTimeout(r, 4000));
      }
    };
    sequence();
  }, []);

  return (
    <ProductDemoMock title="kouchou-ai.plural-reality.com">
      <svg viewBox="0 0 800 450" className="w-full h-full">
        {/* 背景網格 */}
        <GridBg />
        
        {/* Phase 0: 文字輸入 */}
        {phase === 0 && <InputPhase />}
        
        {/* Phase 1: 處理中 */}
        {phase === 1 && <ProcessingPhase />}
        
        {/* Phase 2: 結果顯示 */}
        {phase === 2 && <ResultPhase />}
      </svg>
    </ProductDemoMock>
  );
}

這將在 PatternsShowcase 或產品介紹區塊中以 scroll-triggered 方式顯示。

相關技能