Add privacy policy page and cookie consent banner
Deploy arikigame.com / deploy (push) Successful in 21s
Deploy arikigame.com / deploy (push) Successful in 21s
The site was loading Google Analytics 4 unconditionally with no consent gate — non-essential cookies set before consent (PECR reg. 6), and no privacy policy was published at all. - Add /privacy — full Privacy and Cookie Policy from tinqs/legal, styled with the site's existing design tokens, no tracking on the page - Gate GA4 behind opt-in consent (consent.js / consent.css); nothing loads until the visitor accepts, and declining clears any _ga cookies - Add Privacy Policy + Cookie Settings links to the footer, patched in the footer component so they survive React hydration - Cookie Settings reopens the banner so consent can be withdrawn as easily as it was given (UK GDPR Art. 7(3)) - Add /privacy to sitemap.xml Verified with Playwright: zero googletagmanager requests before consent, GA loads only after Accept, choice persists across reloads, withdrawal works, no horizontal scroll on mobile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
/* Cookie consent — gates Google Analytics behind opt-in (PECR reg. 6 / UK GDPR).
|
||||
No analytics script loads until the visitor actively chooses "Accept".
|
||||
Choice is stored in localStorage under "ariki-consent" as "accepted" | "declined". */
|
||||
(function () {
|
||||
var KEY = 'ariki-consent';
|
||||
var GA_ID = 'G-CMZ6T2LJ0W';
|
||||
|
||||
function stored() {
|
||||
try { return localStorage.getItem(KEY); } catch (e) { return null; }
|
||||
}
|
||||
function remember(v) {
|
||||
try { localStorage.setItem(KEY, v); } catch (e) {}
|
||||
}
|
||||
|
||||
function loadAnalytics() {
|
||||
if (window.__arikiGaLoaded) return;
|
||||
window.__arikiGaLoaded = true;
|
||||
var s = document.createElement('script');
|
||||
s.async = true;
|
||||
s.src = 'https://www.googletagmanager.com/gtag/js?id=' + GA_ID;
|
||||
document.head.appendChild(s);
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag() { window.dataLayer.push(arguments); }
|
||||
window.gtag = gtag;
|
||||
gtag('js', new Date());
|
||||
gtag('config', GA_ID, { anonymize_ip: true });
|
||||
}
|
||||
|
||||
function clearAnalyticsCookies() {
|
||||
var host = location.hostname.replace(/^www\./, '');
|
||||
document.cookie.split(';').forEach(function (c) {
|
||||
var name = c.split('=')[0].trim();
|
||||
if (!/^(_ga|_gid|_gat)/.test(name)) return;
|
||||
['/', location.pathname].forEach(function (p) {
|
||||
['', '.' + host, host].forEach(function (d) {
|
||||
document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=' + p +
|
||||
(d ? '; domain=' + d : '');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function decide(choice) {
|
||||
remember(choice);
|
||||
if (choice === 'accepted') loadAnalytics();
|
||||
else clearAnalyticsCookies();
|
||||
var b = document.getElementById('cookie-banner');
|
||||
if (b) b.parentNode.removeChild(b);
|
||||
}
|
||||
|
||||
function banner() {
|
||||
var wrap = document.createElement('div');
|
||||
wrap.id = 'cookie-banner';
|
||||
wrap.setAttribute('role', 'dialog');
|
||||
wrap.setAttribute('aria-live', 'polite');
|
||||
wrap.setAttribute('aria-label', 'Cookie consent');
|
||||
wrap.innerHTML =
|
||||
'<div class="cookie-banner__inner">' +
|
||||
'<p class="cookie-banner__text">We use analytics cookies to understand how the site is used. ' +
|
||||
'They are optional — the site works fine without them. ' +
|
||||
'See our <a href="/privacy">Privacy Policy</a>.</p>' +
|
||||
'<div class="cookie-banner__actions">' +
|
||||
'<button type="button" class="cookie-banner__btn cookie-banner__btn--ghost" data-consent="declined">Decline</button>' +
|
||||
'<button type="button" class="cookie-banner__btn cookie-banner__btn--solid" data-consent="accepted">Accept</button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
wrap.addEventListener('click', function (e) {
|
||||
var t = e.target.closest('[data-consent]');
|
||||
if (t) decide(t.getAttribute('data-consent'));
|
||||
});
|
||||
document.body.appendChild(wrap);
|
||||
var first = wrap.querySelector('button');
|
||||
if (first) first.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
function start() {
|
||||
var choice = stored();
|
||||
if (choice === 'accepted') { loadAnalytics(); return; }
|
||||
if (choice === 'declined') return;
|
||||
banner();
|
||||
/* The landing page is a hydrated React app: hydration can discard nodes that
|
||||
were appended to <body> beforehand. Re-insert once if that happens. */
|
||||
setTimeout(function () {
|
||||
if (!stored() && !document.getElementById('cookie-banner')) banner();
|
||||
}, 1200);
|
||||
}
|
||||
|
||||
/* Let other pages re-open the banner (e.g. a "Cookie settings" link). */
|
||||
window.arikiCookieSettings = function () {
|
||||
try { localStorage.removeItem(KEY); } catch (e) {}
|
||||
if (!document.getElementById('cookie-banner')) banner();
|
||||
};
|
||||
|
||||
/* Wait for full load so React hydration has settled before we touch <body>. */
|
||||
if (document.readyState === 'complete') {
|
||||
start();
|
||||
} else {
|
||||
window.addEventListener('load', start);
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user