From aa9088723077e2c558fba8ad4a4b20724be9457d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Mar 2026 22:05:41 +0000 Subject: [PATCH 1/6] Add cookie consent banner, Privacy Policy page, and GDPR-compliant GA4 setup Agent-Logs-Url: https://github.com/LookAtWhatAiCanDo/www/sessions/d6f98ac4-9a50-495e-87f1-2965b564afa2 Co-authored-by: paulpv <1393897+paulpv@users.noreply.github.com> --- public/css/style.css | 106 +++++++++++++++++ public/index.html | 20 +++- public/js/consent.js | 58 ++++++++++ public/privacy.html | 262 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 443 insertions(+), 3 deletions(-) create mode 100644 public/js/consent.js create mode 100644 public/privacy.html diff --git a/public/css/style.css b/public/css/style.css index 93dee54..578c1e5 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -798,6 +798,96 @@ section { color: var(--muted); } +.footer-right { + display: flex; + align-items: center; + gap: 0.6rem; +} + +.footer-sep { + color: var(--border); +} + +.footer a { + color: var(--dim); + transition: color 0.2s; +} + +.footer a:hover { + color: var(--accent); +} + +/* ============================================= + COOKIE CONSENT BANNER + ============================================= */ +.cookie-banner { + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 200; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1.5rem; + padding: 1.25rem 2.5rem; + background: var(--dark); + border-top: 1px solid var(--border); + flex-wrap: wrap; +} + +.cookie-banner[hidden] { display: none; } + +.cookie-text { + font-size: 0.78rem; + color: var(--dim); + line-height: 1.6; + flex: 1; + min-width: 0; +} + +.cookie-text a { + color: var(--accent); + text-decoration: underline; + margin-left: 0.4rem; +} + +.cookie-actions { + display: flex; + gap: 0.75rem; + flex-shrink: 0; +} + +.cookie-btn { + font-family: var(--font-mono); + font-size: 0.72rem; + letter-spacing: 0.1em; + text-transform: uppercase; + padding: 0.55rem 1.25rem; + border: 1px solid var(--border); + background: transparent; + color: var(--dim); + cursor: pointer; + transition: color 0.2s, border-color 0.2s, background 0.2s; +} + +.cookie-btn:hover { + color: var(--text); + border-color: var(--muted); +} + +.cookie-btn-accept { + background: var(--accent); + color: var(--black); + border-color: var(--accent); +} + +.cookie-btn-accept:hover { + background: var(--white); + border-color: var(--white); + color: var(--black); +} + /* ============================================= ANIMATIONS ============================================= */ @@ -878,8 +968,24 @@ section { text-align: center; } + .footer-right { + justify-content: center; + } + .projects-footer { flex-direction: column; align-items: flex-start; } + + .cookie-banner { + padding: 1.25rem 1.5rem; + flex-direction: column; + align-items: flex-start; + gap: 1rem; + } + + .cookie-actions { + width: 100%; + justify-content: flex-end; + } } diff --git a/public/index.html b/public/index.html index ad6b120..3a8529e 100644 --- a/public/index.html +++ b/public/index.html @@ -11,14 +11,13 @@ - + @@ -243,10 +242,25 @@

Ready to see what AI can do for you?

© 2026 LookAtWhatAiCanDo, LLC + + + + diff --git a/public/js/consent.js b/public/js/consent.js new file mode 100644 index 0000000..c3f903b --- /dev/null +++ b/public/js/consent.js @@ -0,0 +1,58 @@ +/* ============================================= + consent.js — LookAtWhatAiCanDo + Cookie consent management. + - Checks localStorage for a stored decision. + - Shows/hides the consent banner accordingly. + - On accept: upgrades GA4 consent and fires config. + - On decline: stores decision and hides banner. + ============================================= */ + +(function initConsent() { + var STORAGE_KEY = 'cookie_consent'; + var GA_ID = 'G-QNSWDZWZ74'; + + function enableAnalytics() { + if (typeof gtag !== 'function') return; + gtag('consent', 'update', { 'analytics_storage': 'granted' }); + gtag('config', GA_ID); + } + + function showBanner() { + var banner = document.getElementById('cookie-banner'); + if (banner) banner.removeAttribute('hidden'); + } + + function hideBanner() { + var banner = document.getElementById('cookie-banner'); + if (banner) banner.setAttribute('hidden', ''); + } + + // Apply any previously stored decision immediately + var stored = localStorage.getItem(STORAGE_KEY); + if (stored === 'accepted') { + enableAnalytics(); + } else if (!stored) { + // No decision yet — show the banner + showBanner(); + } + // 'declined' — do nothing (analytics stays denied) + + // Wire up banner buttons + var acceptBtn = document.getElementById('cookie-accept'); + var declineBtn = document.getElementById('cookie-decline'); + + if (acceptBtn) { + acceptBtn.addEventListener('click', function () { + localStorage.setItem(STORAGE_KEY, 'accepted'); + enableAnalytics(); + hideBanner(); + }); + } + + if (declineBtn) { + declineBtn.addEventListener('click', function () { + localStorage.setItem(STORAGE_KEY, 'declined'); + hideBanner(); + }); + } +}()); diff --git a/public/privacy.html b/public/privacy.html new file mode 100644 index 0000000..392fdb0 --- /dev/null +++ b/public/privacy.html @@ -0,0 +1,262 @@ + + + + + + Privacy Policy — LookAtWhatAiCanDo + + + + + + + + + + + + +
+ + + + +
+ ← Back to Home + +
// Legal
+

Privacy Policy

+

Effective date: March 28, 2026

+ +
+

01 — Who We Are

+

+ LookAtWhatAiCanDo, LLC ("we", "our", "us") operates the website at + lookatwhataicando.llc. + We are an AI consulting company. Questions about this policy can be sent to + hello@lookatwhataicando.llc. +

+
+ +
+

02 — What Data We Collect

+

This site does not collect personal information directly. When you consent to analytics, Google Analytics 4 may collect:

+ +

We do not collect your name, email address, or any other identifying information unless you contact us directly.

+
+ +
+

03 — Cookies

+

+ This site stores your consent choice in your browser's localStorage under the key + cookie_consent. This is a local browser storage entry — not an HTTP cookie — and + is never transmitted to our servers. +

+

+ If you accept analytics, Google Analytics 4 sets HTTP cookies (e.g. _ga, _ga_*) + to distinguish visitors and sessions. These are third-party cookies subject to + Google's Privacy Policy. +

+

+ If you decline, no analytics cookies are set. +

+
+ +
+

04 — How We Use Your Data

+

Analytics data is used solely to understand how visitors use this site so we can improve it. We do not:

+ +
+ +
+

05 — Third-Party Services

+

+ We use Google Analytics 4 (Google LLC, USA). When analytics is enabled, data is transferred to + Google servers in the United States under Google's standard contractual clauses. For details, see + Google's Privacy Policy and + Google Analytics data safeguards. +

+

+ This site also loads fonts from Google Fonts. Google may log your IP address when + serving font files. See + Google Fonts privacy FAQ. +

+
+ +
+

06 — Data Retention

+

+ Google Analytics retains event-level data for 14 months by default. Aggregated reports may be retained + indefinitely. You can request deletion of your data at any time by contacting us. +

+
+ +
+

07 — Your Rights

+

Depending on your location, you may have the right to:

+ +

To exercise these rights, email hello@lookatwhataicando.llc.

+
+ +
+

08 — How to Opt Out or Change Your Choice

+

You can change your analytics consent at any time:

+ +
+ +
+

09 — Children's Privacy

+

+ This site is not directed at children under 13 (or under 16 in the EU/UK). We do not knowingly + collect data from children. +

+
+ +
+

10 — Changes to This Policy

+

+ We may update this policy from time to time. The effective date at the top of this page will be + updated when changes are made. Continued use of this site after changes constitutes acceptance of + the revised policy. +

+
+ +
+

11 — Contact

+

+ LookAtWhatAiCanDo, LLC
+ hello@lookatwhataicando.llc +

+
+
+ + + + + + From 57a29b221262e362646065482c4aaeb11065fdc3 Mon Sep 17 00:00:00 2001 From: Paul Peavyhouse Date: Sat, 28 Mar 2026 16:56:16 -0700 Subject: [PATCH 2/6] Update public/privacy.html Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- public/privacy.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/privacy.html b/public/privacy.html index 392fdb0..58245e6 100644 --- a/public/privacy.html +++ b/public/privacy.html @@ -156,7 +156,9 @@

03 — Cookies

If you accept analytics, Google Analytics 4 sets HTTP cookies (e.g. _ga, _ga_*) - to distinguish visitors and sessions. These are third-party cookies subject to + to distinguish visitors and sessions. In our standard setup these are first-party cookies set on + this domain in connection with a service provided by Google, and data collected via these cookies is + subject to Google's Privacy Policy.

From c573eaaa45dc1e06a27b120728d8eb607cd2e229 Mon Sep 17 00:00:00 2001 From: Paul Peavyhouse Date: Sat, 28 Mar 2026 16:57:33 -0700 Subject: [PATCH 3/6] Update public/index.html Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- public/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/index.html b/public/index.html index 3a8529e..6a8bdbe 100644 --- a/public/index.html +++ b/public/index.html @@ -255,8 +255,8 @@

Ready to see what AI can do for you?

Privacy Policy

From 402c82ea4d00b99454dc3d98ee9ca0194897ba6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Mar 2026 23:57:35 +0000 Subject: [PATCH 4/6] Move privacy.html inline styles to style.css Agent-Logs-Url: https://github.com/LookAtWhatAiCanDo/www/sessions/4798a675-8dd7-47be-b5a4-dd16e585ddfa Co-authored-by: paulpv <1393897+paulpv@users.noreply.github.com> --- public/css/style.css | 90 ++++++++++++++++++++++++++++++++++++++++++++ public/privacy.html | 90 -------------------------------------------- 2 files changed, 90 insertions(+), 90 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 578c1e5..7fb0b6b 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -888,6 +888,92 @@ section { color: var(--black); } +/* ============================================= + PRIVACY POLICY PAGE + ============================================= */ +.policy-wrap { + max-width: 760px; + margin: 0 auto; + padding: 7rem 2.5rem 6rem; +} + +.policy-wrap h1 { + font-family: var(--font-display); + font-size: clamp(3rem, 6vw, 5.5rem); + line-height: 1; + color: var(--white); + letter-spacing: 0.02em; + margin-bottom: 0.5rem; +} + +.policy-effective { + font-size: 0.72rem; + letter-spacing: 0.15em; + text-transform: uppercase; + color: var(--dim); + margin-bottom: 3.5rem; +} + +.policy-section { + margin-bottom: 2.5rem; + border-top: 1px solid var(--border); + padding-top: 2rem; +} + +.policy-section h2 { + font-family: var(--font-mono); + font-size: 0.75rem; + font-weight: 500; + letter-spacing: 0.2em; + text-transform: uppercase; + color: var(--accent); + margin-bottom: 1rem; +} + +.policy-section p, +.policy-section li { + font-size: 0.88rem; + color: var(--dim); + line-height: 1.9; + margin-bottom: 0.8rem; +} + +.policy-section ul { + list-style: none; + padding: 0; +} + +.policy-section ul li::before { + content: '→ '; + color: var(--accent); +} + +.policy-section a { + color: var(--accent); + text-decoration: underline; +} + +.policy-section a:hover { + color: var(--white); +} + +.policy-back { + display: inline-block; + font-size: 0.75rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--dim); + border-bottom: 1px solid var(--border); + padding-bottom: 0.2rem; + margin-bottom: 3rem; + transition: color 0.2s, border-color 0.2s; +} + +.policy-back:hover { + color: var(--accent); + border-color: var(--accent); +} + /* ============================================= ANIMATIONS ============================================= */ @@ -988,4 +1074,8 @@ section { width: 100%; justify-content: flex-end; } + + .policy-wrap { + padding: 6rem 1.5rem 4rem; + } } diff --git a/public/privacy.html b/public/privacy.html index 58245e6..1262848 100644 --- a/public/privacy.html +++ b/public/privacy.html @@ -11,96 +11,6 @@ - From ffaa46034bee76a5f82da8012f4ce6959992c695 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:05:45 +0000 Subject: [PATCH 5/6] Wrap localStorage in try/catch and gate event tracking on consent flag Agent-Logs-Url: https://github.com/LookAtWhatAiCanDo/www/sessions/37120059-acd1-4ea7-8df6-de1bd3df7542 Co-authored-by: paulpv <1393897+paulpv@users.noreply.github.com> --- public/js/app.js | 7 ++++--- public/js/consent.js | 12 +++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index 2ac01d0..e129574 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -55,7 +55,7 @@ function renderProjects(projects, fromCache = false) { // Track project card clicks card.addEventListener('click', () => { - if (typeof gtag !== 'undefined') { + if (window.analyticsConsented && typeof gtag === 'function') { gtag('event', 'project_click', { 'project_name': p.name, 'project_url': p.url @@ -117,11 +117,10 @@ loadProjects(); // ── Analytics Event Tracking ────────────────────────────────────────────────── (function initAnalyticsTracking() { - if (typeof gtag === 'undefined') return; - // Track CTA button clicks document.querySelectorAll('.btn-primary, .btn-ghost').forEach(btn => { btn.addEventListener('click', () => { + if (!window.analyticsConsented || typeof gtag !== 'function') return; gtag('event', 'cta_click', { 'button_text': btn.textContent.trim(), 'button_href': btn.getAttribute('href') @@ -133,6 +132,7 @@ loadProjects(); const emailLink = document.querySelector('a[href^="mailto:"]'); if (emailLink) { emailLink.addEventListener('click', () => { + if (!window.analyticsConsented || typeof gtag !== 'function') return; gtag('event', 'contact_email_click', { 'contact_method': 'email_link' }); @@ -142,6 +142,7 @@ loadProjects(); // Track social media link clicks document.querySelectorAll('.social-link').forEach(link => { link.addEventListener('click', () => { + if (!window.analyticsConsented || typeof gtag !== 'function') return; gtag('event', 'social_click', { 'platform': link.querySelector('.social-name')?.textContent || 'unknown', 'url': link.getAttribute('href') diff --git a/public/js/consent.js b/public/js/consent.js index c3f903b..3308c5a 100644 --- a/public/js/consent.js +++ b/public/js/consent.js @@ -15,6 +15,7 @@ if (typeof gtag !== 'function') return; gtag('consent', 'update', { 'analytics_storage': 'granted' }); gtag('config', GA_ID); + window.analyticsConsented = true; } function showBanner() { @@ -28,7 +29,12 @@ } // Apply any previously stored decision immediately - var stored = localStorage.getItem(STORAGE_KEY); + var stored = null; + try { + stored = localStorage.getItem(STORAGE_KEY); + } catch (e) { + // Storage unavailable — treat as no decision + } if (stored === 'accepted') { enableAnalytics(); } else if (!stored) { @@ -43,7 +49,7 @@ if (acceptBtn) { acceptBtn.addEventListener('click', function () { - localStorage.setItem(STORAGE_KEY, 'accepted'); + try { localStorage.setItem(STORAGE_KEY, 'accepted'); } catch (e) {} enableAnalytics(); hideBanner(); }); @@ -51,7 +57,7 @@ if (declineBtn) { declineBtn.addEventListener('click', function () { - localStorage.setItem(STORAGE_KEY, 'declined'); + try { localStorage.setItem(STORAGE_KEY, 'declined'); } catch (e) {} hideBanner(); }); } From 80574cc4eab7f65e2a68a128eab29b4e59e0ac32 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 29 Mar 2026 02:56:56 +0000 Subject: [PATCH 6/6] Fix consent persistence gate; add canTrack() helper in app.js Agent-Logs-Url: https://github.com/LookAtWhatAiCanDo/www/sessions/fcb19d10-e916-4962-aea3-22b9ad64c958 Co-authored-by: paulpv <1393897+paulpv@users.noreply.github.com> --- public/js/app.js | 13 +++++++++---- public/js/consent.js | 5 +++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index e129574..130db02 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -4,6 +4,11 @@ generated at deploy time by GitHub Actions. ============================================= */ +// Returns true if the user has consented to tracking and gtag is available, false otherwise. +function canTrack() { + return window.analyticsConsented === true && typeof gtag === 'function'; +} + function timeAgo(dateString) { if (!dateString) return null; const date = new Date(dateString); @@ -55,7 +60,7 @@ function renderProjects(projects, fromCache = false) { // Track project card clicks card.addEventListener('click', () => { - if (window.analyticsConsented && typeof gtag === 'function') { + if (canTrack()) { gtag('event', 'project_click', { 'project_name': p.name, 'project_url': p.url @@ -120,7 +125,7 @@ loadProjects(); // Track CTA button clicks document.querySelectorAll('.btn-primary, .btn-ghost').forEach(btn => { btn.addEventListener('click', () => { - if (!window.analyticsConsented || typeof gtag !== 'function') return; + if (!canTrack()) return; gtag('event', 'cta_click', { 'button_text': btn.textContent.trim(), 'button_href': btn.getAttribute('href') @@ -132,7 +137,7 @@ loadProjects(); const emailLink = document.querySelector('a[href^="mailto:"]'); if (emailLink) { emailLink.addEventListener('click', () => { - if (!window.analyticsConsented || typeof gtag !== 'function') return; + if (!canTrack()) return; gtag('event', 'contact_email_click', { 'contact_method': 'email_link' }); @@ -142,7 +147,7 @@ loadProjects(); // Track social media link clicks document.querySelectorAll('.social-link').forEach(link => { link.addEventListener('click', () => { - if (!window.analyticsConsented || typeof gtag !== 'function') return; + if (!canTrack()) return; gtag('event', 'social_click', { 'platform': link.querySelector('.social-name')?.textContent || 'unknown', 'url': link.getAttribute('href') diff --git a/public/js/consent.js b/public/js/consent.js index 3308c5a..303a260 100644 --- a/public/js/consent.js +++ b/public/js/consent.js @@ -49,8 +49,9 @@ if (acceptBtn) { acceptBtn.addEventListener('click', function () { - try { localStorage.setItem(STORAGE_KEY, 'accepted'); } catch (e) {} - enableAnalytics(); + var saved = false; + try { localStorage.setItem(STORAGE_KEY, 'accepted'); saved = true; } catch (e) {} + if (saved) enableAnalytics(); hideBanner(); }); }