|
@@ -1,224 +0,0 @@
|
|
|
-<!DOCTYPE html>
|
|
|
|
|
-<html lang="en">
|
|
|
|
|
-<head>
|
|
|
|
|
- <meta charset="UTF-8" />
|
|
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
|
|
|
- <title>nodes</title>
|
|
|
|
|
- <style>
|
|
|
|
|
- :root {
|
|
|
|
|
- --node-color: #ff00ff;
|
|
|
|
|
- --bg-1: #05010a;
|
|
|
|
|
- --bg-2: #13051d;
|
|
|
|
|
- --line-alpha: 0.28;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- * {
|
|
|
|
|
- box-sizing: border-box;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- html, body {
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- background:
|
|
|
|
|
- radial-gradient(circle at 20% 20%, rgba(255, 0, 255, 0.08), transparent 30%),
|
|
|
|
|
- radial-gradient(circle at 80% 30%, rgba(255, 0, 255, 0.06), transparent 24%),
|
|
|
|
|
- linear-gradient(160deg, var(--bg-1), var(--bg-2));
|
|
|
|
|
- font-family: Inter, Arial, sans-serif;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- canvas {
|
|
|
|
|
- display: block;
|
|
|
|
|
- width: 100vw;
|
|
|
|
|
- height: 100vh;
|
|
|
|
|
- cursor: crosshair;
|
|
|
|
|
- }
|
|
|
|
|
- </style>
|
|
|
|
|
-</head>
|
|
|
|
|
-<body>
|
|
|
|
|
- <canvas id="network"></canvas>
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- <script>
|
|
|
|
|
- const canvas = document.getElementById('network');
|
|
|
|
|
- const ctx = canvas.getContext('2d');
|
|
|
|
|
-
|
|
|
|
|
- const CONFIG = {
|
|
|
|
|
- nodeCount: 90,
|
|
|
|
|
- nodeColor: '#ff00ff',
|
|
|
|
|
- nodeRadiusMin: 2.2,
|
|
|
|
|
- nodeRadiusMax: 5.5,
|
|
|
|
|
- linkDistance: 150,
|
|
|
|
|
- mouseInfluence: 190,
|
|
|
|
|
- attractionStrength: 0.02,
|
|
|
|
|
- returnStrength: 0.012,
|
|
|
|
|
- friction: 0.92,
|
|
|
|
|
- pulseSpeed: 0.0025,
|
|
|
|
|
- wobble: 0.18,
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- let width = 0;
|
|
|
|
|
- let height = 0;
|
|
|
|
|
- let dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
|
|
|
- let time = 0;
|
|
|
|
|
-
|
|
|
|
|
- const mouse = {
|
|
|
|
|
- x: 0,
|
|
|
|
|
- y: 0,
|
|
|
|
|
- active: false,
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- class Node {
|
|
|
|
|
- constructor() {
|
|
|
|
|
- this.homeX = Math.random() * width;
|
|
|
|
|
- this.homeY = Math.random() * height;
|
|
|
|
|
- this.x = this.homeX;
|
|
|
|
|
- this.y = this.homeY;
|
|
|
|
|
- this.vx = 0;
|
|
|
|
|
- this.vy = 0;
|
|
|
|
|
- this.radius = CONFIG.nodeRadiusMin + Math.random() * (CONFIG.nodeRadiusMax - CONFIG.nodeRadiusMin);
|
|
|
|
|
- this.seed = Math.random() * Math.PI * 2;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- update(t) {
|
|
|
|
|
- const dxHome = this.homeX - this.x;
|
|
|
|
|
- const dyHome = this.homeY - this.y;
|
|
|
|
|
-
|
|
|
|
|
- this.vx += dxHome * CONFIG.returnStrength;
|
|
|
|
|
- this.vy += dyHome * CONFIG.returnStrength;
|
|
|
|
|
-
|
|
|
|
|
- const wobbleX = Math.cos(t * CONFIG.pulseSpeed + this.seed) * CONFIG.wobble;
|
|
|
|
|
- const wobbleY = Math.sin(t * CONFIG.pulseSpeed * 0.85 + this.seed) * CONFIG.wobble;
|
|
|
|
|
- this.vx += wobbleX * 0.03;
|
|
|
|
|
- this.vy += wobbleY * 0.03;
|
|
|
|
|
-
|
|
|
|
|
- if (mouse.active) {
|
|
|
|
|
- const dx = mouse.x - this.x;
|
|
|
|
|
- const dy = mouse.y - this.y;
|
|
|
|
|
- const dist = Math.hypot(dx, dy) || 0.0001;
|
|
|
|
|
-
|
|
|
|
|
- if (dist < CONFIG.mouseInfluence) {
|
|
|
|
|
- const force = (1 - dist / CONFIG.mouseInfluence) * CONFIG.attractionStrength * 28;
|
|
|
|
|
- this.vx += (dx / dist) * force;
|
|
|
|
|
- this.vy += (dy / dist) * force;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- this.vx *= CONFIG.friction;
|
|
|
|
|
- this.vy *= CONFIG.friction;
|
|
|
|
|
- this.x += this.vx;
|
|
|
|
|
- this.y += this.vy;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- draw(t) {
|
|
|
|
|
- const pulse = 1 + Math.sin(t * 0.004 + this.seed) * 0.16;
|
|
|
|
|
- const r = this.radius * pulse;
|
|
|
|
|
-
|
|
|
|
|
- ctx.beginPath();
|
|
|
|
|
- ctx.arc(this.x, this.y, r, 0, Math.PI * 2);
|
|
|
|
|
- ctx.fillStyle = CONFIG.nodeColor;
|
|
|
|
|
- ctx.shadowColor = CONFIG.nodeColor;
|
|
|
|
|
- ctx.shadowBlur = 16;
|
|
|
|
|
- ctx.fill();
|
|
|
|
|
- ctx.shadowBlur = 0;
|
|
|
|
|
-
|
|
|
|
|
- ctx.beginPath();
|
|
|
|
|
- ctx.arc(this.x, this.y, r * 2.1, 0, Math.PI * 2);
|
|
|
|
|
- ctx.fillStyle = 'rgba(255, 0, 255, 0.05)';
|
|
|
|
|
- ctx.fill();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let nodes = [];
|
|
|
|
|
-
|
|
|
|
|
- function resize() {
|
|
|
|
|
- width = window.innerWidth;
|
|
|
|
|
- height = window.innerHeight;
|
|
|
|
|
- dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
|
|
|
-
|
|
|
|
|
- canvas.width = width * dpr;
|
|
|
|
|
- canvas.height = height * dpr;
|
|
|
|
|
- canvas.style.width = width + 'px';
|
|
|
|
|
- canvas.style.height = height + 'px';
|
|
|
|
|
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
|
|
|
-
|
|
|
|
|
- nodes = Array.from({ length: CONFIG.nodeCount }, () => new Node());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function drawLinks() {
|
|
|
|
|
- for (let i = 0; i < nodes.length; i++) {
|
|
|
|
|
- for (let j = i + 1; j < nodes.length; j++) {
|
|
|
|
|
- const a = nodes[i];
|
|
|
|
|
- const b = nodes[j];
|
|
|
|
|
- const dx = b.x - a.x;
|
|
|
|
|
- const dy = b.y - a.y;
|
|
|
|
|
- const dist = Math.hypot(dx, dy);
|
|
|
|
|
-
|
|
|
|
|
- if (dist < CONFIG.linkDistance) {
|
|
|
|
|
- const alpha = (1 - dist / CONFIG.linkDistance) * 0.65;
|
|
|
|
|
- ctx.beginPath();
|
|
|
|
|
- ctx.moveTo(a.x, a.y);
|
|
|
|
|
- ctx.lineTo(b.x, b.y);
|
|
|
|
|
- ctx.strokeStyle = `rgba(255, 0, 255, ${alpha * 0.65})`;
|
|
|
|
|
- ctx.lineWidth = 1;
|
|
|
|
|
- ctx.stroke();
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- function animate(timestamp) {
|
|
|
|
|
- time = timestamp;
|
|
|
|
|
- ctx.clearRect(0, 0, width, height);
|
|
|
|
|
-
|
|
|
|
|
- const gradient = ctx.createRadialGradient(
|
|
|
|
|
- mouse.active ? mouse.x : width * 0.5,
|
|
|
|
|
- mouse.active ? mouse.y : height * 0.5,
|
|
|
|
|
- 0,
|
|
|
|
|
- width * 0.5,
|
|
|
|
|
- height * 0.5,
|
|
|
|
|
- Math.max(width, height) * 0.7
|
|
|
|
|
- );
|
|
|
|
|
- gradient.addColorStop(0, 'rgba(255, 0, 255, 0.06)');
|
|
|
|
|
- gradient.addColorStop(1, 'rgba(255, 0, 255, 0)');
|
|
|
|
|
- ctx.fillStyle = gradient;
|
|
|
|
|
- ctx.fillRect(0, 0, width, height);
|
|
|
|
|
-
|
|
|
|
|
- nodes.forEach(node => node.update(timestamp));
|
|
|
|
|
- drawLinks();
|
|
|
|
|
- nodes.forEach(node => node.draw(timestamp));
|
|
|
|
|
-
|
|
|
|
|
- requestAnimationFrame(animate);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- window.addEventListener('mousemove', (event) => {
|
|
|
|
|
- mouse.x = event.clientX;
|
|
|
|
|
- mouse.y = event.clientY;
|
|
|
|
|
- mouse.active = true;
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- window.addEventListener('touchmove', (event) => {
|
|
|
|
|
- if (event.touches[0]) {
|
|
|
|
|
- mouse.x = event.touches[0].clientX;
|
|
|
|
|
- mouse.y = event.touches[0].clientY;
|
|
|
|
|
- mouse.active = true;
|
|
|
|
|
- }
|
|
|
|
|
- }, { passive: true });
|
|
|
|
|
-
|
|
|
|
|
- window.addEventListener('mouseleave', () => {
|
|
|
|
|
- mouse.active = false;
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- window.addEventListener('touchend', () => {
|
|
|
|
|
- mouse.active = false;
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- window.addEventListener('resize', resize);
|
|
|
|
|
-
|
|
|
|
|
- resize();
|
|
|
|
|
- requestAnimationFrame(animate);
|
|
|
|
|
- </script>
|
|
|
|
|
-</body>
|
|
|
|
|
-</html>
|
|
|