Fix Animations, Sidebar and Footer.

This commit is contained in:
2025-02-10 13:35:37 +01:00
parent d23c381913
commit 524e651bca
9 changed files with 131 additions and 129 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ define(['./workbox-4a2e5f00'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812" "revision": "3ca0b8505b4bec776b69afdba2768812"
}, { }, {
"url": "index.html", "url": "index.html",
"revision": "0.p5tdckiicb" "revision": "0.t3hi6i7ipso"
}], {}); }], {});
workbox.cleanupOutdatedCaches(); workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
+1 -1
View File
File diff suppressed because one or more lines are too long
+11 -13
View File
@@ -23,19 +23,17 @@ function App() {
<ErrorBoundary> <ErrorBoundary>
<BrowserRouter> <BrowserRouter>
<ScrollProvider> <ScrollProvider>
<div className="flex flex-col min-h-screen bg-background text-foreground relative"> <Suspense fallback={null}>
<Layout> <PageTransition>
<ErrorBoundary> <Layout>
<Suspense fallback={null}> <ErrorBoundary>
<PageTransition> <AppRoutes />
<AppRoutes /> </ErrorBoundary>
</PageTransition> <CookieBanner />
</Suspense> <DebugOverlay />
</ErrorBoundary> </Layout>
</Layout> </PageTransition>
<CookieBanner /> </Suspense>
<DebugOverlay />
</div>
</ScrollProvider> </ScrollProvider>
</BrowserRouter> </BrowserRouter>
</ErrorBoundary> </ErrorBoundary>
+1 -1
View File
@@ -8,7 +8,7 @@ export function ContactInfo() {
initial={{ opacity: 0, y: -20 }} initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
className="p-6 bg-zinc-800/30 border border-zinc-800 rounded-lg mx-4 mb-4" className="pb-6 px-6 pt-4 bg-zinc-800/30 border border-zinc-800 rounded-lg mx-4 mt-2"
> >
<div className="space-y-4"> <div className="space-y-4">
<h3 className="text-sm font-medium text-white tracking-wider uppercase"> <h3 className="text-sm font-medium text-white tracking-wider uppercase">
+66 -58
View File
@@ -8,64 +8,66 @@ const Footer = () => {
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
return ( return (
<footer className="bg-zinc-900 pt-16 pb-8 px-4 sm:px-6"> <footer className="relative bg-zinc-900 pt-8 pb-4 px-4">
<div className="max-w-7xl mx-auto"> <div className="max-w-7xl mx-auto">
<div className="grid grid-cols-1 md:grid-cols-3 gap-12 mb-12"> <div className="flex flex-col space-y-8">
{/* Contact Section */} {/* Contact Section */}
<div className="space-y-4"> <div>
<h3 className="text-white text-lg font-semibold mb-4"> <h3 className="text-white text-base font-semibold mb-3">
{t('footer.sections.contact.title')} {t('footer.sections.contact.title')}
</h3> </h3>
<div className="flex items-center space-x-2 text-zinc-400 hover:text-white transition-colors"> <div className="flex flex-col space-y-2">
<Mail size={18} />
<a <a
href={`mailto:${t('footer.sections.contact.email')}`} href={`mailto:${t('footer.sections.contact.email')}`}
className="hover:underline" className="group flex items-center space-x-2 text-zinc-400 hover:text-white transition-colors text-sm"
aria-label={t('footer.sections.contact.aria.emailLink')} aria-label={t('footer.sections.contact.aria.emailLink')}
> >
{t('footer.sections.contact.email')} <Mail className="h-4 w-4 text-zinc-500 group-hover:text-white transition-colors" size={16} />
<span>{t('footer.sections.contact.email')}</span>
</a> </a>
</div> <div className="flex items-center space-x-2 text-zinc-400 text-sm">
<div className="flex items-center space-x-2 text-zinc-400"> <MapPin className="h-4 w-4 text-zinc-500" size={16} />
<MapPin size={18} /> <span>{t('footer.sections.contact.location')}</span>
<span aria-label={t('footer.sections.contact.aria.locationText')}> </div>
{t('footer.sections.contact.location')}
</span>
</div> </div>
</div> </div>
{/* Navigation Section */} {/* Navigation Section */}
<div className="space-y-4"> <div>
<h3 className="text-white text-lg font-semibold mb-4"> <h3 className="text-white text-base font-semibold mb-3">
{t('footer.sections.navigation.title')} {t('footer.sections.navigation.title')}
</h3> </h3>
<ul className="space-y-2"> <nav className="flex flex-col space-y-2">
<li> <Link
<Link to="/portfolio" className="text-zinc-400 hover:text-white transition-colors"> to="/portfolio"
{t('footer.sections.navigation.links.portfolio')} className="text-zinc-400 hover:text-white transition-colors text-sm"
</Link> >
</li> {t('footer.sections.navigation.links.portfolio')}
<li> </Link>
<Link to="/blog" className="text-zinc-400 hover:text-white transition-colors"> <Link
{t('footer.sections.navigation.links.blog')} to="/blog"
</Link> className="text-zinc-400 hover:text-white transition-colors text-sm"
</li> >
<li> {t('footer.sections.navigation.links.blog')}
<Link to="/about" className="text-zinc-400 hover:text-white transition-colors"> </Link>
{t('footer.sections.navigation.links.about')} <Link
</Link> to="/about"
</li> className="text-zinc-400 hover:text-white transition-colors text-sm"
<li> >
<Link to="/contact" className="text-zinc-400 hover:text-white transition-colors"> {t('footer.sections.navigation.links.about')}
{t('footer.sections.navigation.links.contact')} </Link>
</Link> <Link
</li> to="/contact"
</ul> className="text-zinc-400 hover:text-white transition-colors text-sm"
>
{t('footer.sections.navigation.links.contact')}
</Link>
</nav>
</div> </div>
{/* Social Media Section */} {/* Social Media Section */}
<div className="space-y-4"> <div>
<h3 className="text-white text-lg font-semibold mb-4"> <h3 className="text-white text-base font-semibold mb-3">
{t('footer.sections.social.title')} {t('footer.sections.social.title')}
</h3> </h3>
<div className="flex space-x-4"> <div className="flex space-x-4">
@@ -73,37 +75,43 @@ const Footer = () => {
href="https://www.linkedin.com/in/damjan-savić-720288127/" href="https://www.linkedin.com/in/damjan-savić-720288127/"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-zinc-400 hover:text-white transition-colors" className="group text-zinc-400 hover:text-white transition-colors"
aria-label={t('footer.sections.social.aria.linkedin')} aria-label={t('footer.sections.social.aria.linkedin')}
> >
<Linkedin size={24} /> <Linkedin className="transform transition-transform group-hover:scale-110" size={20} />
</a> </a>
<a <a
href="https://github.com/damjan1996" href="https://github.com/damjan1996"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-zinc-400 hover:text-white transition-colors" className="group text-zinc-400 hover:text-white transition-colors"
aria-label={t('footer.sections.social.aria.github')} aria-label={t('footer.sections.social.aria.github')}
> >
<Github size={24} /> <Github className="transform transition-transform group-hover:scale-110" size={20} />
</a> </a>
</div> </div>
</div> </div>
</div>
{/* Legal Section */} {/* Legal Section */}
<div className="border-t border-zinc-800 pt-8"> <div className="border-t border-zinc-800 pt-4">
<div className="flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0"> <div className="flex flex-col space-y-4">
<p className="text-zinc-400 text-sm"> <p className="text-zinc-400 text-xs text-center">
&copy; {currentYear} Damjan Savić. {t('footer.legal.copyright')} &copy; {currentYear} Damjan Savić. {t('footer.legal.copyright')}
</p> </p>
<div className="flex space-x-6"> <div className="flex justify-center space-x-4">
<Link to="/privacy" className="text-zinc-400 hover:text-white text-sm transition-colors"> <Link
{t('footer.legal.links.privacy')} to="/privacy"
</Link> className="text-zinc-400 hover:text-white text-xs transition-colors"
<Link to="/terms" className="text-zinc-400 hover:text-white text-sm transition-colors"> >
{t('footer.legal.links.terms')} {t('footer.legal.links.privacy')}
</Link> </Link>
<Link
to="/terms"
className="text-zinc-400 hover:text-white text-xs transition-colors"
>
{t('footer.legal.links.terms')}
</Link>
</div>
</div> </div>
</div> </div>
</div> </div>
+36 -26
View File
@@ -120,7 +120,6 @@ const Layout = ({ children }: LayoutProps) => {
} }
return ( return (
// Relativer Container, um z-index-Steuerung zu ermöglichen
<div className="min-h-screen bg-zinc-900 flex flex-col relative"> <div className="min-h-screen bg-zinc-900 flex flex-col relative">
{/* Navigation oberste Ebene */} {/* Navigation oberste Ebene */}
<nav <nav
@@ -182,16 +181,17 @@ const Layout = ({ children }: LayoutProps) => {
<AnimatePresence> <AnimatePresence>
{isMenuOpen && ( {isMenuOpen && (
<> <>
{/* Backdrop: Sichtbar, aber mit pointer-events-none blockiert er keine Interaktionen */} {/* Backdrop mit Click Handler */}
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
transition={{ duration: 0.2 }} transition={{ duration: 0.2 }}
className="fixed inset-0 bg-black/50 md:hidden z-20 pointer-events-none" className="fixed inset-0 bg-black/50 md:hidden z-20"
onClick={() => setIsMenuOpen(false)}
/> />
{/* Sidebar oberhalb des Hauptinhalts */} {/* Sidebar */}
<motion.div <motion.div
initial={{ x: "100%" }} initial={{ x: "100%" }}
animate={{ x: 0 }} animate={{ x: 0 }}
@@ -199,33 +199,43 @@ const Layout = ({ children }: LayoutProps) => {
transition={{ type: "spring", stiffness: 300, damping: 30 }} transition={{ type: "spring", stiffness: 300, damping: 30 }}
className="fixed right-0 top-0 h-full w-80 bg-zinc-900 shadow-xl md:hidden z-50 border-l border-zinc-800" className="fixed right-0 top-0 h-full w-80 bg-zinc-900 shadow-xl md:hidden z-50 border-l border-zinc-800"
> >
{/* Header-Spacer für die Navigation */} {/* Header mit Close Button */}
<div className="h-16" /> <div className="h-16 flex items-center justify-between px-4 border-b border-zinc-800">
<motion.button
onClick={() => setIsMenuOpen(false)}
className="p-2 rounded-lg text-zinc-400 hover:text-white hover:bg-zinc-800/50 transition-colors"
whileTap={{ scale: 0.95 }}
>
<X className="h-6 w-6" />
</motion.button>
</div>
{/* Scrollbarer Sidebar-Inhalt */} {/* Scrollbarer Sidebar-Inhalt */}
<div className="h-[calc(100vh_-_4rem)] overflow-y-auto"> <div className="h-[calc(100vh_-_4rem)] overflow-y-auto">
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
<div className="flex-1"> {/* Contact Info */}
<ContactInfo /> <ContactInfo />
<div className="py-4 px-4">
{navigationLinks.map(({ path, label, icon }) => ( {/* Navigation Links */}
<NavLink <div className="py-4 px-4">
key={path} {navigationLinks.map(({ path, label, icon }) => (
to={path} <NavLink
icon={icon} key={path}
label={label} to={path}
// Alternativ: Hier könnte ein Klick auch das Menü schließen icon={icon}
onClick={() => setIsMenuOpen(false)} label={label}
className="flex items-center w-full mb-1" onClick={() => setIsMenuOpen(false)}
/> className="flex items-center w-full mb-1"
))} />
</div> ))}
</div> </div>
<div className="border-t border-zinc-800 p-6 mt-auto">
{/* Language Switcher */}
<div className="px-4 py-3 border-t border-zinc-800">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm text-zinc-500"> <span className="text-sm text-zinc-500">
Sprache ändern Sprache ändern
</span> </span>
<LanguageSwitcher /> <LanguageSwitcher />
</div> </div>
</div> </div>
@@ -236,7 +246,7 @@ const Layout = ({ children }: LayoutProps) => {
)} )}
</AnimatePresence> </AnimatePresence>
{/* Hauptinhalt hier kann normal gescrollt werden */} {/* Hauptinhalt */}
<main className="flex-1 pt-16 relative z-30">{children}</main> <main className="flex-1 pt-16 relative z-30">{children}</main>
{/* Footer */} {/* Footer */}
+5 -12
View File
@@ -1,7 +1,6 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import SEO from '../../components/SEO'; import SEO from '../../components/SEO';
import PageTransition from '../../components/PageTransition';
import Hero from './components/Hero'; import Hero from './components/Hero';
import Skills from './components/Skills'; import Skills from './components/Skills';
import Workflow from './components/Workflow'; import Workflow from './components/Workflow';
@@ -17,20 +16,14 @@ const AboutPage = () => {
}; };
return ( return (
<PageTransition> <>
<SEO <SEO
title={t('pages.about.seo.title')} title={t('pages.about.seo.title')}
description={t('pages.about.seo.description')} description={t('pages.about.seo.description')}
schema={generateSchema(t)} schema={generateSchema(t)}
/> />
<div className="relative min-h-screen bg-zinc-900"> <div className="relative min-h-screen">
{/* Decorative Elements */}
<div className="fixed inset-0 -z-10 overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-b from-zinc-900/50 to-zinc-900" />
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-full h-1/2 bg-gradient-to-b from-zinc-800/20 to-transparent blur-3xl" />
</div>
{/* Hero Section */} {/* Hero Section */}
<section className="min-h-screen relative"> <section className="min-h-screen relative">
<Hero /> <Hero />
@@ -43,7 +36,7 @@ const AboutPage = () => {
viewport={{ once: true }} viewport={{ once: true }}
variants={sectionVariants} variants={sectionVariants}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="py-24 bg-zinc-900/50" className="py-24 relative"
> >
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<motion.div <motion.div
@@ -70,7 +63,7 @@ const AboutPage = () => {
viewport={{ once: true }} viewport={{ once: true }}
variants={sectionVariants} variants={sectionVariants}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="py-24 bg-zinc-900" className="py-24 relative"
> >
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<motion.div <motion.div
@@ -93,7 +86,7 @@ const AboutPage = () => {
{/* Journey Section */} {/* Journey Section */}
<Journey /> <Journey />
</div> </div>
</PageTransition> </>
); );
}; };
+6 -6
View File
@@ -37,7 +37,7 @@ const Hero = () => {
</div> </div>
{/* Top Navigation */} {/* Top Navigation */}
<div className="absolute top-8 w-full px-8 flex justify-between items-center z-20"> <div className="absolute top-4 w-full px-8 flex justify-between items-center z-20">
<div className="flex gap-4"> <div className="flex gap-4">
<a <a
href="https://www.linkedin.com/in/damjan-savi%C4%87-720288127/" href="https://www.linkedin.com/in/damjan-savi%C4%87-720288127/"
@@ -68,13 +68,13 @@ const Hero = () => {
</div> </div>
{/* Main Content */} {/* Main Content */}
<div className="flex flex-col items-center justify-center min-h-screen px-4 relative z-10"> <div className="flex flex-col items-center justify-start min-h-screen px-4 pt-16 relative z-10">
{/* Profile Image */} {/* Profile Image */}
<motion.div <motion.div
initial={{ opacity: 0, scale: 0.9 }} initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="w-72 h-72 sm:w-96 sm:h-96 mb-8 rounded-full overflow-hidden border-2 border-zinc-800" className="w-48 h-48 sm:w-64 sm:h-64 mb-6 rounded-full overflow-hidden border-2 border-zinc-800"
> >
<img <img
src="/portrait.jpg" src="/portrait.jpg"
@@ -96,7 +96,7 @@ const Hero = () => {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.3 }} transition={{ duration: 0.5, delay: 0.3 }}
className="text-4xl font-bold mb-12 flex items-center" className="text-4xl font-bold mb-8 flex items-center"
> >
{t('pages.home.hero.name')}<span className="animate-pulse ml-1">|</span> {t('pages.home.hero.name')}<span className="animate-pulse ml-1">|</span>
</motion.h1> </motion.h1>
@@ -106,7 +106,7 @@ const Hero = () => {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.4 }} transition={{ duration: 0.5, delay: 0.4 }}
className="flex flex-col sm:flex-row gap-4 sm:gap-8 text-white text-sm z-20" className="flex flex-col sm:flex-row gap-3 sm:gap-6 text-white text-sm z-20"
> >
<button <button
onClick={() => scrollToSection('experience')} onClick={() => scrollToSection('experience')}
@@ -136,7 +136,7 @@ const Hero = () => {
</div> </div>
{/* Contact Button Bottom */} {/* Contact Button Bottom */}
<div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 z-20"> <div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 z-20">
<a <a
href="mailto:info@damjan-savic.com" href="mailto:info@damjan-savic.com"
className="text-white hover:text-zinc-300 transition-colors" className="text-white hover:text-zinc-300 transition-colors"
+3 -10
View File
@@ -1,7 +1,6 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import SEO from '../../components/SEO'; import SEO from '../../components/SEO';
import PageTransition from '../../components/PageTransition';
import PortfolioGrid from './components/PortfolioGrid'; import PortfolioGrid from './components/PortfolioGrid';
const PortfolioPage = () => { const PortfolioPage = () => {
@@ -17,19 +16,13 @@ const PortfolioPage = () => {
const sortOptions = Object.entries(t('portfolio.sort', { returnObjects: true })); const sortOptions = Object.entries(t('portfolio.sort', { returnObjects: true }));
return ( return (
<PageTransition> <>
<SEO <SEO
title={t('portfolio.seo.title')} title={t('portfolio.seo.title')}
description={t('portfolio.seo.description')} description={t('portfolio.seo.description')}
/> />
<div className="relative min-h-screen bg-zinc-900"> <div className="relative min-h-screen">
{/* Decorative Elements */}
<div className="fixed inset-0 -z-10 overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-b from-zinc-900/50 to-zinc-900" />
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-full h-1/2 bg-gradient-to-b from-zinc-800/20 to-transparent blur-3xl" />
</div>
{/* Main Content */} {/* Main Content */}
<motion.section <motion.section
initial="hidden" initial="hidden"
@@ -111,7 +104,7 @@ const PortfolioPage = () => {
</div> </div>
</motion.section> </motion.section>
</div> </div>
</PageTransition> </>
); );
}; };