-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
141 lines (123 loc) · 4.44 KB
/
Copy pathscript.js
File metadata and controls
141 lines (123 loc) · 4.44 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
133
134
135
136
137
138
139
140
141
/* ============================================
SCRAMBLE EFFECT — Hero subtitle
============================================ */
document.addEventListener("DOMContentLoaded", () => {
const el = document.getElementById("typed-output");
const words = ["Bug Bounty Hunter.", "Security Researcher.", "CTF Player."];
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@#$%&!?";
let wordIndex = 0;
// Add blinking cursor via JS so it matches your typed-cursor style
el.style.borderRight = "2px solid var(--accent)";
el.style.paddingRight = "4px";
setInterval(() => {
el.style.borderRightColor =
el.style.borderRightColor === "transparent"
? "var(--accent)"
: "transparent";
}, 500);
function scrambleTo(newWord) {
const duration = 1200;
const start = performance.now();
return new Promise((resolve) => {
function update(now) {
const elapsed = now - start;
const progress = Math.min(elapsed / duration, 1);
el.textContent = newWord
.split("")
.map((char, i) => {
if (char === " ") return " ";
const charProgress = Math.min((progress - i * 0.04) / 0.45, 1);
if (charProgress >= 1) return char;
if (charProgress <= 0)
return chars[Math.floor(Math.random() * chars.length)];
return chars[Math.floor(Math.random() * chars.length)];
})
.join("");
if (progress < 1) {
requestAnimationFrame(update);
} else {
el.textContent = newWord;
resolve();
}
}
requestAnimationFrame(update);
});
}
async function loop() {
while (true) {
await scrambleTo(words[wordIndex]);
await new Promise((r) => setTimeout(r, 2200));
wordIndex = (wordIndex + 1) % words.length;
}
}
loop();
/* ============================================
NAVBAR — highlight active section on scroll
============================================ */
const sections = document.querySelectorAll("section[id]");
const navLinks = document.querySelectorAll(".nav-link");
const mainNav = document.getElementById("mainNav");
const observerOptions = {
root: null,
rootMargin: "-40% 0px -55% 0px",
threshold: 0,
};
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const id = entry.target.getAttribute("id");
navLinks.forEach((link) => {
link.classList.toggle(
"active-link",
link.getAttribute("href") === `#${id}`
);
});
}
});
}, observerOptions);
sections.forEach((sec) => observer.observe(sec));
/* ============================================
NAVBAR — add .scrolled class after scroll
============================================ */
window.addEventListener("scroll", () => {
mainNav.classList.toggle("scrolled", window.scrollY > 50);
});
/* ============================================
SMOOTH SCROLL — close mobile nav on link click
============================================ */
navLinks.forEach((link) => {
link.addEventListener("click", () => {
const collapseEl = document.getElementById("navMenu");
const bsCollapse = bootstrap.Collapse.getInstance(collapseEl);
if (bsCollapse) bsCollapse.hide();
});
});
});
/* ============================================
STATS — count-up animation on scroll into view
============================================ */
const statNumbers = document.querySelectorAll(".stat-number");
const animateCount = (el) => {
const target = parseInt(el.getAttribute("data-target"), 10) || 0;
const duration = 2500;
const startTime = performance.now();
const step = (now) => {
const progress = Math.min((now - startTime) / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3);
el.textContent = Math.round(eased * target);
if (progress < 1) requestAnimationFrame(step);
};
requestAnimationFrame(step);
};
const statObserver = new IntersectionObserver(
(entries, obs) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
animateCount(entry.target);
obs.unobserve(entry.target);
}
});
},
{ threshold: 0.5 }
);
statNumbers.forEach((el) => statObserver.observe(el));