Thermo Cursor

An interactive cursor-following thermodynamic heatmap that renders heat trails in real time. Use it for immersive hero backgrounds, creative portfolios, or anywhere you want visitors to feel their presence on the page.

Generated using Grepped's AI UI component generator — created from scratch, not pulled from a library.

cursorheatmapinteractivethermodynamic

Live Preview — customize or regenerate this in the workspace

Loading preview…

Design Intent

An interactive cursor-following thermodynamic heatmap effect for immersive background visuals.

CSS

css
body { margin: 0; min-height: 100vh; background: #050505; overflow: hidden; }
    canvas { display: block; width: 100%; height: 100%; }

HTML

html
<canvas id="c"></canvas>

Full Source

html
<!DOCTYPE html>
<html>
<head>
  <style>
    body { margin: 0; min-height: 100vh; background: #050505; overflow: hidden; }
    canvas { display: block; width: 100%; height: 100%; }
  </style>
</head>
<body>
  <canvas id="c"></canvas>
  <script>
    var canvas = document.getElementById("c");
    var ctx = canvas.getContext("2d", { alpha: false });
    var resolution = 12;
    var coolingFactor = 0.96;
    var grid, cols, rows, w, h;
    var mouse = { x: -1000, y: -1000, px: -1000, py: -1000, active: false };

    function getThermalColor(t) {
      var r = Math.min(255, Math.max(0, t * 2.5 * 255));
      var g = Math.min(255, Math.max(0, (t * 2.5 - 1) * 255));
      var b = Math.min(255, Math.max(0, (t * 2.5 - 2) * 255 + t * 50));
      return "rgb(" + (r + 10) + "," + (g + 10) + "," + (b + 15) + ")";
    }

    function resize() {
      w = canvas.width = window.innerWidth;
      h = canvas.height = window.innerHeight;
      cols = Math.ceil(w / resolution);
      rows = Math.ceil(h / resolution);
      grid = new Float32Array(cols * rows);
    }

    document.addEventListener("mousemove", function(e) {
      mouse.x = e.clientX; mouse.y = e.clientY; mouse.active = true;
    });
    document.addEventListener("mouseleave", function() { mouse.active = false; });

    function update() {
      if (mouse.active) {
        var dx = mouse.x - mouse.px, dy = mouse.y - mouse.py;
        var dist = Math.sqrt(dx * dx + dy * dy);
        var steps = Math.ceil(dist / (resolution / 2));
        for (var s = 0; s <= steps; s++) {
          var t = steps > 0 ? s / steps : 0;
          var mx = mouse.px + dx * t, my = mouse.py + dy * t;
          var col = Math.floor(mx / resolution), row = Math.floor(my / resolution);
          var radius = 2;
          for (var i = -radius; i <= radius; i++) {
            for (var j = -radius; j <= radius; j++) {
              var c = col + i, r = row + j;
              if (c >= 0 && c < cols && r >= 0 && r < rows) {
                var d = Math.sqrt(i * i + j * j);
                if (d <= radius) grid[c + r * cols] = Math.min(1.0, grid[c + r * cols] + 0.3 * (1 - d / radius));
              }
            }
          }
        }
      }
      mouse.px = mouse.x; mouse.py = mouse.y;

      ctx.fillStyle = "#050505";
      ctx.fillRect(0, 0, w, h);

      for (var r = 0; r < rows; r++) {
        for (var c = 0; c < cols; c++) {
          var idx = c + r * cols;
          var temp = grid[idx];
          grid[idx] *= coolingFactor;
          if (temp > 0.05) {
            var x = c * resolution, y = r * resolution;
            var size = resolution * (0.8 + temp * 0.5);
            var offset = (resolution - size) / 2;
            ctx.fillStyle = getThermalColor(temp);
            ctx.fillRect(x + offset, y + offset, size, size);
          } else if (c % 2 === 0 && r % 2 === 0) {
            ctx.fillStyle = "#18181b";
            ctx.fillRect(c * resolution + resolution / 2 - 1, r * resolution + resolution / 2 - 1, 2, 2);
          }
        }
      }
      requestAnimationFrame(update);
    }

    window.addEventListener("resize", resize);
    resize();
    update();
  </script>
</body>
</html>

More Popular Animations