Commit 7eee6653 authored by HaiLD's avatar HaiLD

Porfolio

parent 823ece3b
anh1.png

246 KB

anh2.png

95.9 KB

hai.jpg

107 KB

This diff is collapsed.
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
const scrollProgress = document.querySelector(".scroll-progress");
const updateScrollProgress = () => {
const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
const progress = maxScroll > 0 ? window.scrollY / maxScroll : 0;
scrollProgress.style.transform = `scaleX(${clamp(progress, 0, 1)})`;
};
window.addEventListener("scroll", updateScrollProgress, { passive: true });
window.addEventListener("resize", updateScrollProgress);
updateScrollProgress();
const spotlight = document.querySelector(".spotlight");
if (spotlight) {
window.addEventListener("pointermove", (event) => {
spotlight.style.setProperty("--x", `${event.clientX}px`);
spotlight.style.setProperty("--y", `${event.clientY}px`);
}, { passive: true });
}
const canvas = document.querySelector(".particle-field");
if (canvas && !prefersReducedMotion) {
const ctx = canvas.getContext("2d");
let W, H, particles;
const PARTICLE_COUNT = 80;
const resize = () => {
W = canvas.width = window.innerWidth;
H = canvas.height = window.innerHeight;
};
const init = () => {
resize();
particles = Array.from({ length: PARTICLE_COUNT }, () => ({
x: Math.random() * W,
y: Math.random() * H,
r: Math.random() * 1.6 + 0.3,
vx: (Math.random() - 0.5) * 0.3,
vy: (Math.random() - 0.5) * 0.3,
alpha: Math.random() * 0.5 + 0.15,
hue: Math.random() > 0.65 ? 318 : 266,
}));
};
const draw = () => {
ctx.clearRect(0, 0, W, H);
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 110) {
ctx.beginPath();
ctx.strokeStyle = `rgba(155,99,255,${0.07 * (1 - dist / 110)})`;
ctx.lineWidth = 0.5;
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.stroke();
}
}
}
/* draw dots */
particles.forEach(p => {
p.x += p.vx;
p.y += p.vy;
if (p.x < 0) p.x = W;
if (p.x > W) p.x = 0;
if (p.y < 0) p.y = H;
if (p.y > H) p.y = 0;
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
ctx.fillStyle = p.hue === 266
? `rgba(155,99,255,${p.alpha})`
: `rgba(255,110,199,${p.alpha})`;
ctx.fill();
});
requestAnimationFrame(draw);
};
window.addEventListener("resize", resize);
init();
draw();
}
const revealObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("is-visible");
revealObserver.unobserve(entry.target);
}
});
}, { threshold: 0.16 });
document.querySelectorAll(".reveal").forEach((element, index) => {
element.style.setProperty("--delay", `${Math.min(index * 60, 360)}ms`);
revealObserver.observe(element);
});
const counterObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const counter = entry.target;
const target = Number(counter.dataset.count);
const decimals = Number(counter.dataset.decimals || 0);
const duration = prefersReducedMotion ? 1 : 1100;
const startTime = performance.now();
const tick = (now) => {
const progress = clamp((now - startTime) / duration, 0, 1);
const eased = 1 - Math.pow(1 - progress, 3);
counter.textContent = (target * eased).toFixed(decimals);
if (progress < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
counterObserver.unobserve(counter);
});
}, { threshold: 0.6 });
document.querySelectorAll("[data-count]").forEach((counter) => counterObserver.observe(counter));
/* ── typing effect ── */
const typeTarget = document.querySelector(".type-target");
if (typeTarget && !prefersReducedMotion) {
const phrases = typeTarget.dataset.phrases.split("|");
let phraseIndex = 0;
let letterIndex = phrases[0].length;
let deleting = true;
const type = () => {
const phrase = phrases[phraseIndex];
typeTarget.textContent = phrase.slice(0, letterIndex);
if (deleting) {
letterIndex -= 1;
if (letterIndex <= 0) {
deleting = false;
phraseIndex = (phraseIndex + 1) % phrases.length;
}
} else {
letterIndex += 1;
if (letterIndex >= phrases[phraseIndex].length) {
deleting = true;
window.setTimeout(type, 1500);
return;
}
}
window.setTimeout(type, deleting ? 42 : 72);
};
window.setTimeout(type, 1300);
}
document.querySelectorAll(".project-card, .skill-panel, .portfolio-frame, .preview-card").forEach((card) => {
if (prefersReducedMotion) return;
card.addEventListener("pointermove", (event) => {
const rect = card.getBoundingClientRect();
const x = (event.clientX - rect.left) / rect.width - 0.5;
const y = (event.clientY - rect.top) / rect.height - 0.5;
card.style.setProperty("--tilt-x", `${(-y * 8).toFixed(2)}deg`);
card.style.setProperty("--tilt-y", `${(x * 8).toFixed(2)}deg`);
card.classList.add("is-tilting");
});
card.addEventListener("pointerleave", () => {
card.classList.remove("is-tilting");
card.style.removeProperty("--tilt-x");
card.style.removeProperty("--tilt-y");
});
});
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment