Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,19 @@
# Portfolio
Personal Portfolio – React & Styled Components

This is my personal portfolio website built with React, Styled Components.
It showcases my projects, skills, and background as a frontend developer.

Hero Section
-Displays my name, photo, intro text, and personal tagline.

Projects Section
-Renders each project from a data file
-Shows an image, description, tags, and links to GitHub + Live demo
-Mobile-friendly layout that flips image + text for alternating projects

Skills & Tech Section
-Lists my technical skills and tools, grouped into categories for clarity.

Footer
-Includes contact details and social icons.
7 changes: 6 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;700;800&display=swap" rel="stylesheet">

<title>Portfolio</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
</html>
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,23 @@
"preview": "vite preview"
},
"dependencies": {
"lucide-react": "^0.554.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"styled-components": "^6.1.19"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.22",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"postcss": "^8.5.6",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try out postcss and tailwind as well and forgot to remove the packages? :)

"tailwindcss": "^4.1.17",
"vite": "^6.2.0"
}
}
Binary file added public/Alice.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/my-words.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/recipe-app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/weather-app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 27 additions & 4 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,31 @@
export const App = () => {
import React from 'react';
import { GlobalStyle } from './GlobalStyles';

// Import Components
import HeroSection from './components/HeroSection';
import TechSection from './components/TechSection';
import ProjectsSection from './components/ProjectsSection';
import SkillsSection from './components/SkillsSection';
import Footer from './components/Footer';
import ArticlesSection from './components/ArticlesSection';

// Import Data
import { userData, techSkills, projects, skillsCategories, articles } from './data/portfolioData';

function App() {
return (
<>
<h1>Portfolio</h1>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatem, laborum! Maxime animi nostrum facilis distinctio neque labore consectetur beatae eum ipsum excepturi voluptatum, dicta repellendus incidunt fugiat, consequatur rem aperiam.</p>
<GlobalStyle />

<HeroSection user={userData} />
<TechSection tech={techSkills} />
<ProjectsSection list={projects} />
<ArticlesSection articles={articles} />
<SkillsSection categories={skillsCategories} />
<Footer user={userData} />

</>
)
);
}

export default App;
25 changes: 25 additions & 0 deletions src/GlobalStyles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// src/GlobalStyles.js
import { createGlobalStyle } from 'styled-components';

export const GlobalStyle = createGlobalStyle`
/* Importing the font Montserrat */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Montserrat', sans-serif;
background-color: #fff;
color: #333;
line-height: 1.6;
}
a {
text-decoration: none;
color: inherit;
}
`;
Comment on lines +4 to +25

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job setting up the global styles.

One small suggestion: you might consider adding a :root section for things like colors, fonts, or spacing. This makes it easier to update styles in one place later on. For example:

:root {
  --primary-color: #333;
  --background-color: #fff;
  --font-family: 'Montserrat', sans-serif;
}

Then you could use these variables in your styles, which can make future changes much quicker and more consistent.

Overall, really solid work! 😏👍

128 changes: 128 additions & 0 deletions src/components/ArticlesSection.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import React from 'react';
import styled from 'styled-components';
import { SquareArrowOutUpRight } from 'lucide-react';

const Section = styled.section`
padding: 100px 20px;
background-color: #000;
`;

const SectionTitle = styled.h2`
text-align: center;
font-size: 3rem;
font-weight: 800;
margin-bottom: 60px;
color: #fff;
`;

const Container = styled.div`
max-width: 800px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 60px;
`;

const ArticleCard = styled.article`
display: flex;
gap: 30px;
align-items: flex-start;
@media (max-width: 768px) {
flex-direction: column;
}
`;

const ImageContainer = styled.div`
flex: 0 0 300px;
img {
width: 100%;
height: 200px;
object-fit: cover;
box-shadow: 10px 10px 0px 0px #eee;
}
@media (max-width: 768px) {
flex: 1;
width: 100%;
img { height: 250px; }
}
`;

const Content = styled.div`
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
`;

const DateText = styled.span`
color: #fff;
font-size: 0.8rem;
font-weight: 700;
text-transform: uppercase;
margin-bottom: 10px;
display: block;
`;

const Title = styled.h3`
color: #fff;
font-size: 1.5rem;
font-weight: 800;
margin-bottom: 10px;
line-height: 1.3;
`;

const Description = styled.p`
font-size: 1.1rem;
color: #fff;
margin-bottom: 20px;
line-height: 1.6;
`;

const ReadButton = styled.a`
display: inline-flex;
align-items: center;
gap: 8px;
background-color: #fff;
color: #000;
padding: 10px 24px;
border-radius: 30px;
font-weight: 600;
font-size: 0.9rem;
width: fit-content;
&:hover {
background-color: #333;
transform: translateY(-2px);
transition: 0.2s;
}
`;

export default function ArticlesSection({ articles }) {
if (!articles || articles.length === 0) return null;

return (
<Section>
<SectionTitle>My Words</SectionTitle>
<Container>
{articles.map((article) => (
<ArticleCard key={article.id}>
<ImageContainer>
<img src={article.image} alt={article.title} />
</ImageContainer>
<Content>
<DateText>{article.date}</DateText>
<Title>{article.title}</Title>
<Description>{article.description}</Description>
<ReadButton href={article.link} target="_blank" rel="noopener noreferrer">
Read Article <SquareArrowOutUpRight size={16} />
</ReadButton>
</Content>
</ArticleCard>
))}
</Container>
</Section>
);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice work on the ArticlesSection component! The layout is clean, responsive, and easy to read. I like the responsive adjustments for mobile look solid! 😀

A couple of suggestions:

Colors & spacing – you could consider moving repeated values like #fff, #000, and spacing units into a :root or theme variables. This would make it easier to tweak styles globally later. And the image sizes – right now the images have fixed heights. Maybe using min-height or making them fully responsive could help avoid cropping issues on unusual image dimensions.

65 changes: 65 additions & 0 deletions src/components/Footer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react';
import styled from 'styled-components';
import { Linkedin, Github } from "lucide-react";

const FooterWrap = styled.footer`
background: #000;
color: #fff;
padding: 80px 20px;
text-align: center;
`;

const FooterTitle = styled.h2`
font-size: 3rem;
margin-bottom: 40px;
`;

const ProfilePic = styled.img`
width: 80px;
height: 80px;
border-radius: 50%;
margin-bottom: 20px;
`;

const Socials = styled.div`
display: flex;
gap: 20px;
justify-content: center;
margin-top: 30px;
`;

const SocialLink = styled.a`
color: #fff;
transition: opacity 0.2s ease;
}
`;

export default function Footer({ user }) {
return (
<FooterWrap>

<FooterTitle>Let's Talk</FooterTitle>

<ProfilePic
src={user.profileImage}
alt={`${user.name}'s profile photo`}
/>

<h3>{user.name}</h3>
<p>{user.phone}</p>
<p>{user.email}</p>

<Socials>
<SocialLink href={user.socials.linkedin} aria-label="LinkedIn profile">
<Linkedin />
</SocialLink>

<SocialLink href={user.socials.github} aria-label="GitHub profile">
<Github />
</SocialLink>
</Socials>

</FooterWrap>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job on the Footer component! The structure is easy to follow. I like that you included alt text for the profile image and aria-labels for the social links — accessibility attention is always a plus 🌹

);
}
72 changes: 72 additions & 0 deletions src/components/HeroSection.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import styled from 'styled-components';

const Section = styled.header`
padding: 100px 20px;
text-align: center;
background: white;
`;

const SmallHeading = styled.p`
text-transform: uppercase;
letter-spacing: 2px;
font-size: 3rem;
font-weight: 700;
color: #555;
margin-bottom: 15px;
`;

const Name = styled.h1`
font-size: 3.5rem;
font-weight: 800;
margin-bottom: 30px;
color: #000;

@media (max-width: 768px) {
font-size: 2.5rem;
}
`;

const ProfileImg = styled.img`
width: 280px;
height: 280px;
border-radius: 50%;
object-fit: cover;
margin: 0 auto 30px;
`;

// This styles the "intro" (Bold text)
const MainIntro = styled.h2`
font-size: 1.5rem;
font-weight: 700;
max-width: 600px;
margin: 0 auto 20px;
line-height: 1.4;
`;

// This styles the "subIntro" (Lighter text)
const SubText = styled.p`
font-size: 1.1rem;
color: #666;
max-width: 500px;
margin: 0 auto;
line-height: 1.6;
`;

export default function HeroSection({ user }) {
return (
<Section>
<SmallHeading>Nihao, I'm</SmallHeading>

<Name>{user.name}</Name>

<ProfileImg src={user.profileImage} alt={user.name} />

{/* 1. The Bold Intro */}
<MainIntro>{user.intro}</MainIntro>

{/* 2. The Lighter Sub-Intro */}
<SubText>{user.subIntro}</SubText>
</Section>
);
}
Loading