{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ember-burn",
  "title": "Ember Burn",
  "description": "The outgoing frame catches fire where it is brightest, boils in the heat, chars at the rim, and blows away as sparks carrying its own colour.",
  "dependencies": ["remotion", "@remotion/transitions"],
  "registryDependencies": ["@remocn/canvas-presentation"],
  "files": [
    {
      "path": "registry/remocn/ember-burn/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 EmberBurnProps = {\n  patches?: number;\n  edgeSoftness?: number;\n  contentBias?: number;\n  heat?: number;\n  glowColor?: string;\n  emberAmount?: number;\n};\n\nconst DEFAULTS = {\n  patches: 5,\n  edgeSoftness: 0.07,\n  contentBias: 0.6,\n  heat: 0.5,\n  glowColor: \"#ff7a2f\",\n  emberAmount: 0.5,\n};\n\nfunction hexToRgb(hex: string): [number, number, number] {\n  const value = hex.replace(\"#\", \"\");\n  const full =\n    value.length === 3\n      ? value\n          .split(\"\")\n          .map((c) => c + c)\n          .join(\"\")\n      : value;\n  const int = Number.parseInt(full, 16);\n  if (Number.isNaN(int)) {\n    return [1, 0.48, 0.18];\n  }\n  return [\n    ((int >> 16) & 255) / 255,\n    ((int >> 8) & 255) / 255,\n    (int & 255) / 255,\n  ];\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_patches;\nuniform float u_softness;\nuniform float u_content;\nuniform float u_heat;\nuniform vec3 u_glow;\nuniform float u_embers;\n\nin vec2 v_uv;\nout vec4 outColor;\n\nconst float EMBER_GRID = 110.0;\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 < 5; i++) {\n    sum += noise(p) * amp;\n    p *= 2.03;\n    amp *= 0.5;\n  }\n  return sum * 1.032;\n}\n\nfloat luma(vec3 c) {\n  return dot(c, vec3(0.2126, 0.7152, 0.0722));\n}\n\nvoid main() {\n  vec2 skewed = vec2(v_uv.x * u_aspect, v_uv.y);\n  float alive =\n    smoothstep(0.0, 0.06, u_progress) * smoothstep(1.0, 0.94, u_progress);\n\n  float brightness = luma(texture(u_from, v_uv).rgb);\n  float field = clamp(\n    clamp(fbm(skewed * u_patches), 0.0, 1.0) *\n      (1.0 - u_content * brightness * 0.9),\n    0.0,\n    1.0\n  );\n\n  float soft = max(u_softness, 0.004);\n  float margin = soft * 3.0 + 0.02;\n  float front = mix(-margin, 1.0 + margin, pow(u_progress, 1.2));\n  float depth = field - front;\n\n  float intact = smoothstep(-soft, soft, depth);\n  float arrived = 1.0 - intact;\n\n  vec2 shimmer = vec2(\n    noise(skewed * u_patches * 2.4 + vec2(0.0, u_progress * 3.1)) - 0.5,\n    noise(skewed * u_patches * 2.4 + vec2(5.2, 1.7 + u_progress * 4.3)) - 0.5\n  );\n  float heatBand = depth / (soft * 6.0);\n  float boil = exp(-heatBand * heatBand) * alive * u_heat * 0.14;\n  vec4 from = texture(u_from, v_uv + shimmer * boil * intact);\n  vec4 to = texture(u_to, v_uv - shimmer * boil * arrived);\n\n  float ramp = depth / (soft * 2.2);\n  float charBand = (ramp - 0.55) * 1.5;\n  float moltenBand = (ramp + 0.55) * 1.5;\n  float rimBand = depth / (soft * 1.1);\n  float charred = intact * exp(-charBand * charBand) * alive;\n  float molten = arrived * exp(-moltenBand * moltenBand) * alive;\n  float rim = exp(-rimBand * rimBand);\n\n  float since = clamp(-depth / (soft * 3.0), 0.0, 1.0);\n  float emberBand = depth / (soft * 2.6);\n  vec2 emberSpace = skewed + vec2(0.0, u_progress * 0.42);\n  float cellSeed = hash(floor(emberSpace * EMBER_GRID));\n  float ember =\n    step(1.0 - u_embers * 0.08, cellSeed) *\n    arrived *\n    exp(-emberBand * emberBand) *\n    alive;\n  vec3 sparkSource = texture(u_from, v_uv + vec2(0.0, since * 0.07)).rgb;\n\n  vec4 color = mix(to, from, intact);\n  color.rgb *= 1.0 - charred * 0.92;\n  color.rgb += (color.rgb * 1.2 + u_glow * 0.6) * molten;\n\n  vec3 spark = mix(u_glow, sparkSource * 1.4 + u_glow * 0.3, 0.5);\n  float light = min(rim * 0.85, 1.4);\n  color.rgb += u_glow * light + spark * ember * 1.3;\n  color.a = clamp(color.a + light + molten + ember, 0.0, 1.0);\n\n  outColor = color;\n}`;\n\nconst shader = makeSceneShader<EmberBurnProps>(\n  FRAGMENT_SHADER,\n  ({ gl, uniform, passedProps }) => {\n    const {\n      patches = DEFAULTS.patches,\n      edgeSoftness = DEFAULTS.edgeSoftness,\n      contentBias = DEFAULTS.contentBias,\n      heat = DEFAULTS.heat,\n      glowColor = DEFAULTS.glowColor,\n      emberAmount = DEFAULTS.emberAmount,\n    } = passedProps;\n    const glow = hexToRgb(glowColor);\n    gl.uniform1f(uniform(\"u_patches\"), patches);\n    gl.uniform1f(uniform(\"u_softness\"), edgeSoftness);\n    gl.uniform1f(uniform(\"u_content\"), contentBias);\n    gl.uniform1f(uniform(\"u_heat\"), heat);\n    gl.uniform3f(uniform(\"u_glow\"), glow[0], glow[1], glow[2]);\n    gl.uniform1f(uniform(\"u_embers\"), emberAmount);\n  },\n);\n\nconst EmberBurnFallback: React.FC<\n  TransitionPresentationComponentProps<EmberBurnProps>\n> = ({\n  children,\n  presentationProgress,\n  presentationDirection,\n  passedProps,\n}) => {\n  const { glowColor = DEFAULTS.glowColor } = passedProps;\n\n  if (presentationDirection === \"exiting\") {\n    return <AbsoluteFill>{children}</AbsoluteFill>;\n  }\n\n  const p = presentationProgress;\n  const reveal = Math.min(1, Math.max(0, (p - 0.25) / 0.5));\n  const flash = Math.max(0, Math.sin(p * Math.PI)) ** 0.6 * 0.4;\n\n  return (\n    <AbsoluteFill>\n      <AbsoluteFill style={{ opacity: reveal }}>{children}</AbsoluteFill>\n      <AbsoluteFill\n        style={{\n          background: glowColor,\n          mixBlendMode: \"screen\",\n          opacity: flash,\n          pointerEvents: \"none\",\n        }}\n      />\n    </AbsoluteFill>\n  );\n};\n\nconst presentation = makeCanvasPresentation<EmberBurnProps>({\n  shader,\n  fallback: EmberBurnFallback,\n});\n\nexport function emberBurn(\n  props: EmberBurnProps = {},\n): TransitionPresentation<EmberBurnProps> {\n  return presentation(props);\n}\n",
      "type": "registry:component",
      "target": "components/remocn/ember-burn.tsx"
    }
  ],
  "type": "registry:component"
}
