---
name: video-generator
description: AI video production workflow using Remotion. Use when creating videos, short films, commercials, or motion graphics. Triggers on requests to make promotional videos, product demos, social media videos, animated explainers, or any programmatic video content. Produces polished motion graphics, not slideshows.
---

> **PORTABILITY NOTE (added 2026-08-13).** This process doc is published from Imperium's
> internal operating system. The METHOD is fully portable: prompts, model choices,
> parameters, and QA gates work anywhere. Paths or helper skills that are not part of
> this kit (e.g. `skills/cloudflare-tunnel/`, `~/.claude/`) are internal conveniences on
> the author's machine; substitute your own equivalents. Set your own API keys
> (e.g. `FIRECRAWL_API_KEY`) via environment variables — none ship with this kit.

# Video Generator (Remotion)

Create professional motion graphics videos programmatically with React and Remotion.

## Default Workflow (ALWAYS follow this)

1. **Scrape brand data** (if featuring a product) using Firecrawl
2. **Create the project** in `output/<project-name>/`
3. **Build all scenes** with proper motion graphics
4. **Install dependencies** with `npm install`
5. **Fix package.json scripts** to use `npx remotion` (not `bun`):
   ```json
   "scripts": {
     "dev": "npx remotion studio",
     "build": "npx remotion bundle"
   }
   ```
5. **Start Remotion Studio** as a background process:
   ```bash
   cd output/<project-name> && npm run dev
   ```
   Wait for "Server ready" on port 3000.
6. **Expose via Cloudflare tunnel** so user can access it:
   ```bash
   bash skills/cloudflare-tunnel/scripts/tunnel.sh start 3000
   ```
7. **Send the user the public URL** (e.g. `https://xxx.trycloudflare.com`)

The user will preview in their browser, request changes, and you edit the source files. Remotion hot-reloads automatically.

### Rendering (only when user explicitly asks to export):
```bash
cd output/<project-name>
npx remotion render CompositionName out/video.mp4
```

## Quick Start

```bash
# Scaffold project
cd output && npx --yes create-video@latest my-video --template blank
cd my-video && npm install

# Add motion libraries
npm install lucide-react

# Fix scripts in package.json (replace any "bun" references with "npx remotion")

# Start dev server
npm run dev

# Expose publicly
bash skills/cloudflare-tunnel/scripts/tunnel.sh start 3000
```

## Fetching Brand Data with Firecrawl

**MANDATORY:** When a video mentions or features any product/company, use Firecrawl to scrape the product's website for brand data, colors, screenshots, and copy BEFORE designing the video. This ensures visual accuracy and brand consistency.

API Key: Set `FIRECRAWL_API_KEY` in `.env` (see TOOLS.md).

### Usage

```bash
bash scripts/firecrawl.sh "https://example.com"
```

Returns structured brand data: brandName, tagline, headline, description, features, logoUrl, faviconUrl, primaryColors, ctaText, socialLinks, plus screenshot URL and OG image URL.

### Download Assets After Scraping

```bash
mkdir -p public/images/brand
curl -s "https://example.com/favicon.svg" -o public/images/brand/logo.svg
curl -s "${OG_IMAGE_URL}" -o public/images/brand/og-image.png
curl -sL "${SCREENSHOT_URL}" -o public/images/brand/screenshot.png
```

## Core Architecture

### Scene Management

Use scene-based architecture with proper transitions:

```tsx
const SCENE_DURATIONS: Record<string, number> = {
  intro: 3000,     // 3s hook
  problem: 4000,   // 4s dramatic
  solution: 3500,  // 3.5s reveal
  features: 5000,  // 5s showcase
  cta: 3000,       // 3s close
};
```

### Video Structure Pattern

```tsx
import {
  AbsoluteFill, Sequence, useCurrentFrame,
  useVideoConfig, interpolate, spring,
  Img, staticFile, Audio,
} from "remotion";

export const MyVideo = () => {
  const frame = useCurrentFrame();
  const { fps, durationInFrames } = useVideoConfig();

  return (
    <AbsoluteFill>
      {/* Background music */}
      <Audio src={staticFile("audio/bg-music.mp3")} volume={0.35} />

      {/* Persistent background layer - OUTSIDE sequences */}
      <AnimatedBackground frame={frame} />

      {/* Scene sequences */}
      <Sequence from={0} durationInFrames={90}>
        <IntroScene />
      </Sequence>
      <Sequence from={90} durationInFrames={120}>
        <FeatureScene />
      </Sequence>
    </AbsoluteFill>
  );
};
```

## Motion Graphics Principles

### AVOID (Slideshow patterns)

- Fading to black between scenes
- Centered text on solid backgrounds
- Same transition for everything
- Linear/robotic animations
- Static screens
- `slideLeft`, `slideRight`, `crossDissolve`, `fadeBlur` presets
- Emoji icons — NEVER use emoji, always use Lucide React icons

### PURSUE (Motion graphics)

- Overlapping transitions (next starts BEFORE current ends)
- Layered compositions (background/midground/foreground)
- Spring physics for organic motion
- Varied timing (2-5s scenes, mixed rhythms)
- Continuous visual elements across scenes
- Custom transitions with clipPath, 3D transforms, morphs
- Lucide React for ALL icons (`npm install lucide-react`) — never emoji

## Transition Techniques

1. **Morph/Scale** - Element scales up to fill screen, becomes next scene's background
2. **Wipe** - Colored shape sweeps across, revealing next scene
3. **Zoom-through** - Camera pushes into element, emerges into new scene
4. **Clip-path reveal** - Circle/polygon grows from point to reveal
5. **Persistent anchor** - One element stays while surroundings change
6. **Directional flow** - Scene 1 exits right, Scene 2 enters from right
7. **Split/unfold** - Screen divides, panels slide apart
8. **Perspective flip** - Scene rotates on Y-axis in 3D

## Animation Timing Reference

```tsx
// Timing values (in seconds)
const timing = {
  micro: 0.1-0.2,      // Small shifts, subtle feedback
  snappy: 0.2-0.4,     // Element entrances, position changes
  standard: 0.5-0.8,   // Scene transitions, major reveals
  dramatic: 1.0-1.5,   // Hero moments, cinematic reveals
};

// Spring configs
const springs = {
  snappy: { stiffness: 400, damping: 30 },
  bouncy: { stiffness: 300, damping: 15 },
  smooth: { stiffness: 120, damping: 25 },
};
```

## Visual Style Guidelines

### Typography
- One display font + one body font max
- Massive headlines, tight tracking
- Mix weights for hierarchy
- Keep text SHORT (viewers can't pause)

### Colors
- **Use brand colors from Firecrawl scrape** as the primary palette — match the product's actual look
- **Avoid purple/indigo gradients** unless the brand uses them or the user explicitly requests them
- Simple, clean backgrounds are generally best — a single dark tone or subtle gradient beats layered textures
- Intentional accent colors pulled from the brand

### Layout
- Use asymmetric layouts, off-center type
- Edge-aligned elements create visual tension
- Generous whitespace as design element
- Use depth sparingly — a subtle backdrop blur or single gradient, not stacked textures

## Remotion Essentials

### Interpolation

```tsx
const opacity = interpolate(frame, [0, 30], [0, 1], {
  extrapolateLeft: "clamp",
  extrapolateRight: "clamp"
});

const scale = spring({
  frame, fps,
  from: 0.8, to: 1,
  durationInFrames: 30,
  config: { damping: 12 }
});
```

### Sequences with Overlap

```tsx
<Sequence from={0} durationInFrames={100}>
  <Scene1 />
</Sequence>
<Sequence from={80} durationInFrames={100}>
  <Scene2 />
</Sequence>
```

### Cross-Scene Continuity

Place persistent elements OUTSIDE Sequence blocks:

```tsx
const PersistentShape = ({ currentScene }: { currentScene: number }) => {
  const positions = {
    0: { x: 100, y: 100, scale: 1, opacity: 0.3 },
    1: { x: 800, y: 200, scale: 2, opacity: 0.5 },
    2: { x: 400, y: 600, scale: 0.5, opacity: 1 },
  };

  return (
    <motion.div
      animate={positions[currentScene]}
      transition={{ duration: 0.8, ease: "easeInOut" }}
      className="absolute w-32 h-32 rounded-full bg-gradient-to-r from-coral to-orange"
    />
  );
};
```

## Quality Tests

Before delivering, verify:

- **Mute test:** Story follows visually without sound?
- **Squint test:** Hierarchy visible when squinting?
- **Timing test:** Motion feels natural, not robotic?
- **Consistency test:** Similar elements behave similarly?
- **Slideshow test:** Does NOT look like PowerPoint?
- **Loop test:** Video loops smoothly back to start?

## Implementation Steps

1. **Firecrawl brand scrape** — If featuring a product, scrape its site first
2. **Director's treatment** — Write vibe, camera style, emotional arc
3. **Visual direction** — Colors, fonts, brand feel, animation style
4. **Scene breakdown** — List every scene with description, duration, text, transitions
5. **Plan assets** — User assets + generated images/videos + brand scrape assets
9. **Define durations** — Vary pacing (2-3s punchy, 4-5s dramatic)
10. **Build persistent layer** — Animated background outside scenes
11. **Build scenes** — Each with enter/exit animations, 3-5 timed moments
12. **Open with hook** — High-impact first scene
13. **Develop narrative** — Content-driven middle scenes
14. **Strong ending** — Intentional, resolved close
15. **Start Remotion Studio** — `npm run dev` on port 3000
16. **Expose via tunnel** — `bash skills/cloudflare-tunnel/scripts/tunnel.sh start 3000`
17. **Send user the public URL** — They preview and request changes live
18. **Iterate** — Edit source, hot-reload, repeat
19. **Render** — Only when user says to export final video

## File Structure

```
my-video/
├── src/
│   ├── Root.tsx              # Composition definitions
│   ├── index.ts              # Entry point
│   ├── index.css             # Global styles
│   ├── MyVideo.tsx           # Main video component
│   └── scenes/               # Scene components (optional)
├── public/
│   ├── images/
│   │   └── brand/            # Firecrawl-scraped assets
│   └── audio/                # Background music
├── remotion.config.ts
└── package.json
```

## Common Components

See `references/components.md` for reusable:
- Animated backgrounds
- Terminal windows
- Feature cards
- Stats displays
- CTA buttons
- Text reveal animations

## Tunnel Management

```bash
# Start tunnel (exposes port 3000 publicly)
bash skills/cloudflare-tunnel/scripts/tunnel.sh start 3000

# Check status
bash skills/cloudflare-tunnel/scripts/tunnel.sh status 3000

# List all tunnels
bash skills/cloudflare-tunnel/scripts/tunnel.sh list

# Stop tunnel
bash skills/cloudflare-tunnel/scripts/tunnel.sh stop 3000
```

---

# references/components.md

# Reusable Components

## Animated Background

```tsx
import { useCurrentFrame, interpolate } from "remotion";

export const AnimatedBackground = ({ frame }: { frame: number }) => {
  const hueShift = interpolate(frame, [0, 300], [0, 360]);
  const gradientAngle = interpolate(frame, [0, 300], [0, 180]);

  return (
    <div
      style={{
        position: "absolute",
        inset: 0,
        background: `linear-gradient(${gradientAngle}deg,
          hsl(${hueShift}, 70%, 15%),
          hsl(${hueShift + 60}, 60%, 10%))`,
      }}
    />
  );
};
```

## Terminal Window

```tsx
export const TerminalWindow = ({
  lines,
  frame,
  fps,
}: {
  lines: string[];
  frame: number;
  fps: number;
}) => {
  const visibleLines = Math.floor(frame / (fps * 0.3));

  return (
    <div className="bg-gray-900 rounded-xl p-6 font-mono text-sm shadow-2xl border border-gray-700">
      <div className="flex gap-2 mb-4">
        <div className="w-3 h-3 rounded-full bg-red-500" />
        <div className="w-3 h-3 rounded-full bg-yellow-500" />
        <div className="w-3 h-3 rounded-full bg-green-500" />
      </div>
      {lines.slice(0, visibleLines).map((line, i) => (
        <div key={i} className="text-green-400 leading-relaxed">
          <span className="text-gray-500">$ </span>{line}
        </div>
      ))}
      {visibleLines <= lines.length && (
        <span className="inline-block w-2 h-5 bg-green-400 animate-pulse" />
      )}
    </div>
  );
};
```

## Feature Card

```tsx
import { spring, useCurrentFrame, useVideoConfig, interpolate } from "remotion";

// icon should be a Lucide React component, NEVER an emoji string
export const FeatureCard = ({
  icon: Icon,
  title,
  description,
  delay = 0,
}: {
  icon: React.FC<{ size?: number; color?: string }>;
  title: string;
  description: string;
  delay?: number;
}) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  const scale = spring({
    frame: frame - delay,
    fps,
    config: { stiffness: 300, damping: 20 },
  });

  const opacity = interpolate(frame - delay, [0, 15], [0, 1], {
    extrapolateLeft: "clamp",
    extrapolateRight: "clamp",
  });

  return (
    <div
      style={{ transform: `scale(${scale})`, opacity }}
      className="bg-white/10 backdrop-blur-md rounded-2xl p-8 border border-white/20"
    >
      <div className="mb-4"><Icon size={40} color="white" /></div>
      <h3 className="text-2xl font-bold text-white mb-2">{title}</h3>
      <p className="text-gray-300">{description}</p>
    </div>
  );
};
```

## Stats Display

```tsx
import { interpolate } from "remotion";

export const StatsDisplay = ({
  value,
  label,
  frame,
  fps,
}: {
  value: number;
  label: string;
  frame: number;
  fps: number;
}) => {
  const progress = interpolate(frame, [0, fps * 1.5], [0, 1], {
    extrapolateRight: "clamp",
  });
  const displayValue = Math.round(value * progress);

  return (
    <div className="text-center">
      <div className="text-7xl font-black text-white tracking-tight">
        {displayValue.toLocaleString()}
      </div>
      <div className="text-lg text-gray-400 uppercase tracking-widest mt-2">
        {label}
      </div>
    </div>
  );
};
```

## CTA Button

```tsx
import { spring, useCurrentFrame, useVideoConfig, interpolate } from "remotion";

export const CTAButton = ({
  text,
  frame,
  fps,
}: {
  text: string;
  frame: number;
  fps: number;
}) => {
  const scale = spring({
    frame,
    fps,
    config: { stiffness: 200, damping: 15 },
  });

  const shimmer = interpolate(frame, [0, fps * 2], [-100, 200]);

  return (
    <div
      style={{ transform: `scale(${scale})` }}
      className="relative inline-block px-12 py-5 bg-gradient-to-r from-blue-500 to-purple-600 rounded-full text-white text-2xl font-bold overflow-hidden"
    >
      {text}
      <div
        className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent"
        style={{ transform: `translateX(${shimmer}%)` }}
      />
    </div>
  );
};
```

## Text Reveal

```tsx
import { interpolate } from "remotion";

export const TextReveal = ({
  text,
  frame,
  fps,
  charDelay = 2,
}: {
  text: string;
  frame: number;
  fps: number;
  charDelay?: number;
}) => {
  return (
    <div className="flex flex-wrap">
      {text.split("").map((char, i) => {
        const charFrame = frame - i * charDelay;
        const opacity = interpolate(charFrame, [0, 8], [0, 1], {
          extrapolateLeft: "clamp",
          extrapolateRight: "clamp",
        });
        const y = interpolate(charFrame, [0, 8], [20, 0], {
          extrapolateLeft: "clamp",
          extrapolateRight: "clamp",
        });

        return (
          <span
            key={i}
            style={{ opacity, transform: `translateY(${y}px)` }}
            className="text-6xl font-bold text-white"
          >
            {char === " " ? "\u00A0" : char}
          </span>
        );
      })}
    </div>
  );
};
```

---

# references/composition-patterns.md

# Remotion Composition Patterns

## Project Structure

```
my-video/
├── src/
│   ├── index.ts              # registerRoot entry point
│   ├── Root.tsx              # <Composition> declarations
│   └── MyVideo/
│       ├── index.tsx         # Main component
│       └── styles.ts         # Optional styles
├── public/                   # Static assets (images, fonts, audio)
├── remotion.config.ts        # Remotion config
├── package.json
└── tsconfig.json
```

## Basic Composition (Root.tsx)

```tsx
import { Composition } from "remotion";
import { MyVideo } from "./MyVideo";

export const RemotionRoot = () => (
  <>
    <Composition
      id="MyVideo"
      component={MyVideo}
      durationInFrames={300}   // 10s at 30fps
      fps={30}
      width={1920}
      height={1080}
      defaultProps={{ title: "Hello World" }}
    />
  </>
);
```

## Common Aspect Ratios

- **16:9 landscape (YouTube):** 1920x1080 or 1280x720
- **9:16 vertical (Reels/TikTok/Shorts):** 1080x1920
- **4:5 Instagram feed:** 1080x1350
- **1:1 square:** 1080x1080

## Key Remotion APIs

```tsx
import {
  useCurrentFrame,      // Current frame number
  useVideoConfig,       // { fps, width, height, durationInFrames }
  interpolate,          // Map frame ranges to values
  spring,               // Physics-based spring animation
  Sequence,             // Time-offset children
  AbsoluteFill,         // Full-frame container
  Img,                  // Image component (preloads)
  Audio,                // Audio component
  Video,                // Video component
  staticFile,           // Reference files in public/
  delayRender,          // Hold render until async ready
  continueRender,       // Resume after delayRender
} from "remotion";
```

## Animation Example

```tsx
import { useCurrentFrame, interpolate, spring, useVideoConfig, AbsoluteFill } from "remotion";

export const FadeInText: React.FC<{ text: string }> = ({ text }) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();

  const opacity = interpolate(frame, [0, 30], [0, 1], { extrapolateRight: "clamp" });
  const scale = spring({ frame, fps, config: { damping: 200 } });

  return (
    <AbsoluteFill className="items-center justify-center bg-black">
      <h1
        style={{ opacity, transform: `scale(${scale})` }}
        className="text-white text-7xl font-bold"
      >
        {text}
      </h1>
    </AbsoluteFill>
  );
};
```

## Sequences (Timing)

```tsx
<AbsoluteFill>
  <Sequence from={0} durationInFrames={60}>
    <Intro />
  </Sequence>
  <Sequence from={60} durationInFrames={120}>
    <MainContent />
  </Sequence>
  <Sequence from={180}>
    <Outro />
  </Sequence>
</AbsoluteFill>
```

## Input Props (Dynamic Data)

Pass data via `--props` flag or `defaultProps`:

```tsx
// Component
export const MyVideo: React.FC<{ title: string; items: string[] }> = ({ title, items }) => { ... };

// Render with props
// npx remotion render MyVideo --props='{"title":"Demo","items":["a","b"]}'
```

## Audio

```tsx
import { Audio, staticFile, Sequence } from "remotion";

<Sequence from={0}>
  <Audio src={staticFile("bgm.mp3")} volume={0.5} />
</Sequence>
```

## Fetching Data (delayRender)

```tsx
const [data, setData] = useState(null);
const [handle] = useState(() => delayRender());

useEffect(() => {
  fetch("https://api.example.com/data")
    .then((r) => r.json())
    .then((d) => { setData(d); continueRender(handle); });
}, []);
```

## TailwindCSS

Remotion supports Tailwind out of the box when scaffolded with `--tailwind`. Use className as normal on any element.

---

# scripts/firecrawl.sh

#!/usr/bin/env bash
# firecrawl.sh - Scrape brand data from a website using Firecrawl API
# Usage: firecrawl.sh <url>
#
# Returns structured brand data: name, tagline, colors, logo, screenshot, etc.
# Requires FIRECRAWL_API_KEY in environment or .env

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
WORKSPACE_DIR="$(dirname "$(dirname "$SKILL_DIR")")"

# Load .env if exists
if [ -f "$WORKSPACE_DIR/.env" ]; then
  set -a
  source "$WORKSPACE_DIR/.env"
  set +a
fi

URL="${1:?Usage: firecrawl.sh <url>}"

if [ -z "${FIRECRAWL_API_KEY:-}" ]; then
  echo "Error: FIRECRAWL_API_KEY not set" >&2
  exit 1
fi

curl -s -X POST 'https://api.firecrawl.dev/v1/scrape' \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer ${FIRECRAWL_API_KEY}" \
  -d "$(cat <<EOF
{
  "url": "$URL",
  "formats": ["markdown", "extract", "screenshot"],
  "extract": {
    "schema": {
      "type": "object",
      "properties": {
        "brandName": {"type": "string"},
        "tagline": {"type": "string"},
        "headline": {"type": "string"},
        "description": {"type": "string"},
        "features": {"type": "array", "items": {"type": "string"}},
        "logoUrl": {"type": "string"},
        "faviconUrl": {"type": "string"},
        "primaryColors": {"type": "array", "items": {"type": "string"}},
        "ctaText": {"type": "string"},
        "socialLinks": {"type": "object"}
      }
    }
  }
}
EOF
)"

---

# scripts/remotion.sh

#!/usr/bin/env bash
# remotion.sh - Wrapper for common Remotion operations
# Usage: remotion.sh <command> [args...]
#
# Commands:
#   init <project-name>          - Scaffold a new Remotion project (blank + tailwind)
#   render <project-dir> [comp] [output] [--props '{}'] [--width N] [--height N]
#   still <project-dir> [comp] [output] [--props '{}'] [--frame N]
#   preview <project-dir>        - Start Remotion Studio dev server
#   list <project-dir>           - List available compositions
#   upgrade <project-dir>        - Upgrade Remotion packages to latest

set -euo pipefail

CMD="${1:-help}"
shift || true

case "$CMD" in
  init)
    PROJECT_NAME="${1:?Usage: remotion.sh init <project-name>}"
    echo "Creating Remotion project: $PROJECT_NAME"
    npx --yes create-video@latest --blank --tailwind --no-skills "$PROJECT_NAME" 2>&1 || {
      # Fallback: manual scaffold if create-video doesn't support flags
      echo "Trying interactive scaffold..."
      echo -e "blank\ny\nn" | npx --yes create-video@latest "$PROJECT_NAME" 2>&1
    }
    echo "Project created at ./$PROJECT_NAME"
    echo "Next: cd $PROJECT_NAME && npm install && npm run dev"
    ;;

  render)
    PROJECT_DIR="${1:?Usage: remotion.sh render <project-dir> [composition] [output] [flags...]}"
    shift
    COMP="${1:-}"
    shift 2>/dev/null || true
    OUTPUT="${1:-}"
    shift 2>/dev/null || true

    cd "$PROJECT_DIR"

    ARGS=()
    if [ -n "$COMP" ]; then ARGS+=("$COMP"); fi
    if [ -n "$OUTPUT" ]; then ARGS+=("$OUTPUT"); fi

    # Pass remaining flags through
    ARGS+=("$@")

    npx remotion render "${ARGS[@]}" 2>&1
    ;;

  still)
    PROJECT_DIR="${1:?Usage: remotion.sh still <project-dir> [composition] [output] [flags...]}"
    shift
    COMP="${1:-}"
    shift 2>/dev/null || true
    OUTPUT="${1:-}"
    shift 2>/dev/null || true

    cd "$PROJECT_DIR"

    ARGS=()
    if [ -n "$COMP" ]; then ARGS+=("$COMP"); fi
    if [ -n "$OUTPUT" ]; then ARGS+=("$OUTPUT"); fi
    ARGS+=("$@")

    npx remotion still "${ARGS[@]}" 2>&1
    ;;

  preview)
    PROJECT_DIR="${1:?Usage: remotion.sh preview <project-dir>}"
    cd "$PROJECT_DIR"
    npm run dev 2>&1
    ;;

  list)
    PROJECT_DIR="${1:?Usage: remotion.sh list <project-dir>}"
    cd "$PROJECT_DIR"
    npx remotion compositions 2>&1
    ;;

  upgrade)
    PROJECT_DIR="${1:?Usage: remotion.sh upgrade <project-dir>}"
    cd "$PROJECT_DIR"
    npx remotion upgrade 2>&1
    ;;

  help|*)
    echo "remotion.sh - Remotion video toolkit"
    echo ""
    echo "Commands:"
    echo "  init <name>                    Scaffold new project"
    echo "  render <dir> [comp] [out]      Render video (mp4/webm/gif)"
    echo "  still <dir> [comp] [out]       Render single frame"
    echo "  preview <dir>                  Start dev server"
    echo "  list <dir>                     List compositions"
    echo "  upgrade <dir>                  Upgrade Remotion"
    ;;
esac