{
  "slug": "command-palette-summon",
  "title": "Command Palette Summon",
  "tagline": "The command palette descends — the whole screen dims and blurs, the ⌘K panel springs down with overshoot, candidate rows appear staggered, and the list narrows in real time as you type.",
  "description": "The command palette descends — the whole screen dims and blurs, the ⌘K panel springs down with overshoot, candidate rows appear staggered, and the list narrow",
  "category": "interaction",
  "tags": [
    "shotcraft",
    "remotion",
    "interaction",
    "ui-entrance"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 5.06,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/command-palette-summon/poster-fde82955e7a6.jpg",
    "video": "https://gimgs.net/motion/media/command-palette-summon/video-976fb3884fe2.mp4"
  },
  "variants": [
    {
      "key": "command-palette-summon",
      "label": "command-palette-summon",
      "description": "The command palette descends — the whole screen dims and blurs, the ⌘K panel springs down with overshoot, candidate rows appear staggered, and the list narrows in real time as you type.",
      "use": "",
      "video": "https://gimgs.net/motion/media/command-palette-summon/video-976fb3884fe2.mp4",
      "poster": "https://gimgs.net/motion/media/command-palette-summon/poster-fde82955e7a6.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/command-palette-summon.html",
  "json_url": "https://gimgs.net/motion/command-palette-summon.json",
  "template_url": "https://gimgs.net/motion/command-palette-summon/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/command-palette-summon.md",
  "updated_at": "2026-09-24T07:39:51.296Z",
  "prompt": "## When to use\n\nThe \"the whole product lives in one input box\" narrative for efficiency-focused products; the signature entrance for a command palette/search/shortcut feature.\n\n## Intention\n\nThe signature ritual of Raycast/Linear launch films: one soft sound, the whole UI world dims to make way, the ⌘K panel drops from above center, the candidate list appears staggered; type two letters, and the list narrows in real time — \"everything you need lives in this one input box.\" The simulated typing follows real human typing speed (R3), and the narrowing's \"squeeze\" feel comes from row-height collapse, not fading.\n\n## Duration & energy\n\n- Duration: 4–5s.\n- Energy: Medium (a ritual feel; the drop and the narrowing are the two small impact beats).\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\nRaycast/Linear 发布片的标志性仪式：一声轻响，整个 UI 世界压暗\n让路，⌘K 面板从中心上方弹落，候选列表错峰浮现；敲两个字母，\n列表实时收窄——\"你要的一切都在这个输入框里\"。模拟交互按真人\n操作速度走（R3），收窄的\"挤压感\"来自行高塌缩而非淡出。\n\n## 动作阶段\n| 阶段 | 帧段参考 | 内容 |\n|------|------|------|\n| 1 静置 | f0–12 | dashboard 初始状态 |\n| 2 让路 | f12–22 | 背景压暗至 45% + blur 10px |\n| 3 降临 | f18–33 | 面板 -20px 落入，过冲 +8px 回正（与压暗跨骑） |\n| 4 候选浮现 | f32–56 | 5 行每 4f 错峰上浮 |\n| 5 输入收窄 | ~f62–90 | 两次按键字符出现，行高塌缩收窄 5→2（塌缩完条件卸载） |\n| 6 选中定格 | 末 ≥40f | 首条高亮（底色+左描边），光标转常亮，真静止 |\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| 压暗 | 亮度 45% + blur 10px，10f | 压不够面板不突出；全黑失去上下文 |\n| 弹落 | 起点上方 20px，overshoot 一拍回正 | 无过冲读作淡入，仪式感没了 |\n| 候选错峰 | 4f/行 | 同帧出读作贴图 |\n| 收窄 | height→0 + overflow hidden 塌缩 | 淡出收窄没有\"挤压\"感；塌缩完必须卸载 |\n| 光标 | 闪烁周期 16f；定格后强制常亮 | 闪到结尾就永不真静止（demo 判例） |\n\n## 已知坑\n- demo 在灰阶/占位素材上调校通过——参数是调校起点非实战定稿，\n  首次实战须以真实素材回验\n- 输入节奏按真人打字速度（R3 判例），两键间隔 ≥12f\n- 组合款见 theme-switch-moves B 式（面板收缩成点引发主题涟漪）\n- 实战候选行放真实功能名——这是功能清单的天然展位（P4 映射）\n- 声音：唤出一声轻响（非游戏音），按键 2 声键帽拟音，\n  高亮一声轻 pop（S1/S4 同源）\n\n## 参考实现\ndemos/interaction/command-palette-summon/\n（CommandPaletteSummon.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/interaction/command-palette-summon/CommandPaletteSummon.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/interaction/command-palette-summon/CommandPaletteSummon.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/interaction/command-palette-summon/CommandPaletteSummon.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/interaction/command-palette-summon/CommandPaletteSummon.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/interaction/command-palette-summon/CommandPaletteSummon.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": 152,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/interaction/command-palette-summon/CommandPaletteSummon.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/interaction/command-palette-summon/CommandPaletteSummon.tsx =====\n// command-palette-summon —— 命令面板降临\n// FakeDashboard 静置 → 整屏压暗+blur → ⌘K 面板从中心上方 20px 带 overshoot 弹落\n// → 5 条候选行错峰浮现 → 模拟输入 2 字母（灰块当字符）候选 5→3→2 收窄\n// → 高亮首条。f=110 后全静止（40f）。光标 f<104 闪烁、之后常亮。\nimport React from 'react';\nimport { useCurrentFrame, interpolate, Easing } from 'remotion';\nimport { G, FakeDashboard } from '../../_fixtures/Fixtures';\n\nconst CL = { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' } as const;\n\n// 时间轴\nconst DIM0 = 12; // 压暗开始（前 12f 初始静置）\nconst DIM1 = 22;\nconst PANEL_IN = 18; // 面板开始弹落\nconst ROWS_START = 32; // 候选行开始错峰浮现\nconst KEY1 = 62; // 第一个字母\nconst KEY2 = 78; // 第二个字母\nconst HL = 94; // 高亮首条\nconst BLINK_END = 104; // 光标停止闪烁（常亮）\n\nconst PANEL_W = 780;\nconst PANEL_X = (1920 - PANEL_W) / 2;\nconst PANEL_Y = 290;\nconst ROW_H = 72;\nconst ROW_GAP = 8;\nconst EXIT_DUR = 10;\n\n// exitAt: 0=留到最后，1=第一次按键后退出，2=第二次按键后退出\nconst ROWS = [\n  { titleW: 52, exitAt: 0 },\n  { titleW: 38, exitAt: 0 },\n  { titleW: 61, exitAt: 2 },\n  { titleW: 45, exitAt: 1 },\n  { titleW: 56, exitAt: 1 },\n];\n\nconst PaletteRow: React.FC<{ i: number; frame: number }> = ({ i, frame }) => {\n  const { titleW, exitAt } = ROWS[i];\n  const inStart = ROWS_START + i * 4;\n  const exitStart = exitAt === 1 ? KEY1 + 3 : exitAt === 2 ? KEY2 + 3 : null;\n\n  if (exitStart !== null && frame >= exitStart + EXIT_DUR) return null; // 条件卸载，非 opacity 0\n\n  const inOp = interpolate(frame, [inStart, inStart + 8], [0, 1], CL);\n  const inY = interpolate(frame, [inStart, inStart + 8], [12, 0], {\n    easing: Easing.out(Easing.cubic),\n    ...CL,\n  });\n  const exitT =\n    exitStart === null\n      ? 1\n      : interpolate(frame, [exitStart, exitStart + EXIT_DUR], [1, 0], {\n          easing: Easing.inOut(Easing.cubic),\n          ...CL,\n        });\n\n  // 高亮首条\n  const hl = i === 0 ? interpolate(frame, [HL, HL + 10], [0, 1], CL) : 0;\n\n  return (\n    <div\n      style={{\n        height: (ROW_H + ROW_GAP) * exitT,\n        opacity: inOp * exitT,\n        overflow: 'hidden',\n      }}\n    >\n      <div\n        style={{\n          height: ROW_H,\n          borderRadius: 12,\n          background: hl > 0 ? `rgba(228,228,226,${hl})` : 'transparent',\n          boxShadow: hl > 0 ? `inset 4px 0 0 rgba(47,47,47,${hl})` : 'none',\n          display: 'flex',\n          alignItems: 'center',\n          gap: 20,\n          padding: '0 22px',\n          boxSizing: 'border-box',\n          transform: `translateY(${inY}px)`,\n        }}\n      >\n        <div style={{ width: 36, height: 36, borderRadius: 9, background: G.mid }} />\n        <div style={{ height: 14, width: `${titleW}%`, background: G.bar, borderRadius: 7 }} />\n        <div style={{ marginLeft: 'auto', width: 58, height: 24, borderRadius: 6, background: G.line }} />\n      </div>\n    </div>\n  );\n};\n\nexport const CommandPaletteSummon: React.FC = () => {\n  const frame = useCurrentFrame();\n\n  // 背景压暗 + blur\n  const dim = interpolate(frame, [DIM0, DIM1], [0, 0.45], CL);\n  const blur = interpolate(frame, [DIM0, DIM1], [0, 10], CL);\n\n  // 面板弹落：上方 20px → 过冲 +8px → 落回 0\n  const panelY =\n    frame < PANEL_IN + 9\n      ? interpolate(frame, [PANEL_IN, PANEL_IN + 9], [-20, 8], {\n          easing: Easing.out(Easing.cubic),\n          ...CL,\n        })\n      : interpolate(frame, [PANEL_IN + 9, PANEL_IN + 15], [8, 0], {\n          easing: Easing.inOut(Easing.cubic),\n          ...CL,\n        });\n  const panelOp = interpolate(frame, [PANEL_IN, PANEL_IN + 7], [0, 1], CL);\n\n  // 模拟输入：已敲字符数\n  const typed = (frame >= KEY1 ? 1 : 0) + (frame >= KEY2 ? 1 : 0);\n  // 光标闪烁（周期 16f），BLINK_END 后常亮保证收尾静止\n  const cursorOn = frame >= BLINK_END ? true : (frame - PANEL_IN) % 16 < 8;\n\n  return (\n    <div style={{ width: 1920, height: 1080, background: G.bg, position: 'relative', overflow: 'hidden' }}>\n      <div style={{ filter: frame < DIM0 ? undefined : `blur(${blur}px)` }}>\n        <FakeDashboard variant=\"A\" />\n      </div>\n      <div style={{ position: 'absolute', inset: 0, background: `rgba(20,20,20,${dim})` }} />\n\n      {frame >= PANEL_IN && (\n        <div\n          style={{\n            position: 'absolute',\n            left: PANEL_X,\n            top: PANEL_Y,\n            width: PANEL_W,\n            transform: `translateY(${panelY}px)`,\n            opacity: panelOp,\n            background: G.card,\n            borderRadius: 18,\n            border: `2px solid ${G.border}`,\n            boxShadow: '0 28px 90px rgba(0,0,0,0.4)',\n            padding: 20,\n            boxSizing: 'border-box',\n          }}\n        >\n          {/* 输入框 */}\n          <div\n            style={{\n              height: 76,\n              borderBottom: `2px solid ${G.line}`,\n              display: 'flex',\n              alignItems: 'center',\n              gap: 14,\n              padding: '0 10px 14px 10px',\n              boxSizing: 'border-box',\n            }}\n          >\n            <div style={{ width: 32, height: 32, borderRadius: 8, background: G.bar }} />\n            {/* 已敲入的字符灰块 */}\n            {Array.from({ length: typed }).map((_, c) => (\n              <div key={c} style={{ width: 28, height: 38, borderRadius: 6, background: '#4a4a48' }} />\n            ))}\n            {/* 光标 */}\n            {cursorOn && <div style={{ width: 4, height: 42, background: G.ink, borderRadius: 2 }} />}\n            {/* 占位提示条：敲第一个字母时卸载 */}\n            {typed === 0 && (\n              <div style={{ width: 260, height: 14, borderRadius: 7, background: G.line, opacity: 0.8 }} />\n            )}\n          </div>\n          {/* 候选行 */}\n          <div style={{ paddingTop: 14 }}>\n            {ROWS.map((_, i) => (\n              <PaletteRow key={i} i={i} frame={frame} />\n            ))}\n          </div>\n        </div>\n      )}\n    </div>\n  );\n};\n\n\n"
}