{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "parallax-depth",
  "title": "Parallax Depth",
  "description": "A flat landscape built from depth layers trucks sideways with a gentle push-in, near layers sliding faster than far ones.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/parallax-depth/index.tsx",
      "content": "\"use client\";\n\nimport type React from \"react\";\nimport { useMemo } from \"react\";\nimport {\n  AbsoluteFill,\n  Easing,\n  interpolate,\n  random,\n  useCurrentFrame,\n  useVideoConfig,\n} from \"remotion\";\n\nexport type ParallaxDepthLayer = {\n  /** Full-frame content of the layer. Leave room past the edges for the truck. */\n  content: React.ReactNode;\n  /** Distance from the camera. 1 is nearest; larger values move less. */\n  depth: number;\n};\n\nexport type ParallaxDepthColors = {\n  sky: string;\n  sun: string;\n  far: string;\n  mid: string;\n  near: string;\n};\n\nexport type ParallaxDepthProps = {\n  /** Custom layers. Falls back to the built-in sun and hills landscape. */\n  layers?: ParallaxDepthLayer[];\n  /** Sideways camera travel at depth 1, as a fraction of the frame width. */\n  distance?: number;\n  /** Push-in toward the scene, in depth units. */\n  push?: number;\n  /** Truck direction: 1 moves the camera right, -1 left. */\n  direction?: 1 | -1;\n  colors?: Partial<ParallaxDepthColors>;\n  /** Sky color. Overrides `colors.sky`. */\n  skyColor?: string;\n  /** Sun color. Overrides `colors.sun`. */\n  sunColor?: string;\n  seed?: number;\n  speed?: number;\n};\n\nexport const parallaxDepthDefaults = {\n  distance: 0.5,\n  push: 0.16,\n  direction: 1 as 1 | -1,\n  colors: {\n    sky: \"#111111\",\n    sun: \"#E5322D\",\n    far: \"#2C2C2C\",\n    mid: \"#555555\",\n    near: \"#8E8E8E\",\n  } satisfies ParallaxDepthColors,\n  seed: 3,\n  speed: 1,\n};\n\nconst DURATION = 60;\n\n// A ridge line built from a few seeded sine octaves, spanning three widths so it\n// never runs out while the camera trucks.\nfunction hillPath(\n  width: number,\n  height: number,\n  base: number,\n  amp: number,\n  waves: number,\n  seed: string,\n) {\n  const octaves = [0, 1, 2].map((o) => ({\n    f: (waves * (o + 1) * (0.8 + random(`${seed}-f${o}`) * 0.4)) / width,\n    a: amp / (o * 1.7 + 1),\n    p: random(`${seed}-p${o}`) * Math.PI * 2,\n  }));\n  const steps = 120;\n  const x0 = -width;\n  const x1 = width * 2;\n  let d = `M ${x0} ${height * 2}`;\n  for (let i = 0; i <= steps; i++) {\n    const x = x0 + ((x1 - x0) * i) / steps;\n    let y = base;\n    for (const o of octaves)\n      y -= o.a * (0.5 + 0.5 * Math.sin(x * o.f * 6.283 + o.p));\n    d += ` L ${x.toFixed(1)} ${y.toFixed(1)}`;\n  }\n  return `${d} L ${x1} ${height * 2} Z`;\n}\n\nfunction useLandscape(colors: ParallaxDepthColors, seed: number) {\n  const { width, height } = useVideoConfig();\n  return useMemo<ParallaxDepthLayer[]>(() => {\n    const svg = (children: React.ReactNode) => (\n      <svg\n        width={width}\n        height={height}\n        viewBox={`0 0 ${width} ${height}`}\n        style={{ overflow: \"visible\" }}\n        aria-hidden\n      >\n        {children}\n      </svg>\n    );\n    const hill = (\n      base: number,\n      amp: number,\n      waves: number,\n      fill: string,\n      key: string,\n    ) => (\n      <path\n        d={hillPath(\n          width,\n          height,\n          height * base,\n          height * amp,\n          waves,\n          `${seed}-${key}`,\n        )}\n        fill={fill}\n      />\n    );\n    return [\n      {\n        depth: 9,\n        content: svg(\n          <circle\n            cx={width * 0.6}\n            cy={height * 0.4}\n            r={height * 0.17}\n            fill={colors.sun}\n          />,\n        ),\n      },\n      { depth: 6, content: svg(hill(0.6, 0.24, 2.2, colors.far, \"far\")) },\n      { depth: 2.6, content: svg(hill(0.76, 0.16, 1.2, colors.mid, \"mid\")) },\n      { depth: 1.2, content: svg(hill(0.92, 0.14, 0.9, colors.near, \"near\")) },\n    ];\n  }, [width, height, colors.sun, colors.far, colors.mid, colors.near, seed]);\n}\n\nexport function ParallaxDepth({\n  layers,\n  distance = parallaxDepthDefaults.distance,\n  push = parallaxDepthDefaults.push,\n  direction = parallaxDepthDefaults.direction,\n  colors: colorOverrides,\n  skyColor,\n  sunColor,\n  seed = parallaxDepthDefaults.seed,\n  speed = parallaxDepthDefaults.speed,\n}: ParallaxDepthProps) {\n  const frame = useCurrentFrame();\n  const { width, fps } = useVideoConfig();\n  const baseColors = { ...parallaxDepthDefaults.colors, ...colorOverrides };\n  const colors = {\n    ...baseColors,\n    sky: skyColor ?? baseColors.sky,\n    sun: sunColor ?? baseColors.sun,\n  };\n  const landscape = useLandscape(colors, seed);\n  const stack = [...(layers ?? landscape)].sort((a, b) => b.depth - a.depth);\n\n  const t = (frame * speed * 30) / fps;\n  const move = interpolate(t, [0, DURATION], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: Easing.bezier(0.45, 0, 0.2, 1),\n  });\n  const camX = (move - 0.5) * distance * width * direction;\n  const camZ = move * push;\n\n  return (\n    <AbsoluteFill style={{ backgroundColor: colors.sky, overflow: \"hidden\" }}>\n      {stack.map((layer, i) => {\n        const depth = Math.max(0.2, layer.depth);\n        // Pinhole camera: scale and offset both fall off with 1 / depth.\n        const scale = depth / Math.max(0.05, depth - camZ);\n        const x = (-camX * scale) / depth;\n        return (\n          <AbsoluteFill\n            key={i}\n            style={{\n              transform: `translateX(${x}px) scale(${scale})`,\n              transformOrigin: \"50% 55%\",\n            }}\n          >\n            {layer.content}\n          </AbsoluteFill>\n        );\n      })}\n    </AbsoluteFill>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/parallax-depth.tsx"
    }
  ],
  "type": "registry:component"
}
