39853-vm/assets/js/main.js
2026-05-01 08:39:24 +00:00

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();
}
});