{
  "slug": "paper-title-card",
  "title": "Paper title card",
  "tagline": "A sentence letterpress-stamps onto paper word by word, one word marked in italic accent color, closed off by a short dash.",
  "description": "A sentence letterpress-stamps onto paper word by word, one word marked in italic accent color, closed off by a short dash.",
  "category": "typography",
  "tags": [
    "shotcraft",
    "remotion",
    "typography",
    "transition",
    "rhythm"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 960,
    "height": 540,
    "fps": 30,
    "duration": 1.83,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/paper-title-card/poster-4e6f23920c42.jpg",
    "video": "https://gimgs.net/motion/media/paper-title-card/video-96cf09acb034.mp4"
  },
  "variants": [
    {
      "key": "paper-title-card",
      "label": "paper-title-card",
      "description": "A sentence letterpress-stamps onto paper word by word, one word marked in italic accent color, closed off with a short dash.",
      "use": "",
      "video": "https://gimgs.net/motion/media/paper-title-card/video-96cf09acb034.mp4",
      "poster": "https://gimgs.net/motion/media/paper-title-card/poster-4e6f23920c42.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/paper-title-card.html",
  "json_url": "https://gimgs.net/motion/paper-title-card.json",
  "template_url": "https://gimgs.net/motion/paper-title-card/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/paper-title-card.md",
  "updated_at": "2026-09-24T07:40:13.059Z",
  "prompt": "## When to use\n\nA chapter transition / value-prop title card; a lead-in card before an important feature appears; a breathing beat for the whole film\n\n## Intention\n\nGive the viewer a sentence's worth of breathing room between two product sequences: state clearly \"what's next, and why it matters.\" The letterpress-stamp texture puts the title card in the same world as paper-and-ink-styled product visuals.\n\n## Duration & energy\n\n- Duration: 1.7–1.8s (50–55f)\n- Energy: Low (a breathing beat separating two high-energy shots)\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n在两段产品画面之间给观众一句话的喘息：说清\"接下来看什么、它值什么\"。letterpress 压印质感让字卡与纸墨风格的产品画面同属一个世界。\n\n## 动效核心\n- 单句文案逐词入场：scale 从大压到 1 + blur→0 + opacity（letterpress 压印配方）\n- 每句恰好一个重点词：italic + 强调色（模板片为琥珀）\n- 强调色短下划线 scaleX 0→1 收束\n- 尾部整卡淡出交棒；可挂 mono 小字副行 + DigitRoll 数字\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| 逐词入场 | 第 i 词 delay = 4 + i·4、9f，bezier(0.2,0.75,0.3,1)；scale 1.28→1 + blur 7px→0 + opacity | scale 起点 1.28 是\"压印\"读感的甜点——对照暗场版 TitleCard 用 40px 上移+blur 10，同配方明暗两版 |\n| 重点词 | italic + 强调色 oklch(52% 0.115 65)，一句恰好一个 accent | 两个 accent 等于没有 accent；重点词选功能名/收益词（C2） |\n| 下划线 | 16→34f scaleX 0→1，220×6px 强调色 | 下划线是收束信号，出现即预告本卡快结束 |\n| 时长 | 全片 4 张字卡均 50–55f（≈1.8s） | 字卡时长定式化——观众读一句话的时间，长了拖节奏短了读不完 |\n| 版式 | 字号 116/serif；纸底 oklch(97.5% 0.008 82) + 中心暖光 radial；尾部 8f 淡出 | 纸底+暖光与产品画面同色系，字卡才不像广告插播 |\n| 副行 | mono 小字 + DigitRoll（如 \"5 of 31 fetched today\"）；digit-roll 参数见 list-stack-press 卡计数器行 | 副行给具体数字最有说服力；数字滚动必须在本卡淡出前落定（教训）——排节拍从本卡淡出帧倒推 |\n\n## 声音\n字卡出场统一钉 swoosh-quick（模板片四张字卡钉在 f220/565/725/885 同音）——同类元素同音是词汇表纪律（S2）；字卡本身无落地重音，能量留给前后镜头。\n\n## 已知坑\n- 文案要具体：带产品功能名+具体收益，抽象隐喻词被用户逐字改掉（C2——\"one board\"→\"one place to go\"、加 \"Paper Radar\"）；文案在画面锁定后按最终镜头重写一遍（C1）\n- 重要功能出场前要有引导字卡（C2 判例：文档镜头前应加一句引导，如 \"Every project, linked to your weekly report\"）——字卡不是装饰，是章节路标\n\n## 参考实现\ndemos/typography/paper-title-card/PaperTitleCard.tsx（原 template/src/aifl/PaperTitleCard.tsx，参数化：words/sub/subDigits 可换文案）\n\n## Reference implementation (Remotion)\n\n- demos/typography/paper-title-card/PaperTitleCard.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/paper-title-card/PaperTitleCard.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/paper-title-card/PaperTitleCard.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/paper-title-card/PaperTitleCard.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/paper-title-card/PaperTitleCard.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": 55,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/paper-title-card/PaperTitleCard.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/paper-title-card/PaperTitleCard.tsx =====\n// paper-title-card —— 一句话逐词 letterpress 压印上纸字卡\n// 章节转场/价值主张呼吸位：单句文案逐词压印入场（scale 大→1 + blur→0），\n// 每句恰一个强调色斜体重点词，强调色下划线 scaleX 收束，尾部整卡淡出。\n// 全片多张字卡统一 50–55f；纸底+中心暖光与纸墨风格产品画面同色系。\n// 参数化：words/sub/subDigits 可换成目标产品文案；subDigits 走 DigitRoll\n// 数字滚动（必须在本卡淡出前落定）。渲染本 demo 前把 text 复制进项目即可。\nimport React from 'react';\nimport { AbsoluteFill, interpolate, useCurrentFrame, Easing } from 'remotion';\n\nexport const PAPER_TITLE_CARD_DURATION = 55; // ≈1.8s @30fps\n\nconst SERIF = 'ui-serif, Georgia, \"Times New Roman\", serif';\nconst MONO = 'ui-monospace, SFMono-Regular, Menlo, monospace';\n\n// 每句恰好一个 accent：功能名/收益词（C2）\nconst WORDS: { text: string; accent?: boolean }[] = [\n  { text: 'All' },\n  { text: 'your' },\n  { text: 'team’s' },\n  { text: 'research,' },\n  { text: 'one', accent: true },\n  { text: 'place' },\n  { text: 'to' },\n  { text: 'go.' },\n];\nconst SUB = 'of 31 fetched today';\nconst SUB_DIGITS = '5';\n\n// 数字滚动列（odometer 风格，复制自 template DigitRoll，数字带双份拼接保证\n// 任何目标位都有滚感；tabular-nums 防横向抖）\nconst DIGITS = '0123456789';\nconst DigitColumn: React.FC<{ ch: string; delay: number; lineH: number; color: string }> = ({ ch, delay, lineH, color }) => {\n  const frame = useCurrentFrame();\n  if (ch < '0' || ch > '9') {\n    return <span style={{ fontSize: 26, lineHeight: `${lineH}px`, color }}>{ch}</span>;\n  }\n  const target = DIGITS.indexOf(ch);\n  const t = interpolate(frame, [delay, delay + 22], [0, 1], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n    easing: Easing.bezier(0.25, 0.8, 0.25, 1),\n  });\n  const offset = (10 + target) * t * lineH;\n  return (\n    <span style={{ display: 'inline-block', height: lineH }}>\n      <span style={{ display: 'block', transform: `translateY(${-offset}px)` }}>\n        {(DIGITS + DIGITS).split('').map((d, j) => (\n          <span\n            key={j}\n            style={{\n              display: 'block',\n              fontSize: 26,\n              lineHeight: `${lineH}px`,\n              color,\n              fontVariantNumeric: 'tabular-nums',\n            }}\n          >\n            {d}\n          </span>\n        ))}\n      </span>\n    </span>\n  );\n};\n\nconst DigitRoll: React.FC<{ value: string; delay: number; color: string }> = ({ value, delay, color }) => {\n  const lineH = 26 * 1.15;\n  return (\n    <span style={{ display: 'inline-flex', overflow: 'hidden', height: lineH, verticalAlign: 'bottom' }}>\n      {value.split('').map((c, i) => (\n        <DigitColumn key={i} ch={c} delay={delay + i * 4} lineH={lineH} color={color} />\n      ))}\n    </span>\n  );\n};\n\nexport const PaperTitleCard: React.FC = () => {\n  const frame = useCurrentFrame();\n  const duration = PAPER_TITLE_CARD_DURATION;\n  const fadeOut = interpolate(frame, [duration - 8, duration], [1, 0], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n  const underline = interpolate(frame, [16, 34], [0, 1], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n    easing: Easing.bezier(0.3, 0, 0.2, 1),\n  });\n  const subT = interpolate(frame, [10, 22], [0, 1], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n  });\n\n  return (\n    <AbsoluteFill\n      style={{\n        backgroundColor: 'oklch(97.5% 0.008 82)',\n        justifyContent: 'center',\n        alignItems: 'center',\n        opacity: fadeOut,\n        backgroundImage:\n          'radial-gradient(1100px 750px at 50% 42%, oklch(99.3% 0.014 88 / 0.85), transparent 65%)',\n      }}\n    >\n      <div style={{ textAlign: 'center', maxWidth: 1500 }}>\n        <div\n          style={{\n            fontFamily: SERIF,\n            fontSize: 116,\n            fontWeight: 600,\n            lineHeight: 1.14,\n            color: 'oklch(18% 0.006 82)',\n            letterSpacing: '-0.012em',\n            display: 'flex',\n            flexWrap: 'wrap',\n            justifyContent: 'center',\n            columnGap: '0.26em',\n          }}\n        >\n          {WORDS.map((w, i) => {\n            const delay = 4 + i * 4;\n            const t = interpolate(frame, [delay, delay + 9], [0, 1], {\n              extrapolateLeft: 'clamp',\n              extrapolateRight: 'clamp',\n              easing: Easing.bezier(0.2, 0.75, 0.3, 1),\n            });\n            return (\n              <span\n                key={i}\n                style={{\n                  opacity: t,\n                  transform: `scale(${1.28 - 0.28 * t})`,\n                  filter: `blur(${(1 - t) * 7}px)`,\n                  display: 'inline-block',\n                  fontStyle: w.accent ? 'italic' : 'normal',\n                  color: w.accent ? 'oklch(52% 0.115 65)' : undefined,\n                }}\n              >\n                {w.text}\n              </span>\n            );\n          })}\n        </div>\n        <div\n          style={{\n            height: 6,\n            width: 220,\n            margin: '38px auto 0',\n            borderRadius: 3,\n            background: 'oklch(52% 0.115 65)',\n            transform: `scaleX(${underline})`,\n          }}\n        />\n        <div\n          style={{\n            fontFamily: MONO,\n            fontSize: 26,\n            letterSpacing: '0.12em',\n            color: 'oklch(50% 0.006 82)',\n            marginTop: 34,\n            opacity: subT,\n            textTransform: 'uppercase',\n            display: 'flex',\n            justifyContent: 'center',\n            alignItems: 'baseline',\n            gap: '0.5em',\n          }}\n        >\n          <DigitRoll value={SUB_DIGITS} delay={12} color=\"oklch(52% 0.115 65)\" />\n          <span>{SUB}</span>\n        </div>\n      </div>\n    </AbsoluteFill>\n  );\n};\n\n\n"
}