✓ Verified 💻 Development ✓ Enhanced Data

Dub Youtube With Voiceai

Dub YouTube videos with Voice.ai TTS.

Rating
4.8 (226 reviews)
Downloads
1,262 downloads
Version
1.0.0

Overview

Dub YouTube videos with Voice.ai TTS.

Complete Documentation

View Source →

Dub YouTube with Voice.ai

This skill follows the Agent Skills specification.

Turn any script into a YouTube-ready voiceover — complete with numbered segments, a stitched master, chapter timestamps, SRT captions, and a review page. Drop the voiceover onto an existing video to dub it in one command.

Built for YouTube creators who want studio-quality narration without the studio. Powered by Voice.ai.


When to use this skill

ScenarioWhy it fits
YouTube long-formFull narration with chapter markers and captions
YouTube ShortsQuick hooks with punchy delivery
Course contentProfessional narration for educational videos
Screen recordingsDub a screencast with clean AI voiceover
Quick iterationSmart caching — edit one section, only that segment re-renders
Batch productionSame voice, consistent quality across every video

The one-command workflow

Have a script and a video? Dub it in one shot:

bash
node voiceai-vo.cjs build \
  --input my-script.md \
  --voice oliver \
  --title "My YouTube Video" \
  --video ./my-recording.mp4 \
  --mux \
  --template youtube

This renders the voiceover, stitches the master audio, and drops it onto your video — all in one command. Output:

  • out/my-youtube-video/muxed.mp4 — your video dubbed with the AI voiceover
  • out/my-youtube-video/master.wav — the standalone audio
  • out/my-youtube-video/review.html — listen and review each segment
  • out/my-youtube-video/chapters.txt — paste directly into your YouTube description
  • out/my-youtube-video/captions.srt — upload to YouTube as subtitles
  • out/my-youtube-video/description.txt — ready-made YouTube description with chapters
Use --sync pad if the audio is shorter than the video, or --sync trim to cut it to match.


Requirements

  • Node.js 20+ — runtime (no npm install needed — the CLI is a single bundled file)
  • VOICE_AI_API_KEY — set as environment variable or in a .env file in the skill root. Get a key at voice.ai/dashboard.
  • ffmpeg (optional) — needed for master stitching, MP3 encoding, loudness normalization, and video dubbing. The pipeline still produces individual segments, the review page, chapters, and captions without it.

Configuration

Set VOICE_AI_API_KEY as an environment variable before running:

bash
export VOICE_AI_API_KEY=your-key-here

The skill does not read .env files or access any files for credentials — only the environment variable.

Use --mock on any command to run the full pipeline without an API key (produces placeholder audio).


Commands

build — Generate a YouTube voiceover from a script

bash
node voiceai-vo.cjs build \
  --input <script.md or script.txt> \
  --voice <voice-alias-or-uuid> \
  --title "My YouTube Video" \
  [--template youtube] \
  [--video input.mp4 --mux --sync shortest] \
  [--force] [--mock]

What it does:

  • Reads the script and splits it into segments (by ## headings for .md, or by sentence boundaries for .txt)
  • Optionally prepends/appends YouTube intro/outro segments
  • Renders each segment via Voice.ai TTS
  • Stitches a master audio file (if ffmpeg is available)
  • Generates YouTube chapters, SRT captions, a review page, and a ready-made description
  • Optionally dubs your video with the voiceover
Full options:

OptionDescription
-i, --input Script file (.txt or .md) — required
-v, --voice Voice alias or UUID — required
-t, --title </td><td class="px-4 py-2 text-sm text-gray-700">Video title (defaults to filename)</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">--template youtube</td><td class="px-4 py-2 text-sm text-gray-700">Auto-inject YouTube intro/outro</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">--mode <mode></td><td class="px-4 py-2 text-sm text-gray-700">headings or auto (default: headings for .md)</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">--max-chars <n></td><td class="px-4 py-2 text-sm text-gray-700">Max characters per auto-chunk (default: 1500)</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">--language <code></td><td class="px-4 py-2 text-sm text-gray-700">Language code (default: en)</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">--video <path></td><td class="px-4 py-2 text-sm text-gray-700">Input video to dub</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">--mux</td><td class="px-4 py-2 text-sm text-gray-700">Enable video dubbing (requires --video)</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">--sync <policy></td><td class="px-4 py-2 text-sm text-gray-700">shortest, pad, or trim (default: shortest)</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">--force</td><td class="px-4 py-2 text-sm text-gray-700">Re-render all segments (ignore cache)</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">--mock</td><td class="px-4 py-2 text-sm text-gray-700">Mock mode — no API calls, placeholder audio</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">-o, --out <dir></td><td class="px-4 py-2 text-sm text-gray-700">Custom output directory</td></tr></tbody></table> <h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4"><code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">replace-audio</code> — Dub an existing video</h3></p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">bash</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-bash">node voiceai-vo.cjs replace-audio \ --video ./my-video.mp4 \ --audio ./out/my-video/master.wav \ [--out ./out/my-video/dubbed.mp4] \ [--sync shortest|pad|trim]</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed">Requires ffmpeg. If not installed, generates helper shell/PowerShell scripts instead.</p><p class="my-4 text-gray-700 leading-relaxed"><table class="w-full my-6 border-collapse"><thead><tr><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Sync policy</th><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Behavior</th></tr></thead><tbody><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">shortest (default)</td><td class="px-4 py-2 text-sm text-gray-700">Output ends when the shorter track ends</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">pad</td><td class="px-4 py-2 text-sm text-gray-700">Pad audio with silence to match video duration</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">trim</td><td class="px-4 py-2 text-sm text-gray-700">Trim audio to match video duration</td></tr></tbody></table> Video stream is copied without re-encoding (<code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">-c:v copy</code>). Audio is encoded as AAC for YouTube compatibility.</p><p class="my-4 text-gray-700 leading-relaxed"><strong class="font-semibold">Privacy:</strong> Video processing is entirely local. Only script text is sent to Voice.ai for TTS. Your video files never leave your machine.</p><p class="my-4 text-gray-700 leading-relaxed"><h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4"><code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">voices</code> — List available voices</h3></p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">bash</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-bash">node voiceai-vo.cjs voices [--limit 20] [--query "deep"] [--mock]</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed"><hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">Available voices</h2></p><p class="my-4 text-gray-700 leading-relaxed">Use short aliases or full UUIDs with <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">--voice</code>:</p><p class="my-4 text-gray-700 leading-relaxed"><table class="w-full my-6 border-collapse"><thead><tr><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Alias</th><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Voice</th><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Gender</th><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Best for YouTube</th></tr></thead><tbody><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">ellie</td><td class="px-4 py-2 text-sm text-gray-700">Ellie</td><td class="px-4 py-2 text-sm text-gray-700">F</td><td class="px-4 py-2 text-sm text-gray-700">Vlogs, lifestyle, social content</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">oliver</td><td class="px-4 py-2 text-sm text-gray-700">Oliver</td><td class="px-4 py-2 text-sm text-gray-700">M</td><td class="px-4 py-2 text-sm text-gray-700">Tutorials, narration, explainers</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">lilith</td><td class="px-4 py-2 text-sm text-gray-700">Lilith</td><td class="px-4 py-2 text-sm text-gray-700">F</td><td class="px-4 py-2 text-sm text-gray-700">ASMR, calm walkthroughs</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">smooth</td><td class="px-4 py-2 text-sm text-gray-700">Smooth Calm Voice</td><td class="px-4 py-2 text-sm text-gray-700">M</td><td class="px-4 py-2 text-sm text-gray-700">Documentaries, long-form essays</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">corpse</td><td class="px-4 py-2 text-sm text-gray-700">Corpse Husband</td><td class="px-4 py-2 text-sm text-gray-700">M</td><td class="px-4 py-2 text-sm text-gray-700">Gaming, entertainment</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">skadi</td><td class="px-4 py-2 text-sm text-gray-700">Skadi</td><td class="px-4 py-2 text-sm text-gray-700">F</td><td class="px-4 py-2 text-sm text-gray-700">Anime, character content</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">zhongli</td><td class="px-4 py-2 text-sm text-gray-700">Zhongli</td><td class="px-4 py-2 text-sm text-gray-700">M</td><td class="px-4 py-2 text-sm text-gray-700">Gaming, dramatic intros</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">flora</td><td class="px-4 py-2 text-sm text-gray-700">Flora</td><td class="px-4 py-2 text-sm text-gray-700">F</td><td class="px-4 py-2 text-sm text-gray-700">Kids content, upbeat videos</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">chief</td><td class="px-4 py-2 text-sm text-gray-700">Master Chief</td><td class="px-4 py-2 text-sm text-gray-700">M</td><td class="px-4 py-2 text-sm text-gray-700">Gaming, action trailers</td></tr></tbody></table> The <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">voices</code> command also returns any additional voices available on the API. Voice list is cached for 10 minutes.</p><p class="my-4 text-gray-700 leading-relaxed"><hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">Build outputs</h2></p><p class="my-4 text-gray-700 leading-relaxed">After a build, the output directory contains everything you need to publish on YouTube:</p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">text</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-text">out/<title-slug>/ segments/ # Numbered WAV files (001-intro.wav, 002-section.wav, …) master.wav # Stitched voiceover (requires ffmpeg) master.mp3 # MP3 for upload (requires ffmpeg) muxed.mp4 # Dubbed video (if --video --mux used) chapters.txt # Paste into YouTube description captions.srt # Upload as YouTube subtitles description.txt # Ready-made YouTube description with chapters review.html # Interactive review page with audio players manifest.json # Build metadata: voice, template, segment list timeline.json # Segment durations and start times</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed"><h3 class="text-xl font-semibold text-gray-900 mt-8 mb-4">YouTube workflow</h3> <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-decimal">Run the build command</li> <li class="ml-6 mb-2 text-gray-700 list-decimal">Upload <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">muxed.mp4</code> (or your original video + <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">master.mp3</code> as audio)</li> <li class="ml-6 mb-2 text-gray-700 list-decimal">Paste <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">chapters.txt</code> content into your YouTube description</li> <li class="ml-6 mb-2 text-gray-700 list-decimal">Upload <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">captions.srt</code> as subtitles in YouTube Studio</li> <li class="ml-6 mb-2 text-gray-700 list-decimal">Done — professional narration, chapters, and captions in minutes</li> </ul> <hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">YouTube template</h2></p><p class="my-4 text-gray-700 leading-relaxed">Use <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">--template youtube</code> to auto-inject a branded intro and outro:</p><p class="my-4 text-gray-700 leading-relaxed"><table class="w-full my-6 border-collapse"><thead><tr><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Segment</th><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Source file</th></tr></thead><tbody><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">Intro (prepended)</td><td class="px-4 py-2 text-sm text-gray-700">templates/youtube_intro.txt</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700">Outro (appended)</td><td class="px-4 py-2 text-sm text-gray-700">templates/youtube_outro.txt</td></tr></tbody></table> Edit the files in <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">templates/</code> to customize your channel's branding.</p><p class="my-4 text-gray-700 leading-relaxed"><hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">Caching</h2></p><p class="my-4 text-gray-700 leading-relaxed">Segments are cached by a hash of: <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">text content + voice ID + language</code>. <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-disc">Unchanged segments are <strong class="font-semibold">skipped</strong> on rebuild — fast iteration</li> <li class="ml-6 mb-2 text-gray-700 list-disc">Modified segments are <strong class="font-semibold">re-rendered</strong> automatically</li> <li class="ml-6 mb-2 text-gray-700 list-disc">Use <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">--force</code> to re-render everything</li> <li class="ml-6 mb-2 text-gray-700 list-disc">Cache manifest is stored in <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">segments/.cache.json</code></li> </ul> <hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">Multilingual dubbing</h2></p><p class="my-4 text-gray-700 leading-relaxed">Voice.ai supports 11 languages — dub your YouTube videos for global audiences:</p><p class="my-4 text-gray-700 leading-relaxed"><code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">en</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">es</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">fr</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">de</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">it</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">pt</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">pl</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">ru</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">nl</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">sv</code>, <code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">ca</code></p><p class="my-4 text-gray-700 leading-relaxed"><div class="relative group my-6"> <div class="absolute top-2 right-2 px-2 py-1 text-xs font-medium text-gray-400 bg-gray-800 rounded">bash</div> <pre class="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm leading-relaxed"><code class="language-bash">node voiceai-vo.cjs build \ --input script-spanish.md \ --voice ellie \ --title "Mi Video" \ --language es \ --video ./my-video.mp4 \ --mux</code></pre> </div></p><p class="my-4 text-gray-700 leading-relaxed">The pipeline auto-selects the multilingual TTS model for non-English languages.</p><p class="my-4 text-gray-700 leading-relaxed"><hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">Troubleshooting</h2></p><p class="my-4 text-gray-700 leading-relaxed"><table class="w-full my-6 border-collapse"><thead><tr><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Issue</th><th class="bg-gray-100 px-4 py-2 text-left text-sm font-semibold text-gray-900 border-b-2 border-gray-300">Solution</th></tr></thead><tbody><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700"><strong class="font-semibold">ffmpeg missing</strong></td><td class="px-4 py-2 text-sm text-gray-700">Pipeline still works — you get segments, review page, chapters, captions. Install ffmpeg for stitching and video dubbing.</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700"><strong class="font-semibold">Rate limits (429)</strong></td><td class="px-4 py-2 text-sm text-gray-700">Segments render sequentially, which stays under most limits. Wait and retry.</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700"><strong class="font-semibold">Insufficient credits (402)</strong></td><td class="px-4 py-2 text-sm text-gray-700">Top up at <a href="https://voice.ai/dashboard" class="text-primary-600 hover:text-primary-700 underline" target="_blank" rel="noopener noreferrer">voice.ai/dashboard</a>. Cached segments won't re-use credits on retry.</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700"><strong class="font-semibold">Long scripts</strong></td><td class="px-4 py-2 text-sm text-gray-700">Caching makes rebuilds fast. Text over 490 chars per segment is automatically split across API calls.</td></tr><tr class="border-b border-gray-200 hover:bg-gray-50"><td class="px-4 py-2 text-sm text-gray-700"><strong class="font-semibold">Windows paths</strong></td><td class="px-4 py-2 text-sm text-gray-700">Wrap paths with spaces in quotes: --input "C:\My Scripts\script.md"</td></tr></tbody></table> See <a href="references/TROUBLESHOOTING.md" class="text-primary-600 hover:text-primary-700 underline" target="_blank" rel="noopener noreferrer"><code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">references/TROUBLESHOOTING.md</code></a> for more.</p><p class="my-4 text-gray-700 leading-relaxed"><hr class="my-8 border-t border-gray-300"></p><p class="my-4 text-gray-700 leading-relaxed"><h2 class="text-2xl font-bold text-gray-900 mt-10 mb-6 border-b border-gray-200 pb-2">References</h2> <ul class="my-4 space-y-2"><li class="ml-6 mb-2 text-gray-700 list-disc"><a href="https://agentskills.io/specification" class="text-primary-600 hover:text-primary-700 underline" target="_blank" rel="noopener noreferrer">Agent Skills Specification</a></li> <li class="ml-6 mb-2 text-gray-700 list-disc"><a href="https://voice.ai" class="text-primary-600 hover:text-primary-700 underline" target="_blank" rel="noopener noreferrer">Voice.ai</a></li> <li class="ml-6 mb-2 text-gray-700 list-disc"><a href="references/VOICEAI_API.md" class="text-primary-600 hover:text-primary-700 underline" target="_blank" rel="noopener noreferrer"><code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">references/VOICEAI_API.md</code></a> — API endpoints, audio formats, models</li> <li class="ml-6 mb-2 text-gray-700 list-disc"><a href="references/TROUBLESHOOTING.md" class="text-primary-600 hover:text-primary-700 underline" target="_blank" rel="noopener noreferrer"><code class="px-1.5 py-0.5 text-sm text-primary-700 bg-primary-50 rounded font-mono border border-primary-200">references/TROUBLESHOOTING.md</code></a> — Common issues and fixes</li> </ul></p></div> </div> </div> <!-- Installation --> <section class="card" aria-labelledby="installation-heading" data-astro-cid-jrlgpo3w> <h2 id="installation-heading" class="text-2xl font-bold text-gray-900 mb-4" data-astro-cid-jrlgpo3w>Installation</h2> <div class="code-block-container relative group " data-astro-cid-i4kugh4e> <div class="flex items-center justify-between px-4 py-2 bg-gray-800 border-b border-gray-700 rounded-t-lg" data-astro-cid-i4kugh4e> <span class="text-sm text-gray-300 font-mono" data-astro-cid-i4kugh4e>Terminal</span> <span class="text-xs text-gray-500 uppercase" data-astro-cid-i4kugh4e>bash</span> </div> <div class="relative" data-astro-cid-i4kugh4e> <pre class="!mt-0 !rounded-t-none" data-astro-cid-i4kugh4e><code id="code-zadjq08dz" class="language-bash" data-astro-cid-i4kugh4e> openclaw install dub-youtube-with-voiceai </code></pre> <!-- Copy Button --> <button class="copy-button absolute top-2 right-2 p-2 bg-gray-700 hover:bg-gray-600 text-gray-300 rounded-lg transition-all duration-200 opacity-0 group-hover:opacity-100 focus:opacity-100" data-code-id="code-zadjq08dz" aria-label="Copy code to clipboard" data-astro-cid-i4kugh4e> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" data-astro-cid-i4kugh4e> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" data-astro-cid-i4kugh4e></path> </svg> <span class="copy-text sr-only" data-astro-cid-i4kugh4e>Copy</span> </button> <!-- Copied Indicator --> <div class="copied-indicator absolute top-2 right-14 px-3 py-1.5 bg-green-600 text-white text-sm rounded-lg opacity-0 transition-opacity duration-200 pointer-events-none" data-astro-cid-i4kugh4e> Copied! </div> </div> </div> <script>(function(){const codeId = "code-zadjq08dz"; const copyButton = document.querySelector(`[data-code-id="${codeId}"]`); const copiedIndicator = document.querySelector('.copied-indicator'); if (copyButton) { copyButton.addEventListener('click', async () => { const codeElement = document.getElementById(codeId); const code = codeElement?.textContent || ''; try { await navigator.clipboard.writeText(code); // Show copied state copyButton.classList.add('copied'); copiedIndicator?.classList.remove('opacity-0'); // Reset after 2 seconds setTimeout(() => { copyButton.classList.remove('copied'); copiedIndicator?.classList.add('opacity-0'); }, 2000); } catch (err) { console.error('Failed to copy code:', err); } }); } })();</script> </section> <!-- Verification Command (if available from enhanced data) --> <!-- Code Examples (if available) --> <div class="card" data-astro-cid-cpq6uwpo><h2 class="text-2xl font-bold text-gray-900 mb-6 flex items-center" data-astro-cid-cpq6uwpo><span class="text-2xl mr-2" data-astro-cid-cpq6uwpo>💻</span>Code Examples</h2><div class="space-y-6" data-astro-cid-cpq6uwpo><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>--template youtube</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>---template-youtube.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>This renders the voiceover, stitches the master audio, and drops it onto your video — all in one command. Output: - `out/my-youtube-video/muxed.mp4` — your video dubbed with the AI voiceover - `out/my-youtube-video/master.wav` — the standalone audio - `out/my-youtube-video/review.html` — listen and review each segment - `out/my-youtube-video/chapters.txt` — paste directly into your YouTube description - `out/my-youtube-video/captions.srt` — upload to YouTube as subtitles - `out/my-youtube-video/description.txt` — ready-made YouTube description with chapters Use `--sync pad` if the audio is shorter than the video, or `--sync trim` to cut it to match. --- ## Requirements - **Node.js 20+** — runtime (no npm install needed — the CLI is a single bundled file) - **VOICE_AI_API_KEY** — set as environment variable or in a `.env` file in the skill root. Get a key at [voice.ai/dashboard](https://voice.ai/dashboard). - **ffmpeg** (optional) — needed for master stitching, MP3 encoding, loudness normalization, and video dubbing. The pipeline still produces individual segments, the review page, chapters, and captions without it. --- ## Configuration Set `VOICE_AI_API_KEY` as an environment variable before running:</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>export VOICE_AI_API_KEY=your-key-here</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>export-voiceaiapikeyyour-key-here.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>The skill does not read `.env` files or access any files for credentials — only the environment variable. Use `--mock` on any command to run the full pipeline without an API key (produces placeholder audio). --- ## Commands ### `build` — Generate a YouTube voiceover from a script</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>[--force] [--mock]</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>---force---mock.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>**What it does:** 1. Reads the script and splits it into segments (by `##` headings for `.md`, or by sentence boundaries for `.txt`) 2. Optionally prepends/appends YouTube intro/outro segments 3. Renders each segment via Voice.ai TTS 4. Stitches a master audio file (if ffmpeg is available) 5. Generates YouTube chapters, SRT captions, a review page, and a ready-made description 6. Optionally dubs your video with the voiceover **Full options:** | Option | Description | |---|---| | `-i, --input <path>` | Script file (.txt or .md) — **required** | | `-v, --voice <id>` | Voice alias or UUID — **required** | | `-t, --title <title>` | Video title (defaults to filename) | | `--template youtube` | Auto-inject YouTube intro/outro | | `--mode <mode>` | `headings` or `auto` (default: headings for .md) | | `--max-chars <n>` | Max characters per auto-chunk (default: 1500) | | `--language <code>` | Language code (default: en) | | `--video <path>` | Input video to dub | | `--mux` | Enable video dubbing (requires --video) | | `--sync <policy>` | `shortest`, `pad`, or `trim` (default: shortest) | | `--force` | Re-render all segments (ignore cache) | | `--mock` | Mock mode — no API calls, placeholder audio | | `-o, --out <dir>` | Custom output directory | ### `replace-audio` — Dub an existing video</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>[--sync shortest|pad|trim]</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>---sync-shortestpadtrim.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>Requires ffmpeg. If not installed, generates helper shell/PowerShell scripts instead. | Sync policy | Behavior | |---|---| | `shortest` (default) | Output ends when the shorter track ends | | `pad` | Pad audio with silence to match video duration | | `trim` | Trim audio to match video duration | Video stream is copied without re-encoding (`-c:v copy`). Audio is encoded as AAC for YouTube compatibility. **Privacy:** Video processing is entirely local. Only script text is sent to Voice.ai for TTS. Your video files never leave your machine. ### `voices` — List available voices</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>node voiceai-vo.cjs voices [--limit 20] [--query "deep"] [--mock]</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>node-voiceai-vocjs-voices---limit-20---query-deep---mock.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>--- ## Available voices Use short aliases or full UUIDs with `--voice`: | Alias | Voice | Gender | Best for YouTube | |----------|----------------------|--------|-----------------------------------| | `ellie` | Ellie | F | Vlogs, lifestyle, social content | | `oliver` | Oliver | M | Tutorials, narration, explainers | | `lilith` | Lilith | F | ASMR, calm walkthroughs | | `smooth` | Smooth Calm Voice | M | Documentaries, long-form essays | | `corpse` | Corpse Husband | M | Gaming, entertainment | | `skadi` | Skadi | F | Anime, character content | | `zhongli`| Zhongli | M | Gaming, dramatic intros | | `flora` | Flora | F | Kids content, upbeat videos | | `chief` | Master Chief | M | Gaming, action trailers | The `voices` command also returns any additional voices available on the API. Voice list is cached for 10 minutes. --- ## Build outputs After a build, the output directory contains everything you need to publish on YouTube:</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><h3 class="text-lg font-semibold text-gray-900 mb-2" data-astro-cid-cpq6uwpo>timeline.json # Segment durations and start times</h3><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>-timelinejson--segment-durations-and-start-times.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>### YouTube workflow 1. Run the build command 2. Upload `muxed.mp4` (or your original video + `master.mp3` as audio) 3. Paste `chapters.txt` content into your YouTube description 4. Upload `captions.srt` as subtitles in YouTube Studio 5. Done — professional narration, chapters, and captions in minutes --- ## YouTube template Use `--template youtube` to auto-inject a branded intro and outro: | Segment | Source file | |---|---| | Intro (prepended) | `templates/youtube_intro.txt` | | Outro (appended) | `templates/youtube_outro.txt` | Edit the files in `templates/` to customize your channel's branding. --- ## Caching Segments are cached by a hash of: `text content + voice ID + language`. - Unchanged segments are **skipped** on rebuild — fast iteration - Modified segments are **re-rendered** automatically - Use `--force` to re-render everything - Cache manifest is stored in `segments/.cache.json` --- ## Multilingual dubbing Voice.ai supports 11 languages — dub your YouTube videos for global audiences: `en`, `es`, `fr`, `de`, `it`, `pt`, `pl`, `ru`, `nl`, `sv`, `ca`</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>example.sh</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-bash" data-astro-cid-cpq6uwpo>node voiceai-vo.cjs build \ --input my-script.md \ --voice oliver \ --title "My YouTube Video" \ --video ./my-recording.mp4 \ --mux \ --template youtube</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>example.sh</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-bash" data-astro-cid-cpq6uwpo>node voiceai-vo.cjs build \ --input <script.md or script.txt> \ --voice <voice-alias-or-uuid> \ --title "My YouTube Video" \ [--template youtube] \ [--video input.mp4 --mux --sync shortest] \ [--force] [--mock]</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>example.sh</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-bash" data-astro-cid-cpq6uwpo>node voiceai-vo.cjs replace-audio \ --video ./my-video.mp4 \ --audio ./out/my-video/master.wav \ [--out ./out/my-video/dubbed.mp4] \ [--sync shortest|pad|trim]</code></pre></div></div><div class="code-example" data-astro-cid-cpq6uwpo><div class="relative" data-astro-cid-cpq6uwpo><div class="flex items-center justify-between bg-gray-800 text-gray-300 px-4 py-2 rounded-t-lg text-sm" data-astro-cid-cpq6uwpo><span class="font-mono" data-astro-cid-cpq6uwpo>example.txt</span><button class="copy-btn px-3 py-1 bg-primary-600 hover:bg-primary-700 text-white rounded text-xs font-medium transition-colors" data-astro-cid-cpq6uwpo> Copy Code </button></div><pre class="bg-gray-900 text-gray-100 p-4 rounded-b-lg overflow-x-auto text-sm leading-relaxed" data-astro-cid-cpq6uwpo><code class="language-text" data-astro-cid-cpq6uwpo>out/<title-slug>/ segments/ # Numbered WAV files (001-intro.wav, 002-section.wav, …) master.wav # Stitched voiceover (requires ffmpeg) master.mp3 # MP3 for upload (requires ffmpeg) muxed.mp4 # Dubbed video (if --video --mux used) chapters.txt # Paste into YouTube description captions.srt # Upload as YouTube subtitles description.txt # Ready-made YouTube description with chapters review.html # Interactive review page with audio players manifest.json # Build metadata: voice, template, segment list timeline.json # Segment durations and start times</code></pre></div></div></div></div> <!-- Configuration Table (if available) --> <div class="card"><h2 class="text-2xl font-bold text-gray-900 mb-6 flex items-center"><span class="text-2xl mr-2">⚙️</span>Configuration Options</h2><div class="overflow-x-auto"><table class="w-full"><thead><tr class="border-b-2 border-gray-300"><th class="text-left py-3 px-4 text-sm font-semibold text-gray-900 bg-gray-50 rounded-tl-lg"> Option </th><th class="text-left py-3 px-4 text-sm font-semibold text-gray-900 bg-gray-50"> Type </th><th class="text-left py-3 px-4 text-sm font-semibold text-gray-900 bg-gray-50"> Default </th><th class="text-left py-3 px-4 text-sm font-semibold text-gray-900 bg-gray-50 rounded-tr-lg"> Description </th></tr></thead><tbody><tr class="border-b border-gray-200 hover:bg-gray-50 transition-colors"><td class="py-3 px-4"><code class="px-2 py-1 text-sm font-mono text-primary-700 bg-primary-50 rounded border border-primary-200">VOICE_AI_API_KEY</code></td><td class="py-3 px-4 text-sm text-gray-600">string</td><td class="py-3 px-4"><code class="px-2 py-1 text-xs font-mono text-gray-700 bg-gray-100 rounded">your-key-here</code></td><td class="py-3 px-4 text-sm text-gray-700">-</td></tr></tbody></table></div></div> <!-- Requirements --> <!-- Code Example --> <!-- Testing Notes --> <!-- Tags --> <div class="card" data-astro-cid-jrlgpo3w> <h2 class="text-2xl font-bold text-gray-900 mb-4" data-astro-cid-jrlgpo3w>Tags</h2> <div class="flex flex-wrap gap-2" data-astro-cid-jrlgpo3w> <span class="px-3 py-1.5 text-sm text-gray-600 bg-gray-50 rounded-lg border border-gray-200" data-astro-cid-jrlgpo3w> #coding_agents-and-ides </span> </div> </div> </div> <!-- Sidebar --> <div class="space-y-6" data-astro-cid-jrlgpo3w> <!-- Quick Info --> <div class="card" data-astro-cid-jrlgpo3w> <h3 class="font-semibold text-gray-900 mb-4" data-astro-cid-jrlgpo3w>Quick Info</h3> <div class="space-y-3 text-sm" data-astro-cid-jrlgpo3w> <div class="flex justify-between" data-astro-cid-jrlgpo3w> <span class="text-gray-600" data-astro-cid-jrlgpo3w>Category</span> <span class="font-medium text-gray-900" data-astro-cid-jrlgpo3w>Development</span> </div> <div class="flex justify-between" data-astro-cid-jrlgpo3w> <span class="text-gray-600" data-astro-cid-jrlgpo3w>Model</span> <span class="font-medium text-gray-900" data-astro-cid-jrlgpo3w>Claude 3.5</span> </div> <div class="flex justify-between" data-astro-cid-jrlgpo3w> <span class="text-gray-600" data-astro-cid-jrlgpo3w>Complexity</span> <span class="font-medium text-gray-900" data-astro-cid-jrlgpo3w>One-Click</span> </div> <div class="flex justify-between" data-astro-cid-jrlgpo3w> <span class="text-gray-600" data-astro-cid-jrlgpo3w>Author</span> <span class="font-medium text-gray-900" data-astro-cid-jrlgpo3w>gizmogremlin</span> </div> <div class="flex justify-between" data-astro-cid-jrlgpo3w> <span class="text-gray-600" data-astro-cid-jrlgpo3w>Last Updated</span> <span class="font-medium text-gray-900" data-astro-cid-jrlgpo3w>3/10/2026</span> </div> </div> </div> <!-- Model Badge --> <div class="card bg-gradient-to-br from-blue-50 to-indigo-50 border-blue-100" data-astro-cid-jrlgpo3w> <div class="flex items-center justify-between" data-astro-cid-jrlgpo3w> <div class="flex items-center space-x-3" data-astro-cid-jrlgpo3w> <div class="text-3xl" data-astro-cid-jrlgpo3w>🚀</div> <div data-astro-cid-jrlgpo3w> <div class="text-sm text-gray-600" data-astro-cid-jrlgpo3w>Optimized for</div> <div class="font-semibold text-gray-900" data-astro-cid-jrlgpo3w>Claude 3.5</div> </div> </div> <span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg bg-purple-50 text-purple-700 border border-purple-200 hover:bg-purple-100 transition-colors" title="Optimized for Claude 3.5 Sonnet" data-astro-cid-3xlfahoe> <span data-astro-cid-3xlfahoe>🧠</span> </span> </div> </div> <!-- Install CTA --> <div class="card bg-gradient-to-br from-primary-500 to-primary-600 text-white border-0" data-astro-cid-jrlgpo3w> <h3 class="font-semibold text-lg mb-2" data-astro-cid-jrlgpo3w>Ready to Install?</h3> <p class="text-primary-100 text-sm mb-4" data-astro-cid-jrlgpo3w> Get started with this skill in seconds </p> <div class="bg-white/20 backdrop-blur-sm rounded-lg p-3 font-mono text-sm" data-astro-cid-jrlgpo3w> openclaw install dub-youtube-with-voiceai </div> </div> <!-- External Links (if available) --> <div class="card" data-astro-cid-jrlgpo3w> <h3 class="font-semibold text-gray-900 mb-4" data-astro-cid-jrlgpo3w>Resources</h3> <div class="space-y-3" data-astro-cid-jrlgpo3w> <a href="https://github.com/openclaw/skills/tree/main/skills/gizmogremlin/dub-youtube-with-voiceai/SKILL.md" target="_blank" rel="noopener noreferrer" class="flex items-center justify-between p-3 rounded-lg border border-gray-200 hover:border-primary-300 hover:bg-primary-50 transition-colors" data-astro-cid-jrlgpo3w> <div class="flex items-center space-x-3" data-astro-cid-jrlgpo3w> <div class="text-2xl" data-astro-cid-jrlgpo3w>📂</div> <div data-astro-cid-jrlgpo3w> <div class="font-medium text-gray-900" data-astro-cid-jrlgpo3w>OpenClaw Skills</div> <div class="text-xs text-gray-500" data-astro-cid-jrlgpo3w>View on OpenClaw GitHub</div> </div> </div> <span class="text-primary-600" data-astro-cid-jrlgpo3w>→</span> </a> </div> </div> </div> </div> </div> </section> <section class="section bg-gray-50" data-astro-cid-jrlgpo3w> <div class="container" data-astro-cid-jrlgpo3w> <h2 class="text-2xl font-bold text-gray-900 mb-6" data-astro-cid-jrlgpo3w>Related Skills</h2> <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6" data-astro-cid-jrlgpo3w> <a href="/skills/4claw/" class="skill-card group block" data-astro-cid-rwasicqo> <!-- Header --> <div class="flex items-start justify-between mb-4" data-astro-cid-rwasicqo> <div class="flex-1" data-astro-cid-rwasicqo> <div class="flex items-center space-x-2 mb-2" data-astro-cid-rwasicqo> <span class="inline-flex items-center px-2 py-0.5 text-xs font-semibold text-green-700 bg-green-50 rounded-full border border-green-200" data-astro-cid-rwasicqo> ✓ Verified </span> <span class="inline-flex items-center px-2 py-0.5 text-xs font-medium text-gray-600 bg-gray-100 rounded-full" data-astro-cid-rwasicqo> 💻 Development </span> </div> <h3 class="text-lg font-semibold text-gray-900 group-hover:text-primary-600 transition-colors" data-astro-cid-rwasicqo> 4claw </h3> </div> </div> <!-- Description --> <p class="text-sm text-gray-600 mb-4 line-clamp-2" data-astro-cid-rwasicqo> 4claw — a moderated imageboard for AI agents. </p> <!-- Badges --> <div class="flex flex-wrap gap-2 mb-4" data-astro-cid-rwasicqo> <span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg bg-purple-50 text-purple-700 border border-purple-200 hover:bg-purple-100 transition-colors" title="Optimized for Claude 3.5 Sonnet" data-astro-cid-3xlfahoe> <span data-astro-cid-3xlfahoe>🧠</span> <span data-astro-cid-3xlfahoe>Claude-Ready</span> </span> <span class="inline-flex items-center px-2.5 py-1 text-xs font-medium rounded-lg class={complexityInfo.level === 1 ? 'text-green-700 bg-green-50' : complexityInfo.level === 2 ? 'text-blue-700 bg-blue-50' : 'text-purple-700 bg-purple-50'}> {complexityInfo.level === 1 ? '⚡' : complexityInfo.level === 2 ? '🔗' : '🧠'} {complexityInfo.name} </span> </div> <!-- Tags --> {skill.tags.length > 0 && ( <div class=" flex flex-wrap gap-1.5 mb-4" data-astro-cid-rwasicqo> <span class="inline-block px-2 py-1 text-xs text-gray-500 bg-gray-50 rounded border border-gray-100" data-astro-cid-rwasicqo> #ai_and-llms </span> </span></div> )} <!-- Footer Stats --> <div class="flex items-center justify-between pt-4 border-t border-gray-100" data-astro-cid-rwasicqo> <div class="flex items-center space-x-4 text-sm text-gray-500" data-astro-cid-rwasicqo> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span class="text-yellow-500" data-astro-cid-rwasicqo>★</span> <span class="font-medium" data-astro-cid-rwasicqo>4.4</span> <span class="text-xs" data-astro-cid-rwasicqo>(118)</span> </div> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span data-astro-cid-rwasicqo>↓</span> <span class="font-medium" data-astro-cid-rwasicqo>4,990</span> </div> </div> <div class="text-xs text-gray-400" data-astro-cid-rwasicqo> v1.0.0 </div> </div> </a> <a href="/skills/aap-passport/" class="skill-card group block" data-astro-cid-rwasicqo> <!-- Header --> <div class="flex items-start justify-between mb-4" data-astro-cid-rwasicqo> <div class="flex-1" data-astro-cid-rwasicqo> <div class="flex items-center space-x-2 mb-2" data-astro-cid-rwasicqo> <span class="inline-flex items-center px-2 py-0.5 text-xs font-semibold text-green-700 bg-green-50 rounded-full border border-green-200" data-astro-cid-rwasicqo> ✓ Verified </span> <span class="inline-flex items-center px-2 py-0.5 text-xs font-medium text-gray-600 bg-gray-100 rounded-full" data-astro-cid-rwasicqo> 💻 Development </span> </div> <h3 class="text-lg font-semibold text-gray-900 group-hover:text-primary-600 transition-colors" data-astro-cid-rwasicqo> Aap Passport </h3> </div> </div> <!-- Description --> <p class="text-sm text-gray-600 mb-4 line-clamp-2" data-astro-cid-rwasicqo> Agent Attestation Protocol - The Reverse Turing Test. </p> <!-- Badges --> <div class="flex flex-wrap gap-2 mb-4" data-astro-cid-rwasicqo> <span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg bg-purple-50 text-purple-700 border border-purple-200 hover:bg-purple-100 transition-colors" title="Optimized for Claude 3.5 Sonnet" data-astro-cid-3xlfahoe> <span data-astro-cid-3xlfahoe>🧠</span> <span data-astro-cid-3xlfahoe>Claude-Ready</span> </span> <span class="inline-flex items-center px-2.5 py-1 text-xs font-medium rounded-lg class={complexityInfo.level === 1 ? 'text-green-700 bg-green-50' : complexityInfo.level === 2 ? 'text-blue-700 bg-blue-50' : 'text-purple-700 bg-purple-50'}> {complexityInfo.level === 1 ? '⚡' : complexityInfo.level === 2 ? '🔗' : '🧠'} {complexityInfo.name} </span> </div> <!-- Tags --> {skill.tags.length > 0 && ( <div class=" flex flex-wrap gap-1.5 mb-4" data-astro-cid-rwasicqo> <span class="inline-block px-2 py-1 text-xs text-gray-500 bg-gray-50 rounded border border-gray-100" data-astro-cid-rwasicqo> #ai_and-llms </span> </span></div> )} <!-- Footer Stats --> <div class="flex items-center justify-between pt-4 border-t border-gray-100" data-astro-cid-rwasicqo> <div class="flex items-center space-x-4 text-sm text-gray-500" data-astro-cid-rwasicqo> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span class="text-yellow-500" data-astro-cid-rwasicqo>★</span> <span class="font-medium" data-astro-cid-rwasicqo>4.3</span> <span class="text-xs" data-astro-cid-rwasicqo>(89)</span> </div> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span data-astro-cid-rwasicqo>↓</span> <span class="font-medium" data-astro-cid-rwasicqo>4,621</span> </div> </div> <div class="text-xs text-gray-400" data-astro-cid-rwasicqo> v1.0.0 </div> </div> </a> <a href="/skills/acestep-lyrics-transcription/" class="skill-card group block" data-astro-cid-rwasicqo> <!-- Header --> <div class="flex items-start justify-between mb-4" data-astro-cid-rwasicqo> <div class="flex-1" data-astro-cid-rwasicqo> <div class="flex items-center space-x-2 mb-2" data-astro-cid-rwasicqo> <span class="inline-flex items-center px-2 py-0.5 text-xs font-semibold text-green-700 bg-green-50 rounded-full border border-green-200" data-astro-cid-rwasicqo> ✓ Verified </span> <span class="inline-flex items-center px-2 py-0.5 text-xs font-medium text-gray-600 bg-gray-100 rounded-full" data-astro-cid-rwasicqo> 💻 Development </span> </div> <h3 class="text-lg font-semibold text-gray-900 group-hover:text-primary-600 transition-colors" data-astro-cid-rwasicqo> Acestep Lyrics Transcription </h3> </div> </div> <!-- Description --> <p class="text-sm text-gray-600 mb-4 line-clamp-2" data-astro-cid-rwasicqo> Transcribe audio to timestamped lyrics using OpenAI Whisper or ElevenLabs Scribe API. </p> <!-- Badges --> <div class="flex flex-wrap gap-2 mb-4" data-astro-cid-rwasicqo> <span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg bg-green-50 text-emerald-700 border border-emerald-200 hover:bg-green-100 transition-colors" title="Works best with GPT-4 and GPT-4 Turbo" data-astro-cid-3xlfahoe> <span data-astro-cid-3xlfahoe>⚡</span> <span data-astro-cid-3xlfahoe>GPT-Optimized</span> </span> <span class="inline-flex items-center px-2.5 py-1 text-xs font-medium rounded-lg class={complexityInfo.level === 1 ? 'text-green-700 bg-green-50' : complexityInfo.level === 2 ? 'text-blue-700 bg-blue-50' : 'text-purple-700 bg-purple-50'}> {complexityInfo.level === 1 ? '⚡' : complexityInfo.level === 2 ? '🔗' : '🧠'} {complexityInfo.name} </span> </div> <!-- Tags --> {skill.tags.length > 0 && ( <div class=" flex flex-wrap gap-1.5 mb-4" data-astro-cid-rwasicqo> <span class="inline-block px-2 py-1 text-xs text-gray-500 bg-gray-50 rounded border border-gray-100" data-astro-cid-rwasicqo> #ai_and-llms </span><span class="inline-block px-2 py-1 text-xs text-gray-500 bg-gray-50 rounded border border-gray-100" data-astro-cid-rwasicqo> #api </span><span class="inline-block px-2 py-1 text-xs text-gray-500 bg-gray-50 rounded border border-gray-100" data-astro-cid-rwasicqo> #script </span> </span></div> )} <!-- Footer Stats --> <div class="flex items-center justify-between pt-4 border-t border-gray-100" data-astro-cid-rwasicqo> <div class="flex items-center space-x-4 text-sm text-gray-500" data-astro-cid-rwasicqo> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span class="text-yellow-500" data-astro-cid-rwasicqo>★</span> <span class="font-medium" data-astro-cid-rwasicqo>3.8</span> <span class="text-xs" data-astro-cid-rwasicqo>(274)</span> </div> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span data-astro-cid-rwasicqo>↓</span> <span class="font-medium" data-astro-cid-rwasicqo>17,648</span> </div> </div> <div class="text-xs text-gray-400" data-astro-cid-rwasicqo> v1.0.0 </div> </div> </a> <a href="/skills/adaptive-suite/" class="skill-card group block" data-astro-cid-rwasicqo> <!-- Header --> <div class="flex items-start justify-between mb-4" data-astro-cid-rwasicqo> <div class="flex-1" data-astro-cid-rwasicqo> <div class="flex items-center space-x-2 mb-2" data-astro-cid-rwasicqo> <span class="inline-flex items-center px-2 py-0.5 text-xs font-semibold text-green-700 bg-green-50 rounded-full border border-green-200" data-astro-cid-rwasicqo> ✓ Verified </span> <span class="inline-flex items-center px-2 py-0.5 text-xs font-medium text-gray-600 bg-gray-100 rounded-full" data-astro-cid-rwasicqo> 💻 Development </span> </div> <h3 class="text-lg font-semibold text-gray-900 group-hover:text-primary-600 transition-colors" data-astro-cid-rwasicqo> Adaptive Suite </h3> </div> </div> <!-- Description --> <p class="text-sm text-gray-600 mb-4 line-clamp-2" data-astro-cid-rwasicqo> A continuously adaptive skill suite that empowers Clawdbot. </p> <!-- Badges --> <div class="flex flex-wrap gap-2 mb-4" data-astro-cid-rwasicqo> <span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg bg-purple-50 text-purple-700 border border-purple-200 hover:bg-purple-100 transition-colors" title="Optimized for Claude 3.5 Sonnet" data-astro-cid-3xlfahoe> <span data-astro-cid-3xlfahoe>🧠</span> <span data-astro-cid-3xlfahoe>Claude-Ready</span> </span> <span class="inline-flex items-center px-2.5 py-1 text-xs font-medium rounded-lg class={complexityInfo.level === 1 ? 'text-green-700 bg-green-50' : complexityInfo.level === 2 ? 'text-blue-700 bg-blue-50' : 'text-purple-700 bg-purple-50'}> {complexityInfo.level === 1 ? '⚡' : complexityInfo.level === 2 ? '🔗' : '🧠'} {complexityInfo.name} </span> </div> <!-- Tags --> {skill.tags.length > 0 && ( <div class=" flex flex-wrap gap-1.5 mb-4" data-astro-cid-rwasicqo> <span class="inline-block px-2 py-1 text-xs text-gray-500 bg-gray-50 rounded border border-gray-100" data-astro-cid-rwasicqo> #ai_and-llms </span><span class="inline-block px-2 py-1 text-xs text-gray-500 bg-gray-50 rounded border border-gray-100" data-astro-cid-rwasicqo> #bot </span> </span></div> )} <!-- Footer Stats --> <div class="flex items-center justify-between pt-4 border-t border-gray-100" data-astro-cid-rwasicqo> <div class="flex items-center space-x-4 text-sm text-gray-500" data-astro-cid-rwasicqo> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span class="text-yellow-500" data-astro-cid-rwasicqo>★</span> <span class="font-medium" data-astro-cid-rwasicqo>4.7</span> <span class="text-xs" data-astro-cid-rwasicqo>(88)</span> </div> <div class="flex items-center space-x-1" data-astro-cid-rwasicqo> <span data-astro-cid-rwasicqo>↓</span> <span class="font-medium" data-astro-cid-rwasicqo>1,625</span> </div> </div> <div class="text-xs text-gray-400" data-astro-cid-rwasicqo> v1.0.0 </div> </div> </a> </div> </div> </section> </main> <footer class="bg-gray-900 text-gray-300"> <div class="container py-16"> <div class="grid grid-cols-2 md:grid-cols-6 gap-8 mb-12"> <!-- Brand Column --> <div class="col-span-2"> <a href="/" class="flex items-center mb-4"> <!-- SVG Logo --> <img src="/logo.svg" alt="AICLawSkills Logo" class="h-10 w-auto rounded-lg"> </a> <p class="text-sm text-gray-400 mb-6 max-w-sm"> The comprehensive skill library for OpenClaw AI automation platform. Explore verified skills and boost your productivity. </p> <!-- Social Proof Stats --> <div class="mb-6 grid grid-cols-3 gap-4"> <div class="text-center"> <div class="text-2xl font-bold text-white">100%</div> <div class="text-xs text-gray-400">Verified Skills</div> </div> <div class="text-center"> <div class="text-2xl font-bold text-white">Free</div> <div class="text-xs text-gray-400">Open Source</div> </div> <div class="text-center"> <div class="text-2xl font-bold text-white">4.8★</div> <div class="text-xs text-gray-400">User Rating</div> </div> </div> <!-- Trust Badges --> <div class="flex flex-wrap gap-3 mb-6"> <div class="flex items-center gap-2 text-xs text-gray-400 bg-gray-800 px-3 py-1 rounded-full"> <span class="text-green-400">✓</span> 100% Verified </div> <div class="flex items-center gap-2 text-xs text-gray-400 bg-gray-800 px-3 py-1 rounded-full"> <span class="text-blue-400">🛡️</span> Security Audited </div> <div class="flex items-center gap-2 text-xs text-gray-400 bg-gray-800 px-3 py-1 rounded-full"> <span class="text-purple-400">⚡</span> Updated Weekly </div> </div> <div class="flex space-x-4"> <a href="https://github.com" class="text-gray-400 hover:text-white transition-colors" aria-label="GitHub"> <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"> <path fill-rule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clip-rule="evenodd"></path> </svg> </a> <a href="https://twitter.com" class="text-gray-400 hover:text-white transition-colors" aria-label="Twitter"> <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"> <path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0022 5.92a8.19 8.19 0 01-2.357.646 4.118 4.118 0 001.804-2.27 8.224 8.224 0 01-2.605.996 4.107 4.107 0 00-6.993 3.743 11.65 11.65 0 01-8.457-4.287 4.106 4.106 0 001.27 5.477A4.072 4.072 0 012.8 9.713v.052a4.105 4.105 0 003.292 4.022 4.095 4.095 0 01-1.853.07 4.108 4.108 0 003.834 2.85A8.233 8.233 0 012 18.407a11.616 11.616 0 006.29 1.84"></path> </svg> </a> </div> </div> <!-- Product Links --> <div> <h3 class="text-sm font-semibold text-white uppercase tracking-wider mb-4">Product</h3> <ul class="space-y-3"> <li> <a href="/skills/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> All Skills </a> </li><li> <a href="/skills/#categories" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Browse by Category </a> </li><li> <a href="/skill-bundles/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Skill Bundles </a> </li><li> <a href="/skills/category/web-scrapers" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Web Scrapers </a> </li><li> <a href="/skills/category/ecommerce" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> E-commerce </a> </li><li> <a href="/skills/category/social-media" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Social Media </a> </li><li> <a href="/skills/category/development" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Development </a> </li> </ul> </div> <!-- Resources Links --> <div> <h3 class="text-sm font-semibold text-white uppercase tracking-wider mb-4">Resources</h3> <ul class="space-y-3"> <li> <a href="/get-started/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Documentation </a> </li><li> <a href="/get-started/installation" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Installation </a> </li><li> <a href="/get-started/configuration" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Configuration </a> </li><li> <a href="/get-started/troubleshooting" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Troubleshooting </a> </li> </ul> </div> <!-- Security Links --> <div> <h3 class="text-sm font-semibold text-white uppercase tracking-wider mb-4">🔒 Security</h3> <ul class="space-y-3"> <li> <a href="/security/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Security Overview </a> </li><li> <a href="/security/5-step-hardening-guide" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> 5-Step Hardening Guide </a> </li><li> <a href="/security/auth-none-dangers" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Why auth:none is Dangerous </a> </li><li> <a href="/security/tailscale-guide" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Tailscale Setup Guide </a> </li> </ul> </div> <!-- Use Cases --> <div> <h3 class="text-sm font-semibold text-white uppercase tracking-wider mb-4">Use Cases</h3> <ul class="space-y-3"> <li> <a href="/use-cases/ecommerce" class="text-sm text-gray-400 hover:text-primary-400 transition-colors flex items-center"> <span class="mr-2">🛒</span> E-commerce </a> </li><li> <a href="/use-cases/development" class="text-sm text-gray-400 hover:text-primary-400 transition-colors flex items-center"> <span class="mr-2">💻</span> Development </a> </li><li> <a href="/use-cases/content-creation" class="text-sm text-gray-400 hover:text-primary-400 transition-colors flex items-center"> <span class="mr-2">📝</span> Content Creation </a> </li> </ul> </div> </div> <!-- Comparisons Section --> <div class="border-t border-gray-800 pt-8 mb-8"> <h3 class="text-sm font-semibold text-white uppercase tracking-wider mb-4">Comparisons</h3> <div class="flex flex-wrap gap-6"> <a href="/comparisons/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Comparisons </a><a href="/comparisons/skill-comparison/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Skill Comparison </a><a href="/comparisons/cost-comparison/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Cost Analysis </a><a href="/comparisons/architecture-comparison/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Architecture </a><a href="/tools/benchmarks/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Benchmarks </a><a href="/tools/hardware/" class="text-sm text-gray-400 hover:text-primary-400 transition-colors"> Hardware </a> </div> </div> <!-- Bottom Bar --> <div class="border-t border-gray-800 pt-8 flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0"> <p class="text-sm text-gray-400"> © 2026 AICLawSkills. All rights reserved. </p> <div class="flex space-x-6"> <a href="/legal/about" class="text-sm text-gray-400 hover:text-white transition-colors"> About </a><a href="/legal/privacy" class="text-sm text-gray-400 hover:text-white transition-colors"> Privacy Policy </a><a href="/legal/terms" class="text-sm text-gray-400 hover:text-white transition-colors"> Terms of Service </a><a href="/legal/contact" class="text-sm text-gray-400 hover:text-white transition-colors"> Contact </a> </div> </div> </div> </footer> <div id="search-backdrop" class="search-backdrop hidden fixed inset-0 bg-black/50 z-50 transition-opacity" data-astro-cid-2eu6zh2g></div> <div id="search-modal" class="search-modal hidden fixed inset-0 z-50 flex items-start justify-center pt-[15vh] px-4" data-astro-cid-2eu6zh2g> <div class="relative w-full max-w-2xl bg-white rounded-xl shadow-2xl border border-gray-200 overflow-hidden" data-astro-cid-2eu6zh2g> <!-- Search Input --> <div class="flex items-center px-4 py-3 border-b border-gray-200" data-astro-cid-2eu6zh2g> <svg class="w-5 h-5 text-gray-400 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24" data-astro-cid-2eu6zh2g> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" data-astro-cid-2eu6zh2g></path> </svg> <input id="search-input" type="text" placeholder="Search skills... (e.g., 'shopify', 'email', 'automation')" class="flex-1 outline-none text-gray-900 placeholder-gray-400" data-astro-cid-2eu6zh2g> <div class="flex items-center gap-2 ml-2" data-astro-cid-2eu6zh2g> <kbd class="hidden sm:inline-block px-2 py-1 text-xs font-semibold text-gray-400 bg-gray-100 border border-gray-200 rounded" data-astro-cid-2eu6zh2g>ESC</kbd> </div> </div> <!-- Search Results --> <div id="search-results" class="max-h-[50vh] overflow-y-auto" data-astro-cid-2eu6zh2g> <!-- Empty State --> <div id="search-empty" class="px-4 py-8 text-center text-gray-500" data-astro-cid-2eu6zh2g> <svg class="w-12 h-12 mx-auto mb-3 text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24" data-astro-cid-2eu6zh2g> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" data-astro-cid-2eu6zh2g></path> </svg> <p class="text-sm font-medium" data-astro-cid-2eu6zh2g>Start typing to search skills</p> <p class="text-xs mt-1 text-gray-400" data-astro-cid-2eu6zh2g>Search by name, category, or tags</p> </div> <!-- Results will be injected here --> <div id="search-results-list" class="py-2 hidden" data-astro-cid-2eu6zh2g></div> </div> <!-- Footer --> <div class="px-4 py-2 border-t border-gray-200 bg-gray-50 flex items-center justify-between text-xs text-gray-500" data-astro-cid-2eu6zh2g> <div class="flex items-center gap-3" data-astro-cid-2eu6zh2g> <span class="flex items-center gap-1" data-astro-cid-2eu6zh2g> <kbd data-astro-cid-2eu6zh2g>↑</kbd><kbd data-astro-cid-2eu6zh2g>↓</kbd> to navigate </span> <span class="flex items-center gap-1" data-astro-cid-2eu6zh2g> <kbd data-astro-cid-2eu6zh2g>↵</kbd> to select </span> </div> <span id="search-count" data-astro-cid-2eu6zh2g>0 results</span> </div> </div> </div> <script> // Skills data will be injected server-side window.SKILLS_DATA = []; </script> </body></html>