[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71K7VSD5ZWN26QJEC4T0770":3,"skill-same:01M71K7VSD5ZWN26QJEC4T0770":75},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":55,"commitSha":56,"skillDirectory":57,"forksCount":58,"repositoryHtmlUrl":59,"repositoryHomepageUrl":6,"ownerHtmlUrl":60,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":61,"install":62,"resources":74},"01M71K7VSD5ZWN26QJEC4T0770","video-gen",null,"Use when generating video from frame sequences — FFmpeg extraction, WebP\u002FAVIF conversion, sprite sheets, scroll-synced playback, video encoding\u002Ftranscoding. Covers the full pipeline from video source to web-ready frame sequence or optimized video.","KeiSeiLab\u002FKeiSeiKit","KeiSeiLab","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F283315582?v=4","01M71K2EW81JRFHSGF0T844B32","KeiSei Lab","01M71K2EWXTB52KWC65E55S6M2",7,"Apache-2.0","https:\u002F\u002Fgithub.com\u002FKeiSeiLab\u002FKeiSeiKit\u002Fblob\u002Fmain\u002Fskills\u002Fvideo-gen\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000016","图像与多媒体","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"gen","video gen","Video","target","webp","path","full","source","sequence","sequences","directory","conversion","image","when","false","synced","generated","default","pipeline","ready","2026-11-10 10:12:30",false,"---\nname: video-gen\ndescription: Use when generating video from frame sequences — FFmpeg extraction, WebP\u002FAVIF conversion, sprite sheets, scroll-synced playback, video encoding\u002Ftranscoding. Covers the full pipeline from video source to web-ready frame sequence or optimized video.\narguments:\n  - name: source\n    description: \"Source: video file path, image sequence directory, or AI-generated frames\"\n    required: true\n  - name: target\n    description: \"Target: frame-sequence, sprite-sheet, optimized-video, gif (default: frame-sequence)\"\n    required: false\n---\n\n# Video-Gen Skill — Frame Sequence Pipeline\n\n## Pipeline Overview\n\n```\nSource Video (MP4\u002FMOV\u002FProRes)\n  │\n  ├─→ [Frame Extraction] FFmpeg → PNG sequence\n  │     │\n  │     ├─→ [Optimize] cwebp → WebP sequence (primary)\n  │     ├─→ [Optimize] avifenc → AVIF sequence (smaller, slower encode)\n  │     └─→ [Sprite Sheet] ImageMagick montage → single image\n  │\n  ├─→ [Video Scrub] FFmpeg re-encode → optimized MP4 for scroll scrub\n  │\n  └─→ [Web Playback] Canvas + ScrollTrigger \u002F video.currentTime\n```\n\n---\n\n## 1. Frame Extraction [E1]\n\n### Basic Extraction\n\n```bash\n# Extract all frames at source FPS\nffmpeg -i source.mp4 -qscale:v 2 frames\u002Fframe_%04d.png\n\n# Extract at specific FPS (30fps → 150 frames for 5s video)\nffmpeg -i source.mp4 -vf \"fps=30\" frames\u002Fframe_%04d.png\n\n# Extract with resolution scaling\nffmpeg -i source.mp4 -vf \"fps=30,scale=1920:1080\" frames\u002Fframe_%04d.png\n\n# Extract specific time range (2s to 7s)\nffmpeg -i source.mp4 -ss 2 -t 5 -vf \"fps=30\" frames\u002Fframe_%04d.png\n```\n\n### Frame Count Guidelines\n\n| Duration | Desktop (30fps) | Mobile (15fps) | Notes |\n|----------|-----------------|----------------|-------|\n| 3 seconds | 90 frames | 45 frames | Short reveal |\n| 5 seconds | 150 frames | 75 frames | Product showcase |\n| 10 seconds | 300 frames | 150 frames | Full story section |\n| 15 seconds | 450 frames | 225 frames | Max recommended |\n\n**Rule:** More frames = smoother but heavier. 120-180 is the sweet spot for most scroll animations.\n\n---\n\n## 2. Format Conversion [E1]\n\n### PNG to WebP (Recommended)\n\n```bash\n# Single file\ncwebp -q 80 frame_0001.png -o frame_0001.webp\n\n# Batch convert all PNGs\nfor f in frames\u002F*.png; do\n  cwebp -q 80 \"$f\" -o \"${f%.png}.webp\"\ndone\n\n# Parallel batch (faster)\nfind frames\u002F -name \"*.png\" | xargs -P 8 -I {} sh -c '\n  cwebp -q 80 \"{}\" -o \"$(echo {} | sed s\u002F.png\u002F.webp\u002F)\"\n'\n```\n\n### PNG to AVIF (Smaller, Slower Encode)\n\n```bash\n# Requires avifenc (brew install libavif)\navifenc --min 20 --max 30 -s 6 frame_0001.png frame_0001.avif\n\n# Batch\nfor f in frames\u002F*.png; do\n  avifenc --min 20 --max 30 -s 6 \"$f\" \"${f%.png}.avif\"\ndone\n```\n\n### Format Comparison\n\n| Format | Quality at q80 | Size vs PNG | Encode Speed | Browser Support |\n|--------|----------------|-------------|--------------|-----------------|\n| WebP | Excellent | -85-90% | Fast | All modern [E1] |\n| AVIF | Excellent | -90-95% | Slow (10x) | Chrome, Firefox, Safari 16+ [E1] |\n| JPEG | Good | -80-85% | Fastest | Universal [E1] |\n| PNG | Lossless | Baseline | Fast | Universal [E1] |\n\n**Decision:** WebP for production (best balance). AVIF if encode time is not an issue and you need minimum size.\n\n---\n\n## 3. Size Budgets [E2]\n\n### Per-Frame Targets\n\n| Resolution | WebP q80 | AVIF q30 | Target per frame |\n|------------|----------|----------|------------------|\n| 960x540 (mobile) | 12-18 KB | 8-12 KB | \u003C15 KB |\n| 1280x720 (tablet) | 18-28 KB | 12-20 KB | \u003C25 KB |\n| 1920x1080 (desktop) | 25-45 KB | 18-30 KB | \u003C35 KB |\n\n### Total Budget\n\n| Frames | Desktop total | Mobile total | Acceptable? |\n|--------|---------------|--------------|-------------|\n| 60 | ~1.5 MB | ~0.7 MB | Great |\n| 120 | ~3.0 MB | ~1.4 MB | Good |\n| 180 | ~4.5 MB | ~2.1 MB | Acceptable |\n| 300 | ~7.5 MB | ~3.5 MB | Heavy, needs lazy load |\n\n**Hard limit:** \u003C5MB total for initial load. Lazy load anything beyond.\n\n---\n\n## 4. Responsive Frame Sets [E2]\n\n### Directory Structure\n\n```\n\u002Fpublic\u002Fframes\u002F\n  \u002Fdesktop\u002F    # 1920x1080, 150 frames\n  \u002Ftablet\u002F     # 1280x720, 120 frames\n  \u002Fmobile\u002F     # 960x540, 75 frames\n```\n\n### FFmpeg Multi-Resolution Script\n\n```bash\n#!\u002Fbin\u002Fbash\nSOURCE=\"source.mp4\"\n\n# Desktop: 1920x1080, 30fps\nmkdir -p frames\u002Fdesktop\nffmpeg -i \"$SOURCE\" -vf \"fps=30,scale=1920:1080\" frames\u002Fdesktop\u002Fframe_%04d.png\n\n# Tablet: 1280x720, 24fps\nmkdir -p frames\u002Ftablet\nffmpeg -i \"$SOURCE\" -vf \"fps=24,scale=1280:720\" frames\u002Ftablet\u002Fframe_%04d.png\n\n# Mobile: 960x540, 15fps\nmkdir -p frames\u002Fmobile\nffmpeg -i \"$SOURCE\" -vf \"fps=15,scale=960:540\" frames\u002Fmobile\u002Fframe_%04d.png\n\n# Convert all to WebP\nfor dir in frames\u002Fdesktop frames\u002Ftablet frames\u002Fmobile; do\n  for f in \"$dir\"\u002F*.png; do\n    cwebp -q 80 \"$f\" -o \"${f%.png}.webp\"\n    rm \"$f\"  # remove PNG after conversion\n  done\ndone\n```\n\n### Responsive Loading (JS)\n\n```js\nfunction getBreakpoint() {\n  const w = window.innerWidth;\n  if (w >= 1280) return { dir: \"desktop\", count: 150 };\n  if (w >= 768)  return { dir: \"tablet\",  count: 120 };\n  return { dir: \"mobile\", count: 75 };\n}\n\nconst { dir, count } = getBreakpoint();\nconst basePath = `\u002Fframes\u002F${dir}\u002Fframe_`;\n```\n\n---\n\n## 5. Sprite Sheet (Alternative) [E2]\n\nFor fewer frames (\u003C60), a single sprite sheet can be faster than individual files:\n\n```bash\n# Create sprite sheet with ImageMagick\nmontage frames\u002Fframe_*.webp -tile 10x6 -geometry 480x270+0+0 spritesheet.webp\n# 60 frames, 10 columns x 6 rows, each 480x270\n```\n\n### CSS Sprite Animation\n\n```css\n.sprite-player {\n  width: 480px;\n  height: 270px;\n  background: url(\"spritesheet.webp\");\n  background-size: 4800px 1620px; \u002F* 10 cols x 6 rows *\u002F\n}\n```\n\n### JS Sprite + Scroll\n\n```js\nconst sprite = document.querySelector(\".sprite-player\");\nconst cols = 10, rows = 6, total = 60;\nconst frameW = 480, frameH = 270;\n\ngsap.to({ frame: 0 }, {\n  frame: total - 1,\n  snap: \"frame\",\n  ease: \"none\",\n  scrollTrigger: {\n    trigger: \"#sprite-section\",\n    start: \"top top\",\n    end: \"+=2000\",\n    pin: true,\n    scrub: 0.5,\n  },\n  onUpdate: function() {\n    const i = Math.round(this.targets()[0].frame);\n    const col = i % cols;\n    const row = Math.floor(i \u002F cols);\n    sprite.style.backgroundPosition = `-${col * frameW}px -${row * frameH}px`;\n  }\n});\n```\n\n---\n\n## 6. Video Scrub (Alternative to Frame Sequence) [E2]\n\nApple's modern approach: single compressed video + scroll-driven playback.\n\n### Optimize Video for Scrub\n\n```bash\n# Encode for web scrub: low bitrate, many keyframes\nffmpeg -i source.mp4 \\\n  -c:v libx264 \\\n  -preset slow \\\n  -crf 23 \\\n  -g 1 \\          # keyframe every frame (critical for scrub)\n  -an \\           # no audio\n  -movflags +faststart \\\n  -vf \"scale=1920:1080\" \\\n  output-scrub.mp4\n```\n\n**Key:** `-g 1` makes every frame a keyframe, enabling instant seeking. File will be larger than normal video but smaller than frame sequence.\n\n### Playback\n\n```js\nconst video = document.getElementById(\"scrub-video\");\n\n\u002F\u002F Ensure video is loaded\nvideo.preload = \"auto\";\n\ngsap.to(video, {\n  currentTime: video.duration || 5, \u002F\u002F fallback if metadata not loaded\n  ease: \"none\",\n  scrollTrigger: {\n    trigger: \"#video-section\",\n    start: \"top top\",\n    end: \"+=4000\",\n    pin: true,\n    scrub: true,\n  }\n});\n\n\u002F\u002F Alternative: manual scroll control\nvideo.addEventListener(\"loadedmetadata\", () => {\n  const section = document.getElementById(\"video-section\");\n  window.addEventListener(\"scroll\", () => {\n    const rect = section.getBoundingClientRect();\n    const progress = Math.max(0, Math.min(1,\n      -rect.top \u002F (rect.height - window.innerHeight)\n    ));\n    video.currentTime = progress * video.duration;\n  });\n});\n```\n\n### Frame Sequence vs Video Scrub\n\n| Factor | Frame Sequence | Video Scrub |\n|--------|---------------|-------------|\n| Smoothness | Best (instant) | Good (may drop on mobile) |\n| File size | 2-5 MB (150 frames) | 1-3 MB (one file) |\n| HTTP requests | 150 requests | 1 request |\n| Memory usage | High (all frames in RAM) | Low (decoded on demand) |\n| Mobile perf | Good | Variable |\n| Complexity | More code | Simpler |\n\n**Decision:** Frame sequence for hero sections where smoothness is critical. Video scrub for secondary content or when bandwidth is limited.\n\n---\n\n## 7. Preloading Strategy [E1]\n\n### Priority Loading\n\n```js\nasync function loadFrames(basePath, count) {\n  const frames = [];\n\n  \u002F\u002F Phase 1: Load first 10 frames immediately (show something fast)\n  const priority = Array.from({ length: 10 }, (_, i) =>\n    loadImage(`${basePath}${String(i + 1).padStart(4, \"0\")}.webp`)\n  );\n  const first10 = await Promise.all(priority);\n  frames.push(...first10);\n\n  \u002F\u002F Phase 2: Load remaining frames in background\n  for (let i = 10; i \u003C count; i++) {\n    const img = new Image();\n    img.src = `${basePath}${String(i + 1).padStart(4, \"0\")}.webp`;\n    frames.push(img);\n  }\n\n  return frames;\n}\n\nfunction loadImage(src) {\n  return new Promise((resolve, reject) => {\n    const img = new Image();\n    img.onload = () => resolve(img);\n    img.onerror = reject;\n    img.src = src;\n  });\n}\n```\n\n### IntersectionObserver Trigger\n\n```js\n\u002F\u002F Only start loading when section is near viewport\nconst observer = new IntersectionObserver(\n  ([entry]) => {\n    if (entry.isIntersecting) {\n      loadFrames(\"\u002Fframes\u002Fdesktop\u002Fframe_\", 150);\n      observer.disconnect();\n    }\n  },\n  { rootMargin: \"200px\" } \u002F\u002F start 200px before visible\n);\nobserver.observe(document.getElementById(\"sequence-section\"));\n```\n\n---\n\n## Workflow\n\n1. **Source video** — get MP4\u002FMOV at highest quality available\n2. **Plan frame count** — duration * fps, aim for 120-180 sweet spot\n3. **Extract frames** — FFmpeg with target resolution and fps\n4. **Convert to WebP** — cwebp q80, check total size budget\n5. **Create responsive sets** — desktop\u002Ftablet\u002Fmobile with different counts\n6. **Implement playback** — Canvas + GSAP ScrollTrigger (or video scrub)\n7. **Add preloading** — priority first 10, lazy rest, IntersectionObserver\n8. **Test on mobile** — check memory usage, reduce frames if needed\n9. **Add fallback** — static image for prefers-reduced-motion\n","# Video-Gen Skill — Frame Sequence Pipeline\n\n## Pipeline Overview\n\n```\nSource Video (MP4\u002FMOV\u002FProRes)\n  │\n  ├─→ [Frame Extraction] FFmpeg → PNG sequence\n  │     │\n  │     ├─→ [Optimize] cwebp → WebP sequence (primary)\n  │     ├─→ [Optimize] avifenc → AVIF sequence (smaller, slower encode)\n  │     └─→ [Sprite Sheet] ImageMagick montage → single image\n  │\n  ├─→ [Video Scrub] FFmpeg re-encode → optimized MP4 for scroll scrub\n  │\n  └─→ [Web Playback] Canvas + ScrollTrigger \u002F video.currentTime\n```\n\n---\n\n## 1. Frame Extraction [E1]\n\n### Basic Extraction\n\n```bash\n# Extract all frames at source FPS\nffmpeg -i source.mp4 -qscale:v 2 frames\u002Fframe_%04d.png\n\n# Extract at specific FPS (30fps → 150 frames for 5s video)\nffmpeg -i source.mp4 -vf \"fps=30\" frames\u002Fframe_%04d.png\n\n# Extract with resolution scaling\nffmpeg -i source.mp4 -vf \"fps=30,scale=1920:1080\" frames\u002Fframe_%04d.png\n\n# Extract specific time range (2s to 7s)\nffmpeg -i source.mp4 -ss 2 -t 5 -vf \"fps=30\" frames\u002Fframe_%04d.png\n```\n\n### Frame Count Guidelines\n\n| Duration | Desktop (30fps) | Mobile (15fps) | Notes |\n|----------|-----------------|----------------|-------|\n| 3 seconds | 90 frames | 45 frames | Short reveal |\n| 5 seconds | 150 frames | 75 frames | Product showcase |\n| 10 seconds | 300 frames | 150 frames | Full story section |\n| 15 seconds | 450 frames | 225 frames | Max recommended |\n\n**Rule:** More frames = smoother but heavier. 120-180 is the sweet spot for most scroll animations.\n\n---\n\n## 2. Format Conversion [E1]\n\n### PNG to WebP (Recommended)\n\n```bash\n# Single file\ncwebp -q 80 frame_0001.png -o frame_0001.webp\n\n# Batch convert all PNGs\nfor f in frames\u002F*.png; do\n  cwebp -q 80 \"$f\" -o \"${f%.png}.webp\"\ndone\n\n# Parallel batch (faster)\nfind frames\u002F -name \"*.png\" | xargs -P 8 -I {} sh -c '\n  cwebp -q 80 \"{}\" -o \"$(echo {} | sed s\u002F.png\u002F.webp\u002F)\"\n'\n```\n\n### PNG to AVIF (Smaller, Slower Encode)\n\n```bash\n# Requires avifenc (brew install libavif)\navifenc --min 20 --max 30 -s 6 frame_0001.png frame_0001.avif\n\n# Batch\nfor f in frames\u002F*.png; do\n  avifenc --min 20 --max 30 -s 6 \"$f\" \"${f%.png}.avif\"\ndone\n```\n\n### Format Comparison\n\n| Format | Quality at q80 | Size vs PNG | Encode Speed | Browser Support |\n|--------|----------------|-------------|--------------|-----------------|\n| WebP | Excellent | -85-90% | Fast | All modern [E1] |\n| AVIF | Excellent | -90-95% | Slow (10x) | Chrome, Firefox, Safari 16+ [E1] |\n| JPEG | Good | -80-85% | Fastest | Universal [E1] |\n| PNG | Lossless | Baseline | Fast | Universal [E1] |\n\n**Decision:** WebP for production (best balance). AVIF if encode time is not an issue and you need minimum size.\n\n---\n\n## 3. Size Budgets [E2]\n\n### Per-Frame Targets\n\n| Resolution | WebP q80 | AVIF q30 | Target per frame |\n|------------|----------|----------|------------------|\n| 960x540 (mobile) | 12-18 KB | 8-12 KB | \u003C15 KB |\n| 1280x720 (tablet) | 18-28 KB | 12-20 KB | \u003C25 KB |\n| 1920x1080 (desktop) | 25-45 KB | 18-30 KB | \u003C35 KB |\n\n### Total Budget\n\n| Frames | Desktop total | Mobile total | Acceptable? |\n|--------|---------------|--------------|-------------|\n| 60 | ~1.5 MB | ~0.7 MB | Great |\n| 120 | ~3.0 MB | ~1.4 MB | Good |\n| 180 | ~4.5 MB | ~2.1 MB | Acceptable |\n| 300 | ~7.5 MB | ~3.5 MB | Heavy, needs lazy load |\n\n**Hard limit:** \u003C5MB total for initial load. Lazy load anything beyond.\n\n---\n\n## 4. Responsive Frame Sets [E2]\n\n### Directory Structure\n\n```\n\u002Fpublic\u002Fframes\u002F\n  \u002Fdesktop\u002F    # 1920x1080, 150 frames\n  \u002Ftablet\u002F     # 1280x720, 120 frames\n  \u002Fmobile\u002F     # 960x540, 75 frames\n```\n\n### FFmpeg Multi-Resolution Script\n\n```bash\n#!\u002Fbin\u002Fbash\nSOURCE=\"source.mp4\"\n\n# Desktop: 1920x1080, 30fps\nmkdir -p frames\u002Fdesktop\nffmpeg -i \"$SOURCE\" -vf \"fps=30,scale=1920:1080\" frames\u002Fdesktop\u002Fframe_%04d.png\n\n# Tablet: 1280x720, 24fps\nmkdir -p frames\u002Ftablet\nffmpeg -i \"$SOURCE\" -vf \"fps=24,scale=1280:720\" frames\u002Ftablet\u002Fframe_%04d.png\n\n# Mobile: 960x540, 15fps\nmkdir -p frames\u002Fmobile\nffmpeg -i \"$SOURCE\" -vf \"fps=15,scale=960:540\" frames\u002Fmobile\u002Fframe_%04d.png\n\n# Convert all to WebP\nfor dir in frames\u002Fdesktop frames\u002Ftablet frames\u002Fmobile; do\n  for f in \"$dir\"\u002F*.png; do\n    cwebp -q 80 \"$f\" -o \"${f%.png}.webp\"\n    rm \"$f\"  # remove PNG after conversion\n  done\ndone\n```\n\n### Responsive Loading (JS)\n\n```js\nfunction getBreakpoint() {\n  const w = window.innerWidth;\n  if (w >= 1280) return { dir: \"desktop\", count: 150 };\n  if (w >= 768)  return { dir: \"tablet\",  count: 120 };\n  return { dir: \"mobile\", count: 75 };\n}\n\nconst { dir, count } = getBreakpoint();\nconst basePath = `\u002Fframes\u002F${dir}\u002Fframe_`;\n```\n\n---\n\n## 5. Sprite Sheet (Alternative) [E2]\n\nFor fewer frames (\u003C60), a single sprite sheet can be faster than individual files:\n\n```bash\n# Create sprite sheet with ImageMagick\nmontage frames\u002Fframe_*.webp -tile 10x6 -geometry 480x270+0+0 spritesheet.webp\n# 60 frames, 10 columns x 6 rows, each 480x270\n```\n\n### CSS Sprite Animation\n\n```css\n.sprite-player {\n  width: 480px;\n  height: 270px;\n  background: url(\"spritesheet.webp\");\n  background-size: 4800px 1620px; \u002F* 10 cols x 6 rows *\u002F\n}\n```\n\n### JS Sprite + Scroll\n\n```js\nconst sprite = document.querySelector(\".sprite-player\");\nconst cols = 10, rows = 6, total = 60;\nconst frameW = 480, frameH = 270;\n\ngsap.to({ frame: 0 }, {\n  frame: total - 1,\n  snap: \"frame\",\n  ease: \"none\",\n  scrollTrigger: {\n    trigger: \"#sprite-section\",\n    start: \"top top\",\n    end: \"+=2000\",\n    pin: true,\n    scrub: 0.5,\n  },\n  onUpdate: function() {\n    const i = Math.round(this.targets()[0].frame);\n    const col = i % cols;\n    const row = Math.floor(i \u002F cols);\n    sprite.style.backgroundPosition = `-${col * frameW}px -${row * frameH}px`;\n  }\n});\n```\n\n---\n\n## 6. Video Scrub (Alternative to Frame Sequence) [E2]\n\nApple's modern approach: single compressed video + scroll-driven playback.\n\n### Optimize Video for Scrub\n\n```bash\n# Encode for web scrub: low bitrate, many keyframes\nffmpeg -i source.mp4 \\\n  -c:v libx264 \\\n  -preset slow \\\n  -crf 23 \\\n  -g 1 \\          # keyframe every frame (critical for scrub)\n  -an \\           # no audio\n  -movflags +faststart \\\n  -vf \"scale=1920:1080\" \\\n  output-scrub.mp4\n```\n\n**Key:** `-g 1` makes every frame a keyframe, enabling instant seeking. File will be larger than normal video but smaller than frame sequence.\n\n### Playback\n\n```js\nconst video = document.getElementById(\"scrub-video\");\n\n\u002F\u002F Ensure video is loaded\nvideo.preload = \"auto\";\n\ngsap.to(video, {\n  currentTime: video.duration || 5, \u002F\u002F fallback if metadata not loaded\n  ease: \"none\",\n  scrollTrigger: {\n    trigger: \"#video-section\",\n    start: \"top top\",\n    end: \"+=4000\",\n    pin: true,\n    scrub: true,\n  }\n});\n\n\u002F\u002F Alternative: manual scroll control\nvideo.addEventListener(\"loadedmetadata\", () => {\n  const section = document.getElementById(\"video-section\");\n  window.addEventListener(\"scroll\", () => {\n    const rect = section.getBoundingClientRect();\n    const progress = Math.max(0, Math.min(1,\n      -rect.top \u002F (rect.height - window.innerHeight)\n    ));\n    video.currentTime = progress * video.duration;\n  });\n});\n```\n\n### Frame Sequence vs Video Scrub\n\n| Factor | Frame Sequence | Video Scrub |\n|--------|---------------|-------------|\n| Smoothness | Best (instant) | Good (may drop on mobile) |\n| File size | 2-5 MB (150 frames) | 1-3 MB (one file) |\n| HTTP requests | 150 requests | 1 request |\n| Memory usage | High (all frames in RAM) | Low (decoded on demand) |\n| Mobile perf | Good | Variable |\n| Complexity | More code | Simpler |\n\n**Decision:** Frame sequence for hero sections where smoothness is critical. Video scrub for secondary content or when bandwidth is limited.\n\n---\n\n## 7. Preloading Strategy [E1]\n\n### Priority Loading\n\n```js\nasync function loadFrames(basePath, count) {\n  const frames = [];\n\n  \u002F\u002F Phase 1: Load first 10 frames immediately (show something fast)\n  const priority = Array.from({ length: 10 }, (_, i) =>\n    loadImage(`${basePath}${String(i + 1).padStart(4, \"0\")}.webp`)\n  );\n  const first10 = await Promise.all(priority);\n  frames.push(...first10);\n\n  \u002F\u002F Phase 2: Load remaining frames in background\n  for (let i = 10; i \u003C count; i++) {\n    const img = new Image();\n    img.src = `${basePath}${String(i + 1).padStart(4, \"0\")}.webp`;\n    frames.push(img);\n  }\n\n  return frames;\n}\n\nfunction loadImage(src) {\n  return new Promise((resolve, reject) => {\n    const img = new Image();\n    img.onload = () => resolve(img);\n    img.onerror = reject;\n    img.src = src;\n  });\n}\n```\n\n### IntersectionObserver Trigger\n\n```js\n\u002F\u002F Only start loading when section is near viewport\nconst observer = new IntersectionObserver(\n  ([entry]) => {\n    if (entry.isIntersecting) {\n      loadFrames(\"\u002Fframes\u002Fdesktop\u002Fframe_\", 150);\n      observer.disconnect();\n    }\n  },\n  { rootMargin: \"200px\" } \u002F\u002F start 200px before visible\n);\nobserver.observe(document.getElementById(\"sequence-section\"));\n```\n\n---\n\n## Workflow\n\n1. **Source video** — get MP4\u002FMOV at highest quality available\n2. **Plan frame count** — duration * fps, aim for 120-180 sweet spot\n3. **Extract frames** — FFmpeg with target resolution and fps\n4. **Convert to WebP** — cwebp q80, check total size budget\n5. **Create responsive sets** — desktop\u002Ftablet\u002Fmobile with different counts\n6. **Implement playback** — Canvas + GSAP ScrollTrigger (or video scrub)\n7. **Add preloading** — priority first 10, lazy rest, IntersectionObserver\n8. **Test on mobile** — check memory usage, reduce frames if needed\n9. **Add fallback** — static image for prefers-reduced-motion\n",{"name":5,"arguments":49,"description":7},[50,53],{"name":31,"required":51,"description":52},true,"Source: video file path, image sequence directory, or AI-generated frames",{"name":27,"required":45,"description":54},"Target: frame-sequence, sprite-sheet, optimized-video, gif (default: frame-sequence)","main","32e85218a997294f68da27ff3dfd638196186ae5","skills\u002Fvideo-gen",8,"https:\u002F\u002Fgithub.com\u002FKeiSeiLab\u002FKeiSeiKit","https:\u002F\u002Fgithub.com\u002FKeiSeiLab","2026-10-11 11:17:46",{"source":8,"skillName":5,"commands":63,"prompt":73,"zipAvailable":51},[64,67,70],{"tool":65,"command":66},"npx","npx skills add KeiSeiLab\u002FKeiSeiKit --skill video-gen",{"tool":68,"command":69},"bunx","bunx skills add KeiSeiLab\u002FKeiSeiKit --skill video-gen",{"tool":71,"command":72},"pnpm","pnpm dlx skills add KeiSeiLab\u002FKeiSeiKit --skill video-gen","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FKeiSeiLab\u002FKeiSeiKit\u002Fblob\u002Fmain\u002Fskills\u002Fvideo-gen\u002FSKILL.md",[],[76,106,130,164,190,206],{"skillNo":77,"name":78,"nameZh":6,"description":79,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":85,"publishedAt":44},"01M71K887N78V089XR5BWT0JAV","wave-audit","3-wave parallel audit — Wave 1 discovery (4 agents), Wave 2 verification (4 agents), Wave 3 cross-optimization (1 synthesizer). Each wave runs agents in parallel. Combines security review, runtime analysis, quality check, and constructor pattern into one unified workflow with evidence grading and Go\u002FNo-Go verdict.","https:\u002F\u002Fgithub.com\u002FKeiSeiLab\u002FKeiSeiKit\u002Fblob\u002Fmain\u002Fskills\u002Fwave-audit\u002FSKILL.md",[82],{"categoryNo":83,"categoryName":84,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105],"wave audit","audit","unified workflow","workflow","analysis","review","verification","security review","pattern","grading","go","security","parallel","evidence","runtime","quality check","discovery","Optimization","one","check",{"skillNo":107,"name":108,"nameZh":6,"description":109,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":110,"categories":111,"keywords":112,"publishedAt":44},"01M71K85100JQS4Y10EGCTWQXW","vm-provision","End-to-end VPS provisioning — select provider → plan → provision → harden → verify (ssh-check + firewall-diff hard-gate) → handoff. 6 phases, ≥6 AskUserQuestion calls, defensive-only. Stops if either verification primitive fails.","https:\u002F\u002Fgithub.com\u002FKeiSeiLab\u002FKeiSeiKit\u002Fblob\u002Fmain\u002Fskills\u002Fvm-provision\u002FSKILL.md",[],[113,114,115,116,117,118,119,120,121,122,104,123,124,105,92,125,126,127,128,129],"vm","vm provision","firewall","diff","provider","select","end","calls","only","either","api","hetzner","plan","defensive","verify","intent","line",{"skillNo":131,"name":132,"nameZh":6,"description":133,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":134,"licenseFileUrl":6,"sourceHtmlUrl":135,"categories":136,"keywords":145,"publishedAt":44},"01M71K80ZRH2H5ES83QS97BB91","visual-explainer","Generate beautiful, self-contained HTML pages that visually explain systems, code changes, plans, and data. Use when the user asks for a diagram, architecture overview, diff review, plan review, project recap, comparison table, or any visual explanation of technical concepts. Also use proactively when you are about to render a complex ASCII table (4+ rows or 3+ columns) — present it as a styled HTML page instead.","MIT","https:\u002F\u002Fgithub.com\u002FKeiSeiLab\u002FKeiSeiKit\u002Fblob\u002Fmain\u002Fskills\u002Fvisual-explainer\u002FSKILL.md",[137,140],{"categoryNo":138,"categoryName":139,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",{"categoryNo":141,"categoryName":142,"taxonomyType":143,"taxonomyTypeDesc":144},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[146,147,148,149,150,36,151,152,125,153,154,155,156,157,158,159,160,161,162,163],"visual","visual explainer","explainer","project","files","you","architecture","generate","present","concepts","table","as","view","for","ascii table","ascii","surf","use",{"skillNo":165,"name":166,"nameZh":6,"description":167,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":168,"categories":169,"keywords":173,"publishedAt":44},"01M71K7R88RM8ENVCRZHZSMM5T","test-matrix","Use when a project needs testing BEYOND unit tests — fuzzing, property-based, load, E2E, or mutation. Five-phase hub-and-spoke pipeline composes the right mix per language × critical path × CI target, scaffolds configs + corpus + fixtures, wires CI jobs, and defines the crash\u002Fregression triage workflow. Pure-click: every decision except intake is an AskUserQuestion.","https:\u002F\u002Fgithub.com\u002FKeiSeiLab\u002FKeiSeiKit\u002Fblob\u002Fmain\u002Fskills\u002Ftest-matrix\u002FSKILL.md",[170],{"categoryNo":171,"categoryName":172,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",[174,175,176,177,27,178,149,179,42,180,181,182,183,184,185,186,187,163,188,189],"test","matrix","test matrix","unit","click","regression","mix","intake","testing","decision","ci","tests","e2e","mutation","load","fuzzing",{"skillNo":191,"name":192,"nameZh":6,"description":193,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":194,"categories":195,"keywords":197,"publishedAt":44},"01M71K7N3ZY9FMMH3CSE1H8K5J","test-gen","Use when generating tests for untested code — happy path, edge cases, error handling","https:\u002F\u002Fgithub.com\u002FKeiSeiLab\u002FKeiSeiKit\u002Fblob\u002Fmain\u002Fskills\u002Ftest-gen\u002FSKILL.md",[196],{"categoryNo":171,"categoryName":172,"taxonomyType":21,"taxonomyTypeDesc":22},[198,24,174,199,27,159,200,185,201,163,37,202,203,204,205,29],"test gen","error","true","edge","file","code","error handling","name",{"skillNo":207,"name":208,"nameZh":6,"description":209,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":210,"categories":211,"keywords":213,"publishedAt":44},"01M71K7JZ57GFDT6PA0H1Z1X84","spawn-agent","Prepare a composed Agent-tool invocation via kei-spawn. Click-only wizard — pick role, describe task, set scope. Emits ready-to-paste prompt + subagent_type + isolation + DNA.","https:\u002F\u002Fgithub.com\u002FKeiSeiLab\u002FKeiSeiKit\u002Fblob\u002Fmain\u002Fskills\u002Fspawn-agent\u002FSKILL.md",[212],{"categoryNo":83,"categoryName":84,"taxonomyType":21,"taxonomyTypeDesc":22},[214,215,216,43,178,217,218,219,220,121,221,222,223,224,225,226,227,228,229,230],"spawn agent","spawn","SET","scope","tool","pick","wizard","prompt","subagent","task","type","DNA","via","paste","describe","role","prepare"]