{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ring-cut",
  "title": "Ring Cut",
  "description": "A circular cut expands from a chosen origin to swap one scene for the next.",
  "dependencies": ["remotion", "@remotion/google-fonts"],
  "files": [
    {
      "path": "registry/remocn/ring-cut/index.tsx",
      "content": "\"use client\";\n\nimport { loadFont as loadManrope } from \"@remotion/google-fonts/Manrope\";\nimport type React from \"react\";\nimport { useId } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nconst manrope = loadManrope(\"normal\", {\n  weights: [\"500\", \"600\"],\n  subsets: [\"latin\"],\n});\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\n/** Full logo as on remocn.dev: R mark (0.76em tall, baseline-aligned) + \"emocn\" in Manrope 600, tracking-tight. */\nfunction RemocnLogo({ fontSize, color }: { fontSize: number; color: string }) {\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        alignItems: \"baseline\",\n        color,\n        fontFamily: manrope.fontFamily,\n        fontWeight: 600,\n        fontSize,\n        letterSpacing: \"-0.025em\",\n        lineHeight: 1,\n      }}\n    >\n      <svg\n        viewBox=\"0 0 124.06 134.26\"\n        style={{ height: \"0.76em\", width: \"auto\" }}\n        aria-hidden\n      >\n        <path fill=\"currentColor\" d={REMOCN_R_PATH} />\n      </svg>\n      <span style={{ marginLeft: \"0.04em\" }}>emocn</span>\n    </div>\n  );\n}\n\nexport interface RingCutProps {\n  from?: string;\n  to?: string;\n  origin?: { x: number; y: number };\n  accent?: string;\n  theme?: \"dark\" | \"light\";\n  speed?: number;\n}\n\nconst CLAMP = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\ntype RingKind = \"a\" | \"b\" | \"line\" | \"final\";\n\nconst START = 6;\nconst DUR = 24;\nconst RINGS: { kind: RingKind; at: number }[] = [\n  { kind: \"b\", at: 0 },\n  { kind: \"line\", at: 3 },\n  { kind: \"a\", at: 6 },\n  { kind: \"b\", at: 9 },\n  { kind: \"line\", at: 12 },\n  { kind: \"a\", at: 15 },\n  { kind: \"final\", at: 19 },\n];\n\nconst EXPAND = Easing.bezier(0.3, 0, 0.2, 1);\nconst SETTLE = Easing.bezier(0.22, 1, 0.36, 1);\n\n/** Final ground: gradients.sh \"mesh\" (dark) and \"pixel-glass\" (light). */\nconst STAGES = {\n  dark: {\n    a: \"#0A0A0B\",\n    aFg: \"#F3F2EF\",\n    b: \"#F5F4F0\",\n    aLight: \"rgba(255,255,255,0.06)\",\n    end: \"#0a0d24\",\n    endFg: \"#F6F4FB\",\n    blobs: [\n      [\"#ecdcf2\", 88, 8, 0.3],\n      [\"#8796ee\", 84, 30, 0.5],\n      [\"#2e45b7\", 58, 74, 1],\n      [\"#0e1645\", 12, 18, 1],\n    ] as [string, number, number, number][],\n  },\n  light: {\n    a: \"#F5F4F0\",\n    aFg: \"#121214\",\n    b: \"#0A0A0B\",\n    aLight: \"rgba(255,255,255,0.9)\",\n    end: \"#f4f3f1\",\n    endFg: \"#121214\",\n    blobs: [\n      [\"#ff4fb8\", 84, 22, 0.5],\n      [\"#3d6bff\", 74, 90, 0.6],\n      [\"#7a3cff\", 16, 18, 0.62],\n      [\"#ffffff\", 50, 50, 0.85],\n    ] as [string, number, number, number][],\n  },\n};\n\n/** Manrope 500 advances, ASCII 32–126, per 1000 em. */\nconst ADV = [\n  200, 329, 417, 924, 602, 901, 657, 229, 435, 435, 446, 575, 278, 420, 270,\n  402, 626, 406, 578, 562, 596, 580, 621, 512, 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, 640, 630, 600, 708, 616, 949, 622, 574, 630, 412,\n  402, 412, 673, 660, 506, 562, 600, 561, 600, 590, 362, 600, 604, 249, 263,\n  510, 249, 865, 604, 600, 600, 600, 374, 530, 412, 604, 523, 779, 536, 538,\n  531, 430, 269, 430, 680,\n];\nconst TRACK = -0.03;\n\nfunction widthEm(text: string) {\n  let w = 0;\n  for (const ch of text) {\n    const c = ch.charCodeAt(0);\n    w += (c >= 32 && c < 127 ? ADV[c - 32] : 600) / 1000 + TRACK;\n  }\n  return Math.max(w - TRACK, 0.5);\n}\n\n/** Logo width in em: R mark + gap + \"emocn\" (Manrope 600, -0.025em). */\nconst LOGO_EM = (0.76 * 124.06) / 134.26 + 0.04 + 3.26 - 0.125;\n\nfunction meshGround(stage: (typeof STAGES)[\"dark\"], k: number) {\n  const drift = [\n    [5, 3],\n    [-4, 4],\n    [-5, -3],\n    [4, -4],\n  ];\n  return [\n    ...stage.blobs.map(\n      ([c, x, y, a], i) =>\n        `radial-gradient(85% 95% at ${x + drift[i][0] * k}% ${y + drift[i][1] * k}%, color-mix(in oklab, ${c} ${Math.round(a * 100)}%, transparent) 0%, transparent 68%)`,\n    ),\n    stage.end,\n  ].join(\", \");\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={17}\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\nfunction Title({\n  text,\n  size,\n  color,\n  scale,\n  lift,\n  logo,\n}: {\n  text: string;\n  size: number;\n  color: string;\n  scale: number;\n  lift: number;\n  logo?: boolean;\n}) {\n  const style: React.CSSProperties = {\n    fontFamily: manrope.fontFamily,\n    fontWeight: 500,\n    fontSize: size,\n    letterSpacing: `${TRACK}em`,\n    lineHeight: 1,\n    whiteSpace: \"nowrap\",\n    color,\n    paddingBottom: 0.06 * size,\n    transform: `translateY(${lift}px) scale(${scale})`,\n  };\n  return (\n    <AbsoluteFill style={{ alignItems: \"center\", justifyContent: \"center\" }}>\n      <div style={style}>\n        {logo ? <RemocnLogo fontSize={size} color={color} /> : text}\n      </div>\n    </AbsoluteFill>\n  );\n}\n\nexport function RingCut({\n  from = \"One idea\",\n  to = \"remocn\",\n  origin = { x: 0.2, y: 0.74 },\n  accent = \"#3B5BFD\",\n  theme = \"dark\",\n  speed = 1,\n}: RingCutProps) {\n  const t = useCurrentFrame() * speed;\n  const { width: W, height: H } = useVideoConfig();\n  const u = H / 720;\n  const stage = STAGES[theme];\n\n  const cx = origin.x * W;\n  const cy = origin.y * H;\n  const reach = Math.max(\n    Math.hypot(cx, cy),\n    Math.hypot(W - cx, cy),\n    Math.hypot(cx, H - cy),\n    Math.hypot(W - cx, H - cy),\n  );\n  const rMax = 1.18 * reach;\n  const line = Math.max(3, 4.5 * u);\n  const radius = (at: number) =>\n    rMax *\n    EXPAND(interpolate(t, [START + at, START + at + DUR], [0, 1], CLAMP));\n\n  const isLogo = to.trim().toLowerCase() === \"remocn\";\n  const fsA = Math.min(150 * u, (0.8 * W) / widthEm(from));\n  const fsB = Math.min(\n    (isLogo ? 172 : 160) * u,\n    (0.66 * W) / (isLogo ? LOGO_EM : widthEm(to)),\n  );\n  const finalAt = RINGS[RINGS.length - 1].at;\n  const finalR = radius(finalAt);\n  const titleDist = Math.hypot(W / 2 - cx, H / 2 - cy);\n  let tReach = START + finalAt;\n  for (let f = START + finalAt; f <= START + finalAt + DUR; f += 0.25) {\n    const r =\n      rMax *\n      EXPAND(interpolate(f, [START + finalAt, START + finalAt + DUR], [0, 1]));\n    if (r >= titleDist) {\n      tReach = f;\n      break;\n    }\n  }\n  const settle = SETTLE(\n    interpolate(t, [tReach - 8, tReach + 26], [0, 1], CLAMP),\n  );\n  const scaleB = 1.07 - 0.07 * settle;\n  const liftB = 14 * u * (1 - settle);\n  const driftA =\n    1 + 0.03 * interpolate(t, [0, START + finalAt + 10], [0, 1], CLAMP);\n  const covered = finalR >= reach + line;\n  const drift = interpolate(t, [0, 96], [0, 1], CLAMP);\n\n  const light = (c: string) =>\n    `radial-gradient(ellipse 80% 90% at 24% 0%, ${c}, transparent 70%)`;\n  const clip = (r: number) => `circle(${r}px at ${cx}px ${cy}px)`;\n\n  return (\n    <AbsoluteFill style={{ background: stage.a, overflow: \"hidden\" }}>\n      {covered ? null : (\n        <>\n          <AbsoluteFill style={{ background: light(stage.aLight) }} />\n          <Title\n            text={from}\n            size={fsA}\n            color={stage.aFg}\n            scale={driftA}\n            lift={0}\n          />\n        </>\n      )}\n      {RINGS.map((ring) => {\n        const r = radius(ring.at);\n        if (r <= 0) return null;\n        if (ring.kind === \"final\") {\n          return (\n            <AbsoluteFill\n              key={ring.at}\n              style={{\n                background: meshGround(stage, drift),\n                clipPath: covered ? undefined : clip(r),\n              }}\n            >\n              <Title\n                text={to}\n                size={fsB}\n                color={stage.endFg}\n                scale={scaleB}\n                lift={liftB}\n                logo={isLogo}\n              />\n            </AbsoluteFill>\n          );\n        }\n        if (covered) return null;\n        if (ring.kind === \"line\") {\n          return (\n            <svg\n              key={ring.at}\n              width={W}\n              height={H}\n              style={{ position: \"absolute\", inset: 0 }}\n              aria-hidden\n            >\n              <circle\n                cx={cx}\n                cy={cy}\n                r={r}\n                fill=\"none\"\n                stroke={accent}\n                strokeWidth={line}\n              />\n            </svg>\n          );\n        }\n        return (\n          <AbsoluteFill\n            key={ring.at}\n            style={{\n              background: ring.kind === \"a\" ? stage.a : stage.b,\n              clipPath: clip(r),\n            }}\n          />\n        );\n      })}\n      <Grain opacity={theme === \"light\" ? 0.04 : 0.055} />\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/ring-cut.tsx"
    }
  ],
  "type": "registry:component"
}
