{
  "slug": "type-entrance-moves",
  "title": "Scramble decode vs letter drop",
  "tagline": "Two title-entrance moves — scramble-decode (an answer grows out of noise) and letter-drop-physics (letters drop with gravity, bounce, and settle), picked by tone.",
  "description": "Two title-entrance moves — scramble-decode (an answer grows out of noise) and letter-drop-physics (letters drop with gravity, bounce, and settle), picked by t",
  "category": "typography",
  "tags": [
    "shotcraft",
    "remotion",
    "typography"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 4.39,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/type-entrance-moves/poster-0bc9fb8dc6f4.jpg",
    "video": "https://gimgs.net/motion/media/type-entrance-moves/video-5485d50ba6aa.mp4"
  },
  "variants": [
    {
      "key": "scramble-decode",
      "label": "scramble-decode · Garbled decode",
      "description": "All letters hold at a fast garbled-character flicker, then lock in as real characters one by one from left to right, with a 2f color-invert flash on each lock and a progress bar advancing in sync at the bottom",
      "use": "Tech/developer products; a rational, \"the answer surfaces\" feel",
      "video": "https://gimgs.net/motion/media/type-entrance-moves/video-5485d50ba6aa.mp4",
      "poster": "https://gimgs.net/motion/media/type-entrance-moves/poster-0bc9fb8dc6f4.jpg"
    },
    {
      "key": "letter-drop-physics",
      "label": "letter-drop-physics · Letter drop",
      "description": "Letters drop from the top in staggered order, gravity-accelerating with two decaying bounces and landing crooked, then all snap upright together on the final beat",
      "use": "A light-toned opener; a \"living thing\" icebreaker for the title",
      "video": "https://gimgs.net/motion/media/type-entrance-moves/video-a052b1d70dfe.mp4",
      "poster": "https://gimgs.net/motion/media/type-entrance-moves/poster-letter-drop-physics-ac97653038c4.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/type-entrance-moves.html",
  "json_url": "https://gimgs.net/motion/type-entrance-moves.json",
  "template_url": "https://gimgs.net/motion/type-entrance-moves/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/type-entrance-moves.md",
  "updated_at": "2026-09-24T07:40:31.481Z",
  "prompt": "## When to use\n\nBig-title/chapter text-card entrances; mutually exclusive with split-flap-title (mechanical flip) and document-typewriter-reveal (typewriter) in the same category\n\n## Intention\n\nTitle entrances already have the typewriter (a steady, document feel) and the split-flap board (a mechanical-ceremony feel) — these two styles fill in two more personalities: A is a terminal-hacker feel — every letter starts as a burst of noise, \"decoding\" one by one from left to right as the viewer watches the answer grow out of the garble, good for a confident reveal on tech products; B is physical-comedy feel — letters drop from above, hit the ground, bounce and wobble upright, then snap together into formation on the final beat, good for a light-toned icebreaker opener. At most 2 of the four title-entrance moves per film (P4).\n\n## Duration & energy\n\n- Duration: Single style 4–5s (including hold and still close; action segment A ~66f / B ~106f)\n- Energy: Medium-high (A leans rational and driving, B leans physical and playful)\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n标题入场库内已有打字机（沉稳文档感）和翻牌屏（机械仪式感），这两式补\n另外两种性格：A 是终端黑客感——字符先是一片高速噪声，从左到右逐个\n\"解码\"锁定，观众看着答案从乱码里长出来，适合技术产品的自信亮相；\nB 是物理喜剧感——字符从天而降、砸地弹跳、歪歪扭扭站定，最后一拍\n集体立正归位，适合轻松调性的开场破冰。四种标题入场同片 ≤2 种（P4）。\n\n## 两式选型\n| 式 | 做法 | 适用 |\n|----|------|------|\n| A scramble-decode 乱码解码 | 全员字符高速跳乱码 hold，随后从左到右逐个锁定真字符，锁定瞬间反色闪 2f，底部进度条同步推进 | 技术/开发者产品；\"答案浮出\"的理性推进感 |\n| B letter-drop-physics 字符坠落 | 字符错峰从顶砸落，重力加速+两次衰减弹跳+落地歪斜站定，最后一拍全体齐整回正 | 轻松调性开场；\"活物感\"的标题破冰 |\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| A 乱码期 | 0–20f 全员每 2f 跳一个随机字符（字符集 A–F/0–9/#$%&），字色 G.mid | 必须用等宽字体 + 固定 1ch 槽宽，比例字体每跳一下整行抖动 |\n| A 解码节拍 | 第 i 字符帧 20+i×6 锁定为真字符转深色；锁定后前 2f 反色闪（深底白字） | 6f/字是\"逐个可辨\"的下限；上限 10f，再慢读作卡住 |\n| A 伪随机 | `CHARSET[floor(h(i*101+floor(f/2)*7+13)*len)]`，h = sin hash | 严禁 Math.random——跳动必须随帧确定，否则渲染不可复现 |\n| B 坠落 | 第 i 字符帧 10+i×5 起跳；y = −DROP + DROP·(t/24)²（重力）→ 4u(1−u) 抛物线弹跳 30% → 二跳 9% | 弹跳衰减比 30%/9% 是\"有弹性但不闹\"的甜点；三跳以上读作果冻 |\n| B 歪斜 | 落地瞬间 rotate 到 seed hash ±6° 并保持 | 歪角是\"物理感\"的一半；全体笔直落地读作普通位移入场 |\n| B 归位一拍 | 全员落定后（如帧 110）6f ease-out 集体 rotate→0 + scale 1.06→1 | 集体归位是句号——错峰归位会把\"立正\"的仪式感拆散 |\n| 收尾 | 两式动作完毕真静止 ≥20f | R1：静止期所有属性锁死（含 B 的歪角归零后） |\n\n## 已知坑\n- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿，\n  首次实战须以真实素材回验\n- A 式是声音强依赖候选位：锁定的\"嗒\"声逐字钉帧（sound-design §4.5）\n  会让解码感翻倍；B 式落地砸声同理，无声版两式都还成立但少一半劲\n- A 式字符集别混入与真字符太像的字形（如 O/0 连跳）——观众会误判已锁定\n- B 式字符间不做真实碰撞（demo 为各字符独立轨迹）——字距排开即可；\n  想要\"互相撞歪\"需预烘焙轨迹，成本高收益薄\n- 标题入场品类已四式（本卡两式 + split-flap-title + document-typewriter-reveal），\n  同片 ≤2 种，且都自带高注意力——别和其他高能量入场同帧抢戏\n\n## 参考实现\ndemos/typography/type-entrance-moves/\n（LetterDropPhysics.tsx / ScrambleDecode.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/typography/type-entrance-moves/LetterDropPhysics.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/type-entrance-moves/LetterDropPhysics.tsx\n- demos/typography/type-entrance-moves/ScrambleDecode.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/type-entrance-moves/ScrambleDecode.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/type-entrance-moves/LetterDropPhysics.tsx, demos/typography/type-entrance-moves/ScrambleDecode.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/type-entrance-moves/LetterDropPhysics.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/type-entrance-moves/LetterDropPhysics.tsx"
    },
    {
      "path": "demos/typography/type-entrance-moves/ScrambleDecode.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/type-entrance-moves/ScrambleDecode.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": 132,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/type-entrance-moves/LetterDropPhysics.tsx",
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/type-entrance-moves/ScrambleDecode.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/type-entrance-moves/LetterDropPhysics.tsx =====\n// 字符坠落堆积（letter-drop-physics）——FallingLetterAnimation。\n// \"GRAVITY\" 7 字符各自绝对定位，第 i 字符从帧 10+i*5 起下落：\n// ① 重力加速 y = D*(t/24)^2 掉 720px 到基线（地板线可见）；\n// ② 落地后 2 次衰减弹跳（高度 30% / 9%，抛物线 4u(1-u) 拼段），\n//    落地瞬间 rotate 到 seed hash ±6° 小歪角并保持；\n// ③ 帧 110 一拍：6f ease-out 全体齐整回正（rotate→0、偏移→0、scale 1.06→1），\n//    帧 116–150 真静止（≥25f）收尾。\nimport React from 'react';\nimport { useCurrentFrame, Easing } from 'remotion';\nimport { G } from '../../_fixtures/Fixtures';\n\n// 确定性伪随机\nconst h = (n: number) => {\n  const s = Math.sin(n * 127.3) * 43758.5453;\n  return s - Math.floor(s);\n};\n\nconst WORD = 'GRAVITY';\nconst SLOT_W = 150; // 每字符槽宽\nconst FONT = 140;\nconst WORD_W = WORD.length * SLOT_W; // 1050\nconst LEFT = (1920 - WORD_W) / 2; // 435\nconst REST_TOP = 452; // 字符落定后的 top\nconst FLOOR_Y = REST_TOP + 152; // 地板线（视觉基线）\n\nconst DROP = 720; // 下落距离\nconst T_FALL = 24; // 落到基线用时\nconst T_B1 = 16; // 第一次弹跳时长（高 30%）\nconst T_B2 = 8; // 第二次弹跳时长（高 9%）\nconst SNAP = 110; // 齐整回正的一拍\nconst SNAP_DUR = 6;\n\nconst easeOutCubic = Easing.out(Easing.cubic);\nconst clamp01 = (x: number) => Math.min(1, Math.max(0, x));\n\n// 单字符纵向位移（相对基线，负 = 在上方）\nconst dropY = (t: number): number => {\n  if (t <= 0) return -DROP;\n  if (t < T_FALL) return -DROP + DROP * (t / T_FALL) ** 2; // 重力加速\n  if (t < T_FALL + T_B1) {\n    const u = (t - T_FALL) / T_B1;\n    return -DROP * 0.3 * 4 * u * (1 - u); // 弹跳 1：30%\n  }\n  if (t < T_FALL + T_B1 + T_B2) {\n    const u = (t - T_FALL - T_B1) / T_B2;\n    return -DROP * 0.09 * 4 * u * (1 - u); // 弹跳 2：9%\n  }\n  return 0;\n};\n\nexport const LetterDropPhysics: React.FC = () => {\n  const frame = useCurrentFrame();\n  // 帧 110 起的齐整回正进度\n  const snap = easeOutCubic(clamp01((frame - SNAP) / SNAP_DUR));\n\n  return (\n    <div style={{ width: 1920, height: 1080, background: G.bg, overflow: 'hidden', position: 'relative' }}>\n      {/* 地板线：字符落点的视觉基线 */}\n      <div style={{ position: 'absolute', left: LEFT - 60, top: FLOOR_Y, width: WORD_W + 120, height: 6, background: G.bar, borderRadius: 3 }} />\n\n      {WORD.split('').map((ch, i) => {\n        const start = 10 + i * 5;\n        const t = frame - start;\n        const y = dropY(t);\n        // 落地瞬间歪到 seed 小角度（±6°），落地前为 0\n        const tiltTarget = (h(i + 1) - 0.5) * 12;\n        const landP = clamp01((t - T_FALL) / 6);\n        const restJitter = (h(i + 11) - 0.5) * 10; // 落定后的 ±5px 竖向错位\n        // snap 一拍：歪角与错位齐整归零，scale 1.06→1 脉冲\n        const rot = tiltTarget * landP * (1 - snap);\n        const jitter = restJitter * landP * (1 - snap);\n        const scale = frame < SNAP ? 1 : 1 + 0.06 * (1 - snap);\n        return (\n          <div\n            key={i}\n            style={{\n              position: 'absolute',\n              left: LEFT + i * SLOT_W,\n              top: REST_TOP,\n              width: SLOT_W,\n              height: FONT,\n              display: 'flex',\n              alignItems: 'flex-end',\n              justifyContent: 'center',\n              transform: `translateY(${y + jitter}px) rotate(${rot}deg) scale(${scale})`,\n              transformOrigin: '50% 100%',\n              fontFamily: 'Helvetica, Arial, sans-serif',\n              fontWeight: 800,\n              fontSize: FONT,\n              lineHeight: 1,\n              color: G.ink,\n            }}\n          >\n            {ch}\n          </div>\n        );\n      })}\n    </div>\n  );\n};\n\n\n// ===== demos/typography/type-entrance-moves/ScrambleDecode.tsx =====\n// 乱码解码字（scramble-decode）——终端黑客感文字入场。\n// 标题 \"DECODE SPEED\" 每字符先高速跳随机字母/数字（每 2f 换一个，seed hash 取字符），\n// 从左到右逐个锁定：第 i 个字符在帧 20+i*6 锁定为真字符，锁定瞬间该字符反色闪\n// （G.ink 色块白字 2f）随即恢复。跳动期字符 G.mid，锁定后 G.ink。\n// 关键帧：0–20f 全员乱跳 → 20–86f 从左到右逐个锁定 → 87–130f 完全静止收尾。\nimport React from 'react';\nimport { useCurrentFrame } from 'remotion';\nimport { G } from '../../_fixtures/Fixtures';\n\nconst TEXT = 'DECODE SPEED';\nconst CHARSET = 'ABCDEF0123456789#$%&';\nconst LOCK_START = 20; // 第 0 个字符锁定帧\nconst LOCK_STEP = 6; // 相邻字符锁定间隔\nconst FLASH_LEN = 2; // 锁定反色闪持续帧数\n\n// 帧确定伪随机\nconst h = (n: number) => {\n  const s = Math.sin(n * 127.3) * 43758.5453;\n  return s - Math.floor(s);\n};\n\nexport const ScrambleDecode: React.FC = () => {\n  const frame = useCurrentFrame();\n  const chars = TEXT.split('');\n  const lockedCount = chars.filter((c, i) => c === ' ' || frame >= LOCK_START + i * LOCK_STEP).length;\n  const allLocked = lockedCount === chars.length;\n\n  return (\n    <div\n      style={{\n        width: 1920,\n        height: 1080,\n        background: G.bg,\n        display: 'flex',\n        flexDirection: 'column',\n        alignItems: 'center',\n        justifyContent: 'center',\n        gap: 48,\n      }}\n    >\n      <div\n        style={{\n          fontFamily: 'ui-monospace, Menlo, Monaco, monospace',\n          fontWeight: 800,\n          fontSize: 120,\n          letterSpacing: 2,\n          display: 'flex',\n        }}\n      >\n        {chars.map((ch, i) => {\n          if (ch === ' ') {\n            return <span key={i} style={{ display: 'inline-block', width: '0.7em' }} />;\n          }\n          const lockFrame = LOCK_START + i * LOCK_STEP;\n          const locked = frame >= lockFrame;\n          const flashing = locked && frame < lockFrame + FLASH_LEN;\n          // 跳动期：每 2 帧换一个伪随机字符\n          const tick = Math.floor(frame / 2);\n          const scrambleChar = CHARSET[Math.floor(h(i * 101 + tick * 7 + 13) * CHARSET.length)];\n          const shown = locked ? ch : scrambleChar;\n          return (\n            <span\n              key={i}\n              style={{\n                display: 'inline-block',\n                width: '1ch',\n                textAlign: 'center',\n                color: flashing ? G.card : locked ? G.ink : G.mid,\n                background: flashing ? G.ink : 'transparent',\n              }}\n            >\n              {shown}\n            </span>\n          );\n        })}\n      </div>\n      {/* 底部进度提示条：已锁定字符数比例，帮助读出\"从左到右扫过\"的推进感 */}\n      <div style={{ width: 900, height: 10, background: G.line, borderRadius: 5, overflow: 'hidden' }}>\n        <div\n          style={{\n            width: `${(lockedCount / chars.length) * 100}%`,\n            height: '100%',\n            background: allLocked ? G.ink : G.mid,\n            borderRadius: 5,\n          }}\n        />\n      </div>\n    </div>\n  );\n};\n\n\n"
}