{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chromatic-wave",
  "title": "Chromatic Wave",
  "description": "A soft wave travels down through the type; where it crosses, the letters ripple and split into RGB fringes.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/chromatic-wave/index.tsx",
      "content": "\"use client\";\n\nimport { useId } from \"react\";\nimport { interpolate, useCurrentFrame, useVideoConfig } from \"remotion\";\n\nexport interface ChromaticWaveProps {\n  className?: string;\n  width?: number;\n  height?: number;\n  headline?: string;\n  kicker?: string;\n  headlineFontFamily?: string;\n  kickerFontFamily?: string;\n  headlineFontSize?: number;\n  kickerFontSize?: number;\n  kickerTracking?: number;\n  headlineBaselineY?: number;\n  kickerBaselines?: [number, number];\n  textCenterX?: number;\n  fill?: string;\n  background?: string;\n  bandHeight?: number;\n  bandCopies?: number;\n  bandPitch?: number;\n  mapStartY?: number;\n  mapEndY?: number;\n  mapTravelFrames?: number;\n  waveHeight?: number;\n  waveWidth?: number;\n  waveSpeed?: number;\n  turbulentAmount?: number;\n  turbulentSize?: number;\n  evolutionPerSecond?: number;\n  mapBlurRadius?: number;\n  mapBlurIterations?: number;\n  glassDisplacement?: number;\n  maxDisplacementPx?: number;\n  glassPasses?: number;\n  channelOrder?: (\"red\" | \"green\" | \"blue\")[];\n}\n\nfunction boxBlurSigma(radius: number, iterations: number): number {\n  const w = 2 * radius + 1;\n  return Math.sqrt((iterations * (w * w - 1)) / 12);\n}\n\nfunction buildMapUri({\n  width,\n  height,\n  bandHeight,\n  bandCopies,\n  bandPitch,\n  mapY,\n  waveHeight,\n  waveWidth,\n  phase,\n}: {\n  width: number;\n  height: number;\n  bandHeight: number;\n  bandCopies: number;\n  bandPitch: number;\n  mapY: number;\n  waveHeight: number;\n  waveWidth: number;\n  phase: number;\n}): string {\n  const overhang = 120;\n  const segments = 48;\n\n  const bands = Array.from({ length: bandCopies }, (_, i) => {\n    const top = mapY + i * bandPitch - bandHeight / 2;\n    const pts = Array.from({ length: segments + 1 }, (_, s) => {\n      const x = -overhang + (s / segments) * (width + overhang * 2);\n      const y =\n        top + Math.sin((x / waveWidth) * Math.PI * 2 + phase) * waveHeight;\n      return `${x},${y}`;\n    });\n    const back = Array.from({ length: segments + 1 }, (_, s) => {\n      const x =\n        -overhang + ((segments - s) / segments) * (width + overhang * 2);\n      const y =\n        top +\n        bandHeight +\n        Math.sin((x / waveWidth) * Math.PI * 2 + phase) * waveHeight;\n      return `${x},${y}`;\n    });\n    return `<path d=\"M${pts.join(\"L\")}L${back.join(\"L\")}Z\" fill=\"url(#g)\"/>`;\n  }).join(\"\");\n\n  const svg =\n    `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${height}\">` +\n    `<defs><linearGradient id=\"g\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">` +\n    `<stop offset=\"0\" stop-color=\"rgb(128,128,128)\"/>` +\n    `<stop offset=\"0.25\" stop-color=\"rgb(0,0,0)\"/>` +\n    `<stop offset=\"0.5\" stop-color=\"rgb(128,128,128)\"/>` +\n    `<stop offset=\"0.75\" stop-color=\"rgb(255,255,255)\"/>` +\n    `<stop offset=\"1\" stop-color=\"rgb(128,128,128)\"/>` +\n    `</linearGradient></defs>` +\n    `<rect width=\"${width}\" height=\"${height}\" fill=\"rgb(128,128,128)\"/>` +\n    bands +\n    `</svg>`;\n\n  return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;\n}\n\nexport function ChromaticWave({\n  className,\n  width = 1920,\n  height = 1080,\n  headline = \"MUSIC\",\n  kicker = \"APPLES\",\n  headlineFontFamily = \"'Helvetica Neue', Helvetica, Arial, sans-serif\",\n  kickerFontFamily = \"'Helvetica Neue', Helvetica, Arial, sans-serif\",\n  headlineFontSize = 174,\n  kickerFontSize = 52,\n  kickerTracking = 32,\n  headlineBaselineY = 601,\n  kickerBaselines = [444, 670],\n  textCenterX = 960,\n  fill = \"#ffffff\",\n  background = \"#000000\",\n  bandHeight = 147.1,\n  bandCopies = 7,\n  bandPitch = 750,\n  mapStartY = 31,\n  mapEndY = 1560,\n  mapTravelFrames = 150,\n  waveHeight = 28,\n  waveWidth = 272,\n  waveSpeed = 1,\n  turbulentAmount = 130,\n  turbulentSize = 163,\n  evolutionPerSecond = 50,\n  mapBlurRadius = 13,\n  mapBlurIterations = 6,\n  glassDisplacement = 100,\n  maxDisplacementPx = 39,\n  glassPasses = 3,\n  channelOrder = [\"blue\", \"green\", \"red\"],\n}: ChromaticWaveProps) {\n  const frame = useCurrentFrame();\n  const { fps } = useVideoConfig();\n  const filterId = useId();\n  const seconds = frame / fps;\n\n  const mapY = interpolate(frame, [0, mapTravelFrames], [mapStartY, mapEndY], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n  });\n\n  const mapUri = buildMapUri({\n    width,\n    height,\n    bandHeight,\n    bandCopies,\n    bandPitch,\n    mapY,\n    waveHeight,\n    waveWidth,\n    phase: seconds * waveSpeed * Math.PI * 2,\n  });\n\n  const mapSigma = boxBlurSigma(mapBlurRadius, mapBlurIterations);\n  const evolution = seconds * evolutionPerSecond;\n\n  const channelMatrix: Record<string, string> = {\n    red: \"1 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 1 0\",\n    green: \"0 0 0 0 0  0 1 0 0 0  0 0 0 0 0  0 0 0 1 0\",\n    blue: \"0 0 0 0 0  0 0 0 0 0  0 0 1 0 0  0 0 0 1 0\",\n  };\n\n  const content = (\n    <>\n      <text\n        x={textCenterX}\n        y={kickerBaselines[0]}\n        textAnchor=\"middle\"\n        fontFamily={kickerFontFamily}\n        fontSize={kickerFontSize}\n        letterSpacing={kickerTracking}\n        fill={fill}\n      >\n        {kicker}\n      </text>\n      <text\n        x={textCenterX}\n        y={headlineBaselineY}\n        textAnchor=\"middle\"\n        fontFamily={headlineFontFamily}\n        fontWeight={700}\n        fontSize={headlineFontSize}\n        fill={fill}\n      >\n        {headline}\n      </text>\n      <text\n        x={textCenterX}\n        y={kickerBaselines[1]}\n        textAnchor=\"middle\"\n        fontFamily={kickerFontFamily}\n        fontSize={kickerFontSize}\n        letterSpacing={kickerTracking}\n        fill={fill}\n      >\n        {kicker}\n      </text>\n    </>\n  );\n\n  return (\n    <div\n      className={className}\n      style={{ position: \"absolute\", inset: 0, overflow: \"hidden\" }}\n    >\n      <svg\n        width=\"100%\"\n        height=\"100%\"\n        viewBox={`0 0 ${width} ${height}`}\n        preserveAspectRatio=\"xMidYMid meet\"\n        style={{ display: \"block\" }}\n      >\n        <defs>\n          {Array.from({ length: glassPasses }, (_, i) => {\n            const depth =\n              ((i + 1) / glassPasses) *\n              maxDisplacementPx *\n              (glassDisplacement / 100);\n            return (\n              <filter\n                key={channelOrder[i] ?? i}\n                id={`${filterId}-${i}`}\n                filterUnits=\"userSpaceOnUse\"\n                x=\"0\"\n                y=\"0\"\n                width={width}\n                height={height}\n                colorInterpolationFilters=\"sRGB\"\n              >\n                <feImage\n                  href={mapUri}\n                  x=\"0\"\n                  y=\"0\"\n                  width={width}\n                  height={height}\n                  result=\"rawMap\"\n                />\n                <feTurbulence\n                  type=\"turbulence\"\n                  baseFrequency={1 / Math.max(turbulentSize, 1)}\n                  numOctaves={1}\n                  seed={11}\n                  result=\"noise\"\n                />\n                <feOffset\n                  in=\"noise\"\n                  dx={evolution}\n                  dy={evolution * 0.4}\n                  result=\"noiseMoved\"\n                />\n                <feDisplacementMap\n                  in=\"rawMap\"\n                  in2=\"noiseMoved\"\n                  scale={turbulentAmount}\n                  xChannelSelector=\"R\"\n                  yChannelSelector=\"G\"\n                  result=\"turbulentMap\"\n                />\n                <feGaussianBlur\n                  in=\"turbulentMap\"\n                  stdDeviation={mapSigma}\n                  result=\"slopeMap\"\n                />\n                <feDisplacementMap\n                  in=\"SourceGraphic\"\n                  in2=\"slopeMap\"\n                  scale={depth}\n                  xChannelSelector=\"R\"\n                  yChannelSelector=\"R\"\n                  result=\"displaced\"\n                />\n                <feColorMatrix\n                  in=\"displaced\"\n                  type=\"matrix\"\n                  values={channelMatrix[channelOrder[i] ?? \"red\"]}\n                />\n              </filter>\n            );\n          })}\n        </defs>\n\n        <rect width={width} height={height} fill={background} />\n\n        {Array.from({ length: glassPasses }, (_, i) => (\n          <g\n            key={channelOrder[i] ?? i}\n            filter={`url(#${filterId}-${i})`}\n            style={{ mixBlendMode: \"screen\" }}\n          >\n            {content}\n          </g>\n        ))}\n      </svg>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/chromatic-wave.tsx"
    }
  ],
  "type": "registry:component"
}
