{
  "slug": "brand-ink-open",
  "title": "Brand Ink Open",
  "tagline": "An ink-line crosshair draws itself → the wordmark stamps in letter by letter → a typewriter subtitle → a full second of stillness, then it floats up and dissolves.",
  "description": "An ink-line crosshair draws itself → the wordmark stamps in letter by letter → a typewriter subtitle → a full second of stillness, then it floats up and disso",
  "category": "intro",
  "tags": [
    "shotcraft",
    "remotion",
    "intro",
    "opening",
    "typography"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 960,
    "height": 540,
    "fps": 30,
    "duration": 2.77,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/brand-ink-open/poster-a28ce0b353e2.jpg",
    "video": "https://gimgs.net/motion/media/brand-ink-open/video-1b566ce6d86e.mp4"
  },
  "variants": [
    {
      "key": "brand-ink-open",
      "label": "brand-ink-open",
      "description": "An ink-line crosshair draws itself → the wordmark stamps in letter by letter → a typewriter subtitle → a full second of stillness, then it floats up and dissolves.",
      "use": "",
      "video": "https://gimgs.net/motion/media/brand-ink-open/video-1b566ce6d86e.mp4",
      "poster": "https://gimgs.net/motion/media/brand-ink-open/poster-a28ce0b353e2.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/brand-ink-open.html",
  "json_url": "https://gimgs.net/motion/brand-ink-open.json",
  "template_url": "https://gimgs.net/motion/brand-ink-open/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/brand-ink-open.md",
  "updated_at": "2026-09-24T07:39:46.244Z",
  "prompt": "## When to use\n\nBrand opens; any opener that \"establishes the name before entering the product.\"\n\n## Intention\n\nGive the brand its memory point in the very first beat: before any product screen appears, let viewers see and remember the name clearly. Quiet, papery/ink texture, with one complete moment of stillness.\n\n## Duration & energy\n\n- Duration: About 2.8s (83f).\n- Energy: Low (a starting position, leaving room to climb in later shots).\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n第一拍先给品牌记忆点：观众在任何产品画面出现之前，先看清并记住名字。安静、纸墨质感、有一个完整的静止时刻。\n\n## 动效核心\n- SVG 十字准星笔画描画（pathLength dashoffset），先竖后横，画完淡出\n- 字标逐字 letterpress：scale 从大压到 1 + blur→0，字底强调色（模板片为琥珀）glint 短划闪过\n- kicker 副标打字机逐字符出现，强调色块光标周期闪烁\n- lockup 完整静止满 1 秒，然后整体上浮 + 缩小 + 淡出，交棒 dashboard\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| 准星描画 | 竖线 0→9f、横线 8→18f，24→34f 淡出 | 描画完必须淡出，残留准星会和字标抢焦点 |\n| 字标逐字 | 第 i 字 delay=10+i·3、12f；scale 1.6→1（origin center bottom）+ blur 6px→0 | \"opacity+位移/scale+blur→0\"是全片入场三件套定式，缺 blur 会显得硬 |\n| kicker 打字机 | 0.7f/字符（28 起 ~43.4 完），光标 2f 周期闪、74f 停闪 | 0.7f/字符只适用于装饰性小字；正文交互打字要 3f/字符（对照 type-and-filter，用户嫌快返工过） |\n| 品牌 hold | 46→76 帧整整 1 秒 | 硬底线：用户两轮点名\"出现后延长停留1秒\"（R1）；短于 1s 必返工 |\n| 退场 | 7f 上浮 40px + 缩 12% + 淡出 | 退场快于入场——观众已读完，拖长反而泄气 |\n\n## 声音\n品牌落定钉 transition-soft（模板片钉在 f12），brand→dashboard 运镜钉 whoosh-fast（f78）。SFX 逐拍钉帧、声明式表管理（S2）；音色走电影系词汇，不用游戏 UI tap（S1）。\n\n## 已知坑\n- hold 要给的是 wordmark 落定时刻，不是普通字卡/内容卡（R1）——曾把 hold 错加给普通内容卡后很快回滚；指代含糊的\"停留\"反馈先确认对象再动手（P3）\n- 开场能量必须低起步：全片能量曲线低开→中段推进→outro 峰值，开场炫技会压死后面的爬升（对照 Q8 的能量曲线要求）\n\n## 参考实现\ndemos/typography/brand-ink-open/BrandInkOpen.tsx（原 template/src/aifl/live/SceneOpen.tsx 帧 0–83 段）\n\n## Reference implementation (Remotion)\n\n- demos/typography/brand-ink-open/BrandInkOpen.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/brand-ink-open/BrandInkOpen.tsx\n\nThe component imports shared fixtures from `demos/_fixtures` and helpers from `assets/lib` in the repo; SKILL.md there documents the render workflow.\n",
  "flow": [
    {
      "step": "Read the recipe",
      "note": "Prompt = when to use, intention, parameter table with typical values and how each one feels, known pitfalls. Treat values as calibrated starting points."
    },
    {
      "step": "Get the code",
      "tool": "git",
      "cmd": "git clone https://github.com/Vincentwei1021/video-shotcraft && cd video-shotcraft && npm install"
    },
    {
      "step": "Register + render",
      "tool": "remotion",
      "cmd": "npx remotion render <CompositionId> out.mp4   # component: demos/typography/brand-ink-open/BrandInkOpen.tsx"
    },
    {
      "step": "Or re-implement",
      "note": "Port the timing/easing from the recipe to HTML + WAAPI (gimgs motion contract) or any engine; keep the same segment windows and easing."
    }
  ],
  "files": [
    {
      "path": "demos/typography/brand-ink-open/BrandInkOpen.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/brand-ink-open/BrandInkOpen.tsx"
    }
  ],
  "contract": "React/Remotion composition (TSX) from the open-source video-shotcraft library (Apache-2.0). It is not a browser-playable template: clone the repo, register the component in a Remotion project and render with `npx remotion render`. The recipe (prompt) describes timing, easing and parameters so you can also re-implement the shot in any engine.",
  "render": {
    "engine": "remotion",
    "frames": 83,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/brand-ink-open/BrandInkOpen.tsx"
    ],
    "note": "Clone https://github.com/Vincentwei1021/video-shotcraft (npm install). The component lives under demos/{category}/{name}/ and imports shared fixtures from demos/_fixtures and helpers from assets/lib. Copy it (with those imports) into a Remotion project, register a <Composition> with the template's width/height/fps/duration in Root.tsx, then `npx remotion render <CompositionId> out.mp4`. SKILL.md in the repo documents the full Claude Code / Codex workflow. The prompt below is the shot recipe (timing, easing, parameter table, pitfalls) — enough to re-implement the shot in HTML/WAAPI or another engine if you prefer."
  },
  "source": "// ===== demos/typography/brand-ink-open/BrandInkOpen.tsx =====\n// brand-ink-open —— 墨线十字准星描画 → 字标逐字 letterpress → 打字机副标\n// → 满一秒静止 → 上浮消散。品牌开场第一拍：任何产品画面出现前先立名号。\n// 参考实现从 template SceneOpen 帧 0–83 段剥离，self-contained：\n// 十字准星 SVG pathLength dashoffset 描画后淡出；字标逐字从大 scale 压到 1 +\n// blur→0（入场三件套定式），字底强调色 glint 短划闪过；kicker mono 打字机\n// 逐字符 + 强调色块光标周期闪；46–76f 整整 1s 静止 hold；退场 7f 上浮+缩+淡。\n// 品牌名/副标/强调色可换成目标品牌。\nimport { AbsoluteFill, interpolate, useCurrentFrame, Easing } from 'remotion';\n\nexport const BRAND_INK_OPEN_DURATION = 104;\n\nconst SERIF = 'ui-serif, Georgia, \"Times New Roman\", serif';\nconst MONO = 'ui-monospace, SFMono-Regular, Menlo, monospace';\nconst INK = 'oklch(18% 0.006 82)';\nconst AMBER = 'oklch(52% 0.115 65)';\nconst INK2 = 'oklch(50% 0.006 82)';\n\nconst WORDMARK = 'AI Foundation Lab';\nconst KICKER = 'TEAM RESEARCH CONSOLE';\n\nexport const BrandInkOpen: React.FC = () => {\n  const frame = useCurrentFrame();\n\n  // --- crosshair draw-on (SVG pathLength = 100): vertical 0→9f, horizontal\n  // 8→18f, then fades 24→34f so it doesn't fight the wordmark ---\n  const vDraw = interpolate(frame, [0, 9], [100, 0], {\n    extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.bezier(0.3, 0, 0.2, 1),\n  });\n  const hDraw = interpolate(frame, [8, 18], [100, 0], {\n    extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.linear,\n  });\n  const crossFade = interpolate(frame, [24, 34], [1, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' });\n\n  // --- kicker typewriter (28 → ~43), 0.7 frames per char (decorative small\n  // text only — real interaction typing should be 3f/char, see type-and-filter) ---\n  const perChar = 0.7;\n  const kickStart = 28;\n  const kickChars = Math.floor(Math.max(0, frame - kickStart) / perChar);\n  const kickDone = kickStart + KICKER.length * perChar;\n  const cursorOn = (() => {\n    if (frame < kickStart) return false;\n    if (frame < kickDone) return true;\n    if (frame > 95) return false;\n    const b = frame - kickDone;\n    return Math.floor(b / 2) % 2 === 0;\n  })();\n\n  // --- brand group rests fully-on for ~1s (wordmark completes ~67f; the last\n  // glyph is done at 10+15*3+12=67), then dissolves out (97 → 104): lift +\n  // shrink + fade. This leaves a clean ~30-frame hold of the COMPLETE title\n  // (67→97), per the card's \"完整标题停留 ~30 帧\" requirement. ---\n  const brandOut = interpolate(frame, [97, 104], [0, 1], {\n    extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.bezier(0.4, 0, 0.5, 1),\n  });\n  const brandOpacity = 1 - brandOut;\n  const groupY = -brandOut * 40;\n  const groupScale = 1 - brandOut * 0.12;\n\n  return (\n    <AbsoluteFill style={{ backgroundColor: '#faf7f2', justifyContent: 'center', alignItems: 'center' }}>\n      <div style={{ textAlign: 'center', opacity: brandOpacity, transform: `translateY(${groupY}px) scale(${groupScale})` }}>\n        {/* crosshair drawn by an invisible amber pen */}\n        <svg width={64} height={64} viewBox=\"0 0 64 64\" style={{ display: 'block', margin: '0 auto 34px', opacity: crossFade }}>\n          <line\n            x1={32} y1={2} x2={32} y2={62}\n            stroke={AMBER} strokeWidth={5} strokeLinecap=\"round\"\n            pathLength={100} strokeDasharray={100} strokeDashoffset={vDraw}\n          />\n          <line\n            x1={2} y1={32} x2={62} y2={32}\n            stroke={AMBER} strokeWidth={5} strokeLinecap=\"round\"\n            pathLength={100} strokeDasharray={100} strokeDashoffset={hDraw}\n          />\n        </svg>\n\n        {/* wordmark: glyph-by-glyph letterpress with amber under-glint */}\n        <div\n          style={{\n            fontFamily: SERIF, fontSize: 132, fontWeight: 600, color: INK,\n            letterSpacing: '-0.01em', lineHeight: 1, whiteSpace: 'pre',\n            display: 'inline-flex', alignItems: 'flex-end',\n          }}\n        >\n          {WORDMARK.split('').map((ch, i) => {\n            const delay = 10 + i * 3;\n            const t = interpolate(frame, [delay, delay + 12], [0, 1], {\n              extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.bezier(0.2, 0.7, 0.25, 1),\n            });\n            const glintCenter = delay + 12;\n            const glint = interpolate(frame, [glintCenter - 4, glintCenter, glintCenter + 4], [0, 1, 0], {\n              extrapolateLeft: 'clamp', extrapolateRight: 'clamp',\n            });\n            return (\n              <span\n                key={i}\n                style={{\n                  position: 'relative', display: 'inline-block', opacity: t,\n                  transform: `scale(${1.6 - 0.6 * t})`, transformOrigin: 'center bottom',\n                  filter: `blur(${(1 - t) * 6}px)`,\n                }}\n              >\n                {ch === ' ' ? ' ' : ch}\n                <span\n                  style={{\n                    position: 'absolute', left: '50%', bottom: -6, transform: 'translateX(-50%)',\n                    width: `${glint * 100}%`, height: 2, background: AMBER, opacity: glint, borderRadius: 2,\n                  }}\n                />\n              </span>\n            );\n          })}\n        </div>\n\n        {/* mono kicker typewriter + amber block cursor */}\n        <div\n          style={{\n            fontFamily: MONO, fontSize: 26, letterSpacing: '0.14em', color: INK2,\n            marginTop: 30, textTransform: 'uppercase', height: 30,\n            display: 'flex', justifyContent: 'center', alignItems: 'center',\n          }}\n        >\n          <span style={{ whiteSpace: 'pre' }}>{KICKER.slice(0, kickChars)}</span>\n          <span\n            style={{\n              display: 'inline-block', width: 14, height: 24, marginLeft: 4,\n              background: AMBER, opacity: cursorOn ? 0.85 : 0,\n            }}\n          />\n        </div>\n      </div>\n    </AbsoluteFill>\n  );\n};\n\n\n"
}