Commit 1a7abf11 authored by HopNV's avatar HopNV

Porfolio

parent 29605342
node_modules/
dist/
.env
.env.*
# Local/generated assets from earlier drafts
assets/
script.js
styles.css
# Porfolio_NguyenVanHop
# Portfolio Nguyen Van Hop
Personal portfolio for a Frontend Developer role, built with ReactJS, Vite, and Tailwind CSS.
## Run
```bash
npm install
npm run dev
```
## Build
```bash
npm run build
```
## Structure
- `src/data/portfolio.js`: editable CV/profile content
- `src/components/`: reusable page sections
- `src/styles.css`: Tailwind layers and shared utility classes
<!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="Nguyen Van Hop - Frontend Developer portfolio built with React, Vite, and Tailwind CSS."
/>
<title>Nguyen Van Hop | Frontend Developer</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
This diff is collapsed.
{
"name": "nguyen-van-hop-portfolio",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.49",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwindcss": "^3.4.17",
"vite": "^6.0.7"
},
"devDependencies": {}
}
export default {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};
import Navbar from "./components/Navbar.jsx";
import Hero from "./components/Hero.jsx";
import About from "./components/About.jsx";
import Skills from "./components/Skills.jsx";
import Projects from "./components/Projects.jsx";
import Education from "./components/Education.jsx";
import Contact from "./components/Contact.jsx";
import Footer from "./components/Footer.jsx";
export default function App() {
return (
<div className="min-h-screen overflow-hidden bg-[radial-gradient(circle_at_top_left,rgba(37,99,235,0.28),transparent_34rem),radial-gradient(circle_at_top_right,rgba(14,165,233,0.16),transparent_28rem),linear-gradient(180deg,#020617_0%,#0f172a_44%,#111827_100%)]">
<div className="pointer-events-none absolute inset-x-0 top-24 -z-10 mx-auto h-[48rem] max-w-7xl">
<div className="absolute left-0 top-8 h-72 w-72 animate-float rounded-full bg-brand-500/10 blur-3xl" />
<div
className="absolute right-0 top-32 h-80 w-80 animate-float rounded-full bg-cyan-400/10 blur-3xl"
style={{ animationDelay: "1.5s" }}
/>
</div>
<Navbar />
<main className="relative">
<Hero />
<About />
<Skills />
<Projects />
<Education />
<Contact />
</main>
<Footer />
</div>
);
}
import SectionHeader from "./SectionHeader.jsx";
export default function About() {
return (
<section className="section-shell section-divider" id="about">
<div className="grid gap-10 lg:grid-cols-[0.8fr_1.2fr] lg:items-start">
<SectionHeader eyebrow="About Me" title="A practical frontend learner with product UI focus." />
<div className="grid gap-6">
<article className="section-panel min-h-[22rem] transition duration-500 ease-out hover:-translate-y-1 hover:border-brand-400/30 hover:shadow-lift sm:p-10 lg:p-12">
<p className="text-xl font-semibold leading-9 text-slate-100 sm:text-lg sm:leading-8">
I am a Multimedia Application Development student at PTIT, looking for
frontend internship opportunities where I can build polished interfaces,
improve product flows, and learn from real engineering teams.
</p>
<p className="mt-6 text-xl font-semibold leading-9 text-slate-100 sm:text-lg sm:leading-8">
My current work centers on responsive pages, role-based dashboards,
forms, data tables, authentication flows, and connecting React
applications with Node.js/Express APIs.
</p>
</article>
<div className="grid gap-4 sm:grid-cols-3">
{["Responsive UI", "Role-based flows", "API Integration"].map((item) => (
<div
className="card-surface flex min-h-24 items-center justify-center px-6 py-7 text-center text-lg font-black text-slate-50"
key={item}
>
{item}
</div>
))}
</div>
</div>
</div>
</section>
);
}
import { profile } from "../data/portfolio.js";
import SectionHeader from "./SectionHeader.jsx";
export default function Contact() {
return (
<section className="section-shell section-divider" id="contact">
<div className="section-panel mx-auto max-w-5xl bg-gradient-to-br from-slate-900 via-slate-900 to-brand-950 p-6 text-center text-white shadow-lift sm:p-10 lg:p-14">
<SectionHeader
align="center"
eyebrow="Contact"
title="Ready to contribute to a frontend team."
/>
<p className="mx-auto mt-5 max-w-2xl leading-8 text-slate-300">
I am open to frontend internship roles where I can build clean interfaces,
improve product workflows, and keep learning with a real engineering team.
</p>
<div className="mx-auto mt-9 grid max-w-4xl gap-4 md:grid-cols-3">
<a
className="link-focus rounded-2xl border border-white/10 bg-white/5 p-5 text-center font-bold text-slate-100 transition duration-300 ease-out hover:-translate-y-1 hover:border-brand-400/30 hover:bg-white/10"
href={`mailto:${profile.email}`}
>
{profile.email}
</a>
<a
className="link-focus rounded-2xl border border-white/10 bg-white/5 p-5 text-center font-bold text-slate-100 transition duration-300 ease-out hover:-translate-y-1 hover:border-brand-400/30 hover:bg-white/10"
href={`tel:${profile.phone.replaceAll(" ", "")}`}
>
{profile.phone}
</a>
<a
className="link-focus rounded-2xl border border-white/10 bg-white/5 p-5 text-center font-bold text-slate-100 transition duration-300 ease-out hover:-translate-y-1 hover:border-brand-400/30 hover:bg-white/10"
href={profile.github}
rel="noreferrer"
target="_blank"
>
GitHub Profile
</a>
</div>
</div>
</section>
);
}
import { education } from "../data/portfolio.js";
import SectionHeader from "./SectionHeader.jsx";
export default function Education() {
return (
<section className="section-shell section-divider" id="education">
<SectionHeader eyebrow="Education / Experience" title="Academic foundation and project practice." />
<div className="mt-12 grid gap-6 lg:grid-cols-[1fr_0.8fr]">
<article className="section-panel transition duration-500 ease-out hover:-translate-y-1 hover:border-brand-400/30 hover:shadow-lift sm:p-8">
<p className="text-sm font-black uppercase tracking-[0.22em] text-brand-400">
{education.time}
</p>
<h3 className="mt-4 text-2xl font-black text-slate-50">{education.school}</h3>
<p className="mt-4 text-lg text-slate-300">Major: {education.major}</p>
<p className="mt-2 text-lg text-slate-300">Current GPA: {education.gpa}</p>
</article>
<article className="section-panel bg-gradient-to-br from-brand-500/20 via-slate-900 to-slate-900 text-white transition duration-500 ease-out hover:-translate-y-1 hover:border-brand-400/30 hover:shadow-lift sm:p-8">
<p className="text-sm font-black uppercase tracking-[0.22em] text-brand-300">
Experience Focus
</p>
<h3 className="mt-4 text-2xl font-black">Frontend Internship Ready</h3>
<p className="mt-4 leading-7 text-slate-300">
Project practice includes responsive UI, role-based screens, REST API
integration, JWT authentication flows, and dashboard-oriented interfaces.
</p>
</article>
</div>
</section>
);
}
import { profile } from "../data/portfolio.js";
export default function Footer() {
return (
<footer className="border-t border-white/10 bg-slate-950/70">
<div className="mx-auto flex max-w-7xl flex-col gap-3 px-5 py-8 text-sm font-semibold text-slate-400 sm:px-8 md:flex-row md:items-center md:justify-between lg:px-12">
<p>
© {new Date().getFullYear()} {profile.name}. Built with React, Vite, and
Tailwind CSS.
</p>
<a className="link-focus rounded-md text-brand-400 hover:text-brand-300" href="#home">
Back to top
</a>
</div>
</footer>
);
}
import { profile } from "../data/portfolio.js";
import profilePhoto from "../../porfolio.jpg";
const highlights = ["ReactJS", "TypeScript", "Tailwind CSS", "REST APIs"];
export default function Hero() {
return (
<section className="section-shell flex min-h-screen items-center pt-32" id="home">
<div className="grid w-full items-center gap-10 lg:grid-cols-[1.05fr_0.95fr] lg:gap-14">
<div className="animate-fadeUp">
<p className="mb-5 inline-flex rounded-full border border-brand-400/30 bg-white/5 px-4 py-2 text-sm font-bold text-brand-300 shadow-soft backdrop-blur-xl">
Frontend Developer Portfolio
</p>
<h1 className="font-display max-w-4xl text-5xl font-extrabold leading-[0.92] tracking-[-0.035em] text-slate-50 sm:text-6xl lg:text-[5.8rem]">
Hi, I&apos;m <span className="text-brand-400">{profile.name}</span>
</h1>
<p className="mt-6 max-w-2xl text-xl leading-9 text-slate-300 sm:text-[1.35rem]">
I build clean, responsive, user-focused interfaces with React, TypeScript,
Tailwind CSS, and practical API integration experience.
</p>
<div className="mt-8 flex flex-wrap gap-3">
{highlights.map((item) => (
<span
className="rounded-full border border-white/10 bg-white/5 px-4 py-2 text-sm font-bold text-slate-200 shadow-sm backdrop-blur-xl"
key={item}
>
{item}
</span>
))}
</div>
<div className="mt-10 flex flex-col gap-3 sm:flex-row">
<a
className="link-focus hover-shine rounded-lg bg-gradient-to-r from-brand-500 to-cyan-500 px-6 py-3 text-center font-black text-white shadow-soft transition duration-300 ease-out hover:-translate-y-1 hover:brightness-110"
href="#projects"
>
View Projects
</a>
<a
className="link-focus rounded-lg border border-white/10 bg-white/5 px-6 py-3 text-center font-black text-slate-100 shadow-sm transition duration-300 ease-out hover:-translate-y-1 hover:border-brand-400/30 hover:bg-white/10 hover:text-white"
href={profile.cv}
rel="noreferrer"
target="_blank"
>
Download CV
</a>
</div>
</div>
<div
className="relative animate-fadeUp lg:justify-self-end"
style={{ animationDelay: "120ms" }}
>
<div className="absolute -inset-6 rounded-[2.25rem] bg-brand-500/20 blur-3xl" />
<div className="card-surface relative overflow-hidden rounded-[2rem] p-4 sm:p-6">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top,rgba(96,165,250,0.16),transparent_38%)]" />
<div className="relative overflow-hidden rounded-[1.5rem] border border-white/10 bg-slate-900/80 shadow-[0_24px_70px_rgba(2,6,23,0.45)]">
<img
alt={profile.name}
className="h-[38rem] w-full object-cover object-center transition duration-700 ease-out hover:scale-[1.03]"
src={profilePhoto}
/>
<div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-slate-950/35 via-transparent to-transparent" />
</div>
</div>
</div>
</div>
</section>
);
}
import { useState } from "react";
import { navItems, profile } from "../data/portfolio.js";
export default function Navbar() {
const [open, setOpen] = useState(false);
return (
<header className="fixed inset-x-0 top-0 z-50 border-b border-white/10 bg-slate-950/75 backdrop-blur-xl">
<div className="mx-auto flex h-20 max-w-7xl items-center justify-between px-5 sm:px-8 lg:px-12">
<a className="link-focus flex items-center gap-3 rounded-lg" href="#home">
<span className="grid h-11 w-11 place-items-center rounded-lg bg-gradient-to-br from-brand-900 via-brand-700 to-brand-400 font-black text-white shadow-soft">
H
</span>
<span className="hidden font-black text-slate-100 sm:block">{profile.name}</span>
</a>
<nav className="hidden items-center gap-7 md:flex">
{navItems.map((item) => (
<a
className="link-focus rounded-md text-sm font-bold text-slate-300 hover:text-brand-400"
href={item.href}
key={item.href}
>
{item.label}
</a>
))}
</nav>
<button
aria-label="Toggle navigation"
className="link-focus grid h-11 w-11 place-items-center rounded-lg border border-white/10 bg-white/5 text-slate-100 md:hidden"
onClick={() => setOpen((value) => !value)}
type="button"
>
<span className="relative h-4 w-5">
<span
className={`absolute left-0 top-1 h-0.5 w-5 bg-current transition ${open ? "translate-y-1.5 rotate-45" : ""}`}
/>
<span
className={`absolute left-0 top-3 h-0.5 w-5 bg-current transition ${open ? "-translate-y-1.5 -rotate-45" : ""}`}
/>
</span>
</button>
</div>
{open && (
<nav className="mx-5 mb-4 grid gap-2 rounded-lg border border-white/10 bg-slate-950/95 p-3 shadow-soft md:hidden">
{navItems.map((item) => (
<a
className="rounded-lg px-3 py-3 font-bold text-slate-300 hover:bg-white/5 hover:text-brand-400"
href={item.href}
key={item.href}
onClick={() => setOpen(false)}
>
{item.label}
</a>
))}
</nav>
)}
</header>
);
}
import { projects } from "../data/portfolio.js";
import SectionHeader from "./SectionHeader.jsx";
export default function Projects() {
return (
<section className="section-shell section-divider" id="projects">
<SectionHeader
eyebrow="Projects"
title="Selected work with clear responsibilities and product flows."
/>
<div className="mt-12 grid gap-6 lg:grid-cols-2">
{projects.map((project, index) => (
<article
className="hover-shine section-panel transition duration-500 ease-out hover:-translate-y-2 hover:border-brand-400/30 hover:shadow-lift sm:p-8"
key={project.title}
>
<div className="flex flex-wrap items-start justify-between gap-4">
<span className="grid h-14 w-14 place-items-center rounded-xl bg-gradient-to-br from-brand-500 to-cyan-500 text-lg font-black text-white shadow-soft">
{String(index + 1).padStart(2, "0")}
</span>
<div className="text-right text-sm font-bold uppercase tracking-wide text-brand-300">
<p>{project.type}</p>
<p className="mt-1 text-slate-400">{project.time}</p>
</div>
</div>
<h3 className="mt-7 text-2xl font-black text-slate-50">{project.title}</h3>
<p className="mt-2 font-bold text-brand-300">Role: {project.role}</p>
<p className="mt-4 leading-7 text-slate-300">{project.description}</p>
<ul className="mt-6 grid gap-3">
{project.features.map((feature) => (
<li className="flex gap-3 text-slate-300" key={feature}>
<span className="mt-2 h-2 w-2 shrink-0 rounded-full bg-brand-400 shadow-[0_0_0_4px_rgba(59,130,246,0.18)]" />
<span>{feature}</span>
</li>
))}
</ul>
<div className="mt-6 flex flex-wrap gap-2">
{project.tech.map((tech) => (
<span
className="rounded-full border border-white/10 bg-white/5 px-3 py-1.5 text-sm font-bold text-slate-200"
key={tech}
>
{tech}
</span>
))}
</div>
<div className="mt-7 flex flex-wrap gap-3">
{project.links.map((link) => (
<a
className="link-focus rounded-lg border border-white/10 bg-white/5 px-4 py-2 font-black text-slate-100 transition duration-300 ease-out hover:-translate-y-1 hover:border-brand-400/30 hover:bg-brand-500 hover:text-white"
href={link.href}
key={link.href}
rel="noreferrer"
target="_blank"
>
{link.label}
</a>
))}
</div>
</article>
))}
</div>
</section>
);
}
export default function SectionHeader({ eyebrow, title, align = "left" }) {
const isCentered = align === "center";
const alignment = isCentered ? "mx-auto text-center" : "";
const titleAlignment = isCentered ? "mx-auto" : "";
return (
<div className={`${alignment} max-w-4xl`}>
<p className="mb-4 text-lg font-extrabold uppercase tracking-[0.3em] text-brand-200 sm:text-xl">
{eyebrow}
</p>
<h2
className={`${titleAlignment} max-w-2xl text-3xl font-black leading-[1.12] tracking-[-0.03em] text-slate-50 sm:text-4xl sm:leading-[1.08] lg:text-5xl lg:leading-[1.08]`}
>
{title}
</h2>
</div>
);
}
import { skillGroups } from "../data/portfolio.js";
import SectionHeader from "./SectionHeader.jsx";
export default function Skills() {
return (
<section className="section-shell section-divider" id="skills">
<SectionHeader eyebrow="Skills" title="Frontend stack with backend fundamentals." />
<div className="mt-12 grid gap-6 md:grid-cols-2 xl:grid-cols-4">
{skillGroups.map((group, index) => (
<article
className="hover-shine section-panel min-h-[18rem] transition duration-500 ease-out hover:-translate-y-2 hover:scale-[1.02] hover:border-brand-400/30 hover:shadow-lift"
key={group.title}
>
<div className="mb-6 grid h-14 w-14 place-items-center rounded-2xl bg-gradient-to-br from-brand-500 to-cyan-500 text-base font-black text-white shadow-soft">
{String(index + 1).padStart(2, "0")}
</div>
<h3 className="text-3xl font-black text-slate-50">{group.title}</h3>
<div className="mt-6 flex flex-wrap gap-2.5">
{group.items.map((skill) => (
<span
className="rounded-full border border-white/10 bg-white/5 px-4 py-2.5 text-base font-bold text-slate-100"
key={skill}
>
{skill}
</span>
))}
</div>
</article>
))}
</div>
</section>
);
}
export const profile = {
name: "Nguyen Van Hop",
role: "Frontend Developer",
email: "nguyenhop0410@gmail.com",
phone: "083 655 8310",
location: "Thu Duc, Ho Chi Minh City",
github: "https://github.com/nguyenvanhop0410",
cv: "/NguyenVanHop_CV_Frontend%20.pdf"
};
export const navItems = [
{ label: "About", href: "#about" },
{ label: "Skills", href: "#skills" },
{ label: "Projects", href: "#projects" },
{ label: "Education", href: "#education" },
{ label: "Contact", href: "#contact" }
];
export const skillGroups = [
{
title: "Frontend",
items: ["ReactJS", "JavaScript", "TypeScript", "Tailwind CSS", "HTML5", "CSS3"]
},
{
title: "Backend Basics",
items: ["Node.js", "Express.js", "MongoDB", "MySQL", "REST APIs", "JWT"]
},
{
title: "Tools & Workflow",
items: ["Vite", "Git", "GitHub", "Postman", "Context API", "LocalStorage"]
},
{
title: "Strengths",
items: ["Responsive UI", "Teamwork", "Self-learning", "UI/UX Attention"]
}
];
export const projects = [
{
title: "Sports-store",
type: "Personal Project",
time: "Aug 2025 - Present",
role: "Frontend developer, API integration, admin UI",
description:
"A sports e-commerce web app with product browsing, cart, checkout, order tracking, admin product CRUD, order management, and dashboard statistics.",
features: [
"Built responsive shopping flows for product listing, cart, checkout, and order tracking.",
"Integrated Node.js/Express REST APIs with JWT authentication.",
"Created admin screens for product CRUD, order management, and statistics."
],
tech: ["ReactJS", "Node.js", "Express.js", "JWT", "LocalStorage"],
links: [
{ label: "Live Demo", href: "https://sports-store-one.vercel.app" },
{ label: "GitHub", href: "https://github.com/nguyenvanhop0410/sports_store" }
]
},
{
title: "Student & Class Management Website",
type: "Team Project",
time: "Dec 2025 - Present",
role: "Frontend developer, role-based UI, protected flows",
description:
"A responsive student and class management system for admin, lecturer, and student workflows with dashboards, data tables, search, forms, detail pages, and authentication.",
features: [
"Developed role-based interfaces for admin, lecturer, and student users.",
"Built dashboard, table, search, form, and detail page patterns.",
"Connected protected flows to Express and MongoDB backend APIs."
],
tech: ["React", "TypeScript", "Vite", "Tailwind CSS", "MongoDB"],
links: [
{
label: "GitHub",
href: "https://github.com/Anroiy123/Student-and-Class-Management-Website---1"
}
]
}
];
export const education = {
school: "Posts and Telecommunications Institute of Technology (PTIT)",
major: "Multimedia Application Development",
time: "2022 - Now",
gpa: "3.06 / 4.00"
};
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";
import "./styles.css";
createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{js,jsx}"],
theme: {
extend: {
colors: {
brand: {
50: "#eff6ff",
100: "#dbeafe",
200: "#bfdbfe",
300: "#93c5fd",
400: "#60a5fa",
500: "#3b82f6",
600: "#2563eb",
700: "#1d4ed8",
800: "#1e40af",
900: "#1e3a8a",
950: "#172554"
}
},
boxShadow: {
soft: "0 18px 50px rgba(30, 64, 175, 0.12)",
lift: "0 28px 80px rgba(30, 64, 175, 0.18)"
},
keyframes: {
float: {
"0%, 100%": { transform: "translateY(0)" },
"50%": { transform: "translateY(-10px)" }
},
fadeUp: {
"0%": { opacity: "0", transform: "translateY(18px)" },
"100%": { opacity: "1", transform: "translateY(0)" }
}
},
animation: {
float: "float 6s ease-in-out infinite",
fadeUp: "fadeUp 0.75s ease-out both"
}
}
},
plugins: []
};
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
});
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