Commit 7eee6653 authored by HaiLD's avatar HaiLD

Porfolio

parent 823ece3b
anh1.png

246 KB

anh2.png

95.9 KB

hai.jpg

107 KB

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="Portfolio of Le Duc Hai, Backend Developer Intern specializing in Node.js, Express.js, TypeScript, RESTful APIs, authentication, and database design."
/>
<title>Le Duc Hai | Backend Developer Intern</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="scroll-progress" aria-hidden="true"></div>
<canvas class="particle-field" aria-hidden="true"></canvas>
<div class="spotlight" aria-hidden="true"></div>
<div class="page-grid" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="#home" aria-label="Le Duc Hai home">
<span>LDH</span>
</a>
<nav class="nav-links" aria-label="Primary navigation">
<a href="#projects">Projects</a>
<a href="#skills">Skills</a>
<a href="#education">Education</a>
<a href="#contact">Contact</a>
</nav>
<a class="header-action" href="CV_LeDucHai_BackendDeveloperIntern.pdf" target="_blank" rel="noreferrer">
View CV
</a>
</header>
<main id="home">
<section class="hero section">
<div class="hero-copy reveal">
<p class="eyebrow">Backend Portfolio</p>
<h1>
Backend Portfolio
<span>Web APIs</span>
</h1>
<p class="hero-text">
<strong>2 Featured Projects</strong>
RESTful APIs, authentication systems, role-based access, dashboards, reports,
and full-stack product flows.
</p>
<a class="hero-mail" href="mailto:haileduc222@gmail.com">haileduc222@gmail.com</a>
<div class="hero-actions">
<a class="btn primary" href="#projects">View Projects</a>
<a class="btn secondary" href="mailto:haileduc222@gmail.com">Hire Me</a>
</div>
<div class="hero-socials" aria-label="Profile links">
<a href="https://github.com/HaiSIUUbscribe" target="_blank" rel="noreferrer" aria-label="GitHub">GH</a>
<a href="mailto:haileduc222@gmail.com" aria-label="Email">EM</a>
<a href="CV_LeDucHai_BackendDeveloperIntern.pdf" target="_blank" rel="noreferrer" aria-label="CV">CV</a>
</div>
<div class="quick-facts" aria-label="Profile highlights">
<span>Ho Chi Minh City</span>
<span><b data-count="2027">0</b> Expected graduation</span>
<span>GPA <b data-count="3.04" data-decimals="2">0</b></span>
</div>
</div>
<div class="hero-visual reveal" aria-label="Portfolio preview with portrait">
<article class="portfolio-frame">
<header class="frame-nav">
<a href="#home" class="frame-logo">Hai.</a>
<nav aria-label="Portfolio preview navigation">
<a href="#home">Home</a>
<a href="#skills">Skills</a>
<a href="#projects">Projects</a>
<a href="#contact">Contact</a>
</nav>
</header>
<div class="frame-hero">
<p class="ghost-word" aria-hidden="true">BACKEND</p>
<div class="frame-intro">
<p>Hi,</p>
<h2>my name is <span>Hai</span></h2>
<p>Backend Developer Intern</p>
</div>
<div class="purple-panel" aria-hidden="true"></div>
<img class="frame-photo" src="hai.jpg" alt="Portrait of Le Duc Hai" />
<div class="frame-socials" aria-label="Quick links">
<a href="https://github.com/HaiSIUUbscribe" target="_blank" rel="noreferrer">GH</a>
<a href="mailto:haileduc222@gmail.com">EM</a>
<a href="CV_LeDucHai_BackendDeveloperIntern.pdf" target="_blank" rel="noreferrer">CV</a>
</div>
</div>
<section class="frame-projects" aria-label="Project previews">
<h3>Projects</h3>
<div class="preview-grid">
<a class="preview-card preview-student" href="https://github.com/HaiSIUUbscribe/WebManagerStudent" target="_blank" rel="noreferrer">
<span>Student Manager API</span>
</a>
<a class="preview-card preview-shop" href="https://shoplite-two.vercel.app" target="_blank" rel="noreferrer">
<span>Shoplite Commerce</span>
</a>
</div>
</section>
</article>
</div>
</section>
<section class="section intro-section">
<div class="section-heading reveal">
<p class="eyebrow">Summary</p>
<h2>Backend-first, product-aware, ready for hands-on engineering.</h2>
</div>
<p class="intro-text reveal">
Skilled in building RESTful APIs with Node.js and Express.js, developing web
applications with React, and working with MySQL databases. Familiar with Git,
Postman, Vercel, Render, MVC architecture, JWT/OAuth authentication, RBAC, and
modern web development practices.
</p>
</section>
<section id="projects" class="section">
<div class="section-heading reveal">
<p class="eyebrow">Selected Projects</p>
<h2>Systems with authentication, dashboards, exports, payments, and notifications.</h2>
</div>
<div class="project-grid">
<article class="project-card reveal">
<div class="project-index">01</div>
<div>
<p class="project-role">Backend Developer | Team size: 4</p>
<h3>Web Manager Student</h3>
<p class="project-date">Nov 2025 - Dec 2025</p>
<p>
RESTful APIs for managing students, classes, courses, enrollments, and
grades using Node.js, Express, TypeScript, and MongoDB/Mongoose.
</p>
<ul class="feature-list">
<li>JWT authentication, bcrypt password hashing, and role-based authorization.</li>
<li>Zod request validation, centralized errors, pagination, search, and grade statistics.</li>
<li>Excel import plus Excel/PDF report export, dashboard, and notification features.</li>
</ul>
<div class="tag-row">
<span>Node.js</span>
<span>Express</span>
<span>TypeScript</span>
<span>MongoDB</span>
</div>
<a class="text-link" href="https://github.com/HaiSIUUbscribe/WebManagerStudent" target="_blank" rel="noreferrer">
GitHub repository
</a>
</div>
</article>
<article class="project-card featured reveal">
<div class="project-index">02</div>
<div>
<p class="project-role">Fullstack Developer | Team size: 1</p>
<h3>Shoplite</h3>
<p class="project-date">Jun 2025 - Jul 2025</p>
<p>
Full-stack e-commerce platform built with React, Node.js, Express, and
MySQL, covering the core purchase journey from product browsing to order tracking.
</p>
<ul class="feature-list">
<li>JWT authentication, Google OAuth, refresh tokens, and role-based access control.</li>
<li>Product management, cart, checkout, vouchers, VNPay, and notifications.</li>
<li>Resend email API integration with deployment on Vercel and Render.</li>
</ul>
<div class="tag-row">
<span>React</span>
<span>MySQL</span>
<span>VNPay</span>
<span>Render</span>
</div>
<div class="link-row">
<a class="text-link" href="https://github.com/HaiSIUUbscribe/shoplite" target="_blank" rel="noreferrer">
GitHub repository
</a>
<a class="text-link" href="https://shoplite-two.vercel.app" target="_blank" rel="noreferrer">
Live demo
</a>
</div>
</div>
</article>
</div>
</section>
<section id="skills" class="section skills-section">
<div class="section-heading reveal">
<p class="eyebrow">Technical Skills</p>
<h2>A compact stack for backend intern work.</h2>
</div>
<div class="skills-layout">
<div class="skill-panel reveal">
<h3>Languages</h3>
<div class="skill-meter"><span style="--w: 86%">JavaScript ES6+</span></div>
<div class="skill-meter"><span style="--w: 78%">TypeScript</span></div>
<div class="skill-meter"><span style="--w: 72%">SQL</span></div>
</div>
<div class="skill-panel reveal">
<h3>Backend</h3>
<div class="chip-cloud">
<span>Node.js</span>
<span>Express.js</span>
<span>RESTful APIs</span>
<span>MVC</span>
<span>JWT</span>
<span>OAuth</span>
<span>RBAC</span>
<span>Zod</span>
</div>
</div>
<div class="skill-panel reveal">
<h3>Data & Tools</h3>
<div class="chip-cloud">
<span>MySQL</span>
<span>Firebase</span>
<span>MongoDB</span>
<span>Git/GitHub</span>
<span>Postman</span>
<span>Vercel</span>
<span>Render</span>
<span>Database design</span>
</div>
</div>
</div>
</section>
<section id="education" class="section timeline-section">
<div class="section-heading reveal">
<p class="eyebrow">Education</p>
<h2>Posts and Telecommunications Institute of Technology.</h2>
</div>
<div class="timeline reveal">
<div class="timeline-dot"></div>
<div>
<p class="project-date">2022 - Present, expected 2027</p>
<h3>Bachelor of Multimedia Technology</h3>
<p>
Studying software, web technologies, and multimedia systems while building
backend-focused projects for real-world workflows.
</p>
</div>
</div>
</section>
<section id="contact" class="section contact-section">
<div class="contact-copy reveal">
<p class="eyebrow">Contact</p>
<h2>Looking for a backend internship where I can contribute and grow.</h2>
<p>
I am eager to apply technical skills, gain hands-on experience, and contribute
to production-minded software projects.
</p>
</div>
<div class="contact-actions reveal">
<a class="contact-link" href="mailto:haileduc222@gmail.com">
<span>Email</span>
haileduc222@gmail.com
</a>
<a class="contact-link" href="tel:+84327092683">
<span>Phone</span>
(+84) 327 092 683
</a>
<a class="contact-link" href="https://github.com/HaiSIUUbscribe" target="_blank" rel="noreferrer">
<span>GitHub</span>
github.com/HaiSIUUbscribe
</a>
</div>
</section>
</main>
<footer class="site-footer">
<p>Le Duc Hai</p>
<a href="#home">Back to top</a>
</footer>
<script src="script.js"></script>
</body>
</html>
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");
});
});
:root {
--bg: #09070d;
--bg-1: #111015;
--bg-2: #18131f;
--bg-3: #241a31;
--ink: #e8edf5;
--ink-2: #b8adca;
--ink-3: #756987;
--cyan: #9b63ff;
--cyan-dim: rgba(155, 99, 255, 0.14);
--cyan-glow:rgba(155, 99, 255, 0.34);
--violet: #6b3fc8;
--violet-dim:rgba(107, 63, 200, 0.16);
--rose: #ff6ec7;
--gold: #f5c542;
--line: rgba(255, 255, 255, 0.07);
--line-2: rgba(255, 255, 255, 0.12);
--shadow: 0 24px 72px rgba(0, 0, 0, 0.55);
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
color: var(--ink);
background: var(--bg);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
line-height: 1.6;
overflow-x: hidden;
}
body::before {
position: fixed;
inset: 0;
z-index: -3;
pointer-events: none;
content: "";
background:
radial-gradient(ellipse 70% 50% at 15% 10%, rgba(155, 99, 255, 0.07) 0%, transparent 55%),
radial-gradient(ellipse 60% 40% at 85% 80%, rgba(124, 111, 247, 0.08) 0%, transparent 50%),
radial-gradient(ellipse 50% 60% at 50% 50%, rgba(240, 93, 138, 0.04) 0%, transparent 60%);
}
.page-grid {
position: fixed;
inset: 0;
z-index: -3;
pointer-events: none;
background-image:
linear-gradient(rgba(255,255,255,0.032) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.032) 1px, transparent 1px);
background-size: 44px 44px;
mask-image: linear-gradient(to bottom, black 0%, transparent 80%);
}
.particle-field {
position: fixed;
inset: 0;
z-index: -2;
pointer-events: none;
opacity: 0.35;
}
.spotlight {
--x: 50vw;
--y: 30vh;
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background: radial-gradient(circle 500px at var(--x) var(--y), rgba(155, 99, 255, 0.055), transparent 60%);
}
/* ── scroll bar ── */
.scroll-progress {
position: fixed;
top: 0; left: 0;
z-index: 80;
width: 100%;
height: 2px;
background: linear-gradient(90deg, var(--cyan), var(--violet), var(--rose));
transform: scaleX(0);
transform-origin: left;
}
a { color: inherit; text-decoration: none; }
.site-header {
position: sticky;
top: 0;
z-index: 20;
display: grid;
grid-template-columns: auto 1fr auto;
gap: 24px;
align-items: center;
width: min(1180px, calc(100% - 36px));
margin: 18px auto 0;
padding: 10px;
border: 1px solid var(--line-2);
background: rgba(8, 11, 20, 0.75);
backdrop-filter: blur(20px);
border-radius: 12px;
}
.brand {
display: grid;
width: 46px;
height: 46px;
place-items: center;
color: var(--bg);
background: var(--cyan);
border-radius: 8px;
font-weight: 900;
letter-spacing: 0;
font-size: 0.95rem;
box-shadow: 0 0 16px var(--cyan-glow);
transition: box-shadow 220ms;
}
.brand:hover { box-shadow: 0 0 32px var(--cyan-glow); }
.nav-links {
display: flex;
justify-content: center;
gap: 4px;
}
.nav-links a,
.header-action {
min-height: 40px;
padding: 9px 14px;
border-radius: 7px;
color: var(--ink-2);
font-size: 0.92rem;
font-weight: 700;
transition: background 220ms, color 220ms, transform 220ms;
}
.nav-links a:hover { color: var(--ink); background: var(--line); }
.header-action {
color: var(--bg);
background: var(--cyan);
font-weight: 800;
box-shadow: 0 0 20px var(--cyan-dim);
transition: box-shadow 220ms, transform 220ms;
}
.header-action:hover {
color: var(--bg);
background: var(--cyan);
box-shadow: 0 0 36px var(--cyan-glow);
transform: translateY(-1px);
}
/* ── SECTION ── */
.section {
width: min(1180px, calc(100% - 36px));
margin: 0 auto;
padding: 96px 0;
}
/* ── HERO ── */
.hero {
position: relative;
display: grid;
grid-template-columns: minmax(0, 0.92fr) minmax(360px, 1.08fr);
gap: 34px;
align-items: center;
min-height: calc(100vh - 112px);
margin-top: 18px;
padding: 68px 72px;
border: 1px solid rgba(155,99,255,0.12);
border-radius: 18px;
overflow: hidden;
background:
radial-gradient(circle at 86% 44%, rgba(155,99,255,0.2), transparent 28%),
radial-gradient(circle at 4% 82%, rgba(155,99,255,0.18), transparent 24%),
linear-gradient(120deg, rgba(8, 17, 30, 0.98) 0%, rgba(8, 13, 24, 0.96) 44%, rgba(5, 12, 23, 0.94) 100%);
box-shadow: var(--shadow), inset 0 0 90px rgba(155,99,255,0.04);
}
.hero::before {
position: absolute;
inset: 0;
content: "";
pointer-events: none;
background:
linear-gradient(90deg, rgba(155,99,255,0.08), transparent 22%, transparent 78%, rgba(155,99,255,0.08)),
linear-gradient(rgba(255,255,255,0.025) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.025) 1px, transparent 1px);
background-size: auto, 42px 42px, 42px 42px;
opacity: 0.68;
}
.hero::after {
position: absolute;
right: -120px;
bottom: -120px;
width: 340px;
height: 340px;
content: "";
border: 1px solid rgba(155,99,255,0.22);
border-radius: 50%;
box-shadow: 0 0 70px rgba(155,99,255,0.14);
}
.hero-copy,
.hero-visual {
position: relative;
z-index: 1;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 10px;
margin: 0 0 18px;
color: var(--cyan);
font-size: 0.8rem;
font-weight: 900;
letter-spacing: 0.16em;
text-transform: uppercase;
}
.eyebrow::before {
width: 30px;
height: 2px;
content: "";
background: currentColor;
box-shadow: 0 0 8px currentColor;
}
h1, h2, h3, p { margin-top: 0; }
.hero h1 {
max-width: 780px;
margin-bottom: 4px;
font-size: clamp(2.9rem, 5.4vw, 5.7rem);
line-height: 0.98;
letter-spacing: -0.01em;
color: var(--ink);
}
.hero h1 span {
color: var(--ink);
}
.hero-role {
max-width: 580px;
min-height: 1.1em;
margin-bottom: 22px;
color: var(--cyan);
font-size: clamp(1.45rem, 2.4vw, 2.35rem);
line-height: 1.1;
text-shadow: 0 0 28px var(--cyan-glow);
}
h1 {
max-width: 780px;
margin-bottom: 24px;
font-size: clamp(3.4rem, 8vw, 6.9rem);
line-height: 0.9;
letter-spacing: -0.01em;
color: var(--ink);
}
h2 {
max-width: 760px;
margin-bottom: 0;
font-size: clamp(2rem, 4vw, 4rem);
line-height: 1;
letter-spacing: -0.01em;
color: var(--ink);
}
h3 {
margin-bottom: 10px;
font-size: clamp(1.35rem, 2vw, 2rem);
line-height: 1.1;
color: var(--ink);
}
.hero-text,
.intro-text,
.contact-copy p,
.timeline p,
.project-card p {
color: var(--ink-2);
}
.hero-text {
max-width: 560px;
margin-bottom: 34px;
font-size: clamp(0.96rem, 1.6vw, 1.08rem);
}
.hero-actions, .link-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
align-items: center;
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 50px;
padding: 13px 28px;
border: 1px solid var(--line-2);
border-radius: 8px;
font-weight: 800;
font-size: 0.95rem;
transition: transform 220ms, box-shadow 220ms, background 220ms;
}
.btn:hover { transform: translateY(-3px); }
.btn.primary {
color: var(--bg);
background: var(--cyan);
border-color: transparent;
box-shadow: 0 0 24px var(--cyan-dim);
}
.btn.primary:hover { box-shadow: 0 0 40px var(--cyan-glow), 0 16px 40px rgba(0,0,0,0.4); }
.btn.secondary {
color: var(--ink);
background: rgba(255,255,255,0.05);
border-color: var(--line-2);
}
.btn.secondary:hover { background: rgba(255,255,255,0.09); box-shadow: 0 10px 30px rgba(0,0,0,0.3); }
/* quick-facts */
.quick-facts {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 24px;
}
.quick-facts span {
padding: 8px 13px;
border: 1px solid var(--line-2);
border-radius: 7px;
background: rgba(255,255,255,0.04);
color: var(--ink-2);
font-size: 0.88rem;
font-weight: 700;
}
.quick-facts b { color: var(--cyan); }
.hero-socials {
display: flex;
gap: 12px;
margin-top: 60px;
}
.hero-socials a {
display: grid;
width: 34px;
height: 34px;
place-items: center;
border: 1px solid rgba(155,99,255,0.44);
border-radius: 50%;
color: var(--cyan);
background: rgba(155,99,255,0.04);
font-size: 0.72rem;
font-weight: 900;
box-shadow: 0 0 20px rgba(155,99,255,0.06);
transition: transform 180ms, background 180ms, color 180ms, box-shadow 180ms;
}
.hero-socials a:hover {
color: var(--bg);
background: var(--cyan);
box-shadow: 0 0 28px var(--cyan-glow);
transform: translateY(-3px);
}
/* ── HERO VISUAL ── */
.hero-visual {
position: relative;
min-height: 520px;
align-self: stretch;
}
.hero-visual::before {
position: absolute;
inset: 46px 8px 28px 80px;
content: "";
border: 1px solid rgba(155,99,255,0.18);
background:
linear-gradient(135deg, rgba(155,99,255,0.18), transparent 34%),
linear-gradient(180deg, rgba(155,99,255,0.08), rgba(155,99,255,0.01));
transform: skewX(-9deg);
border-radius: 18px;
filter: blur(0.2px);
}
.hero-visual::after {
position: absolute;
right: 38px;
bottom: 52px;
width: 150px;
height: 150px;
content: "";
border: 10px solid var(--cyan);
border-left-color: var(--violet);
border-bottom-color: var(--rose);
border-radius: 50%;
opacity: 0.7;
animation: spin 12s linear infinite;
box-shadow: 0 0 24px var(--cyan-dim);
}
/* portfolio mockup redesign */
.hero {
grid-template-columns: minmax(220px, 0.58fr) minmax(540px, 1.42fr);
gap: 54px;
align-items: center;
min-height: calc(100vh - 110px);
padding: 72px 0 96px;
border: 0;
border-radius: 0;
overflow: visible;
background:
radial-gradient(circle at 10% 34%, rgba(155, 99, 255, 0.34), transparent 24%),
radial-gradient(circle at 48% 22%, rgba(255, 110, 199, 0.12), transparent 26%),
linear-gradient(115deg, #3c3047 0%, #171317 36%, #0b090e 100%);
box-shadow: none;
}
.hero::before {
inset: -90px auto auto -130px;
width: 460px;
height: 460px;
border-radius: 50%;
background: radial-gradient(circle, rgba(155, 99, 255, 0.46), transparent 62%);
filter: blur(4px);
opacity: 0.72;
}
.hero::after {
inset: auto -120px -140px auto;
width: 500px;
height: 500px;
border: 0;
border-radius: 50%;
background: radial-gradient(circle, rgba(155, 99, 255, 0.16), transparent 64%);
box-shadow: none;
}
.hero-copy {
max-width: 360px;
padding-left: 22px;
}
.hero .eyebrow {
color: var(--ink);
opacity: 0.82;
}
.hero .eyebrow::before {
background: var(--cyan);
}
.hero h1 {
margin-bottom: 12px;
color: var(--cyan);
font-size: clamp(2.2rem, 4.2vw, 4.1rem);
line-height: 1.06;
}
.hero h1 span {
display: block;
color: var(--cyan);
}
.hero-text {
max-width: 310px;
margin-bottom: 10px;
color: rgba(232, 237, 245, 0.82);
font-size: 1rem;
}
.hero-text strong {
display: block;
margin-bottom: 2px;
color: #ffffff;
}
.hero-mail {
display: inline-flex;
margin: 0 0 24px;
color: rgba(255, 255, 255, 0.74);
font-weight: 800;
}
.hero-actions {
margin-top: 6px;
}
.hero-socials {
margin-top: 54px;
}
.hero-socials a,
.btn.primary,
.header-action,
.brand {
background: var(--cyan);
}
.hero-visual {
min-height: 650px;
perspective: 1200px;
}
.hero-visual::before {
inset: 26px -38px auto auto;
width: 52%;
height: 120px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 0;
background:
linear-gradient(90deg, rgba(255,255,255,0.08) 1px, transparent 1px),
linear-gradient(rgba(255,255,255,0.08) 1px, transparent 1px),
rgba(10, 9, 12, 0.76);
background-size: 36px 36px;
transform: none;
opacity: 0.55;
}
.hero-visual::after {
right: -36px;
bottom: 22px;
width: 42%;
height: 72%;
border: 0;
border-radius: 0;
background:
linear-gradient(rgba(255,255,255,0.055) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.055) 1px, transparent 1px),
rgba(5, 5, 7, 0.74);
background-size: 42px 42px;
opacity: 0.6;
animation: none;
box-shadow: none;
}
.portfolio-frame {
position: relative;
z-index: 2;
width: min(100%, 720px);
min-height: 620px;
margin: 0 auto;
padding: 26px 34px 34px;
border: 1px solid rgba(255, 255, 255, 0.07);
border-radius: 2px;
overflow: hidden;
background: #101014;
box-shadow:
0 34px 90px rgba(0, 0, 0, 0.54),
0 0 0 1px rgba(155, 99, 255, 0.08);
transition: transform 240ms ease, box-shadow 240ms ease;
}
.portfolio-frame::before {
position: absolute;
left: 28px;
top: 74px;
content: "BACKEND";
color: rgba(155, 99, 255, 0.09);
font-size: clamp(5.6rem, 12vw, 10.5rem);
font-weight: 950;
line-height: 0.78;
pointer-events: none;
}
.portfolio-frame:hover {
box-shadow:
0 42px 110px rgba(0, 0, 0, 0.62),
0 0 56px rgba(155, 99, 255, 0.12);
}
.frame-nav {
position: relative;
z-index: 3;
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
margin-bottom: 30px;
}
.frame-logo {
color: #ffffff;
font-size: 0.86rem;
font-style: italic;
font-weight: 900;
}
.frame-nav nav {
display: flex;
gap: 20px;
}
.frame-nav nav a {
color: rgba(255, 255, 255, 0.62);
font-size: 0.72rem;
font-weight: 800;
}
.frame-nav nav a:hover {
color: var(--cyan);
}
.frame-hero {
position: relative;
z-index: 2;
display: grid;
grid-template-columns: minmax(210px, 0.92fr) minmax(220px, 1fr);
min-height: 285px;
}
.ghost-word {
position: absolute;
left: -8px;
top: 4px;
z-index: 0;
margin: 0;
color: rgba(155, 99, 255, 0.09);
font-size: clamp(5.4rem, 12vw, 9.4rem);
font-weight: 950;
line-height: 0.8;
}
.frame-intro {
position: relative;
z-index: 2;
align-self: center;
max-width: 280px;
padding: 28px 28px 30px;
background: rgba(38, 30, 48, 0.82);
backdrop-filter: blur(8px);
}
.frame-intro p {
margin-bottom: 6px;
color: rgba(255, 255, 255, 0.78);
font-size: 0.92rem;
}
.frame-intro h2 {
margin: 0 0 8px;
color: #ffffff;
font-size: clamp(2.1rem, 3.9vw, 3.3rem);
line-height: 1.04;
}
.frame-intro h2 span {
display: block;
color: var(--cyan);
}
.purple-panel {
position: absolute;
right: 34px;
top: 62px;
width: 300px;
height: 210px;
z-index: 1;
background:
radial-gradient(circle at 70% 24%, rgba(255, 255, 255, 0.18), transparent 28%),
linear-gradient(135deg, rgba(155, 99, 255, 0.4), rgba(107, 63, 200, 0.22));
box-shadow: 0 0 54px rgba(155, 99, 255, 0.2);
}
.frame-photo {
position: absolute;
right: 10px;
bottom: 0;
z-index: 3;
width: min(46%, 300px);
height: 320px;
object-fit: cover;
object-position: 50% 32%;
filter: contrast(1.08) brightness(1.1) saturate(1.02);
mix-blend-mode: normal;
box-shadow: -28px 0 60px rgba(0, 0, 0, 0.2);
}
.frame-hero::after {
position: absolute;
right: 28px;
top: 52px;
z-index: 4;
width: 280px;
height: 260px;
content: "";
background:
radial-gradient(circle at 58% 38%, rgba(255, 255, 255, 0.2), transparent 24%),
linear-gradient(90deg, transparent 0 28%, rgba(255, 255, 255, 0.08) 48%, transparent 72%);
mix-blend-mode: screen;
pointer-events: none;
}
.frame-hero::before {
position: absolute;
right: 4px;
bottom: 0;
z-index: 4;
width: 330px;
height: 320px;
content: "";
background:
linear-gradient(90deg, rgba(16,16,20,0.72), transparent 20%, transparent 72%, rgba(16,16,20,0.34)),
linear-gradient(180deg, transparent 64%, rgba(16,16,20,0.34));
pointer-events: none;
}
.frame-socials {
position: absolute;
left: 4px;
bottom: 0;
z-index: 3;
display: flex;
gap: 8px;
}
.frame-socials a {
display: grid;
width: 22px;
height: 22px;
place-items: center;
border: 1px solid rgba(155, 99, 255, 0.44);
border-radius: 50%;
color: rgba(255, 255, 255, 0.76);
font-size: 0.56rem;
font-weight: 900;
}
.frame-socials a:hover {
color: #ffffff;
background: var(--cyan);
}
.frame-projects {
position: relative;
z-index: 3;
margin-top: 46px;
}
.frame-projects h3 {
margin-bottom: 18px;
text-align: center;
color: #ffffff;
font-size: 1rem;
}
.preview-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px;
}
.preview-card {
position: relative;
display: flex;
align-items: end;
min-height: 155px;
padding: 16px;
overflow: hidden;
color: #ffffff;
background-color: var(--bg-2);
background-position: center;
background-size: cover;
transition: transform 200ms ease, filter 200ms ease;
}
.preview-card::before {
position: absolute;
inset: 0;
content: "";
background:
linear-gradient(180deg, rgba(10, 8, 14, 0.1), rgba(10, 8, 14, 0.86)),
linear-gradient(90deg, rgba(10, 8, 14, 0.74), transparent 68%),
repeating-linear-gradient(90deg, rgba(255,255,255,0.11) 0 2px, transparent 2px 34px);
}
.preview-card::after {
position: absolute;
inset: 20px 24px auto auto;
width: 78px;
height: 78px;
content: "";
border-radius: 50%;
background: rgba(155, 99, 255, 0.56);
filter: blur(10px);
}
.preview-student {
background:
linear-gradient(135deg, rgba(155,99,255,0.08), rgba(0,0,0,0.16)),
url("anh2.png");
background-position: 38% 24%;
background-size: cover;
}
.preview-shop {
background:
linear-gradient(135deg, rgba(155,99,255,0.08), rgba(0,0,0,0.16)),
url("anh1.png");
background-position: 24% 38%;
background-size: cover;
}
.preview-card span {
position: relative;
z-index: 2;
font-size: 0.94rem;
font-weight: 900;
}
.preview-card:hover {
filter: brightness(1.12);
transform: translateY(-4px);
}
/* type cursor */
.type-target {
position: relative;
display: inline-block;
color: var(--cyan);
text-shadow: 0 0 32px var(--cyan-glow);
}
.type-target::after {
display: inline-block;
width: 0.07em;
height: 0.82em;
margin-left: 0.06em;
content: "";
background: var(--rose);
animation: caretBlink 760ms steps(1) infinite;
transform: translateY(0.08em);
box-shadow: 0 0 8px var(--rose);
}
.console-card {
position: relative;
z-index: 1;
width: min(100%, 520px);
margin: 40px auto 0;
border: 1px solid var(--line-2);
border-radius: 12px;
overflow: hidden;
background: var(--bg-1);
box-shadow: var(--shadow);
transform: rotate(1.5deg);
}
.console-top {
display: flex;
align-items: center;
gap: 8px;
min-height: 46px;
padding: 0 16px;
border-bottom: 1px solid var(--line);
color: var(--ink-3);
}
.console-top span {
width: 11px; height: 11px;
border-radius: 50%;
background: var(--rose);
}
.console-top span:nth-child(2) { background: var(--gold); }
.console-top span:nth-child(3) { background: #2fe87a; }
.console-top strong {
margin-left: auto;
font-size: 0.82rem;
letter-spacing: 0.08em;
color: var(--ink-3);
}
.console-body { padding: 22px; }
.status-line {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 12px;
align-items: center;
min-height: 52px;
margin-bottom: 12px;
padding: 12px;
border: 1px solid var(--line);
border-radius: 8px;
background: rgba(255,255,255,0.03);
}
.status-line p { margin: 0; color: var(--ink-2); font-size: 0.95rem; }
.status-line b { color: var(--ink); font-size: 0.82rem; }
.status-dot {
width: 10px; height: 10px;
border-radius: 50%;
background: #2fe87a;
box-shadow: 0 0 14px #2fe87a;
}
.status-dot.alt { background: var(--violet); box-shadow: 0 0 14px var(--violet); }
.status-dot.gold { background: var(--gold); box-shadow: 0 0 14px var(--gold); }
pre {
margin: 18px 0 0;
padding: 18px;
overflow: auto;
border-left: 2px solid var(--cyan);
background: rgba(155,99,255,0.04);
color: #c2ffe8;
font-size: 0.88rem;
border-radius: 0 6px 6px 0;
}
.signal-bars {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 10px;
align-items: end;
height: 118px;
padding: 0 22px 22px;
}
.signal-bars span {
height: var(--h);
border-radius: 6px 6px 0 0;
background: linear-gradient(180deg, var(--cyan), var(--violet));
animation: pulseBar 2.6s ease-in-out infinite;
animation-delay: calc(var(--h) * -0.02);
}
/* ── INTRO SECTION ── */
.intro-section {
display: grid;
grid-template-columns: 0.9fr 1.1fr;
gap: 44px;
align-items: start;
border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
}
.intro-text {
margin: 0;
font-size: clamp(1.05rem, 2vw, 1.3rem);
color: var(--ink-2);
}
.section-heading { margin-bottom: 34px; }
/* ── PROJECTS ── */
.project-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px;
}
.project-card,
.skill-panel,
.timeline,
.contact-link {
border: 1px solid var(--line);
border-radius: 12px;
background: var(--bg-1);
box-shadow: 0 12px 40px rgba(0,0,0,0.3);
backdrop-filter: blur(12px);
}
.project-card {
position: relative;
display: grid;
grid-template-columns: auto 1fr;
gap: 22px;
min-height: 100%;
padding: 28px;
overflow: hidden;
transition: transform 240ms ease, box-shadow 240ms ease, border-color 240ms ease;
}
.project-card::before {
position: absolute;
inset: 0 0 auto;
height: 2px;
content: "";
background: linear-gradient(90deg, var(--cyan), var(--violet), var(--rose));
}
.project-card:hover {
border-color: rgba(155,99,255,0.22);
box-shadow: var(--shadow), 0 0 40px rgba(155,99,255,0.07);
transform: translateY(-8px);
}
.project-card.is-tilting,
.skill-panel.is-tilting,
.console-card.is-tilting,
.portfolio-frame.is-tilting,
.preview-card.is-tilting {
transform: perspective(900px) rotateX(var(--tilt-x)) rotateY(var(--tilt-y)) translateY(-4px);
}
.project-card.featured {
background: var(--bg-2);
}
.project-index {
display: grid;
width: 48px;
height: 48px;
place-items: center;
border: 1px solid rgba(155,99,255,0.2);
border-radius: 8px;
color: var(--cyan);
background: var(--cyan-dim);
font-weight: 900;
font-size: 0.95rem;
}
.project-role { margin-bottom: 8px; color: var(--cyan); font-size: 0.84rem; font-weight: 900; text-transform: uppercase; }
.project-date { margin-bottom: 8px; color: var(--rose); font-size: 0.84rem; font-weight: 800; }
.feature-list {
display: grid;
gap: 9px;
padding: 0;
margin: 18px 0;
list-style: none;
}
.feature-list li {
position: relative;
padding-left: 22px;
color: var(--ink-2);
}
.feature-list li::before {
position: absolute;
left: 0;
top: 0.68em;
width: 7px;
height: 7px;
content: "";
background: var(--cyan);
border-radius: 50%;
box-shadow: 0 0 8px var(--cyan-glow);
}
.tag-row, .chip-cloud {
display: flex;
flex-wrap: wrap;
gap: 9px;
}
.tag-row { margin: 20px 0; }
.tag-row span,
.chip-cloud span {
padding: 7px 12px;
border: 1px solid var(--line-2);
border-radius: 7px;
background: rgba(255,255,255,0.04);
color: var(--ink-2);
font-size: 0.84rem;
font-weight: 700;
transition: border-color 180ms, color 180ms, background 180ms;
}
.tag-row span:hover,
.chip-cloud span:hover {
border-color: rgba(155,99,255,0.3);
color: var(--cyan);
background: var(--cyan-dim);
}
.text-link {
display: inline-flex;
align-items: center;
color: var(--cyan);
font-weight: 800;
}
.text-link::after {
margin-left: 8px;
content: "->";
transition: transform 180ms ease;
}
.text-link:hover::after { transform: translateX(4px); }
/* ── SKILLS ── */
.skills-section { border-top: 1px solid var(--line); }
.skills-layout {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 18px;
}
.skill-panel {
min-height: 250px;
padding: 26px;
transition: transform 240ms, box-shadow 240ms, border-color 240ms;
}
.skill-panel:hover {
border-color: rgba(155,99,255,0.2);
box-shadow: 0 20px 56px rgba(0,0,0,0.4), 0 0 32px rgba(155,99,255,0.06);
}
.skill-panel h3 {
margin-bottom: 24px;
color: var(--ink);
font-size: 1.1rem;
font-weight: 800;
}
.skill-meter {
height: 45px;
margin-bottom: 12px;
overflow: hidden;
border: 1px solid var(--line);
border-radius: 8px;
background: rgba(255,255,255,0.03);
}
.skill-meter span {
display: flex;
align-items: center;
width: var(--w);
min-width: 140px;
height: 100%;
padding-left: 14px;
color: var(--bg);
background: linear-gradient(90deg, var(--bg-3), var(--cyan));
font-size: 0.88rem;
font-weight: 800;
transform-origin: left;
animation: meterGrow 1.2s ease both;
}
/* ── EDUCATION ── */
.timeline-section {
display: grid;
grid-template-columns: 0.88fr 1.12fr;
gap: 38px;
align-items: start;
}
.timeline {
position: relative;
display: grid;
grid-template-columns: auto 1fr;
gap: 22px;
padding: 28px;
}
.timeline::before {
position: absolute;
top: 54px;
bottom: 28px;
left: 37px;
width: 2px;
content: "";
background: linear-gradient(var(--cyan), transparent);
}
.timeline-dot {
position: relative;
z-index: 1;
width: 18px;
height: 18px;
margin-top: 8px;
border: 3px solid var(--bg);
border-radius: 50%;
background: var(--cyan);
box-shadow: 0 0 0 6px rgba(155,99,255,0.12), 0 0 20px var(--cyan-glow);
}
/* ── CONTACT ── */
.contact-section {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 28px;
align-items: stretch;
padding-bottom: 70px;
}
.contact-actions { display: grid; gap: 12px; }
.contact-link {
display: grid;
gap: 4px;
padding: 20px;
color: var(--ink);
font-size: clamp(1rem, 2vw, 1.18rem);
font-weight: 900;
transition: transform 220ms, border-color 220ms, background 220ms;
}
.contact-link span {
color: var(--ink-3);
font-size: 0.76rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.contact-link:hover {
border-color: rgba(155,99,255,0.3);
background: var(--cyan-dim);
transform: translateX(6px);
color: var(--cyan);
}
/* ── FOOTER ── */
.site-footer {
display: flex;
justify-content: space-between;
width: min(1180px, calc(100% - 36px));
margin: 0 auto;
padding: 26px 0 42px;
border-top: 1px solid var(--line);
color: var(--ink-3);
font-weight: 700;
font-size: 0.9rem;
}
.site-footer p { margin: 0; }
.site-footer a { color: var(--cyan); transition: opacity 180ms; }
.site-footer a:hover { opacity: 0.75; }
/* ── REVEAL ANIMATION ── */
.reveal {
opacity: 0;
transform: translateY(22px);
transition: opacity 700ms ease var(--delay, 0ms), transform 700ms ease var(--delay, 0ms);
}
.reveal.is-visible { opacity: 1; transform: translateY(0); }
/* ── KEYFRAMES ── */
@keyframes pulseBar {
0%, 100% { transform: scaleY(0.82); opacity: 0.72; }
50% { transform: scaleY(1); opacity: 1; }
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes meterGrow {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
@keyframes caretBlink {
50% { opacity: 0; }
}
/* ── RESPONSIVE ── */
@media (max-width: 940px) {
.site-header { grid-template-columns: auto auto; }
.nav-links {
order: 3;
grid-column: 1 / -1;
justify-content: flex-start;
overflow-x: auto;
}
.hero,
.intro-section,
.timeline-section,
.contact-section { grid-template-columns: 1fr; }
.hero {
gap: 20px;
min-height: auto;
padding: 56px 38px;
}
.hero-visual {
min-height: auto;
}
.portfolio-frame {
width: 100%;
min-height: 580px;
}
.project-grid, .skills-layout { grid-template-columns: 1fr; }
}
@media (max-width: 620px) {
.site-header,
.section,
.site-footer { width: min(100% - 24px, 1180px); }
.site-header { margin-top: 10px; gap: 10px; }
.header-action { justify-self: end; }
.nav-links a,
.header-action { padding-inline: 11px; font-size: 0.84rem; }
.section { padding: 68px 0; }
.hero {
padding: 42px 18px 24px;
border-radius: 14px;
}
.hero h1 {
font-size: clamp(2.45rem, 13vw, 3.8rem);
}
.hero-role {
font-size: clamp(1.25rem, 7vw, 1.75rem);
}
h1 { font-size: clamp(2.7rem, 16vw, 4.2rem); }
h2 { font-size: clamp(1.8rem, 10vw, 3rem); }
.hero-actions, .btn { width: 100%; }
.quick-facts span { flex: 1 1 100%; }
.hero-socials {
margin-top: 34px;
}
.hero-visual { min-height: auto; }
.hero-visual::before,
.hero-visual::after {
display: none;
}
.portfolio-frame {
min-height: auto;
padding: 20px;
}
.frame-nav {
align-items: flex-start;
}
.frame-nav nav {
flex-wrap: wrap;
justify-content: flex-end;
gap: 10px 14px;
}
.frame-hero {
grid-template-columns: 1fr;
min-height: 520px;
}
.ghost-word {
font-size: 4.5rem;
}
.frame-intro {
max-width: 82%;
padding: 22px;
}
.purple-panel {
right: 0;
top: 180px;
width: 78%;
height: 250px;
}
.frame-photo {
right: 0;
width: 72%;
height: 380px;
}
.frame-hero::before,
.frame-hero::after {
right: 0;
width: 74%;
}
.preview-grid {
grid-template-columns: 1fr;
}
.console-card { margin-top: 24px; transform: none; }
.console-body, .project-card, .skill-panel, .timeline { padding: 20px; }
.project-card { grid-template-columns: 1fr; }
.project-index { width: 44px; height: 44px; }
.status-line { grid-template-columns: auto 1fr; }
.status-line b { grid-column: 2; }
pre { font-size: 0.78rem; }
.site-footer { display: grid; gap: 12px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
scroll-behavior: auto !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
.particle-field { display: none; }
}
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