{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dolly-zoom",
  "title": "Dolly Zoom",
  "description": "The vertigo shot: a perspective corridor with a red door stretches deeper while the subject in the center holds its exact size.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/dolly-zoom/index.tsx",
      "content": "\"use client\";\n\nimport type React from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nexport type DollyZoomColors = {\n  background: string;\n  wall: string;\n  wallAlt: string;\n  floor: string;\n  floorAlt: string;\n  line: string;\n  light: string;\n  door: string;\n  figure: string;\n};\n\nexport type DollyZoomProps = {\n  /** Subject held at a constant size. Defaults to a standing figure. */\n  children?: React.ReactNode;\n  /** How far the camera dollies in while zooming out, 0 to 2. */\n  intensity?: number;\n  /** Play the move in reverse: background compresses toward the subject. */\n  reverse?: boolean;\n  colors?: Partial<DollyZoomColors>;\n  /** Door color at the end of the corridor. Overrides `colors.door`. */\n  doorColor?: string;\n  /** Default figure color. Overrides `colors.figure`. */\n  figureColor?: string;\n  /** Corridor edge line color. Overrides `colors.line`. */\n  lineColor?: string;\n  speed?: number;\n};\n\nexport const dollyZoomDefaults = {\n  intensity: 1,\n  reverse: false,\n  colors: {\n    background: \"#0B0B0B\",\n    wall: \"#1B1B1B\",\n    wallAlt: \"#151515\",\n    floor: \"#262626\",\n    floorAlt: \"#1D1D1D\",\n    line: \"#FFFFFF\",\n    light: \"#FFFFFF\",\n    door: \"#E5322D\",\n    figure: \"#FFFFFF\",\n  } satisfies DollyZoomColors,\n  speed: 1,\n};\n\nconst START = 4;\nconst END = 36;\n// Corridor in world units. Screen y points down, so the floor is positive.\nconst HALF_W = 2;\nconst CEIL = -1.3;\nconst FLOOR = 1.7;\nconst SUBJECT_Z = 6;\nconst DOOR_Z = 14;\nconst NEAR = 0.25;\n\ntype Vec = [number, number, number];\n\nexport function DollyZoom({\n  children,\n  intensity = dollyZoomDefaults.intensity,\n  reverse = dollyZoomDefaults.reverse,\n  colors: colorOverrides,\n  doorColor,\n  figureColor,\n  lineColor,\n  speed = dollyZoomDefaults.speed,\n}: DollyZoomProps) {\n  const frame = useCurrentFrame();\n  const { width, height, fps } = useVideoConfig();\n  const baseColors = { ...dollyZoomDefaults.colors, ...colorOverrides };\n  const colors = {\n    ...baseColors,\n    door: doorColor ?? baseColors.door,\n    figure: figureColor ?? baseColors.figure,\n    line: lineColor ?? baseColors.line,\n  };\n  const t = (frame * speed * 30) / fps;\n  const move = interpolate(t, [START, END], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: Easing.bezier(0.45, 0, 0.3, 1),\n  });\n  const amount = reverse ? 1 - move : move;\n\n  // Focal length tracks the subject distance so the subject never changes size.\n  const k = height * 0.19;\n  const d0 = SUBJECT_Z;\n  const d1 = d0 / (1 + 1.7 * Math.max(0, intensity));\n  const dist = d0 + (d1 - d0) * amount;\n  const cam = SUBJECT_Z - dist;\n  const f = k * dist;\n  const cx = width / 2;\n  const cy = height / 2;\n\n  const project = ([x, y, z]: Vec) => {\n    const depth = Math.max(NEAR, z - cam);\n    return [cx + (f * x) / depth, cy + (f * y) / depth] as const;\n  };\n  const poly = (pts: Vec[]) =>\n    pts\n      .map(project)\n      .map(([x, y]) => `${x.toFixed(1)},${y.toFixed(1)}`)\n      .join(\" \");\n\n  const fog = (z: number) =>\n    interpolate(z - cam, [1, DOOR_Z - cam], [1, 0.25], {\n      extrapolateLeft: \"clamp\",\n      extrapolateRight: \"clamp\",\n    });\n\n  const segments: React.ReactNode[] = [];\n  // The corridor runs on behind the camera so its mouth never shows.\n  const firstZ = Math.floor(cam);\n  for (let z = DOOR_Z - 1; z >= firstZ; z--) {\n    const z0 = Math.max(z, cam + NEAR);\n    const z1 = z + 1;\n    if (z1 <= z0) continue;\n    const alt = z % 2 === 0;\n    const L = -HALF_W;\n    const R = HALF_W;\n    segments.push(\n      <g key={z}>\n        <polygon\n          points={poly([\n            [L, FLOOR, z0],\n            [R, FLOOR, z0],\n            [R, FLOOR, z1],\n            [L, FLOOR, z1],\n          ])}\n          fill={alt ? colors.floor : colors.floorAlt}\n        />\n        <polygon\n          points={poly([\n            [L, CEIL, z0],\n            [R, CEIL, z0],\n            [R, CEIL, z1],\n            [L, CEIL, z1],\n          ])}\n          fill={colors.wallAlt}\n        />\n        <polygon\n          points={poly([\n            [L, CEIL, z0],\n            [L, FLOOR, z0],\n            [L, FLOOR, z1],\n            [L, CEIL, z1],\n          ])}\n          fill={alt ? colors.wall : colors.wallAlt}\n        />\n        <polygon\n          points={poly([\n            [R, CEIL, z0],\n            [R, FLOOR, z0],\n            [R, FLOOR, z1],\n            [R, CEIL, z1],\n          ])}\n          fill={alt ? colors.wall : colors.wallAlt}\n        />\n        <polygon\n          points={poly([\n            [-0.35, CEIL, z + 0.3],\n            [0.35, CEIL, z + 0.3],\n            [0.35, CEIL, z + 0.7],\n            [-0.35, CEIL, z + 0.7],\n          ])}\n          fill={colors.light}\n          opacity={0.75 * fog(z)}\n        />\n        {z >= cam + NEAR ? (\n          <polygon\n            points={poly([\n              [L, CEIL, z],\n              [R, CEIL, z],\n              [R, FLOOR, z],\n              [L, FLOOR, z],\n            ])}\n            fill=\"none\"\n            stroke={colors.line}\n            strokeOpacity={0.32 * fog(z)}\n            strokeWidth={Math.max(1, (f / (z - cam)) * 0.012)}\n          />\n        ) : null}\n      </g>,\n    );\n  }\n\n  const edges: Vec[][] = [\n    [\n      [-HALF_W, CEIL, -4],\n      [-HALF_W, CEIL, DOOR_Z],\n    ],\n    [\n      [HALF_W, CEIL, -4],\n      [HALF_W, CEIL, DOOR_Z],\n    ],\n    [\n      [-HALF_W, FLOOR, -4],\n      [-HALF_W, FLOOR, DOOR_Z],\n    ],\n    [\n      [HALF_W, FLOOR, -4],\n      [HALF_W, FLOOR, DOOR_Z],\n    ],\n  ];\n\n  const doorW = 0.85;\n  const doorTop = FLOOR - 2.3;\n  const [, floorY] = project([0, FLOOR, SUBJECT_Z]);\n  const figureH = k * 1.75;\n\n  return (\n    <AbsoluteFill\n      style={{ backgroundColor: colors.background, overflow: \"hidden\" }}\n    >\n      <svg\n        width={width}\n        height={height}\n        viewBox={`0 0 ${width} ${height}`}\n        style={{ position: \"absolute\", inset: 0 }}\n        aria-hidden\n      >\n        <polygon\n          points={poly([\n            [-HALF_W, CEIL, DOOR_Z],\n            [HALF_W, CEIL, DOOR_Z],\n            [HALF_W, FLOOR, DOOR_Z],\n            [-HALF_W, FLOOR, DOOR_Z],\n          ])}\n          fill={colors.wallAlt}\n        />\n        <polygon\n          points={poly([\n            [-doorW, doorTop, DOOR_Z],\n            [doorW, doorTop, DOOR_Z],\n            [doorW, FLOOR, DOOR_Z],\n            [-doorW, FLOOR, DOOR_Z],\n          ])}\n          fill={colors.door}\n        />\n        {segments}\n        {edges.map((edge, i) => {\n          const [a, b] = edge.map((p) =>\n            project([p[0], p[1], Math.max(p[2], cam + NEAR)]),\n          );\n          return (\n            <line\n              key={i}\n              x1={a[0]}\n              y1={a[1]}\n              x2={b[0]}\n              y2={b[1]}\n              stroke={colors.line}\n              strokeOpacity={0.45}\n              strokeWidth={1.5}\n            />\n          );\n        })}\n      </svg>\n      <AbsoluteFill\n        style={{\n          background:\n            \"radial-gradient(ellipse at center, rgba(0,0,0,0) 45%, rgba(0,0,0,0.55) 100%)\",\n        }}\n      />\n      {children ? (\n        <AbsoluteFill\n          style={{ alignItems: \"center\", justifyContent: \"center\" }}\n        >\n          {children}\n        </AbsoluteFill>\n      ) : (\n        <svg\n          viewBox=\"0 0 60 175\"\n          height={figureH}\n          style={{\n            position: \"absolute\",\n            left: cx - (figureH * 60) / 175 / 2,\n            top: floorY - figureH,\n          }}\n          aria-hidden\n        >\n          <g fill={colors.figure}>\n            <circle cx={30} cy={14} r={12} />\n            <path d=\"M12 34 Q30 26 48 34 L54 96 Q54 102 48 102 L44 102 L42 172 L33 172 L30 108 L27 172 L18 172 L16 102 L12 102 Q6 102 6 96 Z\" />\n          </g>\n        </svg>\n      )}\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/dolly-zoom.tsx"
    }
  ],
  "type": "registry:component"
}
