{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "backgrounds-topography",
  "type": "registry:ui",
  "title": "Topography Background",
  "description": "A background component featuring living contour lines that rise under the cursor and ripple on click.",
  "dependencies": [
    "@animate-ui/react",
    "cn",
    "motion"
  ],
  "files": [
    {
      "path": "registry/backgrounds/topography/index.tsx",
      "type": "registry:ui",
      "target": "@components/animate-ui/backgrounds/topography.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport {\n  animate,\n  cancelFrame,\n  frame,\n  motionValue,\n  useInView,\n  useMotionValue,\n  useSpring,\n  type MotionValue,\n  type SpringOptions,\n} from 'motion/react';\n\nimport {\n  ReducedMotionConfig,\n  useShouldReduceMotion,\n} from '@animate-ui/react/utils/motion';\nimport { usePageVisible } from '@animate-ui/react/utils/use-page-visible';\nimport { cn } from 'cn';\n\nconst CELL = 10;\nconst RELAX = 2;\nconst MIN_FRAME = 10;\nconst FEATURE = 280;\nconst RANGE = 0.95;\nconst HILL_RADIUS = 90;\nconst RIPPLE_WIDTH = 60;\n\nconst PERMUTATION = (() => {\n  const table = Array.from({ length: 256 }, (_, i) => i);\n  let seed = 1013;\n  for (let i = 255; i > 0; i--) {\n    seed = (seed * 16807) % 2147483647;\n    const j = seed % (i + 1);\n    [table[i], table[j]] = [table[j]!, table[i]!];\n  }\n  return Uint8Array.from([...table, ...table]);\n})();\n\nfunction fade(t: number) {\n  return t * t * t * (t * (t * 6 - 15) + 10);\n}\n\nfunction lerp(a: number, b: number, t: number) {\n  return a + (b - a) * t;\n}\n\nfunction grad(hash: number, x: number, y: number, z: number) {\n  const h = hash & 15;\n  const u = h < 8 ? x : y;\n  const v = h < 4 ? y : h === 12 || h === 14 ? x : z;\n  return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);\n}\n\nfunction noise(x: number, y: number, z: number) {\n  const p = PERMUTATION;\n  const X = Math.floor(x) & 255;\n  const Y = Math.floor(y) & 255;\n  const Z = Math.floor(z) & 255;\n  x -= Math.floor(x);\n  y -= Math.floor(y);\n  z -= Math.floor(z);\n  const u = fade(x);\n  const v = fade(y);\n  const w = fade(z);\n  const A = p[X]! + Y;\n  const AA = p[A]! + Z;\n  const AB = p[A + 1]! + Z;\n  const B = p[X + 1]! + Y;\n  const BA = p[B]! + Z;\n  const BB = p[B + 1]! + Z;\n  return lerp(\n    lerp(\n      lerp(grad(p[AA]!, x, y, z), grad(p[BA]!, x - 1, y, z), u),\n      lerp(grad(p[AB]!, x, y - 1, z), grad(p[BB]!, x - 1, y - 1, z), u),\n      v,\n    ),\n    lerp(\n      lerp(grad(p[AA + 1]!, x, y, z - 1), grad(p[BA + 1]!, x - 1, y, z - 1), u),\n      lerp(\n        grad(p[AB + 1]!, x, y - 1, z - 1),\n        grad(p[BB + 1]!, x - 1, y - 1, z - 1),\n        u,\n      ),\n      v,\n    ),\n    w,\n  );\n}\n\ntype Contours = {\n  segments: Float64Array;\n  count: number;\n  used: Uint8Array;\n  first: Int32Array;\n  second: Int32Array;\n  points: Float64Array;\n  scratch: Float64Array;\n};\n\nfunction createContours(edges: number): Contours {\n  return {\n    segments: new Float64Array(0),\n    count: 0,\n    used: new Uint8Array(0),\n    first: new Int32Array(edges).fill(-1),\n    second: new Int32Array(edges).fill(-1),\n    points: new Float64Array(0),\n    scratch: new Float64Array(0),\n  };\n}\n\nfunction addSegment(\n  c: Contours,\n  edgeA: number,\n  xA: number,\n  yA: number,\n  edgeB: number,\n  xB: number,\n  yB: number,\n) {\n  if ((c.count + 1) * 6 > c.segments.length) {\n    const capacity = Math.max(256, c.count * 2);\n    const segments = new Float64Array(capacity * 6);\n    segments.set(c.segments);\n    c.segments = segments;\n    c.used = new Uint8Array(capacity);\n    c.points = new Float64Array(capacity * 4 + 16);\n    c.scratch = new Float64Array(capacity * 4 + 16);\n  }\n  const o = c.count * 6;\n  const s = c.segments;\n  s[o] = edgeA;\n  s[o + 1] = xA;\n  s[o + 2] = yA;\n  s[o + 3] = edgeB;\n  s[o + 4] = xB;\n  s[o + 5] = yB;\n  c.count++;\n}\n\nfunction link(c: Contours, edge: number, segment: number) {\n  if (c.first[edge]! < 0) c.first[edge] = segment;\n  else c.second[edge] = segment;\n}\n\nfunction neighbour(c: Contours, edge: number, segment: number) {\n  const a = c.first[edge]!;\n  const next = a === segment ? c.second[edge]! : a;\n  return next >= 0 && !c.used[next] ? next : -1;\n}\n\nfunction traceLevel(path: Path2D, c: Contours) {\n  const s = c.segments;\n  const pts = c.points;\n  for (let i = 0; i < c.count; i++) {\n    c.used[i] = 0;\n    link(c, s[i * 6]!, i);\n    link(c, s[i * 6 + 3]!, i);\n  }\n  const middle = (pts.length >> 2) << 1;\n  for (let start = 0; start < c.count; start++) {\n    if (c.used[start]) continue;\n    c.used[start] = 1;\n    const o = start * 6;\n    let head = middle;\n    let tail = middle;\n    pts[tail++] = s[o + 1]!;\n    pts[tail++] = s[o + 2]!;\n    pts[tail++] = s[o + 4]!;\n    pts[tail++] = s[o + 5]!;\n    let current = start;\n    let edge = s[o + 3]!;\n    for (;;) {\n      const next = neighbour(c, edge, current);\n      if (next < 0) break;\n      c.used[next] = 1;\n      const n = next * 6;\n      const far = s[n] === edge ? 3 : 0;\n      pts[tail++] = s[n + far + 1]!;\n      pts[tail++] = s[n + far + 2]!;\n      edge = s[n + far]!;\n      current = next;\n    }\n    const closed = edge === s[o] && tail - head > 6;\n    if (closed) tail -= 2;\n    else {\n      current = start;\n      edge = s[o]!;\n      for (;;) {\n        const next = neighbour(c, edge, current);\n        if (next < 0) break;\n        c.used[next] = 1;\n        const n = next * 6;\n        const far = s[n] === edge ? 3 : 0;\n        pts[--head] = s[n + far + 2]!;\n        pts[--head] = s[n + far + 1]!;\n        edge = s[n + far]!;\n        current = next;\n      }\n    }\n    smooth(path, pts, head, tail, closed, c.scratch);\n  }\n  for (let i = 0; i < c.count; i++) {\n    const from = s[i * 6]!;\n    const to = s[i * 6 + 3]!;\n    c.first[from] = c.second[from] = -1;\n    c.first[to] = c.second[to] = -1;\n  }\n  c.count = 0;\n}\n\nfunction relax(\n  pts: Float64Array,\n  start: number,\n  n: number,\n  closed: boolean,\n  scratch: Float64Array,\n) {\n  for (let pass = 0; pass < RELAX; pass++) {\n    scratch.set(pts.subarray(start, start + n * 2));\n    for (let i = closed ? 0 : 1; i < (closed ? n : n - 1); i++) {\n      const a = ((i - 1 + n) % n) * 2;\n      const b = ((i + 1) % n) * 2;\n      pts[start + i * 2] =\n        (scratch[a]! + 2 * scratch[i * 2]! + scratch[b]!) / 4;\n      pts[start + i * 2 + 1] =\n        (scratch[a + 1]! + 2 * scratch[i * 2 + 1]! + scratch[b + 1]!) / 4;\n    }\n  }\n}\n\nfunction at(pts: Float64Array, start: number, n: number, i: number) {\n  return start + ((i + n) % n) * 2;\n}\n\nfunction smooth(\n  path: Path2D,\n  pts: Float64Array,\n  start: number,\n  end: number,\n  closed: boolean,\n  scratch: Float64Array,\n) {\n  const n = (end - start) / 2;\n  if (n < 2) return;\n  if (n > 3) relax(pts, start, n, closed, scratch);\n  if (closed && n > 2) {\n    const last = at(pts, start, n, -1);\n    path.moveTo(\n      (pts[last]! + pts[start]!) / 2,\n      (pts[last + 1]! + pts[start + 1]!) / 2,\n    );\n    for (let i = 0; i < n; i++) {\n      const p = at(pts, start, n, i);\n      const q = at(pts, start, n, i + 1);\n      path.quadraticCurveTo(\n        pts[p]!,\n        pts[p + 1]!,\n        (pts[p]! + pts[q]!) / 2,\n        (pts[p + 1]! + pts[q + 1]!) / 2,\n      );\n    }\n    return;\n  }\n  path.moveTo(pts[start]!, pts[start + 1]!);\n  for (let i = 1; i < n - 1; i++) {\n    const p = start + i * 2;\n    path.quadraticCurveTo(\n      pts[p]!,\n      pts[p + 1]!,\n      (pts[p]! + pts[p + 2]!) / 2,\n      (pts[p + 1]! + pts[p + 3]!) / 2,\n    );\n  }\n  path.lineTo(pts[end - 2]!, pts[end - 1]!);\n}\n\nconst VERTEX = `\nattribute vec2 position;\nvoid main() {\n  gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst FRAGMENT = `\n#extension GL_OES_standard_derivatives : enable\nprecision highp float;\nuniform sampler2D permutation;\nuniform vec2 size;\nuniform float dpr;\nuniform float feature;\nuniform float z;\nuniform float step;\nuniform float range;\nuniform float amount;\nuniform float lift;\nuniform float hillRadius;\nuniform vec2 hill;\nuniform vec4 ripples[4];\nuniform float reach;\nuniform float rippleWidth;\nuniform vec3 color;\n\nfloat perm(float i) {\n  return floor(texture2D(permutation, vec2((mod(i, 256.0) + 0.5) / 256.0, 0.5)).r * 255.0 + 0.5);\n}\n\nfloat fade(float t) {\n  return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);\n}\n\nfloat grad(float hash, float x, float y, float z) {\n  float h = mod(hash, 16.0);\n  float u = h < 8.0 ? x : y;\n  float v = h < 4.0 ? y : (h == 12.0 || h == 14.0 ? x : z);\n  return (mod(h, 2.0) == 0.0 ? u : -u) + (mod(floor(h / 2.0), 2.0) == 0.0 ? v : -v);\n}\n\nfloat noise(vec3 p) {\n  vec3 cell = mod(floor(p), 256.0);\n  vec3 f = p - floor(p);\n  float u = fade(f.x);\n  float v = fade(f.y);\n  float w = fade(f.z);\n  float A = perm(cell.x) + cell.y;\n  float AA = perm(A) + cell.z;\n  float AB = perm(A + 1.0) + cell.z;\n  float B = perm(cell.x + 1.0) + cell.y;\n  float BA = perm(B) + cell.z;\n  float BB = perm(B + 1.0) + cell.z;\n  return mix(\n    mix(\n      mix(grad(perm(AA), f.x, f.y, f.z), grad(perm(BA), f.x - 1.0, f.y, f.z), u),\n      mix(grad(perm(AB), f.x, f.y - 1.0, f.z), grad(perm(BB), f.x - 1.0, f.y - 1.0, f.z), u),\n      v\n    ),\n    mix(\n      mix(grad(perm(AA + 1.0), f.x, f.y, f.z - 1.0), grad(perm(BA + 1.0), f.x - 1.0, f.y, f.z - 1.0), u),\n      mix(grad(perm(AB + 1.0), f.x, f.y - 1.0, f.z - 1.0), grad(perm(BB + 1.0), f.x - 1.0, f.y - 1.0, f.z - 1.0), u),\n      v\n    ),\n    w\n  );\n}\n\nvoid main() {\n  vec2 point = vec2(gl_FragCoord.x, size.y - gl_FragCoord.y) / dpr;\n  vec2 n = point / feature;\n  float value = noise(vec3(n, z)) * 1.1 + noise(vec3(n * 2.1, z * 1.7)) * 0.45;\n  if (lift > 0.0) value += lift * exp(-length(point - hill) / hillRadius);\n  for (int i = 0; i < 4; i++) {\n    vec4 ripple = ripples[i];\n    if (ripple.w > 0.5) {\n      float d = length(point - ripple.xy) - ripple.z * reach;\n      value += 0.9 * (1.0 - ripple.z) * cos(d / rippleWidth * 1.8) * exp(-(d * d) / (2.0 * rippleWidth * rippleWidth));\n    }\n  }\n  value *= amount;\n  float level = (value + range) / step;\n  float nearest = floor(level + 0.5);\n  float slope = length(vec2(dFdx(level), dFdy(level)));\n  float pixels = abs(level - nearest) / max(slope, 1e-6);\n  bool major = mod(nearest, 5.0) == 0.0;\n  float width = (major ? 1.5 : 1.0) * dpr;\n  float alpha = slope > 1e-6 ? clamp(width * 0.5 + 0.5 - pixels, 0.0, 1.0) * (major ? 0.85 : 0.4) : 0.0;\n  gl_FragColor = vec4(color * alpha, alpha);\n}\n`;\n\ntype GlFrame = {\n  width: number;\n  height: number;\n  dpr: number;\n  feature: number;\n  z: number;\n  step: number;\n  amount: number;\n  lift: number;\n  hill: [number, number];\n  ripples: Float32Array;\n  reach: number;\n  color: [number, number, number];\n};\n\nfunction compile(gl: WebGLRenderingContext, type: number, source: string) {\n  const shader = gl.createShader(type);\n  if (!shader) return null;\n  gl.shaderSource(shader, source);\n  gl.compileShader(shader);\n  return gl.getShaderParameter(shader, gl.COMPILE_STATUS) ? shader : null;\n}\n\nfunction createGlRenderer(gl: WebGLRenderingContext) {\n  if (!gl.getExtension('OES_standard_derivatives')) return null;\n  const vertex = compile(gl, gl.VERTEX_SHADER, VERTEX);\n  const fragment = compile(gl, gl.FRAGMENT_SHADER, FRAGMENT);\n  const program = gl.createProgram();\n  if (!vertex || !fragment || !program) return null;\n  gl.attachShader(program, vertex);\n  gl.attachShader(program, fragment);\n  gl.linkProgram(program);\n  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return null;\n  gl.useProgram(program);\n\n  const buffer = gl.createBuffer();\n  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n  gl.bufferData(\n    gl.ARRAY_BUFFER,\n    new Float32Array([-1, -1, 3, -1, -1, 3]),\n    gl.STATIC_DRAW,\n  );\n  const position = gl.getAttribLocation(program, 'position');\n  gl.enableVertexAttribArray(position);\n  gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);\n\n  const texture = gl.createTexture();\n  gl.bindTexture(gl.TEXTURE_2D, texture);\n  gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);\n  gl.texImage2D(\n    gl.TEXTURE_2D,\n    0,\n    gl.LUMINANCE,\n    256,\n    1,\n    0,\n    gl.LUMINANCE,\n    gl.UNSIGNED_BYTE,\n    PERMUTATION.subarray(0, 256),\n  );\n  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);\n  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);\n  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);\n  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);\n\n  const at = (name: string) => gl.getUniformLocation(program, name);\n  const uniforms = {\n    permutation: at('permutation'),\n    size: at('size'),\n    dpr: at('dpr'),\n    feature: at('feature'),\n    z: at('z'),\n    step: at('step'),\n    range: at('range'),\n    amount: at('amount'),\n    lift: at('lift'),\n    hillRadius: at('hillRadius'),\n    hill: at('hill'),\n    ripples: at('ripples'),\n    reach: at('reach'),\n    rippleWidth: at('rippleWidth'),\n    color: at('color'),\n  };\n  gl.uniform1i(uniforms.permutation, 0);\n  gl.uniform1f(uniforms.range, RANGE);\n  gl.uniform1f(uniforms.hillRadius, HILL_RADIUS);\n  gl.uniform1f(uniforms.rippleWidth, RIPPLE_WIDTH);\n\n  return (frame: GlFrame) => {\n    const w = Math.max(1, Math.round(frame.width * frame.dpr));\n    const h = Math.max(1, Math.round(frame.height * frame.dpr));\n    gl.viewport(0, 0, w, h);\n    gl.uniform2f(uniforms.size, w, h);\n    gl.uniform1f(uniforms.dpr, frame.dpr);\n    gl.uniform1f(uniforms.feature, frame.feature);\n    gl.uniform1f(uniforms.z, frame.z);\n    gl.uniform1f(uniforms.step, frame.step);\n    gl.uniform1f(uniforms.amount, frame.amount);\n    gl.uniform1f(uniforms.lift, frame.lift);\n    gl.uniform2f(uniforms.hill, frame.hill[0], frame.hill[1]);\n    gl.uniform4fv(uniforms.ripples, frame.ripples);\n    gl.uniform1f(uniforms.reach, frame.reach);\n    gl.uniform3f(uniforms.color, ...frame.color);\n    gl.clearColor(0, 0, 0, 0);\n    gl.clear(gl.COLOR_BUFFER_BIT);\n    gl.drawArrays(gl.TRIANGLES, 0, 3);\n  };\n}\n\nfunction toRgb(color: string): [number, number, number] {\n  const probe = document.createElement('canvas').getContext('2d');\n  if (!probe) return [0.45, 0.45, 0.45];\n  probe.fillStyle = color;\n  probe.fillRect(0, 0, 1, 1);\n  const [r, g, b] = probe.getImageData(0, 0, 1, 1).data;\n  return [r! / 255, g! / 255, b! / 255];\n}\n\ntype Ripple = { x: number; y: number; progress: MotionValue<number> };\n\ntype TopographyBackgroundProps = React.ComponentProps<'div'> & {\n  lineColor?: string;\n  levels?: number;\n  speed?: number;\n  scale?: number;\n  peak?: number;\n  interactive?: boolean;\n  transition?: SpringOptions;\n};\n\nfunction TopographyBackgroundContent({\n  ref,\n  lineColor,\n  levels = 16,\n  speed = 1,\n  scale = 1,\n  peak = 1,\n  interactive = true,\n  transition = { stiffness: 120, damping: 20 },\n  className,\n  children,\n  onPointerMove,\n  onPointerLeave,\n  onPointerDown,\n  ...props\n}: TopographyBackgroundProps) {\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const canvasRef = React.useRef<HTMLCanvasElement>(null);\n  React.useImperativeHandle(ref, () => containerRef.current as HTMLDivElement);\n\n  const inView = useInView(containerRef);\n  const pageVisible = usePageVisible();\n  const reduce = useShouldReduceMotion();\n  const playing = inView && pageVisible && !reduce;\n\n  const pointerX = useMotionValue(0);\n  const pointerY = useMotionValue(0);\n  const raised = useMotionValue(0);\n  const hillX = useSpring(pointerX, transition);\n  const hillY = useSpring(pointerY, transition);\n  const hill = useSpring(raised, { stiffness: 80, damping: 18 });\n  const rise = useMotionValue(reduce ? 1 : 0);\n  const ripplesRef = React.useRef<Ripple[]>([]);\n  const drawRef = React.useRef<(time: number, looping?: boolean) => void>(\n    () => {},\n  );\n  const timeRef = React.useRef(0);\n  const paceRef = React.useRef(MIN_FRAME);\n\n  const options = { lineColor, levels, speed, scale, peak };\n  const optionsRef = React.useRef(options);\n  React.useLayoutEffect(() => {\n    optionsRef.current = options;\n  });\n\n  React.useEffect(() => {\n    if (reduce) {\n      rise.jump(1);\n      return;\n    }\n    const controls = animate(rise, 1, {\n      duration: 1.8,\n      ease: [0.16, 1, 0.3, 1],\n    });\n    return () => controls.stop();\n  }, [reduce, rise]);\n\n  React.useEffect(() => {\n    const canvas = canvasRef.current;\n    const container = containerRef.current;\n    if (!canvas || !container) return;\n\n    const gl = canvas.getContext('webgl', {\n      premultipliedAlpha: true,\n      preserveDrawingBuffer: true,\n      antialias: false,\n    });\n    const render = gl ? createGlRenderer(gl) : null;\n    if (render) {\n      paceRef.current = 0;\n      let width = 0;\n      let height = 0;\n      let dpr = 1;\n      let frames = 0;\n      let textColor = '';\n      let rgb: [number, number, number] = [0.45, 0.45, 0.45];\n      const ripples = new Float32Array(16);\n\n      const resize = () => {\n        const rect = container.getBoundingClientRect();\n        width = rect.width;\n        height = rect.height;\n        dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));\n        canvas.width = Math.max(1, Math.round(width * dpr));\n        canvas.height = Math.max(1, Math.round(height * dpr));\n        canvas.style.width = `${width}px`;\n        canvas.style.height = `${height}px`;\n        if (hill.get() === 0) {\n          pointerX.jump(width / 2);\n          pointerY.jump(height / 2);\n          hillX.jump(width / 2);\n          hillY.jump(height / 2);\n        }\n      };\n\n      const draw = (time: number, looping = false) => {\n        if (width === 0 || height === 0) return;\n        const { lineColor, levels, speed, scale, peak } = optionsRef.current;\n        if (!looping || frames++ % 30 === 0) {\n          const color =\n            lineColor ?? (getComputedStyle(container).color || '#737373');\n          if (color !== textColor) {\n            textColor = color;\n            rgb = toRgb(color);\n          }\n        }\n        ripples.fill(0);\n        ripplesRef.current.slice(-4).forEach((ripple, i) => {\n          ripples[i * 4] = ripple.x;\n          ripples[i * 4 + 1] = ripple.y;\n          ripples[i * 4 + 2] = ripple.progress.get();\n          ripples[i * 4 + 3] = 1;\n        });\n        render({\n          width,\n          height,\n          dpr,\n          feature: FEATURE * scale,\n          z: time * 0.00005 * speed,\n          step: (2 * RANGE) / Math.max(2, Math.round(levels)),\n          amount: rise.get(),\n          lift: hill.get() * peak * 1.5,\n          hill: [hillX.get(), hillY.get()],\n          ripples,\n          reach: Math.hypot(width, height) * 0.8,\n          color: rgb,\n        });\n      };\n\n      drawRef.current = draw;\n      resize();\n      draw(timeRef.current);\n      const observer = new ResizeObserver(() => {\n        resize();\n        draw(timeRef.current);\n      });\n      observer.observe(container);\n      return () => {\n        observer.disconnect();\n        drawRef.current = () => {};\n      };\n    }\n\n    paceRef.current = MIN_FRAME;\n    const context = canvas.getContext('2d');\n    if (!context) return;\n\n    let width = 0;\n    let height = 0;\n    let dpr = 1;\n    let cols = 0;\n    let rows = 0;\n    let field = new Float32Array(0);\n    let levelLow = new Int16Array(0);\n    let levelHigh = new Int16Array(0);\n    let bucketStart = new Int32Array(0);\n    let bucketCells = new Int32Array(0);\n    let contours = createContours(0);\n    let frames = 0;\n    let textColor = '#737373';\n\n    const resize = () => {\n      const rect = container.getBoundingClientRect();\n      width = rect.width;\n      height = rect.height;\n      dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));\n      canvas.width = Math.max(1, Math.round(width * dpr));\n      canvas.height = Math.max(1, Math.round(height * dpr));\n      canvas.style.width = `${width}px`;\n      canvas.style.height = `${height}px`;\n      cols = Math.ceil(width / CELL) + 1;\n      rows = Math.ceil(height / CELL) + 1;\n      field = new Float32Array((cols + 1) * (rows + 1));\n      levelLow = new Int16Array(rows * cols);\n      levelHigh = new Int16Array(rows * cols);\n      contours = createContours(2 * (rows + 1) * (cols + 1) + 4);\n      if (hill.get() === 0) {\n        pointerX.jump(width / 2);\n        pointerY.jump(height / 2);\n        hillX.jump(width / 2);\n        hillY.jump(height / 2);\n      }\n    };\n\n    const sample = (time: number) => {\n      const { speed, scale, peak } = optionsRef.current;\n      const size = FEATURE * scale;\n      const z = time * 0.00005 * speed;\n      const lift = hill.get() * peak * 1.5;\n      const hx = hillX.get();\n      const hy = hillY.get();\n      const ripples = ripplesRef.current;\n      const reach = Math.hypot(width, height) * 0.8;\n      const amount = rise.get();\n      let i = 0;\n      for (let row = 0; row <= rows; row++) {\n        const y = row * CELL;\n        for (let col = 0; col <= cols; col++) {\n          const x = col * CELL;\n          const nx = x / size;\n          const ny = y / size;\n          let value =\n            noise(nx, ny, z) * 1.1 + noise(nx * 2.1, ny * 2.1, z * 1.7) * 0.45;\n          const dx = x - hx;\n          const dy = y - hy;\n          if (lift > 0)\n            value += lift * Math.exp(-Math.hypot(dx, dy) / HILL_RADIUS);\n          for (const ripple of ripples) {\n            const p = ripple.progress.get();\n            const d = Math.hypot(x - ripple.x, y - ripple.y) - p * reach;\n            value +=\n              0.9 *\n              (1 - p) *\n              Math.cos((d / RIPPLE_WIDTH) * 1.8) *\n              Math.exp(-(d * d) / (2 * RIPPLE_WIDTH * RIPPLE_WIDTH));\n          }\n          field[i++] = value * amount;\n        }\n      }\n    };\n\n    const draw = (time: number, looping = false) => {\n      if (width === 0 || height === 0) return;\n      const { lineColor, levels } = optionsRef.current;\n      sample(time);\n      const count = Math.max(2, Math.round(levels));\n      const step = (2 * RANGE) / count;\n      const minor = new Path2D();\n      const major = new Path2D();\n      const stride = cols + 1;\n      let lowest = Infinity;\n      let highest = -Infinity;\n\n      for (let row = 0; row < rows; row++) {\n        for (let col = 0; col < cols; col++) {\n          const i = row * stride + col;\n          const a = field[i]!;\n          const b = field[i + 1]!;\n          const c = field[i + stride + 1]!;\n          const d = field[i + stride]!;\n          const first = Math.ceil((Math.min(a, b, c, d) + RANGE) / step);\n          const last = Math.floor((Math.max(a, b, c, d) + RANGE) / step);\n          levelLow[row * cols + col] = first;\n          levelHigh[row * cols + col] = last;\n          if (first < lowest) lowest = first;\n          if (last > highest) highest = last;\n        }\n      }\n\n      const span = highest - lowest + 1;\n      if (bucketStart.length < span + 1) bucketStart = new Int32Array(span + 1);\n      bucketStart.fill(0, 0, span + 1);\n      let entries = 0;\n      for (let cell = 0; cell < rows * cols; cell++)\n        for (let level = levelLow[cell]!; level <= levelHigh[cell]!; level++) {\n          bucketStart[level - lowest + 1]!++;\n          entries++;\n        }\n      for (let l = 0; l < span; l++) bucketStart[l + 1]! += bucketStart[l]!;\n      if (bucketCells.length < entries)\n        bucketCells = new Int32Array(entries * 2);\n      const fill = bucketStart.slice(0, span);\n      for (let cell = 0; cell < rows * cols; cell++)\n        for (let level = levelLow[cell]!; level <= levelHigh[cell]!; level++)\n          bucketCells[fill[level - lowest]!++] = cell;\n\n      for (let level = lowest; level <= highest; level++) {\n        const v = level * step - RANGE;\n        const end = bucketStart[level - lowest + 1]!;\n        for (let e = bucketStart[level - lowest]!; e < end; e++) {\n          {\n            const cell = bucketCells[e]!;\n            const row = (cell / cols) | 0;\n            const col = cell - row * cols;\n            const i = row * stride + col;\n            const a = field[i]!;\n            const b = field[i + 1]!;\n            const c = field[i + stride + 1]!;\n            const d = field[i + stride]!;\n            const code =\n              (a > v ? 8 : 0) |\n              (b > v ? 4 : 0) |\n              (c > v ? 2 : 0) |\n              (d > v ? 1 : 0);\n            if (code === 0 || code === 15) continue;\n            const x = col * CELL;\n            const y = row * CELL;\n            const top = x + ((v - a) / (b - a)) * CELL;\n            const right = y + ((v - b) / (c - b)) * CELL;\n            const bottom = x + ((v - d) / (c - d)) * CELL;\n            const left = y + ((v - a) / (d - a)) * CELL;\n            const T = i * 2;\n            const B = T + stride * 2;\n            const L = T + 1;\n            const R = T + 3;\n            const k = contours;\n            switch (code) {\n              case 1:\n              case 14:\n                addSegment(k, L, x, left, B, bottom, y + CELL);\n                break;\n              case 2:\n              case 13:\n                addSegment(k, B, bottom, y + CELL, R, x + CELL, right);\n                break;\n              case 3:\n              case 12:\n                addSegment(k, L, x, left, R, x + CELL, right);\n                break;\n              case 4:\n              case 11:\n                addSegment(k, T, top, y, R, x + CELL, right);\n                break;\n              case 6:\n              case 9:\n                addSegment(k, T, top, y, B, bottom, y + CELL);\n                break;\n              case 7:\n              case 8:\n                addSegment(k, L, x, left, T, top, y);\n                break;\n              case 5:\n              case 10: {\n                const center = (a + b + c + d) / 4 > v;\n                if ((code === 5) === center) {\n                  addSegment(k, L, x, left, T, top, y);\n                  addSegment(k, B, bottom, y + CELL, R, x + CELL, right);\n                } else {\n                  addSegment(k, L, x, left, B, bottom, y + CELL);\n                  addSegment(k, T, top, y, R, x + CELL, right);\n                }\n                break;\n              }\n            }\n          }\n        }\n        traceLevel(level % 5 === 0 ? major : minor, contours);\n      }\n\n      context.setTransform(dpr, 0, 0, dpr, 0, 0);\n      context.clearRect(0, 0, width, height);\n      if (!looping || frames++ % 30 === 0)\n        textColor = getComputedStyle(container).color || '#737373';\n      context.strokeStyle = lineColor ?? textColor;\n      context.lineCap = 'round';\n      context.globalAlpha = 0.4;\n      context.lineWidth = 1;\n      context.stroke(minor);\n      context.globalAlpha = 0.85;\n      context.lineWidth = 1.5;\n      context.stroke(major);\n      context.globalAlpha = 1;\n    };\n\n    drawRef.current = draw;\n    resize();\n    draw(timeRef.current);\n    const observer = new ResizeObserver(() => {\n      resize();\n      draw(timeRef.current);\n    });\n    observer.observe(container);\n    return () => {\n      observer.disconnect();\n      drawRef.current = () => {};\n    };\n  }, [hill, hillX, hillY, pointerX, pointerY, rise]);\n\n  React.useEffect(() => {\n    drawRef.current(timeRef.current);\n  }, [lineColor, levels, scale, peak]);\n\n  React.useEffect(() => {\n    if (!playing) return;\n    let waited = 0;\n    const tick = ({ delta }: { delta: number }) => {\n      waited += delta;\n      if (waited < paceRef.current) return;\n      timeRef.current += Math.min(waited, 64);\n      waited = 0;\n      drawRef.current(timeRef.current, true);\n    };\n    frame.render(tick, true);\n    return () => cancelFrame(tick);\n  }, [playing]);\n\n  const point = (event: React.PointerEvent<HTMLDivElement>) => {\n    const rect = event.currentTarget.getBoundingClientRect();\n    return { x: event.clientX - rect.left, y: event.clientY - rect.top };\n  };\n\n  return (\n    <div\n      ref={containerRef}\n      data-slot=\"topography-background\"\n      className={cn('relative size-full overflow-hidden', className)}\n      {...props}\n      onPointerMove={(event) => {\n        onPointerMove?.(event);\n        if (!interactive || reduce) return;\n        const { x, y } = point(event);\n        pointerX.set(x);\n        pointerY.set(y);\n        raised.set(1);\n      }}\n      onPointerLeave={(event) => {\n        onPointerLeave?.(event);\n        raised.set(0);\n      }}\n      onPointerDown={(event) => {\n        onPointerDown?.(event);\n        if (!interactive || reduce) return;\n        const { x, y } = point(event);\n        pointerX.set(x);\n        pointerY.set(y);\n        const ripple: Ripple = { x, y, progress: motionValue(0) };\n        ripplesRef.current = [...ripplesRef.current.slice(-3), ripple];\n        animate(ripple.progress, 1, {\n          duration: 2.6,\n          ease: [0.25, 0.4, 0.45, 1],\n        }).then(() => {\n          ripplesRef.current = ripplesRef.current.filter((r) => r !== ripple);\n        });\n      }}\n    >\n      <canvas\n        ref={canvasRef}\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute inset-0\"\n      />\n      {children}\n    </div>\n  );\n}\n\nfunction TopographyBackground(props: TopographyBackgroundProps) {\n  return (\n    <ReducedMotionConfig>\n      <TopographyBackgroundContent {...props} />\n    </ReducedMotionConfig>\n  );\n}\n\nexport { TopographyBackground, type TopographyBackgroundProps };\n"
    }
  ]
}