Skip to content
Open
Show file tree
Hide file tree
Changes from 12 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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# Portfolio
This is my personal portfolio built with React. It’s fully responsive, accessible, and styled with styled-components. It highlights my tech skills and projects, with links to GitHub and Netlify. The design follows a Figma layout provided by Technigo to keep everything clean and consistent.

https://ssofiejohansson.netlify.app/
48 changes: 37 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<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" />
<title>Portfolio</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

<head>
<meta charset="UTF-8" />
<link
rel="icon"
type="image/svg+xml"
href="/disco.png"
/>
<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:ital,wght@0,100..900;1,100..900&family=Mynerve&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Urbanist:ital,wght@0,100..900;1,100..900&display=swap"
rel="stylesheet"
>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<title>Sofie Johansson</title>
</head>

<body>
<div id="root"></div>
<script
type="module"
src="/src/main.jsx"
></script>
</body>

</html>
14 changes: 11 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,28 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "vite --host",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/free-regular-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"framer-motion": "^12.9.2",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"styled-components": "^6.1.17"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"@vitejs/plugin-react": "^4.4.1",
"babel-plugin-styled-components": "^2.1.4",
"eslint": "^9.21.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
Expand Down
Binary file added public/CV.pdf
Binary file not shown.
Binary file added public/img/disco.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/img/happyhooks.webp
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/img/heatherweather.webp
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/img/portfolio.webp
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/img/quiz.webp
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/img/recipe.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/vite.svg

This file was deleted.

19 changes: 17 additions & 2 deletions src/App.jsx
Copy link

Choose a reason for hiding this comment

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

Looks great! A understandable and clear structure!

Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import { Nav } from "./assets/sections/Nav"
import { Header } from "./assets/sections/Header"
import { Projects } from "./assets/sections/Projects"
import { Skills } from "./assets/sections/Skills"
import { Footer } from "./assets/sections/Footer"
import { Blog } from "./assets/sections/Blog"

export const 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>
<Nav />
<Header />
<main>

<Projects />
<Skills />
<Blog />

</main>
<Footer />
</>
)
}
94 changes: 94 additions & 0 deletions src/assets/sections/Blog.jsx
Copy link

Choose a reason for hiding this comment

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

Simple but it works

Copy link

Choose a reason for hiding this comment

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

Maybe an image would have been nice

Copy link
Author

Choose a reason for hiding this comment

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

I wont use this section :)

Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { ScrollAnimation } from "./components/ScrollAnimation";
import { useState } from "react";
import styled from "styled-components";

const BlogContainer = styled.section`
display: flex;
flex-direction: column;
gap: 20px;
padding: 50px;
margin: 0 auto;

@media (min-width: 768px) {
flex-direction: row;
gap: 40px;
justify-content: center;
}
`;

const BlogPost = styled.article`
background-color: var(--secondary);
border: 1px solid var(--primary);
padding: 20px;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
`;

const BlogExcerpt = styled.p`
font-family: var(--font-family-text);
color: var(--font-secondary);
margin-bottom: 10px;
`;

const BlogContent = styled.p`
font-family: var(--font-family-text);
color: var(--font-secondary);
margin-top: 10px;
display: ${({ isVisible }) => (isVisible ? "block" : "none")};
`;

const ToggleButton = styled.button`
font-family: var(--font-family-text);
color: var(--primary);
background: none;
border: 1px solid var(--primary);
border-radius: 8px;
padding: 8px 16px;
cursor: pointer;

&:hover {
background-color: var(--primary);
color: var(--secondary);
}
`;

export const Blog = () => {
const [isExpanded, setIsExpanded] = useState(false);

const toggleContent = () => {
setIsExpanded(!isExpanded);
};

return (
<ScrollAnimation>
<h2>Blog posts</h2>
<BlogContainer>
<BlogPost>
<h3>Blog title 1</h3>
<BlogExcerpt>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</BlogExcerpt>
<BlogContent isVisible={isExpanded}>
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</BlogContent>
<ToggleButton onClick={toggleContent}>
{isExpanded ? "Read Less" : "Read More"}
</ToggleButton>
</BlogPost>

<BlogPost>
<h3>Blog title 2</h3>
<BlogExcerpt>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</BlogExcerpt>
<BlogContent isVisible={isExpanded}>
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</BlogContent>
<ToggleButton onClick={toggleContent}>
{isExpanded ? "Read Less" : "Read More"}
</ToggleButton>
</BlogPost>

</BlogContainer>
</ScrollAnimation>
);
};
66 changes: 66 additions & 0 deletions src/assets/sections/Footer.jsx
Copy link

Choose a reason for hiding this comment

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

Love how it looks!

Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import profileImage from './img/footer-img.png';
import styled from 'styled-components';
import { Icons } from './components/Icons';
import { ProfileImg } from './Header';

const FooterContainer = styled.section`
Copy link

Choose a reason for hiding this comment

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

Could this have been a footer instead?

Copy link

Choose a reason for hiding this comment

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

Never mind, I guess your right! It is like a section since it covers the whole space

Copy link
Author

Choose a reason for hiding this comment

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

No, I think you were right! I changed it to a footer. Obvs makes more sense

display: flex;
flex-direction: column-reverse;

justify-content: center;
align-items: center;
padding: 60px 60px;
background: #C35132;
color: #FFF;
height: 100vh;

@media (min-width: 768px) {
flex-direction: row;
gap: 100px;

}
`

const FooterBio = styled.div`
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;

@media (min-width: 768px) {
align-items: flex-start;
}
`

const FooterHeading = styled.h2`
color: var(--secondary);
text-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
font-family: 'Mynerve';
font-size: 50px;
font-style: normal;
font-weight: 400;
letter-spacing: -5.76px;
padding-top: 50px;

@media (min-width: 768px) {
font-size: 70px;
padding-top: 0px;
}
`

export const Footer = () => {
return (
<>
<FooterContainer id='contact'>
<ProfileImg src={profileImage} alt="Profile image" />
<FooterBio>
<FooterHeading>Let's Talk!</FooterHeading>
<p>Sofie Johansson</p>
<p>+46(0)72 442 34 97</p>
<p>[email protected]</p>
<Icons color="var(--secondary)" />
</FooterBio>
</FooterContainer>
</>
)
}
88 changes: 88 additions & 0 deletions src/assets/sections/Header.jsx
Copy link

Choose a reason for hiding this comment

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

Great layout and well executed with all the positioning of all the different elements.

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from 'react';
import profileImage from './img/header-img.png';
import styled from 'styled-components';
import { Icons } from './components/Icons';

const HeaderContainer = styled.header`
Copy link

Choose a reason for hiding this comment

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

I like it but in my preference I wish that the content could be a bit more centered.

Screenshot 2025-05-02 at 19 40 23

display: flex;
flex-direction: column;
padding: 50px;
gap: 20px;
padding-top: 100px;

@media (min-width: 768px) {
height: 100vh;
Copy link

Choose a reason for hiding this comment

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

Maybe it would work is this was 100dvh so that it takes the nav into the calculations.

justify-content: center;
align-items: center;
}
`;

const HeaderTitle = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: left;
text-align: left;
`;

const HeaderIntro = styled.div`
display: flex;
flex-direction: column;
padding-top: 10px;

@media (min-width: 768px) {
max-width: 500px;
gap: 10px;
}
`;

const HeaderBio = styled.div`
display: flex;
flex-direction: column;
gap: 20px;
justify-content: center;
align-items: center;

@media (min-width: 768px) {
flex-direction: row;
gap: 80px;
}
`;

export const ProfileImg = styled.img`
width: 250px;
height: 260px;
border-radius: 50%;
margin-top: 20px;
padding: 10px;

@media (min-width: 768px) {
width: 300px;
height: 310px;
margin-top: 0px;
}
`;

export const Header = () => {
return (
<>
<HeaderContainer id="about">
<HeaderTitle>
<h1>Frontend Developer</h1>
<h2>+ former hospitality pro</h2>
</HeaderTitle>
<HeaderBio>
<HeaderIntro>
<h3>Hi, I'm Sofie</h3>
<p>
For the past decade I’ve worked in hospitality, lived in 10 different cities and soaked up all I could from life, cultures and people. But I’ve always been curious about tech and I finally decided to stop dreaming about it and just go for it.
So now I’m deep into JavaScript, React and building very cool things at Technigo — and honestly, I’m loving it. I’m looking for a frontend role where I can keep growing, build more cool stuff and bring some positive energy to a team.
</p>
<Icons color="var(--primary)" />
</HeaderIntro>
<ProfileImg src={profileImage} alt="Profile image" />
</HeaderBio>
</HeaderContainer>
</>
);
};
Loading