{
  "slug": "glass-pill-dictation-typing",
  "title": "Glass pill dictation typing",
  "tagline": "On black, a glass pill pops in at 1.25× then eases to size, an accent light sweeping inside; the cursor leads, placeholder text types in as the glow dims with typing progress, ending as a neutral dark glass bar.",
  "description": "On black, a glass pill pops in at 1.25× then eases to size, an accent light sweeping inside; the cursor leads, placeholder text types in as the glow dims with",
  "category": "interaction",
  "tags": [
    "shotcraft",
    "remotion",
    "interaction"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 2.17,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/glass-pill-dictation-typing/poster-68259133ff35.jpg",
    "video": "https://gimgs.net/motion/media/glass-pill-dictation-typing/video-b44cc497944e.mp4"
  },
  "variants": [
    {
      "key": "glass-pill-dictation-typing",
      "label": "glass-pill-dictation-typing · Glass pill dictation typing",
      "description": "On a pure-black background a fixed-width glass pill pops in at about 1.25x then eases down to size, an accent-colored light sweeping inside from dim-left to bright-right; the cursor leads, then placeholder text types in as the glow dims with the typing progress, ending as a neutral dark glass bar.",
      "use": "",
      "video": "https://gimgs.net/motion/media/glass-pill-dictation-typing/video-b44cc497944e.mp4",
      "poster": "https://gimgs.net/motion/media/glass-pill-dictation-typing/poster-68259133ff35.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/glass-pill-dictation-typing.html",
  "json_url": "https://gimgs.net/motion/glass-pill-dictation-typing.json",
  "template_url": "https://gimgs.net/motion/glass-pill-dictation-typing/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/glass-pill-dictation-typing.md",
  "updated_at": "2026-09-24T07:39:58.500Z",
  "prompt": "## When to use\n\nThe entrance of a voice/AI input box; an interaction cue for \"talk to it\"; a quiet transitional beat between high-energy segments.\n\n## Intention\n\nTell a small story with \"the light dims as input arrives\": while idle it glows (waiting for you to speak), and the moment you start talking it hands the glow over to the text. In 1.7s only three things happen — pop in, type, light fades. Any extra motion (bounce, color change, icon animation) would break this beat's function as a rest.\n\n## Duration & energy\n\n- Duration: About 1.7s (50f@30fps)\n- Energy: Low (the quietest beat in the whole film, only the light receding)\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n用\"光随输入熄灭\"讲一件很小的事：待命时它在发光（等你说话），你一开口\n它就把光让给文字。1.7s 里只做三件事——弹出、打字、光退。任何额外动作\n（弹跳、色变、图标动画）都会让这一拍不再是休止符。\n\n## 动效核心\n- 胶囊高 46px、圆角 23px、`padding: 0 8px 0 16px`，**定宽**\n  `PW = 文本实测宽 + 168px`（≈整句宽度的 2 倍），不随打字伸缩\n- 出场 `scale 1.25→1`，`seg(0→0.22, outCubic)`（≈0.36s）；\n  opacity 在前 0.025 就跳满——先\"在了\"再落位\n- 内嵌强调色光是一层 `linear-gradient(90deg, α0 → α.35 @42% → α.95 @100%)`\n  的左暗右亮渐层（光在胶囊**内部**，不是外部光晕）\n- 打字：光标 t≈0.025 先行出现，字符 `seg(0.06→0.73)` 匀速铺完 18 字符，\n  尾段保持；光标 `0.75→0.8` 撤掉\n- 光熄灭 `g = 1 − seg(0.08→0.76, inOutQuad)`，与打字进度同窗口；\n  `g` 同时驱动三层阴影收敛：内描边 `0.16+0.1g`、内顶光\n  `0.03+0.04g`、外泛光 `26g px / 0.28g`\n- 右端 30px 描边圆角方块内 5 根竖条声波，基准高 `[13,7,10,6,9]`\n  （左高右低），叠 `±1.6·sin(t·18 + i·1.7)` 极轻呼吸\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| 胶囊定宽 | 文本实测宽 + 168px（≈句宽 2 倍） | **定宽是关键**：随字伸缩会读作 chip 而不是输入条 |\n| 出场缩放 | scale 1.25→1，t 0→0.22 outCubic | 1.25 倍是\"从观众面前落回去\"的量；1.5 倍就成了 pop 弹窗 |\n| opacity 时序 | 前 0.025 跳满（早于缩放落位） | 同步淡入会让它像\"渐渐出现\"，失掉那一下轻微的实感 |\n| 内嵌光 | 90deg、0 → .35@42% → .95@100% | 光必须在胶囊内部；改成外 glow 就成了\"待通知\"而不是\"待输入\" |\n| 打字 | 18 字符，t 0.06→0.73 匀速 | 匀速 ≈ 3f/字符；加 ease 就读作\"有人在犹豫\" |\n| 光熄灭 | 1 → 0，t 0.08→0.76 inOutQuad | 与打字同窗口是全片唯一的因果：光让位给文字 |\n| 阴影收敛 | 内描边 0.16+0.1g / 内顶光 0.03+0.04g / 外泛光 26g px | 三层都挂在同一个 g 上，收尾自然落到\"中性深色玻璃条\" |\n| 声波竖条 | 5 根、基准 [13,7,10,6,9]、±1.6px、频率 18 | 幅度到 4px 就成了\"正在录音\"的活跃态，与\"待命\"矛盾 |\n| 光标 | t≈0.025 先行、0.75→0.8 撤 | 光标先于文字出现，是\"输入框已就绪\"的信号 |\n\n## 已知坑\n- 定宽靠一个 `visibility:hidden` 的测量节点算 `offsetWidth`，字体必须\n  与正式文本**完全一致**（`400 21px` + `letter-spacing:.3px`），\n  差一档就宽度不对\n- 声波竖条是持续呼吸（不受 t 分段控制），所以首尾帧的高度不同——\n  用作 loop 素材时要把频率 18 调成 2π 的整数倍\n- 打字进度 `floor(seg(…)*len + 1e-6)`，那个 epsilon 是防止末字符在\n  t=0.73 整点因浮点误差不出现，别删\n- ACCENT_RGB（默认 `146,126,212` 紫）内嵌光与外泛光共用，\n  换项目品牌色只改这一个变量\n- 占位句 \"Speak or type here\"（18 字符）；换句要保持字符数接近，\n  否则匀速打字的节奏和定宽比例都要重调\n\n## 参考实现\ndemos/interaction/glass-pill-dictation-typing/\n（GlassPillDictationTyping.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.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/glass-pill-dictation-typing/GlassPillDictationTyping.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/glass-pill-dictation-typing/GlassPillDictationTyping.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.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": 65,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.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/glass-pill-dictation-typing/GlassPillDictationTyping.tsx =====\n// glass-pill-dictation-typing — Glass Pill Dictation 玻璃胶囊听写（motion-lab 定稿转原生 Remotion）\n// 纯黑底上一条定宽玻璃胶囊：整条以约 1.25 倍略大弹出后缓落到位，胶囊内部自左暗到右亮\n// 铺一层强调色光（ACCENT 变量，默认紫）；光标先行出现，随后打字出现占位句\n// 「Speak or type here」，光随打字进度渐渐熄灭，收尾成中性深色玻璃条；右端是描边圆角\n// 方块里的竖条声波图标——全片最安静的一拍。\n// 设计坐标 480×270（DesignStage 等比放大），参数表数值以此坐标系标定。\nimport React, { useLayoutEffect, useRef, useState } from 'react';\nimport { DesignStage, E, lerp, seg, useT } from '../../_fixtures/Motion';\n\nexport const GLASS_PILL_DICTATION_TYPING_DURATION = 50; // 1650ms @30fps\n\n// 模板强调色：实际使用时按项目品牌色替换这一个变量（内嵌光 + 外泛光共用）\nconst ACCENT_RGB = '146,126,212';\nconst UI = '-apple-system,system-ui,\"Segoe UI\",sans-serif';\nconst PH = 46; // 胶囊高度\nconst ICON = 30; // 右端声波图标方块尺寸\nconst BASE = [13, 7, 10, 6, 9]; // 竖条基准高度（左高右低的听写图标）\nconst TEXT = 'Speak or type here'; // 占位句，长度贴近原片（19→18 字符），打字节奏不变\n\nexport const GlassPillDictationTyping: React.FC = () => {\n  const t = useT();\n\n  // 定宽：原片胶囊宽度约为整句文本宽度的 2 倍，不随词伸缩——挂载时实测一次整句宽度\n  const measRef = useRef<HTMLDivElement>(null);\n  const [textW, setTextW] = useState(180); // 兜底估算值，useLayoutEffect 实测后覆盖（先于首帧绘制）\n  useLayoutEffect(() => {\n    if (measRef.current) setTextW(measRef.current.offsetWidth);\n  }, []);\n  const PW = Math.round(textW + 168);\n\n  // 出场：整体略大（~1.25x）快速浮现，约 0.45s 内缓落到位\n  const s = lerp(seg(t, 0, 0.22, E.outCubic), 1.25, 1);\n  // 打字：caret 先行（~t0.03），字符 t0.06→0.73 匀速铺完，尾段保持\n  const n = Math.floor(seg(t, 0.06, 0.73) * TEXT.length + 1e-6);\n  const caretO = seg(t, 0.025, 0.045) * (1 - seg(t, 0.75, 0.8));\n  // 强调色光随打字进度渐熄；同步收掉描边亮度的一点富余\n  const g = 1 - seg(t, 0.08, 0.76, E.inOutQuad);\n\n  return (\n    <DesignStage bg=\"#000\">\n      <div\n        style={{\n          position: 'absolute',\n          inset: 0,\n          background: '#000',\n          overflow: 'hidden',\n          // 暗部校色：原样片 x264 把外泛光的暗尾（1~2 级灰）压成纯 0，本渲染的\n          // 泛光尾巴因此比样片亮一档、拖得更远。contrast(1.008) 等价于黑端减 ~1 级\n          // （255*0.008/2≈1）、中间调不动，按对照帧实测校准（f1 0.9205→0.9844）。\n          filter: 'contrast(1.008)',\n        }}\n      >\n        {/* 居中走 translateX：PW 为奇数时圆心在 .5 半像素上，left/flex 会被布局\n            取整偏 0.5 设计像素；transform 矩阵不吸附，能与原片圆心逐像素对齐 */}\n        <div\n          style={{\n            position: 'absolute',\n            left: 0,\n            top: (270 - PH) / 2,\n            height: PH,\n            width: PW,\n            borderRadius: PH / 2,\n            boxSizing: 'border-box',\n            display: 'flex',\n            alignItems: 'center',\n            padding: '0 8px 0 16px',\n            overflow: 'hidden',\n            background: '#0d0d13',\n            willChange: 'transform,opacity',\n            opacity: seg(t, 0, 0.025),\n            transform: `translateX(${(480 - PW) / 2}px) scale(${s})`,\n            boxShadow: `inset 0 0 0 1px rgba(255,255,255,${0.16 + 0.1 * g}), inset 0 14px 22px rgba(255,255,255,${0.03 + 0.04 * g}), 0 0 ${26 * g}px rgba(${ACCENT_RGB},${0.28 * g})`,\n          }}\n        >\n          {/* 内嵌强调色光：左暗右亮的渐层，随打字进度熄灭（原片光在胶囊内部，无外部光晕） */}\n          <div\n            style={{\n              position: 'absolute',\n              inset: 0,\n              pointerEvents: 'none',\n              opacity: g,\n              background: `linear-gradient(90deg,rgba(${ACCENT_RGB},0) 0%,rgba(${ACCENT_RGB},.35) 42%,rgba(${ACCENT_RGB},.95) 100%)`,\n            }}\n          />\n          <div\n            style={{\n              position: 'relative',\n              font: `400 21px ${UI}`,\n              color: '#f4f4f5',\n              whiteSpace: 'pre',\n              letterSpacing: 0.3,\n              flex: 'none',\n            }}\n          >\n            {TEXT.slice(0, n)}\n          </div>\n          <div\n            style={{\n              position: 'relative',\n              width: 2,\n              height: 23,\n              borderRadius: 1,\n              background: '#eaeaec',\n              marginLeft: 2,\n              flex: 'none',\n              opacity: caretO,\n            }}\n          />\n          <div\n            style={{\n              position: 'relative',\n              marginLeft: 'auto',\n              width: ICON,\n              height: ICON,\n              borderRadius: 9,\n              boxSizing: 'border-box',\n              border: '1.5px solid rgba(255,255,255,.5)',\n              background: 'rgba(255,255,255,.05)',\n              display: 'flex',\n              alignItems: 'center',\n              justifyContent: 'center',\n              flex: 'none',\n            }}\n          >\n            {/* 声波竖条轻微呼吸（原片几乎静止，仅微动） */}\n            <div style={{ display: 'flex', alignItems: 'center', gap: 2.2, height: '100%' }}>\n              {BASE.map((h, i) => (\n                <div\n                  key={i}\n                  style={{\n                    width: 1.5,\n                    borderRadius: 1.5,\n                    background: '#ececee',\n                    height: h + 1.6 * Math.sin(t * 18 + i * 1.7),\n                  }}\n                />\n              ))}\n            </div>\n          </div>\n        </div>\n        {/* 隐藏测量条：与正文同字体同字距，量一次整句宽度以定胶囊定宽 */}\n        <div\n          ref={measRef}\n          style={{\n            position: 'absolute',\n            visibility: 'hidden',\n            whiteSpace: 'pre',\n            font: `400 21px ${UI}`,\n            letterSpacing: 0.3,\n            top: -999,\n          }}\n        >\n          {TEXT}\n        </div>\n      </div>\n    </DesignStage>\n  );\n};\n\n\n"
}