chore: polish cookie banner — animation, UX, accessibility
- Slide-up entrance / slide-down exit animations (CSS keyframes) - Upward box-shadow for visual depth from page content - Larger toggle (44×24px) for easier tap target on mobile - Split dual-state Manage/Save into: header button Manage↔Close + panel Save choices button - ARIA: role=dialog, role=switch, aria-checked on toggle, focus moves to prefs on open - Use btn-outline / btn component classes for secondary/primary buttons Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,47 +1,99 @@
|
||||
{# Cookie consent banner — included in base.html before </body>.
|
||||
JS-driven: hidden by default, shown only if cookie_consent cookie is absent.
|
||||
No-JS behaviour: banner stays hidden → only essential cookies are set (privacy-safe default). #}
|
||||
<div id="cookie-banner" style="display:none;position:fixed;bottom:0;left:0;right:0;z-index:200;background:#fff;border-top:1px solid #E2E8F0">
|
||||
<div class="container-page" style="padding-top:0.875rem;padding-bottom:0.875rem">
|
||||
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:1rem;flex-wrap:wrap">
|
||||
<div style="flex:1;min-width:260px">
|
||||
<p style="font-size:0.8125rem;color:#475569;margin:0 0 4px">
|
||||
We use cookies for essential site functions.
|
||||
<a href="{{ url_for('public.privacy') }}#cookies" style="text-decoration:underline;color:#1D4ED8">Learn more</a>
|
||||
No-JS behaviour: banner stays hidden → only essential cookies set (privacy-safe default). #}
|
||||
<style>
|
||||
@keyframes cb-slide-up { from { transform:translateY(100%); opacity:0 } to { transform:translateY(0); opacity:1 } }
|
||||
@keyframes cb-slide-down { from { transform:translateY(0); opacity:1 } to { transform:translateY(100%); opacity:0 } }
|
||||
#cookie-banner.cb-enter { animation: cb-slide-up 0.35s cubic-bezier(0.22,1,0.36,1) forwards }
|
||||
#cookie-banner.cb-exit { animation: cb-slide-down 0.25s ease-in forwards }
|
||||
#cookie-manage-btn:hover { background:#F1F5F9!important; border-color:#CBD5E1!important }
|
||||
#cookie-save-btn:hover { border-color:#1D4ED8!important; color:#1D4ED8!important }
|
||||
#cookie-functional-label:focus-within #cookie-track { outline:2px solid #1D4ED8; outline-offset:2px }
|
||||
</style>
|
||||
|
||||
<div id="cookie-banner"
|
||||
role="dialog" aria-label="Cookie consent"
|
||||
style="display:none;position:fixed;bottom:0;left:0;right:0;z-index:200;
|
||||
background:#fff;border-top:1px solid #E2E8F0;
|
||||
box-shadow:0 -4px 32px rgba(15,23,42,0.09)">
|
||||
<div class="container-page" style="padding-top:1rem;padding-bottom:1rem">
|
||||
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:1.25rem;flex-wrap:wrap">
|
||||
|
||||
<!-- Left: copy + collapsible prefs -->
|
||||
<div style="flex:1;min-width:240px">
|
||||
<p style="font-size:0.8125rem;color:#475569;margin:0;line-height:1.55">
|
||||
We use cookies to keep you signed in and improve the site.
|
||||
<a href="{{ url_for('public.privacy') }}#cookies"
|
||||
style="color:#1D4ED8;text-decoration:underline;text-underline-offset:2px">Cookie policy</a>
|
||||
</p>
|
||||
<!-- Preferences panel, collapsed by default -->
|
||||
|
||||
<!-- Preferences panel (collapsed by default) -->
|
||||
<div id="cookie-prefs" hidden
|
||||
style="margin-top:0.75rem;padding:0.75rem;border:1px solid #E2E8F0;border-radius:10px;background:#F8FAFC">
|
||||
<!-- Essential row (locked) -->
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:2px">
|
||||
<span style="font-size:0.8125rem;font-weight:600;color:#1E293B">Essential</span>
|
||||
<span style="font-size:0.75rem;color:#16A34A;font-weight:600">Always on</span>
|
||||
style="margin-top:0.875rem;padding:0.875rem;
|
||||
border:1px solid #E2E8F0;border-radius:12px;background:#F8FAFC">
|
||||
|
||||
<!-- Essential row -->
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;
|
||||
padding-bottom:0.75rem;margin-bottom:0.75rem;border-bottom:1px solid #F1F5F9">
|
||||
<div>
|
||||
<div style="font-size:0.8125rem;font-weight:600;color:#0F172A;margin-bottom:2px">Essential</div>
|
||||
<div style="font-size:0.75rem;color:#64748B;line-height:1.4">Session management. Always required.</div>
|
||||
</div>
|
||||
<span style="font-size:0.625rem;font-weight:700;letter-spacing:0.06em;color:#16A34A;
|
||||
background:#F0FDF4;border:1px solid #BBF7D0;border-radius:6px;
|
||||
padding:3px 8px;flex-shrink:0;margin-top:2px">ON</span>
|
||||
</div>
|
||||
<p style="font-size:0.75rem;color:#64748B;margin:0 0 10px">Session management. Cannot be disabled.</p>
|
||||
<!-- Functional row (toggle) -->
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:2px">
|
||||
<span style="font-size:0.8125rem;font-weight:600;color:#1E293B">Functional</span>
|
||||
|
||||
<!-- Functional row -->
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:0.75rem">
|
||||
<div style="flex:1">
|
||||
<div style="font-size:0.8125rem;font-weight:600;color:#0F172A;margin-bottom:2px">Functional</div>
|
||||
<div style="font-size:0.75rem;color:#64748B;line-height:1.4">A/B testing to improve the experience.</div>
|
||||
</div>
|
||||
<!-- Toggle switch -->
|
||||
<label style="position:relative;display:inline-block;width:36px;height:20px;cursor:pointer;flex-shrink:0">
|
||||
<input type="checkbox" id="cookie-functional" style="opacity:0;width:0;height:0;position:absolute">
|
||||
<span id="cookie-track" style="position:absolute;inset:0;background:#CBD5E1;border-radius:20px;transition:background 0.2s"></span>
|
||||
<span id="cookie-dot" style="position:absolute;top:2px;left:2px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform 0.2s;box-shadow:0 1px 3px rgba(0,0,0,.2)"></span>
|
||||
<label id="cookie-functional-label"
|
||||
style="position:relative;display:inline-block;width:44px;height:24px;
|
||||
cursor:pointer;flex-shrink:0;margin-top:1px">
|
||||
<input type="checkbox" id="cookie-functional"
|
||||
role="switch" aria-checked="false" aria-label="Functional cookies"
|
||||
style="opacity:0;width:0;height:0;position:absolute">
|
||||
<span id="cookie-track"
|
||||
style="position:absolute;inset:0;background:#CBD5E1;border-radius:24px;
|
||||
transition:background 0.2s ease;border-radius:99px"></span>
|
||||
<span id="cookie-dot"
|
||||
style="position:absolute;top:3px;left:3px;width:18px;height:18px;
|
||||
background:#fff;border-radius:50%;
|
||||
box-shadow:0 1px 4px rgba(0,0,0,0.22);
|
||||
transition:transform 0.2s ease"></span>
|
||||
</label>
|
||||
</div>
|
||||
<p style="font-size:0.75rem;color:#64748B;margin:0">A/B testing to improve the site experience.</p>
|
||||
|
||||
<!-- Save choices -->
|
||||
<div style="margin-top:0.875rem;display:flex;justify-content:flex-end">
|
||||
<button type="button" id="cookie-save-btn"
|
||||
style="font-size:0.8125rem;font-family:inherit;cursor:pointer;
|
||||
padding:6px 16px;border:1px solid #E2E8F0;border-radius:8px;
|
||||
background:white;color:#475569;
|
||||
transition:border-color 0.15s ease,color 0.15s ease">
|
||||
Save choices
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Action buttons -->
|
||||
<div style="display:flex;gap:8px;flex-shrink:0;align-items:flex-start;padding-top:2px">
|
||||
<button type="button" id="cookie-manage-btn"
|
||||
style="font-size:0.8125rem;padding:7px 14px;border:1px solid #E2E8F0;border-radius:10px;background:white;cursor:pointer;color:#475569;font-family:inherit;white-space:nowrap">
|
||||
Manage preferences
|
||||
|
||||
<!-- Right: action buttons -->
|
||||
<div style="display:flex;gap:8px;flex-shrink:0;align-items:flex-start;padding-top:1px">
|
||||
<button type="button" id="cookie-manage-btn" class="btn-outline btn-sm"
|
||||
style="font-size:0.8125rem;white-space:nowrap;padding:8px 16px;
|
||||
border-radius:10px;transition:background 0.15s,border-color 0.15s">
|
||||
Manage
|
||||
</button>
|
||||
<button type="button" id="cookie-accept-btn" class="btn"
|
||||
style="font-size:0.8125rem;padding:7px 14px;white-space:nowrap">
|
||||
<button type="button" id="cookie-accept-btn" class="btn btn-sm"
|
||||
style="font-size:0.8125rem;white-space:nowrap;padding:8px 18px;border-radius:10px">
|
||||
Accept all
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,48 +101,57 @@
|
||||
<script>
|
||||
(function () {
|
||||
var COOKIE_NAME = 'cookie_consent';
|
||||
var MAX_AGE = 365 * 24 * 60 * 60;
|
||||
var banner = document.getElementById('cookie-banner');
|
||||
var MAX_AGE = 365 * 24 * 60 * 60;
|
||||
var banner = document.getElementById('cookie-banner');
|
||||
|
||||
// Show only if no consent cookie exists yet
|
||||
// Already consented — bail out
|
||||
if (document.cookie.split(';').some(function (c) {
|
||||
return c.trim().startsWith(COOKIE_NAME + '=');
|
||||
})) return;
|
||||
banner.style.display = 'block';
|
||||
|
||||
var prefs = document.getElementById('cookie-prefs');
|
||||
var checkbox = document.getElementById('cookie-functional');
|
||||
var track = document.getElementById('cookie-track');
|
||||
var dot = document.getElementById('cookie-dot');
|
||||
// Show with slide-up animation
|
||||
banner.style.display = 'block';
|
||||
requestAnimationFrame(function () { banner.classList.add('cb-enter'); });
|
||||
|
||||
var prefs = document.getElementById('cookie-prefs');
|
||||
var checkbox = document.getElementById('cookie-functional');
|
||||
var track = document.getElementById('cookie-track');
|
||||
var dot = document.getElementById('cookie-dot');
|
||||
var manageBtn = document.getElementById('cookie-manage-btn');
|
||||
var saveBtn = document.getElementById('cookie-save-btn');
|
||||
var acceptBtn = document.getElementById('cookie-accept-btn');
|
||||
var panelOpen = false;
|
||||
|
||||
function updateToggle() {
|
||||
track.style.background = checkbox.checked ? '#1D4ED8' : '#CBD5E1';
|
||||
dot.style.transform = checkbox.checked ? 'translateX(16px)' : 'translateX(0)';
|
||||
var on = checkbox.checked;
|
||||
track.style.background = on ? '#1D4ED8' : '#CBD5E1';
|
||||
dot.style.transform = on ? 'translateX(20px)' : 'translateX(0)';
|
||||
checkbox.setAttribute('aria-checked', on ? 'true' : 'false');
|
||||
}
|
||||
checkbox.addEventListener('change', updateToggle);
|
||||
|
||||
manageBtn.addEventListener('click', function () {
|
||||
if (!panelOpen) {
|
||||
prefs.hidden = false;
|
||||
manageBtn.textContent = 'Save preferences';
|
||||
panelOpen = true;
|
||||
} else {
|
||||
var cats = ['essential'];
|
||||
if (checkbox.checked) cats.push('functional');
|
||||
setConsent(cats.join(','));
|
||||
}
|
||||
var opening = prefs.hidden;
|
||||
prefs.hidden = !opening;
|
||||
manageBtn.textContent = opening ? 'Close' : 'Manage';
|
||||
if (opening) prefs.querySelector('input').focus();
|
||||
});
|
||||
|
||||
saveBtn.addEventListener('click', function () {
|
||||
var cats = ['essential'];
|
||||
if (checkbox.checked) cats.push('functional');
|
||||
dismiss(cats.join(','));
|
||||
});
|
||||
|
||||
acceptBtn.addEventListener('click', function () {
|
||||
setConsent('essential,functional');
|
||||
dismiss('essential,functional');
|
||||
});
|
||||
|
||||
function setConsent(value) {
|
||||
document.cookie = COOKIE_NAME + '=' + value + ';path=/;max-age=' + MAX_AGE + ';SameSite=Lax';
|
||||
banner.style.display = 'none';
|
||||
function dismiss(value) {
|
||||
document.cookie = COOKIE_NAME + '=' + value
|
||||
+ ';path=/;max-age=' + MAX_AGE + ';SameSite=Lax';
|
||||
banner.classList.remove('cb-enter');
|
||||
banner.classList.add('cb-exit');
|
||||
setTimeout(function () { banner.style.display = 'none'; }, 280);
|
||||
}
|
||||
}());
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user