diff --git a/README.md b/README.md
index 200f4282..c626f28b 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,31 @@
-# Portfolio
+# Developer Portfolio – Jennifer Jansson
+
+This is my personal developer portfolio built with **React** and **styled-components**.
+It showcases my projects, technical skills, articles, and ways to get in contact with me.
+The design follows a provided Figma layout and the site is fully responsive.
+
+## Features
+
+- Hero section with introduction and portrait
+- Tech overview
+- Featured projects with links to GitHub and live demos
+- Skills section (Code, Toolbox, Upcoming, More)
+- My Words / thoughts about code
+- Contact section with social links
+- Accessible and responsive across all screen sizes from 320-1600px
+- Skip link and focus states for improved accessibility
+
+## Tech Stack
+
+- React
+- Vite
+- JavaScript (ES6)
+- styled-components
+- JSON data for dynamic content
+
+## Getting Started
+
+```bash
+npm install
+npm run dev
+```
diff --git a/index.html b/index.html
index 6676fb2d..deb8818a 100644
--- a/index.html
+++ b/index.html
@@ -1,10 +1,43 @@
-
+
-
+
- Portfolio
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Jennifer Jansson — Frontend Developer Portfolio
diff --git a/package.json b/package.json
index 48911600..83c48e82 100644
--- a/package.json
+++ b/package.json
@@ -11,13 +11,15 @@
},
"dependencies": {
"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",
diff --git a/public/accessibility.png b/public/accessibility.png
new file mode 100644
index 00000000..938fddd4
Binary files /dev/null and b/public/accessibility.png differ
diff --git a/public/arts.png b/public/arts.png
new file mode 100644
index 00000000..46b407d7
Binary files /dev/null and b/public/arts.png differ
diff --git a/public/business.png b/public/business.png
new file mode 100644
index 00000000..6b527446
Binary files /dev/null and b/public/business.png differ
diff --git a/public/footer.png b/public/footer.png
new file mode 100644
index 00000000..60656370
Binary files /dev/null and b/public/footer.png differ
diff --git a/public/happy.png b/public/happy.png
new file mode 100644
index 00000000..c5d039d7
Binary files /dev/null and b/public/happy.png differ
diff --git a/public/jj-favicon.svg b/public/jj-favicon.svg
new file mode 100644
index 00000000..51a85c6e
--- /dev/null
+++ b/public/jj-favicon.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/mic.png b/public/mic.png
new file mode 100644
index 00000000..f9d64048
Binary files /dev/null and b/public/mic.png differ
diff --git a/public/pitch.png b/public/pitch.png
new file mode 100644
index 00000000..335e6b21
Binary files /dev/null and b/public/pitch.png differ
diff --git a/public/portrait.png b/public/portrait.png
new file mode 100644
index 00000000..f7105039
Binary files /dev/null and b/public/portrait.png differ
diff --git a/public/post2.png b/public/post2.png
new file mode 100644
index 00000000..fcd38515
Binary files /dev/null and b/public/post2.png differ
diff --git a/public/react-icon.png b/public/react-icon.png
new file mode 100644
index 00000000..5e819ddd
Binary files /dev/null and b/public/react-icon.png differ
diff --git a/public/recipe.jpg b/public/recipe.jpg
new file mode 100644
index 00000000..b8a21190
Binary files /dev/null and b/public/recipe.jpg differ
diff --git a/public/weather.png b/public/weather.png
new file mode 100644
index 00000000..a5edc9d5
Binary files /dev/null and b/public/weather.png differ
diff --git a/public/web-accessibility.png b/public/web-accessibility.png
new file mode 100644
index 00000000..e5578ead
Binary files /dev/null and b/public/web-accessibility.png differ
diff --git a/pull_request_template.md b/pull_request_template.md
index 4263c7e8..626e7140 100644
--- a/pull_request_template.md
+++ b/pull_request_template.md
@@ -1 +1,5 @@
-Please include a link to your Figma design and a Netlify link.
\ No newline at end of file
+Please include a link to your Figma design and a Netlify link.
+
+https://jeffies-portfolio.netlify.app/
+
+https://www.figma.com/design/QV6stSlcpaPdDl2fojewHc/Figma-designs-for-students--Copy-?node-id=1791-1386&m=dev
diff --git a/src/App.jsx b/src/App.jsx
index a161d8d3..d5d26906 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,8 +1,43 @@
+// src/App.jsx
+import { GlobalStyle } from "./components/GlobalStyles";
+import { Hero } from "./sections/Hero";
+import { Tech } from "./sections/Tech";
+import { Projects } from "./sections/Projects";
+import { Skills } from "./sections/Skills";
+import { Journey } from "./sections/Journey";
+import { Contact } from "./sections/Contact";
+import styled from "styled-components";
+
+const SkipLink = styled.a`
+ position: absolute;
+ left: -999px;
+ top: 16px;
+ padding: 8px 16px;
+ background: #000;
+ color: #fff;
+ border-radius: 8px;
+ z-index: 1000;
+
+ &:focus-visible {
+ left: 16px;
+ }
+`;
+
export const App = () => {
return (
<>
-
Portfolio
-
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.
+
+ Skip to main content
+
+
+
+
+
+
+
+
+
+
>
- )
-}
+ );
+};
diff --git a/src/components/Button.jsx b/src/components/Button.jsx
new file mode 100644
index 00000000..0a2bcde2
--- /dev/null
+++ b/src/components/Button.jsx
@@ -0,0 +1,29 @@
+// src/components/Button.jsx
+import styled from "styled-components";
+
+// Styled button component for links
+const Button = styled.a`
+ display: flex;
+ width: 303px;
+ height: 48px;
+ gap: 16px;
+ padding: 0 16px;
+ align-items: center;
+ border-radius: 12px;
+ text-decoration: none;
+ font-weight: 500;
+ color: #fff;
+ background: #000;
+ font-size: 18px;
+ cursor: pointer;
+ transition: background 0.2s, transform 0.1s;
+
+ &:hover {
+ background: #fff;
+ color: #000;
+ outline: 2px solid #000;
+ }
+
+`;
+
+export default Button;
diff --git a/src/components/Card.jsx b/src/components/Card.jsx
new file mode 100644
index 00000000..562d7fc8
--- /dev/null
+++ b/src/components/Card.jsx
@@ -0,0 +1,21 @@
+import styled from "styled-components";
+
+// (HTML, CSS, API…)
+export const TagsRow = styled.div`
+display: flex;
+align-items: flex-start;
+gap: 4px;
+align-self: stretch;
+`;
+
+// single tag
+export const Tag = styled.span`
+display: flex;
+width: 142px;
+padding: 2px 6px;
+justify-content: center;
+align-items: flex-start;
+border-radius: 4px;
+border: 1px solid #000;
+background: #FFF;
+`;
\ No newline at end of file
diff --git a/src/components/GlobalStyles.jsx b/src/components/GlobalStyles.jsx
new file mode 100644
index 00000000..f1c30d26
--- /dev/null
+++ b/src/components/GlobalStyles.jsx
@@ -0,0 +1,89 @@
+// src/styles/GlobalStyle.jsx
+import { createGlobalStyle } from "styled-components";
+
+export const GlobalStyle = createGlobalStyle`
+ *, *::before, *::after {
+ box-sizing: border-box;
+ }
+
+ html, body {
+ margin: 0;
+ padding: 0;
+ }
+
+ html {
+ scroll-behavior: smooth;
+ }
+
+ body {
+ min-height: 100vh;
+ font-family: "Poppins", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ background-color: #ffffff;
+ color: #000000;
+ line-height: 1.5;
+ -webkit-font-smoothing: antialiased;
+ overflow-x: hidden;
+
+ }
+
+ #root {
+ min-height: 100vh;
+ }
+
+ img {
+ max-width: 100%;
+ height: auto;
+ display: block;
+ }
+
+ a {
+ color: inherit;
+ text-decoration: none;
+ }
+
+ a:hover {
+ text-decoration: underline;
+ }
+
+ a:focus-visible,
+ button:focus-visible {
+ outline: 3px solid #e6229bff;
+ outline-offset: 4px;
+ }
+
+ ul, ol {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ }
+
+ button {
+ font-family: inherit;
+ border: none;
+ background: none;
+ cursor: pointer;
+ }
+
+ h1, h2, h3, h4, h5, h6 {
+ margin: 0;
+ font-weight: 700;
+ letter-spacing: -0.03em;
+ }
+
+ h1 {
+ font-size: clamp(3rem, 6vw, 4.5rem);
+ }
+
+ h2 {
+ font-size: clamp(2rem, 4vw, 3rem);
+ }
+
+ h3 {
+ font-size: clamp(1.3rem, 2.4vw, 1.8rem);
+ }
+
+ p {
+ margin: 0;
+ }
+
+`;
diff --git a/src/components/Icons.jsx b/src/components/Icons.jsx
new file mode 100644
index 00000000..57a4ec03
--- /dev/null
+++ b/src/components/Icons.jsx
@@ -0,0 +1,127 @@
+
+ //Icon for Live demo
+export const LiveIcon = (props) => (
+
+);
+
+// Icon for View code
+export const CodeIcon = (props) => (
+
+);
+
+export const SeeMoreIcon = (props) => (
+
+
+);
+// Icons for Social Media
+export const InstagramIcon = (props) => (
+
+);
+
+export const StackOverflowIcon = (props) => (
+
+);
+
+export const LinkedInIcon = (props) => (
+
+);
+
+export const GitHubIcon = (props) => (
+
+);
diff --git a/src/components/SeeMoreButton.jsx b/src/components/SeeMoreButton.jsx
new file mode 100644
index 00000000..266024ca
--- /dev/null
+++ b/src/components/SeeMoreButton.jsx
@@ -0,0 +1,38 @@
+// src/components/SeeMoreButton.jsx
+import styled from "styled-components";
+import { SeeMoreIcon } from "./Icons";
+
+// Wrapper
+const SeeMoreButtonBase = styled.button`
+ display: flex;
+ width: 303px;
+ height: 48px;
+ gap: 16px;
+ padding: 0 16px;
+ align-items: center;
+ border-radius: 12px;
+ text-decoration: none;
+ font-size: 18px;
+ font-weight: 500;
+ color: #000;
+ background: #fff;
+ cursor: pointer;
+ outline: 2px solid #000;
+ transition: background 0.2s, transform 0.1s;
+
+ &:hover {
+ background: #000;
+ color: #fff;
+ }
+`;
+
+
+export const SeeMoreButton = ({ label, ...props }) => {
+ return (
+
+
+ {label} {/* Screen reader only text */}
+
+ );
+
+};
diff --git a/src/data/media.js b/src/data/media.js
new file mode 100644
index 00000000..3e206742
--- /dev/null
+++ b/src/data/media.js
@@ -0,0 +1,6 @@
+// src/styles/media.js
+export const media = {
+ mobile: "(max-width: 767px)",
+ tablet: "(max-width: 1024px)",
+ desktop: "(min-width: 1025px)",
+};
diff --git a/src/data/posts.json b/src/data/posts.json
new file mode 100644
index 00000000..4dd47afb
--- /dev/null
+++ b/src/data/posts.json
@@ -0,0 +1,46 @@
+[
+ {
+ "id": "post1",
+ "title": "Pitch myself as a developer",
+ "excerpt": "Thoughts on how to to present myself in the tech industry.",
+ "link": "https://www.linkedin.com/posts/jennifer-jansson_i-veckan-har-det-handlat-om-n%C3%A5got-som-l%C3%A4tt-activity-7395085710417973248-1x9O?utm_source=share&utm_medium=member_desktop&rcm=ACoAAB3H4eMBXAYupsRoNRJuXMD_IcGjMjaH20k",
+ "badge": "November 14th",
+ "image": {
+ "src": "/pitch.png",
+ "alt": "Pitch block graphic"
+ }
+ },
+ {
+ "id": "post2",
+ "title": "Styled components",
+ "excerpt": "Learning a new way of styling and the advantages of styled-components compared to regular CSS.",
+ "link": "https://dev.to/pancompany/styled-components-why-you-should-or-should-not-use-it-23c",
+ "badge": "November 24th",
+ "image": {
+ "src": "/post2.png",
+ "alt": "Picture of a mixed color background and styled components word"
+ }
+ },
+ {
+ "id": "post3",
+ "title": "React - getting comfortable",
+ "excerpt": "React went from a human response to a practical tool.",
+ "link": "https://www.simplilearn.com/tutorials/reactjs-tutorial/what-is-reactjs",
+ "badge": "November 18th",
+ "image": {
+ "src": "/react-icon.png",
+ "alt": "Picture of the react icon"
+ }
+ },
+ {
+ "id": "post4",
+ "title": "Accessibility Guidelines",
+ "excerpt": "Implemented ARIA roles and learned how small changes can make a big impact for users.",
+ "link": "https://www.w3.org/WAI/standards-guidelines/wcag/",
+ "badge": "November 6th",
+ "image": {
+ "src": "/web-accessibility.png",
+ "alt": "web accessibility graphic"
+ }
+ }
+]
\ No newline at end of file
diff --git a/src/data/projects.json b/src/data/projects.json
index 7c426028..1fb00fd3 100644
--- a/src/data/projects.json
+++ b/src/data/projects.json
@@ -1,28 +1,87 @@
-{
- "projects": [
- {
- "name": "Business site",
- "image": "https://images.unsplash.com/photo-1557008075-7f2c5efa4cfd?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2497&q=80",
- "tags": [
- "HTML5",
- "CSS3",
- "JavaScript"
- ],
- "netlify": "link",
- "github": "link"
- },
- {
- "name": "Weather app",
- "image": "https://images.unsplash.com/photo-1520792532857-293bd046307a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2370&q=80",
- "tags": [
- "HTML5",
- "CSS3",
- "JavaScript",
- "TypeScript",
- "APIs"
- ],
- "netlify": "link",
- "github": "link"
+[
+ {
+ "id": "business-site",
+ "title": "Business site",
+ "summary": "A fictional responsive website built with HTML, CSS & JavaScript. Includes hero video, card-based layout, signup form, and interactive hamburger menu. No backend connection — focus on responsive UI and clean code.",
+ "tags": [
+ "HTML",
+ "CSS",
+ "JavaScript"
+ ],
+ "live": "https://frontendproject2025.netlify.app/",
+ "code": "https://github.com/JeffieJansson/frontend-project",
+ "image": {
+ "src": "/business.png",
+ "alt": "picture of record studio website"
}
- ]
-}
\ No newline at end of file
+ },
+ {
+ "id": "recipe",
+ "title": "Recipe library",
+ "summary": "An interactive recipe web app with possibility to search, filter and sort recipes by cuisine, diet, time and popularity using Spoonacular API. Data normalized and cached in localStorage for better performance and offline access",
+ "tags": [
+ "HTML",
+ "CSS",
+ "JavaScript",
+ "Spoonacular API"
+ ],
+ "live": "https://library-recipe.netlify.app/",
+ "code": "https://github.com/JeffieJansson/recipe-library",
+ "image": {
+ "src": "/recipe.jpg",
+ "alt": "pictures of food recipes and filtering options"
+ }
+ },
+ {
+ "id": "weather",
+ "title": "Weather app – SMHI API",
+ "summary": "A responsive weather app displaying current weather conditions and 5-day forecasts for multiple Swedish cities using the SMHI API. Built collaboratively using Git and TypeScript for safer data handling and cleaner code.",
+ "tags": [
+ "HTML",
+ "CSS",
+ "TypeScript",
+ "SMHI API"
+ ],
+ "live": "https://project-weather-app-b2.netlify.app/",
+ "code": "https://github.com/marinalendt-png/js-project-weather-app",
+ "image": {
+ "src": "/weather.png",
+ "alt": "picture of weather app"
+ }
+ },
+ {
+ "id": "accessibility",
+ "title": "Build a Quiz with Web Accessibility in mind",
+ "summary": "A responsive Quiz build with web accessibility in mind. The quiz tests users' knowledge of the team that built the website through multiple-choice questions. It provides instant feedback on answers and a final score at the end. As a second page reveals information about the team and answers to the quiz questions.",
+ "tags": [
+ "HTML",
+ "CSS",
+ "JavaScript"
+ ],
+ "live": "https://accessibilitymixedgroup.netlify.app/",
+ "code": "https://github.com/irisdgz/js-project-accessibility",
+ "image": {
+ "src": "/accessibility.png",
+ "alt": "Picture of web accessibility quiz results"
+ }
+ },
+ {
+ "id": "happy thoughts",
+ "title": "Happy Thoughts - Real-Time Validation & API Integration",
+ "summary": " is a Twitter-inspired React application where users share positive messages (5-140 characters) and like others' thoughts. The app features real-time character validation with visual feedback, spam-prevention on likes, and loading states for seamless UX. Built with React hooks (useState, useEffect), it demonstrates clean component architecture with separation of concerns: a centralized API layer, controlled form components with comprehensive validation, and optimistic UI updates. The fully responsive design (320px-1600px+) includes accessibility features (ARIA labels, live regions) and follows DRY principles with well-documented, error-free code.",
+ "tags": [
+ "React",
+ "Javascript",
+ "CSS",
+ "API",
+ "Vite",
+ "react-timeago"
+ ],
+ "live": "https://jennifer-happy-thoughts.netlify.app/",
+ "code": "https://github.com/JeffieJansson/happy-thoughts",
+ "image": {
+ "src": "/happy.png",
+ "alt": "Picture of happy thoughts app"
+ }
+ }
+]
\ No newline at end of file
diff --git a/src/data/skills.json b/src/data/skills.json
new file mode 100644
index 00000000..802df95d
--- /dev/null
+++ b/src/data/skills.json
@@ -0,0 +1,40 @@
+{
+ "code": [
+ "HTML",
+ "CSS",
+ "JavaScript",
+ "GitHub",
+ "API Integration",
+ "DOM Manipulation",
+ "localStorage",
+ "Responsive Design",
+ "Accessibility"
+ ],
+ "toolbox": [
+ "VS Code",
+ "R-Studio",
+ "Postman",
+ "GitHub",
+ "Figma",
+ "BigQuery",
+ "Looker Studio",
+ "GTM",
+ "GTA",
+ "GSC",
+ "Figma"
+ ],
+ "upcoming": [
+ "Node.js",
+ "MongoDB",
+ "TypeScript",
+ "Styled Components"
+ ],
+ "more": [
+ "SEO Optimization",
+ "Data Visualization",
+ "Digital Marketing",
+ "Project Planning",
+ "UX Writing",
+ "Agile Methodology"
+ ]
+}
diff --git a/src/data/tech.json b/src/data/tech.json
new file mode 100644
index 00000000..4b9de408
--- /dev/null
+++ b/src/data/tech.json
@@ -0,0 +1,19 @@
+{
+ "tech": [
+ "HTML",
+ "CSS",
+ "TypeScript",
+ "Web Accessibility",
+ "Flexbox",
+ "Javascript",
+ "ES6",
+ "JSX",
+ "React",
+ "React Hooks",
+ "Node.js",
+ "MongoDB",
+ "Mob-programming",
+ "Pair-programming",
+ "GitHub"
+ ]
+}
\ No newline at end of file
diff --git a/src/index.css b/src/index.css
deleted file mode 100644
index 61010be6..00000000
--- a/src/index.css
+++ /dev/null
@@ -1,4 +0,0 @@
-body {
- background: pink;
- color: hotpink;
-}
\ No newline at end of file
diff --git a/src/main.jsx b/src/main.jsx
index ed109d76..15f32903 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -1,9 +1,7 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
-
import { App } from './App.jsx'
-import './index.css'
createRoot(document.getElementById('root')).render(
diff --git a/src/sections/Contact.jsx b/src/sections/Contact.jsx
new file mode 100644
index 00000000..07207967
--- /dev/null
+++ b/src/sections/Contact.jsx
@@ -0,0 +1,165 @@
+// src/sections/Contact.jsx
+import styled from "styled-components";
+import { media } from "../data/media.js";
+import {
+ GitHubIcon,
+ InstagramIcon,
+ LinkedInIcon,
+ StackOverflowIcon,
+} from "../components/Icons.jsx";
+import footerImage from "/footer.png";
+
+// ---- STYLES ----
+const ContactSection = styled.section`
+ background: #000;
+ display: flex;
+ padding: 128px 0;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
+ align-self: stretch;
+ width: 100%;
+
+ @media ${media.tablet} {
+ padding: 96px 16px;
+ }
+
+ @media ${media.mobile} {
+ padding: 64px 16px;
+ }
+`;
+
+const ContactTitle = styled.h2`
+ color: #fff;
+ text-align: center;
+ font-size: 80px;
+ font-weight: 700;
+ line-height: normal;
+
+ @media ${media.tablet} {
+ font-size: 56px;
+ }
+
+ @media ${media.mobile} {
+ font-size: 40px;
+ }
+`;
+
+const Avatar = styled.img.attrs({ loading: "lazy" })`
+ width: 164px;
+ height: 164px;
+ border-radius: 50%;
+ object-fit: cover;
+ display: block;
+ margin: 0 auto;
+
+ @media ${media.mobile} {
+ width: 128px;
+ height: 128px;
+ }
+`;
+
+const Info = styled.div`
+ text-align: center;
+ margin-top: 16px;
+
+ p {
+ color: #fff;
+ font-size: 30px;
+ margin: 8px 0;
+ }
+
+ a {
+ text-decoration: none;
+ color: #fff;
+ }
+
+ @media ${media.tablet} {
+ p {
+ font-size: 24px;
+ }
+ }
+
+ @media ${media.mobile} {
+ p {
+ font-size: 20px;
+ }
+ }
+`;
+
+const SocialRow = styled.div`
+ display: flex;
+ align-items: flex-end;
+ gap: 32px;
+ margin-top: 24px;
+
+ a {
+ color: inherit;
+ text-decoration: none;
+ }
+
+
+ @media ${media.mobile} {
+ gap: 16px;
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+`;
+
+// ---- COMPONENT ----
+export const Contact = () => (
+
+ Let’s Talk
+
+
+
+
+ I love bridging the gap between design, data, and technology. I have
+ enjoyed understanding user behavior and uncovering insights, but now I
+ have realized that I don't just want to analyze digital experiences — I
+ want to build them. Today, that passion drives me to create intuitive,
+ accessible and meaningful digital products that deliver real value for
+ real users.
+