{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "particle-dissolve",
  "title": "Particle Dissolve",
  "description": "The frame is ground into drifting grey dust whose specks carry real fragments of the picture, and the next scene coalesces back out of the same dust.",
  "dependencies": ["remotion", "@remotion/transitions"],
  "registryDependencies": ["@remocn/canvas-presentation"],
  "files": [
    {
      "path": "registry/remocn/particle-dissolve/index.tsx",
      "content": "\"use client\";\n\nimport type {\n  TransitionPresentation,\n  TransitionPresentationComponentProps,\n} from \"@remotion/transitions\";\nimport type React from \"react\";\nimport { AbsoluteFill } from \"remotion\";\nimport {\n  makeCanvasPresentation,\n  makeSceneShader,\n} from \"@/lib/remocn/canvas-presentation\";\n\nexport type ParticleDissolveDirection =\n  | \"reveal\"\n  | \"up\"\n  | \"down\"\n  | \"left\"\n  | \"right\";\n\nexport type ParticleDissolveProps = {\n  particleSize?: number;\n  scatter?: number;\n  shimmer?: number;\n  aberration?: number;\n  direction?: ParticleDissolveDirection;\n  stagger?: number;\n};\n\nconst DEFAULTS = {\n  particleSize: 4,\n  scatter: 0.08,\n  shimmer: 0.6,\n  aberration: 0.5,\n  direction: \"reveal\" as ParticleDissolveDirection,\n  stagger: 0.55,\n};\n\nconst AXES: Record<ParticleDissolveDirection, [number, number]> = {\n  reveal: [0, 0],\n  up: [0, -1],\n  down: [0, 1],\n  left: [-1, 0],\n  right: [1, 0],\n};\n\nconst FRAGMENT_SHADER = `#version 300 es\nprecision highp float;\n\nuniform sampler2D u_from;\nuniform sampler2D u_to;\nuniform float u_progress;\nuniform float u_aspect;\nuniform float u_cells;\nuniform float u_scatter;\nuniform float u_shimmer;\nuniform float u_aberration;\nuniform float u_stagger;\nuniform vec2 u_dir;\nuniform float u_radial;\n\nin vec2 v_uv;\nout vec4 outColor;\n\nconst float PI = 3.141592653589793;\n\nfloat hash(vec2 p) {\n  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);\n}\n\nfloat noise(vec2 p) {\n  vec2 i = floor(p);\n  vec2 f = fract(p);\n  vec2 u = f * f * (3.0 - 2.0 * f);\n  return mix(\n    mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x),\n    mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x),\n    u.y\n  );\n}\n\nfloat fbm(vec2 p) {\n  float sum = 0.0;\n  float amp = 0.5;\n  for (int i = 0; i < 4; i++) {\n    sum += noise(p) * amp;\n    p *= 2.03;\n    amp *= 0.5;\n  }\n  return sum * 1.0667;\n}\n\nfloat luma(vec3 c) {\n  return dot(c, vec3(0.2126, 0.7152, 0.0722));\n}\n\nvec4 grab(sampler2D tex, vec2 uv, vec2 dir, float amount) {\n  vec4 centre = texture(tex, uv);\n  float r = texture(tex, uv + dir * amount).r;\n  float b = texture(tex, uv - dir * amount).b;\n  return vec4(r, centre.g, b, centre.a);\n}\n\nvoid main() {\n  vec2 grid = vec2(u_cells * u_aspect, u_cells);\n  vec2 skewed = vec2(v_uv.x * u_aspect, v_uv.y);\n  vec2 centred = skewed - vec2(0.5 * u_aspect, 0.5);\n\n  float spatial = u_radial > 0.5\n    ? clamp(length(centred) / 0.62, 0.0, 1.0)\n    : clamp(dot(v_uv - 0.5, u_dir) + 0.5, 0.0, 1.0);\n  float blob = fbm(skewed * 2.2);\n  float order = mix(blob, spatial, 0.55) * u_stagger;\n  float span = max(1.0 - u_stagger, 0.08);\n  float phase = clamp((u_progress - order) / span, 0.0, 1.0);\n  float dust = sin(phase * PI);\n\n  vec2 flow = vec2(\n    fbm(skewed * 3.0 + vec2(0.0, u_progress * 0.8)),\n    fbm(skewed * 3.0 + vec2(5.3, 2.7 - u_progress * 0.7))\n  ) - 0.5;\n  vec2 aim =\n    flow * 2.0 + normalize(centred + 0.0001) * 0.7 * u_radial + u_dir;\n  vec2 heading = aim / max(length(aim), 0.0001);\n\n  float bright = luma(texture(u_from, v_uv).rgb);\n  float travel = u_scatter * dust * (0.18 + bright * 1.25);\n  vec2 drift = vec2(heading.x / u_aspect, heading.y) * travel;\n\n  float split = u_aberration * dust * 0.35;\n  vec4 leaving = grab(u_from, v_uv - drift, drift, split);\n  vec4 arriving = grab(u_to, v_uv + drift, -drift, split);\n\n  vec4 base = mix(leaving, arriving, smoothstep(0.25, 0.75, phase));\n  base.rgb = mix(base.rgb, vec3(luma(base.rgb)), dust);\n  base.rgb *= 1.0 - dust * 0.25;\n\n  vec2 g = (v_uv - drift) * grid;\n  vec2 origin = floor(g);\n  float cover = 0.0;\n  float light = 0.0;\n  for (int oy = -1; oy <= 1; oy++) {\n    for (int ox = -1; ox <= 1; ox++) {\n      vec2 cell = origin + vec2(float(ox), float(oy));\n      vec2 jitter = vec2(hash(cell + 3.1), hash(cell + 8.7));\n      float mote = hash(cell + 13.7);\n      float twinkle =\n        0.5 + 0.5 * sin(u_progress * (12.0 + mote * 30.0) + mote * 6.2831);\n      float radius =\n        (0.36 + 0.46 * hash(cell + 21.3)) * (0.65 + 0.6 * twinkle);\n      float reach = length(g - (cell + jitter));\n      float hit = 1.0 - smoothstep(radius * 0.45, radius, reach);\n      cover = max(cover, hit);\n      light = max(light, hit * mote * twinkle);\n    }\n  }\n  cover = mix(1.0, cover, dust);\n  light *= dust;\n\n  vec4 speck = vec4(\n    base.rgb + vec3(0.36 * light * u_shimmer) * base.a,\n    base.a\n  );\n  vec4 gap = vec4(base.rgb * (1.0 - dust * 0.6), base.a);\n  outColor = mix(gap, speck, cover);\n}`;\n\nconst shader = makeSceneShader<ParticleDissolveProps>(\n  FRAGMENT_SHADER,\n  ({ gl, uniform, height, passedProps }) => {\n    const {\n      particleSize = DEFAULTS.particleSize,\n      scatter = DEFAULTS.scatter,\n      shimmer = DEFAULTS.shimmer,\n      aberration = DEFAULTS.aberration,\n      direction = DEFAULTS.direction,\n      stagger = DEFAULTS.stagger,\n    } = passedProps;\n    const axis = AXES[direction] ?? AXES[DEFAULTS.direction];\n    gl.uniform1f(uniform(\"u_cells\"), height / Math.max(particleSize, 1));\n    gl.uniform1f(uniform(\"u_scatter\"), scatter);\n    gl.uniform1f(uniform(\"u_shimmer\"), shimmer);\n    gl.uniform1f(uniform(\"u_aberration\"), aberration);\n    gl.uniform1f(uniform(\"u_stagger\"), Math.min(Math.max(stagger, 0), 0.92));\n    gl.uniform2f(uniform(\"u_dir\"), axis[0], axis[1]);\n    gl.uniform1f(uniform(\"u_radial\"), direction === \"reveal\" ? 1 : 0);\n  },\n);\n\nconst ParticleDissolveFallback: React.FC<\n  TransitionPresentationComponentProps<ParticleDissolveProps>\n> = ({ children, presentationProgress, presentationDirection }) => {\n  const p = presentationProgress;\n  const entering = presentationDirection === \"entering\";\n  const t = entering ? p : 1 - p;\n  const settle = Math.min(1, Math.max(0, (t - 0.2) / 0.6));\n  const grain = 1 - settle;\n\n  return (\n    <AbsoluteFill\n      style={{\n        opacity: settle,\n        filter:\n          grain > 0.02\n            ? `blur(${grain * 10}px) saturate(${1 - grain * 0.9}) contrast(${1 + grain * 0.5})`\n            : undefined,\n      }}\n    >\n      {children}\n    </AbsoluteFill>\n  );\n};\n\nconst presentation = makeCanvasPresentation<ParticleDissolveProps>({\n  shader,\n  fallback: ParticleDissolveFallback,\n});\n\nexport function particleDissolve(\n  props: ParticleDissolveProps = {},\n): TransitionPresentation<ParticleDissolveProps> {\n  return presentation(props);\n}\n",
      "type": "registry:component",
      "target": "components/remocn/particle-dissolve.tsx"
    }
  ],
  "type": "registry:component"
}
