398 lines
13 KiB
JavaScript
398 lines
13 KiB
JavaScript
document.addEventListener('DOMContentLoaded', () => {
|
|
const COOKIE_NAME = 'ptcs_consent';
|
|
const COOKIE_MAX_AGE = 60 * 60 * 24 * 180;
|
|
const STORAGE_KEYS = {
|
|
personalization: 'ptcs_ui_preferences',
|
|
audience: 'ptcs_local_audience'
|
|
};
|
|
|
|
const banner = document.getElementById('cookie-banner');
|
|
const reopenButton = document.getElementById('cookie-reopen');
|
|
const reopenLabel = document.getElementById('cookie-reopen-label');
|
|
const footerCookieSettings = document.getElementById('footer-cookie-settings');
|
|
const dismissScrollHint = document.getElementById('scroll-hint-dismiss');
|
|
const scrollHint = document.getElementById('scroll-hint');
|
|
const toastStack = document.getElementById('toast-stack');
|
|
const personalizationControls = Array.from(document.querySelectorAll('[data-consent-control="personalization"]'));
|
|
const audienceControls = Array.from(document.querySelectorAll('[data-consent-control="audience"]'));
|
|
const consentActionButtons = Array.from(document.querySelectorAll('[data-cookie-action]'));
|
|
const trackedSections = Array.from(document.querySelectorAll('[data-track-section]'));
|
|
|
|
const state = {
|
|
prefs: {
|
|
essential: true,
|
|
personalization: false,
|
|
audience: false,
|
|
timestamp: null
|
|
},
|
|
audienceCountedThisSession: false,
|
|
visibleSections: new Set()
|
|
};
|
|
|
|
function safeParse(rawValue) {
|
|
if (!rawValue) {
|
|
return null;
|
|
}
|
|
try {
|
|
return JSON.parse(rawValue);
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function storageGet(key) {
|
|
try {
|
|
return window.localStorage.getItem(key);
|
|
} catch (error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function storageSet(key, value) {
|
|
try {
|
|
window.localStorage.setItem(key, value);
|
|
} catch (error) {
|
|
// no-op
|
|
}
|
|
}
|
|
|
|
function storageRemove(key) {
|
|
try {
|
|
window.localStorage.removeItem(key);
|
|
} catch (error) {
|
|
// no-op
|
|
}
|
|
}
|
|
|
|
function getCookie(name) {
|
|
const prefix = `${name}=`;
|
|
const cookies = document.cookie ? document.cookie.split('; ') : [];
|
|
for (const row of cookies) {
|
|
if (row.startsWith(prefix)) {
|
|
return decodeURIComponent(row.substring(prefix.length));
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function setCookie(name, value, maxAge) {
|
|
document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${maxAge}; SameSite=Lax`;
|
|
}
|
|
|
|
function normalizePrefs(rawPrefs = {}) {
|
|
return {
|
|
essential: true,
|
|
personalization: Boolean(rawPrefs.personalization),
|
|
audience: Boolean(rawPrefs.audience),
|
|
timestamp: rawPrefs.timestamp || null
|
|
};
|
|
}
|
|
|
|
function readSavedPrefs() {
|
|
const parsed = safeParse(getCookie(COOKIE_NAME));
|
|
return parsed ? normalizePrefs(parsed) : null;
|
|
}
|
|
|
|
function setControlState(controls, value) {
|
|
controls.forEach((control) => {
|
|
control.checked = Boolean(value);
|
|
});
|
|
}
|
|
|
|
function setBadge(key, label, status) {
|
|
document.querySelectorAll(`[data-consent-badge="${key}"]`).forEach((badge) => {
|
|
badge.textContent = label;
|
|
badge.dataset.state = status;
|
|
});
|
|
}
|
|
|
|
function formatDate(dateString) {
|
|
if (!dateString) {
|
|
return '—';
|
|
}
|
|
const date = new Date(dateString);
|
|
if (Number.isNaN(date.getTime())) {
|
|
return '—';
|
|
}
|
|
return new Intl.DateTimeFormat('fr-FR', {
|
|
dateStyle: 'short',
|
|
timeStyle: 'short'
|
|
}).format(date);
|
|
}
|
|
|
|
function updateSummaryText() {
|
|
const summary = state.prefs.personalization || state.prefs.audience
|
|
? `Réglages actifs : essentiels, ${state.prefs.personalization ? 'personnalisation' : 'personnalisation coupée'} et ${state.prefs.audience ? 'mesure locale activée' : 'mesure locale désactivée'}.`
|
|
: 'Le site conserve uniquement l\'essentiel tant que vous n\'avez pas choisi d\'options supplémentaires.';
|
|
|
|
document.querySelectorAll('[data-consent-summary]').forEach((node) => {
|
|
node.textContent = summary;
|
|
});
|
|
}
|
|
|
|
function updateReminderLabel() {
|
|
if (!reopenLabel) {
|
|
return;
|
|
}
|
|
|
|
if (state.prefs.personalization && state.prefs.audience) {
|
|
reopenLabel.textContent = 'Cookies : choix personnalisés';
|
|
return;
|
|
}
|
|
|
|
if (state.prefs.personalization || state.prefs.audience) {
|
|
reopenLabel.textContent = 'Cookies : options partielles';
|
|
return;
|
|
}
|
|
|
|
reopenLabel.textContent = 'Cookies : essentiels';
|
|
}
|
|
|
|
function updateAudiencePanel(store) {
|
|
const audienceEnabled = state.prefs.audience;
|
|
const visits = audienceEnabled && store ? Number(store.visits || 0) : 0;
|
|
const sections = audienceEnabled && store && Array.isArray(store.sections) ? store.sections.length : 0;
|
|
const lastVisit = audienceEnabled && store ? formatDate(store.lastVisit || null) : '—';
|
|
const statusText = audienceEnabled
|
|
? 'La mesure locale d\'audience est activée sur cet appareil. Les compteurs ci-dessous sont stockés uniquement dans votre navigateur.'
|
|
: 'La mesure locale d\'audience est désactivée. Aucun service tiers n\'est utilisé.';
|
|
|
|
document.querySelectorAll('[data-audience-status]').forEach((node) => {
|
|
node.textContent = statusText;
|
|
});
|
|
document.querySelectorAll('[data-audience-visits]').forEach((node) => {
|
|
node.textContent = String(visits);
|
|
});
|
|
document.querySelectorAll('[data-audience-sections]').forEach((node) => {
|
|
node.textContent = String(sections);
|
|
});
|
|
document.querySelectorAll('[data-audience-last-visit]').forEach((node) => {
|
|
node.textContent = lastVisit;
|
|
});
|
|
}
|
|
|
|
function showToast(message) {
|
|
if (!toastStack) {
|
|
return;
|
|
}
|
|
|
|
const toast = document.createElement('div');
|
|
toast.className = 'app-toast';
|
|
toast.setAttribute('role', 'status');
|
|
toast.textContent = message;
|
|
toastStack.appendChild(toast);
|
|
|
|
requestAnimationFrame(() => {
|
|
toast.classList.add('is-visible');
|
|
});
|
|
|
|
window.setTimeout(() => {
|
|
toast.classList.remove('is-visible');
|
|
window.setTimeout(() => {
|
|
toast.remove();
|
|
}, 220);
|
|
}, 3200);
|
|
}
|
|
|
|
function readAudienceStore() {
|
|
const parsed = safeParse(storageGet(STORAGE_KEYS.audience));
|
|
if (!parsed || typeof parsed !== 'object') {
|
|
return {
|
|
visits: 0,
|
|
sections: [],
|
|
lastVisit: null
|
|
};
|
|
}
|
|
return {
|
|
visits: Number(parsed.visits || 0),
|
|
sections: Array.isArray(parsed.sections) ? parsed.sections : [],
|
|
lastVisit: parsed.lastVisit || null
|
|
};
|
|
}
|
|
|
|
function saveAudienceStore(store) {
|
|
storageSet(STORAGE_KEYS.audience, JSON.stringify(store));
|
|
}
|
|
|
|
function refreshPersonalizationUI() {
|
|
const stored = safeParse(storageGet(STORAGE_KEYS.personalization)) || {};
|
|
if (!scrollHint) {
|
|
return;
|
|
}
|
|
|
|
if (!state.prefs.personalization) {
|
|
scrollHint.hidden = false;
|
|
return;
|
|
}
|
|
|
|
scrollHint.hidden = stored.scrollHintDismissed === true;
|
|
}
|
|
|
|
function applyAudienceState() {
|
|
if (!state.prefs.audience) {
|
|
storageRemove(STORAGE_KEYS.audience);
|
|
state.audienceCountedThisSession = false;
|
|
updateAudiencePanel(null);
|
|
return;
|
|
}
|
|
|
|
const store = readAudienceStore();
|
|
if (!state.audienceCountedThisSession) {
|
|
store.visits += 1;
|
|
store.lastVisit = new Date().toISOString();
|
|
state.audienceCountedThisSession = true;
|
|
}
|
|
|
|
if (state.visibleSections.size > 0) {
|
|
const merged = new Set([...(store.sections || []), ...state.visibleSections]);
|
|
store.sections = Array.from(merged);
|
|
}
|
|
|
|
saveAudienceStore(store);
|
|
updateAudiencePanel(store);
|
|
}
|
|
|
|
function applyPrefs(prefs) {
|
|
state.prefs = normalizePrefs(prefs);
|
|
document.body.dataset.consentPersonalization = state.prefs.personalization ? 'on' : 'off';
|
|
document.body.dataset.consentAudience = state.prefs.audience ? 'on' : 'off';
|
|
|
|
setControlState(personalizationControls, state.prefs.personalization);
|
|
setControlState(audienceControls, state.prefs.audience);
|
|
|
|
setBadge('essential', 'Toujours actif', 'locked');
|
|
setBadge('personalization', state.prefs.personalization ? 'Activée' : 'Désactivée', state.prefs.personalization ? 'on' : 'off');
|
|
setBadge('audience', state.prefs.audience ? 'Activée' : 'Désactivée', state.prefs.audience ? 'on' : 'off');
|
|
|
|
if (!state.prefs.personalization) {
|
|
storageRemove(STORAGE_KEYS.personalization);
|
|
}
|
|
|
|
refreshPersonalizationUI();
|
|
applyAudienceState();
|
|
updateSummaryText();
|
|
updateReminderLabel();
|
|
}
|
|
|
|
function openBanner() {
|
|
if (!banner) {
|
|
return;
|
|
}
|
|
banner.hidden = false;
|
|
if (reopenButton) {
|
|
reopenButton.setAttribute('aria-expanded', 'true');
|
|
}
|
|
}
|
|
|
|
function closeBanner() {
|
|
if (!banner) {
|
|
return;
|
|
}
|
|
banner.hidden = true;
|
|
if (reopenButton) {
|
|
reopenButton.setAttribute('aria-expanded', 'false');
|
|
}
|
|
}
|
|
|
|
function savePrefs(nextPrefs, notice) {
|
|
const normalized = normalizePrefs(nextPrefs);
|
|
normalized.timestamp = new Date().toISOString();
|
|
setCookie(COOKIE_NAME, JSON.stringify(normalized), COOKIE_MAX_AGE);
|
|
applyPrefs(normalized);
|
|
closeBanner();
|
|
showToast(notice);
|
|
}
|
|
|
|
if (dismissScrollHint && scrollHint) {
|
|
dismissScrollHint.addEventListener('click', () => {
|
|
scrollHint.hidden = true;
|
|
if (state.prefs.personalization) {
|
|
storageSet(STORAGE_KEYS.personalization, JSON.stringify({
|
|
scrollHintDismissed: true,
|
|
updatedAt: new Date().toISOString()
|
|
}));
|
|
showToast('Le rappel mobile a été masqué pour cet appareil.');
|
|
}
|
|
});
|
|
}
|
|
|
|
consentActionButtons.forEach((button) => {
|
|
button.addEventListener('click', () => {
|
|
const action = button.dataset.cookieAction;
|
|
|
|
if (action === 'accept') {
|
|
savePrefs({
|
|
essential: true,
|
|
personalization: true,
|
|
audience: true
|
|
}, 'Toutes les options facultatives ont été activées.');
|
|
return;
|
|
}
|
|
|
|
if (action === 'reject') {
|
|
savePrefs({
|
|
essential: true,
|
|
personalization: false,
|
|
audience: false
|
|
}, 'Seuls les cookies essentiels sont conservés.');
|
|
return;
|
|
}
|
|
|
|
if (action === 'save') {
|
|
savePrefs({
|
|
essential: true,
|
|
personalization: personalizationControls.some((control) => control.checked),
|
|
audience: audienceControls.some((control) => control.checked)
|
|
}, 'Vos préférences cookies ont été enregistrées.');
|
|
}
|
|
});
|
|
});
|
|
|
|
[reopenButton, footerCookieSettings].forEach((trigger) => {
|
|
if (!trigger) {
|
|
return;
|
|
}
|
|
trigger.addEventListener('click', () => {
|
|
openBanner();
|
|
});
|
|
});
|
|
|
|
if ('IntersectionObserver' in window && trackedSections.length > 0) {
|
|
const observer = new IntersectionObserver((entries) => {
|
|
let changed = false;
|
|
entries.forEach((entry) => {
|
|
if (!entry.isIntersecting) {
|
|
return;
|
|
}
|
|
|
|
const id = entry.target.id || entry.target.dataset.trackSection || '';
|
|
if (!id) {
|
|
return;
|
|
}
|
|
|
|
if (!state.visibleSections.has(id)) {
|
|
state.visibleSections.add(id);
|
|
changed = true;
|
|
}
|
|
});
|
|
|
|
if (changed && state.prefs.audience) {
|
|
applyAudienceState();
|
|
}
|
|
}, {
|
|
threshold: 0.45
|
|
});
|
|
|
|
trackedSections.forEach((section) => observer.observe(section));
|
|
}
|
|
|
|
const savedPrefs = readSavedPrefs();
|
|
if (savedPrefs) {
|
|
applyPrefs(savedPrefs);
|
|
closeBanner();
|
|
} else {
|
|
applyPrefs({ essential: true, personalization: false, audience: false });
|
|
openBanner();
|
|
}
|
|
});
|