{
  "slug": "lead-word-zoom-assemble",
  "title": "Lead word zoom assemble",
  "tagline": "The lead word fills the frame at 2.3x, still pushing in during the hold, then shrinks to final size while the line slides left into place as the remaining words are pushed in from their slots.",
  "description": "The lead word fills the frame at 2.3x, still pushing in during the hold, then shrinks to final size while the line slides left into place as the remaining wor",
  "category": "typography",
  "tags": [
    "shotcraft",
    "remotion",
    "typography",
    "opening"
  ],
  "tier": "free",
  "license": "Apache-2.0",
  "spec": {
    "width": 1920,
    "height": 1080,
    "fps": 30,
    "duration": 2.8,
    "aspect": "16:9"
  },
  "kind": "remotion",
  "params_schema": {
    "type": "object",
    "properties": {}
  },
  "default_params": {},
  "preview": {
    "poster": "https://gimgs.net/motion/media/lead-word-zoom-assemble/poster-620a9ecfdb64.jpg",
    "video": "https://gimgs.net/motion/media/lead-word-zoom-assemble/video-04cb2810b1ab.mp4"
  },
  "variants": [
    {
      "key": "lead-word-zoom-assemble",
      "label": "lead-word-zoom-assemble · Lead word zoom assemble",
      "description": "The lead word fills the frame at 2.3x, still pushing in during the hold, then shrinks to final size while the line slides left into place as the remaining words are pushed in from their slots; the pivot is pinned to the lead word's center and baseline, a subhead surfaces as the line shifts up, and after a hold the whole frame crash-zooms and defocuses to hand off.",
      "use": "",
      "video": "https://gimgs.net/motion/media/lead-word-zoom-assemble/video-04cb2810b1ab.mp4",
      "poster": "https://gimgs.net/motion/media/lead-word-zoom-assemble/poster-620a9ecfdb64.jpg"
    }
  ],
  "credits": {
    "author": "video-shotcraft (Vincent Wei)",
    "url": "https://github.com/Vincentwei1021/video-shotcraft"
  },
  "page_url": "https://gimgs.net/motion/lead-word-zoom-assemble.html",
  "json_url": "https://gimgs.net/motion/lead-word-zoom-assemble.json",
  "template_url": "https://gimgs.net/motion/lead-word-zoom-assemble/template.html",
  "play_url": null,
  "markdown_url": "https://gimgs.net/motion/lead-word-zoom-assemble.md",
  "updated_at": "2026-09-24T07:40:04.080Z",
  "prompt": "## When to use\n\nAn \"Introducing\" title card for a brand/product name; the second shot of a launch-event-style opener; any one-liner that needs \"one word fills the frame first, then the full sentence completes around it\".\n\n## Intention\n\nThe most common mistake in a title card is fading the whole sentence in at once — the audience receives 5 words simultaneously and remembers none of them. This card turns reading order into **physical order**: give just one word first, and make it big enough to fill the frame and still pushing toward the viewer, forcing it to be read first; then that word shrinks back to its own size and position within the sentence, and the full sentence grows around it. By the time it settles, the audience has already read the lead word twice. The real crux isn't the scale curve, it's the **pivot point**. The lead word travels from \"dead center of the frame\" to its position within the line — if the pivot is the line box's center, the scaling and the leftward slide each drag the characters in different directions, and the baseline visibly jitters in the final frames. Pinning `transform-origin` horizontally to the lead word's center and vertically to the baseline keeps both points still throughout, so the whole line simply \"shrinks\" with no drift at all.\n\n## Duration & energy\n\n- Duration: About 2.8s (84f@30fps; the sentence assembles by f32, the shift-up and subhead run f34–50, a 22f hold, a final 12f crash)\n- Energy: Medium-high (starts with a full-frame big word, but there's only one motion throughout, no second impact)\n\n## Shot recipe (original Chinese — timing, easing, parameter table, known pitfalls)\n\n## 意图\n字卡最容易犯的错是\"整句一起淡入\"——观众同时收到 5 个词，一个都没记住。这张卡把\n阅读顺序做成**物理顺序**：先只给一个词，而且大到占满画面、还在继续朝观众推近，观众\n被迫先读它；然后这个词退回自己在句子里的尺寸和位置，整句围绕它长出来。落定时观众\n已经读过两遍主词。\n\n真正的手感命门不是缩放曲线，而是**支点**。首词从\"画面正中\"走到\"行内第三个字符的\n位置\"，如果支点取行盒中心，缩放和左滑会各自把字往两边拽，末尾几帧基线抖得肉眼可见。\n把 `transform-origin` 横向钉在首词中心、纵向钉在基线，这两点全程不动，整行就只是\n\"收小\"，没有任何漂移。\n\n## 动效核心\n- **两段缩放叠加成一条 scale**：\n  `scale = interpolate(f, [0,12], [2.3, 2.3·1.06], PUSH_EASE)\n         + interpolate(f, [12,24], [0, 1-2.3·1.06], ZOOM_EASE)`。\n  第一段是 hold 期间的推近（`cubic-bezier(.25,1,.5,1)`，减速后挂在峰值），第二段是缩回\n  （从峰值一路减到终字号 1）。写成相加而不是分段判断，两段之间没有速度断点\n- **缩回与左滑共用 ZOOM_EASE**（`cubic-bezier(.5,0,.05,1)`，重 ease-in-out、零回弹）：\n  `translate = interpolate(f, [12,36], [slideDistance, 0], ZOOM_EASE)`。两个动作必须读作\n  **一次**运动；各走各的曲线时会读成\"先缩小、再滑走\"两拍\n- **偏心距怎么来**：`slideDistance = lineWidth · (0.5 - leadRatio)`，`leadRatio` =\n  首词中心在整行里的占比。首词要停在画面正中，整行就得先向右偏这么多\n- **支点实测**：挂载时 `delayRender` 挂起渲染，量三个值——整行 `offsetWidth`、\n  首词 `offsetLeft + offsetWidth/2`、基线。基线用一个**零尺寸 inline-block 尺子**读出\n  （`<span style={{display:'inline-block',width:0,height:0}}>` 的 `offsetTop` 就是基线），\n  比 `lineHeight × 0.8` 之类的估算准得多。量到之后 `continueRender`，`transform-origin`\n  写成 `${leadRatio*100}% ${baseline}px`\n- **后续词是被推进来的，不是淡进来的**：第 i 词（i≥1）在\n  `pushStart = 12 + 6 + (i-1)·4` 起，`translate` 从 `0.5em → 0`\n  （`cubic-bezier(.22,.8,.36,1)`，起步快、落地长），透明度只用 2f 淡完。淡入必须极短，\n  否则词会\"浮现\"而不是\"被塞进槽位\"\n- **词间空格必须放在 inline-block 之外**。跟在词 span 内部尾随时会被行盒裁掉，\n  落定后所有词黏成一团（上游原组件的 bug，这里已修）\n- **场景层的三拍收尾**：f34–50 整行 `translateY 0→−56px`（outCubic），副行在**同一时窗**\n  淡入 + 上移 16px——上移让出的空间当帧被副行填掉，没有空当；f50–72 完全静止 22f；\n  f72–83 整幕 `scale 1→1.2` + `blur 0→9px` + `opacity 1→0.45`（ease-in-quad），推近失焦\n  交棒下一镜\n- **强调词**：精确匹配某个词换强调色（demo 里是 `Lumen`）。品牌词推入落位那一下自带\n  高亮，不用再加第二个动作\n\n## 参数表\n| 参数 | 典型值 | 调节手感 |\n|------|--------|----------|\n| 首词起手倍数 | initialScale 2.3 | 2.3 让首词横向溢出画面一点点（读作\"太大了装不下\"）；<1.8 只是\"稍大的字\"，冲击消失 |\n| 推近 | pushScale 1.06，占满 hold 12f | 6% 是\"还在朝观众来\"的最小可感量；>1.15 会在缩回时读作反向弹跳 |\n| hold | 12f | 首词独占画面的时长；<8f 观众读不完一个长单词，>20f 整句节奏塌 |\n| 缩回 | 12f，ZOOM_EASE | 与左滑同曲线；单独给缩回一条更快的曲线会让整行\"先缩完再滑\"，断成两拍 |\n| 左滑 | assembleDuration 24f | 比缩回长一倍：字号已经落定、位置还在走，这段\"滑行尾巴\"是电影感的来源 |\n| 后续词延迟 | wordDelay 6f（缩回起点之后） | 首词开始缩回才放后续词；同帧放会挤在一起看不清谁在动 |\n| 词间错峰 | wordStagger 4f | 4f 读作\"依次被推进\"；0 则整段后缀一起进，回到\"整句淡入\" |\n| 单词推入 | wordDuration 12f，wordPush 0.5em | 0.5em 是\"刚从槽位外面\"；>1em 会读作从画外飞入，与首词的收束方向打架 |\n| 词淡入 | wordFade 2f | 刻意极短。>6f 词会读作\"浮现\"，推入的力学感消失 |\n| 字号/字重/字距 | 96px / 600 / −0.03em | 负字距在放大 2.3 倍时才不会散架；正字距的大字会读作\"标语横幅\" |\n| 上移 + 副行 | f34–50，−56px，副行同窗 | 两件事必须同窗：先上移再出副行会多出一个空当拍 |\n| 静止 | 22f | 落定后的可读时间，低于 15f 观众来不及读副行 |\n| crash | 末 12f，scale→1.2，blur→9px | 这是转场的**前半**，动机要和下一镜的第一帧对上；单独播时看起来像\"没结束\"是正常的 |\n\n## 已知坑\n- **支点错了就抖基线**。`transform-origin` 必须是「首词中心 % + 基线 px」这一对。\n  取行盒中心（默认 `50% 50%`）时缩放与左滑互相拉扯，末尾几帧基线肉眼可见地跳\n- 基线别估算。`lineHeight × 0.8` 这类经验值在不同字体上差几像素，放大 2.3 倍后就是\n  十几像素的抖动。零尺寸 inline-block 尺子的 `offsetTop` 是唯一可靠的读法\n- `delayRender` 期间整行 `opacity: 0`——**必须**如此。否则未量到支点的第一帧会闪一下\n  错位的大字，成片里就是一帧脏帧\n- 布局是挂载时实测的，所以**结果随渲染环境的字体而变**：跨平台字体回退不同时整行宽度\n  会整体漂移（首词占比也跟着变）。介意的话把量到的三个值写死成常量\n- `scale` / `translate` 用的是独立 CSS 属性（不是 `transform` 简写）。混用会让其中一个\n  被覆盖；要合并成 `transform` 就必须自己保证 `translate → scale` 的顺序\n- 词间空格放进词 span 内部会被行盒裁掉（见上）。这个 bug 在小字号下不明显，96px 时\n  所有词会明显黏连\n- 首词必须是**短词**。\"Introducing\" 这类 10 字符左右刚好；首词超过 14 字符时 2.3 倍\n  放大会溢出画面两侧，观众读不到词尾\n- 强调色只给一个词。两个词都上色时观众不知道该记哪个，而且推入落位的高亮变成了\n  \"彩色标题\"，动作的意义被抹掉\n- 末段 crash 是转场的前半式，不是收尾。单独当片尾用会显得\"断了\"——要收尾就把\n  crash 换成静止 hold 或淡出\n\n## 参考实现\ndemos/typography/lead-word-zoom-assemble/\n（LeadWordZoomAssemble.tsx）\n\n## Reference implementation (Remotion)\n\n- demos/typography/lead-word-zoom-assemble/LeadWordZoomAssemble.tsx — https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/lead-word-zoom-assemble/LeadWordZoomAssemble.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/lead-word-zoom-assemble/LeadWordZoomAssemble.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/lead-word-zoom-assemble/LeadWordZoomAssemble.tsx",
      "url": "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/lead-word-zoom-assemble/LeadWordZoomAssemble.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": 84,
    "repo": "https://github.com/Vincentwei1021/video-shotcraft",
    "files": [
      "https://raw.githubusercontent.com/Vincentwei1021/video-shotcraft/main/demos/typography/lead-word-zoom-assemble/LeadWordZoomAssemble.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/lead-word-zoom-assemble/LeadWordZoomAssemble.tsx =====\n// lead-word-zoom-assemble — 首词推近落位组句\n// 首词以 2.3 倍字号占据画面中央，hold 期间继续向观众推近 6%，随后一条曲线同时\n// 完成「缩回终字号」与「整行左滑到最终排布」，后续词从右侧各自槽位外 0.5em 推入；\n// 整行上移的同一时窗里副行浮出，停一拍后整幕 crash-zoom 推近失焦交棒下一镜。\n//\n// 手感命门是支点：transform-origin 横向钉在**首词中心**、纵向钉在**基线**——\n// 这两点是全程唯一不该移动的锚，挂载时实测一次（基线用零尺寸 inline-block 的\n// offsetTop 读出），否则缩放过程中字会逐帧抖基线。\n// 参数以 1920×1080 标定。\nimport React, { Fragment, useEffect, useRef, useState } from 'react';\nimport {\n  AbsoluteFill,\n  continueRender,\n  delayRender,\n  Easing,\n  getRemotionEnvironment,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from 'remotion';\n\nexport const LEAD_WORD_ZOOM_ASSEMBLE_DURATION = 84; // 2.8s @30fps\n\n// ---- 编舞常量 ----\nconst TEXT = 'Introducing Lumen Deck';\nconst HIGHLIGHT_WORD = 'Lumen'; // 精确匹配的词换强调色：品牌词落位那一下自带高亮\nconst FONT_SIZE = 96;\nconst INITIAL_SCALE = 2.3; // 首词起手放大倍数\nconst INTRO_DURATION = 6; // f：首词淡入\nconst HOLD_DURATION = 12; // f：首词在峰值停留（推近发生在这一段）\nconst PUSH_SCALE = 1.06; // hold 期间继续推近的倍数\nconst RECEDE_DURATION = 12; // f：缩回终字号\nconst ASSEMBLE_DURATION = 24; // f：整行左滑到位\nconst WORD_DELAY = 6; // f：缩回起点后第二个词开始推入\nconst WORD_STAGGER = 4; // f：后续词之间的错峰\nconst WORD_DURATION = 12; // f：单词推入行程\nconst WORD_PUSH = 0.5; // em：后续词起始位置在自己槽位右侧多远\nconst WORD_FADE = 2; // f：后续词淡入（刻意极短——是被推进来的，不是淡进来的）\nconst LETTER_SPACING = '-0.03em';\n\n// ---- 场景层 ----\nconst LIFT: [number, number] = [34, 50]; // f：整行上移 + 副行同帧出现\nconst LIFT_DISTANCE = -56; // px\nconst SUBLINE = 'One shot card, one motion recipe — copy, paste, render.';\nconst CRASH_FRAMES = 12; // f：段尾 crash-zoom 占用的收尾帧数\nconst CRASH_SCALE = 0.2;\nconst CRASH_BLUR = 9;\n\nconst INK = '#1d1d1f';\nconst INK_DIM = '#7a7a7a';\nconst ACCENT = '#7A5AF8';\nconst SANS = '-apple-system, \"PingFang SC\", BlinkMacSystemFont, sans-serif';\nconst MESH_BG =\n  'radial-gradient(52% 44% at 18% 22%, rgba(122,90,248,0.20) 0%, rgba(122,90,248,0) 70%),' +\n  'radial-gradient(46% 42% at 84% 18%, rgba(255,138,178,0.20) 0%, rgba(255,138,178,0) 70%),' +\n  'radial-gradient(58% 50% at 78% 84%, rgba(96,190,255,0.20) 0%, rgba(96,190,255,0) 70%),' +\n  'radial-gradient(50% 46% at 24% 88%, rgba(255,196,112,0.20) 0%, rgba(255,196,112,0) 70%),' +\n  'linear-gradient(180deg, #f7f6f9 0%, #f2f1f5 100%)';\n\n/** 首词向观众漂移：先快后停，峰值处挂住 */\nconst PUSH_EASE = Easing.bezier(0.25, 1, 0.5, 1);\n/** 缩回与左滑共用同一条曲线——两个动作必须读作一次运动，不能各走各的 */\nconst ZOOM_EASE = Easing.bezier(0.5, 0, 0.05, 1);\n/** 后续词推入槽位：起步快，落地长而软 */\nconst WORD_EASE = Easing.bezier(0.22, 0.8, 0.36, 1);\n\nconst TextReveal: React.FC = () => {\n  const frame = useCurrentFrame();\n  const { width } = useVideoConfig();\n\n  // 整行宽度、首词中心占比、基线位置：只在挂载时量一次，量到之前挂起渲染\n  const lineRef = useRef<HTMLSpanElement>(null);\n  const leadRef = useRef<HTMLSpanElement>(null);\n  const baselineRef = useRef<HTMLSpanElement>(null);\n  const [handle] = useState(() => delayRender('lead-word-zoom-assemble: measure line'));\n  const [metrics, setMetrics] = useState<{\n    lineWidth: number;\n    leadRatio: number;\n    baseline: number;\n  } | null>(null);\n\n  useEffect(() => {\n    const line = lineRef.current;\n    const lead = leadRef.current;\n    if (!line || !lead) {\n      continueRender(handle);\n      return;\n    }\n    setMetrics({\n      lineWidth: line.offsetWidth,\n      leadRatio: (lead.offsetLeft + lead.offsetWidth / 2) / line.offsetWidth,\n      baseline: baselineRef.current?.offsetTop ?? line.offsetHeight * 0.8,\n    });\n  }, [handle]);\n\n  useEffect(() => {\n    if (metrics) continueRender(handle);\n  }, [metrics, handle]);\n\n  const words = TEXT.split(' ').filter(Boolean);\n  const ready = metrics !== null;\n  const leadRatio = metrics?.leadRatio ?? 0.14;\n  const lineWidth = metrics?.lineWidth ?? width * 0.5;\n  const baseline = metrics?.baseline ?? FONT_SIZE * 0.88;\n\n  const zoomStart = HOLD_DURATION;\n  // 首词从\"画面正中\"走到\"行内自己的位置\"，位移就是这段偏心距\n  const slideDistance = lineWidth * (0.5 - leadRatio);\n\n  return (\n    <AbsoluteFill style={{ alignItems: 'center', justifyContent: 'center' }}>\n      <span\n        ref={lineRef}\n        style={{\n          position: 'relative',\n          display: 'inline-block',\n          fontSize: FONT_SIZE,\n          fontWeight: 600,\n          color: INK,\n          letterSpacing: LETTER_SPACING,\n          lineHeight: 1.1,\n          whiteSpace: 'nowrap',\n          fontFamily: SANS,\n          transformOrigin: `${leadRatio * 100}% ${baseline}px`,\n          scale:\n            interpolate(frame, [0, HOLD_DURATION], [INITIAL_SCALE, INITIAL_SCALE * PUSH_SCALE], {\n              extrapolateLeft: 'clamp',\n              extrapolateRight: 'clamp',\n              easing: PUSH_EASE,\n            }) +\n            interpolate(\n              frame,\n              [zoomStart, zoomStart + RECEDE_DURATION],\n              [0, 1 - INITIAL_SCALE * PUSH_SCALE],\n              { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: ZOOM_EASE },\n            ),\n          translate: `${interpolate(\n            frame,\n            [zoomStart, zoomStart + ASSEMBLE_DURATION],\n            [slideDistance, 0],\n            { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: ZOOM_EASE },\n          )}px`,\n          opacity: ready ? 1 : 0,\n          textRendering: 'geometricPrecision',\n          ...(getRemotionEnvironment().isRendering ? null : { willChange: 'transform' as const }),\n        }}\n      >\n        {words.map((word, i) => {\n          const isLead = i === 0;\n          const pushStart = zoomStart + WORD_DELAY + (i - 1) * WORD_STAGGER;\n          const opacity = isLead\n            ? interpolate(frame, [0, INTRO_DURATION], [0, 1], {\n                extrapolateLeft: 'clamp',\n                extrapolateRight: 'clamp',\n              })\n            : interpolate(frame, [pushStart, pushStart + WORD_FADE], [0, 1], {\n                extrapolateLeft: 'clamp',\n                extrapolateRight: 'clamp',\n              });\n          return (\n            <Fragment key={i}>\n              <span\n                ref={isLead ? leadRef : undefined}\n                style={{\n                  display: 'inline-block',\n                  opacity,\n                  color: word === HIGHLIGHT_WORD ? ACCENT : undefined,\n                  translate: isLead\n                    ? undefined\n                    : `${interpolate(\n                        frame,\n                        [pushStart, pushStart + WORD_DURATION],\n                        [WORD_PUSH * FONT_SIZE, 0],\n                        { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: WORD_EASE },\n                      )}px`,\n                }}\n              >\n                {word}\n              </span>\n              {/* 词间空格必须放在 inline-block 之外——跟在词里会被行盒裁掉，词会黏在一起 */}\n              {i < words.length - 1 ? ' ' : null}\n            </Fragment>\n          );\n        })}\n        {/* 基线尺：零尺寸 inline-block，它的 offsetTop 就是基线 */}\n        <span ref={baselineRef} style={{ display: 'inline-block', width: 0, height: 0 }} />\n      </span>\n    </AbsoluteFill>\n  );\n};\n\nexport const LeadWordZoomAssemble: React.FC = () => {\n  const frame = useCurrentFrame();\n  const { durationInFrames } = useVideoConfig();\n\n  const lift = interpolate(frame, LIFT, [0, 1], {\n    extrapolateLeft: 'clamp',\n    extrapolateRight: 'clamp',\n    easing: Easing.out(Easing.cubic),\n  });\n  const crash = interpolate(\n    frame,\n    [durationInFrames - CRASH_FRAMES, durationInFrames - 1],\n    [0, 1],\n    { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.in(Easing.quad) },\n  );\n\n  return (\n    <AbsoluteFill style={{ background: MESH_BG, fontFamily: SANS }}>\n      <AbsoluteFill\n        style={{\n          transform: `scale(${1 + crash * CRASH_SCALE})`,\n          filter: crash > 0.01 ? `blur(${crash * CRASH_BLUR}px)` : undefined,\n          opacity: 1 - crash * 0.55,\n        }}\n      >\n        <AbsoluteFill style={{ transform: `translateY(${lift * LIFT_DISTANCE}px)` }}>\n          <TextReveal />\n        </AbsoluteFill>\n        <div\n          style={{\n            position: 'absolute',\n            left: 0,\n            right: 0,\n            top: '50%',\n            marginTop: 62,\n            textAlign: 'center',\n            fontSize: 32,\n            color: INK_DIM,\n            opacity: lift,\n            transform: `translateY(${(1 - lift) * 16}px)`,\n          }}\n        >\n          {SUBLINE}\n        </div>\n      </AbsoluteFill>\n    </AbsoluteFill>\n  );\n};\n\n\n"
}