diff --git a/next.config.mjs b/next.config.mjs index 5b436b6..dcb5e1e 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,6 +1,15 @@ /** @type {import('next').NextConfig} */ const nextConfig = { devIndicators: false, + images: { + remotePatterns: [ + { + protocol: 'https', + hostname: 'avatars.githubusercontent.com', + pathname: '/**', + }, + ], + }, }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index 4e7b11a..732591a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,9 @@ "name": "portfolio", "version": "0.1.0", "dependencies": { + "@ionic/react": "^8.5.7", "framer-motion": "^12.6.2", + "ionicons": "^8.0.8", "next": "15.2.0", "react": "^19.0.0", "react-dom": "^19.0.0" @@ -599,6 +601,50 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@ionic/core": { + "version": "8.5.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.5.7.tgz", + "integrity": "sha512-V5ZRYXD1MgAPdjfLyOejILAdTqIzpMY7/v6GSynFPPWoEpfFbGe/tNsimrYm1/D8iouigYLkJjWSeg2rpIpESA==", + "license": "MIT", + "dependencies": { + "@stencil/core": "4.20.0", + "ionicons": "^7.2.2", + "tslib": "^2.1.0" + } + }, + "node_modules/@ionic/core/node_modules/ionicons": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.4.0.tgz", + "integrity": "sha512-ZK94MMqgzMCPPMhmk8Ouu6goyVHFIlw/ACP6oe3FrikcI0N7CX0xcwVaEbUc0G/v3W0shI93vo+9ve/KpvcNhQ==", + "license": "MIT", + "dependencies": { + "@stencil/core": "^4.0.3" + } + }, + "node_modules/@ionic/react": { + "version": "8.5.7", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.5.7.tgz", + "integrity": "sha512-AgX4iu6SfuBhNgYr0H+K3oGsp7ESkCsnaqZdHRO2+GtKTmo4akMrFPihGj4LrZB/IaYwcvYQR/bPWHuZGJYsnw==", + "license": "MIT", + "dependencies": { + "@ionic/core": "8.5.7", + "ionicons": "^7.0.0", + "tslib": "*" + }, + "peerDependencies": { + "react": ">=16.8.6", + "react-dom": ">=16.8.6" + } + }, + "node_modules/@ionic/react/node_modules/ionicons": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-7.4.0.tgz", + "integrity": "sha512-ZK94MMqgzMCPPMhmk8Ouu6goyVHFIlw/ACP6oe3FrikcI0N7CX0xcwVaEbUc0G/v3W0shI93vo+9ve/KpvcNhQ==", + "license": "MIT", + "dependencies": { + "@stencil/core": "^4.0.3" + } + }, "node_modules/@next/env": { "version": "15.2.0", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.0.tgz", @@ -791,6 +837,110 @@ "node": ">=12.4.0" } }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", + "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", + "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", + "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", + "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", + "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", + "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", + "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", + "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -805,6 +955,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@stencil/core": { + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.20.0.tgz", + "integrity": "sha512-WPrTHFngvN081RY+dJPneKQLwnOFD60OMCOQGmmSHfCW0f4ujPMzzhwWU1gcSwXPWXz5O+8cBiiCaxAbJU7kAg==", + "license": "MIT", + "bin": { + "stencil": "bin/stencil" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.10.0" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -3100,6 +3263,38 @@ "node": ">= 0.4" } }, + "node_modules/ionicons": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/ionicons/-/ionicons-8.0.8.tgz", + "integrity": "sha512-CRHhDQA5vsNxC7raeEPqddgO90iR2F13VZS7hB0Vx7JrxJMM2060E3ddgYbpMQNJjzeBsgpkNwdBeK5qQ7RbLA==", + "license": "MIT", + "dependencies": { + "@stencil/core": "^4.30.0" + } + }, + "node_modules/ionicons/node_modules/@stencil/core": { + "version": "4.31.0", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.31.0.tgz", + "integrity": "sha512-Ei9MFJ6LPD9BMFs+klkHylbVOOYhG10Jv4bvoFf3GMH15kA41rSYkEdr4DiX84ZdErQE2qtFV/2SUyWoXh0AhA==", + "license": "MIT", + "bin": { + "stencil": "bin/stencil" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.10.0" + }, + "optionalDependencies": { + "@rollup/rollup-darwin-arm64": "4.34.9", + "@rollup/rollup-darwin-x64": "4.34.9", + "@rollup/rollup-linux-arm64-gnu": "4.34.9", + "@rollup/rollup-linux-arm64-musl": "4.34.9", + "@rollup/rollup-linux-x64-gnu": "4.34.9", + "@rollup/rollup-linux-x64-musl": "4.34.9", + "@rollup/rollup-win32-arm64-msvc": "4.34.9", + "@rollup/rollup-win32-x64-msvc": "4.34.9" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", diff --git a/package.json b/package.json index dd29cdf..4ada74f 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,9 @@ "lint": "next lint" }, "dependencies": { + "@ionic/react": "^8.5.7", "framer-motion": "^12.6.2", + "ionicons": "^8.0.8", "next": "15.2.0", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/src/app/about/page.js b/src/app/about/page.js index afeeb0d..3e117af 100644 --- a/src/app/about/page.js +++ b/src/app/about/page.js @@ -1,6 +1,7 @@ "use client" import {useRef, useState, useEffect} from "react"; import Link from "next/link"; +import FadeInSection from "@/app/components/FadeInSection"; export default function About() { const timelineData = [ @@ -81,7 +82,7 @@ export default function About() { setCurrentSection(0); setTimelineFinished(false); setTimeout(() => { - aboutMeRef.current?.scrollIntoView({behavior: "smooth", block: "start"}); + window.scrollTo({top: 0, behavior: "smooth"}); }, 100); }, []); useEffect(() => { @@ -143,29 +144,39 @@ export default function About() { position: "relative", // Hinzugefügt overflowX: "hidden" }}> - {/* About Me Section */} -
-

About Me

-

- I’m a passionate developer always excited about new challenges. - Scroll down to learn more about me! -

-
+ +
+

About Me

+

+ I’m a passionate developer always excited about new challenges. + Scroll down to learn more about me! +

+
+
{/* Timeline Section */} -
+
{/* Navigation buttons and timeline content here */} -
+
{timelineData.map((item, index) => { const adjustedSkills = skills.map((s) => { const stepLevel = s.progression?.[index] ?? 0; @@ -192,15 +203,26 @@ export default function About() {

{item.title}

{item.date}

{item.content}

-
+
{adjustedSkills.filter((s) => s.level > 0).map((skill, sIndex) => (
-

{skill.name}

-
+

{skill.name}

+
{getSkillLabel(skill.level)}
@@ -212,12 +234,36 @@ export default function About() {
{currentSection > 0 && ( - )} {currentSection < timelineData.length - 1 && ( - )} diff --git a/src/app/contact/page.js b/src/app/contact/page.js new file mode 100644 index 0000000..8c6fd42 --- /dev/null +++ b/src/app/contact/page.js @@ -0,0 +1,67 @@ +"use client"; + +import React, {useEffect} from "react"; +import Link from "next/link"; +import Image from "next/image"; + +export default function ContactPage() { + useEffect(() => { + // Load Ionicons if not already present + if (!window.customElements.get("ion-icon")) { + const script = document.createElement("script"); + script.setAttribute("type", "module"); + script.setAttribute("src", "https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"); + document.head.appendChild(script); + + const noModuleScript = document.createElement("script"); + noModuleScript.setAttribute("nomodule", ""); + noModuleScript.setAttribute("src", "https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"); + document.head.appendChild(noModuleScript); + } + }, []); + + return ( +
+
+

Kontakt und Impressum

+ + + +
+

Impressum

+
+

+ Frederik Spirgi
+ Frederik@spirgi.com
+ 077 533 70 36 +

+
+
+
+
+ ); +} + +function toggleSidebar() { + const sidebar = document.getElementById("sidebar"); + if (!sidebar) return; + const isOpen = sidebar.style.transform === "translateX(0%)"; + sidebar.style.transform = isOpen ? "translateX(-100%)" : "translateX(0%)"; +} \ No newline at end of file diff --git a/src/app/globals.css b/src/app/globals.css index 5586a86..69ab67f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -369,3 +369,114 @@ nav.navbar { } } +/* === Contact Page === */ +.contact-list { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 2rem; + margin-top: 2rem; +} + +.contact-item { + display: flex; + flex-direction: column; + align-items: center; + max-width: 220px; + text-align: center; +} + +.contact-pic { + width: 180px; + height: 180px; + border-radius: 50%; + object-fit: cover; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + transition: transform 0.3s ease; +} + +.contact-pic:hover { + transform: scale(1.05); +} + +.contact-name { + font-size: 1.2rem; + font-weight: bold; + margin-top: 1rem; + color: #ffffff; +} + +.contact-link { + text-decoration: none; + color: #ddd; + display: flex; + flex-direction: column; + align-items: center; + margin-top: 0.5rem; + position: relative; +} + +.contact-link .icon { + font-size: 0.9rem; + margin-top: 0.5rem; + text-align: center; + color: #ccc; + transition: color 0.3s; +} + +.contact-link:hover .icon { + color: #fff; +} + +.impressum { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 2rem; + margin-top: 2rem; +} + +.impressum-item { + background-color: rgba(255, 255, 255, 0.08); + padding: 1rem 2rem; + border-radius: 10px; + color: #ffffff; + min-width: 240px; + text-align: center; + box-shadow: 0 0 10px rgba(255, 255, 255, 0.05); +} + +main h1, +main h2 { + text-align: center; + color: #ffffff; + margin-bottom: 1rem; +} + +main { + animation: fadeIn 0.8s ease-in-out; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(15px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (max-width: 768px) { + .contact-pic { + width: 140px; + height: 140px; + } + + .impressum-item { + padding: 1rem; + font-size: 0.95rem; + } +} + diff --git a/src/app/projects/page.js b/src/app/projects/page.js index 1b2bbd8..ea8af92 100644 --- a/src/app/projects/page.js +++ b/src/app/projects/page.js @@ -2,6 +2,7 @@ import { useState } from "react"; import Image from "next/image"; +import FadeInSection from "@/app/components/FadeInSection"; const projectData = [ { @@ -46,22 +47,24 @@ export default function ProjectsPage() { return (
-
-

{project.title}

- {project.title} -

{project.description}

- - - -
+ +
+

{project.title}

+ {project.title} +

{project.description}

+ + + +
+