{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "cathode",
  "type": "registry:ui",
  "title": "Cathode",
  "description": "A CRT / VHS tube background: a synthetic phosphor signal rolls under dense scanlines while a slow vertical tracking bar sweeps down and shears the picture, the whole frame barrel-distorted into a bulging glass tube with a dark bezel, radial chromatic aberration, phosphor bloom, aperture-grille shimmer, and animated snow — all graded through a single phosphor tint. Original OGL fragment shader, DPR-clamped, offscreen-paused, reduced-motion-safe.",
  "dependencies": [
    "ogl"
  ],
  "registryDependencies": [
    "https://crucibleui.com/r/ogl-canvas.json",
    "https://crucibleui.com/r/use-visibility-pause.json",
    "https://crucibleui.com/r/use-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/default/backgrounds/cathode/cathode.tsx",
      "type": "registry:ui",
      "target": "components/crucible/cathode.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport { OglCanvas } from \"@/lib/ogl-canvas\";\nimport { useReducedMotion } from \"@/hooks/use-reduced-motion\";\n\nexport interface CathodeProps extends React.ComponentPropsWithoutRef<\"div\"> {\n  /**\n   * Screen palette as `[phosphor, ambient]`. `phosphor` is the glowing tube\n   * tint that colours the scanned image and bloom; `ambient` is the faint\n   * screen-on wash in the dark areas. Defaults to a classic green-phosphor\n   * terminal on near-black. Pass e.g. `[\"#ffb454\", \"#140a02\"]` for amber,\n   * or `[\"#bfe6ff\", \"#050a12\"]` for a cold blue-white tube.\n   */\n  colors?: [string, string];\n  /** Playback-rate multiplier for the roll, tracking bar, and snow. 1 = default. Default 1. */\n  speed?: number;\n  /**\n   * Overall luminosity of the phosphor, scanlines, and bloom, 0–2. 1 = default;\n   * lower banks a dim standby tube, higher blazes. Default 1.\n   */\n  intensity?: number;\n  /**\n   * Barrel-distortion / tube-bulge amount, 0–1. 0 is a flat panel; 1 is a\n   * fat vintage CRT with pronounced curvature and a dark bezel. Default 0.32.\n   */\n  curvature?: number;\n  /**\n   * Snow / signal-noise amount, 0–1. 0 is a clean transmission; 1 is heavy\n   * VHS static. Default 0.35.\n   */\n  noise?: number;\n  /** Freeze the animation at its current frame. */\n  paused?: boolean;\n}\n\nconst DEFAULT_COLORS: [string, string] = [\n  \"#7cffc4\", // green phosphor\n  \"#05130d\", // ambient screen-on wash\n];\n\n/** Hex → [r,g,b] in 0–1. Accepts #rgb / #rrggbb. */\nfunction hexToRgb(hex: string): [number, number, number] {\n  let h = hex.replace(\"#\", \"\").trim();\n  if (h.length === 3) {\n    h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];\n  }\n  const int = parseInt(h, 16);\n  if (Number.isNaN(int) || h.length !== 6) return [0.49, 1, 0.77];\n  return [((int >> 16) & 255) / 255, ((int >> 8) & 255) / 255, (int & 255) / 255];\n}\n\nfunction clamp(v: number, lo: number, hi: number): number {\n  return Math.min(Math.max(v, lo), hi);\n}\n\n/**\n * Original CRT / VHS tube fragment shader. The \"signal\" is a synthetic\n * monochrome phosphor field — faintly drifting domain-warped bands under a\n * dense pattern of rolling horizontal scanlines. A slow vertical TRACKING bar\n * sweeps down the screen, brightening a thin band and shearing the picture\n * with a burst of horizontal jitter as it passes (the classic bad-vertical-\n * hold tear). The whole frame is barrel-distorted so it bulges like a glass\n * tube, masked by a dark bezel at the rim. Chromatic aberration splits the\n * red/blue channels radially — strongest at the curved edges — and the bright\n * scan bands smear into a soft phosphor bloom. A fine aperture-grille shimmer\n * and animated snow ride on top, all graded through the phosphor tint and a\n * radial vignette. `uMotion` (0 under reduced motion) kills the roll, bar, and\n * snow, leaving a still, lit tube. GLSL ES 1.00 (WebGL1/2 compatible).\n */\nconst FRAGMENT = /* glsl */ `\nprecision highp float;\n\nvarying vec2 vUv;\n\nuniform float uTime;\nuniform vec2 uResolution;\n\nuniform vec3 uPhosphor;\nuniform vec3 uAmbient;\nuniform float uSpeed;\nuniform float uIntensity;\nuniform vec2 uCurve;    // barrel divisors (smaller = more bulge)\nuniform float uSnow;    // snow amount\nuniform float uMotion;  // 0 under reduced motion, else 1\n\nconst float TAU = 6.28318530718;\nconst float SCAN = 240.0; // scanline pairs across the tube height\n\nfloat hash21(vec2 p) {\n  p = fract(p * vec2(123.34, 456.21));\n  p += dot(p, p + 45.32);\n  return fract(p.x * p.y);\n}\nfloat vnoise(vec2 p) {\n  vec2 i = floor(p);\n  vec2 f = fract(p);\n  float a = hash21(i);\n  float b = hash21(i + vec2(1.0, 0.0));\n  float c = hash21(i + vec2(0.0, 1.0));\n  float d = hash21(i + vec2(1.0, 1.0));\n  vec2 u = f * f * (3.0 - 2.0 * f);\n  return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\nfloat fbm(vec2 p) {\n  float v = 0.0;\n  float amp = 0.5;\n  mat2 m = mat2(1.6, 1.2, -1.2, 1.6);\n  for (int i = 0; i < 3; i++) {\n    v += amp * vnoise(p);\n    p = m * p;\n    amp *= 0.5;\n  }\n  return v;\n}\n\n// Signed distance of uv.y from the descending tracking bar (wrapped 0..1).\nfloat barDist(float y, float t) {\n  float bp = fract(0.5 - t * 0.07 * uMotion);\n  float d = y - bp;\n  return d - floor(d + 0.5);\n}\n\n// The synthetic monochrome signal at a sample point.\nfloat signal(vec2 uv, float t) {\n  // Rolling scanlines: dense horizontal lines scrolling slowly upward.\n  float rows = uv.y * SCAN - t * 1.2 * uMotion;\n  float scan = 0.55 + 0.45 * sin(rows * TAU);\n\n  // Faint drifting phosphor picture (soft ghost bands, domain-warped).\n  float pic = 0.16\n    + 0.11 * fbm(vec2(uv.x * 2.2, uv.y * 3.2) + vec2(0.0, -t * 0.06 * uMotion))\n    + 0.06 * sin(uv.y * 6.5 + t * 0.2 * uMotion);\n\n  // Tracking bar: a thin bright band with a soft leading glow.\n  float bd = barDist(uv.y, t);\n  float bar = exp(-bd * bd * 300.0) * 0.9 + exp(-bd * bd * 40.0) * 0.14;\n  pic += bar * uMotion;\n\n  return max(pic, 0.0) * scan;\n}\n\nvoid main() {\n  vec2 uv0 = vUv;\n  float aspect = uResolution.x / max(uResolution.y, 1.0);\n  float t = uTime * uSpeed;\n\n  // ---- barrel distortion (tube bulge) ----\n  vec2 cc = uv0 * 2.0 - 1.0;\n  vec2 warp = (cc.yx * cc.yx) / uCurve;\n  vec2 uv = cc + cc * warp;\n  // Bezel: fade to black just before the curved rim.\n  vec2 em = smoothstep(1.0, 0.965, abs(uv));\n  float mask = em.x * em.y;\n  uv = uv * 0.5 + 0.5;\n\n  // ---- tracking-bar shear: horizontal jitter as the bar passes ----\n  float bd = barDist(uv.y, t);\n  float near = exp(-bd * bd * 90.0);\n  uv.x += (fbm(vec2(uv.y * 60.0, t * 3.0)) - 0.5) * 0.03 * near * uMotion;\n\n  // ---- chromatic aberration: radial R/B split, strongest at the edges ----\n  vec2 rc = uv - 0.5;\n  float edge = dot(rc, rc);\n  vec2 dir = rc / max(length(rc), 1e-4);\n  float ca = (0.0016 + edge * 0.02);\n\n  float lg = signal(uv, t);\n  float lr = signal(uv + dir * ca, t);\n  float lb = signal(uv - dir * ca, t);\n\n  vec3 col = vec3(lr, lg, lb) * uPhosphor;\n  // Pronounce the coloured fringe at high-contrast edges.\n  col.r += (lr - lg) * 0.7;\n  col.b += (lb - lg) * 0.7;\n\n  // ---- phosphor bloom: vertical smear of the bright scan bands ----\n  float bloom = signal(uv + vec2(0.0, 0.009), t) + signal(uv - vec2(0.0, 0.009), t);\n  col += uPhosphor * bloom * 0.13 * uIntensity;\n\n  // Ambient screen-on wash so the dark areas still glow like a live tube.\n  col += uAmbient;\n\n  // ---- fine aperture-grille shimmer (subtle, anti-aliased amplitude) ----\n  float grille = 0.94 + 0.06 * sin(uv.x * aspect * 520.0);\n  col *= grille;\n\n  // ---- radial vignette ----\n  col *= 1.0 - 1.05 * edge;\n\n  // ---- snow / signal noise ----\n  float sn = hash21(gl_FragCoord.xy + fract(t) * 131.7);\n  col += (sn - 0.5) * uSnow * uMotion * (0.6 + 0.4 * uIntensity);\n\n  // ---- bezel + overall level ----\n  col *= mask * uIntensity;\n\n  gl_FragColor = vec4(max(col, 0.0), 1.0);\n}\n`;\n\n/**\n * Cathode — a CRT / VHS tube background. A synthetic phosphor signal rolls\n * under dense scanlines while a slow vertical tracking bar sweeps down and\n * shears the picture, the whole frame barrel-distorted into a bulging glass\n * tube with a dark bezel, radial chromatic aberration, phosphor bloom, an\n * aperture-grille shimmer, and animated snow — all graded through a single\n * phosphor tint (`colors`). Built on the `ogl-canvas` harness: DPR-clamped,\n * offscreen- and hidden-tab-paused, reduced-motion-safe (freezes to a still,\n * lit tube — scanlines and vignette, no roll or snow), full GL teardown.\n * Drop it inside any `relative` container; it fills and sits behind content.\n */\nexport function Cathode({\n  colors = DEFAULT_COLORS,\n  speed = 1,\n  intensity = 1,\n  curvature = 0.32,\n  noise = 0.35,\n  paused = false,\n  className,\n  ...props\n}: CathodeProps) {\n  const reducedMotion = useReducedMotion();\n\n  const uniforms = React.useMemo(() => {\n    const curv = clamp(curvature, 0, 1);\n    const flatten = Math.max(Math.min(curv / 0.32, 1), 1e-4);\n    return {\n      uPhosphor: { value: hexToRgb(colors[0]) },\n      uAmbient: { value: hexToRgb(colors[1]) },\n      uSpeed: { value: Math.max(speed, 0) },\n      uIntensity: { value: clamp(intensity, 0, 2) },\n      // Smaller divisors = more bulge. Wider on X so the tube reads landscape.\n      // Below the default the divisors grow without bound, so 0 is truly flat.\n      uCurve: {\n        value: [\n          Math.max(12.5 - curv * 9.0, 2.5) / flatten,\n          Math.max(9.5 - curv * 6.8, 2.2) / flatten,\n        ] as [number, number],\n      },\n      uSnow: { value: clamp(noise, 0, 1) * 0.22 },\n      uMotion: { value: reducedMotion ? 0 : 1 },\n    };\n  }, [colors, speed, intensity, curvature, noise, reducedMotion]);\n\n  return (\n    <OglCanvas\n      aria-hidden\n      data-crucible=\"cathode\"\n      className={cn(\"absolute inset-0 -z-10 bg-neutral-950\", className)}\n      fragment={FRAGMENT}\n      uniforms={uniforms}\n      dpr={2}\n      paused={paused}\n      reducedMotionTime={6}\n      {...props}\n    />\n  );\n}\n"
    }
  ]
}