Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b45e797
add foundation for the portfolio and styling for intro section images
sandrahagevall Nov 18, 2025
894919e
add font Poppins
sandrahagevall Nov 18, 2025
20a8a40
add components to the layout
sandrahagevall Nov 19, 2025
12a4f1e
add props to ProjectCard, and styling for the tags
sandrahagevall Nov 19, 2025
5c7d048
add more data to projects.json
sandrahagevall Nov 19, 2025
51d96c5
add components and styling for buttons and add svg for buttons
sandrahagevall Nov 20, 2025
60e5466
add component for H2 heading and styling for images in ProjectCard
sandrahagevall Nov 20, 2025
fc60d5d
change color on the text
sandrahagevall Nov 20, 2025
22294ca
add components for contact and contactinfo
sandrahagevall Nov 20, 2025
10962ce
add images for featured projects and some styling, also more props to…
sandrahagevall Nov 21, 2025
330f388
add styled components for button and a new component SectionContainer…
sandrahagevall Nov 24, 2025
66b423b
add component for GlobalStyle and theme.js for styling
sandrahagevall Nov 25, 2025
d6efc6a
add styled component for Hero section
sandrahagevall Nov 25, 2025
3d9bbfc
add styling to hero.js
sandrahagevall Nov 26, 2025
09c3645
add styled component for tech-component, also add component for icons…
sandrahagevall Nov 26, 2025
8ad0121
styled component for featuredprojects and projectcard
sandrahagevall Nov 26, 2025
5f119fd
add styled component to Skills and SkillsGroup. Also add styling to P…
sandrahagevall Nov 27, 2025
0557082
add styled component for MyWords and Contact, also animation on scrol…
sandrahagevall Nov 27, 2025
44d6f47
add styling to projectcards images
sandrahagevall Nov 27, 2025
dacd5d3
add styling to blogpostcard and contact, also made folders for all th…
sandrahagevall Nov 28, 2025
a8b006f
change names for icons and img
sandrahagevall Nov 28, 2025
2d0ef9b
add netlify link to readme
sandrahagevall Nov 28, 2025
3af5f90
cleaned up code and add focus color on buttons for tab
sandrahagevall Nov 29, 2025
d9308e6
add favicon, skip link and cleaned up code
sandrahagevall Nov 30, 2025
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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
# Portfolio
# Sandra Hagevall — Portfolio

## 🚀 Live Demo
https://sandrahagevall.netlify.app/
39 changes: 28 additions & 11 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
<!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/png"
href="/images/favicon.png"
/>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
<link
href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
rel="stylesheet"
/>
<title>Sandra Hagevall - Portfolio</title>
</head>

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

</html>
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@
"preview": "vite preview"
},
"dependencies": {
"framer-motion": "^12.23.24",
"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",
"@vitejs/plugin-react": "^4.7.0",
"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
3 changes: 3 additions & 0 deletions public/gitcontact.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/github.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/images/arrow.svg
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/images/business2.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/images/careerchange.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/images/contactimg.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/images/eventfinder2.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/images/favicon.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/images/footerimg.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/images/frontend.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/images/kugghjul.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/images/linkedin.svg
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/images/logistik.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/images/placeholder.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/images/recipe2.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/images/topimg.png

Choose a reason for hiding this comment

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

You look very confident and professional! Love the picture🤩🙌🏻

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/images/watherapp2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/instagram.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/link.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 28 additions & 2 deletions src/App.jsx

Choose a reason for hiding this comment

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

Clean and well structured App.jsx! I learned from your code that having

element is good practice for SEO, accessibility etc. 💫 Well done!

Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
import { Hero } from "./components/Hero"
import { Tech } from "./components/Tech"
import { FeaturedProjects } from "./components/FeaturedProjects"
import { Skills } from "./components/Skills"
import { MyWords } from "./components/MyWords"
import { Contact } from "./components/Contact"
import { GlobalStyle } from "./components/GlobalStyle.jsx"
import { theme } from "./components/theme.js"
import { ThemeProvider } from "styled-components"


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>
<ThemeProvider theme={theme}>
<GlobalStyle />
<a href="#main" className="skip-link">Skip to main content</a>
Copy link

Choose a reason for hiding this comment

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

Nice that you include a "skip to content" link!

<Hero />
<main id="main" tabIndex={-1}>

<Tech />

<FeaturedProjects />

<Skills />

<MyWords />

<Contact />
</main>
</ThemeProvider>
</>
)
}
75 changes: 75 additions & 0 deletions src/components/Button.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import styled from "styled-components"

export const StyledButton = styled.button`

Choose a reason for hiding this comment

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

Do you use outside of this component? Maybe no need to export if it's local? :)

Also, I would recommend to style instead of . It's better for accessibility💡 I write more about this in the projectCard.jsx file!

display: flex;
align-items: center;
width: 303px;
height: 48px;
padding: ${({ theme }) => `${theme.spacing.sm} ${theme.spacing.md}`};
gap: ${({ theme }) => theme.spacing.md};
font-family: inherit;
font-weight: 500;
font-size: 18px;
cursor: pointer;
border-radius: 12px;

background: ${({ theme, $variant }) =>
$variant === "secondary" ? "transparent" : theme.colors.primary};

color: ${({ theme, $variant }) =>
$variant === "secondary" ? theme.colors.primary : theme.colors.secondary};
border: ${({ theme, $variant }) =>
$variant === "secondary" ? `1px solid ${theme.colors.primary}` : "none"};

transition: 0.2s ease-in-out;

/* Smooth transition for hover and focus ring */
transition: opacity 0.18s ease-in-out, box-shadow 0.12s ease-in-out;

&:hover {
opacity: 0.85;
}

&:focus {
outline: none;
}

/* Use focus-visible so mouse users aren't shown the ring; adapt ring by variant */
&:focus-visible {
outline: none;
box-shadow: ${({ $variant, theme }) =>
$variant === "secondary"
? "0 0 0 3px rgba(0,0,0,0.85)"
: `0 0 0 3px ${theme.colors.accent}`};
outline-offset: 2px;
}
`

const Icon = styled.img`
width: 2rem;
height: 2rem;
object-fit: contain;
`

export const Button = ({ icon, children, variant = "primary", as: asProp, href, target, rel, ariaLabel, ...rest }) => {
// Render as an anchor when 'as="a"' is passed or when 'href' exists.
return (asProp === "a" || href) ? (
<StyledButton
as="a"
href={href}
target={target}
rel={rel}
$variant={variant}
aria-label={ariaLabel}
{...rest}
>
{icon && <Icon src={icon} alt="" />}
{children}
</StyledButton>
) : (
<StyledButton type="button" $variant={variant} {...rest}>
{icon && <Icon src={icon} alt="" />}
{children}
</StyledButton>
)
}
37 changes: 37 additions & 0 deletions src/components/Contact/Contact.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Heading } from '../Heading'
import { ContactInfo } from './ContactInfo.jsx'
import { SectionContainer } from '../SectionContainer'
import { IconButton } from '../IconButton'
import styled from 'styled-components'

export const ContactWrapper = styled.section`
background-color: ${({ theme }) => theme.colors.primary};
color: ${({ theme }) => theme.colors.secondary};
`
export const SocialWrapper = styled.div`
display: flex;
gap: 1.5rem;
margin-top: 2rem;

justify-content: center; /* ← centrera ikonerna */
`

export const Contact = () => {
return (
<ContactWrapper>
<SectionContainer>
<Heading>Let's Talk</Heading>
<ContactInfo
name="Sandra Hagevall"
phone="+46(0)703 15 53 85"
email="sandrahagevall@hotmail.com"
imageSrc="/images/contactimg.png"
/>
<SocialWrapper>
<IconButton icon="/images/linkedin.svg" url="https://www.linkedin.com/in/sandra-hagevall-8001b5183/" label="LinkedIn" />
<IconButton icon="/gitcontact.svg" url="https://github.com/sandrahagevall" label="GitHub" />
</SocialWrapper>
</SectionContainer>
</ContactWrapper>
)
}
66 changes: 66 additions & 0 deletions src/components/Contact/ContactInfo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import styled from "styled-components"

export const ContactInfoWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
`

export const ContactImage = styled.img`
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;


@media ${({ theme }) => theme.breakpoints.desktop} {
width: 150px;
height: 150px;
}
`

export const TextContainer = styled.div`
width: 100%;
text-align: left;

p {
margin: 0.25rem 0;
padding: 0 12px;
}

.contact-link {
text-decoration: none;
color: ${({ theme }) => theme.colors.secondary};
transition: 0.2s ease;
}

.contact-link:hover {
text-decoration: underline;
opacity: 0.7;
}

@media ${({ theme }) => theme.breakpoints.tablet} {
text-align: center;
}
`


export const ContactInfo = ({ name, phone, email, imageSrc }) => {
return (
<ContactInfoWrapper>

<ContactImage src={imageSrc} alt={`Contact ${name}`} />

<TextContainer>
<p>{name}</p>
<p>
<a href={`tel:${phone}`} className="contact-link">{phone}</a>
</p>
<p>
<a href={`mailto:${email}`} className="contact-link">{email}</a>
</p>
</TextContainer>
</ContactInfoWrapper>
)
}
1 change: 1 addition & 0 deletions src/components/Contact/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Contact } from "./Contact"
32 changes: 32 additions & 0 deletions src/components/FeaturedProjects/FeaturedProjects.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ProjectCard } from "./ProjectCard.jsx"
import projectsData from "../../data/projects.json"
import { Heading } from "../Heading"
import { Button } from "../Button"
import { SectionContainer } from "../SectionContainer"
import { FeaturedWrapper, FeaturedInner } from "./FeaturedProjects.styled.js"

export const FeaturedProjects = () => {
return (
<FeaturedWrapper>
<SectionContainer>
<FeaturedInner>
<Heading>Featured Projects</Heading>

{projectsData.projects.map((project) => (
<ProjectCard
key={project.name}
tags={project.tags}
title={project.name}
description={project.description}
image={project.image}
liveUrl={project.netlify}
codeUrl={project.github}
position={project.position}
/>
))}
<Button variant="secondary" icon="/images/arrow.svg">See more projects</Button>
</FeaturedInner>
</SectionContainer>
</FeaturedWrapper>
)
}
15 changes: 15 additions & 0 deletions src/components/FeaturedProjects/FeaturedProjects.styled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import styled from "styled-components"

export const FeaturedWrapper = styled.section`
background-color: ${({ theme }) => theme.colors.secondary};
color: ${({ theme }) => theme.colors.primary};
`
export const FeaturedInner = styled.div`
display: flex;
flex-direction: column;
align-items: center;
gap: 4rem;
width: 100%;
max-width: 1600px;
margin: 0 auto;
`
Loading