Skip to content

Commit 10b42f9

Browse files
committed
(feat) updated
1 parent 487f540 commit 10b42f9

File tree

5 files changed

+431
-43
lines changed

5 files changed

+431
-43
lines changed

assets/js/utils/background.js

Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
// Wait for DOM to be fully loaded
2+
document.addEventListener("DOMContentLoaded", () => {
3+
// Set current year in footer
4+
const currentYearElements = document.querySelectorAll("#currentYear");
5+
currentYearElements.forEach((el) => {
6+
if (el) el.textContent = new Date().getFullYear();
7+
});
8+
9+
// Theme toggle functionality
10+
const themeToggle = document.getElementById("themeToggle");
11+
const body = document.body;
12+
13+
// Check for saved theme preference
14+
const savedTheme = localStorage.getItem("theme");
15+
if (savedTheme) {
16+
body.className = savedTheme === "light-mode" ? "light-mode" : "dark-mode";
17+
} else {
18+
// Use system preference as default
19+
if (
20+
window.matchMedia &&
21+
window.matchMedia("(prefers-color-scheme: light)").matches
22+
) {
23+
body.className = "light-mode";
24+
} else {
25+
body.className = "dark-mode";
26+
}
27+
}
28+
29+
// Toggle theme when button is clicked
30+
if (themeToggle) {
31+
themeToggle.addEventListener("click", () => {
32+
const isLightMode = body.classList.contains("light-mode");
33+
34+
if (isLightMode) {
35+
body.classList.replace("light-mode", "dark-mode");
36+
localStorage.setItem("theme", "dark-mode");
37+
} else {
38+
body.classList.replace("dark-mode", "light-mode");
39+
localStorage.setItem("theme", "light-mode");
40+
}
41+
});
42+
}
43+
44+
// Share functionality
45+
const shareBtn = document.getElementById("shareBtn");
46+
if (shareBtn) {
47+
shareBtn.addEventListener("click", async () => {
48+
try {
49+
if (navigator.share) {
50+
await navigator.share({
51+
title: document.title,
52+
text: "Check out my portfolio!",
53+
url: window.location.href,
54+
});
55+
} else {
56+
// Fallback for browsers that don't support Web Share API
57+
navigator.clipboard.writeText(window.location.href);
58+
alert("Link copied to clipboard!");
59+
}
60+
} catch (err) {
61+
console.error("Error sharing:", err);
62+
}
63+
});
64+
}
65+
66+
// Initialize particle animation
67+
initParticleAnimation();
68+
69+
// Portfolio filter functionality
70+
const filterBtns = document.querySelectorAll(".filter-btn");
71+
const portfolioItems = document.querySelectorAll(".portfolio-item");
72+
73+
if (filterBtns.length > 0 && portfolioItems.length > 0) {
74+
filterBtns.forEach((btn) => {
75+
btn.addEventListener("click", () => {
76+
// Remove active class from all buttons
77+
filterBtns.forEach((b) => b.classList.remove("active"));
78+
// Add active class to clicked button
79+
btn.classList.add("active");
80+
81+
const filter = btn.getAttribute("data-filter");
82+
83+
// Filter portfolio items
84+
portfolioItems.forEach((item) => {
85+
if (
86+
filter === "all" ||
87+
item.getAttribute("data-category") === filter
88+
) {
89+
item.style.display = "block";
90+
setTimeout(() => {
91+
item.style.opacity = "1";
92+
item.style.transform = "translateY(0)";
93+
}, 100);
94+
} else {
95+
item.style.opacity = "0";
96+
item.style.transform = "translateY(20px)";
97+
setTimeout(() => {
98+
item.style.display = "none";
99+
}, 300);
100+
}
101+
});
102+
});
103+
});
104+
}
105+
106+
// Animate skill bars on scroll
107+
const skillBars = document.querySelectorAll(".skill-progress-bar");
108+
if (skillBars.length > 0) {
109+
const animateSkills = () => {
110+
skillBars.forEach((bar) => {
111+
const barPosition = bar.getBoundingClientRect().top;
112+
const screenPosition = window.innerHeight / 1.2;
113+
114+
if (barPosition < screenPosition) {
115+
const width =
116+
bar.parentElement.previousElementSibling.lastElementChild
117+
.textContent;
118+
bar.style.setProperty("--skill-percent", width);
119+
}
120+
});
121+
};
122+
123+
window.addEventListener("scroll", animateSkills);
124+
// Initial check
125+
setTimeout(animateSkills, 500);
126+
}
127+
128+
// Track link clicks (for analytics purposes)
129+
document.querySelectorAll("a").forEach((link) => {
130+
link.addEventListener("click", function (e) {
131+
// Don't track internal links
132+
if (this.hostname === window.location.hostname) return;
133+
134+
const linkText = this.textContent.trim();
135+
const linkHref = this.href;
136+
137+
console.log(`Link clicked: ${linkText} (${linkHref})`);
138+
// In a real implementation, you would send this data to your analytics service
139+
});
140+
});
141+
142+
// Add jump to top button
143+
const jumpToTopContainer = document.body;
144+
const jumpToTopBtn = document.createElement("button");
145+
jumpToTopBtn.className = "jump-to-top";
146+
jumpToTopBtn.innerHTML = '<i class="fas fa-arrow-up"></i>';
147+
jumpToTopBtn.setAttribute("aria-label", "Jump to top");
148+
jumpToTopBtn.setAttribute("title", "Jump to top");
149+
jumpToTopContainer.appendChild(jumpToTopBtn);
150+
151+
// Show/hide jump to top button on scroll
152+
window.addEventListener("scroll", () => {
153+
if (window.pageYOffset > 300) {
154+
jumpToTopBtn.classList.add("visible");
155+
} else {
156+
jumpToTopBtn.classList.remove("visible");
157+
}
158+
});
159+
160+
// Scroll to top when button is clicked
161+
jumpToTopBtn.addEventListener("click", () => {
162+
window.scrollTo({
163+
top: 0,
164+
behavior: "smooth",
165+
});
166+
});
167+
});
168+
169+
// Particle animation
170+
function initParticleAnimation() {
171+
const canvas = document.getElementById("particleCanvas");
172+
if (!canvas) return;
173+
174+
const ctx = canvas.getContext("2d");
175+
let particles = [];
176+
let mouseX = 0;
177+
let mouseY = 0;
178+
let width, height;
179+
const isDarkMode = document.body.classList.contains("dark-mode");
180+
181+
// Set canvas dimensions
182+
function resizeCanvas() {
183+
width = window.innerWidth;
184+
height = window.innerHeight;
185+
canvas.width = width;
186+
canvas.height = height;
187+
createParticles();
188+
}
189+
190+
// Create particles
191+
function createParticles() {
192+
particles = [];
193+
const particleCount = Math.min(Math.floor((width * height) / 10000), 150);
194+
195+
for (let i = 0; i < particleCount; i++) {
196+
particles.push({
197+
x: Math.random() * width,
198+
y: Math.random() * height,
199+
radius: Math.random() * 3 + 1,
200+
color: getParticleColor(),
201+
speed: Math.random() * 0.5 + 0.2,
202+
direction: Math.random() * Math.PI * 2,
203+
opacity: Math.random() * 0.5 + 0.2,
204+
connected: [],
205+
});
206+
}
207+
}
208+
209+
/* // Get particle color based on theme
210+
function getParticleColor() {
211+
const darkColors = [
212+
"#7c3aed",
213+
"#a78bfa",
214+
"#c4b5fd",
215+
"#4f46e5",
216+
"#2563eb",
217+
"#06b6d4",
218+
"#10b981",
219+
"#ec4899",
220+
"#f59e0b",
221+
];
222+
const lightColors = [
223+
"#7c3aed",
224+
"#a78bfa",
225+
"#c4b5fd",
226+
"#4f46e5",
227+
"#2563eb",
228+
"#06b6d4",
229+
"#10b981",
230+
"#ec4899",
231+
"#f59e0b",
232+
];
233+
234+
const colors = isDarkMode ? darkColors : lightColors;
235+
return colors[Math.floor(Math.random() * colors.length)];
236+
} */
237+
238+
// Track mouse position
239+
document.addEventListener("mousemove", (e) => {
240+
mouseX = e.clientX;
241+
mouseY = e.clientY;
242+
});
243+
244+
// Draw particles
245+
function drawParticles() {
246+
ctx.clearRect(0, 0, width, height);
247+
248+
// Update and draw particles
249+
particles.forEach((p, i) => {
250+
// Move particle
251+
p.x += Math.cos(p.direction) * p.speed;
252+
p.y += Math.sin(p.direction) * p.speed;
253+
254+
// Bounce off edges
255+
if (p.x < 0 || p.x > width) {
256+
p.direction = Math.PI - p.direction;
257+
}
258+
if (p.y < 0 || p.y > height) {
259+
p.direction = -p.direction;
260+
}
261+
262+
// Slight attraction to mouse
263+
const dx = mouseX - p.x;
264+
const dy = mouseY - p.y;
265+
const distance = Math.sqrt(dx * dx + dy * dy);
266+
267+
if (distance < 200) {
268+
const angle = Math.atan2(dy, dx);
269+
p.x += Math.cos(angle) * 0.2;
270+
p.y += Math.sin(angle) * 0.2;
271+
}
272+
273+
// Draw particle
274+
ctx.beginPath();
275+
ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
276+
ctx.fillStyle = p.color;
277+
ctx.globalAlpha = p.opacity;
278+
ctx.fill();
279+
280+
// Reset connections
281+
p.connected = [];
282+
});
283+
284+
// Draw connections between particles
285+
ctx.globalAlpha = 0.2;
286+
particles.forEach((p1, i) => {
287+
particles.forEach((p2, j) => {
288+
if (i !== j && !p1.connected.includes(j) && !p2.connected.includes(i)) {
289+
const dx = p1.x - p2.x;
290+
const dy = p1.y - p2.y;
291+
const distance = Math.sqrt(dx * dx + dy * dy);
292+
293+
if (distance < 150) {
294+
p1.connected.push(j);
295+
p2.connected.push(i);
296+
297+
ctx.beginPath();
298+
ctx.moveTo(p1.x, p1.y);
299+
ctx.lineTo(p2.x, p2.y);
300+
ctx.strokeStyle = p1.color;
301+
ctx.lineWidth = 0.5;
302+
ctx.stroke();
303+
}
304+
}
305+
});
306+
});
307+
308+
requestAnimationFrame(drawParticles);
309+
}
310+
311+
// Initialize
312+
window.addEventListener("resize", resizeCanvas);
313+
resizeCanvas();
314+
drawParticles();
315+
}
316+
317+
// Logo Dark/Light
318+
319+
document.addEventListener("DOMContentLoaded", function () {
320+
const logo = document.getElementById("themeLogo");
321+
if (!logo) return;
322+
323+
// Detect if we're on a subpage (about.html, etc.)
324+
const isSubPage = window.location.pathname.includes("/pages/");
325+
326+
function setLogoByTheme() {
327+
const isDark = document.body.classList.contains("dark-mode");
328+
// FIX: Correct path for subpages
329+
const basePath = isSubPage
330+
? "../assets/images/brand/"
331+
: "./assets/images/brand/";
332+
logo.src = isDark
333+
? basePath + "white-logo.svg"
334+
: basePath + "black-logo.svg";
335+
}
336+
337+
// Initial set
338+
setLogoByTheme();
339+
340+
// Listen for theme changes
341+
const themeToggle = document.getElementById("themeToggle");
342+
if (themeToggle) {
343+
themeToggle.addEventListener("click", function () {
344+
setTimeout(setLogoByTheme, 10); // Wait for class to update
345+
});
346+
}
347+
});
348+
349+
// Table of Contents
350+
// Highlight active ToC link as you scroll
351+
document.addEventListener("DOMContentLoaded", function () {
352+
const tocLinks = document.querySelectorAll(".toc-fixed .toc-list a");
353+
const sectionIds = Array.from(tocLinks).map((link) =>
354+
link.getAttribute("href")
355+
);
356+
const sections = sectionIds.map((id) => document.querySelector(id));
357+
358+
function onScroll() {
359+
let scrollPos = window.scrollY || window.pageYOffset;
360+
let activeIndex = 0;
361+
sections.forEach((section, i) => {
362+
if (section && section.offsetTop - 80 <= scrollPos) {
363+
activeIndex = i;
364+
}
365+
});
366+
tocLinks.forEach((link) => link.classList.remove("active"));
367+
if (tocLinks[activeIndex]) tocLinks[activeIndex].classList.add("active");
368+
}
369+
370+
window.addEventListener("scroll", onScroll);
371+
onScroll();
372+
});

0 commit comments

Comments
 (0)