diff --git a/bun.lock b/bun.lock index 8df91b8..e3fae79 100644 --- a/bun.lock +++ b/bun.lock @@ -11,12 +11,12 @@ "@iconify-json/logos": "^1.2.4", "@iconify-json/mdi": "^1.2.3", "@iconify-json/ph": "^1.2.2", - "@studio-freight/lenis": "^1.0.42", "@tailwindcss/postcss": "^4.0.12", "@tailwindcss/vite": "^4.0.12", "astro": "^5.4.2", "astro-icon": "^1.1.5", "framer-motion": "^12.4.7", + "lenis": "^1.2.3", "next": "15.1.7", "react": "^19.0.0", "react-dom": "^19.0.0", @@ -343,8 +343,6 @@ "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], - "@studio-freight/lenis": ["@studio-freight/lenis@1.0.42", "", {}, "sha512-HJAGf2DeM+BTvKzHv752z6Z7zy6bA643nZM7W88Ft9tnw2GsJSp6iJ+3cekjyMIWH+cloL2U9X82dKXgdU8kPg=="], - "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], @@ -983,6 +981,8 @@ "language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="], + "lenis": ["lenis@1.2.3", "", { "peerDependencies": { "@nuxt/kit": ">=3.0.0", "react": ">=17.0.0", "vue": ">=3.0.0" }, "optionalPeers": ["@nuxt/kit", "react", "vue"] }, "sha512-H3VUn62jvQPfyxGW2F0STJPhP1VzX5r05KtiZ0uxHYD9xtbfTvj2eX/Km26+x13zlaKfHacEe1DTC3ouFrxw+g=="], + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], "lightningcss": ["lightningcss@1.29.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.2", "lightningcss-darwin-x64": "1.29.2", "lightningcss-freebsd-x64": "1.29.2", "lightningcss-linux-arm-gnueabihf": "1.29.2", "lightningcss-linux-arm64-gnu": "1.29.2", "lightningcss-linux-arm64-musl": "1.29.2", "lightningcss-linux-x64-gnu": "1.29.2", "lightningcss-linux-x64-musl": "1.29.2", "lightningcss-win32-arm64-msvc": "1.29.2", "lightningcss-win32-x64-msvc": "1.29.2" } }, "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA=="], diff --git a/package.json b/package.json index cb18f96..3f72548 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,12 @@ "@iconify-json/logos": "^1.2.4", "@iconify-json/mdi": "^1.2.3", "@iconify-json/ph": "^1.2.2", - "@studio-freight/lenis": "^1.0.42", "@tailwindcss/postcss": "^4.0.12", "@tailwindcss/vite": "^4.0.12", "astro": "^5.4.2", "astro-icon": "^1.1.5", "framer-motion": "^12.4.7", + "lenis": "^1.2.3", "next": "15.1.7", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..832477b --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,49 @@ +{ + "name": "Jan-Marlon Leibl | Fullstack Developer", + "short_name": "JL Portfolio", + "description": "Personal website and portfolio of Jan-Marlon Leibl, Fullstack Developer", + "start_url": "/", + "display": "standalone", + "background_color": "#0A0A0A", + "theme_color": "#0A0A0A", + "orientation": "portrait-primary", + "screenshots": [ + { + "src": "/screenshots/mobile.png", + "sizes": "375x812", + "type": "image/png", + "form_factor": "narrow", + "label": "Jan-Marlon Leibl's Portfolio (Mobile)" + }, + { + "src": "/screenshots/desktop.png", + "sizes": "1280x800", + "type": "image/png", + "form_factor": "wide", + "label": "Jan-Marlon Leibl's Portfolio (Desktop)" + } + ], + "shortcuts": [ + { + "name": "About Me", + "short_name": "About", + "description": "Learn more about Jan-Marlon Leibl", + "url": "/#about", + "icons": [{ "src": "/icons/about-icon.png", "sizes": "192x192" }] + }, + { + "name": "My Work", + "short_name": "Work", + "description": "View Jan-Marlon's portfolio projects", + "url": "/#work", + "icons": [{ "src": "/icons/work-icon.png", "sizes": "192x192" }] + }, + { + "name": "Contact", + "short_name": "Contact", + "description": "Get in touch with Jan-Marlon", + "url": "/#contact", + "icons": [{ "src": "/icons/contact-icon.png", "sizes": "192x192" }] + } + ] +} \ No newline at end of file diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..8f34ca8 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,7 @@ +# www.robotstxt.org/ +# Allow crawling of all content +User-agent: * +Allow: / + +# Optimize crawling +Crawl-delay: 10 \ No newline at end of file diff --git a/public/service-worker.js b/public/service-worker.js new file mode 100644 index 0000000..5006803 --- /dev/null +++ b/public/service-worker.js @@ -0,0 +1,122 @@ +const CACHE_NAME = 'jleibl-portfolio-v1'; + +const PRECACHE_ASSETS = [ + '/', + '/index.html', + '/manifest.json', +]; + +const STYLE_ASSETS = [ + '/styles/global.css', + '/styles/theme.css' +]; + +const IMAGE_ASSETS = [ + '/images/profile-image.jpg' +]; + +const ALL_ASSETS = [ + ...PRECACHE_ASSETS, + ...STYLE_ASSETS, + ...IMAGE_ASSETS +]; + +self.addEventListener('install', event => { + self.skipWaiting(); + event.waitUntil( + caches.open(CACHE_NAME) + .then(cache => { + console.log('Opened cache'); + return cache.addAll(PRECACHE_ASSETS); + }) + ); +}); + +self.addEventListener('activate', event => { + event.waitUntil( + caches.keys().then(cacheNames => { + return Promise.all( + cacheNames.map(cacheName => { + if (cacheName !== CACHE_NAME) { + console.log('Deleting old cache:', cacheName); + return caches.delete(cacheName); + } + }) + ); + }) + ); + + return self.clients.claim(); +}); + +function shouldCache(url) { + if (url.origin === self.location.origin) { + if (url.pathname.endsWith('.html') || + url.pathname.endsWith('.css') || + url.pathname.endsWith('.js') || + url.pathname.endsWith('.jpg') || + url.pathname.endsWith('.jpeg') || + url.pathname.endsWith('.png') || + url.pathname.endsWith('.svg') || + url.pathname.endsWith('.webp') || + url.pathname.endsWith('.woff') || + url.pathname.endsWith('.woff2') || + url.pathname.endsWith('.json')) { + return true; + } + + if (url.pathname === '/') { + return true; + } + } + + return false; +} + +self.addEventListener('fetch', event => { + if (event.request.mode === 'navigate' || shouldCache(new URL(event.request.url))) { + event.respondWith( + caches.match(event.request) + .then(response => { + if (response) { + return response; + } + + const fetchRequest = event.request.clone(); + + return fetch(fetchRequest) + .then(response => { + if (!response || response.status !== 200 || response.type !== 'basic') { + return response; + } + + const responseToCache = response.clone(); + + if (shouldCache(new URL(event.request.url))) { + caches.open(CACHE_NAME) + .then(cache => { + cache.put(event.request, responseToCache); + }); + } + + return response; + }) + .catch(error => { + console.log('Fetch failed:', error); + + if (event.request.mode === 'navigate') { + return caches.match('/'); + } + + return null; + }); + }) + ); + } +}); + +self.addEventListener('message', event => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); \ No newline at end of file diff --git a/src/components/layout/Header.astro b/src/components/layout/Header.astro index 30ca759..314ac6f 100644 --- a/src/components/layout/Header.astro +++ b/src/components/layout/Header.astro @@ -6,7 +6,7 @@ import MobileMenu from './MobileMenu.astro'; class="fixed top-0 left-0 right-0 z-40 nav-glass backdrop-blur-lg theme-transition" role="banner" > -
+