From cdf290b235c4b8e2ef079007300d3c4be29ff9f7 Mon Sep 17 00:00:00 2001 From: Kuo Ming Chen Date: Sat, 28 Feb 2026 12:33:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(cookie):=20=E6=96=B0=E5=A2=9E=20Cookie=20?= =?UTF-8?q?=E5=90=8C=E6=84=8F=E5=BD=88=E7=AA=97=E8=88=87=E7=8B=80=E6=85=8B?= =?UTF-8?q?=E8=A8=98=E6=86=B6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 13 +++++++ src/cookie-consent.css | 88 ++++++++++++++++++++++++++++++++++++++++++ src/cookie-consent.js | 42 ++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 src/cookie-consent.css create mode 100644 src/cookie-consent.js diff --git a/index.html b/index.html index 4aa07c4..7ee62a3 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,19 @@
+ + + + diff --git a/src/cookie-consent.css b/src/cookie-consent.css new file mode 100644 index 0000000..9331e45 --- /dev/null +++ b/src/cookie-consent.css @@ -0,0 +1,88 @@ +#cookie-banner { + position: fixed; + bottom: 24px; + right: 24px; + z-index: 99999; + background-color: #ffffff; + color: #1e293b; + width: 100%; + max-width: 400px; + padding: 24px; + border-radius: 12px; + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1); + display: none; + flex-direction: column; + gap: 16px; + border: 1px solid #e2e8f0; + font-family: 'Inter', 'Noto Sans TC', sans-serif; + + /* Initial hidden state */ + opacity: 0; + transform: translateY(30px); + transition: opacity 0.4s ease, transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); +} + +#cookie-banner.visible { + opacity: 1; + transform: translateY(0); +} + +.cookie-banner-content h3 { + margin: 0 0 8px 0; + font-size: 1.125rem; + font-weight: 600; + color: #0f172a; +} + +.cookie-banner-content p { + margin: 0; + font-size: 0.875rem; + line-height: 1.5; + color: #475569; +} + +.cookie-banner-actions { + display: flex; + gap: 12px; + justify-content: flex-end; +} + +.cookie-btn { + padding: 8px 16px; + border-radius: 6px; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + border: none; + font-family: inherit; +} + +.cookie-btn.btn-primary { + background-color: #3b82f6; + color: #ffffff; +} + +.cookie-btn.btn-primary:hover { + background-color: #2563eb; +} + +.cookie-btn.btn-secondary { + background-color: #f1f5f9; + color: #475569; +} + +.cookie-btn.btn-secondary:hover { + background-color: #e2e8f0; +} + +/* RWD */ +@media (max-width: 480px) { + #cookie-banner { + bottom: 16px; + right: 16px; + left: 16px; + width: auto; + max-width: none; + } +} diff --git a/src/cookie-consent.js b/src/cookie-consent.js new file mode 100644 index 0000000..d83d646 --- /dev/null +++ b/src/cookie-consent.js @@ -0,0 +1,42 @@ +import './cookie-consent.css'; + +const COOKIE_CONSENT_ACKNOWLEDGED = 'COOKIE_CONSENT_ACKNOWLEDGED'; + +function initCookieConsent() { + const banner = document.getElementById('cookie-banner'); + if (!banner) return; + + const hasConsented = localStorage.getItem(COOKIE_CONSENT_ACKNOWLEDGED); + if (hasConsented) { + return; + } + + // Show banner + banner.style.display = 'flex'; + + // Wait for next frame to trigger CSS transition + requestAnimationFrame(() => { + requestAnimationFrame(() => { + banner.classList.add('visible'); + }); + }); + + const hideBanner = (value) => { + localStorage.setItem(COOKIE_CONSENT_ACKNOWLEDGED, value); + banner.classList.remove('visible'); + banner.addEventListener('transitionend', () => { + if (!banner.classList.contains('visible')) { + banner.style.display = 'none'; + } + }, { once: true }); + }; + + document.getElementById('cookie-btn-accept')?.addEventListener('click', () => hideBanner('true')); + document.getElementById('cookie-btn-decline')?.addEventListener('click', () => hideBanner('false')); +} + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initCookieConsent); +} else { + initCookieConsent(); +}