-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
132 lines (122 loc) · 4.32 KB
/
script.js
File metadata and controls
132 lines (122 loc) · 4.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/*
* JavaScript to power the interactive elements on Mohith Kasula's portfolio.
* This script handles the animated particle network on the hero section
* and animates skill progress bars when they enter the viewport.
*/
document.addEventListener('DOMContentLoaded', () => {
initNetworkCanvas();
initSkillAnimation();
});
/**
* Creates an animated network of particles behind the hero text. Particles move
* randomly and connect with lines when within a certain distance. The effect
* is inspired by neural networks and healthcare data connectivity.
*/
function initNetworkCanvas() {
const canvas = document.getElementById('network-canvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
let width, height;
const particles = [];
const NUM_PARTICLES = 70;
const MAX_DISTANCE = 120;
// Resize canvas to full hero dimensions
function resize() {
width = canvas.width = window.innerWidth;
// Height equals the hero section height to avoid scrolling issues
const hero = document.getElementById('hero');
height = canvas.height = hero ? hero.offsetHeight : window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
// Particle constructor
function Particle() {
this.x = Math.random() * width;
this.y = Math.random() * height;
const angle = Math.random() * Math.PI * 2;
const speed = 0.5 + Math.random() * 0.7;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
this.radius = 2 + Math.random() * 2;
}
// Create initial particles
for (let i = 0; i < NUM_PARTICLES; i++) {
particles.push(new Particle());
}
// Update particle positions and draw network
function update() {
ctx.clearRect(0, 0, width, height);
// Draw connections first to avoid overlaying on top of particles
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const p1 = particles[i];
const p2 = particles[j];
const dx = p1.x - p2.x;
const dy = p1.y - p2.y;
const dist = Math.hypot(dx, dy);
if (dist < MAX_DISTANCE) {
const alpha = 1 - dist / MAX_DISTANCE;
ctx.strokeStyle = `rgba(0, 168, 232, ${alpha * 0.5})`;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
}
}
}
// Draw particles and update positions
for (const p of particles) {
ctx.fillStyle = '#00a8e8';
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
ctx.fill();
p.x += p.vx;
p.y += p.vy;
// Bounce off edges
if (p.x < 0 || p.x > width) p.vx *= -1;
if (p.y < 0 || p.y > height) p.vy *= -1;
}
requestAnimationFrame(update);
}
update();
}
/**
* Animates skill bars when they enter the viewport using an IntersectionObserver.
* Each progress bar has a data-value attribute that determines its final width.
*/
function initSkillAnimation() {
const progressBars = document.querySelectorAll('.progress');
if (progressBars.length === 0) return;
// Immediately set progress values on page load as a fallback so bars
// appear on devices that don't support hover/focus events (e.g. mobile).
progressBars.forEach(bar => {
const val = bar.getAttribute('data-value');
// Initialize the custom CSS property used to control the filled width.
bar.style.setProperty('--progress', val + '%');
});
// Define options for the IntersectionObserver to animate the bars when
// the skills section enters the viewport. The observer provides a smooth
// animation effect on larger screens, but isn't strictly necessary on
// touch devices, which will already have the widths set.
const options = {
threshold: 0.4
};
const animateBars = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
progressBars.forEach(bar => {
const val = bar.getAttribute('data-value');
bar.style.setProperty('--progress', val + '%');
});
// Only animate once to improve performance
observer.unobserve(entry.target);
}
});
};
const observer = new IntersectionObserver(animateBars, options);
const skillsSection = document.getElementById('skills');
if (skillsSection) {
observer.observe(skillsSection);
}
}