diff --git a/README.md b/README.md
index 200f4282..1c9fd18b 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,7 @@
# Portfolio
+
+https://holacarmensita.com/
+
+Missing:
+1. A presentation of some thoughts that you have around code. (If you didn't write an article last week - use placeholder text for now). Had a hard time adding a section in my navbar, without messing th elogic up, working on it!
+2. Animation inView() seems to not work on mobile, will change it as soon as I can.
diff --git a/anteckningar.txt b/anteckningar.txt
new file mode 100644
index 00000000..bea21f9a
--- /dev/null
+++ b/anteckningar.txt
@@ -0,0 +1,45 @@
+Reminders:
+Det är en global CSS-regel, och GlobalStyle är platsen för:
+
+1. Globala variabler (:root)
+
+2. Resets och grunder
+
+3. Responsiva helpers
+
+4. Tillståndsklasser som .dyslexic, .dark, .reduced-motion, etc.
+
+
+
+✨ Portfolio - Nice to have features (To Do)
+
+1. 🔁 Text Truncation
+ - Kortare projektbeskrivningar med "..." vid behov
+ - CSS eller Tailwind: `truncate` eller `line-clamp`
+
+2. 🎨 Animationer
+ - Fade in-effekter när komponenter scrollas in
+ - Hover-effekter på knappar och projektkort
+
+3. 🧭 Smooth Scroll
+ - Mjuk scrollning vid klick på navigationslänkar
+ - CSS: `scroll-behavior: smooth` i `html` eller `body`
+
+4. 💡 Dark Mode Toggle
+ - Lägga till en knapp för att växla mellan mörkt och ljust tema
+ - Styla med Tailwind's `dark:`-klasser
+
+5. 📱 Responsiv layout
+ - Säkerställa att portfolion fungerar snyggt på mobil, surfplatta och desktop
+
+6. 🧠 Tillgänglighet
+ - Fokusringar på interaktiva element
+ - Alt-texter, aria-labels och korrekt semantik
+
+7. 🧼 Data-driven content
+ - Byta hårdkodade delar mot innehåll från profile.json och projects.json
+ - Strukturera props rent och skalbart
+
+8. 🌍 Footer
+ - Lägg till en footer med copyright, länk till CV eller kontakt
+
diff --git a/index.html b/index.html
index 6676fb2d..bceca9e4 100644
--- a/index.html
+++ b/index.html
@@ -1,10 +1,39 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
- Portfolio
+
+
+ ¡Hola Carmensita!
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 00000000..a218b143
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,8 @@
+[build]
+ command = "npm run build"
+ publish = "dist"
+
+[[redirects]]
+ from = "/*"
+ to = "/index.html"
+ status = 200
\ No newline at end of file
diff --git a/package.json b/package.json
index 48911600..2d751e41 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,8 @@
},
"dependencies": {
"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",
@@ -23,5 +24,12 @@
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"vite": "^6.2.0"
+ },
+ "editor.quickSuggestions": {
+ "strings": true
+ },
+ "emmet.includeLanguages": {
+ "javascript": "javascriptreact",
+ "javascriptreact": "html"
}
}
diff --git a/public/3dspace.gif b/public/3dspace.gif
new file mode 100644
index 00000000..11557388
Binary files /dev/null and b/public/3dspace.gif differ
diff --git a/public/BEFOREAFTER2.png b/public/BEFOREAFTER2.png
new file mode 100644
index 00000000..395cf120
Binary files /dev/null and b/public/BEFOREAFTER2.png differ
diff --git a/public/BeforeAfterLists.png b/public/BeforeAfterLists.png
new file mode 100644
index 00000000..2341be40
Binary files /dev/null and b/public/BeforeAfterLists.png differ
diff --git a/public/CoParentingApp.png b/public/CoParentingApp.png
new file mode 100644
index 00000000..5ede817c
Binary files /dev/null and b/public/CoParentingApp.png differ
diff --git a/public/GithubButton.svg b/public/GithubButton.svg
new file mode 100644
index 00000000..4568d180
--- /dev/null
+++ b/public/GithubButton.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/public/HappyThoughtsApp.png b/public/HappyThoughtsApp.png
new file mode 100644
index 00000000..361c1ba6
Binary files /dev/null and b/public/HappyThoughtsApp.png differ
diff --git a/public/HomePageDesignFAQ.png b/public/HomePageDesignFAQ.png
new file mode 100644
index 00000000..5bec1a65
Binary files /dev/null and b/public/HomePageDesignFAQ.png differ
diff --git a/public/HomePageFAQ.png b/public/HomePageFAQ.png
new file mode 100644
index 00000000..00effe87
Binary files /dev/null and b/public/HomePageFAQ.png differ
diff --git a/public/HomedesignArticals.png b/public/HomedesignArticals.png
new file mode 100644
index 00000000..59f03459
Binary files /dev/null and b/public/HomedesignArticals.png differ
diff --git a/public/Ideas.png b/public/Ideas.png
new file mode 100644
index 00000000..4fc3acc9
Binary files /dev/null and b/public/Ideas.png differ
diff --git a/public/ProfilePage.png b/public/ProfilePage.png
new file mode 100644
index 00000000..1e1e2bc4
Binary files /dev/null and b/public/ProfilePage.png differ
diff --git a/public/beforeAfter.png b/public/beforeAfter.png
new file mode 100644
index 00000000..0d4909f6
Binary files /dev/null and b/public/beforeAfter.png differ
diff --git a/public/browsingIdea.gif b/public/browsingIdea.gif
new file mode 100644
index 00000000..57c5b6aa
Binary files /dev/null and b/public/browsingIdea.gif differ
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 00000000..60f2beb1
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1,2988 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/flatProfile.png b/public/flatProfile.png
new file mode 100644
index 00000000..48001f4a
Binary files /dev/null and b/public/flatProfile.png differ
diff --git a/public/fonts/Aileron-Bold.woff b/public/fonts/Aileron-Bold.woff
new file mode 100644
index 00000000..3b3897b6
Binary files /dev/null and b/public/fonts/Aileron-Bold.woff differ
diff --git a/public/fonts/Aileron-Regular.woff b/public/fonts/Aileron-Regular.woff
new file mode 100644
index 00000000..a7e09752
Binary files /dev/null and b/public/fonts/Aileron-Regular.woff differ
diff --git a/public/fonts/Aileron-SemiBold.woff b/public/fonts/Aileron-SemiBold.woff
new file mode 100644
index 00000000..043c23b8
Binary files /dev/null and b/public/fonts/Aileron-SemiBold.woff differ
diff --git a/public/fonts/TAN-Rosebud.ttf b/public/fonts/TAN-Rosebud.ttf
new file mode 100644
index 00000000..d2ca5c06
Binary files /dev/null and b/public/fonts/TAN-Rosebud.ttf differ
diff --git a/public/github.svg b/public/github.svg
new file mode 100644
index 00000000..8090c532
--- /dev/null
+++ b/public/github.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/public/happyThoughts.png b/public/happyThoughts.png
new file mode 100644
index 00000000..052ca504
Binary files /dev/null and b/public/happyThoughts.png differ
diff --git a/public/imgHover.svg b/public/imgHover.svg
new file mode 100644
index 00000000..930c2b5a
--- /dev/null
+++ b/public/imgHover.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/public/instagram.svg b/public/instagram.svg
new file mode 100644
index 00000000..b4a248e3
--- /dev/null
+++ b/public/instagram.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/public/linkedIn.svg b/public/linkedIn.svg
new file mode 100644
index 00000000..40f928b6
--- /dev/null
+++ b/public/linkedIn.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/public/logo.svg b/public/logo.svg
new file mode 100644
index 00000000..f3924ae6
--- /dev/null
+++ b/public/logo.svg
@@ -0,0 +1,4153 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/profile.jpg b/public/profile.jpg
new file mode 100644
index 00000000..e8e41867
Binary files /dev/null and b/public/profile.jpg differ
diff --git a/public/recipeLibrary.png b/public/recipeLibrary.png
new file mode 100644
index 00000000..beae8415
Binary files /dev/null and b/public/recipeLibrary.png differ
diff --git a/public/resume.pdf b/public/resume.pdf
new file mode 100644
index 00000000..0e3bf218
Binary files /dev/null and b/public/resume.pdf differ
diff --git a/public/shapes/circle.svg b/public/shapes/circle.svg
new file mode 100644
index 00000000..4ad395d9
--- /dev/null
+++ b/public/shapes/circle.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/public/shapes/waveOrange.svg b/public/shapes/waveOrange.svg
new file mode 100644
index 00000000..af434d0c
--- /dev/null
+++ b/public/shapes/waveOrange.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/public/shapes/waveSkills.svg b/public/shapes/waveSkills.svg
new file mode 100644
index 00000000..8212fed3
--- /dev/null
+++ b/public/shapes/waveSkills.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/public/stackoverflow.svg b/public/stackoverflow.svg
new file mode 100644
index 00000000..d91e1e3a
--- /dev/null
+++ b/public/stackoverflow.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/public/thumbnail.png b/public/thumbnail.png
new file mode 100644
index 00000000..ee09e100
Binary files /dev/null and b/public/thumbnail.png differ
diff --git a/public/thumbnailLarge.png b/public/thumbnailLarge.png
new file mode 100644
index 00000000..df324a9c
Binary files /dev/null and b/public/thumbnailLarge.png differ
diff --git a/public/vite.svg b/public/vite.svg
deleted file mode 100644
index e7b8dfb1..00000000
--- a/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/weatherApp.png b/public/weatherApp.png
new file mode 100644
index 00000000..587aaf66
Binary files /dev/null and b/public/weatherApp.png differ
diff --git a/public/web.svg b/public/web.svg
new file mode 100644
index 00000000..db551cc4
--- /dev/null
+++ b/public/web.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/App.jsx b/src/App.jsx
index a161d8d3..1dd5e20d 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,8 +1,29 @@
+import Navigation from './components/Navigation';
+import IntroSection from './components/IntroSection';
+import AboutMeNew from './components/AboutMeNew';
+import Skills from './components/Skills';
+import Projects from './components/Projects';
+import GetInTouch from './components/GetInTouch';
+import Footer from './components/Footer';
+
+import { useEffect } from 'react';
+
export const App = () => {
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ }, []);
+
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.
+
+
+
+
+
+
+
+
+
>
- )
-}
+ );
+};
diff --git a/src/components/AboutMeNew.jsx b/src/components/AboutMeNew.jsx
new file mode 100644
index 00000000..9eb23fd6
--- /dev/null
+++ b/src/components/AboutMeNew.jsx
@@ -0,0 +1,87 @@
+import React, { useRef, useEffect, useState } from 'react';
+import ProfileCardNew from './ProfileCardNew';
+import Button from './Button';
+import styled from 'styled-components';
+import CircleLeft from './CircleLeft';
+import SocialMedia from './SocialMedia';
+
+const AboutSection = styled.section`
+ position: relative;
+ min-height: 100vh;
+`;
+
+const SocialWrapper = styled.div`
+ position: fixed;
+ inset: 0;
+ z-index: 1000;
+ opacity: ${({ $visible }) => ($visible ? 1 : 0)};
+ transition: opacity 0.5s ease-in-out;
+ /* Wrappern släpper igenom alla events */
+ pointer-events: none;
+
+ /* Endast barn-komponenten (SocialMedia) kan få events när synlig */
+ & > * {
+ pointer-events: ${({ $visible }) => ($visible ? 'auto' : 'none')};
+ }
+`;
+
+const CornerButtonWrapper = styled.div`
+ display: none;
+ @media (min-width: 1024px) {
+ position: fixed;
+ top: 2rem;
+ right: 3rem;
+ z-index: 1000;
+ display: block;
+ opacity: ${({ $visible }) => ($visible ? 1 : 0)};
+ pointer-events: ${({ $visible }) => ($visible ? 'auto' : 'none')};
+ transition: opacity 0.5s ease-in-out;
+ }
+`;
+
+function AboutMeNew() {
+ const sectionRef = useRef(null);
+ const [isVisible, setIsVisible] = useState(false);
+
+ useEffect(() => {
+ const threshold = 0.3; // 30 % av sektionen
+
+ function onScroll() {
+ if (!sectionRef.current) return;
+ const rect = sectionRef.current.getBoundingClientRect();
+
+ // Beräkna synlig höjd av sektionen:
+ const visibleHeight =
+ Math.min(rect.bottom, window.innerHeight) - Math.max(rect.top, 0);
+ const ratio = Math.max(0, Math.min(visibleHeight / rect.height, 1));
+
+ // Sätt isVisible = true när ≥ threshold är synligt
+ setIsVisible(ratio >= threshold);
+ }
+
+ window.addEventListener('scroll', onScroll);
+ onScroll(); // kör direkt för initial state
+ return () => window.removeEventListener('scroll', onScroll);
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default AboutMeNew;
diff --git a/src/components/Accessibility.jsx b/src/components/Accessibility.jsx
new file mode 100644
index 00000000..294071aa
--- /dev/null
+++ b/src/components/Accessibility.jsx
@@ -0,0 +1,12 @@
+import styled from 'styled-components';
+import ToggleFontButton from './ToggleFontButton';
+
+function Accessibility() {
+ return (
+ <>
+
+ >
+ );
+}
+
+export default Accessibility;
diff --git a/src/components/Button.jsx b/src/components/Button.jsx
new file mode 100644
index 00000000..a01d22e6
--- /dev/null
+++ b/src/components/Button.jsx
@@ -0,0 +1,133 @@
+//Done together with ChatGPT and the code from "https://css-shape.com/wavy-line/" and @riklomas codepen https://codepen.io/riklomas/pen/erKJoN
+
+import React, { useRef, useLayoutEffect, useEffect, useState } from 'react';
+import styled from 'styled-components';
+
+const STROKE_WIDTH = 3;
+const AMPLITUDE = 3;
+const BASELINE = AMPLITUDE + STROKE_WIDTH / 2;
+const WAVELENGTH = 30;
+const ANG_FREQ = (2 * Math.PI) / WAVELENGTH;
+
+const LinkWrapper = styled.a`
+ position: relative;
+ display: inline-block;
+ text-decoration: none;
+ padding-bottom: ${STROKE_WIDTH * 2}px;
+
+ /* Override global hover style - keep font-weight normal */
+ &:hover {
+ font-weight: inherit;
+ }
+`;
+const ButtonWrapper = styled.button`
+ position: relative;
+ display: inline-block;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ font: inherit;
+
+ /* Ensure no font-weight change on hover */
+ &:hover {
+ font-weight: inherit;
+ }
+`;
+const WaveSvg = styled.svg`
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ pointer-events: none;
+ width: 100%;
+`;
+
+export default function Button({
+ text,
+ onClick,
+ href,
+ type = 'button',
+ target,
+ rel,
+ ariaLabel,
+}) {
+ const wrapperRef = useRef();
+ const pathRef = useRef();
+ const [width, setWidth] = useState(0);
+ const t = useRef(0);
+ const rafId = useRef();
+ const animating = useRef(false);
+
+ // draw one frame
+ const drawWave = () => {
+ if (!width || !pathRef.current) return;
+ const pts = Array.from({ length: Math.ceil(width) }, (_, i) => {
+ const y = BASELINE + AMPLITUDE * Math.sin(ANG_FREQ * i + t.current);
+ return `${i},${y}`;
+ }).join(' L');
+ pathRef.current.setAttribute('d', `M${pts}`);
+ };
+
+ // animation loop
+ const animate = () => {
+ if (!animating.current) return;
+ drawWave();
+ t.current += 0.1;
+ rafId.current = requestAnimationFrame(animate);
+ };
+
+ const enter = () => {
+ if (!animating.current) {
+ animating.current = true;
+ animate();
+ }
+ };
+ const leave = () => {
+ animating.current = false;
+ };
+
+ // measure size after layout, and whenever it changes
+ useLayoutEffect(() => {
+ if (!wrapperRef.current) return;
+ const measure = (entry) => {
+ setWidth(entry.contentRect.width);
+ };
+ const ro = new ResizeObserver((entries) => entries.forEach(measure));
+ ro.observe(wrapperRef.current);
+ return () => {
+ ro.disconnect();
+ cancelAnimationFrame(rafId.current);
+ };
+ }, []);
+
+ // redraw instantly if width updates
+ useEffect(drawWave, [width]);
+
+ const viewBoxH = AMPLITUDE * 2 + STROKE_WIDTH;
+
+ const Wrapper = href ? LinkWrapper : ButtonWrapper;
+ const props = href ? { href, target, rel } : { onClick, type };
+
+ return (
+
+ {text}
+
+
+
+
+ );
+}
diff --git a/src/components/CircleLeft.jsx b/src/components/CircleLeft.jsx
new file mode 100644
index 00000000..bc0faf30
--- /dev/null
+++ b/src/components/CircleLeft.jsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import styled, { css } from 'styled-components';
+
+const SvgWrapper = styled.div`
+ width: 30vw;
+ max-width: 250px;
+ position: absolute;
+ top: ${({ $YPosition }) => $YPosition};
+ transform: translate(-50%, -50%);
+
+ /* Default mobil */
+ display: ${({ $visibility }) =>
+ $visibility === 'desktop' ? 'none' : 'inline-block'};
+
+ /* Desktop skriver över */
+ @media (min-width: 768px) {
+ display: ${({ $visibility }) =>
+ $visibility === 'mobile' ? 'none' : 'inline-block'};
+ width: 15vw;
+ max-width: 250px;
+ }
+`;
+
+// color(default: '#34EDB3')
+// YPosition: CSS value for top (default: '50%')
+// visibility: 'all' | 'mobile' | 'desktop' (default: 'all')
+function CircleLeft({
+ color = '#34EDB3',
+ YPosition = '50%',
+ visibility = 'all',
+}) {
+ return (
+
+
+
+
+
+ );
+}
+
+export default CircleLeft;
diff --git a/src/components/CircleRight.jsx b/src/components/CircleRight.jsx
new file mode 100644
index 00000000..e34dce89
--- /dev/null
+++ b/src/components/CircleRight.jsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import styled, { css } from 'styled-components';
+
+const SvgWrapper = styled.div`
+ width: 30vw;
+ max-width: 250px;
+ position: absolute;
+ top: ${({ $YPosition }) => $YPosition};
+ right: 0;
+ transform: translate(50%, -50%);
+
+ /* Default mobil */
+ display: ${({ $visibility }) =>
+ $visibility === 'desktop' ? 'none' : 'inline-block'};
+
+ /* Desktop skriver över */
+ @media (min-width: 768px) {
+ display: ${({ $visibility }) =>
+ $visibility === 'mobile' ? 'none' : 'inline-block'};
+ width: 15vw;
+ max-width: 250px;
+ }
+`;
+
+// color(default: '#34EDB3')
+// YPosition: CSS value for top (default: '50%')
+// visibility: 'all' | 'mobile' | 'desktop' (default: 'all')
+function CircleRight({
+ color = '#34EDB3',
+ YPosition = '50%',
+ visibility = 'all',
+}) {
+ return (
+
+
+
+
+
+ );
+}
+
+export default CircleRight;
diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx
new file mode 100644
index 00000000..5227f4d9
--- /dev/null
+++ b/src/components/Footer.jsx
@@ -0,0 +1,20 @@
+import styled from 'styled-components';
+import LayoutWrapper from './LayoutWrapper';
+
+const PageFooter = styled.footer`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ padding-bottom: 2rem;
+`;
+
+function Footer() {
+ return (
+
+ © 2025 Casandra Gustafsson. Crafted with love.
+
+ );
+}
+
+export default Footer;
diff --git a/src/components/GetInTouch.jsx b/src/components/GetInTouch.jsx
new file mode 100644
index 00000000..e7e811b8
--- /dev/null
+++ b/src/components/GetInTouch.jsx
@@ -0,0 +1,79 @@
+import SocialMedia from './SocialMedia';
+import profile from '../data/profile.json';
+import styled from 'styled-components';
+import LayoutWrapper from './LayoutWrapper';
+
+const Background = styled.div`
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: 100vh;
+`;
+
+const GetInTouchSection = styled.section`
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ gap: 2rem;
+`;
+
+const LinkContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+`;
+
+const EmailLink = styled.a`
+ color: var(--link-color);
+ text-decoration: underline;
+ text-decoration-color: var(--link-color);
+ text-decoration-thickness: 2px;
+ font-size: 1.1rem;
+ font-weight: 500;
+ transition: text-decoration-color 0.3s ease;
+
+ &:hover {
+ text-decoration-color: var(--color-accent);
+ }
+
+ &:focus {
+ outline: 2px solid var(--color-accent);
+ outline-offset: 2px;
+ border-radius: 4px;
+ }
+`;
+
+function GetInTouch() {
+ return (
+
+
+
+ Get in touch! / ¡Hablemos!
+
+
+ {profile.email}
+
+
+ {/*
+ {profile.phone}
+ */}
+
+
+
+
+
+ );
+}
+
+export default GetInTouch;
diff --git a/src/components/HeroAnimation.jsx b/src/components/HeroAnimation.jsx
new file mode 100644
index 00000000..94605d62
--- /dev/null
+++ b/src/components/HeroAnimation.jsx
@@ -0,0 +1,223 @@
+import React from 'react';
+import styled, { keyframes } from 'styled-components';
+
+const fadeIn = keyframes`
+ from { opacity: 0; }
+ to { opacity: 1; }
+`;
+
+const slideRotateInRight = keyframes`
+ from {
+ transform: translateX(100%) rotate(90deg);
+ opacity: 0;
+ }
+ to {
+ transform: translateX(0) rotate(0deg);
+ opacity: 1;
+ }
+`;
+
+const slideRotateInLeft = keyframes`
+ from {
+ transform: translateX(-100%) rotate(-90deg);
+ opacity: 0;
+ }
+ to {
+ transform: translateX(0) rotate(0deg);
+ opacity: 1;
+ }
+`;
+
+const pop = keyframes`
+ 0% {
+ opacity: 0;
+ transform: scale(0.8);
+ }
+ 50% {
+ opacity: 1;
+ transform: scale(1.2);
+ }
+ 100% {
+ opacity: 1;
+ transform: scale(1);
+ }
+`;
+
+const revealLeftToRight = keyframes`
+ from { clip-path: inset(0 0 0 100%); }
+ to { clip-path: inset(0 0 0 0); }
+`;
+const revealRightToLeft = keyframes`
+ from { clip-path: inset(0 100% 0 0); }
+ to { clip-path: inset(0 0 0 0); }
+`;
+const revealTopToBottom = keyframes`
+ from { clip-path: inset(100% 0 0 0); }
+ to { clip-path: inset(0 0 0 0); }
+`;
+const revealBottomToTop = keyframes`
+ from { clip-path: inset(0 0 100% 0); }
+ to { clip-path: inset(0 0 0 0); }
+`;
+
+const StyledSVG = styled.svg`
+ width: 100%; /* tar 100% av behållarens bredd */
+ height: auto; /* behåller aspect ratio automatiskt */
+ max-width: 800px;
+ overflow: visible;
+
+ /* Klassberoende animationer */
+ path.fadeIn {
+ opacity: 0;
+ animation: ${fadeIn} 2s ease-out both;
+ }
+
+ path.revealLeft {
+ clip-path: inset(0 0 0 100%);
+ animation: ${revealLeftToRight} 2s ease-out forwards;
+ }
+ path.revealRight {
+ clip-path: inset(0 100% 0 0);
+ animation: ${revealRightToLeft} 2s ease-out forwards;
+ }
+ path.revealDown {
+ clip-path: inset(100% 0 0 0);
+ animation: ${revealTopToBottom} 2s ease-out forwards;
+ }
+ path.revealUp {
+ clip-path: inset(0 0 100% 0);
+ animation: ${revealBottomToTop} 2s ease-out forwards;
+ }
+
+ path.pop {
+ transform-origin: center center; /* relativt mittpunkten */
+ animation: ${pop} 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both;
+ /* cubic-bezier ger lite extra “studs” i slutet */
+ }
+
+ path.slideRotateInRight {
+ /* Se till att procent-baserad translate tar pathens bbox som referens */
+ transform-box: fill-box;
+ transform-origin: center center;
+ /* Startläge */
+ transform: translateX(100%) rotate(90deg);
+ opacity: 0;
+ /* Animation */
+ animation: ${slideRotateInRight} 2s ease-out forwards;
+ }
+
+ path.slideRotateInLeft {
+ transform-box: fill-box; /* procent-translate mot eget bbox */
+ transform-origin: center center;
+ /* Startläge: utanför till vänster + roterad -90° */
+ transform: translateX(-100%) rotate(-90deg);
+ opacity: 0;
+ /* Animation: 1s, ease-out, behåll slutläge */
+ animation: ${slideRotateInLeft} 2s ease-out forwards;
+ }
+
+ /* Rektangeln med fill="#584793" dyker upp först */
+ rect[fill='#584793'] {
+ opacity: 1;
+ animation: ${revealTopToBottom} 2s ease-out forwards;
+ animation-delay: 0s;
+ }
+
+ /* Stegvis delay för varje path */
+ path.BlueOverRed {
+ animation-delay: 2.5s;
+ }
+ path.YellowCub {
+ animation-delay: 2s;
+ }
+ path.BlueCircleInYellowCub {
+ animation-delay: 2.5s;
+ transform: translateY(15px);
+ }
+ path.Wave {
+ animation-delay: 4s;
+ }
+ path.RedCube {
+ animation-delay: 2s;
+ }
+ path.mustardCube {
+ animation-delay: 1s;
+ }
+ path.blueCircleRedYellow {
+ animation-delay: 2.5s;
+ }
+ path.yellowHalfCircle {
+ animation-delay: 2.5s;
+ }
+ path.blueHalfCircleUnderYellowCircle {
+ animation-delay: 2.5s;
+ }
+ /* path:nth-of-type(10) {
+ //halvcurkel blå under gul
+ animation-delay: 4s;
+ } */
+`;
+
+const HeroAnimation = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
+
+export default HeroAnimation;
diff --git a/src/components/HolaAnimation.jsx b/src/components/HolaAnimation.jsx
new file mode 100644
index 00000000..57579178
--- /dev/null
+++ b/src/components/HolaAnimation.jsx
@@ -0,0 +1,87 @@
+//Made with ChatGPT
+
+import React from 'react';
+import styled, { keyframes } from 'styled-components';
+
+const liftVerySmooth = keyframes`
+ 0% {
+ transform: translateY(100%) scale(0.95);
+ opacity: 0;
+ }
+ 80% {
+ transform: translateY(-2%) scale(1.02);
+ opacity: 1;
+ }
+ 100% {
+ transform: translateY(0) scale(1);
+ opacity: 1;
+ }
+`;
+
+const slideUp = keyframes`
+ from {
+ top: 43%;
+ }
+ to {
+ top: 3rem;
+ }
+`;
+
+const Container = styled.div`
+ position: absolute;
+ left: 50%;
+ top: 43%;
+ transform: translateX(-50%);
+ animation: ${slideUp} 2.5s cubic-bezier(0.645, 0.045, 0.355, 1)
+ ${({ $slideDelay }) => $slideDelay}s forwards;
+ pointer-events: none;
+`;
+
+const WordMask = styled.div`
+ display: inline-block;
+ overflow: hidden;
+ height: 2rem;
+ line-height: 2rem;
+`;
+
+//Didn't find any other way to get the feel of rising up from the ground then putting a item infront???? Tried clip-path in several diffrent ways, nothing gave the same feel. Dont like this solution, very non resposive.
+const Letter = styled.span`
+ display: inline-block;
+ font-size: 2rem;
+ transform-origin: bottom center;
+ opacity: 0;
+ animation: ${liftVerySmooth} 1.5s cubic-bezier(0.4, 0, 0.2, 1) forwards;
+ animation-delay: ${({ $delay }) => $delay}s;
+`;
+
+const HolaAnimation = ({ onFinished }) => {
+ const text = '¡Hola!';
+ const letters = Array.from(text);
+
+ const letterDelay = 0.2;
+ const letterDuration = 1.5;
+ const totalLetters = letters.length;
+ const slideDelay = (totalLetters - 1) * letterDelay + letterDuration;
+
+ return (
+
+
+ {letters.map((char, i) => (
+ {
+ if (i === totalLetters - 1 && onFinished) {
+ onFinished();
+ }
+ }}
+ >
+ {char}
+
+ ))}
+
+
+ );
+};
+
+export default HolaAnimation;
diff --git a/src/components/IntroSection.jsx b/src/components/IntroSection.jsx
new file mode 100644
index 00000000..4e12538a
--- /dev/null
+++ b/src/components/IntroSection.jsx
@@ -0,0 +1,141 @@
+import Button from './Button';
+import styled from 'styled-components';
+import LayoutWrapper from './LayoutWrapper';
+import HeroAnimation from './HeroAnimation';
+import HolaAnimation from './HolaAnimation';
+import Accessibility from './Accessibility';
+import { useState } from 'react';
+
+const CornerButtonWrapper = styled.div`
+ display: none;
+
+ @media (min-width: 768px) {
+ display: inline;
+ position: absolute;
+ top: 2rem;
+ right: 3rem;
+ }
+`;
+
+const Section = styled.section`
+ position: relative;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ background-color: var(--color-bg);
+ color: var(--color-text);
+`;
+
+const HeroAnimationConatiner = styled.div`
+ width: 100%;
+ padding-bottom: 3rem;
+
+ @media only screen and (max-width: 1000px) and (orientation: landscape) {
+ max-width: 60%;
+ padding-top: 2rem;
+ padding-bottom: 0rem;
+ }
+
+ @media (min-width: 1366px) {
+ max-width: 850px;
+ padding-bottom: 1rem;
+ }
+`;
+
+const AccessibilityWrapper = styled.div`
+ display: none;
+
+ @media (min-width: 768px) {
+ display: inline;
+ position: absolute;
+ top: 3rem;
+ left: 3rem;
+ }
+`;
+
+const ArrowButton = styled.button`
+ all: unset;
+ position: absolute;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ color: #584793;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ bottom: 8rem;
+
+ @media (min-width: 768px) {
+ bottom: 6rem;
+ }
+
+ @media (min-width: 1366px) {
+ bottom: 4rem;
+ }
+
+ @media only screen and (max-width: 1000px) and (orientation: landscape) {
+ display: none;
+ }
+
+ svg {
+ width: 3rem;
+ transition: transform 0.3s ease;
+ }
+
+ &:hover,
+ &:focus {
+ color: #7a5bbf;
+ }
+
+ &:hover svg,
+ &:focus svg {
+ transform: translateY(0.25rem);
+ }
+`;
+
+function IntroSection() {
+ const [showHero, setShowHero] = useState(false);
+
+ const scrollToAbout = () => {
+ const el = document.getElementById('about');
+ if (el) {
+ el.scrollIntoView({ behavior: 'smooth' });
+ }
+ };
+
+ return (
+
+ {/*
+
+ */}
+
+
+
+
+
+
+ setShowHero(true)} />
+ {showHero && }
+
+
+ Welcome to my place
+
+
+
+
+
+
+ );
+}
+
+export default IntroSection;
diff --git a/src/components/LayoutWrapper.jsx b/src/components/LayoutWrapper.jsx
new file mode 100644
index 00000000..0dba83c3
--- /dev/null
+++ b/src/components/LayoutWrapper.jsx
@@ -0,0 +1,50 @@
+import styled from 'styled-components';
+
+const LayoutWrapper = styled.div`
+ min-height: 100dvh;
+ /* border: 1px solid black; */
+ width: 100%;
+ display: inline-flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ padding-block: clamp(1rem, 10vw, 3rem);
+ padding-inline: clamp(1rem, 5vw, 3rem);
+ max-width: 1180px;
+
+ /* Extra stora skärmar */
+ @media (min-width: 1440px) {
+ max-width: 1150px;
+ }
+
+ @media (min-width: 1920px) {
+ max-width: 1500px;
+ }
+
+ /* LANDSCAPE – max-width 1024px, bara i landskapsläge */
+ @media screen and (max-width: 1024px) and (orientation: landscape) {
+ max-width: 768px;
+ }
+
+ /* PORTRAIT – max-width 1024px, bara i stående portrait-läge */
+ @media screen and (max-width: 1024px) and (orientation: portrait) {
+ max-width: 700px; /* eller vad du vill för portrait */
+ }
+
+ /* PORTRAIT – max-width 1024px, bara i stående portrait-läge */
+ @media screen and (min-width: 1025px) and (max-width: 1366px) and (orientation: landscape) {
+ max-width: 900px; /* lite bredare här också */
+ }
+
+ /* Tablet/tablet-stor mobil (oavsett orientation, men under 768px bredd) */
+ @media (max-width: 768px) {
+ max-width: 600px;
+ }
+
+ /* Mindre mobiler */
+ @media (max-width: 480px) {
+ max-width: 375px;
+ }
+`;
+
+export default LayoutWrapper;
diff --git a/src/components/MiddleSection.jsx b/src/components/MiddleSection.jsx
new file mode 100644
index 00000000..772bb13a
--- /dev/null
+++ b/src/components/MiddleSection.jsx
@@ -0,0 +1,89 @@
+import styled from 'styled-components';
+import LayoutWrapper from './LayoutWrapper';
+import {
+ tittutAnimation,
+ opacityAppear,
+ appearAnimation,
+ tittutAnimationSmall,
+} from '../styles/styled-utils';
+
+const Wrapper = styled.div`
+ padding-bottom: 5rem;
+ min-height: 80vh;
+
+ @media (min-width: 768px) {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 3rem;
+ }
+
+ p {
+ ${opacityAppear}
+ }
+
+ h3 {
+ ${tittutAnimationSmall}
+ }
+`;
+
+const Shape = styled.svg`
+ ${appearAnimation}
+ height: auto;
+`;
+
+const Title = styled.h1`
+ ${tittutAnimation}
+ text-transform: none;
+
+ &:nth-of-type(1) {
+ }
+ &:nth-of-type(2) {
+ }
+ &:nth-of-type(3) {
+ /* styling for the 2nd Title */
+ }
+`;
+
+const TitleConatiner = styled.div`
+ display: flex;
+ flex-direction: column;
+ max-width: fit-content;
+`;
+
+function MiddleSection({ shapeSrc }) {
+ return (
+
+
+
+ Welcome to my portfolio
+
+ {/* From thoughtful design to inclusive code */}
+
+ Creativo
+ Interactivo
+ Inclusivo
+
+
+
+
+
+
+
+ );
+}
+
+export default MiddleSection;
diff --git a/src/components/Navigation.jsx b/src/components/Navigation.jsx
new file mode 100644
index 00000000..b1d45101
--- /dev/null
+++ b/src/components/Navigation.jsx
@@ -0,0 +1,122 @@
+import styled from 'styled-components';
+import { useEffect, useState } from 'react';
+
+//Done together with ChatGPT
+
+const NavWrapper = styled.nav`
+ position: fixed;
+ left: 3rem;
+ top: 2.5rem;
+ transform: ${({ $sticky }) =>
+ $sticky ? 'translateY(0)' : 'translateY(calc(100vh - 13rem))'};
+ transition: transform 1.5s ease-in-out;
+ z-index: 1000;
+
+ @media (max-width: 767px) {
+ display: none;
+ }
+
+ @media only screen and (max-width: 1000px) and (orientation: landscape) {
+ display: none;
+ }
+`;
+
+const NavList = styled.ul`
+ list-style: none;
+ padding: 0;
+ margin: 0;
+`;
+
+const NavLink = styled.a`
+ text-decoration: none;
+ color: inherit;
+
+ &.active {
+ text-decoration: underline;
+ text-decoration-color: var(--color-accent);
+ text-decoration-thickness: 2px;
+ }
+
+ &:hover {
+ text-decoration: underline;
+ text-decoration-color: var(--color-accent);
+ text-decoration-thickness: 2px;
+ }
+`;
+
+function Navigation() {
+ const [isSticky, setIsSticky] = useState(false);
+ const [activeId, setActiveId] = useState('');
+
+ // 1) On mount: grab any #hash, set active, strip it from URL
+ useEffect(() => {
+ const rawHash = window.location.hash.replace('#', '');
+ if (rawHash) {
+ setActiveId(rawHash);
+ window.history.replaceState(null, '', rawHash);
+ // optional: scroll into view if you need it
+ // document.getElementById(rawHash)?.scrollIntoView({ block: 'start' });
+ }
+ }, []);
+
+ // 2) Show nav when scrolling past 100px
+ useEffect(() => {
+ const handleScroll = () => setIsSticky(window.scrollY > 100);
+ window.addEventListener('scroll', handleScroll);
+ return () => window.removeEventListener('scroll', handleScroll);
+ }, []);
+
+ // 3) Observe sections to update URL & active link
+ useEffect(() => {
+ const ids = ['hola', 'about', 'skills', 'projects', 'contact'];
+ const elements = ids
+ .map((id) => document.getElementById(id))
+ .filter(Boolean);
+
+ const observer = new IntersectionObserver(
+ (entries) => {
+ entries.forEach((entry) => {
+ if (entry.isIntersecting) {
+ const id = entry.target.id;
+ setActiveId(id);
+ window.history.replaceState(null, '', id);
+ }
+ });
+ },
+ { rootMargin: '-50% 0px -50% 0px' }
+ );
+
+ elements.forEach((el) => observer.observe(el));
+ return () => elements.forEach((el) => observer.unobserve(el));
+ }, []);
+
+ // Render
+ return (
+
+
+ {['hola', 'about', 'skills', 'projects', 'contact'].map((id) => (
+
+ {
+ e.preventDefault(); // stop the #hash jump
+ document
+ .getElementById(id)
+ ?.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ window.history.replaceState(null, '', id);
+ setActiveId(id);
+ }}
+ className={activeId === id ? 'active' : ''}
+ >
+ {id === 'hola'
+ ? 'Hola!'
+ : id.charAt(0).toUpperCase() + id.slice(1)}
+
+
+ ))}
+
+
+ );
+}
+
+export default Navigation;
diff --git a/src/components/ProfileCardNew.jsx b/src/components/ProfileCardNew.jsx
new file mode 100644
index 00000000..2a98b68a
--- /dev/null
+++ b/src/components/ProfileCardNew.jsx
@@ -0,0 +1,185 @@
+import profile from '../data/profile.json';
+import styled from 'styled-components';
+import SocialMedia from './SocialMedia';
+import LayoutWrapper from './LayoutWrapper';
+import CircleLeft from './CircleLeft';
+import CircleRight from './CircleRight';
+import {
+ slideUpAnimation,
+ appearAnimation,
+ opacityAppear,
+} from '../styles/styled-utils';
+import Button from './Button';
+import { useState } from 'react';
+
+const FullWidthSection = styled.section`
+ position: relative;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ //Viktig för sociala media komponenten och cirklarna
+`;
+
+const ProfileCardContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 2rem;
+
+ @media (min-width: 768px) {
+ gap: 3rem;
+ }
+`;
+
+const ButtonWrapper = styled.div`
+ max-width: fit-content;
+ /* Hide on mobile when $mobile is false */
+ display: ${({ $mobile }) => ($mobile === false ? 'none' : 'flex')};
+
+ /* Mobile adjustments (when $mobile is true, show it only on mobile) */
+ @media (max-width: 768px) {
+ display: ${({ $mobile }) =>
+ $mobile === true ? 'flex' : 'none'}; /* Show on mobile, hide on desktop */
+ }
+
+ /* Desktop adjustments */
+ @media (min-width: 768px) {
+ display: ${({ $mobile }) =>
+ $mobile === true ? 'none' : 'flex'}; /* Hide on mobile, show on desktop */
+ }
+`;
+
+const ProfileImageContainer = styled.div`
+ ${slideUpAnimation}
+ position: relative;
+ width: 100%;
+ max-width: 280px;
+ aspect-ratio: 1 / 1;
+ border-radius: 50%;
+ overflow: hidden;
+
+ @media (min-width: 1366px) {
+ max-width: 320px;
+ }
+`;
+
+const ProfileImage = styled.div`
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: url(${profile.image}) center/cover no-repeat;
+ border-radius: 50%;
+ transition: opacity 0.6s ease-in-out, transform 0.6s ease;
+ opacity: ${({ $isHovered }) => ($isHovered ? 0 : 1)};
+
+ /* Lägg till en subtle zoom på hover */
+ transform: ${({ $isHovered }) => ($isHovered ? 'scale(1.05)' : 'scale(1)')};
+`;
+
+const ProfileImageHover = styled.div`
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: url(${profile.hoverImg}) center/cover no-repeat;
+ border-radius: 50%;
+ transition: opacity 0.6s ease-in-out, transform 0.6s ease;
+ opacity: ${({ $isHovered }) => ($isHovered ? 1 : 0)};
+
+ /* Lägg till en subtle zoom på hover */
+ transform: ${({ $isHovered }) => ($isHovered ? 'scale(1.05)' : 'scale(1)')};
+`;
+
+const ProfileTitle = styled.div`
+ ${slideUpAnimation}
+ text-align: center;
+
+ h4 {
+ font-size: 1.5rem;
+ font-weight: normal;
+ }
+
+ p {
+ padding-top: 0.5rem;
+ }
+`;
+
+const ProfileText = styled.div`
+ ${slideUpAnimation}
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 2rem;
+ text-transform: none;
+ text-align: center;
+ max-width: 500px;
+
+ h3 {
+ font-size: 1.5rem;
+ font-weight: normal;
+ }
+
+ @media (min-width: 1366px) {
+ max-width: 650px;
+ }
+`;
+
+const Test = styled.div`
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+`;
+
+function ProfileCard() {
+ const [isHovered, setIsHovered] = useState(false);
+
+ return (
+
+
+
+
+ Hola! I'm
+ Casandra
+ @Github HolaCarmensita
+
+ setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ role='img'
+ aria-label='Picture of Casandra'
+ >
+
+
+
+
+ Frontend Developer with a background in digital design
+ {profile.descriptionShort}
+
+ {/* Ready to build increíble things together?
*/}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default ProfileCard;
diff --git a/src/components/ProjectCard.jsx b/src/components/ProjectCard.jsx
new file mode 100644
index 00000000..0f681f9c
--- /dev/null
+++ b/src/components/ProjectCard.jsx
@@ -0,0 +1,219 @@
+import Techniques from './Techniques';
+import Button from './Button';
+import styled from 'styled-components';
+import { slideUpAnimation } from '../styles/styled-utils';
+import { useState } from 'react';
+
+const TextContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ gap: 1rem;
+
+ @media (min-width: 1024px) {
+ grid-column: 2;
+ grid-row: 1;
+ }
+`;
+
+const CardContainer = styled.article`
+ ${(props) => !props.$galleryOpen && slideUpAnimation}
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 2rem;
+ text-align: left;
+
+ & > img {
+ width: 100%;
+ height: auto;
+ object-fit: contain;
+ }
+
+ @media (min-width: 1024px) {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 3rem;
+
+ & > img {
+ grid-column: 1;
+ grid-row: 1;
+ }
+ }
+`;
+
+const TitleAndBody = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: stretch;
+ gap: 0rem;
+ @media (min-width: 1180px) {
+ gap: 0.5rem;
+ }
+`;
+
+const ButtonContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ padding-top: 0.5rem;
+`;
+
+const ButtonWithIcon = styled.div`
+ display: flex; /* inline-flex så det fortfarande beter sig som en knapprad */
+ align-items: center; /* vertikal centrering */
+ gap: 8px; /* avstånd mellan ikon och knapp */
+`;
+
+const GallerySection = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ margin-top: 2rem;
+ width: 100%;
+
+ @media (min-width: 1024px) {
+ grid-column: 1 / -1; /* Span both columns */
+ grid-row: 2;
+ margin-top: 0;
+ }
+`;
+
+const GalleryToggleButton = styled.button`
+ max-width: fit-content;
+ background: none;
+ border: none;
+ color: currentColor;
+ text-decoration: underline;
+ cursor: pointer;
+ padding: 0.5rem 0;
+ font-size: 0.9rem;
+ text-align: left;
+ transition: opacity 0.3s ease;
+
+ &:hover {
+ text-decoration: underline var(--color-accent);
+ }
+`;
+
+const GalleryGrid = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 6rem;
+ align-items: center;
+`;
+
+const GalleryItem = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+`;
+
+const GalleryImage = styled.img`
+ max-width: 100%;
+ height: auto;
+ object-fit: contain;
+ border-radius: 4px;
+`;
+
+const GalleryCaption = styled.p`
+ font-size: 0.85rem;
+ color: var(--color-text);
+ opacity: 0.7;
+ margin: 0;
+ text-align: center;
+`;
+
+const ProjectWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+`;
+
+function ProjectCard({ project }) {
+ const [showGallery, setShowGallery] = useState(false);
+
+ return (
+
+
+
+
+
+
+
+ {project.name}
+ {project.description}
+
+
+
+
+
+
+
+
+ {project.github && (
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ {/* Gallery Section */}
+ {project.gallery && project.gallery.length > 0 && (
+
+ setShowGallery(!showGallery)}>
+ {showGallery ? '− Hide images' : '+ View more images'}
+
+ {showGallery && (
+
+ {project.gallery.map((item, index) => (
+
+
+ {item.caption}
+
+ ))}
+
+ )}
+
+ )}
+
+ );
+}
+
+export default ProjectCard;
diff --git a/src/components/Projects.jsx b/src/components/Projects.jsx
new file mode 100644
index 00000000..cbd3b0f3
--- /dev/null
+++ b/src/components/Projects.jsx
@@ -0,0 +1,50 @@
+import projectsData from '../data/projects.json';
+import ProjectCard from './ProjectCard';
+import styled from 'styled-components';
+import LayoutWrapper from './LayoutWrapper';
+import {
+ slideUpAnimation,
+ appearAnimation,
+ slideUpAnimationFast,
+} from '../styles/styled-utils';
+
+const FullWidthSection = styled.section`
+ position: relative;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+`;
+
+const ProjectSection = styled.section`
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+ text-align: center;
+`;
+
+const ProjectCardContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 8rem;
+`;
+
+function Projects() {
+ return (
+
+
+
+ Featured Projects
+
+ {projectsData.projects.map((project, index) => (
+
+ ))}
+
+
+
+
+ );
+}
+
+export default Projects;
diff --git a/src/components/Skills.jsx b/src/components/Skills.jsx
new file mode 100644
index 00000000..ef328818
--- /dev/null
+++ b/src/components/Skills.jsx
@@ -0,0 +1,60 @@
+import profile from '../data/profile.json';
+import SkillCategory from './SkillsCategory';
+import styled from 'styled-components';
+import LayoutWrapper from './LayoutWrapper';
+import CircleLeft from './CircleLeft';
+import CircleRight from './CircleRight';
+import { slideUpAnimation, hoverScale } from '../styles/styled-utils';
+
+const FullWidthSection = styled.section`
+ position: relative;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+`;
+const SkillCard = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 4rem;
+ @media (min-width: 768px) {
+ gap: 8rem;
+ }
+`;
+
+const SkillsSection = styled.section`
+ width: 100%;
+ h2 {
+ ${slideUpAnimation}
+ text-transform: uppercase;
+ text-align: center;
+ }
+`;
+
+const Divader = styled.div`
+ padding: 2rem;
+ @media (min-width: 768px) {
+ padding: 4rem;
+ }
+`;
+
+function Skills() {
+ return (
+
+
+
+ Skills
+
+ {Object.entries(profile.skills).map(([title, skills]) => (
+
+ ))}
+
+
+
+ {/*
+ */}
+
+ );
+}
+
+export default Skills;
diff --git a/src/components/SkillsCategory.jsx b/src/components/SkillsCategory.jsx
new file mode 100644
index 00000000..80fb0161
--- /dev/null
+++ b/src/components/SkillsCategory.jsx
@@ -0,0 +1,89 @@
+import styled from 'styled-components';
+import { hoverScale, slideUpAnimation } from '../styles/styled-utils';
+
+const SkillCard = styled.article`
+ ${slideUpAnimation}
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+
+ align-items: flex-start; /* default */
+
+ &:nth-child(even) {
+ align-items: flex-end;
+ }
+
+ &:nth-child(even) ul {
+ justify-content: flex-end;
+ }
+
+ svg {
+ width: 50vw;
+ height: auto;
+
+ @media (min-width: 768px) {
+ width: 20rem;
+ }
+ }
+
+ &:nth-of-type(even) {
+ .wave {
+ color: var(--color-magenta);
+ }
+ }
+
+ &:nth-of-type(odd) {
+ .wave {
+ color: var(--color-accent);
+ }
+ }
+
+ h3 {
+ text-transform: uppercase;
+ }
+
+ ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-wrap: wrap;
+ }
+
+ li::after {
+ content: ',';
+ margin-right: 0.25ch;
+ }
+
+ li:last-child::after {
+ content: '';
+ }
+`;
+
+function SkillCategory({ title, skills }) {
+ return (
+
+ {title}
+
+ {skills.map((skill, index) => (
+ {skill}
+ ))}
+
+
+
+
+
+ );
+}
+
+export default SkillCategory;
diff --git a/src/components/SocialMedia.jsx b/src/components/SocialMedia.jsx
new file mode 100644
index 00000000..e34da512
--- /dev/null
+++ b/src/components/SocialMedia.jsx
@@ -0,0 +1,74 @@
+import profile from '../data/profile.json';
+import styled, { css } from 'styled-components';
+import { slideUpAnimationFast } from '../styles/styled-utils';
+
+const StyledSocialWrapper = styled.ul`
+ // (mobile)
+ display: ${({ visibility }) => (visibility === 'desktop' ? 'none' : 'flex')};
+ justify-content: center;
+ align-items: center;
+ list-style: none;
+ padding: 0;
+ gap: 1.5rem;
+
+ //Vertikal, absolut-variant
+ ${({ $variant }) =>
+ $variant === 'verticalAbsolute' &&
+ css`
+ flex-direction: column;
+ position: absolute;
+ top: 50%;
+ right: 5rem;
+ gap: 3rem;
+ padding-bottom: 1rem;
+ transform: translateY(-50%);
+ img {
+ width: 2rem;
+ height: 2rem;
+ }
+ `}
+
+ //Desktop-skriver över
+ @media (min-width: 1024px) {
+ display: ${({ visibility }) => (visibility === 'mobile' ? 'none' : 'flex')};
+ }
+
+ li {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ li a {
+ transition: transform 0.3s ease, filter 0.3s ease;
+ display: inline-block;
+ }
+
+ li a:hover,
+ li a:focus {
+ transform: scale(1.2);
+ filter: brightness(1.2);
+ }
+`;
+
+function SocialMedia({ variant = 'horizontal', visibility = 'all' }) {
+ return (
+
+ {Object.entries(profile.socialMedia).map(([key, media]) => (
+
+
+
+
+
+ ))}
+
+ );
+}
+
+export default SocialMedia;
diff --git a/src/components/Techniques.jsx b/src/components/Techniques.jsx
new file mode 100644
index 00000000..621883b7
--- /dev/null
+++ b/src/components/Techniques.jsx
@@ -0,0 +1,33 @@
+import styled from 'styled-components';
+
+const TechContainer = styled.ul`
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ row-gap: 0.5rem;
+ column-gap: 1rem;
+ font-size: 14px;
+
+ color: grey;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ text-transform: uppercase;
+
+ @media (min-width: 768px) {
+ justify-content: flex-start;
+ font-size: 16px;
+ }
+`;
+
+function Techniques({ list }) {
+ return (
+
+ {list.map((tech, index) => (
+ {tech}
+ ))}
+
+ );
+}
+
+export default Techniques;
diff --git a/src/components/ToggleFontButton.jsx b/src/components/ToggleFontButton.jsx
new file mode 100644
index 00000000..9c86a787
--- /dev/null
+++ b/src/components/ToggleFontButton.jsx
@@ -0,0 +1,65 @@
+import { useEffect, useState } from 'react';
+import styled from 'styled-components';
+
+const SwitchContainer = styled.label`
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ cursor: pointer;
+`;
+
+const Switch = styled.span`
+ position: relative;
+ display: inline-block;
+ width: 50px;
+ height: 28px;
+ background-color: ${({ active }) =>
+ active ? 'var(--color-accent)' : '#ccc'};
+ border-radius: 34px;
+ transition: background-color 0.3s ease;
+`;
+
+const Slider = styled.span`
+ position: absolute;
+ top: 3px;
+ left: 3px;
+ width: 22px;
+ height: 22px;
+ background-color: white;
+ border-radius: 50%;
+ transition: transform 0.3s ease;
+ transform: ${({ active }) => (active ? 'translateX(22px)' : 'translateX(0)')};
+`;
+
+const HiddenCheckbox = styled.input.attrs({ type: 'checkbox' })`
+ display: none;
+`;
+
+function ToggleFontSwitch() {
+ const [isDyslexic, setIsDyslexic] = useState(false);
+
+ useEffect(() => {
+ const html = document.documentElement;
+ if (isDyslexic) {
+ html.classList.add('dyslexic');
+ } else {
+ html.classList.remove('dyslexic');
+ }
+ }, [isDyslexic]);
+
+ return (
+
+ setIsDyslexic((prev) => !prev)}
+ aria-pressed={isDyslexic}
+ />
+
+
+
+ {isDyslexic ? 'Standard font' : 'Read-friendly font'}
+
+ );
+}
+
+export default ToggleFontSwitch;
diff --git a/src/data/profile.json b/src/data/profile.json
new file mode 100644
index 00000000..399bc64b
--- /dev/null
+++ b/src/data/profile.json
@@ -0,0 +1,71 @@
+{
+ "title": "FRONTEND DEVELOPER",
+ "descriptionShort": "I'm always excited to tackle challenges in designing interfaces and bringing them to life through code. I thrive when working with UX- and accessibility-driven development, and I'm eager to dive deeper into backend technologies. Away from the computer, you’ll find me running trails and rocking motherhood.",
+ "descriptionShortSales": "I’m a passionate developer with a background in digital design—always excited to tackle challenges in designing interfaces and bringing them to life through code. I thrive when working with UX-driven code and am always eager to dive deeper into backend development. Away from the computer, you’ll find me running trails and rocking motherhood. Ready to build increíble things together? ¡Hablemos!",
+ "descriptionLong": "My journey in IT began with digital design \u2013 a safe entry into the IT world for someone like me, who struggled with internal doubts and stereotypes. Did I really belong in development?\n\nMy background in digital design not only gave me solid knowledge in accessibility and UX but also shaped me into the developer I am today \u2013 one with a keen eye for visual design. Now, after two years of dedicated studies in frontend development, during which I also had the opportunity to work with backend technologies, there\u2019s no longer any doubt in my mind that there\u2019s a place for me in this industry.\n\nMy journey has strengthened my self-confidence, sparked my ambition to grow into a full-stack role, and made it natural for me to advocate for more women to step confidently into IT.\n\nNow, I look forward to further strengthening my skills, exploring new languages and technologies, and contributing my expertise to an innovative team!",
+ "image": "/profile.jpg",
+ "hoverImg": "/imgHover.svg",
+ "descriptionImg": "image of Casandra",
+ "descriptionHoverImg": "Illustration of peruvian women with hat",
+ "email": "casandra.c.gustafsson@gmail.com",
+ "phone": "+46 701 234 567",
+ "skills": {
+ "code": [
+ "HTML5",
+ "CSS3",
+ "JavaScript (ES6+)",
+ "TypeScript",
+ "React",
+ "Vue.js",
+ "Node.js",
+ "Express.js",
+ "MongoDB",
+ "Styled Components",
+ "Zustand"
+ ],
+ "toolbox": [
+ "Figma",
+ "Adobe Illustrator",
+ "Adobe Photoshop",
+ "Postman",
+ "Git & GitHub",
+ "Vite",
+ "Three.js",
+ "Cloudinary",
+ "Multer"
+ ],
+ "upcoming": [
+ "React Native",
+ "Backend Development",
+ "testdriven development",
+ "Further "
+ ],
+ "more": [
+ "Agile Methodology (Scrum)",
+ "UX Design",
+ "Web Accessibility (WCAG)",
+ "Process Design",
+ "Workshop Facilitation",
+ "IT Consulting",
+ "Change Management"
+ ]
+ },
+ "socialMedia": {
+ "github": {
+ "url": "https://github.com/HolaCarmensita",
+ "icon": "/github.svg"
+ },
+ "linkedin": {
+ "url": "https://www.linkedin.com/in/casandra-gustafsson/",
+ "icon": "/linkedIn.svg"
+ },
+ "stackoverflow": {
+ "url": "https://stackoverflow.com/users/22833434/casandra-gustafsson",
+ "icon": "/stackoverflow.svg"
+ },
+ "instagram": {
+ "url": "https://www.instagram.com/casandragustafsson/",
+ "icon": "/instagram.svg"
+ }
+ }
+}
diff --git a/src/data/projects.json b/src/data/projects.json
index 7c426028..786c1365 100644
--- a/src/data/projects.json
+++ b/src/data/projects.json
@@ -1,28 +1,96 @@
{
"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",
+ "name": "The Pensieve",
+ "image": "/flatProfile.png",
"tags": [
- "HTML5",
- "CSS3",
- "JavaScript"
+ "React",
+ "Three.js",
+ "Node.js",
+ "Express.js",
+ "MongoDB",
+ "Styled Components",
+ "Zustand"
],
- "netlify": "link",
- "github": "link"
+ "netlify": "https://aesthetic-dolphin-63dc60.netlify.app/profile",
+ "github": "https://github.com/HolaCarmensita/project-final",
+ "description": "A collaborative platform where users can share and discover creative ideas in an immersive 3D environment. Connect with ideas and find collaborators for your projects through idea connections.",
+ "gallery": [
+ {
+ "image": "/3dspace.gif",
+ "caption": "Ideas floating in 3D space"
+ },
+ {
+ "image": "/Ideas.png",
+ "caption": "Ideas in 3D space"
+ },
+ {
+ "image": "/browsingIdea.gif",
+ "caption": "Exploring an idea"
+ },
+
+ {
+ "image": "/ProfilePage.png",
+ "caption": "User profile page"
+ }
+ ]
+ },
+ {
+ "name": "Coparenting App",
+ "image": "/CoParentingApp.png",
+ "tags": ["Figma", "Illustrator", "Photoshop"],
+ "netlify": "https://www.co-parenting.app/about-us/",
+ "github": "",
+ "description": "The Co-parenting app makes shared parenting simpler — It's built to bring clarity, calm, and collaboration where it matters most. I had the privilege of redesigning the app with a focus on improving usability and aesthetics while keeping major code changes to a minimum. So, what can you do with almost only CSS changes? A lot! I also designed their new homepage, carefully tailored to meet their goals and requirements.",
+ "gallery": [
+ {
+ "image": "/beforeAfter.png",
+ "caption": "Before and after pictures of the apps design"
+ },
+ {
+ "image": "/BeforeAfterLists.png",
+ "caption": "Before and after pictures of the apps design"
+ },
+ {
+ "image": "/HomePageFAQ.png",
+ "caption": "FAQ page Coparenting app homepage"
+ },
+ {
+ "image": "/HomedesignArticals.png",
+ "caption": "Knowledge page Coparenting app homepage"
+ }
+ ]
+ },
+ {
+ "name": "Happy Thoughts App",
+ "image": "/happyThoughts.png",
+ "tags": ["HTML5", "CSS3", "JavaScript", "API", "Styled Components"],
+ "netlify": "https://holahappythoughts.netlify.app/",
+ "github": "https://github.com/HolaCarmensita/js-project-happy-thoughts",
+ "description": "A cheerful React app using a public API to fetch, post, and like positive messages in real time. Fully responsive with smooth animations and a clean component structure using hooks."
+ },
+ {
+ "name": "Recipe Explorer",
+ "image": "/recipeLibrary.png",
+ "tags": ["HTML5", "CSS3", "JavaScript", "API"],
+ "netlify": "https://recipe-library-technigo.netlify.app/",
+ "github": "https://github.com/HolaCarmensita/js-project-recipe-library",
+ "description": "Recipe Explorer fetches real recipes from Spoonacular, displays dynamic cards, lets you combine filters and sorting, hit a \"Surprise Me!\" button for a random pick, and handles empty or quota-exceeded states."
},
{
- "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",
+ "name": "Weather Dashboard",
+ "image": "/weatherApp.png",
"tags": [
"HTML5",
"CSS3",
- "JavaScript",
+ "React",
"TypeScript",
- "APIs"
+ "API",
+ "Styled Components"
],
- "netlify": "link",
- "github": "link"
+ "netlify": "https://earth2weather.netlify.app/",
+ "github": "https://github.com/HolaCarmensita/js-project-weather-app?tab=readme-ov-file",
+ "description": "A collaboratively built, dynamic TypeScript app that uses the OpenWeatherMap API to deliver real-time conditions and a 4-day forecast in a fully responsive interface."
}
]
-}
\ No newline at end of file
+}
diff --git a/src/index.css b/src/index.css
index 61010be6..e69de29b 100644
--- a/src/index.css
+++ b/src/index.css
@@ -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..8cb024a2 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -1,12 +1,11 @@
-import { StrictMode } from 'react'
-import { createRoot } from 'react-dom/client'
-
-import { App } from './App.jsx'
-
-import './index.css'
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+import { App } from './App.jsx';
+import GlobalStyle from './styles/GlobalStyle.jsx';
createRoot(document.getElementById('root')).render(
+
- ,
-)
+
+);
diff --git a/src/styles/GlobalStyle.jsx b/src/styles/GlobalStyle.jsx
new file mode 100644
index 00000000..9aaaae34
--- /dev/null
+++ b/src/styles/GlobalStyle.jsx
@@ -0,0 +1,143 @@
+import { createGlobalStyle } from 'styled-components';
+
+const GlobalStyle = createGlobalStyle`
+ /* Custom font */
+ @font-face {
+ font-family: 'TAN-Rosebud';
+ src: url('/fonts/TAN-Rosebud.ttf') format('truetype');
+ font-weight: normal;
+ font-style: normal;
+ font-display: swap;
+ }
+
+ /* CSS Variables */
+ :root {
+ font-size: 16px;
+ --font-main: 'Belgrano', serif;
+ ${'' /* --font-main: 'Aileron Regular', sans-serif; */}
+ --font-display: 'TAN-Rosebud', serif;
+ --font-alt: 'Lexend', sans-serif;
+ --color-bg: #FAFBF4;
+ --color-text: #584793;
+ --color-accent: #FF7300;
+ --color-purple: #584793;
+ --color-magenta: #34EDB3;
+ --color-mustard: #FFBF00;
+ --link-color: #584793;
+ --link-hover-color: #FFBF00;
+ }
+
+ /* Global Reset & Base Styles */
+ *, *::before, *::after {
+ box-sizing: border-box;
+ }
+
+ * {
+ margin: 0;
+ }
+
+ html {
+ scroll-behavior: smooth;
+ }
+
+ html, body {
+ height: 100%; /*Vet ej om jag behöver denna men ev vid sticky footer etc*/
+ font-family: var(--font-main);
+ background-color: var(--color-bg);
+ color: var(--color-text);
+ line-height: 1.5;
+ -webkit-font-smoothing: antialiased;
+ overflow-x: clip;
+
+ }
+
+ #root {
+ isolation: isolate;
+ }
+
+ img, picture, video, canvas, svg {
+ display: block;
+ max-width: 100%;
+ }
+
+ input, button, textarea, select {
+ font: inherit;
+ }
+
+
+h1 {
+ font-size: 3rem; //48px om vi har basic font på 16px
+ text-transform: uppercase;
+ font-weight: 300;
+
+ @media (min-width: 768px) {
+ font-size: 4rem;
+ }
+
+}
+
+h2{
+ font-size: 2.5rem;
+ text-transform: uppercase;
+ font-weight: 300;
+ padding: 1rem 0 1rem 0;
+
+ @media (min-width: 768px) {
+ font-size: 3.5rem;
+ padding: 2rem 0 2rem 0;
+ }
+
+}
+
+
+h3{
+ font-size: 1.5rem;
+ text-transform: uppercase;
+ font-weight: 300;
+ @media (min-width: 768px) {
+ font-size: 2rem;
+ }
+
+}
+
+
+
+
+ p, h1, h2, h3, h4, h5, h6 {
+ overflow-wrap: break-word;
+ }
+
+ p {
+ text-wrap: pretty;
+ }
+
+ h1, h2, h3, h4, h5, h6 {
+ text-wrap: balance;
+ }
+
+ a, button {
+ cursor: pointer;
+ }
+
+ a {
+ color: var(--link-color);
+}
+
+ a:visited {
+ color: var(--link-color);
+}
+
+
+/* Style */
+
+
+
+ /* Accessibilty*/
+
+html.dyslexic {
+ --font-main: var(--font-alt);
+}
+
+`;
+
+export default GlobalStyle;
diff --git a/src/styles/styled-utils.js b/src/styles/styled-utils.js
new file mode 100644
index 00000000..10be0b4a
--- /dev/null
+++ b/src/styles/styled-utils.js
@@ -0,0 +1,190 @@
+import { css, keyframes } from 'styled-components';
+
+//////////////
+// COMPATIBLE ANIMATIONS - Works in all browsers
+// These animations use a combination of modern CSS and fallback support
+//////////////
+
+export const slideUpAnimation = css`
+ /* Modern browsers with view-timeline support */
+ @supports (animation-timeline: view()) {
+ opacity: 0;
+ transform: translateY(200px);
+ animation: slideUp 2.5s ease forwards;
+ animation-timeline: view();
+ animation-range: entry 0% cover 40%;
+ }
+
+ /* Fallback - simple animation for older browsers */
+ @supports not (animation-timeline: view()) {
+ animation: slideUpFallback 0.8s ease forwards;
+ }
+
+ @keyframes slideUp {
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+
+ @keyframes slideUpFallback {
+ from {
+ opacity: 0;
+ transform: translateY(50px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+`;
+
+export const slideUpAnimationFast = css`
+ /* Modern browsers with view-timeline support */
+ @supports (animation-timeline: view()) {
+ opacity: 0;
+ transform: translateY(200px);
+ animation: slideUpFast 2.5s ease forwards;
+ animation-timeline: view();
+ animation-range: entry 0% cover 20%;
+ }
+
+ /* Fallback - simple animation for older browsers */
+ @supports not (animation-timeline: view()) {
+ animation: slideUpFastFallback 0.6s ease forwards;
+ }
+
+ @keyframes slideUpFast {
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+
+ @keyframes slideUpFastFallback {
+ from {
+ opacity: 0;
+ transform: translateY(50px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+ }
+`;
+
+////////////////////
+export const appearAnimation = css`
+ /* Modern browsers with view-timeline support */
+ @supports (animation-timeline: view()) {
+ opacity: 0;
+ transform: scale(0.9);
+ transform-origin: center center;
+ animation: appearAnimation 1s linear forwards;
+ animation-timeline: view();
+ animation-range: entry 0% cover 30%;
+ }
+
+ /* Fallback - simple animation for older browsers */
+ @supports not (animation-timeline: view()) {
+ animation: appearAnimationFallback 0.6s ease forwards;
+ }
+
+ @keyframes appearAnimation {
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
+
+ @keyframes appearAnimationFallback {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+ }
+`;
+
+export const opacityAppear = css`
+ /* Modern browsers with view-timeline support */
+ @supports (animation-timeline: view()) {
+ opacity: 0;
+ animation: opacityAppear 1s linear forwards;
+ animation-timeline: view();
+ animation-range: entry 0% cover 10%;
+ }
+
+ /* Fallback - simple animation for older browsers */
+ @supports not (animation-timeline: view()) {
+ animation: opacityAppearFallback 0.6s ease forwards;
+ }
+
+ @keyframes opacityAppear {
+ from {
+ opacity: 0;
+ transform-origin: center center;
+ }
+ to {
+ opacity: 1;
+ }
+ }
+
+ @keyframes opacityAppearFallback {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+ }
+`;
+
+export const tittutAnimation = css`
+ /* Modern browsers with view-timeline support */
+ @supports (animation-timeline: view()) {
+ clip-path: inset(0 0 100% 0);
+ transform-origin: top;
+ animation: tittutAnimation linear forwards;
+ animation-timeline: view();
+ animation-range: entry 0% cover 50%;
+ }
+
+ /* Fallback - simple animation for older browsers */
+ @supports not (animation-timeline: view()) {
+ animation: tittutAnimationFallback 0.8s ease forwards;
+ }
+
+ @keyframes tittutAnimation {
+ from {
+ clip-path: inset(0 0 100% 0); /* 👈 klipper BORT allt utom toppen */
+ transform-origin: top;
+ }
+ to {
+ clip-path: inset(0 0 0 0);
+ }
+ }
+
+ @keyframes tittutAnimationFallback {
+ from {
+ clip-path: inset(0 0 100% 0);
+ }
+ to {
+ clip-path: inset(0 0 0 0);
+ }
+ }
+`;
+
+// Smooth scale-on-hover
+export const hoverScale = css`
+ /* apply a smooth scale transform on hover or focus */
+ transition: transform 0.3s ease-in-out;
+
+ &:hover,
+ &:focus {
+ transform: scale(1.05);
+ }
+`;
diff --git a/vite.config.js b/vite.config.js
index 8b0f57b9..c0e3f082 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,7 +1,8 @@
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
// https://vite.dev/config/
export default defineConfig({
+ base: '/',
plugins: [react()],
-})
+});