pstzx

21 st Contour Line

Component 1

About

Contur line effect 21 st dev

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds 21 st Contour Line to your project and fits it to your theme.

Code

How 21 st Contour Line is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

import React, { useEffect, useRef, useState } from "react";

type Pt = [number, number];

interface Shockwave {
  x: number;
  y: number;
  radius: number;
  intensity: number;
}

interface RippleRing {
  id: number;
  x: number;
  y: number;
  size: number;
  color: string;
}

const css = `
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  user-select: none;
  -webkit-user-select: none;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #ffffff;
  color: #000000;
}

@media (prefers-color-scheme: dark) {
  html, body {
    background: #050505;
    color: #ffffff;
  }
}

.contour-hero {
  --bg: #ffffff;
  --fg: #000000;
  --border-inset: 24px;
  --border-ring: 18px;
  --glass-glow: rgba(255, 255, 255, 0.7);
  --glass-base: rgba(255, 255, 255, 0.2);
  --glass-shade: rgba(0, 0, 0, 0.2);
  --fx: -999px;
  --fy: -999px;
  --push: 0px;
  position: fixed;
  inset: 0;
  background: var(--bg);
  color: var(--fg);
  cursor: crosshair;
  overflow: hidden;
}

@media (prefers-color-scheme: dark) {
  .contour-hero {
    --bg: #050505;
    --fg: #ffffff;
    --glass-glow: rgba(255, 255, 255, 0.45);
    --glass-base: rgba(0, 0, 0, 0.35);
    --glass-shade: rgba(255, 255, 255, 0.1);
    color-scheme: dark;
  }
}

.contour-hero canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* Outer decorative liquid glass frame */
.contour-hero .glass-frame {
  position: absolute;
  z-index: 10;
  inset: calc(var(--border-inset) - var(--push));
  border-radius: 36px;
  padding: var(--border-ring);
  pointer-events: none;
  background:
    radial-gradient(280px 280px at calc(var(--fx) - var(--border-inset)) calc(var(--fy) - var(--border-inset)), var(--glass-glow), transparent 75%),
    linear-gradient(135deg, var(--glass-glow) 0%, transparent 35%, transparent 65%, var(--glass-shade) 100%),
    var(--glass-base);
  -webkit-backdrop-filter: blur(10px) saturate(1.8);
  backdrop-filter: blur(10px) saturate(1.8);
  backdrop-filter: url(#contour-refract) blur(6px) saturate(1.8);
  box-shadow:
    inset 0 1px 2px var(--glass-glow),
    inset 0 -1px 2px var(--glass-shade),
    inset 0 0 0 1px color-mix(in srgb, var(--fg) 24%, transparent);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  transition: inset 0.16s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.contour-hero .glass-inner-rim {
  position: absolute;
  z-index: 11;
  pointer-events: none;
  inset: calc(var(--border-inset) + var(--border-ring) - var(--push));
  border-radius: calc(36px - var(--border-ring));
  box-shadow:
    0 0 0 1.5px color-mix(in srgb, var(--fg) 32%, transparent),
    0 1px 3px 1px var(--glass-glow);
  transition: inset 0.16s cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* Outward ripple ring element */
.contour-hero .ripple-ring {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  z-index: 20;
  transform: translate(-50%, -50%) scale(0.1);
  border: 2px solid currentColor;
  box-shadow: 0 0 18px currentColor, inset 0 0 12px currentColor;
  animation: rippleExpand 0.85s cubic-bezier(0.1, 0.7, 0.2, 1) forwards;
}

@keyframes rippleExpand {
  0% {
    transform: translate(-50%, -50%) scale(0.1);
    opacity: 0.95;
  }
  60% {
    opacity: 0.5;
  }
  100% {
    transform: translate(-50%, -50%) scale(1);
    opacity: 0;
    border-width: 1px;
  }
}
`;

const CELL = 5;
const L0 = 0.08;
const STEP = 0.022;
const N = 42;

const STYLES = [
  { w: 0.5, a: 0.55 },
  { w: 1.0, a: 0.8 },
  { w: 1.8, a: 0.95 },
  { w: 3.2, a: 1.0 },
];

const levelClass = (n: number) =>
  n % 8 === 0 ? 3 : n % 4 === 0 ? 2 : n % 2 === 0 ? 1 : 0;

export default function App() {
  const heroRef = useRef<HTMLElement>(null);
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const shockwavesRef = useRef<Shockwave[]>([]);
  const [ripples, setRipples] = useState<RippleRing[]>([]);
  const rippleIdRef = useRef(0);

  useEffect(() => {
    const canvas = canvasRef.current;
    const hero = heroRef.current;
    if (!canvas || !hero) return;

    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    let W = 0,
      H = 0,
      nx = 0,
      ny = 0,
      dpr = 1;
    let fieldData = new Float32Array(0);
    let mx = 0.5,
      my = 0.5,
      tx = 0.5,
      ty = 0.5;
    let amp = 0,
      intro = 0,
      tt = 0,
      last = 0,
      prev: number | null = null;
    let borderInset = 24;
    let borderRing = 18;
    let rafId = 0;

    // Offscreen raster & SDF distance map for the 21st.dev vector logo
    const logoCanvas = document.createElement("canvas");
    const logoCtx = logoCanvas.getContext("2d");
    let logoDistMap: Float32Array | null = null;
    let mapW = 0,
      mapH = 0;

    const getInkColor = () =>
      window.matchMedia("(prefers-color-scheme: dark)").matches
        ? "#ffffff"
        : "#000000";

    // Generate exact raster and Euclidean distance field for 21st.dev logo
    // Directly mapped from the 512x512 pixel coordinates of the reference image
    const updateLogoDistanceMap = () => {
      if (!logoCtx) return;
      mapW = nx + 1;
      mapH = ny + 1;
      logoCanvas.width = mapW;
      logoCanvas.height = mapH;

      logoCtx.clearRect(0, 0, mapW, mapH);
      logoCtx.fillStyle = "#ffffff";

      const logoPixelSize = Math.min(W, H) * 0.64;
      const scale = logoPixelSize / 512 / CELL;
      const offsetX = (mapW - 512 * scale) * 0.5;
      const offsetY = (mapH - 512 * scale) * 0.5;

      logoCtx.save();
      logoCtx.translate(offsetX, offsetY);
      logoCtx.scale(scale, scale);

      logoCtx.beginPath();

      // --- SHAPE 1: Top-Right block ("1") ---
      logoCtx.moveTo(225, 74);
      logoCtx.lineTo(438 - 56, 74);
      logoCtx.arcTo(438, 74, 438, 74 + 56, 56);
      logoCtx.lineTo(438, 332);
      logoCtx.lineTo(316, 332);
      logoCtx.lineTo(316, 186);
      logoCtx.arcTo(316, 180, 310, 180, 6);
      logoCtx.lineTo(225, 180);
      logoCtx.closePath();

      // --- SHAPE 2: Bottom-Left block ("2") ---
      logoCtx.moveTo(74, 180);
      logoCtx.lineTo(225, 180);
      logoCtx.lineTo(225, 326);
      logoCtx.arcTo(225, 332, 231, 332, 6);
      logoCtx.lineTo(316, 332);
      logoCtx.lineTo(316, 438);
      logoCtx.lineTo(74 + 56, 438);
      logoCtx.arcTo(74, 438, 74, 438 - 56, 56);
      logoCtx.lineTo(74, 349);
      logoCtx.lineTo(212, 273);
      logoCtx.quadraticCurveTo(218, 268, 210, 268);
      logoCtx.lineTo(74, 268);
      logoCtx.closePath();

      logoCtx.fill();
      logoCtx.restore();

      // Build exact Euclidean Signed Distance Transform (SDF) from raster image
      const imgData = logoCtx.getImageData(0, 0, mapW, mapH).data;
      logoDistMap = new Float32Array(mapW * mapH);
      const INF = 1e6;
      const insideMap = new Uint8Array(mapW * mapH);

      for (let idx = 0; idx < mapW * mapH; idx++) {
        insideMap[idx] = imgData[idx * 4 + 3] > 127 ? 1 : 0;
      }

      const R = 24;
      for (let y = 0; y < mapH; y++) {
        for (let x = 0; x < mapW; x++) {
          const idx = y * mapW + x;
          const inside = insideMap[idx];
          let minD2 = INF;
          const yMin = Math.max(0, y - R),
            yMax = Math.min(mapH - 1, y + R);
          const xMin = Math.max(0, x - R),
            xMax = Math.min(mapW - 1, x + R);

          for (let qy = yMin; qy <= yMax; qy++) {
            const row = qy * mapW;
            const dy2 = (qy - y) * (qy - y);
            if (dy2 >= minD2) continue;
            for (let qx = xMin; qx <= xMax; qx++) {
              if (insideMap[row + qx] !== inside) {
                const d2 = (qx - x) * (qx - x) + dy2;
                if (d2 < minD2) minD2 = d2;
              }
            }
          }
          const dist = Math.sqrt(minD2);
          logoDistMap[idx] = inside ? -dist : dist;
        }
      }
    };

    const resize = () => {
      dpr = Math.min(window.devicePixelRatio || 1, 2);
      W = canvas.clientWidth;
      H = canvas.clientHeight;
      canvas.width = W * dpr;
      canvas.height = H * dpr;

      const comp = getComputedStyle(hero);
      borderInset = parseFloat(comp.getPropertyValue("--border-inset")) || 24;
      borderRing = parseFloat(comp.getPropertyValue("--border-ring")) || 18;

      nx = Math.ceil(W / CELL) + 1;
      ny = Math.ceil(H / CELL) + 1;
      fieldData = new Float32Array((nx + 1) * (ny + 1));

      updateLogoDistanceMap();
    };

    const evaluateField = (t: number) => {
      const sx = 6 / Math.max(W, 600);
      let k = 0;
      const shockwaves = shockwavesRef.current;

      for (let j = 0; j <= ny; j++) {
        const py = j * CELL;
        for (let i = 0; i <= nx; i++, k++) {
          const px = i * CELL;
          const x = px * sx;
          const y = py * sx;

          // Gentle background topographic flow
          let v =
            Math.sin(x * 1.15 + t * 0.08 + 1.3 * Math.sin(y * 0.8)) * 0.11 +
            Math.sin(y * 0.95 - x * 0.5 + t * 0.06) * 0.09 +
            Math.sin((x + y) * 1.7 + t * 0.04) * 0.04;

          // Subtle cursor elevation
          const dxm = (px - mx * W) / (W * 0.11);
          const dym = (py - my * H) / (H * 0.11);
          v += 0.22 * Math.exp(-(dxm * dxm + dym * dym));

          // Crisp 21st.dev logo contour elevation via exact SDF distance map
          if (logoDistMap) {
            const d = logoDistMap[j * mapW + i];
            if (d < 14) {
              const edgeSlope = 1 / (1 + Math.exp(d * 0.95));
              const innerTerrace = d < 0 ? Math.min(0.18, -d * 0.018) : 0;
              v += edgeSlope * 0.78 + innerTerrace;
            }
          }

          // Ripple shockwaves from border interactions
          for (let s = 0; s < shockwaves.length; s++) {
            const sw = shockwaves[s];
            const d = Math.hypot(px - sw.x, py - sw.y);
            const ringDist = d - sw.radius;
            if (Math.abs(ringDist) < 70) {
              const wave = Math.cos((ringDist / 70) * Math.PI) * 0.5 + 0.5;
              v += Math.sin(ringDist * 0.18) * wave * sw.intensity;
            }
          }

          fieldData[k] = 0.5 + amp * v;
        }
      }
    };

    const drawContourLines = () => {
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      ctx.clearRect(0, 0, W, H);

      // Clip strictly within inner glass border
      ctx.save();
      ctx.beginPath();
      const clipX = borderInset + borderRing;
      const clipY = borderInset + borderRing;
      const clipW = W - (borderInset + borderRing) * 2;
      const clipH = H - (borderInset + borderRing) * 2;
      const radius = Math.max(0, 36 - borderRing);

      if (typeof ctx.roundRect === "function") {
        ctx.roundRect(
          clipX,
          clipY,
          Math.max(0, clipW),
          Math.max(0, clipH),
          radius
        );
      } else {
        ctx.rect(clipX, clipY, Math.max(0, clipW), Math.max(0, clipH));
      }
      ctx.clip();

      ctx.strokeStyle = getInkColor();
      ctx.lineJoin = "round";
      const w = nx + 1;

      for (let pass = 0; pass < STYLES.length; pass++) {
        ctx.beginPath();
        for (let n = 0; n < N; n++) {
          if (levelClass(n) !== pass) continue;
          const L = L0 + n * STEP;

          for (let j = 0; j < ny; j++) {
            for (let i = 0; i < nx; i++) {
              const p = j * w + i;
              const a = fieldData[p];
              const b = fieldData[p + 1];
              const c = fieldData[p + w + 1];
              const d = fieldData[p + w];

              if (L < Math.min(a, b, c, d) || L > Math.max(a, b, c, d))
                continue;

              const cs =
                ((a > L ? 1 : 0) << 3) |
                ((b > L ? 1 : 0) << 2) |
                ((c > L ? 1 : 0) << 1) |
                (d > L ? 1 : 0);

              if (cs === 0 || cs === 15) continue;

              const x0 = i * CELL;
              const y0 = j * CELL;
              const T: Pt = [x0 + (CELL * (L - a)) / (b - a), y0];
              const R: Pt = [x0 + CELL, y0 + (CELL * (L - b)) / (c - b)];
              const B: Pt = [x0 + (CELL * (L - d)) / (c - d), y0 + CELL];
              const E: Pt = [x0, y0 + (CELL * (L - a)) / (d - a)];

              let s: Pt[] = [];
              switch (cs) {
                case 1:
                case 14:
                  s = [E, B];
                  break;
                case 2:
                case 13:
                  s = [B, R];
                  break;
                case 3:
                case 12:
                  s = [E, R];
                  break;
                case 4:
                case 11:
                  s = [T, R];
                  break;
                case 5:
                  s = [T, E, B, R];
                  break;
                case 6:
                case 9:
                  s = [T, B];
                  break;
                case 7:
                case 8:
                  s = [T, E];
                  break;
                case 10:
                  s = [T, R, E, B];
                  break;
              }

              for (let q = 0; q < s.length; q += 2) {
                ctx.moveTo(s[q][0], s[q][1]);
                ctx.lineTo(s[q + 1][0], s[q + 1][1]);
              }
            }
          }
        }
        ctx.lineWidth = STYLES[pass].w;
        ctx.globalAlpha = STYLES[pass].a;
        ctx.stroke();
      }

      ctx.restore();
      ctx.globalAlpha = 1;
    };

    const loop = (ts: number) => {
      const dt = prev === null ? 0 : (ts - prev) / 1000;
      prev = ts;

      intro += dt;
      tt += dt;

      amp = Math.min(1, intro / 2.4);
      amp = 1 - Math.pow(1 - amp, 3);

      mx += (tx - mx) * 0.08;
      my += (ty - my) * 0.08;

      const shockwaves = shockwavesRef.current;
      for (let i = shockwaves.length - 1; i >= 0; i--) {
        const sw = shockwaves[i];
        sw.radius += 240 * dt;
        sw.intensity *= Math.pow(0.1, dt);
        if (sw.radius > Math.max(W, H) * 0.8 || sw.intensity < 0.01) {
          shockwaves.splice(i, 1);
        }
      }

      if (ts - last > 30 || amp < 1) {
        last = ts;
        evaluateField(tt);
        drawContourLines();
      }

      rafId = requestAnimationFrame(loop);
    };

    const handleMove = (e: globalThis.PointerEvent) => {
      hero.style.setProperty("--fx", `${e.clientX}px`);
      hero.style.setProperty("--fy", `${e.clientY}px`);
      tx = e.clientX / window.innerWidth;
      ty = e.clientY / window.innerHeight;
    };

    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    const handleSchemeChange = () => {
      drawContourLines();
    };

    resize();
    window.addEventListener("pointermove", handleMove);
    window.addEventListener("resize", resize);
    mediaQuery.addEventListener("change", handleSchemeChange);
    rafId = requestAnimationFrame(loop);

    return () => {
      cancelAnimationFrame(rafId);
      window.removeEventListener("pointermove", handleMove);
      window.removeEventListener("resize", resize);
      mediaQuery.removeEventListener("change", handleSchemeChange);
    };
  }, []);

  const handlePointerDown = (e: React.PointerEvent<HTMLElement>) => {
    const hero = heroRef.current;
    if (!hero) return;

    const comp = getComputedStyle(hero);
    const borderInset = parseFloat(comp.getPropertyValue("--border-inset")) || 24;
    const borderRing = parseFloat(comp.getPropertyValue("--border-ring")) || 18;
    const edgeThreshold = borderInset + borderRing + 25;

    const W = hero.clientWidth;
    const H = hero.clientHeight;
    const x = e.clientX;
    const y = e.clientY;

    const isNearEdge =
      x <= edgeThreshold ||
      x >= W - edgeThreshold ||
      y <= edgeThreshold ||
      y >= H - edgeThreshold;

    if (isNearEdge) {
      // Push glass border outward
      hero.style.setProperty("--push", "10px");
      setTimeout(() => {
        hero.style.setProperty("--push", "0px");
      }, 180);

      // Internal heightfield shockwave
      shockwavesRef.current.push({
        x,
        y,
        radius: 5,
        intensity: 0.35,
      });

      const inkColor = window.matchMedia("(prefers-color-scheme: dark)").matches
        ? "#ffffff"
        : "#000000";
      const size = Math.max(W, H) * 0.45;

      // Spawn 3 concentric outward ripple rings
      for (let i = 0; i < 3; i++) {
        setTimeout(() => {
          const id = ++rippleIdRef.current;
          setRipples((prev) => [
            ...prev,
            { id, x, y, size, color: inkColor },
          ]);

          setTimeout(() => {
            setRipples((prev) => prev.filter((r) => r.id !== id));
          }, 900);
        }, i * 140);
      }
    }
  };

  return (
    <header
      className="contour-hero"
      ref={heroRef}
      onPointerDown={handlePointerDown}
    >
      <style>{css}</style>

      {/* SVG filter for liquid glass border refraction */}
      <svg
        width="0"
        height="0"
        style={{ position: "absolute" }}
        aria-hidden="true"
        focusable="false"
      >
        <filter
          id="contour-refract"
          x="0"
          y="0"
          width="100%"
          height="100%"
          colorInterpolationFilters="sRGB"
        >
          <feTurbulence
            type="fractalNoise"
            baseFrequency="0.012 0.03"
            numOctaves={2}
            seed={4}
            result="noise"
          />
          <feGaussianBlur in="noise" stdDeviation="2" result="soft" />
          <feDisplacementMap
            in="SourceGraphic"
            in2="soft"
            scale="26"
            xChannelSelector="R"
            yChannelSelector="G"
          />
        </filter>
      </svg>

      <canvas
        ref={canvasRef}
        role="img"
        aria-label="Topographic 21st.dev logo contour map"
      />
      <div className="glass-frame" aria-hidden="true" />
      <div className="glass-inner-rim" aria-hidden="true" />

      {ripples.map((r) => (
        <div
          key={r.id}
          className="ripple-ring"
          style={{
            width: `${r.size}px`,
            height: `${r.size}px`,
            left: `${r.x}px`,
            top: `${r.y}px`,
            color: r.color,
          }}
        />
      ))}
    </header>
  );
}

More by pstzx

View all
  • Contour Land Map

    Contour Land Map

    11
  • Contour line Mountain

    Contour line Mountain

    5
  • Sign In Water Effect Glass

    Sign In Water Effect Glass

    4
  • Button New & Search

    Button New & Search

    2
  • Contour line map

    Contour line map

    1
  • hole gradient

    hole gradient

    1