liten

Contour Drift

Topographic contour hairlines traced from a seeded height field with marching squares, drifting almost imperceptibly.

surveyed terrain

The field is three to four seeded sine octaves over a fixed coarse grid; each frame, several iso levels are traced with marching squares and stroked as 1px hairlines. Line alpha rises with elevation in honest steps, so higher contours read lit from above. Freezes to a single static frame under reduced motion.

Installation

Complete the shared Setup first, then copy the component into components/ui/contour-drift.tsx.

components/ui/contour-drift.tsx
'use client';import * as React from 'react';import { cn } from '@/lib/cn';export type ContourDriftProps = {  accent?: string;  accentPeaks?: boolean;  levels?: number;  speed?: number;  className?: string;};function seeded(n: number) {  const x = Math.sin(n * 12.9898) * 43758.5453;  return x - Math.floor(x);}function toRgb(hex: string): [number, number, number] {  let h = hex.replace('#', '');  if (h.length === 3)    h = h      .split('')      .map((c) => c + c)      .join('');  const n = parseInt(h, 16);  return [(n >> 16) & 255, (n >> 8) & 255, n & 255];}const COLS = 90;const ROWS = 60;const OCTAVES = [0, 1, 2, 3].map((i) => {  const angle = seeded(i * 17.3 + 3.1) * Math.PI * 2;  const freq = [1.6, 3.1, 5.7, 9.4][i];  return {    kx: Math.cos(angle) * freq,    ky: Math.sin(angle) * freq,    amp: [0.26, 0.13, 0.07, 0.04][i],    phase: seeded(i * 91.7 + 11.9) * Math.PI * 2,    rate: [0.021, 0.033, 0.047, 0.062][i],  };});const SEGS: ReadonlyArray<ReadonlyArray<readonly [number, number]>> = [  [],  [[3, 2]],  [[2, 1]],  [[3, 1]],  [[0, 1]],  [[0, 1], [3, 2]],  [[0, 2]],  [[0, 3]],  [[0, 3]],  [[0, 2]],  [[0, 3], [2, 1]],  [[0, 1]],  [[3, 1]],  [[2, 1]],  [[3, 2]],  [],];export function ContourDrift({  accent = '#f0883e',  accentPeaks = false,  levels = 9,  speed = 1,  className,}: ContourDriftProps) {  const canvasRef = React.useRef<HTMLCanvasElement>(null);  const accentRef = React.useRef(accent);  accentRef.current = accent;  React.useEffect(() => {    const canvas = canvasRef.current;    if (!canvas) return;    const ctx = canvas.getContext('2d');    if (!ctx) return;    const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;    const root = document.documentElement;    let raf = 0;    let w = 0;    let h = 0;    let dpr = 1;    const field = new Float32Array((COLS + 1) * (ROWS + 1));    const resize = () => {      const rect = canvas.getBoundingClientRect();      dpr = Math.min(window.devicePixelRatio || 1, 2);      w = rect.width;      h = rect.height;      canvas.width = Math.max(1, Math.round(w * dpr));      canvas.height = Math.max(1, Math.round(h * dpr));      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);    };    const sample = (u: number, v: number, t: number) => {      let n = 0.5;      for (const o of OCTAVES)        n += o.amp * Math.sin(u * o.kx + v * o.ky + o.phase + t * o.rate);      return n;    };    const draw = (t: number) => {      const dark = root.classList.contains('dark');      const [ar, ag, ab] = toRgb(accentRef.current);      ctx.clearRect(0, 0, w, h);      ctx.lineWidth = 1;      for (let gy = 0; gy <= ROWS; gy++) {        const v = (gy / ROWS) * 2.1;        for (let gx = 0; gx <= COLS; gx++) {          field[gy * (COLS + 1) + gx] = sample((gx / COLS) * 3.4, v, t);        }      }      const cw = w / COLS;      const ch = h / ROWS;      const nLevels = Math.max(2, levels);      const aTop = dark ? 0.42 : 0.34;      const aBase = 0.05;      for (let li = 0; li < nLevels; li++) {        const iso = 0.18 + (li / (nLevels - 1)) * 0.64;        const alpha = aBase + (aTop - aBase) * (li / (nLevels - 1));        const peak = accentPeaks && li === nLevels - 1;        ctx.strokeStyle = peak          ? `rgba(${ar},${ag},${ab},${alpha.toFixed(3)})`          : dark            ? `rgba(255,255,255,${alpha.toFixed(3)})`            : `rgba(0,0,0,${alpha.toFixed(3)})`;        ctx.beginPath();        for (let gy = 0; gy < ROWS; gy++) {          const r0 = gy * (COLS + 1);          const r1 = (gy + 1) * (COLS + 1);          for (let gx = 0; gx < COLS; gx++) {            const tl = field[r0 + gx];            const tr = field[r0 + gx + 1];            const br = field[r1 + gx + 1];            const bl = field[r1 + gx];            const idx =              (tl > iso ? 8 : 0) |              (tr > iso ? 4 : 0) |              (br > iso ? 2 : 0) |              (bl > iso ? 1 : 0);            if (idx === 0 || idx === 15) continue;            const segs = SEGS[idx];            for (const [e1, e2] of segs) {              for (let e = 0; e < 2; e++) {                const edge = e === 0 ? e1 : e2;                let x = 0;                let y = 0;                if (edge === 0) {                  x = (gx + (iso - tl) / (tr - tl)) * cw;                  y = gy * ch;                } else if (edge === 1) {                  x = (gx + 1) * cw;                  y = (gy + (iso - tr) / (br - tr)) * ch;                } else if (edge === 2) {                  x = (gx + (iso - bl) / (br - bl)) * cw;                  y = (gy + 1) * ch;                } else {                  x = gx * cw;                  y = (gy + (iso - tl) / (bl - tl)) * ch;                }                if (e === 0) ctx.moveTo(x, y);                else ctx.lineTo(x, y);              }            }          }        }        ctx.stroke();      }    };    resize();    if (reduce) {      draw(0);    } else {      let start: number | null = null;      const loop = (now: number) => {        if (start === null) start = now;        const t = ((now - start) / 1000) * speed;        draw(t);        raf = requestAnimationFrame(loop);      };      raf = requestAnimationFrame(loop);    }    const ro = new ResizeObserver(() => {      resize();      if (reduce) draw(0);    });    ro.observe(canvas);    const mo = new MutationObserver(() => {      if (reduce) draw(0);    });    mo.observe(root, { attributes: true, attributeFilter: ['class'] });    return () => {      cancelAnimationFrame(raf);      ro.disconnect();      mo.disconnect();    };  }, [accentPeaks, levels, speed]);  return (    <canvas      ref={canvasRef}      aria-hidden      className={cn(        'pointer-events-none h-full w-full select-none',        '[mask-image:radial-gradient(ellipse_92%_100%_at_50%_50%,black_38%,transparent_97%)]',        className,      )}    />  );}

Usage

Example.tsx
import { ContourDrift } from '@/components/ui/contour-drift';

export default function Example() {
  return (
    <div className="relative h-[340px] w-full overflow-hidden rounded-xl border border-black/[0.08] bg-[#fafafa] dark:border-white/10 dark:bg-[#0a0a0b]">
      <ContourDrift />
    </div>
  );
}

Examples

More levels and an accent tint on the single highest contour band.

Props

PropTypeDefaultDescription
accentstring"#f0883e"Warm tint for the highest contour band. Only used when accentPeaks is set.
accentPeaksbooleanfalseTint the single highest contour band with the accent.
levelsnumber9Number of contour levels.
speednumber1Drift speed. 0 freezes it to a static frame.
classNamestring-Forwarded to the canvas.
On this page0%