{
  "slug": "fui-hud-moves",
  "title": "FUI/HUD moves",
  "tagline": "Two FUI/HUD styles: line-unfold-panel, a single line unfolding into a panel (line-to-plane CRT grammar), and reticle-lock-on, a viewfinder flying in to lock onto a target.",
  "description": "Two FUI/HUD styles: line-unfold-panel, a single line unfolding into a panel (line-to-plane CRT grammar), and reticle-lock-on, a viewfinder flying in to lock o",
  "category": "effects",
  "tags": [
    "shotcraft",
    "remotion",
    "effects",
    "ui-entrance"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 4.72,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/fui-hud-moves/poster-ff3e9f878010.jpg",
    "video": "https://gimgs.net/motion/media/fui-hud-moves/video-902ab1bcf4f8.mp4"
  },
  "variants": [
    {
      "key": "line-unfold-panel",
      "label": "line-unfold-panel · Line-to-plane panel unfold",
      "description": "scaleX 0→1 (out poly4, a sharp 5f snap) followed by scaleY 3px→full height (out cubic, 9f), content fades in one beat early; exit mirrors the sequence in reverse.",
      "use": "Panel entrance/exit in dark segments; power-on/off ritual feel",
      "video": "https://gimgs.net/motion/media/fui-hud-moves/video-902ab1bcf4f8.mp4",
      "poster": "https://gimgs.net/motion/media/fui-hud-moves/poster-ff3e9f878010.jpg"
    },
    {
      "key": "reticle-lock-on",
      "label": "reticle-lock-on · Reticle lock-on",
      "description": "Four L-corners are four mirrored copies of the same rectangle pair; the fly-in (10f out cubic) and the shrink (2.2×→0.94×→1 overshoot rebound) are decoupled; on the lock-on frame the target brightens slightly and a label pops in with back easing.",
      "use": "Target call-out; timed to hit a sound effect",
      "video": "https://gimgs.net/motion/media/fui-hud-moves/video-ecb2d920f595.mp4",
      "poster": "https://gimgs.net/motion/media/fui-hud-moves/poster-reticle-lock-on-9907c72b4f5e.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/fui-hud-moves.html",
  "json_url": "https://gimgs.net/motion/fui-hud-moves.json",
  "template_url": "https://gimgs.net/motion/fui-hud-moves/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/fui-hud-moves.md",
  "updated_at": "2026-09-24T07:39:58.436Z",
  "prompt": "## When to use\n\nUse A for panel entrances/exits in dark, tech-flavored segments; use B for any \"look here\" target call-out (replaces circling/arrow annotation without freezing the frame).\n\n## Intention\n\nTwo motifs from sci-fi fictional interfaces (Jarvis/Territory) that can be made neutral. A is a panel's ritual power-on/off: a 1px thin line snaps out very fast → stretches vertically into a full panel → content fades in ahead of the beat, exiting in reverse as the line compresses back to a point and dies (old-CRT power-off); a grayscale thin line already works, no tech-blue needed. B is a capture-in-motion: four L-shaped corner marks rush in from off-frame, overshoot and rebound, \"clicking\" onto the four corners of a target element plus a popped label — division of labor with freeze-annotate: that card freezes the frame to annotate, this one calls things out while things keep flowing.\n\n## Duration & energy\n\n- Duration: A: 3–4s (including exit) / B: 2–3s\n- Energy: A: Medium / B: Medium-high (the lock-on frame is the hit point)\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n科幻虚构界面（Jarvis/Territory）的两个可中性化母题。A 是面板的\n仪式感开关机：1px 细线极快抽出→纵向撑开成面板→内容淡入，退场反向\n压线缩点熄灭（老 CRT 关机）；灰阶细线即成立，不需要科技蓝。\nB 是运动中的捕获：四个 L 形角标从画外冲入、超调回弹、\"咔\"地咬合\n到目标元素四角+弹标签——与 freeze-annotate 分工：那卡冻结画面标注，\n本卡在流动中点名。\n\n## 两式选型\n| 式 | 做法 | 适用 |\n|----|------|------|\n| A line-unfold-panel | scaleX 0→1（out poly4 急抽 5f）接 scaleY 3px→满高（out cubic 9f），内容提前一拍淡入；退场镜像反序 | 暗场面板入退场；开关机仪式感 |\n| B reticle-lock-on | 四 L 角=同一对矩形四份镜像，飞入（10f out cubic）与收缩（2.2×→0.94×→1 超调回弹）解耦；咬合帧目标微亮+标签 back 弹出 | 目标点名；踩音效重拍 |\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| A 两段节奏 | 抽线 5f 急 vs 撑面 9f 缓 | 节奏差是手法本体，等长就平了 |\n| A 线体 | 白色发光条 3px 级；线/点阶段与面板阶段条件切换 | 熄灭后必须条件卸载才有真静止 |\n| B 飞入行程 | 画外 ≥1000px 冲入 | 行程短读作\"角标出现\"不是\"扑过来\" |\n| B 超调 | 收缩过头到 0.94× 再回弹 1 | 无超调没有\"咔\"的咬合感 |\n| B 咬合帧 | 目标白 overlay 快闪 0.55→停 0.28 + 标签同帧弹出 | 微亮与标签错帧就散了 |\n\n## 已知坑\n- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿，\n  首次实战须以真实素材回验\n- Remotion 无 `Easing.quart`——用 `Easing.poly(4)`（demo 实测报\n  \"easing is not a function\" 判例）\n- B 一支片 ≤2 次：连用读作军事模板；两次锁定目标必须不同\n- A 与 glow-flyline 同属暗场词汇，相邻段落别背靠背堆光效（Q4 同源）\n- 声音：A 抽线一声短 whoosh、撑开一声轻 pop；B 咬合帧一声\"咔\"\n  （机械 click 而非游戏 UI 音，S1 边界内）\n\n## 参考实现\ndemos/effects/fui-hud-moves/\n（LineUnfoldPanel.tsx / ReticleLockOn.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/effects/fui-hud-moves/LineUnfoldPanel.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/fui-hud-moves/LineUnfoldPanel.tsx\n- demos/effects/fui-hud-moves/ReticleLockOn.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/fui-hud-moves/ReticleLockOn.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/effects/fui-hud-moves/LineUnfoldPanel.tsx, demos/effects/fui-hud-moves/ReticleLockOn.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/effects/fui-hud-moves/LineUnfoldPanel.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/fui-hud-moves/LineUnfoldPanel.tsx"
    },
    {
      "path": "demos/effects/fui-hud-moves/ReticleLockOn.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/fui-hud-moves/ReticleLockOn.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": 142,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/fui-hud-moves/LineUnfoldPanel.tsx",
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/effects/fui-hud-moves/ReticleLockOn.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/effects/fui-hud-moves/LineUnfoldPanel.tsx =====\n// line-unfold-panel —— 一线展面（Jarvis/FUI 母题）\n// 暗底。入场两拍：3px 细线从中点向两侧极快抽出（5f）→ 定宽后纵向\n// 撑开成 Card 面板（9f，out 缓动）→ 内容延迟淡入。\n// 静置展示后反向退场：压扁成线（7f）→ 线缩成点 → 熄灭，像老 CRT 关机。\n// f0–12 空场静置；入场 f12–34；持面板至 f78；退场 f78–98；末静止 ≥42f（140f）。\nimport React from 'react';\nimport { useCurrentFrame, interpolate, Easing } from 'remotion';\nimport { G, Card } from '../../_fixtures/Fixtures';\n\nconst PANEL_W = 760;\nconst PANEL_H = 460;\nconst CX = 960;\nconst CY = 540;\n\n// —— 入场时间表 ——\nconst T0 = 12; // 点亮起点\nconst LINE_END = T0 + 5; // 线抽出完成 f17\nconst UNFOLD_END = LINE_END + 9; // 面板撑开完成 f26\nconst CONTENT_END = UNFOLD_END + 8; // 内容淡入完成 f34\n\n// —— 退场时间表 ——\nconst OUT0 = 78; // 开始压扁\nconst COLLAPSE_END = OUT0 + 7; // 压成线 f85\nconst SHRINK_END = COLLAPSE_END + 6; // 线缩成点 f91\nconst OFF = SHRINK_END + 4; // 点熄灭 f95\n\nconst clamp = { extrapolateLeft: 'clamp' as const, extrapolateRight: 'clamp' as const };\n\nexport const LineUnfoldPanel: React.FC = () => {\n  const frame = useCurrentFrame();\n\n  // 入场：scaleX（线抽出）快进快停，入场后保持 1\n  const inSX = interpolate(frame, [T0, LINE_END], [0.004, 1], {\n    easing: Easing.out(Easing.poly(4)),\n    ...clamp,\n  });\n  // 入场：scaleY（纵向撑开），线阶段压在 3px\n  const inSY = interpolate(frame, [LINE_END, UNFOLD_END], [3 / PANEL_H, 1], {\n    easing: Easing.out(Easing.cubic),\n    ...clamp,\n  });\n  // 内容淡入（面板撑开过半才开始）\n  const contentOp = interpolate(frame, [UNFOLD_END - 3, CONTENT_END], [0, 1], {\n    easing: Easing.out(Easing.quad),\n    ...clamp,\n  });\n\n  // 退场：先压 Y 回线，再缩 X 回点\n  const outSY = interpolate(frame, [OUT0, COLLAPSE_END], [1, 3 / PANEL_H], {\n    easing: Easing.in(Easing.cubic),\n    ...clamp,\n  });\n  const outSX = interpolate(frame, [COLLAPSE_END, SHRINK_END], [1, 0.004], {\n    easing: Easing.in(Easing.poly(4)),\n    ...clamp,\n  });\n  // 内容在压扁前先撤\n  const contentOutOp = interpolate(frame, [OUT0 - 4, OUT0 + 2], [1, 0], clamp);\n\n  const sx = frame < OUT0 ? inSX : outSX;\n  const sy = frame < OUT0 ? inSY : outSY;\n\n  // 末点熄灭：opacity 快落。f >= OFF 后整个元素条件卸载 → 真静止\n  const dotOp = interpolate(frame, [SHRINK_END, OFF], [1, 0], {\n    easing: Easing.in(Easing.quad),\n    ...clamp,\n  });\n\n  const alive = frame >= T0 && frame < OFF;\n  // 面板阶段（sy 足够大）显示卡片内容；线/点阶段显示发光条\n  const isPanel = sy > 0.15;\n\n  return (\n    <div style={{ width: 1920, height: 1080, background: '#1c1c1b', overflow: 'hidden', position: 'relative' }}>\n      <div\n        style={{\n          position: 'absolute',\n          top: 120,\n          width: '100%',\n          textAlign: 'center',\n          fontFamily: 'Helvetica, Arial, sans-serif',\n          fontWeight: 800,\n          fontSize: 52,\n          color: G.mid,\n          letterSpacing: 2,\n        }}\n      >\n        LINE UNFOLD PANEL\n      </div>\n      {alive && (\n        <div\n          style={{\n            position: 'absolute',\n            left: CX - PANEL_W / 2,\n            top: CY - PANEL_H / 2,\n            width: PANEL_W,\n            height: PANEL_H,\n            transform: `scaleX(${sx}) scaleY(${sy})`,\n            transformOrigin: '50% 50%',\n            opacity: dotOp,\n          }}\n        >\n          {isPanel ? (\n            <>\n              <Card w={PANEL_W} h={PANEL_H} seed={3} style={{ border: `2px solid ${G.mid}`, boxShadow: '0 0 40px rgba(255,255,255,0.18)' }} />\n              {/* 内容层单独控 opacity：盖一层暗板模拟\"内容未亮\" */}\n              <div\n                style={{\n                  position: 'absolute',\n                  inset: 2,\n                  borderRadius: 12,\n                  background: G.card,\n                  opacity: 1 - Math.min(contentOp, contentOutOp),\n                }}\n              />\n            </>\n          ) : (\n            // 线/点阶段：白色发光条填满整个盒（被 scale 压成线）\n            <div style={{ width: '100%', height: '100%', background: '#ffffff', boxShadow: '0 0 60px rgba(255,255,255,0.9)', borderRadius: 2 }} />\n          )}\n        </div>\n      )}\n    </div>\n  );\n};\n\n\n// ===== demos/effects/fui-hud-moves/ReticleLockOn.tsx =====\n// reticle-lock-on —— 准星咬合（钢铁侠 HUD / 安德的游戏）\n// FakeDashboard 静置。四个 L 形角标组成的取景框从画外飞入（大框），\n// 超调回弹后收缩贴紧目标卡片四角\"咔\"地咬合定格；咬合帧卡片微亮 +\n// 旁弹一行小标签。画面不冻结，是运动中的捕获。\n// f0–14 面板静置；f14–24 飞入；f24–46 收缩（含过冲回弹）；f46 咬合；\n// 标签 f46–56 弹出；f56 后真静止 ≥84f（140f 总长）。\nimport React from 'react';\nimport { useCurrentFrame, interpolate, Easing } from 'remotion';\nimport { G, FakeDashboard } from '../../_fixtures/Fixtures';\n\n// 目标：variant A 网格第 2 张卡（第一行中间）。\n// 布局推导：侧栏 220 + padding 36，网格 3 列 gap 28，\n// 内容区宽 1700-72=1628 → 卡宽 (1628-56)/3 ≈ 524，卡高 (1080-72-72-28)/2 ≈ 454...\n// 实测近似：目标框取第一行中间卡的外接矩形（含少量呼吸边距）。\nconst CARD_X = 220 + 36 + 524 + 28; // ≈ 808\nconst CARD_Y = 72 + 36; // 108\nconst CARD_W = 524;\nconst CARD_H = 425;\nconst PAD = 14; // 咬合后角标与卡的呼吸距\n\nconst TCX = CARD_X + CARD_W / 2;\nconst TCY = CARD_Y + CARD_H / 2;\n\nconst FLY_IN = 14; // 飞入开始\nconst FLY_END = 24; // 飞入结束（大框就位）\nconst LOCK_END = 46; // 咬合帧\nconst LABEL_END = 56;\n\nconst ARM = 56; // L 臂长\nconst THICK = 10; // L 粗\n\nconst clamp = { extrapolateLeft: 'clamp' as const, extrapolateRight: 'clamp' as const };\n\nexport const ReticleLockOn: React.FC = () => {\n  const frame = useCurrentFrame();\n\n  // 飞入：整框从右下画外冲进来，同时是个放大 2.2× 的大框\n  const flyT = interpolate(frame, [FLY_IN, FLY_END], [0, 1], {\n    easing: Easing.out(Easing.cubic),\n    ...clamp,\n  });\n  const flyDX = (1 - flyT) * 1100;\n  const flyDY = (1 - flyT) * 620;\n\n  // 收缩咬合：半宽/半高从 2.2× 收到贴紧，带超调（收过头 6% 再弹回）\n  const shrink = interpolate(\n    frame,\n    [FLY_END, LOCK_END - 8, LOCK_END - 3, LOCK_END],\n    [2.2, 1.06, 0.94, 1],\n    { easing: Easing.inOut(Easing.cubic), ...clamp },\n  );\n  const hw = (CARD_W / 2 + PAD) * shrink;\n  const hh = (CARD_H / 2 + PAD) * shrink;\n\n  // 咬合帧：卡片微亮（一层白色 overlay 快闪后停在 0.28）\n  const glow = interpolate(frame, [LOCK_END, LOCK_END + 3, LOCK_END + 10], [0, 0.55, 0.28], clamp);\n\n  // 标签：咬合后从角标右上弹出（scaleX 展开 + 淡入）\n  const labelT = interpolate(frame, [LOCK_END + 2, LABEL_END], [0, 1], {\n    easing: Easing.out(Easing.back(1.8)),\n    ...clamp,\n  });\n\n  const showReticle = frame >= FLY_IN;\n  const locked = frame >= LOCK_END;\n\n  // 四个 L 角：位置 = 中心 ± (hw, hh)，各自镜像\n  const corners = [\n    { x: TCX - hw, y: TCY - hh, sx: 1, sy: 1 },\n    { x: TCX + hw, y: TCY - hh, sx: -1, sy: 1 },\n    { x: TCX - hw, y: TCY + hh, sx: 1, sy: -1 },\n    { x: TCX + hw, y: TCY + hh, sx: -1, sy: -1 },\n  ];\n\n  return (\n    <div style={{ width: 1920, height: 1080, background: G.bg, overflow: 'hidden', position: 'relative' }}>\n      <FakeDashboard variant=\"A\" />\n      {/* 咬合微亮层：条件渲染，锁定前不存在 */}\n      {locked && (\n        <div\n          style={{\n            position: 'absolute',\n            left: CARD_X,\n            top: CARD_Y,\n            width: CARD_W,\n            height: CARD_H,\n            borderRadius: 14,\n            background: '#ffffff',\n            opacity: glow,\n            pointerEvents: 'none',\n          }}\n        />\n      )}\n      {showReticle && (\n        <div style={{ position: 'absolute', left: 0, top: 0, transform: `translate(${flyDX}px, ${flyDY}px)` }}>\n          {corners.map((c, i) => (\n            <div\n              key={i}\n              style={{\n                position: 'absolute',\n                left: c.x,\n                top: c.y,\n                transform: `scale(${c.sx}, ${c.sy})`,\n                transformOrigin: '0 0',\n              }}\n            >\n              {/* L 形角标：横臂 + 竖臂 */}\n              <div style={{ position: 'absolute', left: 0, top: 0, width: ARM, height: THICK, background: G.ink }} />\n              <div style={{ position: 'absolute', left: 0, top: 0, width: THICK, height: ARM, background: G.ink }} />\n            </div>\n          ))}\n          {/* 小标签：右上角标外侧弹出 */}\n          {frame >= LOCK_END + 2 && (\n            <div\n              style={{\n                position: 'absolute',\n                left: TCX + hw + 18,\n                top: TCY - hh - 6,\n                transform: `scale(${labelT})`,\n                transformOrigin: '0 50%',\n                background: G.ink,\n                color: '#fff',\n                fontFamily: 'Helvetica, Arial, sans-serif',\n                fontWeight: 700,\n                fontSize: 26,\n                padding: '10px 18px',\n                borderRadius: 8,\n                whiteSpace: 'nowrap',\n                opacity: Math.min(1, labelT * 1.5),\n              }}\n            >\n              TARGET · CARD_02\n            </div>\n          )}\n        </div>\n      )}\n    </div>\n  );\n};\n\n\n"
}