# Hero 16

> Hero 16 variant for block.

A shadcn/ui block from the [@uiable](https://uiable.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uiable/block-hero-16).

- Page, with a live preview: https://shadcn.exchange/r/uiable/block-hero-16
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uiable/block-hero-16-light-2615c3f27642a662.png
- Registry item: https://uiable.com/r/block-hero-16.json
- Categories: hero
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://uiable.com/r/block-hero-16.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `framer-motion`, `ogl`

## Files

### `src/components/uiable/blocks/hero/hero-16/hero-16.tsx`

```tsx
"use client"

import { useEffect, useRef } from "react"

// third-party
import { motion, useMotionValue, useMotionTemplate } from "framer-motion"
import { Renderer, Program, Mesh, Triangle } from "ogl"

type MoltenMetalColorMode = "molten" | "ember" | "frost"

interface MoltenMetalProps {
  color1?: string
  color2?: string
  color3?: string
  speed?: number
  scale?: number
  detail?: number
  glow?: number
  coreSize?: number
  swirl?: number
  fold?: number
  blackPoint?: number
  brightness?: number
  colorMode?: MoltenMetalColorMode
  grain?: boolean
  grainIntensity?: number
  mouseInteraction?: boolean
  mouseStrength?: number
  opacity?: number
  backgroundColor?: string
  lightMode?: boolean
  className?: string
}

const hexToRgb = (hex: string): [number, number, number] => {
  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
  if (!result) return [1, 1, 1]
  return [
    parseInt(result[1], 16) / 255,
    parseInt(result[2], 16) / 255,
    parseInt(result[3], 16) / 255,
  ]
}

const colorModeToFloat = (mode: MoltenMetalColorMode): number =>
  mode === "ember" ? 1 : mode === "frost" ? 2 : 0

const moltenMetalVertex = `#version 300 es
in vec2 position;
void main() {
  gl_Position = vec4(position, 0.0, 1.0);
}
`

const moltenMetalFragment = `#version 300 es
precision highp float;
uniform vec2 iResolution;
uniform float iTime;
uniform float uSpeed;
uniform float uScale;
uniform float uDetail;
uniform float uGlow;
uniform float uCoreSize;
uniform float uSwirl;
uniform float uFold;
uniform float uBlackPoint;
uniform float uBrightness;
uniform float uColorMode;
uniform float uGrain;
uniform float uGrainIntensity;
uniform float uOpacity;
uniform vec2 uMouse;
uniform float uMouseStrength;
uniform bool uEnableMouse;
uniform vec3 uColor1;
uniform vec3 uColor2;
uniform vec3 uColor3;
uniform vec3 uBackgroundColor;
uniform bool uLightMode;
out vec4 fragColor;

float hash(vec2 p) {
  return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
}

void main() {
  float time = iTime * uSpeed;
  vec2 p = uScale * ((gl_FragCoord.xy - 0.5 * iResolution.xy) / iResolution.y) - 0.5;

  vec2 drift = vec2(0.0);
  if (uEnableMouse) {
    drift = (uMouse - 0.5) * uMouseStrength * 2.0;
  }
  p += drift;

  vec2 i = p;
  float c = 0.0;
  float r = length(p + vec2(sin(time), sin(time * 0.3 + 5.0)) * 0.5);
  float d = length(p);
  float rot = d + time + p.x * uSwirl;

  float cosRot = cos(rot);
  mat2 warp = mat2(cos(rot - sin(time / 5.0)), sin(rot), -sin(cosRot - time), cosRot) * uFold;
  float glowCore = uGlow * uCoreSize;

  for (float n = 0.0; n < 8.0; n++) {
    if (n >= uDetail) break;
    p *= warp;
    float t = r - time / (n + 3.0);
    i -= p + vec2(cos(t - i.x - r) + sin(t + i.y), sin(t - i.y) + cos(t + i.x) + r);
    c += glowCore / length(vec2(sin(i.x + t), cos(i.y + t)));
  }

  c /= 6.0;

  float intensity = max(c - uBlackPoint, 0.0) * uBrightness;

  float g = clamp(intensity, 0.0, 1.0);

  float mid = 0.5;
  if (uColorMode > 1.5) {
    mid = 0.65;
  } else if (uColorMode > 0.5) {
    mid = 0.35;
  }

  vec3 col = mix(uColor1, uColor2, smoothstep(0.0, mid, g));
  col = mix(col, uColor3, smoothstep(mid, 1.0, g));

  float a = g;
  if (uGrain > 0.5) {
    float gr = hash(gl_FragCoord.xy + iTime);
    a += (gr - 0.5) * uGrainIntensity;
  }
  a = clamp(a, 0.0, 1.0) * uOpacity;
  if (uLightMode) {
    float signal = 1.0 - exp(-max(c, 0.0) * 6.5);
    float body = smoothstep(0.075, 0.68, signal);
    float ridge = smoothstep(0.42, 0.92, signal);

    vec3 lightCol = mix(uColor1, uColor2, smoothstep(0.08, 0.52, signal));
    lightCol = mix(lightCol, uColor3, smoothstep(0.52, 0.96, signal));
    lightCol = mix(lightCol, lightCol * 0.72, ridge * 0.24);

    float coverage = body * mix(0.2, 0.86, signal) * uOpacity;
    if (uGrain > 0.5) {
      float gr = hash(gl_FragCoord.xy + iTime);
      coverage += (gr - 0.5) * uGrainIntensity * body * 0.16;
    }
    fragColor = vec4(mix(uBackgroundColor, lightCol, clamp(coverage, 0.0, 0.92)), 1.0);
  } else {
    fragColor = vec4(col * a, a);
  }
}
`

type MoltenMetalCtx = {
  renderer: InstanceType<typeof Renderer>
  program: InstanceType<typeof Program>
  mesh: InstanceType<typeof Mesh>
}
const moltenMetalCtxMap = new WeakMap<HTMLDivElement, MoltenMetalCtx>()

// Used only by this block (hero-16).
function MoltenMetal({
  color1 = "#5227FF",
  color2 = "#FF9FFC",
  color3 = "#FFFFFF",
  speed = 0.35,
  scale = 4,
  detail = 3,
  glow = 1.6,
  coreSize = 0.1,
  swirl = 1,
  fold = -0.2,
  blackPoint = 0.05,
  brightness = 1.3,
  colorMode = "molten",
  grain = true,
  grainIntensity = 0.05,
  mouseInteraction = true,
  mouseStrength = 0.3,
  opacity = 1.0,
  backgroundColor = "#FFFFFF",
  lightMode = false,
  className = "",
}: MoltenMetalProps) {
  const containerRef = useRef<HTMLDivElement | null>(null)

  useEffect(() => {
    const container = containerRef.current
    if (!container) return

    const renderer = new Renderer({
      webgl: 2,
      alpha: true,
      premultipliedAlpha: true,
      antialias: false,
      dpr: Math.min(window.devicePixelRatio || 1, 2),
    })

    const gl = renderer.gl
    gl.clearColor(0, 0, 0, 0)
    const canvas = gl.canvas
    canvas.style.width = "100%"
    canvas.style.height = "100%"
    canvas.style.display = "block"
    container.appendChild(canvas)

    const geometry = new Triangle(gl)
    const program = new Program(gl, {
      vertex: moltenMetalVertex,
      fragment: moltenMetalFragment,
      uniforms: {
        iTime: { value: 0 },
        iResolution: { value: new Float32Array([1, 1]) },
        uSpeed: { value: 0.35 },
        uScale: { value: 4 },
        uDetail: { value: 3 },
        uGlow: { value: 1.6 },
        uCoreSize: { value: 0.1 },
        uSwirl: { value: 1 },
        uFold: { value: -0.2 },
        uBlackPoint: { value: 0.05 },
        uBrightness: { value: 1.3 },
        uColorMode: { value: 0 },
        uGrain: { value: 1 },
        uGrainIntensity: { value: 0.05 },
        uOpacity: { value: 1.0 },
        uMouse: { value: new Float32Array([0.5, 0.5]) },
        uMouseStrength: { value: 0.3 },
        uEnableMouse: { value: true },
        uColor1: { value: new Float32Array([1, 1, 1]) },
        uColor2: { value: new Float32Array([1, 1, 1]) },
        uColor3: { value: new Float32Array([1, 1, 1]) },
        uBackgroundColor: { value: new Float32Array([1, 1, 1]) },
        uLightMode: { value: false },
      },
    })

    const mesh = new Mesh(gl, { geometry, program })
    moltenMetalCtxMap.set(container, { renderer, program, mesh })

    const setSize = () => {
      const rect = container.getBoundingClientRect()
      const w = Math.max(1, Math.floor(rect.width))
      const h = Math.max(1, Math.floor(rect.height))
      renderer.setSize(w, h)
      const res = program.uniforms.iResolution.value as Float32Array
      res[0] = gl.drawingBufferWidth
      res[1] = gl.drawingBufferHeight
      renderer.render({ scene: mesh })
    }

    const ro = new ResizeObserver(setSize)
    ro.observe(container)
    setSize()

    const targetMouse: [number, number] = [0.5, 0.5]
    const currentMouse: [number, number] = [0.5, 0.5]

    const handleMouseMove = (e: MouseEvent) => {
      const rect = canvas.getBoundingClientRect()
      targetMouse[0] = (e.clientX - rect.left) / rect.width
      targetMouse[1] = 1.0 - (e.clientY - rect.top) / rect.height
    }
    const handleMouseLeave = () => {
      targetMouse[0] = 0.5
      targetMouse[1] = 0.5
    }
    canvas.addEventListener("mousemove", handleMouseMove)
    canvas.addEventListener("mouseleave", handleMouseLeave)

    let raf = 0
    let isVisible = true
    let isPageVisible = !document.hidden
    const t0 = performance.now()

    const loop = (t: number) => {
      program.uniforms.iTime.value = (t - t0) * 0.001
      currentMouse[0] += 0.05 * (targetMouse[0] - currentMouse[0])
      currentMouse[1] += 0.05 * (targetMouse[1] - currentMouse[1])
      const m = program.uniforms.uMouse.value as Float32Array
      m[0] = currentMouse[0]
      m[1] = currentMouse[1]
      renderer.render({ scene: mesh })
      raf = requestAnimationFrame(loop)
    }

    const tryStart = () => {
      if (isVisible && isPageVisible && raf === 0)
        raf = requestAnimationFrame(loop)
    }
    const tryStop = () => {
      if (raf !== 0) {
        cancelAnimationFrame(raf)
        raf = 0
      }
    }

    const io = new IntersectionObserver(
      ([entry]) => {
        isVisible = entry.isIntersecting
        if (isVisible) {
          tryStart()
        } else {
          tryStop()
        }
      },
      { threshold: 0 }
    )
    io.observe(container)

    const onVisibility = () => {
      isPageVisible = !document.hidden
      if (isPageVisible) {
        tryStart()
      } else {
        tryStop()
      }
    }
    document.addEventListener("visibilitychange", onVisibility)

    tryStart()

    return () => {
      tryStop()
      ro.disconnect()
      io.disconnect()
      document.removeEventListener("visibilitychange", onVisibility)
      canvas.removeEventListener("mousemove", handleMouseMove)
      canvas.removeEventListener("mouseleave", handleMouseLeave)
      moltenMetalCtxMap.delete(container)
      if (canvas.parentNode === container) container.removeChild(canvas)
      gl.getExtension("WEBGL_lose_context")?.loseContext()
    }
  }, [])

  useEffect(() => {
    const container = containerRef.current
    if (!container) return
    const ctx = moltenMetalCtxMap.get(container)
    if (!ctx) return
    const u = ctx.program.uniforms

    u.uSpeed.value = speed
    u.uScale.value = scale
    u.uDetail.value = detail
    u.uGlow.value = glow
    u.uCoreSize.value = Math.max(coreSize, 0.001)
    u.uSwirl.value = swirl
    u.uFold.value = fold
    u.uBlackPoint.value = blackPoint
    u.uBrightness.value = brightness
    u.uColorMode.value = colorModeToFloat(colorMode)
    u.uGrain.value = grain ? 1 : 0
    u.uGrainIntensity.value = grainIntensity
    u.uOpacity.value = opacity
    u.uMouseStrength.value = mouseStrength
    u.uEnableMouse.value = mouseInteraction
    const c1 = hexToRgb(color1)
    const c2 = hexToRgb(color2)
    const c3 = hexToRgb(color3)
    const bg = hexToRgb(backgroundColor)
    const uc1 = u.uColor1.value as Float32Array
    const uc2 = u.uColor2.value as Float32Array
    const uc3 = u.uColor3.value as Float32Array
    uc1[0] = c1[0]
    uc1[1] = c1[1]
    uc1[2] = c1[2]
    uc2[0] = c2[0]
    uc2[1] = c2[1]
    uc2[2] = c2[2]
    uc3[0] = c3[0]
    uc3[1] = c3[1]
    uc3[2] = c3[2]
    u.uBackgroundColor.value[0] = bg[0]
    u.uBackgroundColor.value[1] = bg[1]
    u.uBackgroundColor.value[2] = bg[2]
  }, [
    color1,
    color2,
    color3,
    speed,
    scale,
    detail,
    glow,
    coreSize,
    swirl,
    fold,
    blackPoint,
    brightness,
    colorMode,
    grain,
    grainIntensity,
    mouseInteraction,
    mouseStrength,
    opacity,
    backgroundColor,
    lightMode,
  ])

  return (
    <div
      ref={containerRef}
      className={`relative h-full w-full overflow-hidden ${className}`.trim()}
    />
  )
}

// Used only by this block (hero-16).
function HeroBackground() {
  return (
    <div className="absolute inset-0 z-10">
      <div className="absolute inset-0 z-10 bg-card/90"></div>
      <MoltenMetal
        color1="#ffffff"
        color2="#00786f"
        color3="#ffffff"
        colorMode="molten"
        speed={0.45}
        scale={7.3}
        detail={5}
        glow={3}
        coreSize={0.09}
        swirl={0.75}
        fold={-0.31}
        blackPoint={0.14}
        brightness={1.3}
        opacity={1}
        grain
        grainIntensity={0}
        mouseInteraction
        mouseStrength={1}
      />
    </div>
  )
}

const fadeInUp: any = {
  hidden: { opacity: 0, y: 25 },
  visible: (i: number = 0) => ({
    opacity: 1,
    y: 0,
    transition: {
      duration: 0.6,
      delay: i * 0.1,
      ease: [0.25, 0.46, 0.45, 0.94],
    },
  }),
}

export default function Hero16() {
  const heroRef = useRef<HTMLElement>(null)
  const mouseX = useMotionValue(0)
  const mouseY = useMotionValue(0)

  const handleMouseMove = (e: React.MouseEvent) => {
    if (!heroRef.current) return
    const { left, top } = heroRef.current.getBoundingClientRect()
    mouseX.set(e.clientX - left)
    mouseY.set(e.clientY - top)
  }

  return (
    <section
      ref={heroRef}
      onMouseMove={handleMouseMove}
      className="relative overflow-hidden px-4 pt-32 pb-16 text-center md:px-8 md:pt-38 md:pb-20"
    >
      <HeroBackground />
      {/* Dynamic Glow following mouse */}
      <motion.div
        className="pointer-events-none absolute inset-0 opacity-40"
        style={{
          background: useMotionTemplate`
            radial-gradient(
              400px circle at ${mouseX}px ${mouseY}px,
              rgba(45, 212, 191, 0.15),
              transparent 80%
            )
          `,
        }}
      />
      <div className="pointer-events-none absolute top-10 left-1/3 -z-10 h-72 w-72 rounded-full bg-teal-100/40 blur-[100px]" />

      <motion.div
        initial="hidden"
        animate="visible"
        variants={fadeInUp}
        custom={0}
        className="relative z-10 container mx-auto"
      >
        <span className="mb-6 inline-block rounded-full border border-teal-100/50 bg-teal-50/80 px-4 py-1.5 text-xs font-bold tracking-[0.2em] text-teal-700 uppercase shadow-sm">
          Connect With Us
        </span>
        <h1 className="text-4xl leading-[1.1] font-extrabold tracking-tight text-teal-950 sm:text-5xl md:text-6xl">
          Let's build the{" "}
          <span className="bg-gradient-to-r from-teal-600 to-emerald-500 bg-clip-text text-transparent">
            future of payments
          </span>
        </h1>
        <p className="mx-auto mt-8 max-w-3xl text-lg leading-relaxed text-slate-600 sm:text-xl">
          Have technical questions about our APIs, pricing brackets, or ledger
          synchronization engine? Connect with our support engineers.
        </p>
      </motion.div>
    </section>
  )
}
```
