{
  "slug": "typing-code-block",
  "title": "Two code reveals, side by side",
  "tagline": "The same syntax-highlighted code reveals two ways side by side — left: line-level stagger fading up 8px, right: character-by-character typing keeping each token's original color, with a #3a4468 block cursor on the current character.",
  "description": "The same syntax-highlighted code reveals two ways side by side — left: line-level stagger fading up 8px, right: character-by-character typing keeping each tok",
  "category": "typography",
  "tags": [
    "shotcraft",
    "remotion",
    "typography"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 5.1,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/typing-code-block/poster-ebdf1fc4ecb2.jpg",
    "video": "https://gimgs.net/motion/media/typing-code-block/video-52fa238fb669.mp4"
  },
  "variants": [
    {
      "key": "typing-code-block",
      "label": "typing-code-block",
      "description": "The same syntax-highlighted code reveals two ways side by side — left: line-level stagger fading up 8px over 4f, right: character-by-character typing keeping each token's original color, with a #3a4468 block cursor on the current character",
      "use": "",
      "video": "https://gimgs.net/motion/media/typing-code-block/video-52fa238fb669.mp4",
      "poster": "https://gimgs.net/motion/media/typing-code-block/poster-ebdf1fc4ecb2.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/typing-code-block.html",
  "json_url": "https://gimgs.net/motion/typing-code-block.json",
  "template_url": "https://gimgs.net/motion/typing-code-block/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/typing-code-block.md",
  "updated_at": "2026-09-24T07:40:32.744Z",
  "prompt": "## When to use\n\nCode/config reveal shots; the \"just three lines\" demo for developer products; a side-by-side reference for choosing between two reveal paces\n\n## Intention\n\nThere are really only two honest solutions for revealing code: **whole lines floating up** (reading structure) and **typing one character at a time** (reading process). This card puts both side by side on the same code block as a reference for choosing between them: the left side, 4 lines in 4 beats, done — good for \"the code isn't the point, the result is\"; the right side, 138 frames to type 59 characters — good for \"watch me write it.\" The key detail is that the right side **never loses its color while typing** — the common mistake is typing in white first and coloring it after, which degrades into a terminal echo, not a code editor.\n\n## Duration & energy\n\n- Duration: ~4.6s (138f@30fps)\n- Energy: Medium (the right side keeps typing continuously, the left side resolves in one beat)\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n代码揭示只有两种真解法：**整行浮出来**（读的是结构）和**一个字一个字打**\n（读的是过程）。这卡把两者并排放在同一段代码上，是选型用的对照镜：\n左侧 4 行 4 拍就完，适合\"代码不是重点、结果才是\"；右侧 138 帧才打完\n59 个字符，适合\"看我写\"。关键细节是右侧**打字不丢色**——常见错误是\n打字时先出白字再着色，那就退化成终端回显，不是代码编辑器。\n\n## 动效核心\n- token 模型：每行是 `[[文本, 颜色], …]`，6 色板（关键字 #c792ea、\n  标识 #e8eaf0、函数 #82aaff、字符串 #c3e88d、标点 #89ddff、注释 #546e7a）\n- **左侧行级 stagger**：`seg(t, 0.08+i·0.14, +0.3, E.outCubic)`——行间 0.14\n  （≈19f）、单行窗 0.3（≈41f），`opacity = k`、`translateY = (1-k)·8`px\n- **右侧逐字符**：`typed = floor(seg(t, 0.08, 0.9) · chars.length)` 线性推进，\n  每个字符 span 建的时候就带 token 色，打到时只翻 opacity 0→1 ——\n  **着色在 setup 期完成，render 期不碰颜色**，所以字一出来就是对的色\n- 光标是背景块不是竖线：`i === typed` 的字符 `background:#3a4468` 且强制\n  `opacity:1` —— 下一个待打字符先以色块占位，块往右挪即是光标移动\n- 两侧面板同规格（45% 宽、72% 高、#10121a 底 + #1c2030 边框、圆角 8），\n  顶部 8px 字距 2px 的小标签 `LINE FADE-IN` / `CHAR TYPING` 标注手法\n- 右侧行 `min-height:1.9em` 撑住空行——不撑的话未打的行高度为 0，\n  面板内容会随打字往下长（跳版）\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| 代码量 | 4 行 / 约 59 字符 | 打字速度 = 字符数 / 113f；行数超过 6 行面板要缩字号，超过 100 字符右侧打不完 |\n| 行 stagger | 0.14（≈19f），单行窗 0.3（≈41f） | 19f 是\"逐行\"可读下限；<8f 四行齐亮没有 stagger，>28f 最后一行等太久 |\n| 行位移 | `(1-k)·8`px 上浮 | 8px 是代码块该有的克制幅度；>20px 行像卡片飞进来，抢了代码本身 |\n| 打字窗 | `seg(t, 0.08, 0.9)`（11f→124f） | 结束点留 14f 静置读全文；推到 1.0 打完即切，最后一行没人读 |\n| 光标块 | `#3a4468` 背景，仅当前字符 | 方块光标是编辑器语感；换成竖线要另加闪烁，代码块里反而更碎 |\n| 面板 | 45%×72%、`#10121a` 底、`#1c2030` 边框、圆角 8、12px/1.9 行高 | 1.9 行高是代码可读下限；两侧必须同规格，尺寸不一就不是对照而是两个镜头 |\n| 空行占位 | 右侧行 `min-height:1.9em` | 不设则面板随打字长高，整块跳版——这是本卡最容易漏的一处 |\n\n## 已知坑\n- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿，\n  首次实战须以真实素材回验\n- **换成项目代码时字符总数要贴近 59**：右侧打字是把 113f 平摊到全部\n  字符上的，代码翻倍则每字符不到 1 帧（糊成整行刷出），代码减半则一字\n  一拍慢得像卡顿——改代码量必须同步调打字窗，别只换文案\n- 着色必须在 setup 期写进每个 span：render 期改色（先白后染）会把\n  \"代码编辑器\"降级成\"终端回显\"，这是同类效果最常见的失手\n- 左侧行级淡入与右侧逐字符**不同步**（左侧 4 行 ~68f 就收完，右侧要打到\n  124f），这是对照镜的本意；若实战只留一侧，另一侧的时间窗要重新铺满全片\n- token 数组是手写的，没有真词法分析——换代码时得手动切 token；\n  切错色比不着色更糟（关键字被染成字符串色，懂代码的观众立刻出戏）\n- 实战里通常**只留一侧**：对照版是选型镜，正片里两块面板一起动会分散\n  注意力，选定手法后单面板居中放大\n\n## 参考实现\ndemos/typography/typing-code-block/\n（TypingCodeBlock.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/typography/typing-code-block/TypingCodeBlock.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/typing-code-block/TypingCodeBlock.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/typing-code-block/TypingCodeBlock.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/typing-code-block/TypingCodeBlock.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/typing-code-block/TypingCodeBlock.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": 153,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/typing-code-block/TypingCodeBlock.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/typing-code-block/TypingCodeBlock.tsx =====\n// typing-code-block — Code Block Reveal 代码块揭示（motion-lab 定稿转原生 Remotion）\n// 同一段语法高亮代码的两种 reveal 对照：左侧逐行淡入上浮（行级 stagger），\n// 右侧逐字符打字但保持 token 着色，当前字符位以方块底色块提示光标。\n// 设计坐标 480×270（DesignStage 等比放大），参数表数值以此坐标系标定。\nimport React from 'react';\nimport { DesignStage, E, seg, useT } from '../../_fixtures/Motion';\n\nexport const TYPING_CODE_BLOCK_DURATION = 138; // 4600ms @30fps\n\n// token 配色：关键字/标识符/函数/字符串/标点/注释\nconst K = '#c792ea';\nconst ID = '#e8eaf0';\nconst FN = '#82aaff';\nconst ST = '#c3e88d';\nconst PU = '#89ddff';\nconst CM = '#546e7a';\n// 每行是 [text, color] token 序列\nconst LINES: [string, string][][] = [\n  [['const ', K], ['app', ID], [' = ', PU], ['createApp', FN], ['();', ID]],\n  [['app', ID], ['.', PU], ['use', FN], ['(', ID], ['router', ID], [');', ID]],\n  [['app', ID], ['.', PU], ['mount', FN], ['(', ID], [\"'#root'\", ST], [');', ID]],\n  [['// ready', CM]],\n];\n// 右侧打字用的逐字符扁平序列（保留 token 颜色 + 行号）\nconst FLAT: { ch: string; color: string; row: number }[] = [];\nLINES.forEach((line, row) => {\n  for (const [txt, color] of line) for (const ch of txt) FLAT.push({ ch, color, row });\n});\n\n// 代码面板容器：左右两块共用的外框 + 顶部小标签\nconst Panel: React.FC<{ x: number; label: string; children: React.ReactNode }> = ({\n  x,\n  label,\n  children,\n}) => (\n  <div\n    style={{\n      position: 'absolute',\n      left: `${x}%`,\n      top: '14%',\n      width: '45%',\n      height: '72%',\n      background: '#10121a',\n      border: '1px solid #1c2030',\n      borderRadius: 8,\n      padding: '10px 12px',\n      boxSizing: 'border-box',\n      fontFamily: '\"SF Mono\",Menlo,monospace',\n      fontSize: 12,\n      lineHeight: 1.9,\n    }}\n  >\n    <div style={{ fontSize: 8, letterSpacing: 2, color: '#4a5270', marginBottom: 6 }}>{label}</div>\n    {children}\n  </div>\n);\n\nexport const TypingCodeBlock: React.FC = () => {\n  const t = useT();\n  // 右侧打字进度：t∈[0.08,0.9] 线性推进到全部字符\n  const typed = Math.floor(seg(t, 0.08, 0.9) * FLAT.length);\n  return (\n    <DesignStage bg=\"#0a0b10\">\n      {/* 左：逐行淡入上浮（行级 stagger） */}\n      <Panel x={3.5} label=\"LINE FADE-IN\">\n        {LINES.map((line, i) => {\n          const k = seg(t, 0.08 + i * 0.14, 0.08 + i * 0.14 + 0.3, E.outCubic);\n          return (\n            <div key={i} style={{ opacity: k, transform: `translateY(${(1 - k) * 8}px)` }}>\n              {line.map(([txt, color], j) => (\n                <span key={j} style={{ color }}>\n                  {txt}\n                </span>\n              ))}\n            </div>\n          );\n        })}\n      </Panel>\n      {/* 右：逐字符打字（保色），当前字符位带方块光标底色 */}\n      <Panel x={51.5} label=\"CHAR TYPING\">\n        {LINES.map((_, row) => (\n          <div key={row} style={{ minHeight: '1.9em' }}>\n            {FLAT.map((c, i) =>\n              c.row === row ? (\n                <span\n                  key={i}\n                  style={{\n                    color: c.color,\n                    // 光标位字符以底色块形式提示（保持可见）\n                    opacity: i < typed || i === typed ? 1 : 0,\n                    background: i === typed && typed < FLAT.length ? '#3a4468' : 'transparent',\n                  }}\n                >\n                  {c.ch}\n                </span>\n              ) : null,\n            )}\n          </div>\n        ))}\n      </Panel>\n    </DesignStage>\n  );\n};\n\n\n"
}