- Added FadeInSection component to About and Projects pages for animations.

- Updated About page to use `window.scrollTo` for smooth scrolling.
- Created Contact page with sections for "Kontakt" and "Impressum."
- Integrated Ionicons into Contact page.
- Added support for external images in Next.js config.
- Added `@ionic/react` and `ionicons` dependencies.
- Styled Contact page with custom CSS rules.
This commit is contained in:
IM23a-spirgif
2025-05-22 11:13:17 +02:00
parent 777f783ad5
commit fb4f318e1f
7 changed files with 477 additions and 44 deletions
+74 -28
View File
@@ -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 */}
<section
ref={aboutMeRef}
style={{
minHeight: "100vh",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
padding: "2rem",
boxSizing: "border-box",
}}
>
<h1 style={{fontSize: "2.5rem", marginBottom: "1rem"}}>About Me</h1>
<p style={{maxWidth: "600px", textAlign: "center"}}>
I’m a passionate developer always excited about new challenges.
Scroll down to learn more about me!
</p>
</section>
<FadeInSection>
<section
ref={aboutMeRef}
style={{
minHeight: "100vh",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
padding: "2rem",
boxSizing: "border-box",
}}
>
<h1 style={{fontSize: "2.5rem", marginBottom: "1rem"}}>About Me</h1>
<p style={{maxWidth: "600px", textAlign: "center"}}>
I’m a passionate developer always excited about new challenges.
Scroll down to learn more about me!
</p>
</section>
</FadeInSection>
{/* Timeline Section */}
<section style={{ position: "relative", height: "100vh", overflowY: "hidden" }}>
<section style={{position: "relative", height: "100vh", overflowY: "hidden"}}>
{/* Navigation buttons and timeline content here */}
<div ref={scrollContainerRef} style={{ display: "flex", overflowX: "auto", scrollSnapType: "x mandatory", width: "100%", height: "100%", scrollBehavior: "smooth", scrollbarWidth: "none", msOverflowStyle: "none" }}>
<div ref={scrollContainerRef} style={{
display: "flex",
overflowX: "auto",
scrollSnapType: "x mandatory",
width: "100%",
height: "100%",
scrollBehavior: "smooth",
scrollbarWidth: "none",
msOverflowStyle: "none"
}}>
{timelineData.map((item, index) => {
const adjustedSkills = skills.map((s) => {
const stepLevel = s.progression?.[index] ?? 0;
@@ -192,15 +203,26 @@ export default function About() {
<h2>{item.title}</h2>
<h3>{item.date}</h3>
<p>{item.content}</p>
<div style={{ display: "flex", gap: "2rem", flexWrap: "wrap", justifyContent: "center" }}>
<div style={{display: "flex", gap: "2rem", flexWrap: "wrap", justifyContent: "center"}}>
{adjustedSkills.filter((s) => s.level > 0).map((skill, sIndex) => (
<div
key={`${skill.name}-${index}`}
className={currentSection === index ? "skill-card animate-skill" : "skill-card"}
style={{ textAlign: "center", width: "120px", animationDelay: `${sIndex * 100}ms`, animationFillMode: "both" }}
style={{
textAlign: "center",
width: "120px",
animationDelay: `${sIndex * 100}ms`,
animationFillMode: "both"
}}
>
<p style={{ fontWeight: "bold" }}>{skill.name}</p>
<div style={{ padding: "0.5rem 1rem", backgroundColor: "#1a001a", border: "2px solid #3d003d", borderRadius: "10px", color: "#fff" }}>
<p style={{fontWeight: "bold"}}>{skill.name}</p>
<div style={{
padding: "0.5rem 1rem",
backgroundColor: "#1a001a",
border: "2px solid #3d003d",
borderRadius: "10px",
color: "#fff"
}}>
{getSkillLabel(skill.level)}
</div>
</div>
@@ -212,12 +234,36 @@ export default function About() {
</div>
{currentSection > 0 && (
<button onClick={() => handleButtonClick(-1)} style={{ position: "absolute", top: "50%", left: "1rem", transform: "translateY(-50%)", zIndex: 10, backgroundColor: "#333", color: "#fff", border: "none", padding: "0.75rem 1rem", cursor: "pointer", borderRadius: "4px" }}>
<button onClick={() => handleButtonClick(-1)} style={{
position: "absolute",
top: "50%",
left: "1rem",
transform: "translateY(-50%)",
zIndex: 10,
backgroundColor: "#333",
color: "#fff",
border: "none",
padding: "0.75rem 1rem",
cursor: "pointer",
borderRadius: "4px"
}}>
&larr;
</button>
)}
{currentSection < timelineData.length - 1 && (
<button onClick={() => handleButtonClick(1)} style={{ position: "absolute", top: "50%", right: "1rem", transform: "translateY(-50%)", zIndex: 10, backgroundColor: "#333", color: "#fff", border: "none", padding: "0.75rem 1rem", cursor: "pointer", borderRadius: "4px" }}>
<button onClick={() => handleButtonClick(1)} style={{
position: "absolute",
top: "50%",
right: "1rem",
transform: "translateY(-50%)",
zIndex: 10,
backgroundColor: "#333",
color: "#fff",
border: "none",
padding: "0.75rem 1rem",
cursor: "pointer",
borderRadius: "4px"
}}>
&rarr;
</button>
)}
+67
View File
@@ -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 (
<div className="css_body">
<main>
<h1>Kontakt und Impressum</h1>
<article>
<h2>Kontakt</h2>
<section className="contact-list">
<div className="contact-item">
<p className="contact-name">Frederik Spirgi</p>
<a className="contact-link" href="https://github.com/IM23a-spirgif" target="_blank"
rel="noopener noreferrer">
<img className="contact-pic" src="https://avatars.githubusercontent.com/u/143030312?v=4"
alt="Frederik Spirgi"/>
<span className="icon">
Writer & Developer
<br/>
<ion-icon name="logo-github"></ion-icon>
</span>
</a>
</div>
</section>
</article>
<article>
<h2>Impressum</h2>
<section className="impressum">
<p className="impressum-item">
<strong>Frederik Spirgi</strong><br/>
Frederik@spirgi.com<br/>
077 533 70 36
</p>
</section>
</article>
</main>
</div>
);
}
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%)";
}
+111
View File
@@ -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;
}
}
+19 -16
View File
@@ -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 (
<div className="projectContainer">
<div className={`projectCard ${fade ? "fade-out" : "fade-in"}`}>
<h2 className="projectTitle">{project.title}</h2>
<Image
src={project.image}
alt={project.title}
width={600}
height={400}
className="projectImage"
/>
<p className="projectDescription">{project.description}</p>
<a href={project.link} target="_blank" rel="noopener noreferrer">
<button className="learnMoreButton" style={{ marginTop: "1.5rem" }}>
View Project
</button>
</a>
</div>
<FadeInSection>
<div className={`projectCard ${fade ? "fade-out" : "fade-in"}`}>
<h2 className="projectTitle">{project.title}</h2>
<Image
src={project.image}
alt={project.title}
width={600}
height={400}
className="projectImage"
/>
<p className="projectDescription">{project.description}</p>
<a href={project.link} target="_blank" rel="noopener noreferrer">
<button className="learnMoreButton" style={{ marginTop: "1.5rem" }}>
View Project
</button>
</a>
</div>
</FadeInSection>
<div className="projectNavigation">
<button onClick={() => handleNavigation("prev")} className="arrowButton">
&larr;