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
type="button"
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"
tabindex="-1"
aria-label={`Switch to ${lang.name} language`}
@@ -208,11 +208,7 @@ const currentLanguage = languages.find(lang => lang.code === currentLang) || lan
closeMenu();
} else {
openMenu();
// Focus the first menu item for better keyboard navigation
const firstMenuItem = menu.querySelector('button[role="menuitem"]');
if (firstMenuItem) {
firstMenuItem.focus();
}
// Don't automatically focus any menu item to avoid default highlighting
}
});

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

View File

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

View File

@@ -71,43 +71,53 @@ const {
>
<!-- ✅ 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">
<!-- Site Title -->
<a class="inline-block font-bold text-xl" href={getHomePermalink(currentLang)}>
{SITE?.name}
</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 -->
<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 }) => (
<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}
</a>
))}
</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>
</footer>