{
  "slug": "lower-third",
  "title": "Lower third — slide bar",
  "tagline": "Name and role slide in on a frosted bar, hold, then wipe out. Overlay-ready (transparent background). 5 s, 16:9.",
  "description": "Free CC0 lower-third motion template for interviews and talking-head videos: accent bar, frosted box, name + role, transparent background option. Self-contained HTML, agent-friendly.",
  "category": "lower-third",
  "tags": [
    "lower-third",
    "caption",
    "interview",
    "overlay"
  ],
  "tier": "free",
  "license": "CC0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 5,
    "aspect": "16:9"
  },
  "kind": "html",
  "params_schema": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string",
        "default": "Ada Lovelace",
        "maxLength": 40,
        "description": "Big line (person, place or topic)"
      },
      "role": {
        "type": "string",
        "default": "Founder, Analytical Engines",
        "maxLength": 60,
        "description": "Small uppercase line in the accent colour"
      },
      "accent": {
        "type": "string",
        "format": "color",
        "default": "#ffb224",
        "description": "Bar and role colour"
      },
      "align": {
        "type": "string",
        "enum": [
          "left",
          "right"
        ],
        "default": "left",
        "description": "Which side of the frame"
      },
      "transparent": {
        "type": "boolean",
        "default": false,
        "description": "true = transparent background for compositing over footage (render PNG frames with alpha, e.g. -c:v prores_ks -pix_fmt yuva444p10le or WebM VP9 with alpha)"
      },
      "holdSeconds": {
        "type": "number",
        "minimum": 1,
        "maximum": 4.4,
        "default": 3.6,
        "description": "When the out-animation starts (seconds). The clip is always 5 s"
      }
    }
  },
  "default_params": {
    "name": "Ada Lovelace",
    "role": "Founder, Analytical Engines",
    "accent": "#ffb224",
    "align": "left",
    "transparent": false,
    "holdSeconds": 3.6
  },
  "preview": {
    "poster": "https://gimgs.net/motion/media/lower-third/poster-8f9566c3b9f2.jpg",
    "video": ""
  },
  "variants": [],
  "credits": {
    "author": "gimgs",
    "url": "https://gimgs.net/motion"
  },
  "page_url": "https://gimgs.net/motion/lower-third.html",
  "json_url": "https://gimgs.net/motion/lower-third.json",
  "template_url": "https://gimgs.net/motion/lower-third/template.html",
  "play_url": "https://gimgs.net/motion/lower-third/play",
  "markdown_url": "https://gimgs.net/motion/lower-third.md",
  "updated_at": "2026-09-24T07:24:46.000Z",
  "prompt": "## When to use\nIdentify a speaker or a location in interview / documentary / podcast footage. Composite it over the video (set `transparent: true`, render with alpha) or use the built-in dark gradient for a stand-alone caption card.\n\n## How to adapt\n- `name` ≤ 24 characters keeps one line at 64px; longer names wrap.\n- `align: \"right\"` mirrors the whole layout for right-side framing.\n- `holdSeconds` controls how long the card stays before wiping out; the file is fixed at 5 s so cut the tail if you need it shorter.\n- Timeline: bar 0–0.5 s, box wipe 0.25–0.85 s, name 0.55–1.05 s, role 0.75–1.25 s, out from `holdSeconds` for 0.4 s.\n\n## Compositing\nRender PNG frames with `--default-background-color=00000000` (Chromium) or `omitBackground: true` (Playwright) when `transparent` is true, then `ffmpeg -framerate 30 -i frame-%04d.png -c:v prores_ks -pix_fmt yuva444p10le lower-third.mov`.\n\n## Do not change\n`window.motion`, `#stage` size, `animation-fill-mode: both`.",
  "flow": [
    {
      "step": "Set params",
      "note": "name, role, accent; transparent=true for overlays."
    },
    {
      "step": "Render with alpha",
      "cmd": "playwright: page.screenshot({ omitBackground: true }) per frame → ffmpeg -framerate 30 -i frame-%04d.png -c:v prores_ks -pix_fmt yuva444p10le out.mov"
    },
    {
      "step": "Composite",
      "cmd": "ffmpeg -i footage.mp4 -i out.mov -filter_complex overlay=0:0:enable='between(t,2,7)' final.mp4"
    }
  ],
  "files": [],
  "contract": "Self-contained HTML. Declares window.motion = { width, height, fps, duration, params, apply(params) [, seek(t)] }; root element #stage sized width×height; animations are CSS/WAAPI with animation-fill-mode: both so any t can be seeked via document.getAnimations(). The host runtime (injected by /play) merges ?p params, calls apply(), scales #stage to the viewport and, when ?t is given, pauses and seeks deterministically.",
  "render": {
    "frames": 150,
    "frame_url": "https://gimgs.net/motion/lower-third/play?p=<base64url(JSON params)>&t=<seconds>",
    "note": "Open frame_url in a headless browser with viewport width×height (deviceScaleFactor 1), wait for document.documentElement.dataset.motionReady === '1', screenshot; repeat for t = i/fps (i = 0…frames-1); then `ffmpeg -framerate <fps> -i frame-%04d.png -c:v libx264 -pix_fmt yuv420p out.mp4`. Or drop ?t to play it live."
  },
  "source": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Lower third — slide bar</title>\n<style>\n  @font-face{font-family:'Bricolage Grotesque';font-weight:600 800;font-display:block;src:url(https://gimgs.net/fonts/bricolage-600-latin.woff2) format('woff2')}\n  @font-face{font-family:'IBM Plex Sans';font-weight:400 600;font-display:block;src:url(https://gimgs.net/fonts/plexsans-400-latin.woff2) format('woff2')}\n  html,body{margin:0;background:#000}\n  #stage{position:relative;width:1920px;height:1080px;overflow:hidden;font-family:'IBM Plex Sans',system-ui,sans-serif;color:#fff;\n    background:var(--bg,linear-gradient(160deg,#1b2735,#090a0f))}\n  #stage.transparent{background:transparent}\n  .lt{position:absolute;bottom:120px;left:110px;display:flex;align-items:stretch;gap:0}\n  #stage.right .lt{left:auto;right:110px;flex-direction:row-reverse}\n  .bar{width:14px;background:var(--accent,#ffb224);transform-origin:50% 100%;animation:bar .5s cubic-bezier(.2,.8,.2,1) both,barOut .4s var(--out) ease-in both}\n  @keyframes bar{from{transform:scaleY(0)}to{transform:scaleY(1)}}\n  @keyframes barOut{to{transform:scaleY(0)}}\n  .box{position:relative;overflow:hidden;padding:22px 44px 26px 34px;background:rgba(10,12,18,.82);backdrop-filter:blur(10px);\n    clip-path:inset(0 100% 0 0);animation:box .6s .25s cubic-bezier(.2,.8,.2,1) both,boxOut .4s var(--out) ease-in both}\n  #stage.right .box{clip-path:inset(0 0 0 100%)}\n  @keyframes box{to{clip-path:inset(0 0 0 0)}}\n  @keyframes boxOut{to{clip-path:inset(0 0 0 100%)}}\n  #stage.right .box{animation-name:boxR,boxOutR}\n  @keyframes boxR{to{clip-path:inset(0 0 0 0)}}\n  @keyframes boxOutR{to{clip-path:inset(0 100% 0 0)}}\n  .name{font-family:'Bricolage Grotesque',Inter,system-ui,sans-serif;font-weight:800;font-size:64px;letter-spacing:-.02em;line-height:1.05;\n    transform:translateY(30px);opacity:0;animation:up .5s .55s cubic-bezier(.2,.8,.2,1) both}\n  .role{margin-top:8px;font-size:28px;font-weight:500;color:var(--accent,#ffb224);letter-spacing:.06em;text-transform:uppercase;\n    transform:translateY(24px);opacity:0;animation:up .5s .75s cubic-bezier(.2,.8,.2,1) both}\n  @keyframes up{to{transform:none;opacity:1}}\n</style>\n</head>\n<body>\n<div id=\"stage\">\n  <div class=\"lt\">\n    <div class=\"bar\"></div>\n    <div class=\"box\"><div class=\"name\" id=\"name\"></div><div class=\"role\" id=\"role\"></div></div>\n  </div>\n</div>\n<script>\nwindow.motion = {\n  width: 1920, height: 1080, fps: 30, duration: 5,\n  params: { name: \"Ada Lovelace\", role: \"Founder, Analytical Engines\", accent: \"#ffb224\", align: \"left\", transparent: false, holdSeconds: 3.6 },\n  apply: function (p) {\n    var s = document.getElementById(\"stage\");\n    s.style.setProperty(\"--accent\", p.accent);\n    // out animations start after the hold; the composition is 5 s so keep hold <= 4.4\n    var hold = Math.max(1, Math.min(4.4, +p.holdSeconds || 3.6));\n    s.style.setProperty(\"--out\", hold + \"s\");\n    s.classList.toggle(\"right\", p.align === \"right\");\n    s.classList.toggle(\"transparent\", !!p.transparent);\n    document.getElementById(\"name\").textContent = p.name;\n    document.getElementById(\"role\").textContent = p.role;\n  }\n};\n</script>\n</body>\n</html>\n"
}