{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "word-insert",
  "title": "Word Insert",
  "description": "A sentence edits itself, opening a gap and inserting a new word with a gradient.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/word-insert/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/Manrope\";\nimport { type CSSProperties, type ReactNode, useId, useMemo } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst { fontFamily: sans } = loadFont(\"normal\", {\n  weights: [\"500\"],\n  subsets: [\"latin\"],\n});\n\nexport interface WordInsertProps {\n  states?: string[];\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\n// Manrope 500 advance widths (1/1000 em) for ASCII 32–126, punctuation, kerning.\nconst MANROPE_500 = [\n  200, 329, 417, 924, 602, 901, 657, 229, 435, 435, 446, 575, 278, 420, 270,\n  403, 626, 406, 579, 563, 596, 581, 621, 513, 591, 621, 314, 318, 622, 750,\n  622, 534, 907, 646, 619, 723, 681, 575, 510, 714, 697, 249, 476, 612, 518,\n  846, 696, 732, 616, 732, 641, 630, 600, 708, 616, 949, 623, 575, 631, 413,\n  403, 413, 673, 660, 506, 563, 600, 561, 601, 591, 363, 600, 605, 249, 263,\n  510, 249, 865, 605, 601, 600, 601, 374, 531, 412, 605, 523, 779, 536, 538,\n  531, 430, 269, 430, 680,\n];\nconst MANROPE_500_EXTRA: Record<string, number> = {\n  \"‘\": 243,\n  \"’\": 243,\n  \"“\": 399,\n  \"”\": 399,\n  \"…\": 658,\n  \"–\": 540,\n  \"—\": 780,\n};\nconst KERN_PAIRS = [\n  \"AA-35 AC20 AG20 AO20 AQ20 AS20 AT60 AU20 AV70 AW70 AY60 Af25 At20\",\n  \"Av40 Aw40 Ay50 A’80 A“120 A”80 B,30 B.30 BT20 BV30 BW30 BX30 BY30\",\n  \"C,50 C.50 CA20 CS-30 CV20 CW20 CX30 CY30 D,50 D.50 DA20 DT20 DV30\",\n  \"DW30 DX30 DY30 DZ12 EC25 EG30 EO30 EQ30 ES20 F,115 F.115 FA10 FJ25\",\n  \"Fa20 Fc15 Fd15 Fe15 Fo15 Fq20 G,50 G.50 GA20 GT20 GV30 GW30 GX30 GY30\",\n  \"IY40 J,50 J.50 JA20 KC15 KG35 KO35 KQ30 KS30 Kc40 Kd40 Ke40 Kg30 Ko40\",\n  \"Kq35 Ks20 Kt31 Ku35 Ky45 K“20 LG20 LO20 LQ20 LT100 LV80 LW80 LY100\",\n  \"Lf20 Lt25 Lv35 Lw35 Ly50 L’120 L“120 L”120 O,50 O.50 OA20 OT20 OV30\",\n  \"OW30 OX50 OY30 Ox20 P,160 P.160 PA50 PJ25 PX30 Pa20 Pf-40 Q,30 Q.30\",\n  \"QA20 QT20 QV30 QW30 QX30 QY30 Qx20 RC15 RY12 S,25 S.25 SS-15 SV20\",\n  \"SW20 SX30 SY30 Sy10 T,100 T.100 TA20 TJ80 TT-60 TY-30 Ta70 Tc80 Td80\",\n  \"Te80 Tg80 Tm50 Tn50 To80 Tp50 Tq80 Tr50 Ts60 Tu50 Tv60 Tw60 Tx70 Ty70\",\n  \"Tz60 T”-40 U,20 U.20 UA30 US30 V,60 V.60 VA70 VC20 VG20 VJ30 VO20\",\n  \"VQ20 VS20 Va45 Vc60 Vd60 Ve60 Vg45 Vi20 Vl20 Vm30 Vn30 Vo60 Vp30 Vq45\",\n  \"Vs30 Vu30 Vv30 Vw30 Vx30 Vz20 W,60 W.60 WA70 WC20 WG20 WJ30 WO20 WQ20\",\n  \"WS20 Wa45 Wc60 Wd60 We60 Wg45 Wi20 Wl20 Wm30 Wn30 Wo60 Wp30 Wq45 Ws30\",\n  \"Wu30 Wv30 Ww30 Wx30 Wz20 XC30 XG30 XO30 XQ30 XS20 XT-40 Xc20 Xd20\",\n  \"Xe20 Xo20 Y,140 Y.140 YA70 YC40 YG40 YI25 YJ80 YO40 YQ40 YS30 YT-30\",\n  \"YX20 YY-31 Ya80 Yb25 Yc80 Yd80 Ye80 Yg80 Ym50 Yn50 Yo80 Yp50 Yq80\",\n  \"Yr40 Ys80 Yu40 Yv35 Yw35 Yx30 Yy35 Yz35 ZT-40 Zv60 Zw60 aT60 aV30\",\n  \"aW30 aY60 av20 aw20 ay30 a’30 a“30 a”20 b,20 b.20 bT60 bV30 bW30 bX20\",\n  \"bY80 bv20 bw20 bx20 by30 bz20 b’30 b“30 b”20 c,20 c.20 cT60 cV30 cW30\",\n  \"cY60 cc15 cd15 ce15 ci15 cl15 co15 cy20 c’30 c“30 c”20 dY40 e,20 e.20\",\n  \"eT60 eV30 eW30 eY60 ea10 em15 en15 ep15 ev20 ew20 ex20 ey10 e”30 f,50\",\n  \"f.50 fI-31 fT-60 fa15 fb-31 fc20 fd20 fe20 fh-31 fi-15 fk-31 fl-15\",\n  \"fo20 f’-30 f“-40 f”-30 gT50 gY50 g“50 hT60 h’20 h“30 h”20 i’37 kc10\",\n  \"kd10 ke10 ko10 kq30 ks10 l’37 mT60 mY70 my20 m’40 m“20 m”20 nT60 nY70\",\n  \"ny20 n’40 n“20 n”20 o,20 o.20 oT60 oV30 oW30 oX20 oY80 ov20 ow20 ox12\",\n  \"oy20 oz20 o’20 o“30 o”30 p,20 p.20 pT60 pV30 pW30 pX20 pY80 pv20 pw20\",\n  \"px12 py20 pz20 p’20 p“30 p”30 qY40 r,80 r.80 rY20 ra17 rc10 rd10 re10\",\n  \"rg20 ro10 rs20 ry10 sT50 sV30 sW30 sY60 sv20 sw20 sx20 sy20 s’20 s“30\",\n  \"s”30 tY20 tc22 td22 te22 to22 ty15 uY40 v,80 v.80 vA20 vT60 vY35 va30\",\n  \"vc20 vd20 ve20 vg20 vo20 vq20 vs20 w,80 w.80 wA20 wT60 wY35 wa30 wc20\",\n  \"wd20 we20 wg20 wo20 wq20 ws20 xT50 xY30 xa20 xc20 xd20 xe20 xg20 xo20\",\n  \"xq20 xv10 xw10 y,80 y.80 yA15 yT40 yY35 ya20 yc20 yd20 ye20 yf-40\",\n  \"yg15 yo20 yt-40 y”-30 zT50 zY35 za30 zc40 zd40 ze40 zo40 zq30 zy15\",\n  \"’A120 ’S30 ’a30 ’c75 ’d75 ’e75 ’g30 ’i-30 ’l-30 ’o75 ’q30 ’s80 “A120\",\n  \"“J120 “a30 “c30 “d30 “e30 “g30 “o30 “q30 “s30 “t-30 ”A120 ”J100 ”c30\",\n  \"”d30 ”e30 ”f30 ”g40 ”o30 ”q30 ”s30\",\n].join(\" \");\nconst KERN = new Map(\n  KERN_PAIRS.split(\" \").map((p) => [p.slice(0, 2), -Number(p.slice(2)) / 1000]),\n);\nconst TRACK = -0.03;\nconst ASC = 1.066;\nconst LINE = 1.366;\nconst CAP = 0.72;\nconst SPACE = 0.2 + TRACK;\nconst LEAD = 1.06;\nconst MAX_SIZE = 0.2;\nconst FILL = 0.84;\n\nconst ENTER_STAGGER = 3;\nconst ENTER_LEN = 18;\nconst FIRST = 36;\nconst STEP = 50;\nconst MOVE = 22;\nconst INSERT_DELAY = 12;\nconst INSERT_LEN = 18;\nconst INSERT_STAGGER = 3;\nconst ACCENT_HOLD = 12;\nconst SETTLE = 16;\nconst HOLD = 30;\n\nconst EASE_OUT = Easing.bezier(0.22, 1, 0.36, 1);\nconst EASE_IO = Easing.bezier(0.65, 0, 0.35, 1);\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\n// Inserted words are painted with one stage-wide gradient (clip-text):\n// gradients.sh \"aurora\" on dark, \"ribbon\" on light. `null` = the accent.\nconst THEMES = {\n  dark: {\n    bg: \"#0A0A0B\",\n    fg: \"#F3F2EF\",\n    grain: 0.07,\n    stops: [\"#a0f0c8\", \"#3fb5a3\", null, \"#b25c9c\"],\n  },\n  light: {\n    bg: \"#F5F4F0\",\n    fg: \"#121214\",\n    grain: 0.045,\n    stops: [null, \"#7a5cff\", \"#ff3d9a\", \"#ff8a3d\"],\n  },\n} as const;\n\nfunction advance(ch: string) {\n  const code = ch.charCodeAt(0);\n  if (code >= 32 && code < 127) return MANROPE_500[code - 32] / 1000;\n  return (MANROPE_500_EXTRA[ch] ?? 620) / 1000;\n}\n\nfunction textEm(s: string) {\n  let w = 0;\n  for (let i = 0; i < s.length; i++) {\n    if (i > 0) w += KERN.get(s[i - 1] + s[i]) ?? 0;\n    w += advance(s[i]) + TRACK;\n  }\n  return w;\n}\n\nfunction wordKey(token: string) {\n  return token.toLowerCase().replace(/[^\\p{L}\\p{N}]/gu, \"\");\n}\n\nfunction tokenize(state: string) {\n  return state.split(/\\s+/).filter(Boolean);\n}\n\nfunction matchTokens(a: string[], b: string[]) {\n  const ka = a.map(wordKey);\n  const kb = b.map(wordKey);\n  const dp = Array.from({ length: a.length + 1 }, () =>\n    new Array<number>(b.length + 1).fill(0),\n  );\n  for (let i = a.length - 1; i >= 0; i--) {\n    for (let j = b.length - 1; j >= 0; j--) {\n      dp[i][j] =\n        ka[i] === kb[j]\n          ? dp[i + 1][j + 1] + 1\n          : Math.max(dp[i + 1][j], dp[i][j + 1]);\n    }\n  }\n  const pairs: [number, number][] = [];\n  let i = 0;\n  let j = 0;\n  while (i < a.length && j < b.length) {\n    if (ka[i] === kb[j]) {\n      pairs.push([i, j]);\n      i++;\n      j++;\n    } else if (dp[i + 1][j] >= dp[i][j + 1]) i++;\n    else j++;\n  }\n  return pairs;\n}\n\ninterface Point {\n  x: number;\n  y: number;\n}\n\ninterface Layout {\n  tokens: string[];\n  x: number[];\n  y: number[];\n  w: number[];\n}\n\ninterface Track {\n  key: string;\n  idx: (number | null)[];\n  virtualFrom: (Point | null)[];\n  virtualTo: (Point | null)[];\n  bornAt: number;\n  bornOrder: number;\n}\n\ninterface Plan {\n  tracks: Track[];\n  owner: number[][];\n  pairs: [number, number][][];\n}\n\nfunction planTracks(states: string[][]): Plan {\n  const tracks: Track[] = [];\n  const blankIdx = () => states.map(() => null as number | null);\n  const blankPt = () => states.map(() => null as Point | null);\n  const add = (s: number, j: number, order: number) => {\n    const idx = blankIdx();\n    idx[s] = j;\n    tracks.push({\n      key: `t${tracks.length}`,\n      idx,\n      virtualFrom: blankPt(),\n      virtualTo: blankPt(),\n      bornAt: s,\n      bornOrder: order,\n    });\n    return tracks.length - 1;\n  };\n  const owner: number[][] = [states[0].map((_, i) => add(0, i, i))];\n  const pairs: [number, number][][] = [[]];\n  for (let s = 1; s < states.length; s++) {\n    const matched = matchTokens(states[s - 1], states[s]);\n    pairs.push(matched);\n    const fromB = new Map(matched.map(([i, j]) => [j, i]));\n    let order = 0;\n    owner.push(\n      states[s].map((_, j) => {\n        const i = fromB.get(j);\n        if (i === undefined) return add(s, j, order++);\n        const id = owner[s - 1][i];\n        tracks[id].idx[s] = j;\n        return id;\n      }),\n    );\n  }\n  return { tracks, owner, pairs };\n}\n\nfunction lineOf(plan: Plan, states: string[][], cut: number | null) {\n  const last = states.length - 1;\n  return states.map((tokens, s) => {\n    let line = 0;\n    return tokens.map((_, i) => {\n      const lastIdx = plan.tracks[plan.owner[s][i]].idx[last];\n      if (cut !== null && lastIdx !== null && lastIdx >= cut) line = 1;\n      return line;\n    });\n  });\n}\n\nfunction widestLine(states: string[][], lines: number[][]) {\n  let widest = 0;\n  states.forEach((tokens, s) => {\n    const sums = [0, 0];\n    const counts = [0, 0];\n    tokens.forEach((tok, i) => {\n      const l = lines[s][i];\n      sums[l] += textEm(tok) + (counts[l] > 0 ? SPACE : 0);\n      counts[l]++;\n    });\n    widest = Math.max(widest, sums[0], sums[1]);\n  });\n  return widest;\n}\n\nfunction layoutState(tokens: string[], lines: number[]): Layout {\n  const w = tokens.map(textEm);\n  const used = [...new Set(lines)];\n  const x: number[] = new Array(tokens.length).fill(0);\n  const y: number[] = new Array(tokens.length).fill(0);\n  used.forEach((line, row) => {\n    const members = tokens.map((_, i) => i).filter((i) => lines[i] === line);\n    const lineW =\n      members.reduce((s, i) => s + w[i], 0) +\n      SPACE * Math.max(0, members.length - 1);\n    let cursor = -lineW / 2;\n    for (const i of members) {\n      x[i] = cursor;\n      y[i] = (row - (used.length - 1) / 2) * LEAD;\n      cursor += w[i] + SPACE;\n    }\n  });\n  return { tokens, x, y, w };\n}\n\nfunction gapPoint(\n  src: Layout,\n  dst: Layout,\n  dstIndex: number,\n  map: Map<number, number>,\n): Point {\n  const n = dst.tokens.length;\n  const sameLine = (a: number, b: number) =>\n    Math.abs(dst.y[a] - dst.y[b]) < 1e-6;\n  let runStart = dstIndex;\n  while (\n    runStart > 0 &&\n    !map.has(runStart - 1) &&\n    sameLine(runStart - 1, dstIndex)\n  )\n    runStart--;\n  let runEnd = dstIndex;\n  while (\n    runEnd < n - 1 &&\n    !map.has(runEnd + 1) &&\n    sameLine(runEnd + 1, dstIndex)\n  )\n    runEnd++;\n  const runLeft = dst.x[runStart];\n  const runW = dst.x[runEnd] + dst.w[runEnd] - runLeft;\n  const at = (k: number) =>\n    k >= 0 && k < n && sameLine(k, dstIndex) ? map.get(k) : undefined;\n  const prev = at(runStart - 1);\n  const next = at(runEnd + 1);\n  let left = runLeft;\n  let y = dst.y[dstIndex];\n  if (prev !== undefined && next !== undefined) {\n    left = (src.x[prev] + src.w[prev] + src.x[next]) / 2 - runW / 2;\n    y = src.y[prev];\n  } else if (prev !== undefined) {\n    left = src.x[prev] + src.w[prev] + SPACE;\n    y = src.y[prev];\n  } else if (next !== undefined) {\n    left = src.x[next] - SPACE - runW;\n    y = src.y[next];\n  }\n  return { x: left + (dst.x[dstIndex] - runLeft), y };\n}\n\nfunction resolveVirtual(plan: Plan, layouts: Layout[]) {\n  for (let s = 1; s < layouts.length; s++) {\n    const a = layouts[s - 1];\n    const b = layouts[s];\n    const fromB = new Map(plan.pairs[s].map(([i, j]) => [j, i]));\n    const fromA = new Map(plan.pairs[s].map(([i, j]) => [i, j]));\n    b.tokens.forEach((_, j) => {\n      if (!fromB.has(j))\n        plan.tracks[plan.owner[s][j]].virtualFrom[s] = gapPoint(a, b, j, fromB);\n    });\n    a.tokens.forEach((_, i) => {\n      if (!fromA.has(i))\n        plan.tracks[plan.owner[s - 1][i]].virtualTo[s] = gapPoint(\n          b,\n          a,\n          i,\n          fromA,\n        );\n    });\n  }\n  return plan.tracks;\n}\n\nfunction splitChange(from: string, to: string) {\n  let pre = 0;\n  while (pre < from.length && pre < to.length && from[pre] === to[pre]) pre++;\n  let suf = 0;\n  while (\n    suf < from.length - pre &&\n    suf < to.length - pre &&\n    from[from.length - 1 - suf] === to[to.length - 1 - suf]\n  )\n    suf++;\n  return {\n    prefix: from.slice(0, pre),\n    midFrom: from.slice(pre, from.length - suf),\n    midTo: to.slice(pre, to.length - suf),\n    suffix: from.slice(from.length - suf),\n  };\n}\n\nfunction Grain({ opacity }: { opacity: number }) {\n  const id = `g${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`;\n  return (\n    <svg\n      width=\"100%\"\n      height=\"100%\"\n      style={{ position: \"absolute\", inset: 0, opacity, pointerEvents: \"none\" }}\n      aria-hidden\n    >\n      <filter id={id}>\n        <feTurbulence\n          type=\"fractalNoise\"\n          baseFrequency=\"0.9\"\n          numOctaves={2}\n          seed={23}\n          stitchTiles=\"stitch\"\n        />\n        <feColorMatrix values=\"0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0 0.5  0 0 0 0.9 0\" />\n      </filter>\n      <rect width=\"100%\" height=\"100%\" filter={`url(#${id})`} />\n    </svg>\n  );\n}\n\nconst DEFAULT_STATES = [\n  \"Launch videos.\",\n  \"Launch videos, as code.\",\n  \"Beautiful launch videos, as code.\",\n];\n\nexport function wordInsertDuration(states: number = DEFAULT_STATES.length) {\n  const last = FIRST + Math.max(0, states - 2) * STEP;\n  return last + INSERT_DELAY + INSERT_LEN + ACCENT_HOLD + SETTLE + HOLD;\n}\n\nexport function WordInsert({\n  states = DEFAULT_STATES,\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: WordInsertProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const c = THEMES[theme];\n\n  const tokenized = useMemo(\n    () => (states.length ? states : [\"\"]).map(tokenize),\n    [states],\n  );\n  const fillW = W * FILL;\n  const { F, tracks, layouts } = useMemo(() => {\n    const plan = planTracks(tokenized);\n    const lastLen = tokenized[tokenized.length - 1].length;\n    let best = { size: 0, lines: lineOf(plan, tokenized, null) };\n    for (let cut = -1; cut < lastLen; cut++) {\n      if (cut === 0) continue;\n      const lines = lineOf(plan, tokenized, cut < 0 ? null : cut);\n      const size = Math.min(\n        H * MAX_SIZE,\n        fillW / Math.max(0.1, widestLine(tokenized, lines)),\n      );\n      if (size > best.size + 0.5) best = { size, lines };\n    }\n    const laid = tokenized.map((tokens, s) =>\n      layoutState(tokens, best.lines[s]),\n    );\n    return { F: best.size, tracks: resolveVirtual(plan, laid), layouts: laid };\n  }, [tokenized, fillW, H]);\n\n  const startOf = (s: number) => FIRST + (s - 1) * STEP;\n  let cur = 0;\n  for (let s = 1; s < layouts.length; s++) if (t >= startOf(s)) cur = s;\n  const p =\n    cur === 0\n      ? 1\n      : EASE_IO(\n          interpolate(t, [startOf(cur), startOf(cur) + MOVE], [0, 1], CLAMP),\n        );\n  const prev = Math.max(0, cur - 1);\n  const la = layouts[prev];\n  const lb = layouts[cur];\n  const baseline = H / 2 + (CAP * F) / 2;\n\n  // One gradient band across the whole measure; each inserted word samples\n  // the slice it sits on, drifting a few percent over the shot.\n  const final = layouts[layouts.length - 1];\n  const bandL = Math.min(0, ...final.x);\n  const bandR = Math.max(0, ...final.x.map((v, i) => v + final.w[i]));\n  const bandEm = Math.max(0.1, bandR - bandL);\n  const drift = 0.04 * bandEm * Math.sin(t * 0.012);\n  const stops = c.stops.map((col) => col ?? accent);\n  const gradient = `linear-gradient(90deg, ${stops\n    .map((col, i) => `${col} ${((i / (stops.length - 1)) * 100).toFixed(1)}%`)\n    .join(\", \")})`;\n  const PAD = 0.1;\n  const paint = (text: string, ex: number, extra?: CSSProperties) => (\n    <span\n      style={{\n        ...extra,\n        marginLeft: `${-PAD}em`,\n        padding: `0 ${PAD}em`,\n        color: \"transparent\",\n        backgroundImage: gradient,\n        backgroundSize: `${bandEm * 1.1}em 100%`,\n        backgroundPosition: `${-(ex - PAD - bandL + bandEm * 0.05) + drift}em 0`,\n        backgroundRepeat: \"no-repeat\",\n        WebkitBackgroundClip: \"text\",\n        backgroundClip: \"text\",\n      }}\n    >\n      {text}\n    </span>\n  );\n\n  const words: ReactNode[] = [];\n  for (const tr of tracks) {\n    const ia = tr.idx[prev];\n    const ib = tr.idx[cur];\n    if (ia === null && ib === null) continue;\n\n    let x = 0;\n    let y = 0;\n    let opacity = 1;\n    let rise = 0;\n    let content: ReactNode = null;\n    // Inserted words carry the gradient for the rest of the shot.\n    const born = tr.bornAt > 0;\n\n    if (cur === 0 && ib !== null) {\n      x = lb.x[ib];\n      y = lb.y[ib];\n      const start = ib * ENTER_STAGGER;\n      const e = EASE_OUT(\n        interpolate(t, [start, start + ENTER_LEN], [0, 1], CLAMP),\n      );\n      opacity = e;\n      rise = (1 - e) * 0.24;\n      content = lb.tokens[ib];\n    } else if (ia !== null && ib !== null) {\n      x = la.x[ia] + (lb.x[ib] - la.x[ia]) * p;\n      y = la.y[ia] + (lb.y[ib] - la.y[ia]) * p;\n      const from = la.tokens[ia];\n      const to = lb.tokens[ib];\n      if (from === to || p >= 1) {\n        content = to;\n      } else {\n        const sp = splitChange(from, to);\n        const mix = interpolate(p, [0.2, 0.8], [0, 1], CLAMP);\n        const preW = textEm(sp.prefix);\n        const midW =\n          textEm(sp.midFrom) + (textEm(sp.midTo) - textEm(sp.midFrom)) * p;\n        const part = (key: string, text: string, left: number, o: number) => {\n          const style: CSSProperties = {\n            position: \"absolute\",\n            left: `${left}em`,\n            top: 0,\n            opacity: o,\n          };\n          if (born)\n            return (\n              <span key={key} style={style}>\n                {paint(text, x + left)}\n              </span>\n            );\n          return (\n            <span key={key} style={style}>\n              {text}\n            </span>\n          );\n        };\n        content = [\n          part(\"pre\", sp.prefix, 0, 1),\n          part(\"from\", sp.midFrom, preW, 1 - mix),\n          part(\"to\", sp.midTo, preW, mix),\n          part(\"suf\", sp.suffix, preW + midW, 1),\n        ];\n      }\n    } else if (ib !== null) {\n      const from = tr.virtualFrom[cur] ?? { x: lb.x[ib], y: lb.y[ib] };\n      x = from.x + (lb.x[ib] - from.x) * p;\n      y = from.y + (lb.y[ib] - from.y) * p;\n      content = lb.tokens[ib];\n    } else if (ia !== null) {\n      const to = tr.virtualTo[cur] ?? { x: la.x[ia], y: la.y[ia] };\n      x = la.x[ia] + (to.x - la.x[ia]) * p;\n      y = la.y[ia] + (to.y - la.y[ia]) * p;\n      const out = interpolate(\n        t,\n        [startOf(cur), startOf(cur) + MOVE * 0.45],\n        [0, 1],\n        { ...CLAMP, easing: EASE_IO },\n      );\n      opacity = 1 - out;\n      rise = -out * 0.14;\n      content = la.tokens[ia];\n    }\n\n    if (tr.bornAt > 0) {\n      const start =\n        startOf(tr.bornAt) + INSERT_DELAY + tr.bornOrder * INSERT_STAGGER;\n      const e = EASE_OUT(\n        interpolate(t, [start, start + INSERT_LEN], [0, 1], CLAMP),\n      );\n      if (tr.bornAt === cur) {\n        opacity *= e;\n        rise = (1 - e) * 0.32;\n      }\n    }\n    if (born && typeof content === \"string\") content = paint(content, x);\n\n    words.push(\n      <div\n        key={tr.key}\n        style={{\n          position: \"absolute\",\n          left: `${x}em`,\n          top: `${y - ASC + rise}em`,\n          opacity,\n          color: c.fg,\n          whiteSpace: \"pre\",\n        }}\n      >\n        {content}\n      </div>,\n    );\n  }\n\n  return (\n    <AbsoluteFill style={{ background: c.bg, overflow: \"hidden\" }}>\n      <div\n        style={{\n          position: \"absolute\",\n          left: W / 2,\n          top: baseline,\n          fontFamily: sans,\n          fontWeight: 500,\n          fontSize: F,\n          lineHeight: LINE,\n          letterSpacing: `${TRACK}em`,\n          fontKerning: \"normal\",\n        }}\n      >\n        {words}\n      </div>\n      <Grain opacity={c.grain} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/word-insert.tsx"
    }
  ],
  "type": "registry:component"
}
