{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "light-leak",
  "title": "Light Leak",
  "description": "A warm analog light leak drifts in from an edge and blows the frame out to near-white at the cut, then cools off to reveal the next scene.",
  "dependencies": ["remotion", "@remotion/transitions"],
  "files": [
    {
      "path": "registry/remocn/light-leak/index.tsx",
      "content": "\"use client\";\n\nimport type {\n  TransitionPresentation,\n  TransitionPresentationComponentProps,\n} from \"@remotion/transitions\";\nimport type React from \"react\";\nimport { AbsoluteFill, Easing, interpolate, useVideoConfig } from \"remotion\";\n\nexport type LightLeakOrigin = \"left\" | \"right\" | \"top\" | \"bottom\";\n\nexport type LightLeakProps = {\n  /** Leak colors, warmest first. The last one is the blow-out tint. */\n  colors?: string[];\n  /** Edge the leak drifts in from. */\n  origin?: LightLeakOrigin;\n  /** Strength of the leak and the blow-out, 0 to 1. */\n  intensity?: number;\n};\n\nexport const lightLeakDefaults: Required<LightLeakProps> = {\n  colors: [\"#FF5A1F\", \"#E5322D\", \"#FFB347\", \"#FFF3E0\"],\n  origin: \"left\",\n  intensity: 1,\n};\n\n// Each flare: offset along the drift axis, position across it, size, strength.\nconst FLARES = [\n  { along: 0, across: 0.3, size: 0.85, alpha: 1 },\n  { along: -0.18, across: 0.78, size: 0.7, alpha: 0.85 },\n  { along: 0.12, across: 0.55, size: 0.55, alpha: 0.9 },\n  { along: -0.32, across: 0.12, size: 0.6, alpha: 0.7 },\n  { along: 0.05, across: 0.95, size: 0.5, alpha: 0.6 },\n];\n// Heat of the leak: ramps into the cut, then cools off more slowly.\nfunction heat(p: number) {\n  if (p <= 0.5) return Easing.in(Easing.quad)(p / 0.5);\n  return 1 - Easing.out(Easing.quad)((p - 0.5) / 0.5);\n}\n\nfunction Leak({\n  p,\n  colors,\n  origin,\n  intensity,\n}: {\n  p: number;\n  colors: string[];\n  origin: LightLeakOrigin;\n  intensity: number;\n}) {\n  const { width, height } = useVideoConfig();\n  const k = heat(p) * intensity;\n  const span = Math.max(width, height);\n  const drift = interpolate(p, [0, 1], [-0.25, 1.05], {\n    easing: Easing.bezier(0.3, 0.1, 0.5, 0.9),\n  });\n  const palette = colors.length > 0 ? colors : lightLeakDefaults.colors;\n  const blowout = palette[palette.length - 1];\n  // A narrow peak right at the cut takes the frame to near-white.\n  const flash = Math.exp(-(((p - 0.5) / 0.1) ** 2)) * intensity;\n\n  return (\n    <AbsoluteFill style={{ pointerEvents: \"none\" }}>\n      {FLARES.map((f, i) => {\n        let along = drift + f.along;\n        if (origin === \"right\" || origin === \"bottom\") along = 1 - along;\n        const horizontal = origin === \"left\" || origin === \"right\";\n        const x = (horizontal ? along : f.across) * width;\n        const y = (horizontal ? f.across : along) * height;\n        const color = palette[i % Math.max(1, palette.length - 1)];\n        return (\n          <AbsoluteFill\n            key={f.across}\n            style={{\n              mixBlendMode: \"screen\",\n              // sqrt keeps the leak hot as it enters; drift, not fade, sells it.\n              opacity: Math.min(1, Math.sqrt(k) * f.alpha * 1.1),\n              background: `radial-gradient(circle ${span * f.size}px at ${x}px ${y}px, color-mix(in srgb, ${color} 55%, ${blowout}) 0%, ${color} 22%, color-mix(in srgb, ${color} 55%, transparent) 45%, transparent 72%)`,\n            }}\n          />\n        );\n      })}\n      <AbsoluteFill\n        style={{ backgroundColor: blowout, opacity: Math.min(0.96, flash) }}\n      />\n    </AbsoluteFill>\n  );\n}\n\nconst LightLeakPresentation: React.FC<\n  TransitionPresentationComponentProps<LightLeakProps>\n> = ({\n  children,\n  presentationProgress,\n  presentationDirection,\n  passedProps,\n}) => {\n  const { colors, origin, intensity } = {\n    ...lightLeakDefaults,\n    ...passedProps,\n  };\n  const p = presentationProgress;\n  const k = heat(p) * intensity;\n  // Hard cut under the blow-out: the incoming scene owns the frame from 0.5.\n  const visible = presentationDirection === \"exiting\" ? p < 0.5 : p >= 0.5;\n\n  return (\n    <AbsoluteFill style={{ opacity: visible ? 1 : 0 }}>\n      <AbsoluteFill\n        style={{\n          // Squared so the picture only burns out close to the cut.\n          filter: `brightness(${1 + k * k * 1.6}) saturate(${1 + k * 0.4})`,\n        }}\n      >\n        {children}\n      </AbsoluteFill>\n      {visible && k > 0.001 ? (\n        <Leak p={p} colors={colors} origin={origin} intensity={intensity} />\n      ) : null}\n    </AbsoluteFill>\n  );\n};\n\nexport function lightLeak(\n  props: LightLeakProps = {},\n): TransitionPresentation<LightLeakProps> {\n  return { component: LightLeakPresentation, props };\n}\n",
      "type": "registry:component",
      "target": "components/remocn/light-leak.tsx"
    }
  ],
  "type": "registry:component"
}
