Update site content and structure, including localization adjustments for addresses, removal of unused files, and enhancements to the layout and styling for better user experience.
This commit is contained in:
File diff suppressed because one or more lines are too long
4
dist/404.html
vendored
4
dist/404.html
vendored
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
import{i as e}from"./theme.BcH1Etvo.js";document.addEventListener("DOMContentLoaded",()=>{e(),function(){if("undefined"==typeof window)return;const e=new IntersectionObserver(e=>{e.forEach(e=>{e.isIntersecting&&e.target.classList.add("in-view")})},{threshold:.1,rootMargin:"0px 0px -50px 0px"});document.querySelectorAll(".animate-on-scroll").forEach(o=>e.observe(o))}(),function(){"PerformanceObserver"in window&&(new PerformanceObserver(e=>{const o=e.getEntries(),n=o[o.length-1];console.log("LCP:",n.startTime),n.startTime<2500?console.log("✅ LCP is good"):console.log("⚠️ LCP needs improvement")}).observe({entryTypes:["largest-contentful-paint"]}),new PerformanceObserver(e=>{e.getEntries().forEach(e=>{const o=e;console.log("FID:",o.processingStart-o.startTime),o.processingStart-o.startTime<100?console.log("✅ FID is good"):console.log("⚠️ FID needs improvement")})}).observe({entryTypes:["first-input"]}),new PerformanceObserver(e=>{let o=0;e.getEntries().forEach(e=>{e.hadRecentInput||(o+=e.value)}),console.log("CLS:",o),o<.1?console.log("✅ CLS is good"):console.log("⚠️ CLS needs improvement")}).observe({entryTypes:["layout-shift"]}));window.addEventListener("load",()=>{const e=performance.now();console.log("Page load time:",e);const o=performance.getEntriesByType("navigation")[0];o&&(console.log("DOM Content Loaded:",o.domContentLoadedEventEnd-o.domContentLoadedEventStart),console.log("Load Complete:",o.loadEventEnd-o.loadEventStart))})}(),"serviceWorker"in navigator&&navigator.serviceWorker.register("/sw.js").then(e=>{console.log("SW registered: ",e)}).catch(e=>{console.log("SW registration failed: ",e)})});
|
|
1
dist/_astro/BaseLayout.astro_astro_type_script_index_0_lang.C5sG_tfp.js
vendored
Normal file
1
dist/_astro/BaseLayout.astro_astro_type_script_index_0_lang.C5sG_tfp.js
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import{i as e}from"./theme.BcH1Etvo.js";document.addEventListener("DOMContentLoaded",()=>{e(),function(){if("undefined"==typeof window)return;const e=new IntersectionObserver(e=>{e.forEach(e=>{e.isIntersecting&&e.target.classList.add("in-view")})},{threshold:.1,rootMargin:"0px 0px -50px 0px"});document.querySelectorAll(".animate-on-scroll").forEach(t=>e.observe(t))}(),function(){"PerformanceObserver"in window&&(new PerformanceObserver(e=>{const t=e.getEntries(),o=t[t.length-1];console.log("LCP:",o.startTime),o.startTime<2500?console.log("✅ LCP is good"):console.log("⚠️ LCP needs improvement")}).observe({entryTypes:["largest-contentful-paint"]}),new PerformanceObserver(e=>{e.getEntries().forEach(e=>{const t=e;console.log("FID:",t.processingStart-t.startTime),t.processingStart-t.startTime<100?console.log("✅ FID is good"):console.log("⚠️ FID needs improvement")})}).observe({entryTypes:["first-input"]}),new PerformanceObserver(e=>{let t=0;e.getEntries().forEach(e=>{e.hadRecentInput||(t+=e.value)}),console.log("CLS:",t),t<.1?console.log("✅ CLS is good"):console.log("⚠️ CLS needs improvement")}).observe({entryTypes:["layout-shift"]}));window.addEventListener("load",()=>{const e=performance.now();console.log("Page load time:",e);const t=performance.getEntriesByType("navigation")[0];t&&(console.log("DOM Content Loaded:",t.domContentLoadedEventEnd-t.domContentLoadedEventStart),console.log("Load Complete:",t.loadEventEnd-t.loadEventStart))})}(),function(){const e=new Set;function t(t){if(!e.has(t))try{const o=document.createElement("link");o.rel="prefetch",o.href=t,o.as="document",document.head.appendChild(o),e.add(t),console.log(`Preloaded: ${t}`)}catch(o){console.warn(`Failed to preload ${t}:`,o)}}function o(e){const o=e.target.closest("a");if(!o)return;const n=o.getAttribute("href");if(!n)return;if(n.startsWith("http")||n.startsWith("mailto:")||n.startsWith("tel:")||n.startsWith("#"))return;let r;try{r=new URL(n,window.location.origin).href}catch{return}r!==window.location.href&&setTimeout(()=>{t(r)},100)}function n(){document.addEventListener("mouseenter",o,{capture:!0,passive:!0}),document.addEventListener("touchstart",o,{capture:!0,passive:!0})}n(),new MutationObserver(e=>{e.forEach(e=>{"childList"===e.type&&e.addedNodes.length>0&&n()})}).observe(document.body,{childList:!0,subtree:!0})}(),"serviceWorker"in navigator&&navigator.serviceWorker.register("/sw.js").then(e=>{console.log("SW registered: ",e)}).catch(e=>{console.log("SW registration failed: ",e)})});
|
1
dist/_astro/about.Ct3MDOu0.css
vendored
1
dist/_astro/about.Ct3MDOu0.css
vendored
File diff suppressed because one or more lines are too long
1
dist/_astro/about.DJBbvL2M.css
vendored
Normal file
1
dist/_astro/about.DJBbvL2M.css
vendored
Normal file
File diff suppressed because one or more lines are too long
24
dist/_headers
vendored
24
dist/_headers
vendored
@@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
X-Frame-Options: DENY
|
|
||||||
X-Content-Type-Options: nosniff
|
|
||||||
Referrer-Policy: strict-origin-when-cross-origin
|
|
||||||
Permissions-Policy: camera=(), microphone=(), geolocation=()
|
|
||||||
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://fonts.googleapis.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://support.tiber365.it; frame-ancestors 'none';
|
|
||||||
|
|
||||||
/*
|
|
||||||
Cache-Control: public, max-age=31536000, immutable
|
|
||||||
|
|
||||||
/sw.js
|
|
||||||
Cache-Control: public, max-age=0, must-revalidate
|
|
||||||
|
|
||||||
/manifest.json
|
|
||||||
Cache-Control: public, max-age=31536000, immutable
|
|
||||||
|
|
||||||
/favicon.svg
|
|
||||||
Cache-Control: public, max-age=31536000, immutable
|
|
||||||
|
|
||||||
/images/*
|
|
||||||
Cache-Control: public, max-age=31536000, immutable
|
|
||||||
|
|
||||||
/sitemap.xml
|
|
||||||
Cache-Control: public, max-age=3600
|
|
48
dist/_redirects
vendored
48
dist/_redirects
vendored
@@ -1,7 +1,41 @@
|
|||||||
# Redirect language routes to root
|
# Security headers for all pages
|
||||||
/en/* / 301
|
/*
|
||||||
/nl/* / 301
|
X-Frame-Options: DENY
|
||||||
/it/* / 301
|
X-Content-Type-Options: nosniff
|
||||||
/en / 301
|
Referrer-Policy: strict-origin-when-cross-origin
|
||||||
/nl / 301
|
Permissions-Policy: camera=(), microphone=(), geolocation=()
|
||||||
/it / 301
|
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://fonts.googleapis.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://support.tiber365.it; frame-ancestors 'none';
|
||||||
|
|
||||||
|
# Cache control for static assets
|
||||||
|
/*.js
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/*.css
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/*.svg
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/*.png
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/*.jpg
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/*.ico
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/sw.js
|
||||||
|
Cache-Control: public, max-age=0, must-revalidate
|
||||||
|
|
||||||
|
/manifest.json
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/favicon.svg
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/images/*
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/sitemap.xml
|
||||||
|
Cache-Control: public, max-age=3600
|
5
dist/blog/index.html
vendored
5
dist/blog/index.html
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
dist/de/404/index.html
vendored
4
dist/de/404/index.html
vendored
File diff suppressed because one or more lines are too long
6
dist/de/about/index.html
vendored
6
dist/de/about/index.html
vendored
File diff suppressed because one or more lines are too long
6
dist/de/blog/index.html
vendored
6
dist/de/blog/index.html
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
dist/de/contact/index.html
vendored
6
dist/de/contact/index.html
vendored
File diff suppressed because one or more lines are too long
8
dist/de/index.html
vendored
8
dist/de/index.html
vendored
File diff suppressed because one or more lines are too long
4
dist/de/privacy/index.html
vendored
4
dist/de/privacy/index.html
vendored
File diff suppressed because one or more lines are too long
4
dist/de/terms/index.html
vendored
4
dist/de/terms/index.html
vendored
File diff suppressed because one or more lines are too long
4
dist/en/404/index.html
vendored
4
dist/en/404/index.html
vendored
File diff suppressed because one or more lines are too long
6
dist/en/about/index.html
vendored
6
dist/en/about/index.html
vendored
File diff suppressed because one or more lines are too long
6
dist/en/blog/index.html
vendored
6
dist/en/blog/index.html
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
dist/en/contact/index.html
vendored
6
dist/en/contact/index.html
vendored
File diff suppressed because one or more lines are too long
8
dist/en/index.html
vendored
8
dist/en/index.html
vendored
File diff suppressed because one or more lines are too long
3
dist/en/privacy/index.html
vendored
3
dist/en/privacy/index.html
vendored
File diff suppressed because one or more lines are too long
4
dist/en/terms/index.html
vendored
4
dist/en/terms/index.html
vendored
File diff suppressed because one or more lines are too long
4
dist/fr/404/index.html
vendored
4
dist/fr/404/index.html
vendored
File diff suppressed because one or more lines are too long
6
dist/fr/about/index.html
vendored
6
dist/fr/about/index.html
vendored
File diff suppressed because one or more lines are too long
6
dist/fr/blog/index.html
vendored
6
dist/fr/blog/index.html
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
dist/fr/contact/index.html
vendored
6
dist/fr/contact/index.html
vendored
File diff suppressed because one or more lines are too long
8
dist/fr/index.html
vendored
8
dist/fr/index.html
vendored
File diff suppressed because one or more lines are too long
4
dist/fr/privacy/index.html
vendored
4
dist/fr/privacy/index.html
vendored
File diff suppressed because one or more lines are too long
4
dist/fr/terms/index.html
vendored
4
dist/fr/terms/index.html
vendored
File diff suppressed because one or more lines are too long
25
dist/index.html
vendored
25
dist/index.html
vendored
File diff suppressed because one or more lines are too long
4
dist/nl/404/index.html
vendored
4
dist/nl/404/index.html
vendored
File diff suppressed because one or more lines are too long
6
dist/nl/about/index.html
vendored
6
dist/nl/about/index.html
vendored
File diff suppressed because one or more lines are too long
6
dist/nl/blog/index.html
vendored
6
dist/nl/blog/index.html
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
dist/nl/contact/index.html
vendored
6
dist/nl/contact/index.html
vendored
File diff suppressed because one or more lines are too long
8
dist/nl/index.html
vendored
8
dist/nl/index.html
vendored
File diff suppressed because one or more lines are too long
4
dist/nl/privacy/index.html
vendored
4
dist/nl/privacy/index.html
vendored
File diff suppressed because one or more lines are too long
4
dist/nl/terms/index.html
vendored
4
dist/nl/terms/index.html
vendored
File diff suppressed because one or more lines are too long
6
dist/services/index.html
vendored
6
dist/services/index.html
vendored
File diff suppressed because one or more lines are too long
2
node_modules/.astro/data-store.json
generated
vendored
2
node_modules/.astro/data-store.json
generated
vendored
File diff suppressed because one or more lines are too long
6
node_modules/.vite/deps/_metadata.json
generated
vendored
6
node_modules/.vite/deps/_metadata.json
generated
vendored
@@ -7,19 +7,19 @@
|
|||||||
"astro > cssesc": {
|
"astro > cssesc": {
|
||||||
"src": "../../cssesc/cssesc.js",
|
"src": "../../cssesc/cssesc.js",
|
||||||
"file": "astro___cssesc.js",
|
"file": "astro___cssesc.js",
|
||||||
"fileHash": "adbc09e7",
|
"fileHash": "3fe2d173",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
"astro > aria-query": {
|
"astro > aria-query": {
|
||||||
"src": "../../aria-query/lib/index.js",
|
"src": "../../aria-query/lib/index.js",
|
||||||
"file": "astro___aria-query.js",
|
"file": "astro___aria-query.js",
|
||||||
"fileHash": "dec3cd63",
|
"fileHash": "5253c813",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
},
|
},
|
||||||
"astro > axobject-query": {
|
"astro > axobject-query": {
|
||||||
"src": "../../axobject-query/lib/index.js",
|
"src": "../../axobject-query/lib/index.js",
|
||||||
"file": "astro___axobject-query.js",
|
"file": "astro___axobject-query.js",
|
||||||
"fileHash": "a84c99eb",
|
"fileHash": "355d5bcf",
|
||||||
"needsInterop": true
|
"needsInterop": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
X-Frame-Options: DENY
|
|
||||||
X-Content-Type-Options: nosniff
|
|
||||||
Referrer-Policy: strict-origin-when-cross-origin
|
|
||||||
Permissions-Policy: camera=(), microphone=(), geolocation=()
|
|
||||||
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://fonts.googleapis.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://support.tiber365.it; frame-ancestors 'none';
|
|
||||||
|
|
||||||
/*
|
|
||||||
Cache-Control: public, max-age=31536000, immutable
|
|
||||||
|
|
||||||
/sw.js
|
|
||||||
Cache-Control: public, max-age=0, must-revalidate
|
|
||||||
|
|
||||||
/manifest.json
|
|
||||||
Cache-Control: public, max-age=31536000, immutable
|
|
||||||
|
|
||||||
/favicon.svg
|
|
||||||
Cache-Control: public, max-age=31536000, immutable
|
|
||||||
|
|
||||||
/images/*
|
|
||||||
Cache-Control: public, max-age=31536000, immutable
|
|
||||||
|
|
||||||
/sitemap.xml
|
|
||||||
Cache-Control: public, max-age=3600
|
|
@@ -1,7 +1,41 @@
|
|||||||
# Redirect language routes to root
|
# Security headers for all pages
|
||||||
/en/* / 301
|
/*
|
||||||
/nl/* / 301
|
X-Frame-Options: DENY
|
||||||
/it/* / 301
|
X-Content-Type-Options: nosniff
|
||||||
/en / 301
|
Referrer-Policy: strict-origin-when-cross-origin
|
||||||
/nl / 301
|
Permissions-Policy: camera=(), microphone=(), geolocation=()
|
||||||
/it / 301
|
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://fonts.googleapis.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://support.tiber365.it; frame-ancestors 'none';
|
||||||
|
|
||||||
|
# Cache control for static assets
|
||||||
|
/*.js
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/*.css
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/*.svg
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/*.png
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/*.jpg
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/*.ico
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/sw.js
|
||||||
|
Cache-Control: public, max-age=0, must-revalidate
|
||||||
|
|
||||||
|
/manifest.json
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/favicon.svg
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/images/*
|
||||||
|
Cache-Control: public, max-age=31536000, immutable
|
||||||
|
|
||||||
|
/sitemap.xml
|
||||||
|
Cache-Control: public, max-age=3600
|
@@ -19,7 +19,7 @@
|
|||||||
"hero": {
|
"hero": {
|
||||||
"title": "Professionelle IT-Services für Ihr Unternehmen",
|
"title": "Professionelle IT-Services für Ihr Unternehmen",
|
||||||
"subtitle": "Wir unterstützen Freelancer und kleine Unternehmen mit zuverlässigem Microsoft 365 Support, Netzwerklösungen, Webhosting und maßgeschneiderten IT-Projekten.",
|
"subtitle": "Wir unterstützen Freelancer und kleine Unternehmen mit zuverlässigem Microsoft 365 Support, Netzwerklösungen, Webhosting und maßgeschneiderten IT-Projekten.",
|
||||||
"trusted": "Vertraut von Unternehmen in ganz Italien",
|
"trusted": "Vertraut von Unternehmen in ganz den Niederlanden",
|
||||||
"cta": {
|
"cta": {
|
||||||
"primary": "Heute starten",
|
"primary": "Heute starten",
|
||||||
"secondary": "Unsere Services ansehen"
|
"secondary": "Unsere Services ansehen"
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"email": "info@tiber365.it",
|
"email": "info@tiber365.it",
|
||||||
"phone": "+39 123 456 7890",
|
"phone": "+39 123 456 7890",
|
||||||
"address": "Rom, Italien"
|
"address": "Amsterdam, Niederlande"
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
"hero": {
|
"hero": {
|
||||||
"title": "Professional IT Services for Your Business",
|
"title": "Professional IT Services for Your Business",
|
||||||
"subtitle": "Empowering freelancers and small businesses with reliable Microsoft 365 support, networking solutions, web hosting, and custom IT projects.",
|
"subtitle": "Empowering freelancers and small businesses with reliable Microsoft 365 support, networking solutions, web hosting, and custom IT projects.",
|
||||||
"trusted": "Trusted by businesses across Italy",
|
"trusted": "Trusted by businesses across the Netherlands",
|
||||||
"cta": {
|
"cta": {
|
||||||
"primary": "Get Started Today",
|
"primary": "Get Started Today",
|
||||||
"secondary": "View Our Services"
|
"secondary": "View Our Services"
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"email": "info@tiber365.it",
|
"email": "info@tiber365.it",
|
||||||
"phone": "+39 123 456 7890",
|
"phone": "+39 123 456 7890",
|
||||||
"address": "Rome, Italy"
|
"address": "Amsterdam, Netherlands"
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
"hero": {
|
"hero": {
|
||||||
"title": "Services IT Professionnels pour Votre Entreprise",
|
"title": "Services IT Professionnels pour Votre Entreprise",
|
||||||
"subtitle": "Nous aidons les freelances et petites entreprises avec un support Microsoft 365 fiable, des solutions réseau, de l'hébergement web et des projets IT personnalisés.",
|
"subtitle": "Nous aidons les freelances et petites entreprises avec un support Microsoft 365 fiable, des solutions réseau, de l'hébergement web et des projets IT personnalisés.",
|
||||||
"trusted": "Fait confiance par les entreprises à travers l'Italie",
|
"trusted": "Fait confiance par les entreprises à travers les Pays-Bas",
|
||||||
"cta": {
|
"cta": {
|
||||||
"primary": "Commencer Aujourd'hui",
|
"primary": "Commencer Aujourd'hui",
|
||||||
"secondary": "Voir Nos Services"
|
"secondary": "Voir Nos Services"
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"email": "info@tiber365.it",
|
"email": "info@tiber365.it",
|
||||||
"phone": "+39 123 456 7890",
|
"phone": "+39 123 456 7890",
|
||||||
"address": "Rome, Italie"
|
"address": "Amsterdam, Pays-Bas"
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
"hero": {
|
"hero": {
|
||||||
"title": "Professionele IT Services voor Uw Bedrijf",
|
"title": "Professionele IT Services voor Uw Bedrijf",
|
||||||
"subtitle": "Ondersteuning van freelancers en kleine bedrijven met betrouwbare Microsoft 365 ondersteuning, netwerkoplossingen, webhosting en aangepaste IT-projecten.",
|
"subtitle": "Ondersteuning van freelancers en kleine bedrijven met betrouwbare Microsoft 365 ondersteuning, netwerkoplossingen, webhosting en aangepaste IT-projecten.",
|
||||||
"trusted": "Vertrouwd door bedrijven in heel Italië",
|
"trusted": "Vertrouwd door bedrijven in heel Nederland",
|
||||||
"cta": {
|
"cta": {
|
||||||
"primary": "Begin Vandaag",
|
"primary": "Begin Vandaag",
|
||||||
"secondary": "Bekijk Onze Diensten"
|
"secondary": "Bekijk Onze Diensten"
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"email": "info@tiber365.it",
|
"email": "info@tiber365.it",
|
||||||
"phone": "+39 123 456 7890",
|
"phone": "+39 123 456 7890",
|
||||||
"address": "Rome, Italië"
|
"address": "Amsterdam, Nederland"
|
||||||
},
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"name": "Naam",
|
"name": "Naam",
|
||||||
|
@@ -65,11 +65,11 @@ const structuredData = {
|
|||||||
},
|
},
|
||||||
"address": {
|
"address": {
|
||||||
"@type": "PostalAddress",
|
"@type": "PostalAddress",
|
||||||
"addressCountry": "IT"
|
"addressCountry": "NL"
|
||||||
},
|
},
|
||||||
"serviceArea": {
|
"serviceArea": {
|
||||||
"@type": "Country",
|
"@type": "Country",
|
||||||
"name": "Italy"
|
"name": "Netherlands"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
---
|
---
|
||||||
@@ -163,11 +163,13 @@ const structuredData = {
|
|||||||
import { initScrollAnimations } from '../utils/animations';
|
import { initScrollAnimations } from '../utils/animations';
|
||||||
import { initTheme } from '../utils/theme';
|
import { initTheme } from '../utils/theme';
|
||||||
import { initPerformanceMonitoring } from '../utils/performance';
|
import { initPerformanceMonitoring } from '../utils/performance';
|
||||||
|
import { initPreloading } from '../utils/preload';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
initTheme();
|
initTheme();
|
||||||
initScrollAnimations();
|
initScrollAnimations();
|
||||||
initPerformanceMonitoring();
|
initPerformanceMonitoring();
|
||||||
|
initPreloading();
|
||||||
|
|
||||||
// Register service worker
|
// Register service worker
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
|
@@ -136,7 +136,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"email": "info@tiber365.it",
|
"email": "info@tiber365.it",
|
||||||
"phone": "+39 123 456 7890",
|
"phone": "+39 123 456 7890",
|
||||||
"address": "Italy"
|
"address": "Netherlands"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cta": {
|
"cta": {
|
||||||
|
@@ -41,7 +41,7 @@ const pageStructuredData = {
|
|||||||
},
|
},
|
||||||
"serviceArea": {
|
"serviceArea": {
|
||||||
"@type": "Country",
|
"@type": "Country",
|
||||||
"name": "Italy"
|
"name": "Netherlands"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -1,28 +1,70 @@
|
|||||||
---
|
---
|
||||||
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
|
import Header from '../../components/Header.astro';
|
||||||
|
import Footer from '../../components/Footer.astro';
|
||||||
|
import { getBlogPostBySlug, getBlogPosts } from '../../utils/directus';
|
||||||
|
import { useTranslations } from '../../utils/i18n';
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
// Get all blog posts to create redirects
|
|
||||||
const { getBlogPosts } = await import('../../utils/directus');
|
|
||||||
const posts = await getBlogPosts();
|
const posts = await getBlogPosts();
|
||||||
|
|
||||||
return posts
|
return posts
|
||||||
.filter((post) => typeof post.slug === 'string' && post.slug.trim() !== '')
|
.filter((post) => typeof post.slug === 'string' && post.slug.trim() !== '')
|
||||||
.map((post) => ({
|
.map((post) => ({
|
||||||
params: { slug: post.slug },
|
params: { slug: post.slug },
|
||||||
props: { slug: post.slug },
|
props: { post },
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { slug } = Astro.props;
|
const { post } = Astro.props;
|
||||||
|
const t = await useTranslations('en');
|
||||||
---
|
---
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
<BaseLayout title={`${post.title} | ${t('blog.title')}`} description={post.content.replace(/<[^>]+>/g, '').substring(0, 160)}>
|
||||||
<head>
|
<Header />
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Redirecting...</title>
|
<main class="flex-1">
|
||||||
<meta http-equiv="refresh" content="0;url=/en/blog/{slug}">
|
<!-- Hero Section -->
|
||||||
<link rel="canonical" href="/en/blog/{slug}">
|
<section class="bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800 py-20">
|
||||||
</head>
|
<div class="container-custom">
|
||||||
<body>
|
<div class="max-w-4xl mx-auto">
|
||||||
<p>Redirecting to <a href="/en/blog/{slug}">blog post</a>...</p>
|
<nav class="mb-6">
|
||||||
</body>
|
<a href="/en/blog" class="text-primary hover:underline flex items-center">
|
||||||
</html>
|
← {t('blog.backToBlog')}
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
<header class="text-center">
|
||||||
|
<h1 class="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mb-6">
|
||||||
|
{post.title}
|
||||||
|
</h1>
|
||||||
|
<p class="text-xl text-gray-600 dark:text-gray-300">
|
||||||
|
{new Date(post.date_created).toLocaleDateString('en-US', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Article Content -->
|
||||||
|
<section class="py-16">
|
||||||
|
<div class="container-custom">
|
||||||
|
<article class="max-w-4xl mx-auto">
|
||||||
|
<div class="prose prose-lg dark:prose-invert max-w-none prose-headings:text-gray-900 dark:prose-headings:text-white prose-p:text-gray-700 dark:prose-p:text-gray-300 prose-a:text-primary prose-a:no-underline hover:prose-a:underline prose-strong:text-gray-900 dark:prose-strong:text-white prose-code:text-primary prose-code:bg-gray-100 dark:prose-code:bg-gray-800 prose-code:px-1 prose-code:py-0.5 prose-code:rounded" set:html={post.content}></div>
|
||||||
|
|
||||||
|
<!-- Back to Blog Button -->
|
||||||
|
<div class="mt-12 pt-8 border-t border-border text-center">
|
||||||
|
<a href="/en/blog" class="btn btn-primary">
|
||||||
|
← {t('blog.backToBlog')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</BaseLayout>
|
@@ -1,15 +1,97 @@
|
|||||||
---
|
---
|
||||||
// Static redirect to English blog
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
|
import Header from '../../components/Header.astro';
|
||||||
|
import Footer from '../../components/Footer.astro';
|
||||||
|
import { getBlogPosts } from '../../utils/directus';
|
||||||
|
import { useTranslations } from '../../utils/i18n';
|
||||||
|
|
||||||
|
const t = await useTranslations('en');
|
||||||
|
|
||||||
|
let posts = [];
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
function stripHtml(html) {
|
||||||
|
return html.replace(/<[^>]+>/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
posts = await getBlogPosts();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error in blog page:', e);
|
||||||
|
error = e.message;
|
||||||
|
}
|
||||||
---
|
---
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
<BaseLayout title={t('blog.title')} description={t('blog.description')}>
|
||||||
<head>
|
<Header />
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Redirecting...</title>
|
<main class="flex-1">
|
||||||
<meta http-equiv="refresh" content="0;url=/en/blog">
|
<!-- Hero Section -->
|
||||||
<link rel="canonical" href="/en/blog">
|
<section class="bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800 py-20">
|
||||||
</head>
|
<div class="container-custom">
|
||||||
<body>
|
<div class="text-center max-w-3xl mx-auto">
|
||||||
<p>Redirecting to <a href="/en/blog">blog</a>...</p>
|
<h1 class="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mb-6">
|
||||||
</body>
|
{t('blog.title')}
|
||||||
</html>
|
</h1>
|
||||||
|
<p class="text-xl text-gray-600 dark:text-gray-300">
|
||||||
|
{t('blog.description')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Blog Posts Section -->
|
||||||
|
<section class="py-16">
|
||||||
|
<div class="container-custom">
|
||||||
|
{error ? (
|
||||||
|
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-6 mb-8">
|
||||||
|
<p class="text-red-800 dark:text-red-200">
|
||||||
|
{t('blog.error')}
|
||||||
|
</p>
|
||||||
|
{import.meta.env.DEV && (
|
||||||
|
<pre class="mt-2 text-sm text-red-600 dark:text-red-400">{error}</pre>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : posts.length === 0 ? (
|
||||||
|
<div class="text-center py-12">
|
||||||
|
<p class="text-gray-600 dark:text-gray-400 text-lg">
|
||||||
|
{t('blog.noPosts')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
{posts.map((post) => (
|
||||||
|
<article class="card hover:shadow-lg transition-shadow duration-300 group">
|
||||||
|
<div class="flex flex-col h-full">
|
||||||
|
<div class="flex-1">
|
||||||
|
<h2 class="text-xl font-bold mb-3 group-hover:text-primary transition-colors">
|
||||||
|
<a href={`/en/blog/${post.slug}`} class="hover:underline">
|
||||||
|
{post.title}
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
<p class="text-muted-foreground text-sm mb-4">
|
||||||
|
{new Date(post.date_created).toLocaleDateString('en-US')}
|
||||||
|
</p>
|
||||||
|
<div class="text-foreground text-sm mb-6 line-clamp-4">
|
||||||
|
{stripHtml(post.content).substring(0, 200)}{stripHtml(post.content).length > 200 ? '...' : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pt-4 border-t border-border">
|
||||||
|
<a
|
||||||
|
href={`/en/blog/${post.slug}`}
|
||||||
|
class="inline-flex items-center text-primary font-medium hover:underline transition-colors"
|
||||||
|
>
|
||||||
|
{t('blog.readMore')} →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</BaseLayout>
|
@@ -1,4 +1,25 @@
|
|||||||
---
|
---
|
||||||
// Redirect to English version
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
return Astro.redirect('/en/');
|
import Header from '../components/Header.astro';
|
||||||
---
|
import Footer from '../components/Footer.astro';
|
||||||
|
import Hero from '../components/Hero.astro';
|
||||||
|
import Services from '../components/Services.astro';
|
||||||
|
import Testimonials from '../components/Testimonials.astro';
|
||||||
|
import CTA from '../components/CTA.astro';
|
||||||
|
import { useTranslations } from '../utils/i18n';
|
||||||
|
|
||||||
|
const t = await useTranslations('en');
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title={t('home.title')} description={t('home.description')}>
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<main class="flex-1">
|
||||||
|
<Hero />
|
||||||
|
<Services />
|
||||||
|
<Testimonials />
|
||||||
|
<CTA />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</BaseLayout>
|
@@ -2,13 +2,14 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* CSS Variables for theming */
|
||||||
:root {
|
:root {
|
||||||
|
--color-background: 255 255 255;
|
||||||
|
--color-foreground: 15 23 42;
|
||||||
--color-primary: 59 130 246;
|
--color-primary: 59 130 246;
|
||||||
--color-primary-foreground: 255 255 255;
|
--color-primary-foreground: 255 255 255;
|
||||||
--color-secondary: 100 116 139;
|
--color-secondary: 100 116 139;
|
||||||
--color-secondary-foreground: 255 255 255;
|
--color-secondary-foreground: 255 255 255;
|
||||||
--color-background: 255 255 255;
|
|
||||||
--color-foreground: 15 23 42;
|
|
||||||
--color-muted: 248 250 252;
|
--color-muted: 248 250 252;
|
||||||
--color-muted-foreground: 100 116 139;
|
--color-muted-foreground: 100 116 139;
|
||||||
--color-border: 226 232 240;
|
--color-border: 226 232 240;
|
||||||
@@ -41,7 +42,7 @@
|
|||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
@apply font-display;
|
@apply font-display;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Smooth scrolling */
|
/* Smooth scrolling */
|
||||||
html {
|
html {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
@@ -65,7 +66,7 @@
|
|||||||
background-color: rgb(var(--color-secondary));
|
background-color: rgb(var(--color-secondary));
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background-color: rgb(var(--color-secondary) / 0.8);
|
background-color: rgb(var(--color-secondary) / 0.8);
|
||||||
}
|
}
|
||||||
@@ -75,34 +76,31 @@
|
|||||||
.btn {
|
.btn {
|
||||||
@apply inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50;
|
@apply inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
@apply btn;
|
|
||||||
background-color: rgb(var(--color-primary));
|
background-color: rgb(var(--color-primary));
|
||||||
color: rgb(var(--color-primary-foreground));
|
color: rgb(var(--color-primary-foreground));
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover {
|
.btn-primary:hover {
|
||||||
background-color: rgb(var(--color-primary) / 0.9);
|
background-color: rgb(var(--color-primary) / 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
@apply btn;
|
|
||||||
background-color: rgb(var(--color-secondary));
|
background-color: rgb(var(--color-secondary));
|
||||||
color: rgb(var(--color-secondary-foreground));
|
color: rgb(var(--color-secondary-foreground));
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary:hover {
|
.btn-secondary:hover {
|
||||||
background-color: rgb(var(--color-secondary) / 0.8);
|
background-color: rgb(var(--color-secondary) / 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline {
|
.btn-outline {
|
||||||
@apply btn;
|
|
||||||
border: 1px solid rgb(var(--color-border));
|
border: 1px solid rgb(var(--color-border));
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: rgb(var(--color-foreground));
|
color: rgb(var(--color-foreground));
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline:hover {
|
.btn-outline:hover {
|
||||||
background-color: rgb(var(--color-accent));
|
background-color: rgb(var(--color-accent));
|
||||||
}
|
}
|
||||||
@@ -118,11 +116,11 @@
|
|||||||
.bg-background {
|
.bg-background {
|
||||||
background-color: rgb(var(--color-background));
|
background-color: rgb(var(--color-background));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-foreground {
|
.text-foreground {
|
||||||
color: rgb(var(--color-foreground));
|
color: rgb(var(--color-foreground));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-muted-foreground {
|
.text-muted-foreground {
|
||||||
color: rgb(var(--color-muted-foreground));
|
color: rgb(var(--color-muted-foreground));
|
||||||
}
|
}
|
||||||
@@ -130,7 +128,7 @@
|
|||||||
.bg-muted {
|
.bg-muted {
|
||||||
background-color: rgb(var(--color-muted));
|
background-color: rgb(var(--color-muted));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-primary {
|
.bg-primary {
|
||||||
background-color: rgb(var(--color-primary));
|
background-color: rgb(var(--color-primary));
|
||||||
}
|
}
|
||||||
@@ -138,15 +136,15 @@
|
|||||||
.text-primary {
|
.text-primary {
|
||||||
color: rgb(var(--color-primary));
|
color: rgb(var(--color-primary));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-primary-foreground {
|
.text-primary-foreground {
|
||||||
color: rgb(var(--color-primary-foreground));
|
color: rgb(var(--color-primary-foreground));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-accent {
|
.bg-accent {
|
||||||
background-color: rgb(var(--color-accent));
|
background-color: rgb(var(--color-accent));
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-border {
|
.border-border {
|
||||||
border-color: rgb(var(--color-border));
|
border-color: rgb(var(--color-border));
|
||||||
}
|
}
|
||||||
@@ -158,9 +156,57 @@
|
|||||||
transform: translateY(20px);
|
transform: translateY(20px);
|
||||||
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animate-on-scroll.in-view {
|
.animate-on-scroll.in-view {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Preloading styles */
|
||||||
|
.link-preloading {
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-preloading::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
left: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 2px;
|
||||||
|
background-color: rgb(var(--color-primary));
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-preloading:hover::after {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Preload indicator */
|
||||||
|
.preload-indicator {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: linear-gradient(90deg, transparent, rgb(var(--color-primary)), transparent);
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
z-index: 9999;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preload-indicator.active {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smooth transitions for page changes */
|
||||||
|
.page-transition {
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-transition.loading {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
}
|
}
|
133
src/utils/preload.ts
Normal file
133
src/utils/preload.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
// Preload utility for improving perceived performance
|
||||||
|
export function initPreloading() {
|
||||||
|
// Track preloaded URLs to avoid duplicate requests
|
||||||
|
const preloadedUrls = new Set<string>();
|
||||||
|
|
||||||
|
// Function to preload a URL
|
||||||
|
function preloadUrl(url: string) {
|
||||||
|
if (preloadedUrls.has(url)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create a link element for preloading
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.rel = 'prefetch';
|
||||||
|
link.href = url;
|
||||||
|
link.as = 'document';
|
||||||
|
|
||||||
|
// Add to head
|
||||||
|
document.head.appendChild(link);
|
||||||
|
|
||||||
|
// Mark as preloaded
|
||||||
|
preloadedUrls.add(url);
|
||||||
|
|
||||||
|
console.log(`Preloaded: ${url}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to preload ${url}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to handle link hover
|
||||||
|
function handleLinkHover(event: Event) {
|
||||||
|
const target = event.target as HTMLElement;
|
||||||
|
const link = target.closest('a');
|
||||||
|
|
||||||
|
if (!link) return;
|
||||||
|
|
||||||
|
const href = link.getAttribute('href');
|
||||||
|
if (!href) return;
|
||||||
|
|
||||||
|
// Skip external links, anchors, and special protocols
|
||||||
|
if (href.startsWith('http') || href.startsWith('mailto:') || href.startsWith('tel:') || href.startsWith('#')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert relative URLs to absolute
|
||||||
|
let url: string;
|
||||||
|
try {
|
||||||
|
url = new URL(href, window.location.origin).href;
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if it's the current page
|
||||||
|
if (url === window.location.href) return;
|
||||||
|
|
||||||
|
// Preload with a small delay to avoid preloading on accidental hovers
|
||||||
|
setTimeout(() => {
|
||||||
|
preloadUrl(url);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listeners to all links
|
||||||
|
function addPreloadListeners() {
|
||||||
|
// Use event delegation for better performance
|
||||||
|
document.addEventListener('mouseenter', handleLinkHover, {
|
||||||
|
capture: true,
|
||||||
|
passive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also preload on touchstart for mobile devices
|
||||||
|
document.addEventListener('touchstart', handleLinkHover, {
|
||||||
|
capture: true,
|
||||||
|
passive: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize preloading
|
||||||
|
addPreloadListeners();
|
||||||
|
|
||||||
|
// Re-add listeners when new content is loaded (for SPA-like behavior)
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
|
||||||
|
// New content added, ensure listeners are active
|
||||||
|
addPreloadListeners();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
preloadUrl,
|
||||||
|
preloadedUrls
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preload specific important pages immediately
|
||||||
|
export function preloadCriticalPages() {
|
||||||
|
const criticalPages = [
|
||||||
|
'/en/',
|
||||||
|
'/nl/',
|
||||||
|
'/de/',
|
||||||
|
'/fr/',
|
||||||
|
'/en/about',
|
||||||
|
'/nl/about',
|
||||||
|
'/de/about',
|
||||||
|
'/fr/about',
|
||||||
|
'/en/contact',
|
||||||
|
'/nl/contact',
|
||||||
|
'/de/contact',
|
||||||
|
'/fr/contact',
|
||||||
|
'/en/blog',
|
||||||
|
'/nl/blog',
|
||||||
|
'/de/blog',
|
||||||
|
'/fr/blog'
|
||||||
|
];
|
||||||
|
|
||||||
|
criticalPages.forEach(page => {
|
||||||
|
const url = new URL(page, window.location.origin).href;
|
||||||
|
if (url !== window.location.href) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.rel = 'prefetch';
|
||||||
|
link.href = url;
|
||||||
|
link.as = 'document';
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}, 1000); // Delay to not interfere with initial page load
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
Reference in New Issue
Block a user