- Added dynamic scroll prompt functionality triggered by timeline completion.

- Introduced reusable `Nav` and `Footer` components.
- Updated layout with new navigation, footer, and responsive styling.
- Refactored timeline buttons for clarity and relocation.
- Improved global and scroll prompt styles for better UI/UX.
- Set up mobile responsiveness for navigation bar.
This commit is contained in:
IM23a-spirgif
2025-05-22 10:37:36 +02:00
parent 96a9e0b733
commit 777f783ad5
5 changed files with 214 additions and 143 deletions
+72 -98
View File
@@ -72,9 +72,11 @@ export default function About() {
const sectionRefs = useRef([]);
const aboutMeRef = useRef(null);
const buttonSectionRef = useRef(null);
const scrollContainerRef = useRef(null);
const [currentSection, setCurrentSection] = useState(0);
const [timelineFinished, setTimelineFinished] = useState(false);
const [showScrollPrompt, setShowScrollPrompt] = useState(false);
useEffect(() => {
setCurrentSection(0);
setTimelineFinished(false);
@@ -82,6 +84,26 @@ export default function About() {
aboutMeRef.current?.scrollIntoView({behavior: "smooth", block: "start"});
}, 100);
}, []);
useEffect(() => {
let scrollListener;
if (timelineFinished) {
setShowScrollPrompt(true);
scrollListener = () => {
setShowScrollPrompt(false);
window.removeEventListener("scroll", scrollListener);
};
window.addEventListener("scroll", scrollListener);
}
return () => {
if (scrollListener) {
window.removeEventListener("scroll", scrollListener);
}
};
}, [timelineFinished]);
const handleButtonClick = (direction) => {
setCurrentSection((prev) => {
const newSection = Math.max(0, Math.min(prev + direction, timelineData.length - 1));
@@ -115,7 +137,12 @@ export default function About() {
return () => observer.disconnect();
}, [timelineData.length]);
return (
<div style={{width: "100%", overflowX: "hidden"}}>
// Ändern Sie den äußeren Container-Style
<div style={{
width: "100%",
position: "relative", // Hinzugefügt
overflowX: "hidden"
}}>
{/* About Me Section */}
<section
ref={aboutMeRef}
@@ -136,60 +163,9 @@ export default function About() {
</p>
</section>
{/* Timeline Section */}
<section style={{position: "relative", height: "100vh", overflowY: "hidden"}}>
{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",
}}
>
&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",
}}
>
&rarr;
</button>
)}
<div
ref={scrollContainerRef}
style={{
display: "flex",
overflowX: "auto",
scrollSnapType: "x mandatory",
width: "100%",
height: "100%",
scrollBehavior: "smooth",
scrollbarWidth: "none",
msOverflowStyle: "none",
}}
>
<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" }}>
{timelineData.map((item, index) => {
const adjustedSkills = skills.map((s) => {
const stepLevel = s.progression?.[index] ?? 0;
@@ -213,61 +189,61 @@ export default function About() {
boxSizing: "border-box",
}}
>
<h2 style={{marginBottom: "1rem"}}>{item.title}</h2>
<h3 style={{marginBottom: "1rem"}}>{item.date}</h3>
<p style={{maxWidth: "600px", textAlign: "center", marginBottom: "2rem"}}>
{item.content}
</p>
{/* SKILLS: show per stage */}
<div style={{display: "flex", gap: "2rem"}}>
<div style={{
display: "flex",
gap: "2rem",
flexWrap: "wrap",
justifyContent: "center"
}}>
<h2>{item.title}</h2>
<h3>{item.date}</h3>
<p>{item.content}</p>
<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",
}}
>
<p style={{
fontSize: "1.1rem",
fontWeight: "bold",
marginBottom: "0.5rem"
}}>
{skill.name}
</p>
<div
style={{
padding: "0.5rem 1rem",
backgroundColor: "#1a001a",
border: "2px solid #3d003d",
borderRadius: "10px",
fontSize: "1rem",
color: "#fff",
}}
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" }}>
{getSkillLabel(skill.level)}
</div>
</div>
))}
</div>
</div>
</section>
);
})}
</div>
{/* End of scroll container */}
{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" }}>
&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" }}>
&rarr;
</button>
)}
</section>
{timelineFinished && showScrollPrompt && (
<div className="scrollPrompt">
⬇ Scroll down to continue ⬇
</div>
)}
{/* FINAL BUTTON SECTION - MOVED OUTSIDE */}
{timelineFinished && (
<div style={{marginTop: "3rem", textAlign: "center", width: "100%"}}>
// Fügen Sie dem Button-Section einen z-Index hinzu
<section
ref={buttonSectionRef}
style={{
minHeight: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
padding: "2rem",
boxSizing: "border-box",
position: "relative", // Hinzugefügt
zIndex: 1 // Hinzugefügt
}}
>
<Link href="/projects">
<button
className="learnMoreButton"
@@ -287,10 +263,8 @@ export default function About() {
View Projects
</button>
</Link>
</div>
)}
</section>
)}
</div>
)
}
+15
View File
@@ -0,0 +1,15 @@
export default function Footer() {
return (
<footer style={{
padding: "2rem",
textAlign: "center",
background: "#1a001a",
borderTop: "2px solid #3d003d",
marginTop: "auto"
}}>
<p style={{ margin: 0, color: "#ccc" }}>
© {new Date().getFullYear()} Frederik Spirgi. All rights reserved.
</p>
</footer>
);
}
+15
View File
@@ -0,0 +1,15 @@
"use client";
import Link from "next/link";
export default function Nav() {
return (
<nav className="navbar">
<Link href="/" className="nav-logo">Fretux</Link>
<div className="nav-links">
<Link href="/about" className="nav-link">About</Link>
<Link href="/projects" className="nav-link">Projects</Link>
<Link href="/contact" className="nav-link">Contact</Link>
</div>
</nav>
);
}
+63 -6
View File
@@ -138,16 +138,18 @@ html {
.scrollPrompt {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
padding: 0.5rem 1rem;
border-radius: 5px;
color: #fff;
bottom: 40px;
left: 0;
width: 100%;
text-align: center;
font-size: 1rem;
padding: 0.5rem 1rem;
color: #fff;
border-radius: 5px;
z-index: 1000;
background: none;
animation: pulsePrompt 2s infinite ease-in-out;
pointer-events: none;
}
.fade-in-section.is-visible {
@@ -312,3 +314,58 @@ body {
animation: fadeInButton 0.5s ease-out;
}
/* === Navigation Styles === */
nav.navbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 2rem;
background-color: #1a001a;
border-bottom: 2px solid #3d003d;
position: sticky;
top: 0;
z-index: 999;
}
.navbar .nav-logo {
color: #fff;
font-size: 1.5rem;
font-weight: bold;
text-decoration: none;
}
.navbar .nav-links {
display: flex;
gap: 1.5rem;
}
.navbar .nav-link {
color: #fff;
text-decoration: none;
font-size: 1rem;
transition: color 0.3s ease;
}
.navbar .nav-link:hover {
color: #ff99ff;
}
/* Optional: mobile responsiveness */
@media (max-width: 768px) {
.navbar {
flex-direction: column;
align-items: flex-start;
padding: 1rem;
}
.navbar .nav-links {
flex-direction: column;
gap: 1rem;
width: 100%;
}
.navbar .nav-link {
width: 100%;
}
}
+15 -5
View File
@@ -1,5 +1,7 @@
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import Nav from "./components/Nav";
import Footer from "./components/Footer";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -12,17 +14,25 @@ const geistMono = Geist_Mono({
});
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "Fretux Portfolio",
description: "Frederik Spirgi – Full-stack developer",
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<body style={{
display: "flex",
flexDirection: "column",
minHeight: "100vh",
background: "linear-gradient(135deg, #000, #1a001a)",
color: "#fff",
}}>
<Nav />
<main style={{ flex: 1 }}>
{children}
</main>
<Footer />
</body>
</html>
);