https://motioncanvas.online/preview/kinetic-gravity-well
Scroll Down

Loading Canvas Shader...

Next-Gen Scroll Mechanics

Experience Beautiful Ambient Depth

This browser window is simulating a production webpage. As you scroll down, the ambient Kinetic Gravity Well background dynamically zooms and pulls focus in real-time, creating beautiful, cinematic parallax depth.

Built for Ultra Performance

Modern web layouts require buttery-smooth animations. Our styles run completely on the GPU, avoiding CPU reflow and main-thread layout jank.

Focus Pull Parallax

Scroll depth controls blur intensity and lens scaling simultaneously to guide focus elegantly.

GPU-Accelerated

Uses hardware transforms and native filters, optimized for stable 120fps scrolling.

Highly Customizable

Adjust speed, maximum blur limit, zoom multipliers, and filters in real-time.

© 2026 MOTIONCANVAS. ALL RIGHTS RESERVED.ACTIVE BACKGROUND: KINETIC GRAVITY WELL

Kinetic Gravity Well

🟣 Canvas

An abstract, massive orbital simulation constructed from thousands of mathematically precise microscopic kinetic dots that violently react to cursor-driven gravity physics.

#abstract#minimalism#orbital#interactive#physics#canvas
Advertisement
Sponsor Advertisement
728 × 90 | Responsive Banner

Responsive Horizontal Leaderboard

Slot ID: ca-pub-detail-below-preview

Google AdSense Placeholder

Interactive Settings

Motion PresetsCustom Tuning
Animation Speed1.0x
Blur (Aesthetic diffusion)0px
Layer Opacity100%
Scale Zoom1.0x
Rotation Angle0°
Advertisement
Sponsor Advertisement
300 × 250 | Rectangle

Medium Rectangle Block

Slot ID: ca-pub-detail-after-customization

Google AdSense Placeholder

Quick Copy Actions

Download File Packages

Keyboard Shortcuts

Space Play/Pause
F Fullscreen
R Reset Slider
C Copy Code
Advertisement
Sponsor Advertisement
728 × 90 | Responsive Banner

Responsive Horizontal Leaderboard

Slot ID: ca-pub-detail-before-related

Google AdSense Placeholder

More in Abstract

Kinetic Gravity Well - HTML5 Canvas & JavaScript Abstract background for React & Tailwind

Integrate the Kinetic Gravity Well directly into your website. This asset is rendered using HTML5 Canvas 2D render loop. It is optimized for zero layout-shifts and runs with high-performance hardware-accelerated processing.

Performance Specifications

  • Render Mode: CANVAS (HTML5 Canvas & JavaScript)
  • Fluidity: Locked at 60fps dynamic loop
  • Bundle Footprint: Zero external NPM dependencies
  • SEO Indexing status: 100% Crawlable static semantic HTML

🛠️ Integration Capabilities

Our templates expose inline design tokens like --color-1 and --color-2 for infinite color palettes. This template is designed to fit inside hero elements, full-screen landing pages, and interactive presentation cards.

Technical Code Reference & Syntaxes for Googlebot & Crawlers

The snippets below display the direct, unminified source code utilized for rendering this background.

HTML & Inline CSS Snippet (Vanilla)

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Kinetic Gravity Well</title>
  <style>
    html, body {
      margin: 0;
      padding: 0;
      width: 100%;
      height: 100%;
      overflow: hidden;
      background: #09090b;
    }
    
    .kinetic-gravity-container {
      width: 100%;
      height: 100%;
      background: #050505;
      position: relative;
      overflow: hidden;
    }
    #gravity-canvas {
      width: 100%;
      height: 100%;
      display: block;
    }
  </style>
</head>
<body>

  <div class="kinetic-gravity-container" style="width: 100%; height: 100%; overflow: hidden; background: #050505;">
    <canvas id="gravity-canvas" style="width: 100%; height: 100%; display: block; filter: contrast(1.15) brightness(1.05);"></canvas>
    <script>
      (function() {
        const canvas = document.getElementById('gravity-canvas');
        if (!canvas) return;
        const ctx = canvas.getContext('2d');
        if (!ctx) return;
  
        let width = 0;
        let height = 0;
        const dpr = window.devicePixelRatio || 1;
  
        const mouse = { x: -1000, y: -1000, active: false };
        const lerpedMouse = { x: -1000, y: -1000 };
  
        const particles = [];
        const numParticles = 2200;
  
        function initSimulation() {
          const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
          width = rect ? rect.width : window.innerWidth;
          height = rect ? rect.height : window.innerHeight;
  
          canvas.width = width * dpr;
          canvas.height = height * dpr;
          ctx.scale(dpr, dpr);
  
          particles.length = 0;
  
          const maxDist = Math.max(width, height) * 0.7;
          const ringBands = [
            { min: 60, max: 80, density: 0.1 },
            { min: 100, max: 130, density: 0.15 },
            { min: 140, max: 180, density: 0.25 },
            { min: 200, max: 215, density: 0.05 },
            { min: 230, max: 280, density: 0.25 },
            { min: 310, max: 380, density: 0.15 },
            { min: 410, max: maxDist, density: 0.05 },
          ];
  
          for (let i = 0; i < numParticles; i++) {
            let band = ringBands[2];
            const rVal = Math.random();
            let cumulative = 0;
            for (const b of ringBands) {
              cumulative += b.density;
              if (rVal <= cumulative) {
                band = b;
                break;
              }
            }
  
            const baseRadius = band.min + Math.random() * (band.max - band.min);
            const angle = Math.random() * Math.PI * 2;
            const baseSpeed = 0.001 + (3.5 / baseRadius);
            const angularSpeed = baseSpeed * (0.85 + Math.random() * 0.3);
            const orbitDirection = Math.random() > 0.04 ? 1 : -1;
  
            const randColor = Math.random();
            let color = 'rgba(255, 255, 255, 0.85)';
            if (randColor < 0.45) {
              color = 'rgba(148, 163, 184, 0.65)';
            } else if (randColor < 0.75) {
              color = 'rgba(71, 85, 105, 0.4)';
            } else if (randColor < 0.92) {
              color = 'rgba(255, 255, 255, 0.35)';
            } else {
              color = 'rgba(255, 255, 255, 0.95)';
            }
  
            const size = Math.random() > 0.85 ? (Math.random() > 0.95 ? 2.0 : 1.2) : 0.6 + Math.random() * 0.4;
  
            const cx = width / 2;
            const cy = height / 2;
            const px = cx + Math.cos(angle) * baseRadius;
            const py = cy + Math.sin(angle) * baseRadius;
  
            particles.push({
              x: px,
              y: py,
              vx: 0,
              vy: 0,
              baseRadius,
              angle,
              angularSpeed,
              orbitDirection,
              size,
              color,
            });
          }
        }
  
        initSimulation();
  
        const updateMouse = (clientX, clientY) => {
          const rect = canvas.getBoundingClientRect();
          mouse.x = clientX - rect.left;
          mouse.y = clientY - rect.top;
          mouse.active = true;
        };
  
        window.addEventListener('mousemove', (e) => {
          updateMouse(e.clientX, e.clientY);
        }, { passive: true });
  
        window.addEventListener('touchmove', (e) => {
          if (e.touches && e.touches[0]) {
            updateMouse(e.touches[0].clientX, e.touches[0].clientY);
          }
        }, { passive: true });
  
        const handleMouseLeave = () => {
          mouse.active = false;
          mouse.x = -1000;
          mouse.y = -1000;
        };
  
        window.addEventListener('touchend', handleMouseLeave);
        canvas.addEventListener('mouseleave', handleMouseLeave);
  
        window.addEventListener('message', (e) => {
          if (!e.data) return;
          if (e.data.type === 'mousemove') {
            mouse.x = e.data.x;
            mouse.y = e.data.y;
            mouse.active = true;
          } else if (e.data.type === 'mouseleave') {
            handleMouseLeave();
          }
        });
  
        window.addEventListener('resize', () => {
          initSimulation();
        });
  
        function render() {
          ctx.fillStyle = '#050505';
          ctx.fillRect(0, 0, width, height);
  
          const cx = width / 2;
          const cy = height / 2;
  
          const singGrad = ctx.createRadialGradient(cx, cy, 0, cx, cy, 140);
          singGrad.addColorStop(0, 'rgba(0, 0, 0, 1)');
          singGrad.addColorStop(0.5, 'rgba(3, 3, 4, 0.85)');
          singGrad.addColorStop(1, 'rgba(5, 5, 5, 0)');
          ctx.fillStyle = singGrad;
          ctx.beginPath();
          ctx.arc(cx, cy, 140, 0, Math.PI * 2);
          ctx.fill();
  
          if (mouse.active) {
            if (lerpedMouse.x === -1000) {
              lerpedMouse.x = mouse.x;
              lerpedMouse.y = mouse.y;
            } else {
              lerpedMouse.x += (mouse.x - lerpedMouse.x) * 0.12;
              lerpedMouse.y += (mouse.y - lerpedMouse.y) * 0.12;
            }
          } else {
            lerpedMouse.x = -1000;
            lerpedMouse.y = -1000;
          }
  
          const gravityRadius = 300;
          const springK = 0.025;
          const damping = 0.93;
          const maxVelocity = 14;
  
          for (let i = 0; i < particles.length; i++) {
            const p = particles[i];
            p.angle += p.angularSpeed * p.orbitDirection;
  
            const targetX = cx + Math.cos(p.angle) * p.baseRadius;
            const targetY = cy + Math.sin(p.angle) * p.baseRadius;
  
            if (lerpedMouse.x !== -1000) {
              const dx = lerpedMouse.x - p.x;
              const dy = lerpedMouse.y - p.y;
              const dist = Math.hypot(dx, dy);
  
              if (dist < gravityRadius && dist > 1.0) {
                const force = (gravityRadius - dist) / gravityRadius;
                const gravityPower = force * force * 1.6;
  
                const pullAngle = Math.atan2(dy, dx);
                p.vx += Math.cos(pullAngle) * gravityPower;
                p.vy += Math.sin(pullAngle) * gravityPower;
              }
            }
  
            const rx = targetX - p.x;
            const ry = targetY - p.y;
  
            p.vx += rx * springK;
            p.vy += ry * springK;
  
            p.vx *= damping;
            p.vy *= damping;
  
            const currentVelocity = Math.hypot(p.vx, p.vy);
            if (currentVelocity > maxVelocity) {
              p.vx = (p.vx / currentVelocity) * maxVelocity;
              p.vy = (p.vy / currentVelocity) * maxVelocity;
            }
  
            p.x += p.vx;
            p.y += p.vy;
  
            ctx.fillStyle = p.color;
            ctx.fillRect(p.x, p.y, p.size, p.size);
          }
  
          if (lerpedMouse.x !== -1000) {
            const mouseGlow = ctx.createRadialGradient(
              lerpedMouse.x, lerpedMouse.y, 0,
              lerpedMouse.x, lerpedMouse.y, 160
            );
            mouseGlow.addColorStop(0, 'rgba(255, 255, 255, 0.05)');
            mouseGlow.addColorStop(0.5, 'rgba(200, 210, 230, 0.01)');
            mouseGlow.addColorStop(1, 'rgba(0, 0, 0, 0)');
            ctx.fillStyle = mouseGlow;
            ctx.beginPath();
            ctx.arc(lerpedMouse.x, lerpedMouse.y, 160, 0, Math.PI * 2);
            ctx.fill();
          }
  
          requestAnimationFrame(render);
        }
  
        requestAnimationFrame(render);
      })();
    </script>
  </div>

  
</body>
</html>

React Component Wrapper (TSX)

import React from 'react';

export default function KineticGravityWellBackground() {
  

  return (
    <div 
       
      style={{ width: '100%', height: '100%', position: 'relative', overflow: 'hidden' }}
    >
      <style dangerouslySetInnerHTML={{ __html: `
        .kinetic-gravity-container {
          width: 100%;
          height: 100%;
          background: #050505;
          position: relative;
          overflow: hidden;
        }
        #gravity-canvas {
          width: 100%;
          height: 100%;
          display: block;
        }
      ` }} />
      
      {/* HTML Structure */}
      <div 
        style={{ width: '100%', height: '100%' }}
        dangerouslySetInnerHTML={{ __html: `
          <div class="kinetic-gravity-container" style="width: 100%; height: 100%; overflow: hidden; background: #050505;">
            <canvas id="gravity-canvas" style="width: 100%; height: 100%; display: block; filter: contrast(1.15) brightness(1.05);"></canvas>
            <script>
              (function() {
                const canvas = document.getElementById('gravity-canvas');
                if (!canvas) return;
                const ctx = canvas.getContext('2d');
                if (!ctx) return;
          
                let width = 0;
                let height = 0;
                const dpr = window.devicePixelRatio || 1;
          
                const mouse = { x: -1000, y: -1000, active: false };
                const lerpedMouse = { x: -1000, y: -1000 };
          
                const particles = [];
                const numParticles = 2200;
          
                function initSimulation() {
                  const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
                  width = rect ? rect.width : window.innerWidth;
                  height = rect ? rect.height : window.innerHeight;
          
                  canvas.width = width * dpr;
                  canvas.height = height * dpr;
                  ctx.scale(dpr, dpr);
          
                  particles.length = 0;
          
                  const maxDist = Math.max(width, height) * 0.7;
                  const ringBands = [
                    { min: 60, max: 80, density: 0.1 },
                    { min: 100, max: 130, density: 0.15 },
                    { min: 140, max: 180, density: 0.25 },
                    { min: 200, max: 215, density: 0.05 },
                    { min: 230, max: 280, density: 0.25 },
                    { min: 310, max: 380, density: 0.15 },
                    { min: 410, max: maxDist, density: 0.05 },
                  ];
          
                  for (let i = 0; i < numParticles; i++) {
                    let band = ringBands[2];
                    const rVal = Math.random();
                    let cumulative = 0;
                    for (const b of ringBands) {
                      cumulative += b.density;
                      if (rVal <= cumulative) {
                        band = b;
                        break;
                      }
                    }
          
                    const baseRadius = band.min + Math.random() * (band.max - band.min);
                    const angle = Math.random() * Math.PI * 2;
                    const baseSpeed = 0.001 + (3.5 / baseRadius);
                    const angularSpeed = baseSpeed * (0.85 + Math.random() * 0.3);
                    const orbitDirection = Math.random() > 0.04 ? 1 : -1;
          
                    const randColor = Math.random();
                    let color = 'rgba(255, 255, 255, 0.85)';
                    if (randColor < 0.45) {
                      color = 'rgba(148, 163, 184, 0.65)';
                    } else if (randColor < 0.75) {
                      color = 'rgba(71, 85, 105, 0.4)';
                    } else if (randColor < 0.92) {
                      color = 'rgba(255, 255, 255, 0.35)';
                    } else {
                      color = 'rgba(255, 255, 255, 0.95)';
                    }
          
                    const size = Math.random() > 0.85 ? (Math.random() > 0.95 ? 2.0 : 1.2) : 0.6 + Math.random() * 0.4;
          
                    const cx = width / 2;
                    const cy = height / 2;
                    const px = cx + Math.cos(angle) * baseRadius;
                    const py = cy + Math.sin(angle) * baseRadius;
          
                    particles.push({
                      x: px,
                      y: py,
                      vx: 0,
                      vy: 0,
                      baseRadius,
                      angle,
                      angularSpeed,
                      orbitDirection,
                      size,
                      color,
                    });
                  }
                }
          
                initSimulation();
          
                const updateMouse = (clientX, clientY) => {
                  const rect = canvas.getBoundingClientRect();
                  mouse.x = clientX - rect.left;
                  mouse.y = clientY - rect.top;
                  mouse.active = true;
                };
          
                window.addEventListener('mousemove', (e) => {
                  updateMouse(e.clientX, e.clientY);
                }, { passive: true });
          
                window.addEventListener('touchmove', (e) => {
                  if (e.touches && e.touches[0]) {
                    updateMouse(e.touches[0].clientX, e.touches[0].clientY);
                  }
                }, { passive: true });
          
                const handleMouseLeave = () => {
                  mouse.active = false;
                  mouse.x = -1000;
                  mouse.y = -1000;
                };
          
                window.addEventListener('touchend', handleMouseLeave);
                canvas.addEventListener('mouseleave', handleMouseLeave);
          
                window.addEventListener('message', (e) => {
                  if (!e.data) return;
                  if (e.data.type === 'mousemove') {
                    mouse.x = e.data.x;
                    mouse.y = e.data.y;
                    mouse.active = true;
                  } else if (e.data.type === 'mouseleave') {
                    handleMouseLeave();
                  }
                });
          
                window.addEventListener('resize', () => {
                  initSimulation();
                });
          
                function render() {
                  ctx.fillStyle = '#050505';
                  ctx.fillRect(0, 0, width, height);
          
                  const cx = width / 2;
                  const cy = height / 2;
          
                  const singGrad = ctx.createRadialGradient(cx, cy, 0, cx, cy, 140);
                  singGrad.addColorStop(0, 'rgba(0, 0, 0, 1)');
                  singGrad.addColorStop(0.5, 'rgba(3, 3, 4, 0.85)');
                  singGrad.addColorStop(1, 'rgba(5, 5, 5, 0)');
                  ctx.fillStyle = singGrad;
                  ctx.beginPath();
                  ctx.arc(cx, cy, 140, 0, Math.PI * 2);
                  ctx.fill();
          
                  if (mouse.active) {
                    if (lerpedMouse.x === -1000) {
                      lerpedMouse.x = mouse.x;
                      lerpedMouse.y = mouse.y;
                    } else {
                      lerpedMouse.x += (mouse.x - lerpedMouse.x) * 0.12;
                      lerpedMouse.y += (mouse.y - lerpedMouse.y) * 0.12;
                    }
                  } else {
                    lerpedMouse.x = -1000;
                    lerpedMouse.y = -1000;
                  }
          
                  const gravityRadius = 300;
                  const springK = 0.025;
                  const damping = 0.93;
                  const maxVelocity = 14;
          
                  for (let i = 0; i < particles.length; i++) {
                    const p = particles[i];
                    p.angle += p.angularSpeed * p.orbitDirection;
          
                    const targetX = cx + Math.cos(p.angle) * p.baseRadius;
                    const targetY = cy + Math.sin(p.angle) * p.baseRadius;
          
                    if (lerpedMouse.x !== -1000) {
                      const dx = lerpedMouse.x - p.x;
                      const dy = lerpedMouse.y - p.y;
                      const dist = Math.hypot(dx, dy);
          
                      if (dist < gravityRadius && dist > 1.0) {
                        const force = (gravityRadius - dist) / gravityRadius;
                        const gravityPower = force * force * 1.6;
          
                        const pullAngle = Math.atan2(dy, dx);
                        p.vx += Math.cos(pullAngle) * gravityPower;
                        p.vy += Math.sin(pullAngle) * gravityPower;
                      }
                    }
          
                    const rx = targetX - p.x;
                    const ry = targetY - p.y;
          
                    p.vx += rx * springK;
                    p.vy += ry * springK;
          
                    p.vx *= damping;
                    p.vy *= damping;
          
                    const currentVelocity = Math.hypot(p.vx, p.vy);
                    if (currentVelocity > maxVelocity) {
                      p.vx = (p.vx / currentVelocity) * maxVelocity;
                      p.vy = (p.vy / currentVelocity) * maxVelocity;
                    }
          
                    p.x += p.vx;
                    p.y += p.vy;
          
                    ctx.fillStyle = p.color;
                    ctx.fillRect(p.x, p.y, p.size, p.size);
                  }
          
                  if (lerpedMouse.x !== -1000) {
                    const mouseGlow = ctx.createRadialGradient(
                      lerpedMouse.x, lerpedMouse.y, 0,
                      lerpedMouse.x, lerpedMouse.y, 160
                    );
                    mouseGlow.addColorStop(0, 'rgba(255, 255, 255, 0.05)');
                    mouseGlow.addColorStop(0.5, 'rgba(200, 210, 230, 0.01)');
                    mouseGlow.addColorStop(1, 'rgba(0, 0, 0, 0)');
                    ctx.fillStyle = mouseGlow;
                    ctx.beginPath();
                    ctx.arc(lerpedMouse.x, lerpedMouse.y, 160, 0, Math.PI * 2);
                    ctx.fill();
                  }
          
                  requestAnimationFrame(render);
                }
          
                requestAnimationFrame(render);
              })();
            </script>
          </div>
        ` }}
      />
    </div>
  );
}

Next.js App Router Component (use client)

'use client';

import React from 'react';

export default function KineticGravityWellBackground() {
  

  return (
    <div 
       
      className="w-full h-full relative overflow-hidden"
    >
      <style dangerouslySetInnerHTML={{ __html: `
        .kinetic-gravity-container {
          width: 100%;
          height: 100%;
          background: #050505;
          position: relative;
          overflow: hidden;
        }
        #gravity-canvas {
          width: 100%;
          height: 100%;
          display: block;
        }
      ` }} />
      
      {/* HTML Structure */}
      <div 
        className="w-full h-full"
        dangerouslySetInnerHTML={{ __html: `
          <div class="kinetic-gravity-container" style="width: 100%; height: 100%; overflow: hidden; background: #050505;">
            <canvas id="gravity-canvas" style="width: 100%; height: 100%; display: block; filter: contrast(1.15) brightness(1.05);"></canvas>
            <script>
              (function() {
                const canvas = document.getElementById('gravity-canvas');
                if (!canvas) return;
                const ctx = canvas.getContext('2d');
                if (!ctx) return;
          
                let width = 0;
                let height = 0;
                const dpr = window.devicePixelRatio || 1;
          
                const mouse = { x: -1000, y: -1000, active: false };
                const lerpedMouse = { x: -1000, y: -1000 };
          
                const particles = [];
                const numParticles = 2200;
          
                function initSimulation() {
                  const rect = canvas.parentNode ? canvas.parentNode.getBoundingClientRect() : null;
                  width = rect ? rect.width : window.innerWidth;
                  height = rect ? rect.height : window.innerHeight;
          
                  canvas.width = width * dpr;
                  canvas.height = height * dpr;
                  ctx.scale(dpr, dpr);
          
                  particles.length = 0;
          
                  const maxDist = Math.max(width, height) * 0.7;
                  const ringBands = [
                    { min: 60, max: 80, density: 0.1 },
                    { min: 100, max: 130, density: 0.15 },
                    { min: 140, max: 180, density: 0.25 },
                    { min: 200, max: 215, density: 0.05 },
                    { min: 230, max: 280, density: 0.25 },
                    { min: 310, max: 380, density: 0.15 },
                    { min: 410, max: maxDist, density: 0.05 },
                  ];
          
                  for (let i = 0; i < numParticles; i++) {
                    let band = ringBands[2];
                    const rVal = Math.random();
                    let cumulative = 0;
                    for (const b of ringBands) {
                      cumulative += b.density;
                      if (rVal <= cumulative) {
                        band = b;
                        break;
                      }
                    }
          
                    const baseRadius = band.min + Math.random() * (band.max - band.min);
                    const angle = Math.random() * Math.PI * 2;
                    const baseSpeed = 0.001 + (3.5 / baseRadius);
                    const angularSpeed = baseSpeed * (0.85 + Math.random() * 0.3);
                    const orbitDirection = Math.random() > 0.04 ? 1 : -1;
          
                    const randColor = Math.random();
                    let color = 'rgba(255, 255, 255, 0.85)';
                    if (randColor < 0.45) {
                      color = 'rgba(148, 163, 184, 0.65)';
                    } else if (randColor < 0.75) {
                      color = 'rgba(71, 85, 105, 0.4)';
                    } else if (randColor < 0.92) {
                      color = 'rgba(255, 255, 255, 0.35)';
                    } else {
                      color = 'rgba(255, 255, 255, 0.95)';
                    }
          
                    const size = Math.random() > 0.85 ? (Math.random() > 0.95 ? 2.0 : 1.2) : 0.6 + Math.random() * 0.4;
          
                    const cx = width / 2;
                    const cy = height / 2;
                    const px = cx + Math.cos(angle) * baseRadius;
                    const py = cy + Math.sin(angle) * baseRadius;
          
                    particles.push({
                      x: px,
                      y: py,
                      vx: 0,
                      vy: 0,
                      baseRadius,
                      angle,
                      angularSpeed,
                      orbitDirection,
                      size,
                      color,
                    });
                  }
                }
          
                initSimulation();
          
                const updateMouse = (clientX, clientY) => {
                  const rect = canvas.getBoundingClientRect();
                  mouse.x = clientX - rect.left;
                  mouse.y = clientY - rect.top;
                  mouse.active = true;
                };
          
                window.addEventListener('mousemove', (e) => {
                  updateMouse(e.clientX, e.clientY);
                }, { passive: true });
          
                window.addEventListener('touchmove', (e) => {
                  if (e.touches && e.touches[0]) {
                    updateMouse(e.touches[0].clientX, e.touches[0].clientY);
                  }
                }, { passive: true });
          
                const handleMouseLeave = () => {
                  mouse.active = false;
                  mouse.x = -1000;
                  mouse.y = -1000;
                };
          
                window.addEventListener('touchend', handleMouseLeave);
                canvas.addEventListener('mouseleave', handleMouseLeave);
          
                window.addEventListener('message', (e) => {
                  if (!e.data) return;
                  if (e.data.type === 'mousemove') {
                    mouse.x = e.data.x;
                    mouse.y = e.data.y;
                    mouse.active = true;
                  } else if (e.data.type === 'mouseleave') {
                    handleMouseLeave();
                  }
                });
          
                window.addEventListener('resize', () => {
                  initSimulation();
                });
          
                function render() {
                  ctx.fillStyle = '#050505';
                  ctx.fillRect(0, 0, width, height);
          
                  const cx = width / 2;
                  const cy = height / 2;
          
                  const singGrad = ctx.createRadialGradient(cx, cy, 0, cx, cy, 140);
                  singGrad.addColorStop(0, 'rgba(0, 0, 0, 1)');
                  singGrad.addColorStop(0.5, 'rgba(3, 3, 4, 0.85)');
                  singGrad.addColorStop(1, 'rgba(5, 5, 5, 0)');
                  ctx.fillStyle = singGrad;
                  ctx.beginPath();
                  ctx.arc(cx, cy, 140, 0, Math.PI * 2);
                  ctx.fill();
          
                  if (mouse.active) {
                    if (lerpedMouse.x === -1000) {
                      lerpedMouse.x = mouse.x;
                      lerpedMouse.y = mouse.y;
                    } else {
                      lerpedMouse.x += (mouse.x - lerpedMouse.x) * 0.12;
                      lerpedMouse.y += (mouse.y - lerpedMouse.y) * 0.12;
                    }
                  } else {
                    lerpedMouse.x = -1000;
                    lerpedMouse.y = -1000;
                  }
          
                  const gravityRadius = 300;
                  const springK = 0.025;
                  const damping = 0.93;
                  const maxVelocity = 14;
          
                  for (let i = 0; i < particles.length; i++) {
                    const p = particles[i];
                    p.angle += p.angularSpeed * p.orbitDirection;
          
                    const targetX = cx + Math.cos(p.angle) * p.baseRadius;
                    const targetY = cy + Math.sin(p.angle) * p.baseRadius;
          
                    if (lerpedMouse.x !== -1000) {
                      const dx = lerpedMouse.x - p.x;
                      const dy = lerpedMouse.y - p.y;
                      const dist = Math.hypot(dx, dy);
          
                      if (dist < gravityRadius && dist > 1.0) {
                        const force = (gravityRadius - dist) / gravityRadius;
                        const gravityPower = force * force * 1.6;
          
                        const pullAngle = Math.atan2(dy, dx);
                        p.vx += Math.cos(pullAngle) * gravityPower;
                        p.vy += Math.sin(pullAngle) * gravityPower;
                      }
                    }
          
                    const rx = targetX - p.x;
                    const ry = targetY - p.y;
          
                    p.vx += rx * springK;
                    p.vy += ry * springK;
          
                    p.vx *= damping;
                    p.vy *= damping;
          
                    const currentVelocity = Math.hypot(p.vx, p.vy);
                    if (currentVelocity > maxVelocity) {
                      p.vx = (p.vx / currentVelocity) * maxVelocity;
                      p.vy = (p.vy / currentVelocity) * maxVelocity;
                    }
          
                    p.x += p.vx;
                    p.y += p.vy;
          
                    ctx.fillStyle = p.color;
                    ctx.fillRect(p.x, p.y, p.size, p.size);
                  }
          
                  if (lerpedMouse.x !== -1000) {
                    const mouseGlow = ctx.createRadialGradient(
                      lerpedMouse.x, lerpedMouse.y, 0,
                      lerpedMouse.x, lerpedMouse.y, 160
                    );
                    mouseGlow.addColorStop(0, 'rgba(255, 255, 255, 0.05)');
                    mouseGlow.addColorStop(0.5, 'rgba(200, 210, 230, 0.01)');
                    mouseGlow.addColorStop(1, 'rgba(0, 0, 0, 0)');
                    ctx.fillStyle = mouseGlow;
                    ctx.beginPath();
                    ctx.arc(lerpedMouse.x, lerpedMouse.y, 160, 0, Math.PI * 2);
                    ctx.fill();
                  }
          
                  requestAnimationFrame(render);
                }
          
                requestAnimationFrame(render);
              })();
            </script>
          </div>
        ` }}
      />
    </div>
  );
}

Raw CSS Stylesheet Snippet

.kinetic-gravity-container {
  width: 100%;
  height: 100%;
  background: #050505;
  position: relative;
  overflow: hidden;
}
#gravity-canvas {
  width: 100%;
  height: 100%;
  display: block;
}