{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shape-morph-o",
  "title": "Shape Morph O",
  "description": "A shape morphs into the O of the wordmark beside the R mark.",
  "dependencies": ["remotion", "@remotion/google-fonts", "@remotion/paths"],
  "files": [
    {
      "path": "registry/remocn/shape-morph-o/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont } from \"@remotion/google-fonts/Manrope\";\nimport { interpolatePath } from \"@remotion/paths\";\nimport { useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport {\n  AbsoluteFill,\n  continueRender,\n  delayRender,\n  Easing,\n  interpolate,\n  interpolateColors,\n  spring,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst WEIGHT = 500;\nconst display = loadFont(\"normal\", { weights: [\"500\"], subsets: [\"latin\"] });\n\nconst REMOCN_R_PATH =\n  \"M 0.01 0.81 C 0.01 1.73, 0.36 2.79, 1.09 4.13 C 4.91 11.04, 13.45 17.16, 21.7 18.9 C 22.94 19.16, 23.18 19.16, 51.39 19.27 C 76.07 19.36, 80.02 19.4, 81.32 19.57 C 89.89 20.69, 96.2 24.68, 99.38 31.01 C 103.19 38.56, 102.53 50.1, 97.91 57.07 C 94.66 61.96, 89.68 64.99, 83.26 66 C 82.81 66.07, 70.18 66.83, 55.2 67.69 C 24.82 69.43, 27.03 69.23, 24.18 70.4 C 19.9 72.15, 14.84 75.7, 10.65 79.89 C 4.86 85.68, 1.3 91.91, 0.25 98.13 C 0.12 98.85, 0.08 103.13, 0.04 116.66 L 0 134.26 9.5 134.26 L 19 134.26 19.05 119.41 C 19.1 103.27, 19.08 103.82, 19.82 101.04 C 21.79 93.65, 27.86 88.75, 36.45 87.63 C 37.23 87.53, 39.41 87.5, 43.57 87.53 C 50.12 87.59, 50.75 87.65, 53.22 88.45 C 56.61 89.56, 59.67 91.86, 62.02 95.07 C 62.52 95.76, 69.35 103.85, 77.2 113.07 C 85.04 122.28, 91.63 130.04, 91.85 130.32 C 92.07 130.59, 92.5 131.34, 92.82 131.97 C 93.52 133.35, 94.11 133.93, 95.06 134.13 C 95.5 134.23, 98.97 134.26, 106.36 134.23 L 117.01 134.19 100.82 113.07 C 91.91 101.45, 84.52 91.78, 84.39 91.57 C 83.36 89.89, 83.66 87.53, 85.09 86 C 85.79 85.25, 86.36 84.94, 88.07 84.38 C 96.18 81.72, 104.15 76.62, 109.97 70.36 C 120.59 58.93, 124.06 44.32, 119.44 30.43 C 114.59 15.81, 101.93 4.02, 87.64 0.83 C 83.93 0.01, 88.09 0.08, 41.85 0.04 L 0.01 0 0.01 0.81 Z\";\n/** remocn.dev R mark: viewBox 124.06 x 134.26, set 0.76em tall on the baseline. */\nconst MARK_W = 124.06;\nconst MARK_H = 134.26;\nconst MARK_EM = 0.76;\nconst MARK_ADV = (MARK_EM * MARK_W) / MARK_H;\n/** Gap between the mark and the next letter, as on the site header. */\nconst MARK_GAP = 0.04;\n\nexport interface ShapeMorphOProps {\n  /** The word the shape lands in. Its first \"o\" (or O, 0, Q) becomes the shape. */\n  word?: string;\n  /**\n   * Set a leading \"r\" as the remocn.dev R mark, so the default word reads as\n   * the logo. Ignored when the word doesn't start with \"r\".\n   */\n  mark?: boolean;\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  /** Playback multiplier. */\n  speed?: number;\n}\n\nconst THEMES = {\n  dark: { bg: \"#0A0A0B\", fg: \"#F3F2EF\" },\n  light: { bg: \"#F5F4F0\", fg: \"#121214\" },\n} as const;\n\n/** Points per outline. Every shape uses the same count so paths morph 1:1. */\nconst N = 192;\nconst PROBE = 600;\nconst TRACK = -0.02;\n/** Minimum clear ink space between neighbouring glyphs, in em. */\nconst MIN_GAP = 0.03;\n\n// Timeline (frames at speed 1).\nconst ENTER = 10;\nconst FIRST = 8;\nconst GAP = 16;\nconst MORPHS = 5;\nconst LAND = FIRST + MORPHS * GAP + 2;\nconst REVEAL = LAND + 9;\n/** The counter opens once the word is out, after a short solid beat. */\nconst COUNTER = REVEAL + 15;\nconst COUNTER_LEN = 9;\n\n/** Absolute rotation (deg) each shape settles at: circle, cookie, clover, burst, pill, square. */\nconst ROT = [0, 90, 135, 210, 270, 360];\n\nconst SPRING_MORPH = { damping: 15, stiffness: 150, mass: 0.8 };\nconst SPRING_LAND = { damping: 24, stiffness: 150, mass: 0.9 };\nconst SPRING_REVEAL = { damping: 19, stiffness: 120, mass: 0.8 };\n\ntype Pt = [number, number];\n\nconst softAbs = (v: number, e = 0.015) => Math.sqrt(v * v + e);\n\nfunction polar(r: (a: number) => number, scale = 1): Pt[] {\n  const out: Pt[] = [];\n  for (let k = 0; k < N; k++) {\n    const a = (k / N) * Math.PI * 2;\n    const rr = r(a) * scale;\n    out.push([Math.cos(a) * rr, Math.sin(a) * rr]);\n  }\n  return out;\n}\n\n/** Distance from the origin to a closed convex outline along angle `a`. */\nfunction rayToSdf(sdf: (x: number, y: number) => number, a: number) {\n  let lo = 0;\n  let hi = 3;\n  for (let i = 0; i < 40; i++) {\n    const mid = (lo + hi) / 2;\n    if (sdf(Math.cos(a) * mid, Math.sin(a) * mid) < 0) lo = mid;\n    else hi = mid;\n  }\n  return (lo + hi) / 2;\n}\n\nfunction pillSdf(x: number, y: number) {\n  const r = 0.56;\n  const half = 1.04 - r;\n  const qx = Math.max(Math.abs(x) - half, 0);\n  return Math.hypot(qx, y) - r;\n}\n\nfunction roundSquareSdf(x: number, y: number) {\n  const half = 0.84;\n  const rc = 0.3;\n  const qx = Math.abs(x) - (half - rc);\n  const qy = Math.abs(y) - (half - rc);\n  return (\n    Math.hypot(Math.max(qx, 0), Math.max(qy, 0)) +\n    Math.min(Math.max(qx, qy), 0) -\n    rc\n  );\n}\n\n/** The expressive shape library, all in unit space (radius ~1). */\nfunction buildShapes(): Pt[][] {\n  const circle = polar(() => 1);\n  // 8 broad scallops with tight valleys.\n  const cookie = polar(\n    (a) => 0.87 + 0.13 * softAbs(Math.cos(4 * a), 0.06) ** 0.75,\n    1.02,\n  );\n  // 4 round leaves on the diagonals, pinched between.\n  const clover = polar(\n    (a) => 0.56 + 0.44 * softAbs(Math.cos(2 * a + Math.PI / 2), 0.03) ** 0.5,\n    1.06,\n  );\n  // 10 soft points.\n  const burst = polar(\n    (a) => 0.85 + 0.15 * ((Math.cos(10 * a) + 1) / 2) ** 1.1,\n    1.06,\n  );\n  const pill = polar((a) => rayToSdf(pillSdf, a));\n  const square = polar((a) => rayToSdf(roundSquareSdf, a));\n  return [circle, cookie, clover, burst, pill, square];\n}\n\nconst f = (v: number) => (Math.abs(v) < 1e-9 ? \"0\" : v.toFixed(4));\n\n/** Closed Catmull-Rom through the points, as N cubic segments. */\nfunction toPath(pts: Pt[]) {\n  const n = pts.length;\n  let d = `M${f(pts[0][0])} ${f(pts[0][1])}`;\n  for (let i = 0; i < n; i++) {\n    const p0 = pts[(i - 1 + n) % n];\n    const p1 = pts[i];\n    const p2 = pts[(i + 1) % n];\n    const p3 = pts[(i + 2) % n];\n    const c1x = p1[0] + (p2[0] - p0[0]) / 6;\n    const c1y = p1[1] + (p2[1] - p0[1]) / 6;\n    const c2x = p2[0] - (p3[0] - p1[0]) / 6;\n    const c2y = p2[1] - (p3[1] - p1[1]) / 6;\n    d += `C${f(c1x)} ${f(c1y)} ${f(c2x)} ${f(c2y)} ${f(p2[0])} ${f(p2[1])}`;\n  }\n  return `${d}Z`;\n}\n\ninterface Metrics {\n  /** Pen x of every glyph, in em, tracking included. */\n  pen: number[];\n  /** Advance of every glyph, in em. */\n  adv: number[];\n  width: number;\n  ascent: number;\n  descent: number;\n  slot: number;\n  /** Slot glyph's ink centre relative to its pen / baseline, in em. */\n  cx: number;\n  cy: number;\n  /** Half ink height of the slot glyph, in em. */\n  halfH: number;\n  /** Outer contour of the slot glyph, normalised so halfH = 1. */\n  contour: Pt[];\n  /** Its counter, same centre and scale; null if the glyph has none. */\n  counter: Pt[] | null;\n}\n\nfunction findSlot(chars: string[], from: number) {\n  for (const c of [\"o\", \"O\", \"0\", \"Q\"]) {\n    const i = chars.indexOf(c, from);\n    if (i >= 0) return i;\n  }\n  return Math.floor(chars.length / 2);\n}\n\nfunction measure(chars: string[], withMark: boolean): Metrics {\n  const font = `${WEIGHT} ${PROBE}px \"${display.fontFamily}\"`;\n  const ctx = document.createElement(\"canvas\").getContext(\"2d\");\n  if (!ctx) throw new Error(\"shape-morph-o: no 2d context\");\n  ctx.font = font;\n  const w = (s: string) => ctx.measureText(s).width / PROBE;\n  // With the mark, glyph 0 is the R and text metrics start at glyph 1.\n  const lead = withMark ? 1 : 0;\n  const m = ctx.measureText(chars.slice(lead).join(\"\"));\n  const slot = findSlot(chars, lead);\n\n  const size = Math.ceil(PROBE * 1.6);\n  const canvas = document.createElement(\"canvas\");\n  canvas.width = size;\n  canvas.height = size;\n  const g = canvas.getContext(\"2d\", { willReadFrequently: true });\n  if (!g) throw new Error(\"shape-morph-o: no 2d context\");\n  const padX = Math.round(PROBE * 0.3);\n  const base = Math.round(PROBE * 1.2);\n  g.font = font;\n  g.fillStyle = \"#000\";\n  g.textBaseline = \"alphabetic\";\n  const raster = (ch: string) => {\n    g.clearRect(0, 0, size, size);\n    g.fillText(ch, padX, base);\n    return g.getImageData(0, 0, size, size).data;\n  };\n\n  // Per-row ink extents of every glyph, so neighbours never touch.\n  const profiles = new Map<string, { l: Float32Array; r: Float32Array }>();\n  for (const ch of new Set(chars.slice(lead))) {\n    const px = raster(ch);\n    const l = new Float32Array(size).fill(Number.NaN);\n    const r = new Float32Array(size).fill(Number.NaN);\n    for (let y = 0; y < size; y++) {\n      for (let x = 0; x < size; x++) {\n        if (px[(y * size + x) * 4 + 3] > 24) {\n          if (Number.isNaN(l[y])) l[y] = (x - padX) / PROBE;\n          r[y] = (x + 1 - padX) / PROBE;\n        }\n      }\n    }\n    profiles.set(ch, { l, r });\n  }\n\n  const pen: number[] = [];\n  const adv: number[] = [];\n  let push = 0;\n  const origin = withMark ? MARK_ADV + MARK_GAP : 0;\n  if (withMark) {\n    adv.push(MARK_ADV);\n    pen.push(0);\n  }\n  for (let i = lead; i < chars.length; i++) {\n    adv.push(w(chars[i]));\n    pen.push(\n      origin +\n        w(chars.slice(lead, i + 1).join(\"\")) -\n        adv[i] +\n        (i - lead) * TRACK +\n        push,\n    );\n    const a = profiles.get(chars[i - 1]);\n    const b = profiles.get(chars[i]);\n    if (i > lead && a && b) {\n      let gap = Number.POSITIVE_INFINITY;\n      for (let y = 0; y < size; y++) {\n        const g0 = pen[i] + b.l[y] - (pen[i - 1] + a.r[y]);\n        if (!Number.isNaN(g0)) gap = Math.min(gap, g0);\n      }\n      if (gap < MIN_GAP) {\n        push += MIN_GAP - gap;\n        pen[i] += MIN_GAP - gap;\n      }\n    }\n  }\n\n  // Rasterise the slot glyph and ray-cast its outer contour from the ink centre.\n  const data = raster(chars[slot] ?? \"o\");\n  const alpha = (x: number, y: number) => {\n    const x0 = Math.floor(x);\n    const y0 = Math.floor(y);\n    if (x0 < 0 || y0 < 0 || x0 >= size - 1 || y0 >= size - 1) return 0;\n    const fx = x - x0;\n    const fy = y - y0;\n    const at = (xx: number, yy: number) => data[(yy * size + xx) * 4 + 3] / 255;\n    return (\n      at(x0, y0) * (1 - fx) * (1 - fy) +\n      at(x0 + 1, y0) * fx * (1 - fy) +\n      at(x0, y0 + 1) * (1 - fx) * fy +\n      at(x0 + 1, y0 + 1) * fx * fy\n    );\n  };\n  let minX = size;\n  let maxX = -1;\n  let minY = size;\n  let maxY = -1;\n  for (let y = 0; y < size; y++) {\n    for (let x = 0; x < size; x++) {\n      if (data[(y * size + x) * 4 + 3] > 127) {\n        if (x < minX) minX = x;\n        if (x > maxX) maxX = x;\n        if (y < minY) minY = y;\n        if (y > maxY) maxY = y;\n      }\n    }\n  }\n  const cxPx = (minX + maxX + 1) / 2;\n  const cyPx = (minY + maxY + 1) / 2;\n  const halfPx = Math.max(1, (maxY - minY + 1) / 2);\n  const rMax = Math.hypot(maxX - minX, maxY - minY) / 2 + 6;\n  const contour: Pt[] = [];\n  for (let k = 0; k < N; k++) {\n    const a = (k / N) * Math.PI * 2;\n    const ca = Math.cos(a);\n    const sa = Math.sin(a);\n    let edge = 0;\n    let prev = 0;\n    for (let r = rMax; r > 0; r -= 0.5) {\n      const v = alpha(cxPx + ca * r, cyPx + sa * r);\n      if (v >= 0.5) {\n        edge = r + (0.5 * (v - 0.5)) / Math.max(1e-6, v - prev);\n        break;\n      }\n      prev = v;\n    }\n    contour.push([(ca * edge) / halfPx, (sa * edge) / halfPx]);\n  }\n\n  // Counter: march outward from the centre until the ray meets ink.\n  let counter: Pt[] | null = null;\n  if (alpha(cxPx, cyPx) < 0.5) {\n    counter = [];\n    for (let k = 0; k < N; k++) {\n      const a = (k / N) * Math.PI * 2;\n      const ca = Math.cos(a);\n      const sa = Math.sin(a);\n      let edge = 0;\n      let prev = alpha(cxPx, cyPx);\n      for (let r = 0.5; r < rMax; r += 0.5) {\n        const v = alpha(cxPx + ca * r, cyPx + sa * r);\n        if (v >= 0.5) {\n          edge = r - (0.5 * (v - 0.5)) / Math.max(1e-6, v - prev);\n          break;\n        }\n        prev = v;\n      }\n      counter.push([(ca * edge) / halfPx, (sa * edge) / halfPx]);\n    }\n  }\n\n  return {\n    pen,\n    adv,\n    width: pen[chars.length - 1] + adv[chars.length - 1],\n    ascent: Math.max(m.actualBoundingBoxAscent / PROBE, withMark ? MARK_EM : 0),\n    descent: m.actualBoundingBoxDescent / PROBE,\n    slot,\n    cx: (cxPx - padX) / PROBE,\n    cy: (cyPx - base) / PROBE,\n    halfH: halfPx / PROBE,\n    contour,\n    counter,\n  };\n}\n\nfunction useMetrics(chars: string[], withMark: boolean) {\n  const [metrics, setMetrics] = useState<Metrics | null>(null);\n  const [handle] = useState(() => delayRender(\"shape-morph-o: measuring\"));\n  const released = useRef(false);\n  useEffect(() => {\n    let live = true;\n    Promise.all([\n      display.waitUntilDone(),\n      document.fonts.load(`${WEIGHT} 100px \"${display.fontFamily}\"`),\n    ])\n      .catch(() => undefined)\n      .then(() => {\n        if (!live) return;\n        setMetrics(measure(chars, withMark));\n        if (!released.current) {\n          released.current = true;\n          continueRender(handle);\n        }\n      });\n    return () => {\n      live = false;\n    };\n  }, [chars, withMark, handle]);\n  return metrics;\n}\n\n/**\n * gradients.sh \"mesh\" palette laid inside the shape: a light-to-deep wash\n * that stays upright while the shape spins, plus one soft blob that drifts.\n * The accent is the middle stop.\n */\nconst MESH = {\n  dark: { top: \"#C9CCF6\", blob: \"#ECDCF2\", blobAlpha: 0.7, deep: \"#1D2A8F\" },\n  light: { top: \"#8796EE\", blob: \"#ECDCF2\", blobAlpha: 0.45, deep: \"#1D2A8F\" },\n} as const;\n\nexport function ShapeMorphO({\n  word = \"remocn\",\n  mark = true,\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: ShapeMorphOProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H, fps } = useVideoConfig();\n  const c = THEMES[theme];\n  const uid = useId().replace(/[^a-zA-Z0-9_-]/g, \"\");\n  const chars = useMemo(() => Array.from(word), [word]);\n  const withMark = mark && chars.length > 1 && /^r$/i.test(chars[0]);\n  const m = useMetrics(chars, withMark);\n  const shapes = useMemo(() => buildShapes().map(toPath), []);\n  const oPath = useMemo(() => (m ? toPath(m.contour) : \"\"), [m]);\n\n  if (!m) {\n    return <div style={{ position: \"absolute\", inset: 0, background: c.bg }} />;\n  }\n\n  // Word layout.\n  const F = Math.min((W * 0.66) / m.width, H * 0.36);\n  const x0 = (W - m.width * F) / 2;\n  const baseline = H / 2 + ((m.ascent - m.descent) / 2) * F;\n  const oX = x0 + (m.pen[m.slot] + m.cx) * F;\n  const oY = baseline + m.cy * F;\n  const oHalf = m.halfH * F;\n\n  // Shape chain: each morph starts from wherever the previous one was.\n  const prog = (k: number) =>\n    spring({\n      frame: t - (FIRST + (k - 1) * GAP),\n      fps,\n      config: SPRING_MORPH,\n    });\n  let d = shapes[0];\n  let rot = 0;\n  for (let k = 1; k <= MORPHS; k++) {\n    const start = FIRST + (k - 1) * GAP;\n    const p = prog(k);\n    rot += (ROT[k] - ROT[k - 1]) * p;\n    if (t >= start) d = interpolatePath(p, d, shapes[k]);\n  }\n  const land = spring({ frame: t - LAND, fps, config: SPRING_LAND });\n  if (t >= LAND) d = interpolatePath(land, d, oPath);\n\n  // The counter eases open from the centre, turning the solid bowl into the letter.\n  const open = interpolate(t, [COUNTER, COUNTER + COUNTER_LEN], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: Easing.bezier(0.22, 1, 0.36, 1),\n  });\n  if (m.counter && open > 0) {\n    d += toPath(m.counter.map(([x, y]): Pt => [x * open, y * open]));\n  }\n\n  const R0 = Math.min(W, H) * 0.28;\n  const enter = interpolate(t, [0, ENTER], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: (x) => 1 - (1 - x) ** 3,\n  });\n  const scale = interpolate(land, [0, 1], [R0, oHalf]) * (0.8 + 0.2 * enter);\n  const sx = interpolate(land, [0, 1], [W / 2, oX]);\n  const sy = interpolate(land, [0, 1], [H / 2, oY]);\n\n  // Word halves slide out from behind the landed shape.\n  const reveal = spring({ frame: t - REVEAL, fps, config: SPRING_REVEAL });\n  const left = chars.slice(0, m.slot);\n  const right = chars.slice(m.slot + 1);\n  const leftSpan = m.slot > 0 ? oX - (x0 + m.pen[0] * F) + 4 : 0;\n  const rightEnd =\n    right.length > 0\n      ? x0 + (m.pen[chars.length - 1] + m.adv[chars.length - 1]) * F\n      : oX;\n  const rightSpan = rightEnd - oX + 4;\n  const dxL = (1 - reveal) * leftSpan;\n  const dxR = -(1 - reveal) * rightSpan;\n\n  const glyph = (ch: string, i: number, dx: number) => {\n    if (i === 0 && withMark) {\n      const k = (MARK_EM * F) / MARK_H;\n      return (\n        <path\n          key=\"mark\"\n          d={REMOCN_R_PATH}\n          fill={c.fg}\n          transform={`translate(${x0 + m.pen[0] * F + dx} ${baseline - MARK_EM * F}) scale(${k})`}\n        />\n      );\n    }\n    return (\n      <text\n        key={`${i}-${ch}`}\n        x={x0 + m.pen[i] * F + dx}\n        y={baseline}\n        fill={c.fg}\n        style={{\n          fontFamily: display.fontFamily,\n          fontWeight: WEIGHT,\n          fontSize: F,\n          whiteSpace: \"pre\",\n        }}\n      >\n        {ch}\n      </text>\n    );\n  };\n\n  // The mesh drifts a few percent over the shot, never enough to read as motion.\n  const mesh = MESH[theme];\n  const blobX = sx + (0.62 + Math.sin(t / 40) * 0.12) * scale;\n  const blobY = sy + (-0.7 + Math.cos(t / 52) * 0.08) * scale;\n  const tilt = Math.sin(t / 60) * 0.12;\n\n  return (\n    <AbsoluteFill style={{ background: c.bg }}>\n      <svg\n        width={W}\n        height={H}\n        viewBox={`0 0 ${W} ${H}`}\n        style={{ position: \"absolute\", inset: 0 }}\n        aria-hidden\n      >\n        <defs>\n          <clipPath id={`l${uid}`}>\n            <rect x={0} y={0} width={oX} height={H} />\n          </clipPath>\n          <clipPath id={`r${uid}`}>\n            <rect x={oX} y={0} width={W - oX} height={H} />\n          </clipPath>\n          <clipPath id={`s${uid}`}>\n            <path\n              d={d}\n              clipRule=\"evenodd\"\n              transform={`translate(${sx} ${sy}) rotate(${rot}) scale(${scale})`}\n            />\n          </clipPath>\n          <linearGradient\n            id={`w${uid}`}\n            gradientUnits=\"userSpaceOnUse\"\n            x1={sx - (0.55 + tilt) * scale}\n            y1={sy - scale}\n            x2={sx + (0.55 + tilt) * scale}\n            y2={sy + scale}\n          >\n            <stop offset=\"0\" stopColor={mesh.top} />\n            <stop\n              offset=\"0.22\"\n              stopColor={interpolateColors(0.62, [0, 1], [mesh.top, accent])}\n            />\n            <stop offset=\"0.5\" stopColor={accent} />\n            <stop\n              offset=\"0.78\"\n              stopColor={interpolateColors(0.55, [0, 1], [accent, mesh.deep])}\n            />\n            <stop offset=\"1\" stopColor={mesh.deep} />\n          </linearGradient>\n          <radialGradient id={`b${uid}`}>\n            <stop\n              offset=\"0\"\n              stopColor={mesh.blob}\n              stopOpacity={mesh.blobAlpha}\n            />\n            <stop offset=\"1\" stopColor={mesh.blob} stopOpacity={0} />\n          </radialGradient>\n        </defs>\n        {reveal > 0 && (\n          <>\n            <g clipPath={`url(#l${uid})`}>\n              {left.map((ch, i) => glyph(ch, i, dxL))}\n            </g>\n            <g clipPath={`url(#r${uid})`}>\n              {right.map((ch, i) => glyph(ch, m.slot + 1 + i, dxR))}\n            </g>\n          </>\n        )}\n        <g clipPath={`url(#s${uid})`}>\n          <rect\n            x={sx - scale * 1.6}\n            y={sy - scale * 1.6}\n            width={scale * 3.2}\n            height={scale * 3.2}\n            fill={`url(#w${uid})`}\n          />\n          <circle\n            cx={blobX}\n            cy={blobY}\n            r={scale * 0.95}\n            fill={`url(#b${uid})`}\n          />\n        </g>\n      </svg>\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/shape-morph-o.tsx"
    }
  ],
  "type": "registry:component"
}
