Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions public/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,182 @@ 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);
}

/* =============================================
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
============================================= */
Expand Down Expand Up @@ -878,8 +1054,28 @@ 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;
}

.policy-wrap {
padding: 6rem 1.5rem 4rem;
}
}
20 changes: 17 additions & 3 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300&family=Instrument+Serif:ital@0;1&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="css/style.css" />

<!-- Google tag (gtag.js) -->
<!-- Google tag (gtag.js) — analytics_storage denied until user consents -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-QNSWDZWZ74"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());

gtag('config', 'G-QNSWDZWZ74');
gtag('consent', 'default', { 'analytics_storage': 'denied' });
</script>
</head>
<body>
Expand Down Expand Up @@ -243,10 +242,25 @@ <h2 class="section-title">Ready to see what <em>AI can do for you?</em></h2>
© 2026 LookAtWhatAiCanDo, LLC
</div>
<div class="footer-right">
<a href="/privacy.html">Privacy Policy</a>
<span class="footer-sep">·</span>
lookatwhataicando.llc
</div>
</footer>

<!-- COOKIE CONSENT BANNER -->
<div id="cookie-banner" class="cookie-banner" hidden aria-live="polite" role="region" aria-label="Cookie consent">
<p class="cookie-text">
We use <strong>Google Analytics</strong> to understand how visitors use this site. No advertising cookies are set.
<a href="/privacy.html">Privacy Policy</a>
</p>
<div class="cookie-actions">
<button type="button" id="cookie-decline" class="cookie-btn cookie-btn-decline">Decline</button>
<button type="button" id="cookie-accept" class="cookie-btn cookie-btn-accept">Accept</button>
</div>
</div>

<script src="js/consent.js"></script>
<script src="js/app.js"></script>
Comment thread
paulpv marked this conversation as resolved.
</body>
</html>
12 changes: 9 additions & 3 deletions public/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -55,7 +60,7 @@ function renderProjects(projects, fromCache = false) {

// Track project card clicks
card.addEventListener('click', () => {
if (typeof gtag !== 'undefined') {
if (canTrack()) {
gtag('event', 'project_click', {
'project_name': p.name,
'project_url': p.url
Expand Down Expand Up @@ -117,11 +122,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 (!canTrack()) return;
gtag('event', 'cta_click', {
'button_text': btn.textContent.trim(),
'button_href': btn.getAttribute('href')
Expand All @@ -133,6 +137,7 @@ loadProjects();
const emailLink = document.querySelector('a[href^="mailto:"]');
if (emailLink) {
emailLink.addEventListener('click', () => {
if (!canTrack()) return;
gtag('event', 'contact_email_click', {
'contact_method': 'email_link'
});
Expand All @@ -142,6 +147,7 @@ loadProjects();
// Track social media link clicks
document.querySelectorAll('.social-link').forEach(link => {
link.addEventListener('click', () => {
if (!canTrack()) return;
gtag('event', 'social_click', {
'platform': link.querySelector('.social-name')?.textContent || 'unknown',
'url': link.getAttribute('href')
Expand Down
65 changes: 65 additions & 0 deletions public/js/consent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* =============================================
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);
window.analyticsConsented = true;
}

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 = null;
try {
stored = localStorage.getItem(STORAGE_KEY);
} catch (e) {
// Storage unavailable — treat as no decision
}
if (stored === 'accepted') {
enableAnalytics();
Comment thread
paulpv marked this conversation as resolved.
} 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 () {
var saved = false;
try { localStorage.setItem(STORAGE_KEY, 'accepted'); saved = true; } catch (e) {}
if (saved) enableAnalytics();
hideBanner();
});
}

if (declineBtn) {
declineBtn.addEventListener('click', function () {
try { localStorage.setItem(STORAGE_KEY, 'declined'); } catch (e) {}
hideBanner();
});
}
}());
Loading
Loading