added cookie notice, added terms and privacy notice, added return to top button

This commit is contained in:
becarta
2025-03-06 23:27:26 +01:00
parent 9f2f0107e9
commit f62c3458a5
11 changed files with 710 additions and 42 deletions

View File

@@ -0,0 +1,104 @@
---
import { getTranslation } from '~/i18n/translations';
const { lang = 'en' } = Astro.params;
const t = getTranslation(lang);
---
<div id="cookie-banner" class="fixed bottom-0 left-0 right-0 z-50 p-4 content-backdrop shadow-lg transform transition-transform duration-300 translate-y-full" style="display: none;">
<div class="container mx-auto max-w-6xl flex flex-col sm:flex-row items-center justify-between gap-4">
<div class="text-sm text-gray-800 dark:text-gray-200 font-medium">
<p>
{t.cookies.message}
<a href={`/${lang}/privacy#cookie-usage`} class="text-blue-600 dark:text-blue-400 hover:underline">{t.cookies.learnMore}</a>
</p>
</div>
<div class="flex-shrink-0">
<button id="accept-cookies" class="btn-primary px-4 py-2 rounded-md">
{t.cookies.accept}
</button>
</div>
</div>
</div>
<script is:inline>
document.addEventListener('DOMContentLoaded', () => {
setupCookieBanner();
});
document.addEventListener('astro:after-swap', () => {
setupCookieBanner();
});
// Helper function to get cookie value
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
return null;
}
// Helper function to set cookie with expiration
function setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
const expires = `expires=${date.toUTCString()}`;
document.cookie = `${name}=${value}; ${expires}; path=/; SameSite=Lax`;
}
function setupCookieBanner() {
const cookieBanner = document.getElementById('cookie-banner');
const acceptButton = document.getElementById('accept-cookies');
if (!cookieBanner || !acceptButton) return;
// Check if user has already accepted cookies
if (getCookie('cookieConsentAccepted') === 'true') {
cookieBanner.style.display = 'none';
return;
}
// Also check localStorage as a fallback
try {
if (localStorage && localStorage.getItem('cookieConsentAccepted') === 'true') {
cookieBanner.style.display = 'none';
// Also set the cookie for future visits
setCookie('cookieConsentAccepted', 'true', 365);
return;
}
} catch (e) {
console.error('Error accessing localStorage:', e);
// Continue checking cookies
}
// Show the banner
cookieBanner.style.display = 'block';
// Show the banner with a slight delay for better UX
setTimeout(() => {
cookieBanner.classList.remove('translate-y-full');
}, 500);
// Handle accept button click
acceptButton.addEventListener('click', () => {
// Store consent in cookie (primary storage)
setCookie('cookieConsentAccepted', 'true', 365);
// Also store in localStorage as backup
try {
localStorage.setItem('cookieConsentAccepted', 'true');
} catch (e) {
console.error('Error setting localStorage:', e);
// Continue with cookie storage
}
// Hide the banner with animation
cookieBanner.classList.add('translate-y-full');
// Remove from DOM after animation completes
setTimeout(() => {
cookieBanner.style.display = 'none';
}, 300);
});
}
</script>

View File

@@ -55,7 +55,7 @@ const currentLanguage = languages.find(lang => lang.code === currentLang) || lan
<button <button
type="button" type="button"
data-lang-code={lang.code} data-lang-code={lang.code}
class="text-gray-700 dark:text-gray-300 block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white focus:bg-gray-100 dark:focus:bg-gray-700 focus:text-gray-900 dark:focus:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:focus:ring-indigo-400 transition-colors duration-200" class="text-gray-700 dark:text-gray-300 block w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white transition-colors duration-200"
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
aria-label={`Switch to ${lang.name} language`} aria-label={`Switch to ${lang.name} language`}
@@ -208,11 +208,7 @@ const currentLanguage = languages.find(lang => lang.code === currentLang) || lan
closeMenu(); closeMenu();
} else { } else {
openMenu(); openMenu();
// Focus the first menu item for better keyboard navigation // Don't automatically focus any menu item to avoid default highlighting
const firstMenuItem = menu.querySelector('button[role="menuitem"]');
if (firstMenuItem) {
firstMenuItem.focus();
}
} }
}); });

View File

@@ -0,0 +1,119 @@
---
---
<button
id="back-to-top"
class="back-to-top"
aria-label="Back to top"
title="Return to Top"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
</button>
<style>
.back-to-top {
position: fixed;
bottom: 80px; /* Increased from 30px to avoid cookie banner */
right: 40px; /* Increased from 30px for more margin from edge */
display: flex;
align-items: center;
justify-content: center;
width: 45px;
height: 45px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.2);
color: #fff;
border: none;
cursor: pointer;
opacity: 0;
visibility: hidden;
transform: translateY(10px);
transition: all 0.3s ease;
z-index: 90; /* Lower than cookie banner to ensure no conflicts */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.back-to-top:hover {
background-color: rgba(0, 0, 0, 0.4);
transform: translateY(-2px) scale(1.05);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
}
.back-to-top.visible {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
/* Only show on larger screens */
@media (max-width: 767px) {
.back-to-top {
display: none;
}
}
/* Dark mode support */
:global(.dark) .back-to-top {
background-color: rgba(255, 255, 255, 0.2);
color: #fff;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
:global(.dark) .back-to-top:hover {
background-color: rgba(255, 255, 255, 0.3);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
}
</style>
<script is:inline>
// Function to initialize the back to top button
function initBackToTop() {
const backToTopButton = document.getElementById('back-to-top');
if (!backToTopButton) return;
// Show button when scrolling down
const toggleBackToTopButton = () => {
if (window.scrollY > 300) {
backToTopButton.classList.add('visible');
} else {
backToTopButton.classList.remove('visible');
}
};
// Scroll to top with smooth behavior
const scrollToTop = (e) => {
e.preventDefault();
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
// Add event listeners
window.addEventListener('scroll', toggleBackToTopButton);
backToTopButton.addEventListener('click', scrollToTop);
// Initial check
toggleBackToTopButton();
}
// Initialize on page load
initBackToTop();
// Re-initialize on view transitions (for Astro's View Transitions)
document.addEventListener('astro:page-load', initBackToTop);
document.addEventListener('astro:after-swap', initBackToTop);
</script>

View File

@@ -66,7 +66,6 @@ const iconNames: string[] = [
'tabler:code', 'tabler:code',
'tabler:cloud', 'tabler:cloud',
'tabler:device-laptop', 'tabler:device-laptop',
'tabler:chart-line',
'tabler:database', 'tabler:database',
'tabler:brand-github', 'tabler:brand-github',
'tabler:device-desktop', 'tabler:device-desktop',
@@ -80,7 +79,6 @@ const iconNames: string[] = [
'tabler:shield', 'tabler:shield',
'tabler:lock', 'tabler:lock',
'tabler:key', 'tabler:key',
'tabler:rocket',
// Tangentially related icons for visual diversity // Tangentially related icons for visual diversity
'tabler:bulb', 'tabler:bulb',

View File

@@ -66,7 +66,6 @@ const iconNames: string[] = [
'tabler:code', 'tabler:code',
'tabler:cloud', 'tabler:cloud',
'tabler:device-laptop', 'tabler:device-laptop',
'tabler:chart-line',
'tabler:database', 'tabler:database',
'tabler:brand-github', 'tabler:brand-github',
'tabler:device-desktop', 'tabler:device-desktop',
@@ -80,22 +79,13 @@ const iconNames: string[] = [
'tabler:shield', 'tabler:shield',
'tabler:lock', 'tabler:lock',
'tabler:key', 'tabler:key',
'tabler:rocket',
'tabler:satellite-off',
// Tangentially related icons for visual diversity // Tangentially related icons for visual diversity
'tabler:bulb', 'tabler:bulb',
'tabler:puzzle',
'tabler:compass', 'tabler:compass',
'tabler:chart-dots',
'tabler:math',
'tabler:atom',
'tabler:binary', 'tabler:binary',
'tabler:circuit-resistor',
'tabler:infinity', 'tabler:infinity',
'tabler:planet',
'tabler:brain', 'tabler:brain',
'tabler:cube',
]; ];
// Function to get a random value within a range // Function to get a random value within a range

View File

@@ -71,43 +71,53 @@ const {
> >
<!-- ✅ Combined Footer Section --> <!-- ✅ Combined Footer Section -->
<div class="flex flex-col md:flex-row md:items-center md:justify-between py-6 md:py-8space-y-4 md:space-y-0"> <div class="flex flex-col md:flex-row md:justify-between py-6 md:py-8 space-y-6 md:space-y-0">
<!-- Site Title with Terms & Privacy Links --> <!-- Left Section: Company Name and Business Details -->
<div class="flex flex-col items-start space-y-2"> <div class="flex flex-col items-start space-y-2">
<!-- Site Title --> <!-- Site Title -->
<a class="inline-block font-bold text-xl" href={getHomePermalink(currentLang)}> <a class="inline-block font-bold text-xl" href={getHomePermalink(currentLang)}>
{SITE?.name} {SITE?.name}
</a> </a>
<!-- Business Information (Dutch Law Requirements) -->
<div class="text-sm text-gray-500 space-y-1">
<p>Postbus 12345, 1000 AB Amsterdam, Nederland</p>
<p>KVK: 87654321 | BTW: NL123456789B01</p>
<p>contact@example.com | +31 6 12345678</p>
</div>
</div>
<!-- Right Section: Social Icons and Terms/Privacy Links -->
<div class="flex flex-col items-start md:items-end space-y-4">
<!-- Social Icons -->
{
socialLinks?.length && (
<ul class="flex space-x-4">
{socialLinks.map(({ ariaLabel, href, icon }) => (
<li>
<a
class="text-muted dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg p-2 inline-flex items-center"
aria-label={ariaLabel}
href={href}
>
{icon && <Icon name={icon} class="w-5 h-5" />}
</a>
</li>
))}
</ul>
)
}
<!-- Terms & Privacy Policy Links --> <!-- Terms & Privacy Policy Links -->
<div class="flex items-center space-x-4 text-sm text-muted"> <div class="flex items-center space-x-4 text-sm text-gray-500">
{secondaryLinks.map(({ text, href }) => ( {secondaryLinks.map(({ text, href }) => (
<a class="hover:text-gray-700 dark:hover:text-gray-200 transition duration-150 ease-in-out" href={href}> <a class="hover:text-gray-700 hover:underline dark:hover:text-gray-200 transition duration-150 ease-in-out" href={href}>
{text} {text}
</a> </a>
))} ))}
</div> </div>
</div> </div>
<!-- Social Icons -->
{
socialLinks?.length && (
<ul class="flex space-x-4">
{socialLinks.map(({ ariaLabel, href, icon }) => (
<li>
<a
class="text-muted dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg p-2 inline-flex items-center"
aria-label={ariaLabel}
href={href}
>
{icon && <Icon name={icon} class="w-5 h-5" />}
</a>
</li>
))}
</ul>
)
}
</div> </div>
</div> </div>
</footer> </footer>

View File

@@ -18,6 +18,11 @@ export interface Translation {
terms: string; terms: string;
privacyPolicy: string; privacyPolicy: string;
}; };
cookies: {
message: string;
learnMore: string;
accept: string;
};
hero: { hero: {
title: string; title: string;
greeting: string; greeting: string;
@@ -136,6 +141,11 @@ export const translations: Record<string, Translation> = {
title: 'About me', title: 'About me',
aboutUs: 'About me', aboutUs: 'About me',
}, },
cookies: {
message: 'This website uses cookies to store your language preference and remember your cookie consent. No personal data is collected.',
learnMore: 'Learn more in our Privacy Policy',
accept: 'OK',
},
navigation: { navigation: {
home: 'Home', home: 'Home',
about: 'About', about: 'About',
@@ -487,6 +497,11 @@ export const translations: Record<string, Translation> = {
title: 'Over mij', title: 'Over mij',
aboutUs: 'Over mij', aboutUs: 'Over mij',
}, },
cookies: {
message: 'Deze website gebruikt cookies om uw taalvoorkeur op te slaan en uw cookie-toestemming te onthouden. Er worden geen persoonlijke gegevens verzameld.',
learnMore: 'Lees meer in ons Privacybeleid',
accept: 'OK',
},
navigation: { navigation: {
home: 'Home', home: 'Home',
about: 'Over', about: 'Over',
@@ -838,6 +853,11 @@ export const translations: Record<string, Translation> = {
title: 'Über mich', title: 'Über mich',
aboutUs: 'Über mich', aboutUs: 'Über mich',
}, },
cookies: {
message: 'Diese Website verwendet Cookies, um Ihre Spracheinstellung zu speichern und Ihre Cookie-Zustimmung zu merken. Es werden keine persönlichen Daten gesammelt.',
learnMore: 'Erfahren Sie mehr in unserer Datenschutzrichtlinie',
accept: 'OK',
},
navigation: { navigation: {
home: 'Start', home: 'Start',
about: 'Über', about: 'Über',
@@ -1189,6 +1209,11 @@ export const translations: Record<string, Translation> = {
title: 'À propos de moi', title: 'À propos de moi',
aboutUs: 'À propos de moi', aboutUs: 'À propos de moi',
}, },
cookies: {
message: 'Ce site utilise des cookies pour enregistrer votre préférence de langue et mémoriser votre consentement aux cookies. Aucune donnée personnelle n\'est collectée.',
learnMore: 'En savoir plus dans notre Politique de confidentialité',
accept: 'OK',
},
navigation: { navigation: {
home: 'Accueil', home: 'Accueil',
about: 'À propos', about: 'À propos',

View File

@@ -14,6 +14,8 @@ import BasicScripts from '~/components/common/BasicScripts.astro';
import StructuredData from '~/components/common/StructuredData.astro'; import StructuredData from '~/components/common/StructuredData.astro';
import LanguagePersistence from '~/components/LanguagePersistence.astro'; import LanguagePersistence from '~/components/LanguagePersistence.astro';
import GlobalBackground from '~/components/ui/GlobalBackground.astro'; import GlobalBackground from '~/components/ui/GlobalBackground.astro';
import CookieBanner from '~/components/CookieBanner.astro';
import BackToTop from '~/components/ui/BackToTop.astro';
// Comment the line below to disable View Transitions // Comment the line below to disable View Transitions
import { ClientRouter } from 'astro:transitions'; import { ClientRouter } from 'astro:transitions';
@@ -63,5 +65,7 @@ const { language, textDirection } = I18N;
<BasicScripts /> <BasicScripts />
<LanguagePersistence /> <LanguagePersistence />
<CookieBanner />
<BackToTop />
</body> </body>
</html> </html>

View File

@@ -32,7 +32,6 @@ export const getHeaderData = (lang = 'en') => {
{ text: t.navigation.education, href: getPermalink('/aboutme', 'page', lang) + '#education', isHashLink: true }, { text: t.navigation.education, href: getPermalink('/aboutme', 'page', lang) + '#education', isHashLink: true },
] ]
}, },
{ text: t.navigation.blog, href: getPermalink('/blog', 'page', lang) },
] ]
}; };
}; };

View File

@@ -0,0 +1,268 @@
---
export const prerender = true;
import Layout from '~/layouts/PageLayout.astro';
import StructuredData from '~/components/common/StructuredData.astro';
import Hero from '~/components/widgets/Hero.astro';
import { getTranslation, supportedLanguages } from '~/i18n/translations';
export async function getStaticPaths() {
return supportedLanguages.map(lang => ({
params: { lang },
}));
}
const { lang } = Astro.params;
if (!supportedLanguages.includes(lang)) {
return Astro.redirect('/en/privacy');
}
const t = getTranslation(lang);
const metadata = {
title: t.footer.privacyPolicy,
description: 'Privacy Policy outlining our data collection practices, cookie usage, and your rights under GDPR.',
};
// Table of Contents items
const tocItems = [
{ id: 'introduction', title: 'Introduction' },
{ id: 'data-collection', title: 'Data Collection Policy' },
{ id: 'cookie-usage', title: 'Cookie & Storage Usage' },
{ id: 'localstorage', title: 'LocalStorage Usage' },
{ id: 'clear-preferences', title: 'How to Clear Your Preferences' },
{ id: 'user-rights', title: 'Your Rights (GDPR Compliance)' },
{ id: 'data-security', title: 'Data Security' },
{ id: 'third-party', title: 'Third-Party Websites' },
{ id: 'changes', title: 'Changes to Privacy Policy' },
{ id: 'contact', title: 'Contact Information' },
];
---
<Layout metadata={metadata}>
<Fragment slot="announcement"></Fragment>
<!-- Legal Document Structured Data for SEO -->
<StructuredData slot="structured-data" data={{
"@context": "https://schema.org",
"@type": "WebPage",
"name": "Privacy Policy",
"description": "Privacy Policy outlining our data collection practices, cookie usage, and your rights under GDPR.",
"url": Astro.url.origin + "/" + lang + "/privacy",
"mainEntity": {
"@type": "Article",
"headline": "Privacy Policy",
"datePublished": "2025-03-06",
"dateModified": "2025-03-06"
}
}} />
<!-- Hero Widget -->
<Hero
id="hero"
title={t.footer.privacyPolicy}
isDark={false}
>
<Fragment slot="subtitle">
Last updated: March 6, 2025 (Added cookie consent banner)
</Fragment>
</Hero>
<!-- Content Widget -->
<div class="mx-auto px-4 sm:px-6 py-4 max-w-4xl">
<!-- Table of Contents -->
<div class="bg-gray-50 dark:bg-slate-800 p-5 rounded-lg mb-10">
<h2 class="text-xl font-bold mb-3">Table of Contents</h2>
<ul class="space-y-2">
{tocItems.map(item => (
<li>
<a href={`#${item.id}`} class="text-blue-600 dark:text-blue-400 hover:underline">
{item.title}
</a>
</li>
))}
</ul>
</div>
<!-- Privacy Policy Content -->
<div class="prose prose-lg max-w-4xl dark:prose-invert dark:prose-headings:text-slate-300 prose-md prose-headings:font-heading prose-headings:leading-tighter prose-headings:tracking-tighter prose-headings:font-bold prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-img:rounded-md prose-img:shadow-lg">
<h2 id="introduction" class="text-2xl font-bold mt-8 mb-4">1. Introduction</h2>
<p>
This Privacy Policy explains how we handle information when you visit our website. We are committed to protecting your privacy and complying with applicable data protection laws, including the General Data Protection Regulation (GDPR).
</p>
<p>
We value transparency and want you to understand what information we collect, why we collect it, and how we use it. This policy applies to all visitors to our website.
</p>
<h2 id="data-collection" class="text-2xl font-bold mt-8 mb-4">2. Data Collection Policy</h2>
<p>
<strong>We do not collect or store any personal user data.</strong> Our website is designed to provide information without requiring you to submit any personal information.
</p>
<p>
The only data stored is your preferences for language and theme settings, which are stored locally on your device using browser technologies (cookies and LocalStorage) and are never transmitted to our servers. More details about this are provided in the sections below.
</p>
<p>
We do not:
</p>
<ul>
<li>Collect your name, email address, or other contact information unless you voluntarily provide it through our contact form</li>
<li>Track your browsing behavior</li>
<li>Use analytics services that collect personal data</li>
<li>Use advertising or marketing tracking technologies</li>
<li>Share any information with third parties</li>
<li>Store your preferences on our servers</li>
</ul>
<p>
If you choose to contact us using our contact form, the information you provide (such as your name and email address) will only be used to respond to your inquiry and will not be stored longer than necessary for that purpose.
</p>
<h2 id="cookie-usage" class="text-2xl font-bold mt-8 mb-4">3. Cookie & Storage Usage</h2>
<p>
<strong>Our website uses cookies strictly for essential functionality.</strong> These cookies are necessary for the proper functioning of our website and do not collect any personal information.
</p>
<p>
Details about the cookies we use:
</p>
<ul>
<li>
<strong>Name:</strong> preferredLanguage
<ul>
<li><strong>Purpose:</strong> Remembers your language preference (e.g., English, Dutch, German, French)</li>
<li><strong>Data stored:</strong> Only the language code (e.g., "en", "nl", "de", "fr")</li>
<li><strong>Duration:</strong> 30 days</li>
<li><strong>Type:</strong> First-party cookie (not shared with any third parties)</li>
</ul>
</li>
<li>
<strong>Name:</strong> cookieConsentAccepted
<ul>
<li><strong>Purpose:</strong> Remembers that you have acknowledged our cookie notice</li>
<li><strong>Data stored:</strong> A simple "true" value</li>
<li><strong>Duration:</strong> 365 days</li>
<li><strong>Type:</strong> First-party cookie and LocalStorage item (not shared with any third parties)</li>
</ul>
</li>
</ul>
<p>
In addition to cookies, we also use LocalStorage to store certain preferences. Details about this are provided in the next section.
</p>
<p>
We do not use any tracking, analytics, or third-party cookies. No personal information is collected through our cookies or LocalStorage.
</p>
<h2 id="localstorage" class="text-2xl font-bold mt-8 mb-4">4. LocalStorage Usage</h2>
<p>
<strong>Our website uses LocalStorage to enhance your experience by remembering your preferences and consent choices.</strong>
</p>
<p>
Details about our LocalStorage usage:
</p>
<ul>
<li><strong>Data stored:</strong>
<ul>
<li>Theme preference (light/dark mode)</li>
<li>Cookie consent acceptance status</li>
</ul>
</li>
<li><strong>Purpose:</strong> To remember your preferences and consent choices between visits</li>
<li><strong>Location:</strong> Stored only on your device and never sent to our servers</li>
<li><strong>Duration:</strong> Persists until you clear your browser's LocalStorage</li>
</ul>
<p>
LocalStorage is a technology that allows websites to store data directly in your browser. Unlike cookies, LocalStorage data is not sent with every request to the server, which makes it more efficient for storing user preferences that only need to be accessed by your browser.
</p>
<p>
No personal information is collected or stored in LocalStorage. The data is used solely to enhance your browsing experience by maintaining your preferred settings.
</p>
<h2 id="clear-preferences" class="text-2xl font-bold mt-8 mb-4">5. How to Clear Your Preferences</h2>
<p>
If you wish to reset your language or theme settings, you can clear your browser's cookies and LocalStorage data. Here's how to do it in common browsers:
</p>
<p>
<strong>Chrome:</strong>
</p>
<ol>
<li>Click the three dots in the top-right corner</li>
<li>Select "Settings"</li>
<li>Go to "Privacy and security"</li>
<li>Click "Clear browsing data"</li>
<li>Select "Cookies and other site data" and "Cached images and files"</li>
<li>Click "Clear data"</li>
</ol>
<p>
<strong>Firefox:</strong>
</p>
<ol>
<li>Click the three lines in the top-right corner</li>
<li>Select "Settings"</li>
<li>Go to "Privacy & Security"</li>
<li>Under "Cookies and Site Data," click "Clear Data"</li>
<li>Ensure "Cookies and Site Data" is checked</li>
<li>Click "Clear"</li>
</ol>
<p>
<strong>Safari:</strong>
</p>
<ol>
<li>Click "Safari" in the top menu</li>
<li>Select "Preferences"</li>
<li>Go to the "Privacy" tab</li>
<li>Click "Manage Website Data"</li>
<li>Find our website and click "Remove" or "Remove All"</li>
</ol>
<p>
After clearing your browser data, your language will reset to the default (English) and your theme will reset to the system default.
</p>
<h2 id="user-rights" class="text-2xl font-bold mt-8 mb-4">6. Your Rights (GDPR Compliance)</h2>
<p>
Under the General Data Protection Regulation (GDPR), you have various rights regarding your personal data. However, since we do not collect or store personal data (except for the language preference cookie which does not contain personal information), most of these rights are not applicable in practice.
</p>
<p>
Nevertheless, you have the right to:
</p>
<ul>
<li><strong>Delete your cookie and LocalStorage data:</strong> You can delete the language preference cookie and theme preference LocalStorage data at any time through your browser settings (see section 5 for instructions)</li>
<li><strong>Be informed:</strong> This privacy policy provides transparent information about our data practices</li>
<li><strong>Object:</strong> You can choose to disable cookies and LocalStorage in your browser settings</li>
</ul>
<p>
If you have any questions about your rights or wish to exercise any of them, please contact us using the information provided at the end of this policy.
</p>
<h2 id="data-security" class="text-2xl font-bold mt-8 mb-4">7. Data Security</h2>
<p>
We take appropriate technical and organizational measures to ensure the security of any information transmitted to us. However, please be aware that no method of transmission over the internet or method of electronic storage is 100% secure.
</p>
<p>
Our website uses HTTPS encryption to ensure that any communication between your browser and our website is secure.
</p>
<h2 id="third-party" class="text-2xl font-bold mt-8 mb-4">8. Third-Party Websites</h2>
<p>
Our website may contain links to other websites that are not operated by us. If you click on a third-party link, you will be directed to that third party's site. We strongly advise you to review the Privacy Policy of every site you visit.
</p>
<p>
We have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
</p>
<h2 id="changes" class="text-2xl font-bold mt-8 mb-4">9. Changes to Privacy Policy</h2>
<p>
We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "Last updated" date at the top of this page.
</p>
<p>
You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.
</p>
<h2 id="contact" class="text-2xl font-bold mt-8 mb-4">10. Contact Information</h2>
<p>
If you have any questions about this Privacy Policy or our data practices, please contact us at:
</p>
<p>
Email: contact@example.com<br>
Postal Address: Postbus 12345, 1000 AB Amsterdam, Nederland
</p>
</div>
</div>
</Layout>

View File

@@ -0,0 +1,155 @@
---
export const prerender = true;
import Layout from '~/layouts/PageLayout.astro';
import StructuredData from '~/components/common/StructuredData.astro';
import Hero from '~/components/widgets/Hero.astro';
import { getTranslation, supportedLanguages } from '~/i18n/translations';
export async function getStaticPaths() {
return supportedLanguages.map(lang => ({
params: { lang },
}));
}
const { lang } = Astro.params;
if (!supportedLanguages.includes(lang)) {
return Astro.redirect('/en/terms');
}
const t = getTranslation(lang);
const metadata = {
title: t.footer.terms,
description: 'Terms and Conditions for our website, outlining user rights, responsibilities, and legal information.',
};
// Table of Contents items
const tocItems = [
{ id: 'scope', title: 'Scope of Services' },
{ id: 'user-rights', title: 'User Rights & Responsibilities' },
{ id: 'intellectual-property', title: 'Intellectual Property' },
{ id: 'liability', title: 'Limitation of Liability' },
{ id: 'governing-law', title: 'Governing Law' },
{ id: 'cookies', title: 'Cookie Usage' },
{ id: 'changes', title: 'Changes to Terms' },
{ id: 'contact', title: 'Contact Information' },
];
---
<Layout metadata={metadata}>
<Fragment slot="announcement"></Fragment>
<!-- Legal Document Structured Data for SEO -->
<StructuredData slot="structured-data" data={{
"@context": "https://schema.org",
"@type": "WebPage",
"name": "Terms and Conditions",
"description": "Terms and Conditions for our website, outlining user rights, responsibilities, and legal information.",
"url": Astro.url.origin + "/" + lang + "/terms",
"mainEntity": {
"@type": "Article",
"headline": "Terms and Conditions",
"datePublished": "2025-03-06",
"dateModified": "2025-03-06"
}
}} />
<!-- Hero Widget -->
<Hero
id="hero"
title={t.footer.terms}
isDark={false}
>
<Fragment slot="subtitle">
Last updated: March 6, 2025
</Fragment>
</Hero>
<!-- Content Widget -->
<div class="mx-auto px-4 sm:px-6 py-4 max-w-4xl">
<!-- Table of Contents -->
<div class="bg-gray-50 dark:bg-slate-800 p-5 rounded-lg mb-10">
<h2 class="text-xl font-bold mb-3">Table of Contents</h2>
<ul class="space-y-2">
{tocItems.map(item => (
<li>
<a href={`#${item.id}`} class="text-blue-600 dark:text-blue-400 hover:underline">
{item.title}
</a>
</li>
))}
</ul>
</div>
<!-- Terms Content -->
<div class="prose prose-lg max-w-4xl dark:prose-invert dark:prose-headings:text-slate-300 prose-md prose-headings:font-heading prose-headings:leading-tighter prose-headings:tracking-tighter prose-headings:font-bold prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-img:rounded-md prose-img:shadow-lg">
<p>
Please read these terms and conditions carefully before using our website. By accessing or using our website, you agree to be bound by these terms and conditions.
</p>
<h2 id="scope" class="text-2xl font-bold mt-8 mb-4">1. Scope of Services</h2>
<p>
Our website provides information about our professional services, expertise, and industry insights. The content on this website is for general informational purposes only and does not constitute professional advice. We may update, modify, or remove content at any time without notice.
</p>
<h2 id="user-rights" class="text-2xl font-bold mt-8 mb-4">2. User Rights & Responsibilities</h2>
<p>
When using our website, you agree to:
</p>
<ul>
<li>Use the website in accordance with these terms and conditions and all applicable laws and regulations</li>
<li>Not use the website in any way that could damage, disable, overburden, or impair our services</li>
<li>Not attempt to gain unauthorized access to any part of the website or any system or network connected to the website</li>
<li>Not use any automated means to access or collect data from the website</li>
<li>Not use the website to transmit any harmful code or material</li>
</ul>
<h2 id="intellectual-property" class="text-2xl font-bold mt-8 mb-4">3. Intellectual Property</h2>
<p>
All content on this website, including but not limited to text, graphics, logos, images, audio clips, digital downloads, and data compilations, is the property of the website owner or its content suppliers and is protected by Dutch and international copyright laws.
</p>
<p>
You may view, download, and print content from this website for your personal, non-commercial use, provided that you do not modify the content and that you retain all copyright and other proprietary notices.
</p>
<h2 id="liability" class="text-2xl font-bold mt-8 mb-4">4. Limitation of Liability</h2>
<p>
To the fullest extent permitted by applicable law, we exclude all representations, warranties, and conditions relating to our website and the use of this website. We will not be liable for any direct, indirect, or consequential loss or damage arising under these terms and conditions or in connection with our website, whether arising in tort, contract, or otherwise, including, without limitation, any loss of profit, contracts, business, goodwill, data, income, revenue, or anticipated savings.
</p>
<p>
This does not exclude or limit our liability for death or personal injury resulting from our negligence, nor our liability for fraudulent misrepresentation or misrepresentation as to a fundamental matter, nor any other liability which cannot be excluded or limited under applicable law.
</p>
<h2 id="governing-law" class="text-2xl font-bold mt-8 mb-4">5. Governing Law</h2>
<p>
These terms and conditions are governed by and construed in accordance with the laws of the Netherlands. Any disputes relating to these terms and conditions shall be subject to the exclusive jurisdiction of the courts of the Netherlands.
</p>
<p>
If you are a consumer, you will benefit from any mandatory provisions of the law of the country in which you are resident. Nothing in these terms and conditions affects your rights as a consumer to rely on such mandatory provisions of local law.
</p>
<h2 id="cookies" class="text-2xl font-bold mt-8 mb-4">6. Cookie Usage</h2>
<p>
Our website uses only one cookie, which is used exclusively for storing your selected language preference. This cookie is essential for the proper functioning of the language selection feature on our website. We do not use any tracking, analytics, or third-party cookies.
</p>
<p>
The language preference cookie stores only your selected language choice and does not collect any personal information. This cookie is stored on your device for a period of 30 days, after which it will expire unless you visit our website again.
</p>
<h2 id="changes" class="text-2xl font-bold mt-8 mb-4">7. Changes to Terms</h2>
<p>
We may revise these terms and conditions at any time by amending this page. You are expected to check this page from time to time to take notice of any changes we make, as they are legally binding on you. Some of the provisions contained in these terms and conditions may also be superseded by provisions or notices published elsewhere on our website.
</p>
<h2 id="contact" class="text-2xl font-bold mt-8 mb-4">8. Contact Information</h2>
<p>
If you have any questions about these terms and conditions, please contact us at:
</p>
<p>
Email: contact@example.com<br>
Postal Address: Postbus 12345, 1000 AB Amsterdam, Nederland
</p>
</div>
</div>
</Layout>