{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "Topography-TS-TW",
	"title": "Topography",
	"description": "A living contour map with glowing, elevation-tinted lines.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:component",
			"path": "Topography/Topography.tsx",
			"content": "import React, { useEffect, useRef } from 'react';\nimport { Renderer, Program, Mesh, Triangle } from 'ogl';\n\nexport type ColorMode = 'elevation' | 'uniform' | 'alternating';\n\nexport interface TopographyProps {\n  lowColor?: string;\n  midColor?: string;\n  highColor?: string;\n  speed?: number;\n  morphAmount?: number;\n  morphSpeed?: number;\n  bands?: number;\n  thickness?: number;\n  scale?: number;\n  pixelSize?: number;\n  glow?: number;\n  colorMode?: ColorMode;\n  contrast?: number;\n  brightness?: number;\n  fillBands?: boolean;\n  opacity?: number;\n  grain?: boolean;\n  grainIntensity?: number;\n  mouseInteraction?: boolean;\n  mouseRadius?: number;\n  mouseStrength?: number;\n  lightMode?: boolean;\n  className?: string;\n}\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n  const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n  if (!result) return [1, 1, 1];\n  return [parseInt(result[1], 16) / 255, parseInt(result[2], 16) / 255, parseInt(result[3], 16) / 255];\n};\n\nconst colorModeToFloat = (mode: ColorMode): number => {\n  if (mode === 'uniform') return 1.0;\n  if (mode === 'alternating') return 2.0;\n  return 0.0;\n};\n\nconst vertex = `#version 300 es\nin vec2 position;\nvoid main() {\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragment = `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform float uMorphAmount;\nuniform float uBands;\nuniform float uThickness;\nuniform float uScale;\nuniform float uPixelSize;\nuniform float uGlow;\nuniform float uColorMode;\nuniform float uContrast;\nuniform float uBrightness;\nuniform float uFillBands;\nuniform float uOpacity;\nuniform float uLightMode;\nuniform vec3 uLow;\nuniform vec3 uMid;\nuniform vec3 uHigh;\nuniform vec2 uMouse;\nuniform float uMouseEnabled;\nuniform float uMouseRadius;\nuniform float uMouseStrength;\nuniform float uMouseActive;\nuniform float uGrain;\nuniform float uGrainIntensity;\nuniform vec4 uCtrlA;\nuniform vec4 uCtrlB;\nuniform vec4 uCtrlC;\nuniform vec4 uCtrlD;\nout vec4 fragColor;\n\nfloat bez(float t, vec4 c) {\n  float w = 6.2831853 * t;\n  return 0.5 * (c.x * sin(w) + c.y * cos(w) + c.z * sin(2.0 * w) + c.w * cos(2.0 * w));\n}\n\nfloat field(vec2 uv) {\n  vec2 a = vec2(bez(uv.x, uCtrlA), bez(uv.x, uCtrlB));\n  vec2 b = vec2(bez(uv.y, uCtrlC), bez(uv.y, uCtrlD));\n  return distance(a, b);\n}\n\nvec3 elevationColor(float e) {\n  vec3 c = mix(uLow, uMid, smoothstep(0.0, 0.5, e));\n  c = mix(c, uHigh, smoothstep(0.5, 1.0, e));\n  return c;\n}\n\nvoid main() {\n  vec2 res = iResolution.xy;\n  vec2 uv = gl_FragCoord.xy / res;\n\n  vec2 suv = (uv - 0.5) / max(uScale, 0.001) + 0.5;\n\n  vec2 sampleUv = suv;\n  if (uPixelSize > 1.0) {\n    vec2 px = res / uPixelSize;\n    sampleUv = (floor(suv * px) + 0.5) / px;\n  }\n\n  float fv = field(sampleUv);\n\n  if (uMouseEnabled > 0.5) {\n    vec2 d = uv - uMouse;\n    d.x *= res.x / max(res.y, 1.0);\n    float r = max(uMouseRadius, 0.001);\n    float bump = exp(-dot(d, d) / (r * r)) * uMouseStrength * uMouseActive;\n    fv += bump;\n  }\n\n  float f = fv * uBands;\n  float frac = fract(f);\n  float lineDist = min(frac, 1.0 - frac);\n\n  float aa = fwidth(f) + 0.0001;\n  float mask = 1.0 - smoothstep(uThickness - aa, uThickness + aa, lineDist);\n\n  float glowR = uThickness + uGlow * 0.5 + aa;\n  float glow = (1.0 - smoothstep(uThickness, glowR, lineDist)) * step(0.0001, uGlow);\n\n  float elev = clamp(fv / (uMorphAmount * 2.5 + 0.001), 0.0, 1.0);\n\n  vec3 lineCol;\n  if (uColorMode < 0.5) {\n    lineCol = elevationColor(elev);\n  } else if (uColorMode < 1.5) {\n    lineCol = uMid;\n  } else {\n    float parity = mod(floor(f), 2.0);\n    lineCol = mix(uMid, uHigh, parity);\n  }\n\n  float coverage = clamp(mask + glow * 0.55, 0.0, 1.0);\n  coverage = pow(coverage, max(uContrast, 0.001));\n\n  vec3 outColor = lineCol;\n  float outAlpha = coverage;\n\n  if (uFillBands > 0.5) {\n    vec3 fillCol = elevationColor(elev);\n    float fillA = 0.1 * elev;\n    outColor = mix(fillCol, lineCol, coverage);\n    outAlpha = clamp(coverage + fillA, 0.0, 1.0);\n  }\n\n  if (uGrain > 0.5) {\n    float g = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233)) + iTime) * 43758.5453);\n    outAlpha += (g - 0.5) * uGrainIntensity;\n  }\n\n  outColor *= uBrightness;\n  outColor = clamp(outColor, 0.0, 1.0);\n\n  float a = clamp(outAlpha, 0.0, 1.0) * uOpacity;\n  if (uLightMode > 0.5) {\n    float peak = max(outColor.r, max(outColor.g, outColor.b));\n    vec3 chroma = pow(clamp(outColor / max(peak, 0.0001), 0.0, 1.0), vec3(1.18));\n    fragColor = vec4(mix(vec3(1.0), chroma, a * 0.94), 1.0);\n  } else {\n    fragColor = vec4(outColor * a, a);\n  }\n}\n`;\n\ntype TopographyCtx = {\n  renderer: InstanceType<typeof Renderer>;\n  program: InstanceType<typeof Program>;\n  mesh: InstanceType<typeof Mesh>;\n};\nconst ctxMap = new WeakMap<HTMLDivElement, TopographyCtx>();\n\nconst CTRL_INDICES = [\n  [1, -2, 3, -4],\n  [9, -8, 7, -6],\n  [5, 2, 5, -5],\n  [-1, -3, 8, 9]\n];\n\nconst Topography: React.FC<TopographyProps> = ({\n  lowColor = '#5227FF',\n  midColor = '#FF9FFC',\n  highColor = '#FFFFFF',\n  speed = 0.35,\n  morphAmount = 3.0,\n  morphSpeed = 0.05,\n  bands = 2.0,\n  thickness = 0.01,\n  scale = 1.0,\n  pixelSize = 1.0,\n  glow = 0.5,\n  colorMode = 'elevation',\n  contrast = 3.0,\n  brightness = 1.0,\n  fillBands = false,\n  opacity = 1.0,\n  grain = true,\n  grainIntensity = 0.05,\n  mouseInteraction = true,\n  mouseRadius = 0.3,\n  mouseStrength = 0.4,\n  lightMode = false,\n  className = ''\n}) => {\n  const containerRef = useRef<HTMLDivElement | null>(null);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n\n    const renderer = new Renderer({\n      webgl: 2,\n      alpha: true,\n      premultipliedAlpha: true,\n      antialias: false,\n      dpr: Math.min(window.devicePixelRatio || 1, 2)\n    });\n\n    const gl = renderer.gl;\n    gl.clearColor(0, 0, 0, 0);\n    const canvas = gl.canvas as HTMLCanvasElement;\n    canvas.style.width = '100%';\n    canvas.style.height = '100%';\n    canvas.style.display = 'block';\n    container.appendChild(canvas);\n\n    const geometry = new Triangle(gl);\n    const program = new Program(gl, {\n      vertex,\n      fragment,\n      uniforms: {\n        iTime: { value: 0 },\n        iResolution: { value: new Float32Array([1, 1]) },\n        uSpeed: { value: 0.35 },\n        uMorphAmount: { value: 3.0 },\n        uMorphSpeed: { value: 0.05 },\n        uBands: { value: 2.0 },\n        uThickness: { value: 0.01 },\n        uScale: { value: 1.0 },\n        uPixelSize: { value: 1.0 },\n        uGlow: { value: 0.5 },\n        uColorMode: { value: 0.0 },\n        uContrast: { value: 3.0 },\n        uBrightness: { value: 1.0 },\n        uFillBands: { value: 0.0 },\n        uOpacity: { value: 1.0 },\n        uLightMode: { value: 0.0 },\n        uGrain: { value: 1.0 },\n        uGrainIntensity: { value: 0.05 },\n        uLow: { value: new Float32Array([1, 1, 1]) },\n        uMid: { value: new Float32Array([1, 1, 1]) },\n        uHigh: { value: new Float32Array([1, 1, 1]) },\n        uMouse: { value: new Float32Array([0.5, 0.5]) },\n        uMouseEnabled: { value: 1.0 },\n        uMouseRadius: { value: 0.3 },\n        uMouseStrength: { value: 0.4 },\n        uMouseActive: { value: 0.0 },\n        uCtrlA: { value: new Float32Array([0, 0, 0, 0]) },\n        uCtrlB: { value: new Float32Array([0, 0, 0, 0]) },\n        uCtrlC: { value: new Float32Array([0, 0, 0, 0]) },\n        uCtrlD: { value: new Float32Array([0, 0, 0, 0]) }\n      }\n    });\n\n    const mesh = new Mesh(gl, { geometry, program });\n    ctxMap.set(container, { renderer, program, mesh });\n\n    const setSize = () => {\n      const rect = container.getBoundingClientRect();\n      const w = Math.max(1, Math.floor(rect.width));\n      const h = Math.max(1, Math.floor(rect.height));\n      renderer.setSize(w, h);\n      const res = program.uniforms.iResolution.value as Float32Array;\n      res[0] = gl.drawingBufferWidth;\n      res[1] = gl.drawingBufferHeight;\n      renderer.render({ scene: mesh });\n    };\n\n    const ro = new ResizeObserver(setSize);\n    ro.observe(container);\n    setSize();\n\n    const currentMouse: [number, number] = [0.5, 0.5];\n    const targetMouse: [number, number] = [0.5, 0.5];\n    let mouseActive = 0;\n    let mouseActiveTarget = 0;\n\n    const onMouseMove = (e: MouseEvent) => {\n      const rect = canvas.getBoundingClientRect();\n      targetMouse[0] = (e.clientX - rect.left) / rect.width;\n      targetMouse[1] = 1.0 - (e.clientY - rect.top) / rect.height;\n      mouseActiveTarget = 1;\n    };\n    const onMouseLeave = () => {\n      mouseActiveTarget = 0;\n    };\n    canvas.addEventListener('mousemove', onMouseMove);\n    canvas.addEventListener('mouseleave', onMouseLeave);\n\n    const ctrlArrays = [\n      program.uniforms.uCtrlA.value as Float32Array,\n      program.uniforms.uCtrlB.value as Float32Array,\n      program.uniforms.uCtrlC.value as Float32Array,\n      program.uniforms.uCtrlD.value as Float32Array\n    ];\n\n    let raf = 0;\n    let isVisible = true;\n    let isPageVisible = !document.hidden;\n    const t0 = performance.now();\n\n    const loop = (t: number) => {\n      const time = (t - t0) * 0.001;\n      const u = program.uniforms;\n      u.iTime.value = time;\n\n      const ma = u.uMorphAmount.value as number;\n      const sp = u.uSpeed.value as number;\n      const msp = u.uMorphSpeed.value as number;\n      for (let g = 0; g < 4; g++) {\n        const arr = ctrlArrays[g];\n        const idx = CTRL_INDICES[g];\n        for (let j = 0; j < 4; j++) {\n          const i = idx[j];\n          arr[j] = ma * Math.sin(time * sp * Math.sin(i * msp) + i);\n        }\n      }\n\n      currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0]);\n      currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1]);\n      const m = program.uniforms.uMouse.value as Float32Array;\n      m[0] = currentMouse[0];\n      m[1] = currentMouse[1];\n\n      mouseActive += 0.05 * (mouseActiveTarget - mouseActive);\n      u.uMouseActive.value = mouseActive;\n\n      renderer.render({ scene: mesh });\n      raf = requestAnimationFrame(loop);\n    };\n\n    const tryStart = () => {\n      if (isVisible && isPageVisible && raf === 0) raf = requestAnimationFrame(loop);\n    };\n    const tryStop = () => {\n      if (raf !== 0) {\n        cancelAnimationFrame(raf);\n        raf = 0;\n      }\n    };\n\n    const io = new IntersectionObserver(\n      ([entry]) => {\n        isVisible = entry.isIntersecting;\n        isVisible ? tryStart() : tryStop();\n      },\n      { threshold: 0 }\n    );\n    io.observe(container);\n\n    const onVisibility = () => {\n      isPageVisible = !document.hidden;\n      isPageVisible ? tryStart() : tryStop();\n    };\n    document.addEventListener('visibilitychange', onVisibility);\n\n    tryStart();\n\n    return () => {\n      tryStop();\n      ro.disconnect();\n      io.disconnect();\n      document.removeEventListener('visibilitychange', onVisibility);\n      canvas.removeEventListener('mousemove', onMouseMove);\n      canvas.removeEventListener('mouseleave', onMouseLeave);\n      ctxMap.delete(container);\n      try {\n        container.removeChild(canvas);\n      } catch {}\n      gl.getExtension('WEBGL_lose_context')?.loseContext();\n    };\n  }, []);\n\n  useEffect(() => {\n    const container = containerRef.current;\n    if (!container) return;\n    const ctx = ctxMap.get(container);\n    if (!ctx) return;\n    const { program } = ctx;\n    const u = program.uniforms;\n\n    u.uSpeed.value = speed;\n    u.uMorphAmount.value = morphAmount;\n    u.uMorphSpeed.value = morphSpeed;\n    u.uBands.value = bands;\n    u.uThickness.value = thickness;\n    u.uScale.value = scale;\n    u.uPixelSize.value = pixelSize;\n    u.uGlow.value = glow;\n    u.uColorMode.value = colorModeToFloat(colorMode);\n    u.uContrast.value = contrast;\n    u.uBrightness.value = brightness;\n    u.uFillBands.value = fillBands ? 1.0 : 0.0;\n    u.uOpacity.value = opacity;\n    u.uLightMode.value = lightMode ? 1.0 : 0.0;\n    u.uGrain.value = grain ? 1.0 : 0.0;\n    u.uGrainIntensity.value = grainIntensity;\n    u.uLow.value = new Float32Array(hexToRgb(lowColor));\n    u.uMid.value = new Float32Array(hexToRgb(midColor));\n    u.uHigh.value = new Float32Array(hexToRgb(highColor));\n    u.uMouseEnabled.value = mouseInteraction ? 1.0 : 0.0;\n    u.uMouseRadius.value = mouseRadius;\n    u.uMouseStrength.value = mouseStrength;\n  }, [\n    lowColor,\n    midColor,\n    highColor,\n    speed,\n    morphAmount,\n    morphSpeed,\n    bands,\n    thickness,\n    scale,\n    pixelSize,\n    glow,\n    colorMode,\n    contrast,\n    brightness,\n    fillBands,\n    opacity,\n    grain,\n    grainIntensity,\n    mouseInteraction,\n    mouseRadius,\n    mouseStrength,\n    lightMode\n  ]);\n\n  return <div ref={containerRef} className={`relative h-full w-full overflow-hidden ${className}`.trim()} />;\n};\n\nexport default Topography;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"ogl@^1.0.11"
	]
}