{
  "slug": "flag-intro",
  "title": "Country flag intro",
  "tagline": "Colour band slides in, the round flag pops, the country name wipes on. Uses gimgs.net/flags. 4 s, 16:9.",
  "description": "Free CC0 country intro motion template: circular flag from gimgs.net/flags, ISO code, name and subtitle. Self-contained HTML, deterministic, agent-friendly.",
  "category": "intro",
  "tags": [
    "flag",
    "country",
    "travel",
    "geo",
    "intro"
  ],
  "tier": "free",
  "license": "CC0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 4,
    "aspect": "16:9"
  },
  "kind": "html",
  "params_schema": {
    "type": "object",
    "properties": {
      "country": {
        "type": "string",
        "default": "vn",
        "maxLength": 6,
        "description": "ISO 3166-1 alpha-2 code (vn, us, jp…) or a regional code like gb-eng — resolved to https://gimgs.net/flags/{code}.svg"
      },
      "name": {
        "type": "string",
        "default": "Vietnam",
        "maxLength": 24,
        "description": "Country / city name"
      },
      "subtitle": {
        "type": "string",
        "default": "Hanoi · GMT+7",
        "maxLength": 48,
        "description": "Small line under the name (capital, time zone, episode…)"
      },
      "accent": {
        "type": "string",
        "format": "color",
        "default": "#da251d",
        "description": "Band and code colour — take it from the flag"
      },
      "bg": {
        "type": "string",
        "format": "color",
        "default": "#f7f6f3",
        "description": "Background (light or dark; subtitle contrast adapts)"
      },
      "fg": {
        "type": "string",
        "format": "color",
        "default": "#1a1a1a",
        "description": "Name colour"
      }
    }
  },
  "default_params": {
    "country": "vn",
    "name": "Vietnam",
    "subtitle": "Hanoi · GMT+7",
    "accent": "#da251d",
    "bg": "#f7f6f3",
    "fg": "#1a1a1a"
  },
  "preview": {
    "poster": "https://gimgs.net/motion/media/flag-intro/poster-d577245ee98a.jpg",
    "video": ""
  },
  "variants": [],
  "credits": {
    "author": "gimgs",
    "url": "https://gimgs.net/motion"
  },
  "page_url": "https://gimgs.net/motion/flag-intro.html",
  "json_url": "https://gimgs.net/motion/flag-intro.json",
  "template_url": "https://gimgs.net/motion/flag-intro/template.html",
  "play_url": "https://gimgs.net/motion/flag-intro/play",
  "markdown_url": "https://gimgs.net/motion/flag-intro.md",
  "updated_at": "2026-09-24T07:24:52.272Z",
  "prompt": "## When to use\nChapter card for travel vlogs, country-by-country comparisons, sports/olympics recaps, geo-data explainers.\n\n## How to adapt\n- `country` is the ISO alpha-2 code; the flag is fetched from `https://gimgs.net/flags/{code}.svg` (circular, all countries + gb-eng/gb-sct/gb-wls). No upload needed.\n- Pick `accent` from the flag's dominant colour so the band and the flag rhyme (vn → #da251d, jp → #bc002d, br → #009c3b, us → #3c3b6e).\n- Dark variant: `bg: \"#0b0c10\", fg: \"#ffffff\"`; the subtitle switches to light automatically.\n- Timeline: band 0–0.7 s, flag 0.35–1.15 s, code 0.8–1.3 s, name wipe 0.95–1.75 s, subtitle 1.5–2.0 s; holds from 2 s.\n- Batch: one render per country, then concat — keep everything but `country/name/subtitle/accent` constant.\n\n## Do not change\n`window.motion`, `#stage` size, `animation-fill-mode: both`.",
  "flow": [
    {
      "step": "Pick codes",
      "note": "ISO alpha-2, lowercase."
    },
    {
      "step": "Render per country",
      "cmd": "for cc in vn jp br; do P=$(printf '{\"country\":\"%s\",\"name\":\"%s\"}' $cc $cc | base64 -w0 | tr '+/' '-_' | tr -d '='); …frames…; done"
    },
    {
      "step": "Concat",
      "cmd": "ffmpeg -f concat -safe 0 -i list.txt -c copy countries.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": 120,
    "frame_url": "https://gimgs.net/motion/flag-intro/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>Country flag intro</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 Mono';font-weight:400 500;font-display:block;src:url(https://gimgs.net/fonts/plexmono-500-latin.woff2) format('woff2')}\n  html,body{margin:0;background:#000}\n  #stage{position:relative;width:1920px;height:1080px;overflow:hidden;background:var(--bg,#f7f6f3);color:var(--fg,#1a1a1a);font-family:'Bricolage Grotesque',Inter,system-ui,sans-serif}\n  .band{position:absolute;left:0;top:0;bottom:0;width:38%;background:var(--accent,#ffb224);transform:translateX(-100%);animation:band .7s cubic-bezier(.2,.8,.2,1) both}\n  @keyframes band{to{transform:none}}\n  .flag{position:absolute;left:38%;top:50%;width:360px;height:360px;border-radius:50%;overflow:hidden;box-shadow:0 30px 80px rgba(0,0,0,.25);\n    transform:translate(-50%,-50%) scale(0);animation:flag .8s .35s cubic-bezier(.2,.9,.2,1.15) both}\n  .flag img{width:100%;height:100%;object-fit:cover;display:block}\n  @keyframes flag{to{transform:translate(-50%,-50%) scale(1)}}\n  .txt{position:absolute;left:calc(38% + 260px);top:50%;transform:translateY(-50%)}\n  .code{font-family:'IBM Plex Mono',ui-monospace,monospace;font-size:34px;letter-spacing:.3em;color:var(--accent,#ffb224);text-transform:uppercase;\n    opacity:0;transform:translateY(20px);animation:up .5s .8s cubic-bezier(.2,.8,.2,1) both}\n  .name{font-weight:800;font-size:132px;letter-spacing:-.03em;line-height:1;margin-top:10px;\n    clip-path:inset(0 100% 0 0);animation:wipe .8s .95s cubic-bezier(.2,.8,.2,1) both}\n  .sub{font-size:38px;font-weight:600;color:rgba(0,0,0,.55);margin-top:22px;opacity:0;transform:translateY(20px);animation:up .5s 1.5s cubic-bezier(.2,.8,.2,1) both}\n  #stage.dark .sub{color:rgba(255,255,255,.6)}\n  @keyframes up{to{transform:none;opacity:1}}\n  @keyframes wipe{to{clip-path:inset(0 0 0 0)}}\n</style>\n</head>\n<body>\n<div id=\"stage\">\n  <div class=\"band\"></div>\n  <div class=\"flag\"><img id=\"flag\" alt=\"\"></div>\n  <div class=\"txt\"><div class=\"code\" id=\"code\"></div><div class=\"name\" id=\"name\"></div><div class=\"sub\" id=\"sub\"></div></div>\n</div>\n<script>\nwindow.motion = {\n  width: 1920, height: 1080, fps: 30, duration: 4,\n  params: { country: \"vn\", name: \"Vietnam\", subtitle: \"Hanoi · GMT+7\", accent: \"#da251d\", bg: \"#f7f6f3\", fg: \"#1a1a1a\" },\n  apply: function (p) {\n    var s = document.getElementById(\"stage\");\n    s.style.setProperty(\"--accent\", p.accent); s.style.setProperty(\"--bg\", p.bg); s.style.setProperty(\"--fg\", p.fg);\n    // rough luminance check so the subtitle stays readable on dark backgrounds\n    var m = /^#?([0-9a-f]{6})$/i.exec(String(p.bg || \"\")), dark = false;\n    if (m) { var n = parseInt(m[1], 16); dark = (0.299 * (n >> 16) + 0.587 * ((n >> 8) & 255) + 0.114 * (n & 255)) < 128; }\n    s.classList.toggle(\"dark\", dark);\n    var cc = String(p.country || \"\").toLowerCase().replace(/[^a-z-]/g, \"\");\n    var img = document.getElementById(\"flag\");\n    var src = \"https://gimgs.net/flags/\" + cc + \".svg\";\n    if (img.getAttribute(\"src\") !== src) img.src = src;\n    document.getElementById(\"code\").textContent = cc;\n    document.getElementById(\"name\").textContent = p.name;\n    document.getElementById(\"sub\").textContent = p.subtitle;\n  }\n};\n</script>\n</body>\n</html>\n"
}