-
Notifications
You must be signed in to change notification settings - Fork 55
Sandra - Portfolio #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
b45e797
894919e
20a8a40
12a4f1e
5c7d048
51d96c5
60e5466
fc60d5d
22294ca
10962ce
330f388
66b423b
d6efc6a
3d9bbfc
09c3645
8ad0121
5f119fd
0557082
44d6f47
dacd5d3
a8b006f
2d0ef9b
3af5f90
d9308e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,4 @@ | ||
| # Portfolio | ||
| # Sandra Hagevall — Portfolio | ||
|
|
||
| ## 🚀 Live Demo | ||
| https://sandrahagevall.netlify.app/ |
| 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> |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
| </> | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import styled from "styled-components" | ||
|
|
||
| export const StyledButton = styled.button` | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
| ) | ||
| } | ||
| 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> | ||
| ) | ||
| } |
| 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> | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { Contact } from "./Contact" |
| 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> | ||
| ) | ||
| } |
| 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; | ||
| ` |
There was a problem hiding this comment.
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🤩🙌🏻