Creating Oneshot Landing Pages logo

Creating Oneshot Landing Pages

Community
nroadley
creating-oneshot-landing-pages

Designs and builds video-driven parallax hero landing pages with smooth video scrubbing, progress-locked typography, and clean navigation. Powered by Gemini Omni and Nano Banana.

Overview

Publishernroadley
RepositoryCreating-Oneshot-Hero-Landing-Pages
Skill namecreating-oneshot-landing-pages
Stars
134
Forks
11
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by nroadley on GitHub. Read the source before you install it.

Installation

Install the Creating Oneshot Landing Pages AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/nroadley/Creating-Oneshot-Hero-Landing-Pages/tree/main
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/nroadley/Creating-Oneshot-Hero-Landing-Pages.git \
  .claude/skills/creating-oneshot-landing-pages
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Creating Oneshot Landing Pages in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Creating Oneshot Landing Pages on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Creating Oneshot Landing Pages is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Creating Oneshot Hero Landing Pages

An Antigravity skill to design and build video-driven parallax hero landing pages and looping motion showcases.


1. 4-Step Execution Pipeline

Execute every landing page request in this order:

  1. Step 1: Reference Images: Generate reference images via generate_image (the built-in Antigravity tool powered by Nano Banana / gemini-3.1-flash-image) and save them into ./assets/ (e.g. ./assets/hero_ref.jpg, ./assets/showcase_ref.jpg).
  2. Step 2: Video Generation: Execute Python REST calls against gemini-omni-flash-preview (Section 3) using the reference images to generate 10s 16:9 .mp4 video files into ./assets/.
  3. Step 3: Frontend Implementation: Implement the Hero Video Scrub Scaffold (Section 4) in index.html with smooth scroll video scrubbing, progress-locked hero text, and clean navigation.
  4. Step 4: Verification & Local Hosting: Launch a local preview server (python3 -m http.server 3000 --directory <dir>), verify all video assets return 200 OK, and inspect the visual layout.

2. Design Guidelines

  • Navigation Guidelines:
    • Pick ONE Focused Composition:
      • [Logo] + [2–4 Editorial Links Max] (strictly <= 4 links)
      • [Logo] + [Menu / Index Trigger]
      • [Logo] + [Brand Tagline or Location] + [Menu]
      • [Logo] + [Single Quiet CTA]
      • [Split Nav] with [2 Links] Left, [Logo] Center, [2 Links or 1 CTA] Right
    • Keep It Simple: If you have 2–4 inline links, do not stack a heavy CTA button and hamburger menu next to them. If using a drawer, keep the bar quiet.
    • No Mechanical Numbering or Fake Tickers: Do not prefix mechanical numbers (01, 02, 03) onto nav links. Do not add fake GPS coordinates, sensor readings, or status dots (● LIVE).
  • No Decorative SVG Icons on Cards or UI:
    • Do not place decorative SVG icons, Lucide icons (<i data-lucide=...>), or icon badge containers inside feature cards, headings, bullet points, or buttons.
    • Cards, pillars, and sections should rely on typography, whitespace, and photography/video. (SVGs are fine for a logo mark or modal close button).
  • No Toy Widgets, Interactive Simulators, or Mini-Games:
    • Landing pages are editorial showcases.
    • Do not generate character stat calculators, RPG inventory boxes, physics toy widgets, interactive sandbox cards, or canvas-confetti particle scripts.
    • For playful brands or games, express personality through color palettes, typography, and video motion rather than playable JavaScript mini-games or calculators.
  • No Unrequested Audio Buttons or Sound Synthesizers:
    • Do not generate audio toggle buttons (🔊), background sound generators, or Web Audio API synthesizers (AudioContext, oscillators).
  • Clean Typography Over Badges:
    • Default to unbordered typography, subtle font weights, fine line accents (—), or roman numerals (I, II) instead of encasing words inside rounded pill badges ([ BADGE ]).
    • When a tag or table cell is necessary:
      • 1–2 Words Maximum: Do not put long descriptions or sentences into compact badges (e.g. use LEO, not LEO Station Rendezvous).
      • Include whitespace-nowrap: Keep badge text on a single line so it never breaks into awkward stacks.
      • Single-Row Filter Bars: Filter buttons should stay on a single unbroken row (flex items-center gap-2 overflow-x-auto whitespace-nowrap).
      • Responsive Tables: Data tables should use responsive scroll wrappers (overflow-x-auto min-w-[650px]).
  • Media & Hero Video Rules:
    • The hero must use a <video id="hero-video"> element. Do not substitute video with <canvas> image slideshows or static <img> tags.
    • Supplemental showcases generated by Omni should be looping <video autoplay muted loop playsinline> elements.
    • No Visible Scrubber HUDs: Do not render timeline scrub bars, progress percentage meters ("00%"), or cockpit dials over media.
  • Progress-Locked Hero Text:
    • Calculate hero text opacity and translation directly from scroll progress in JavaScript (see Section 4). Scrolling reveals text, pausing keeps it readable, reversing hides it.
    • Do not use time-based CSS transitions (transition: opacity 0.7s) on hero scroll text that cause text to lag or scroll past unread. (CSS animations are fine in standard body sections).
  • Layout & Stacking Rules:
    • Root containers (html, body, #root, wrappers) must have overflow: visible (or overflow-x: clip). Do not use overflow-hidden or overflow-x-hidden on parent wrappers, which breaks CSS position: sticky.
    • Hero videos should stretch edge-to-edge (w-full h-full object-cover), not styled as small rounded UI cards.
  • Typography & Color:
    • Use modern Google Fonts (e.g. Italiana, Cormorant Garamond, Plus Jakarta Sans, Space Grotesk).
    • Use curated color palettes (e.g. terracotta, basalt, obsidian, titanium, alabaster) rather than default generic rainbow colors.

3. Omni Video Python Generator

Install dependencies:

bash
pip install google-genai pillow

Save generated .mp4 files directly to ./assets/ using this script:

python
import base64
import io
import json
import os
import ssl
import sys
import urllib.error
import urllib.request
from PIL import Image

def generate_omni_video(
    api_key: str,
    img_path: str,
    prompt: str,
    output_mp4_path: str
) -> bool:
    ctx = ssl.create_default_context()
    url = "https://generativelanguage.googleapis.com/v1beta/interactions"
    
    try:
        img = Image.open(img_path).convert("RGB")
        buf = io.BytesIO()
        img.save(buf, format="PNG")
        b64_img = base64.b64encode(buf.getvalue()).decode("utf-8")
    except Exception as e:
        print(f"Error opening image '{img_path}': {e}", file=sys.stderr)
        return False

    payload = {
        "model": "models/gemini-omni-flash-preview",
        "generation_config": {"thinking_level": "high"},
        "response_format": {"type": "video", "aspect_ratio": "16:9", "duration": "10s"},
        "input": [
            {"type": "text", "text": prompt + " No text, no titles, no subtitles, no overlays."},
            {"type": "image", "mime_type": "image/png", "data": b64_img}
        ]
    }
    
    req = urllib.request.Request(
        url,
        data=json.dumps(payload).encode("utf-8"),
        headers={
            "Content-Type": "application/json",
            "x-goog-api-key": api_key
        }
    )
    
    try:
        with urllib.request.urlopen(req, context=ctx) as response:
            res = json.loads(response.read().decode("utf-8"))
            for step in res.get("steps", []):
                for item in step.get("content", []):
                    if item.get("type") == "video" or "video" in str(item.get("mime_type")):
                        os.makedirs(os.path.dirname(output_mp4_path) or ".", exist_ok=True)
                        with open(output_mp4_path, "wb") as f:
                            f.write(base64.b64decode(item["data"]))
                        return True
            print("No video data found in response payload.", file=sys.stderr)
            return False
    except urllib.error.HTTPError as e:
        error_body = e.read().decode("utf-8", errors="replace")
        print(f"API HTTP Error {e.code} ({e.reason}): {error_body}", file=sys.stderr)
        return False
    except urllib.error.URLError as e:
        print(f"Network / URL Error: {e.reason}", file=sys.stderr)
        return False
    except Exception as e:
        print(f"Unexpected error during video generation: {e}", file=sys.stderr)
        return False

4. Hero Video Scrub Scaffold (HTML + CSS + JS)

Use this architecture for the sticky 400vh video scrubbing engine:

html
<!DOCTYPE html>
<html lang="en" class="scroll-smooth">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Brand — Hero Showcase</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;1,400&family=Plus+Jakarta+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
  <style>
    html, body {
      margin: 0; padding: 0;
      overflow-x: clip; /* Clean clipping without breaking position:sticky */
      background: #08090d; color: #f7f5f0;
      font-family: 'Plus Jakarta Sans', system-ui, sans-serif;
    }
    .phase-text { will-change: opacity, transform; }
    header.nav-scrolled {
      background: rgba(8, 9, 13, 0.85);
      backdrop-filter: blur(16px);
      border-bottom: 1px solid rgba(255, 255, 255, 0.08);
      padding-top: 1rem; padding-bottom: 1rem;
    }
  </style>
</head>
<body class="relative antialiased">
  <!-- Navigation goes here -->
  
  <!-- 400vh Sticky Scroll Hero Video Engine -->
  <div id="hero-section" class="relative h-[400vh] w-full">
    <div class="sticky top-0 w-full h-screen overflow-hidden flex items-center justify-center">
      <video id="hero-video" class="absolute inset-0 w-full h-full object-cover pointer-events-none" playsinline muted preload="auto"></video>
      <div class="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-black/30 pointer-events-none"></div>
      
      <!-- Phase 0 Typography (0% - 25% Scroll) -->
      <div id="phase-0" class="phase-text absolute bottom-20 left-8 md:left-20 max-w-xl pointer-events-none">
        <h1 class="font-serif text-5xl md:text-7xl font-light text-white leading-tight">First Headline Statement.</h1>
      </div>
      <!-- Phase 1 Typography (30% - 60% Scroll) -->
      <div id="phase-1" class="phase-text absolute top-1/3 right-8 md:right-24 max-w-lg pointer-events-none opacity-0">
        <h2 class="font-serif text-4xl md:text-5xl font-light text-white leading-snug">Second Narrative Phase.</h2>
      </div>
      <!-- Phase 2 Typography (65% - 95% Scroll) -->
      <div id="phase-2" class="phase-text absolute bottom-24 left-8 md:left-20 max-w-lg pointer-events-none opacity-0">
        <h2 class="font-serif text-4xl md:text-5xl font-light text-white leading-snug">Third Concluding Statement.</h2>
      </div>
    </div>
  </div>

  <!-- Supplemental Content & Looping Showcases -->
  <section class="py-32 px-8 md:px-20 max-w-7xl mx-auto">
    <!-- Body Content Here -->
  </section>

  <!-- Video Scrubbing Engine & Progress-Locked Text -->
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const heroVideo = document.getElementById('hero-video');
      const heroSection = document.getElementById('hero-section');
      const nav = document.querySelector('header');
      const phase0 = document.getElementById('phase-0');
      const phase1 = document.getElementById('phase-1');
      const phase2 = document.getElementById('phase-2');

      // 1. In-Memory Blob Preloader with explicit video.load()
      fetch('./assets/hero.mp4')
        .then(res => {
          if (!res.ok) throw new Error('Video network response was not ok');
          return res.blob();
        })
        .then(blob => {
          heroVideo.src = URL.createObjectURL(blob);
          heroVideo.load();
        })
        .catch(() => {
          heroVideo.src = './assets/hero.mp4';
          heroVideo.load();
        });

      // 2. Normalized Scroll Progress
      let targetProgress = 0, currentProgress = 0;
      function updateScroll() {
        const rect = heroSection.getBoundingClientRect();
        const max = rect.height - window.innerHeight;
        if (max > 0) targetProgress = Math.max(0, Math.min(1, -rect.top / max));
        if (nav) {
          if (window.scrollY > 80) nav.classList.add('nav-scrolled');
          else nav.classList.remove('nav-scrolled');
        }
      }
      window.addEventListener('scroll', updateScroll, { passive: true });
      window.addEventListener('resize', updateScroll);

      // 3. Progress-Locked Text Opacity Calculator
      function calcOpacity(progress, enterStart, enterEnd, exitStart, exitEnd) {
        if (progress < enterStart || progress > exitEnd) return 0;
        if (progress < enterEnd) return (progress - enterStart) / (enterEnd - enterStart);
        if (progress > exitStart) return Math.max(0, 1 - (progress - exitStart) / (exitEnd - exitStart));
        return 1.0;
      }

      // 4. Animation Loop
      function scrubLoop() {
        currentProgress += (targetProgress - currentProgress) * 0.15;
        
        // Seek Video
        if (heroVideo.duration && !heroVideo.seeking) {
          const targetTime = currentProgress * heroVideo.duration;
          if (Math.abs(heroVideo.currentTime - targetTime) > 0.015) {
            heroVideo.currentTime = targetTime;
          }
        }

        // Lock Typography directly to scroll progress
        const op0 = calcOpacity(targetProgress, 0.0, 0.05, 0.20, 0.28);
        const op1 = calcOpacity(targetProgress, 0.28, 0.38, 0.55, 0.65);
        const op2 = calcOpacity(targetProgress, 0.65, 0.75, 0.90, 0.98);

        if (phase0) { phase0.style.opacity = op0.toFixed(3); phase0.style.transform = `translateY(${-targetProgress * 40}px)`; }
        if (phase1) { phase1.style.opacity = op1.toFixed(3); phase1.style.transform = `translateY(${(0.45 - targetProgress) * 30}px)`; }
        if (phase2) { phase2.style.opacity = op2.toFixed(3); phase2.style.transform = `translateY(${(0.80 - targetProgress) * 30}px)`; }

        requestAnimationFrame(scrubLoop);
      }
      requestAnimationFrame(scrubLoop);
    });
  </script>
</body>
</html>

Frequently asked questions

What does the Creating Oneshot Landing Pages AI skill do?

Designs and builds video-driven parallax hero landing pages with smooth video scrubbing, progress-locked typography, and clean navigation. Powered by Gemini Omni and Nano Banana.

Why use Creating Oneshot Landing Pages on TypingMind?

Because you install it once and use it with any model. Creating Oneshot Landing Pages is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Creating Oneshot Landing Pages in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/nroadley/Creating-Oneshot-Hero-Landing-Pages/tree/main. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Creating Oneshot Landing Pages?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Creating Oneshot Landing Pages?

As many as you like. As long as a model supports skills, you can use Creating Oneshot Landing Pages with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Creating Oneshot Landing Pages AI skill free?

Yes. It is published on GitHub by nroadley under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇