Prism
← All primitives

ContourField

Map

A topographic map: flowing elevation contours from a procedural field with crisp resolution-independent isolines that glow. Reads instantly as terrain. Zero data, pointer-reactive, palette-driven.

$npx shadcn@latest add https://prism.icglabs.co/r/contour-field.json
Dependencies:three@react-three/fiber
View raw manifest →

components/prism/ContourField.tsx
"use client";

import { useMemo, useRef } from "react";
import { Canvas, useFrame, useThree } from "@react-three/fiber";
import * as THREE from "three";
import { useDeviceTier } from "@/lib/useDeviceTier";
import { AutoDpr, PauseWhenOffscreen } from "@/components/showcase/CanvasPerf";

/**
 * ContourField — a topographic "map": flowing elevation contour lines drawn from
 * a procedural noise field with resolution-independent isolines (fwidth), tinted
 * low→high and glowing on each contour. Reads instantly as terrain/elevation.
 * Self-contained, pointer-reactive, palette-driven.
 */
export type ContourFieldProps = {
  /** [low terrain, high terrain, contour-line glow] hex. */
  palette?: [string, string, string];
  speed?: number;
  className?: string;
};

const VERT = /* glsl */ `
varying vec2 vUv;
void main() { vUv = uv; gl_Position = vec4(position.xy, 0.0, 1.0); }
`;

const FRAG = /* glsl */ `
precision highp float;
varying vec2 vUv;
uniform float uTime;
uniform vec2 uMouse;
uniform float uAspect;
uniform vec3 uLow;
uniform vec3 uHigh;
uniform vec3 uLine;

float hash(vec2 p){ p=fract(p*vec2(123.34,345.45)); p+=dot(p,p+34.345); return fract(p.x*p.y); }
float noise(vec2 p){ vec2 i=floor(p),f=fract(p); vec2 u=f*f*(3.0-2.0*f);
  float a=hash(i),b=hash(i+vec2(1,0)),c=hash(i+vec2(0,1)),d=hash(i+vec2(1,1));
  return mix(mix(a,b,u.x),mix(c,d,u.x),u.y); }
float fbm(vec2 p){ float v=0.0,a=0.5; for(int i=0;i<5;i++){ v+=a*noise(p); p*=2.0; a*=0.5; } return v; }

void main(){
  vec2 uv = vUv; uv.x *= uAspect;
  uv += (uMouse - 0.5) * 0.3;
  float t = uTime * 0.04;
  float h = fbm(uv * 3.0 + vec2(t, t * 0.5));

  // crisp, resolution-independent isolines
  float N = 13.0;
  float d = fwidth(h * N);
  float lines = abs(fract(h * N - 0.5) - 0.5) / max(d, 1e-4);
  float c = 1.0 - min(lines, 1.0);

  vec3 terrain = mix(uLow, uHigh, smoothstep(0.15, 0.85, h));
  vec3 col = terrain * (0.45 + 0.4 * h) + c * uLine * 1.5;

  float vig = smoothstep(1.05, 0.3, length(vUv - 0.5));
  col *= mix(0.3, 1.0, vig);
  col += (hash(vUv * 900.0 + t) - 0.5) * 0.02; // dither
  gl_FragColor = vec4(col, 1.0);
}
`;

function toV3(hex: string) {
  const c = new THREE.Color(hex);
  return new THREE.Vector3(c.r, c.g, c.b);
}

function FieldMesh({ palette, speed }: { palette: [string, string, string]; speed: number }) {
  const mat = useRef<THREE.ShaderMaterial>(null);
  const mouse = useRef(new THREE.Vector2(0.5, 0.5));
  const size = useThree((s) => s.size);
  const reduced =
    typeof window !== "undefined" &&
    window.matchMedia("(prefers-reduced-motion: reduce)").matches;

  const uniforms = useMemo(
    () => ({
      uTime: { value: 0 },
      uMouse: { value: new THREE.Vector2(0.5, 0.5) },
      uAspect: { value: 1 },
      uLow: { value: toV3(palette[0]) },
      uHigh: { value: toV3(palette[1]) },
      uLine: { value: toV3(palette[2]) },
    }),
    [palette]
  );

  useFrame((state, delta) => {
    const m = mat.current;
    if (!m) return;
    if (!reduced) m.uniforms.uTime.value += delta * speed;
    m.uniforms.uAspect.value = size.width / size.height;
    const target = mouse.current;
    target.set((state.pointer.x + 1) / 2, (state.pointer.y + 1) / 2);
    m.uniforms.uMouse.value.lerp(target, 0.05);
  });

  return (
    <mesh>
      <planeGeometry args={[2, 2]} />
      <shaderMaterial ref={mat} vertexShader={VERT} fragmentShader={FRAG} uniforms={uniforms} />
    </mesh>
  );
}

export function ContourField({
  palette = ["#0c1030", "#5b7cff", "#1fd4e6"],
  speed = 1,
  className,
}: ContourFieldProps) {
  const q = useDeviceTier();
  return (
    <div className={className} style={{ width: "100%", height: "100%" }}>
      <Canvas dpr={q.dpr} gl={{ antialias: false, powerPreference: "high-performance" }}>
        <FieldMesh palette={palette} speed={speed} />
        <AutoDpr />
        <PauseWhenOffscreen />
      </Canvas>
    </div>
  );
}
Live demo — read-only. Every section is a real, copyable primitive.