{
  "name": "rgb-glitch-text",
  "type": "registry:ui",
  "description": "Signal-lock RGB glitch text with channel splits, slices, and scanlines",
  "dependencies": [
    "remotion"
  ],
  "registryDependencies": [
    "layout"
  ],
  "files": [
    {
      "path": "registry/bases/default/primitives/rgb-glitch-text.tsx",
      "type": "registry:ui",
      "content": "import { Easing, interpolate, useCurrentFrame, useVideoConfig } from \"remotion\";\nimport { scaleFont } from \"@/remotion/lib/layout\";\n\nexport type RgbGlitchTextProps = {\n  text: string;\n  delayInFrames?: number;\n  durationInFrames?: number;\n  glitchStartFrame?: number;\n  glitchDurationInFrames?: number;\n  fontSize?: number;\n  color?: string;\n  fontWeight?: number;\n  fontFamily?: string;\n  redChannelColor?: string;\n  cyanChannelColor?: string;\n  accentColor?: string;\n  glowColor?: string;\n  intensity?: number;\n  sliceCount?: number;\n  maxWidth?: number | string;\n  textAlign?: React.CSSProperties[\"textAlign\"];\n};\n\nconst DEFAULT_RED = \"#f472b6\";\nconst DEFAULT_CYAN = \"#2dd4bf\";\nconst DEFAULT_ACCENT = \"#e8b86d\";\nconst DEFAULT_FONT_FAMILY =\n  \"Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \\\"Segoe UI\\\", sans-serif\";\n\nfunction clamp(value: number, min: number, max: number) {\n  return Math.min(max, Math.max(min, value));\n}\n\nfunction pulse(frame: number, start: number, duration: number) {\n  return interpolate(\n    frame,\n    [start, start + duration * 0.45, start + duration],\n    [0, 1, 0],\n    {\n      extrapolateLeft: \"clamp\",\n      extrapolateRight: \"clamp\",\n      easing: Easing.bezier(0.16, 1, 0.3, 1),\n    },\n  );\n}\n\nfunction sliceOffset(index: number, burst: number, amplitude: number) {\n  const direction = index % 2 === 0 ? 1 : -1;\n  const variance = 0.52 + ((index * 37) % 41) / 100;\n  return direction * burst * amplitude * variance;\n}\n\nexport const RgbGlitchText: React.FC<RgbGlitchTextProps> = ({\n  text,\n  delayInFrames = 0,\n  durationInFrames,\n  glitchStartFrame,\n  glitchDurationInFrames,\n  fontSize: fontSizeProp,\n  color = \"#f4f4f5\",\n  fontWeight = 800,\n  fontFamily,\n  redChannelColor = DEFAULT_RED,\n  cyanChannelColor = DEFAULT_CYAN,\n  accentColor = DEFAULT_ACCENT,\n  glowColor = \"rgba(232,184,109,0.3)\",\n  intensity = 1,\n  sliceCount = 5,\n  maxWidth,\n  textAlign = \"center\",\n}) => {\n  const frame = useCurrentFrame();\n  const { width } = useVideoConfig();\n  const fontSize = fontSizeProp ?? scaleFont(92, width);\n  const start = glitchStartFrame ?? delayInFrames + 10;\n  const duration = Math.max(6, glitchDurationInFrames ?? durationInFrames ?? 30);\n  const localFrame = frame - start;\n  const safeIntensity = clamp(intensity, 0, 2);\n  const normalizedSliceCount = clamp(Math.round(sliceCount), 3, 9);\n  const slices = Array.from({ length: normalizedSliceCount }, (_, index) => index);\n\n  const firstBurst = pulse(localFrame, 0, duration * 0.34);\n  const secondBurst = pulse(localFrame, duration * 0.42, duration * 0.28);\n  const finalBurst = pulse(localFrame, duration * 0.72, duration * 0.2);\n  const glitchAmount = Math.max(firstBurst, secondBurst * 0.82, finalBurst * 0.58);\n  const settle = interpolate(localFrame, [duration * 0.72, duration], [0, 1], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: Easing.out(Easing.cubic),\n  });\n  const channelOffset = glitchAmount * scaleFont(10, width) * safeIntensity;\n  const sliceAmplitude = scaleFont(22, width) * safeIntensity;\n  const scanlineOpacity = glitchAmount * 0.42;\n  const glowOpacity = interpolate(\n    Math.max(glitchAmount, settle * 0.45),\n    [0, 1],\n    [0.12, 0.54],\n    { extrapolateLeft: \"clamp\", extrapolateRight: \"clamp\" },\n  );\n\n  const baseTextStyle: React.CSSProperties = {\n    display: \"block\",\n    fontSize,\n    fontWeight,\n    lineHeight: 0.98,\n    color,\n    whiteSpace: \"pre-wrap\",\n    textAlign,\n    fontFamily: fontFamily ?? DEFAULT_FONT_FAMILY,\n  };\n\n  return (\n    <span\n      style={{\n        position: \"relative\",\n        display: \"inline-block\",\n        maxWidth,\n        isolation: \"isolate\",\n        filter: `drop-shadow(0 0 ${scaleFont(24, width)}px ${glowColor})`,\n      }}\n    >\n      <span\n        style={{\n          ...baseTextStyle,\n          position: \"relative\",\n          textShadow: `0 0 ${scaleFont(18, width)}px rgba(244,244,245,${glowOpacity * 0.24}), 0 0 ${scaleFont(42, width)}px ${glowColor}`,\n        }}\n      >\n        {text}\n      </span>\n      <span\n        aria-hidden\n        style={{\n          ...baseTextStyle,\n          position: \"absolute\",\n          inset: 0,\n          color: redChannelColor,\n          opacity: glitchAmount * 0.82,\n          mixBlendMode: \"screen\",\n          transform: `translate(${channelOffset * -1}px, ${firstBurst * scaleFont(-2, width)}px)`,\n          zIndex: 1,\n        }}\n      >\n        {text}\n      </span>\n      <span\n        aria-hidden\n        style={{\n          ...baseTextStyle,\n          position: \"absolute\",\n          inset: 0,\n          color: cyanChannelColor,\n          opacity: glitchAmount * 0.82,\n          mixBlendMode: \"screen\",\n          transform: `translate(${channelOffset}px, ${secondBurst * scaleFont(2, width)}px)`,\n          zIndex: 1,\n        }}\n      >\n        {text}\n      </span>\n      {slices.map((index) => {\n        const sliceHeight = 100 / normalizedSliceCount;\n        const top = index * sliceHeight;\n        const bottom = Math.max(0, 100 - top - sliceHeight * 0.72);\n        const burst = index % 3 === 0 ? firstBurst : index % 3 === 1 ? secondBurst : finalBurst;\n        const offset = sliceOffset(index, burst, sliceAmplitude);\n        const colorShift = index % 2 === 0 ? cyanChannelColor : redChannelColor;\n\n        return (\n          <span\n            key={index}\n            aria-hidden\n            style={{\n              ...baseTextStyle,\n              position: \"absolute\",\n              inset: 0,\n              color: index === normalizedSliceCount - 1 ? accentColor : colorShift,\n              opacity: burst * (0.32 + index * 0.04),\n              clipPath: `inset(${top}% 0 ${bottom}% 0)`,\n              translate: `${offset}px 0px`,\n              mixBlendMode: \"screen\",\n              zIndex: 2,\n            }}\n          >\n            {text}\n          </span>\n        );\n      })}\n      <span\n        aria-hidden\n        style={{\n          position: \"absolute\",\n          inset: `${scaleFont(-8, width)}px 0`,\n          opacity: scanlineOpacity,\n          backgroundImage: `repeating-linear-gradient(180deg, transparent 0, transparent ${scaleFont(7, width)}px, ${accentColor}55 ${scaleFont(7, width)}px, ${accentColor}55 ${scaleFont(9, width)}px)`,\n          transform: `translateY(${interpolate(localFrame, [0, duration], [scaleFont(-18, width), scaleFont(18, width)], {\n            extrapolateLeft: \"clamp\",\n            extrapolateRight: \"clamp\",\n          })}px)`,\n          pointerEvents: \"none\",\n          mixBlendMode: \"screen\",\n          zIndex: 3,\n        }}\n      />\n    </span>\n  );\n};\n"
    }
  ],
  "atlas": {
    "lane": "atoms",
    "drive": "time",
    "tier": "advanced",
    "tags": [
      "text"
    ]
  }
}