Skip to content

Commit 79cdeb8

Browse files
committed
�[38;2;248;248;242mFix reel titles, add GA4 + consent, add /legal�[0m
�[38;2;248;248;242m- Reel titles: video 3 and 4 were labelled backwards after the�[0m �[38;2;248;248;242m earlier swap. 3AVY_8l8sz0 is Trailer 2025 and mt68nUVAWn8 is�[0m �[38;2;248;248;242m the TV promo. Fix in both languages.�[0m �[38;2;248;248;242m- Analytics: add Google Analytics 4 (G-TGK8K3CK84) to Base.astro�[0m �[38;2;248;248;242m with Consent Mode v2 defaulting every category to 'denied' at�[0m �[38;2;248;248;242m page load. If the visitor has already accepted on a previous�[0m �[38;2;248;248;242m visit (localStorage.cookie-consent === 'granted') the default�[0m �[38;2;248;248;242m for analytics_storage is 'granted' instead, so no bounce back�[0m �[38;2;248;248;242m to the banner on subsequent pages.�[0m �[38;2;248;248;242m- Cookie banner: new CookieBanner.astro component wired into�[0m �[38;2;248;248;242m both language home pages and both legal pages. Shows once,�[0m �[38;2;248;248;242m bottom-centre, with Accept and "Only essential" buttons and a�[0m �[38;2;248;248;242m link to the cookies section of the legal page. Persists the�[0m �[38;2;248;248;242m choice and pushes gtag('consent', 'update', ...) accordingly.�[0m �[38;2;248;248;242m- Legal pages: new /legal/ (EN) and /es/legal/ (ES) with three�[0m �[38;2;248;248;242m sections: Legal notice (LSSI-CE 34/2002), Privacy policy�[0m �[38;2;248;248;242m (GDPR + LOPDGDD), Cookies. Covers owner details, data�[0m �[38;2;248;248;242m categories, legal basis, processors (FormSubmit, Google,�[0m �[38;2;248;248;242m GitHub), user rights and how to reopen the banner. Footer�[0m �[38;2;248;248;242m now links to each section.�[0m �[38;2;248;248;242mCo-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>�[0m
1 parent aa341bc commit 79cdeb8

8 files changed

Lines changed: 964 additions & 4 deletions

File tree

src/components/CookieBanner.astro

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
---
2+
export interface Props {
3+
lang: 'es' | 'en';
4+
}
5+
const { lang } = Astro.props;
6+
7+
const t = {
8+
es: {
9+
intro: 'Uso cookies analíticas para saber qué contenidos funcionan y mejorar la web. Nada de publicidad ni de terceros.',
10+
more: 'Más información',
11+
href: '/es/legal/#cookies',
12+
accept: 'Aceptar',
13+
reject: 'Solo esenciales',
14+
label: 'Aviso de cookies',
15+
},
16+
en: {
17+
intro: 'I use analytics cookies to see what works and improve the site. No ads, no third parties.',
18+
more: 'Learn more',
19+
href: '/legal/#cookies',
20+
accept: 'Accept',
21+
reject: 'Only essential',
22+
label: 'Cookie notice',
23+
},
24+
}[lang];
25+
---
26+
27+
<aside
28+
class="cookie-banner"
29+
role="dialog"
30+
aria-label={t.label}
31+
aria-live="polite"
32+
hidden
33+
data-cookie-banner
34+
>
35+
<div class="cookie-banner-inner">
36+
<p class="cookie-banner-copy">
37+
{t.intro}
38+
<a href={t.href} class="cookie-banner-link">{t.more}</a>
39+
</p>
40+
<div class="cookie-banner-actions">
41+
<button type="button" class="cookie-banner-btn cookie-banner-btn-ghost" data-cookie-reject>
42+
{t.reject}
43+
</button>
44+
<button type="button" class="cookie-banner-btn cookie-banner-btn-primary" data-cookie-accept>
45+
{t.accept}
46+
</button>
47+
</div>
48+
</div>
49+
</aside>
50+
51+
<script is:inline>
52+
(function () {
53+
var banner = document.querySelector('[data-cookie-banner]');
54+
if (!banner) return;
55+
56+
var stored = null;
57+
try {
58+
stored = localStorage.getItem('cookie-consent');
59+
} catch (e) {}
60+
61+
if (!stored) {
62+
banner.hidden = false;
63+
// Delay adding the visible class so CSS transition kicks in.
64+
requestAnimationFrame(function () {
65+
banner.classList.add('is-visible');
66+
});
67+
}
68+
69+
function persist(value) {
70+
try {
71+
localStorage.setItem('cookie-consent', value);
72+
} catch (e) {}
73+
}
74+
75+
function close() {
76+
banner.classList.remove('is-visible');
77+
// Wait for the transition to finish before hiding entirely.
78+
setTimeout(function () {
79+
banner.hidden = true;
80+
}, 260);
81+
}
82+
83+
var accept = banner.querySelector('[data-cookie-accept]');
84+
var reject = banner.querySelector('[data-cookie-reject]');
85+
86+
if (accept) {
87+
accept.addEventListener('click', function () {
88+
persist('granted');
89+
if (typeof gtag === 'function') {
90+
gtag('consent', 'update', { analytics_storage: 'granted' });
91+
}
92+
close();
93+
});
94+
}
95+
if (reject) {
96+
reject.addEventListener('click', function () {
97+
persist('denied');
98+
if (typeof gtag === 'function') {
99+
gtag('consent', 'update', { analytics_storage: 'denied' });
100+
}
101+
close();
102+
});
103+
}
104+
})();
105+
</script>
106+
107+
<style>
108+
.cookie-banner {
109+
position: fixed;
110+
left: 50%;
111+
bottom: 1.25rem;
112+
transform: translate(-50%, 130%);
113+
z-index: var(--z-toast);
114+
width: min(calc(100vw - 2rem), 44rem);
115+
padding: 1rem 1.25rem;
116+
background: color-mix(in oklch, var(--surface-raised) 92%, transparent);
117+
border: 1px solid var(--stroke-medium);
118+
border-radius: var(--radius-lg);
119+
box-shadow: 0 30px 80px -20px oklch(0 0 0 / 0.7);
120+
backdrop-filter: saturate(140%) blur(16px);
121+
-webkit-backdrop-filter: saturate(140%) blur(16px);
122+
opacity: 0;
123+
transition: transform 320ms var(--ease-out-quart), opacity 320ms var(--ease-out-quart);
124+
}
125+
126+
.cookie-banner.is-visible {
127+
transform: translate(-50%, 0);
128+
opacity: 1;
129+
}
130+
131+
.cookie-banner-inner {
132+
display: flex;
133+
flex-direction: column;
134+
gap: 0.85rem;
135+
align-items: flex-start;
136+
}
137+
138+
@media (min-width: 720px) {
139+
.cookie-banner-inner {
140+
flex-direction: row;
141+
align-items: center;
142+
gap: 1.25rem;
143+
}
144+
}
145+
146+
.cookie-banner-copy {
147+
flex: 1;
148+
color: var(--ink-body);
149+
font-family: var(--font-body);
150+
font-size: 0.85rem;
151+
line-height: 1.5;
152+
margin: 0;
153+
}
154+
155+
.cookie-banner-link {
156+
color: var(--ink-primary);
157+
text-decoration: underline;
158+
text-decoration-color: var(--crimson);
159+
text-underline-offset: 3px;
160+
transition: color var(--dur-fast);
161+
white-space: nowrap;
162+
margin-left: 0.25rem;
163+
}
164+
165+
.cookie-banner-link:hover { color: var(--crimson); }
166+
167+
.cookie-banner-actions {
168+
display: flex;
169+
align-items: center;
170+
gap: 0.5rem;
171+
flex-shrink: 0;
172+
flex-wrap: wrap;
173+
}
174+
175+
.cookie-banner-btn {
176+
display: inline-flex;
177+
align-items: center;
178+
justify-content: center;
179+
padding: 0.6rem 1rem;
180+
border-radius: var(--radius-pill);
181+
font-family: var(--font-body);
182+
font-weight: 600;
183+
font-size: 0.8rem;
184+
line-height: 1;
185+
cursor: pointer;
186+
border: 1px solid transparent;
187+
transition: background-color var(--dur-fast), border-color var(--dur-fast), color var(--dur-fast), transform var(--dur-fast);
188+
white-space: nowrap;
189+
}
190+
191+
.cookie-banner-btn-primary {
192+
background: var(--crimson);
193+
color: var(--ink-primary);
194+
}
195+
196+
.cookie-banner-btn-primary:hover {
197+
background: var(--crimson-hot);
198+
}
199+
200+
.cookie-banner-btn-ghost {
201+
background: transparent;
202+
color: var(--ink-primary);
203+
border-color: var(--stroke-medium);
204+
}
205+
206+
.cookie-banner-btn-ghost:hover {
207+
background: color-mix(in oklch, var(--surface-lifted) 60%, transparent);
208+
border-color: var(--stroke-strong);
209+
}
210+
</style>

src/components/Footer.astro

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ const t = {
1616
{ label: 'Instagram', href: 'https://instagram.com/davidmaestromago' },
1717
{ label: 'Facebook', href: 'https://facebook.com/davidmaestromago' },
1818
],
19+
legal: [
20+
{ label: 'Aviso legal', href: '/es/legal/#legal-notice' },
21+
{ label: 'Privacidad', href: '/es/legal/#privacy' },
22+
{ label: 'Cookies', href: '/es/legal/#cookies' },
23+
],
1924
back: 'Volver arriba',
2025
},
2126
en: {
@@ -27,6 +32,11 @@ const t = {
2732
{ label: 'Instagram', href: 'https://instagram.com/davidmaestromago' },
2833
{ label: 'Facebook', href: 'https://facebook.com/davidmaestromago' },
2934
],
35+
legal: [
36+
{ label: 'Legal notice', href: '/legal/#legal-notice' },
37+
{ label: 'Privacy', href: '/legal/#privacy' },
38+
{ label: 'Cookies', href: '/legal/#cookies' },
39+
],
3040
back: 'Back to top',
3141
},
3242
}[lang];
@@ -55,6 +65,14 @@ const year = new Date().getFullYear();
5565
<div class="footer-meta">
5666
<span{year} David Maestro · A Coruña</span>
5767
<span>{t.reserved}</span>
68+
<nav class="footer-legal" aria-label="Legal">
69+
{t.legal.map((l, i) => (
70+
<>
71+
{i > 0 && <span aria-hidden="true">·</span>}
72+
<a href={l.href}>{l.label}</a>
73+
</>
74+
))}
75+
</nav>
5876
<a href="#" class="footer-back">
5977
<span>{t.back}</span>
6078
<span aria-hidden="true">↑</span>
@@ -159,6 +177,25 @@ const year = new Date().getFullYear();
159177
color: var(--ink-quiet);
160178
}
161179

180+
.footer-legal {
181+
display: flex;
182+
align-items: center;
183+
gap: 0.5rem;
184+
flex-wrap: wrap;
185+
padding-top: 0.35rem;
186+
}
187+
188+
.footer-legal a {
189+
color: var(--ink-muted);
190+
transition: color var(--dur-fast);
191+
}
192+
193+
.footer-legal a:hover { color: var(--crimson); }
194+
195+
.footer-legal span {
196+
color: var(--ink-quiet);
197+
}
198+
162199
.footer-back {
163200
display: inline-flex;
164201
align-items: center;

src/components/Reel.astro

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ const t = {
1212
videos: [
1313
{ id: 'eROBHCC36xk', title: 'Trailer 2026' },
1414
{ id: 'hrg_9QQrZgQ', title: 'Recap 2026' },
15-
{ id: '3AVY_8l8sz0', title: 'Promo TV' },
16-
{ id: 'mt68nUVAWn8', title: 'Trailer 2025' },
15+
{ id: '3AVY_8l8sz0', title: 'Trailer 2025' },
16+
{ id: 'mt68nUVAWn8', title: 'Promo TV' },
1717
],
1818
},
1919
en: {
@@ -23,8 +23,8 @@ const t = {
2323
videos: [
2424
{ id: 'eROBHCC36xk', title: 'Trailer 2026' },
2525
{ id: 'hrg_9QQrZgQ', title: 'Recap 2026' },
26-
{ id: '3AVY_8l8sz0', title: 'TV Promo' },
27-
{ id: 'mt68nUVAWn8', title: 'Trailer 2025' },
26+
{ id: '3AVY_8l8sz0', title: 'Trailer 2025' },
27+
{ id: 'mt68nUVAWn8', title: 'TV Promo' },
2828
],
2929
},
3030
}[lang];

src/layouts/Base.astro

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,26 @@ const skipCopy = lang === 'es' ? 'Saltar al contenido' : 'Skip to content';
5858
<link rel="apple-touch-icon" sizes="180x180" href="/favicon-180.png" />
5959
<link rel="manifest" href="/site.webmanifest" />
6060

61+
<!-- Google Analytics 4 with Consent Mode v2 (default: denied) -->
62+
<script is:inline>
63+
window.dataLayer = window.dataLayer || [];
64+
function gtag(){dataLayer.push(arguments);}
65+
// Read any previously stored consent so it survives across pages.
66+
var stored = null;
67+
try { stored = localStorage.getItem('cookie-consent'); } catch (e) {}
68+
var granted = stored === 'granted';
69+
gtag('consent', 'default', {
70+
ad_storage: 'denied',
71+
ad_user_data: 'denied',
72+
ad_personalization: 'denied',
73+
analytics_storage: granted ? 'granted' : 'denied',
74+
wait_for_update: 500,
75+
});
76+
gtag('js', new Date());
77+
gtag('config', 'G-TGK8K3CK84', { anonymize_ip: true });
78+
</script>
79+
<script is:inline async src="https://www.googletagmanager.com/gtag/js?id=G-TGK8K3CK84"></script>
80+
6181
<script type="application/ld+json" set:html={JSON.stringify({
6282
'@context': 'https://schema.org',
6383
'@type': 'Person',

src/pages/es/index.astro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Testimonials from '../../components/Testimonials.astro';
1313
import Faq from '../../components/Faq.astro';
1414
import Contact from '../../components/Contact.astro';
1515
import Footer from '../../components/Footer.astro';
16+
import CookieBanner from '../../components/CookieBanner.astro';
1617
1718
const title = 'David Maestro · Mago e ilusionista';
1819
const description = 'David Maestro (A Coruña, 1997). Finalista de Pura Magia (RTVE) y Got Talent España. Mago residente de Virgin Voyages. Magia de escena para teatros y galas, y magia de cerca para bodas, restaurantes y eventos corporativos.';
@@ -34,4 +35,5 @@ const description = 'David Maestro (A Coruña, 1997). Finalista de Pura Magia (R
3435
<Contact lang="es" />
3536
</main>
3637
<Footer lang="es" />
38+
<CookieBanner lang="es" />
3739
</Base>

0 commit comments

Comments
 (0)