<div class="o-cookie-banner" role="dialog" aria-labelledby="cookie-banner-text" aria-live="polite">
<div class="o-cookie-banner__content">
<p id="cookie-banner-text" class="o-cookie-banner__text">
Wir verwenden Tracking-Skripte, um unsere Website zu verbessern und dir relevante Inhalte anzuzeigen.
<a href="#" class="o-cookie-banner__privacy-link" target="_blank" rel="noopener">
Datenschutzerklärung
</a>
</p>
<div class="o-cookie-banner__actions">
<button type="button" class="a-button o-cookie-banner__button o-cookie-banner__button--accept" data-action="accept">
Akzeptieren
</button>
<button type="button" class="a-button a-button--secondary o-cookie-banner__button o-cookie-banner__button--decline" data-action="decline">
Ablehnen
</button>
</div>
</div>
<button type="button" class="o-cookie-banner__close" aria-label="Close banner" hidden>
<svg role="img" aria-hidden="true">
<title>Close banner</title>
<use xlink:href="#close"></use>
</svg>
</button>
</div>
<div class="o-cookie-banner" role="dialog" aria-labelledby="cookie-banner-text" aria-live="polite">
<div class="o-cookie-banner__content">
<p id="cookie-banner-text" class="o-cookie-banner__text">
{{{ text }}}
{{#if privacy_link}}
<a href="{{ privacy_link }}" class="o-cookie-banner__privacy-link" target="_blank" rel="noopener">
{{ privacy_text }}
</a>
{{/if}}
</p>
<div class="o-cookie-banner__actions">
<button type="button" class="a-button o-cookie-banner__button o-cookie-banner__button--accept" data-action="accept">
{{ accept_text }}
</button>
<button type="button" class="a-button a-button--secondary o-cookie-banner__button o-cookie-banner__button--decline" data-action="decline">
{{ decline_text }}
</button>
</div>
</div>
<button type="button" class="o-cookie-banner__close" aria-label="Close banner" hidden>
<svg role="img" aria-hidden="true">
<title>Close banner</title>
<use xlink:href="#close"></use>
</svg>
</button>
</div>
@use "../../../scss/variables" as v;
@use "../../../scss/mixins" as m;
.o-cookie-banner {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 9999;
background: v.$color-grey-1;
box-shadow: 0 -2px 20px rgba(0, 0, 0, 0.20);
padding: 20px;
display: none;
animation: slideUp 0.3s ease-out;
@include m.media(v.$DESKTOP) {
left: auto;
right: 8%;
bottom: 0;
width: 800px;
max-width: calc(100vw - 40px);
border: 12px solid v.$color-grey-2;
border-top: 12px solid v.$color-grey-2;
border-bottom: none;
border-radius: 4px 4px 0 0;
box-shadow: 0 -2px 30px rgba(0, 0, 0, 0.30);
}
&--visible {
display: flex;
flex-direction: column;
}
&__content {
flex: 1;
display: flex;
flex-direction: column;
gap: 15px;
@include m.media(v.$DESKTOP) {
gap: 20px;
}
}
&__text {
margin: 0;
font-size: 14px;
line-height: 1.5;
color: v.$color-text;
@include m.media(v.$DESKTOP) {
font-size: 15px;
}
}
&__privacy-link {
color: v.$color-primary;
text-decoration: underline;
transition: color v.$transition-fast v.$transition-ease;
@include m.hover {
color: v.$color-primary-dark;
}
}
&__actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
@include m.media(v.$DESKTOP) {
gap: 15px;
}
}
&__button {
flex: 1;
min-width: 120px;
font-size: 14px;
padding: 10px 20px;
@include m.media(v.$DESKTOP) {
flex: 0 1 auto;
}
&--accept {
background: v.$color-primary;
color: v.$color-white;
border: none;
@include m.hover {
background: v.$color-primary-dark;
}
}
&--decline {
background: v.$color-white;
color: v.$color-text;
border: 1px solid v.$color-grey-2;
@include m.hover {
background: v.$color-grey-1;
}
}
}
&__close {
position: absolute;
top: 10px;
right: 10px;
background: transparent;
border: none;
padding: 5px;
cursor: pointer;
opacity: 0.6;
transition: opacity v.$transition-fast v.$transition-ease;
display: none;
@include m.hover {
opacity: 1;
}
&[hidden] {
display: none;
}
&:not([hidden]) {
display: block;
}
svg {
width: 20px;
height: 20px;
stroke: v.$color-text;
}
}
}
@keyframes slideUp {
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
import BaseView from 'base-view';
const STORAGE_KEY = 'trackingConsent';
const CONSENT_EVENT = 'trackingConsentChanged';
/**
* Cookie Banner Component
* Manages user consent for tracking scripts
*/
export default class OCookieBanner extends BaseView {
initialize() {
this.acceptButton = this.getScopedElement('[data-action="accept"]');
this.declineButton = this.getScopedElement('[data-action="decline"]');
this.closeButton = this.getScopedElement('.o-cookie-banner__close');
// Check if consent already exists
const consent = this.getConsent();
if (consent === null) {
// No consent stored, show banner
this.show();
} else {
// Consent exists, load scripts if accepted
if (consent.accepted) {
// Try to load scripts immediately
this.loadTrackingScripts();
// Also try after DOM is fully loaded (in case window.trackingScripts is set later)
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
this.loadTrackingScripts();
});
}
}
}
// Bind events
this.on('click', '[data-action="accept"]', this.handleAccept.bind(this));
this.on('click', '[data-action="decline"]', this.handleDecline.bind(this));
this.on('click', '.o-cookie-banner__close', this.handleClose.bind(this));
}
/**
* Show the banner
*/
show() {
this.addClass('o-cookie-banner--visible');
this.element.setAttribute('aria-hidden', 'false');
}
/**
* Hide the banner
*/
hide() {
this.removeClass('o-cookie-banner--visible');
this.element.setAttribute('aria-hidden', 'true');
}
/**
* Handle accept button click
*/
handleAccept(e) {
e.preventDefault();
this.saveConsent(true);
this.loadTrackingScripts();
this.hide();
}
/**
* Handle decline button click
*/
handleDecline(e) {
e.preventDefault();
this.saveConsent(false);
this.hide();
}
/**
* Handle close button click
*/
handleClose(e) {
e.preventDefault();
this.hide();
}
/**
* Show close button after consent decision
*/
showCloseButton() {
if (this.closeButton) {
this.closeButton.removeAttribute('hidden');
}
}
/**
* Save consent to localStorage
* @param {boolean} accepted
*/
saveConsent(accepted) {
const consent = {
accepted: accepted,
timestamp: Date.now()
};
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(consent));
this.trigger(CONSENT_EVENT, { detail: consent });
} catch (e) {
// Silent fail - consent not saved
}
}
/**
* Get consent from localStorage
* @returns {Object|null}
*/
getConsent() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
return stored ? JSON.parse(stored) : null;
} catch (e) {
// Silent fail - return null
return null;
}
}
/**
* Load tracking scripts if consent was given
*/
loadTrackingScripts() {
// Check if tracking scripts are defined
if (typeof window.trackingScripts === 'undefined') {
return;
}
const scripts = window.trackingScripts;
// Load Facebook Pixel
if (scripts.facebookPixel && scripts.facebookPixel.id) {
this.loadFacebookPixel(scripts.facebookPixel.id);
}
// Load custom scripts
Object.keys(scripts).forEach(key => {
if (key !== 'facebookPixel' && scripts[key].type === 'external') {
this.loadExternalScript(scripts[key].url, key);
}
});
}
/**
* Load Facebook Pixel
* @param {string} pixelId
*/
loadFacebookPixel(pixelId) {
// Check if already loaded or initialized
if (window.fbq || window._fbPixelInitialized) {
return;
}
// Mark as initialized to prevent duplicate loading
window._fbPixelInitialized = true;
// Facebook Pixel Code
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
window.fbq('init', pixelId);
window.fbq('track', 'PageView');
}
/**
* Load external script
* @param {string} url
* @param {string} key
*/
loadExternalScript(url, key) {
// Check if already loaded
const existingScript = document.querySelector(`script[data-tracking-script="${key}"]`);
if (existingScript) {
return;
}
const script = document.createElement('script');
script.src = url;
script.async = true;
script.setAttribute('data-tracking-script', key);
script.onload = () => {
// Script loaded successfully
};
script.onerror = () => {
// Script failed to load
};
document.head.appendChild(script);
}
/**
* Public API: Check if user has consented
* @returns {boolean}
*/
static hasConsent() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (!stored) return false;
const consent = JSON.parse(stored);
return consent.accepted === true;
} catch (e) {
return false;
}
}
/**
* Public API: Reset consent (for testing)
*/
static resetConsent() {
try {
localStorage.removeItem(STORAGE_KEY);
// Consent reset - reload page to see banner
} catch (e) {
// Failed to reset consent
}
}
}
// Expose public API
window.CookieBanner = {
hasConsent: OCookieBanner.hasConsent,
resetConsent: OCookieBanner.resetConsent
};
{
"text": "Wir verwenden Tracking-Skripte, um unsere Website zu verbessern und dir relevante Inhalte anzuzeigen.",
"privacy_link": "#",
"privacy_text": "Datenschutzerklärung",
"accept_text": "Akzeptieren",
"decline_text": "Ablehnen"
}
No notes defined.