@koehler8/cms-ext-compliance 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +68 -0
- package/components/CookieConsent.vue +264 -0
- package/components/Cookies.vue +497 -0
- package/components/Legal.vue +42 -0
- package/components/Privacy.vue +425 -0
- package/components/Terms.vue +852 -0
- package/components/index.js +15 -0
- package/extension.config.json +52 -0
- package/index.js +33 -0
- package/package.json +31 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="legal-page section-shell" data-analytics-section="cookies">
|
|
3
|
+
<div class="container legal-page__container">
|
|
4
|
+
<article class="legal-card">
|
|
5
|
+
<header class="legal-card__header">
|
|
6
|
+
<h2 class="legal-card__title">{{ headerTitle }}</h2>
|
|
7
|
+
<span class="legal-divider" aria-hidden="true"></span>
|
|
8
|
+
<p class="legal-card__meta">Last revised: {{ lastUpdated }}</p>
|
|
9
|
+
</header>
|
|
10
|
+
|
|
11
|
+
<div class="legal-content">
|
|
12
|
+
<section class="consent-panel">
|
|
13
|
+
<h3 class="consent-panel__title">Cookie Consent Management</h3>
|
|
14
|
+
<p>We respect your privacy. When the cookie consent banner is enabled, you'll see a small prompt on the site to accept or decline analytics tracking. Regardless of the banner, you can always manage your preference here.</p>
|
|
15
|
+
<p><strong>Current Status:</strong> <span :class="['consent-status', consentStatusClass]">{{ consentStatusText }}</span></p>
|
|
16
|
+
<p v-if="isAccepted">
|
|
17
|
+
You have accepted cookies and analytics tracking. Use the controls below to revoke that consent whenever you choose.
|
|
18
|
+
</p>
|
|
19
|
+
<p v-else-if="isDeclined">
|
|
20
|
+
You have declined cookies and analytics tracking. You can opt back in at any time using the controls below.
|
|
21
|
+
</p>
|
|
22
|
+
<p v-else>
|
|
23
|
+
You have not yet made a decision about cookies.
|
|
24
|
+
<template v-if="bannerEnabled">
|
|
25
|
+
The consent notice will continue to appear while you browse until you choose an option.
|
|
26
|
+
</template>
|
|
27
|
+
<template v-else>
|
|
28
|
+
Use the controls on this page whenever you are ready to accept or decline.
|
|
29
|
+
</template>
|
|
30
|
+
</p>
|
|
31
|
+
<div class="consent-controls">
|
|
32
|
+
<button
|
|
33
|
+
class="primary-button consent-button"
|
|
34
|
+
:disabled="isAccepted"
|
|
35
|
+
type="button"
|
|
36
|
+
@click="handleAccept"
|
|
37
|
+
>
|
|
38
|
+
Accept Analytics
|
|
39
|
+
</button>
|
|
40
|
+
<button
|
|
41
|
+
class="consent-button consent-button--decline"
|
|
42
|
+
:disabled="isDeclined"
|
|
43
|
+
type="button"
|
|
44
|
+
@click="handleDecline"
|
|
45
|
+
>
|
|
46
|
+
Decline Analytics
|
|
47
|
+
</button>
|
|
48
|
+
</div>
|
|
49
|
+
</section>
|
|
50
|
+
<template v-if="hasCustomContent">
|
|
51
|
+
<p v-if="customIntro">{{ customIntro }}</p>
|
|
52
|
+
<div
|
|
53
|
+
v-if="customBody"
|
|
54
|
+
class="legal-rich-text"
|
|
55
|
+
v-html="customBody"
|
|
56
|
+
/>
|
|
57
|
+
<section
|
|
58
|
+
v-for="(section, index) in customSections"
|
|
59
|
+
:key="section.id || index"
|
|
60
|
+
class="legal-section"
|
|
61
|
+
>
|
|
62
|
+
<h3 v-if="section.title">{{ section.title }}</h3>
|
|
63
|
+
<p v-if="section.summary">{{ section.summary }}</p>
|
|
64
|
+
<div
|
|
65
|
+
v-if="section.body"
|
|
66
|
+
class="legal-rich-text"
|
|
67
|
+
v-html="section.body"
|
|
68
|
+
/>
|
|
69
|
+
<p
|
|
70
|
+
v-for="(paragraph, paragraphIndex) in section.paragraphs"
|
|
71
|
+
:key="`paragraph-${paragraphIndex}`"
|
|
72
|
+
>
|
|
73
|
+
{{ paragraph }}
|
|
74
|
+
</p>
|
|
75
|
+
<ul v-if="section.list.length">
|
|
76
|
+
<li
|
|
77
|
+
v-for="(item, itemIndex) in section.list"
|
|
78
|
+
:key="`item-${itemIndex}`"
|
|
79
|
+
>
|
|
80
|
+
{{ item }}
|
|
81
|
+
</li>
|
|
82
|
+
</ul>
|
|
83
|
+
</section>
|
|
84
|
+
</template>
|
|
85
|
+
<template v-else>
|
|
86
|
+
<p>About this Policy</p>
|
|
87
|
+
<p>Our Privacy Policy explains our principles when it comes to the collection, processing, and
|
|
88
|
+
storage of your personal information. This policy explains the use of cookies in more details,
|
|
89
|
+
such as what cookies are and how they are used. However, to get a full picture of how we
|
|
90
|
+
handle your privacy this policy should be read together with our Privacy Policy.</p>
|
|
91
|
+
<p>What are cookies?</p>
|
|
92
|
+
<p>Cookies are text files, containing small amounts of information, which are downloaded to
|
|
93
|
+
your browsing device, such as your computer, mobile device or smartphone, when you visit
|
|
94
|
+
our website or use our services. Cookies can be recognised by the website that downloaded
|
|
95
|
+
them — or other websites that use the same cookies. This helps websites know if the
|
|
96
|
+
browsing device has visited them before.</p>
|
|
97
|
+
<p>We use two types of cookies: persistent cookies and session cookies. A persistent cookie
|
|
98
|
+
lasts beyond the current session and is used for many purposes, such as recognizing you as
|
|
99
|
+
an existing user, so it’s easier to return to us and interact with our services. Since a
|
|
100
|
+
persistent cookie stays in your browser, it will be read by us when you return to one of our
|
|
101
|
+
sites or visit a third-party site that uses our services. Session cookies last only as long as the
|
|
102
|
+
session (usually the current visit to a website or a browser session).</p>
|
|
103
|
+
<p>Do I need to accept cookies?</p>
|
|
104
|
+
<p>No, you do not need to accept cookies. But, please be advised that if you do not accept
|
|
105
|
+
cookies the service might be difficult or impossible to use.</p>
|
|
106
|
+
<p>You can adjust settings on your browser so that you will be notified when you receive a
|
|
107
|
+
cookie. Please refer to your browser documentation to check if cookies have been enabled
|
|
108
|
+
on your computer or to request not to receive cookies. As cookies allow you to take
|
|
109
|
+
advantage of some of the Website’s essential features, we recommend that you accept
|
|
110
|
+
cookies. For instance, if you block or otherwise reject our cookies, you will not be able to use
|
|
111
|
+
any products or services on the website that may require you to log in.</p>
|
|
112
|
+
<p>What are the cookies used for?</p>
|
|
113
|
+
<p>Functional Cookies Functional cookies are essential to provide our services as we want to
|
|
114
|
+
provide them. They are used to remember your preferences on our website and to provide
|
|
115
|
+
an enhanced and personalised experience. The information collected by these cookies is
|
|
116
|
+
usually anonymised, so we cannot identify you personally. Functional cookies do not track
|
|
117
|
+
your internet usage or gather information that could be used for selling advertising. These
|
|
118
|
+
cookies are usually session cookies that will expire when you close your browsing session,
|
|
119
|
+
but some are also persistent cookies.</p>
|
|
120
|
+
<p>Essential or ‘Strictly Necessary’ Cookies</p>
|
|
121
|
+
<p>These cookies are essential to provide our services. Without these cookies, parts of our
|
|
122
|
+
website will not function. These cookies do not track where you have been on the internet
|
|
123
|
+
and do remember preferences beyond your current visit and do not gather information about
|
|
124
|
+
you that could be used for marketing purposes. These cookies are usually session cookies
|
|
125
|
+
which will expire when you close your browsing session.</p>
|
|
126
|
+
<p>Analytical Performance Cookies</p>
|
|
127
|
+
<p>Analytical performance cookies are used to monitor the performance of our website and
|
|
128
|
+
services, for example, to determine the number of page views and the number of unique
|
|
129
|
+
users a website has. Web analytics services may be designed and operated by third parties.
|
|
130
|
+
The information provided by these cookies allows us to analyse patterns of user behaviour
|
|
131
|
+
and we use that information to enhance user experience or identify areas of the website
|
|
132
|
+
which may require maintenance. The information is anonymous, cannot be used to identify
|
|
133
|
+
you, does not contain personal information and is only used for statistical purposes.</p>
|
|
134
|
+
<p>Advertising Cookies</p>
|
|
135
|
+
<p>These cookies remember that you have visited a website and use that information to provide
|
|
136
|
+
you with content or advertising which is tailored to your interests. They are also used to limit
|
|
137
|
+
the number of times you see an advertisement as well as help measure the effectiveness of
|
|
138
|
+
the advertising campaign. The information collected by these cookies may be shared with
|
|
139
|
+
trusted third-party partners such as advertisers.</p>
|
|
140
|
+
<p>We may update this Cookie Policy from time to time for operational, legal or regulatory
|
|
141
|
+
reasons.</p>
|
|
142
|
+
<p>If you have any questions regarding our policy on cookies please contact: {{ supportEmail }}</p>
|
|
143
|
+
</template>
|
|
144
|
+
</div>
|
|
145
|
+
</article>
|
|
146
|
+
</div>
|
|
147
|
+
</section>
|
|
148
|
+
</template>
|
|
149
|
+
|
|
150
|
+
<script setup>
|
|
151
|
+
import { computed, inject, onMounted, onUnmounted, ref } from 'vue';
|
|
152
|
+
|
|
153
|
+
import {
|
|
154
|
+
getConsentStatus,
|
|
155
|
+
acceptConsent,
|
|
156
|
+
declineConsent,
|
|
157
|
+
ConsentStatus,
|
|
158
|
+
isCookieBannerEnabled,
|
|
159
|
+
scheduleAnalyticsLoad
|
|
160
|
+
} from '@koehler8/cms/utils/cookieConsent';
|
|
161
|
+
import { trackEvent } from '@koehler8/cms/utils/analytics';
|
|
162
|
+
|
|
163
|
+
const props = defineProps({
|
|
164
|
+
content: {
|
|
165
|
+
type: Object,
|
|
166
|
+
default: null,
|
|
167
|
+
},
|
|
168
|
+
configKey: {
|
|
169
|
+
type: String,
|
|
170
|
+
default: null,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const injectedSiteData = inject('siteData', ref({}));
|
|
175
|
+
|
|
176
|
+
const siteName = computed(() => injectedSiteData.value?.site?.title || '');
|
|
177
|
+
const siteUrl = computed(() => injectedSiteData.value?.site?.url || '');
|
|
178
|
+
const supportEmail = computed(() => injectedSiteData.value?.site?.supportEmail || '');
|
|
179
|
+
const customContent = computed(() =>
|
|
180
|
+
props.content && typeof props.content === 'object' ? props.content : null,
|
|
181
|
+
);
|
|
182
|
+
const consentStatus = ref(ConsentStatus.PENDING);
|
|
183
|
+
const bannerEnabled = isCookieBannerEnabled();
|
|
184
|
+
const TOKEN_TICKER = '';
|
|
185
|
+
const googleId = computed(() => injectedSiteData.value?.site?.googleId || '');
|
|
186
|
+
|
|
187
|
+
const headerTitle = computed(() => {
|
|
188
|
+
if (customContent.value?.title) {
|
|
189
|
+
return customContent.value.title;
|
|
190
|
+
}
|
|
191
|
+
if (siteName.value) {
|
|
192
|
+
return `${siteName.value} - Cookies Policy`;
|
|
193
|
+
}
|
|
194
|
+
return 'Cookies Policy';
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const lastUpdated = computed(() => customContent.value?.lastUpdated || 'August 2025');
|
|
198
|
+
const customIntro = computed(() =>
|
|
199
|
+
typeof customContent.value?.intro === 'string' ? customContent.value.intro : '',
|
|
200
|
+
);
|
|
201
|
+
const customBody = computed(() =>
|
|
202
|
+
typeof customContent.value?.body === 'string' ? customContent.value.body : '',
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
function normalizeSectionItems(sections) {
|
|
206
|
+
if (!Array.isArray(sections)) return [];
|
|
207
|
+
return sections
|
|
208
|
+
.map((section, index) => {
|
|
209
|
+
if (!section || typeof section !== 'object') return null;
|
|
210
|
+
const paragraphs = Array.isArray(section.paragraphs)
|
|
211
|
+
? section.paragraphs.filter((text) => typeof text === 'string' && text.trim().length)
|
|
212
|
+
: [];
|
|
213
|
+
const list = Array.isArray(section.list)
|
|
214
|
+
? section.list.filter((text) => typeof text === 'string' && text.trim().length)
|
|
215
|
+
: [];
|
|
216
|
+
const summary = typeof section.summary === 'string' ? section.summary : null;
|
|
217
|
+
const body = typeof section.body === 'string' ? section.body : null;
|
|
218
|
+
const html = typeof section.html === 'string' ? section.html : null;
|
|
219
|
+
const title = typeof section.title === 'string' ? section.title : '';
|
|
220
|
+
if (!title && !summary && !body && !html && !paragraphs.length && !list.length) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
id: section.id || title || `section-${index}`,
|
|
225
|
+
title,
|
|
226
|
+
summary,
|
|
227
|
+
body: body || html,
|
|
228
|
+
paragraphs,
|
|
229
|
+
list,
|
|
230
|
+
};
|
|
231
|
+
})
|
|
232
|
+
.filter(Boolean);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const customSections = computed(() => normalizeSectionItems(customContent.value?.sections));
|
|
236
|
+
const hasCustomContent = computed(
|
|
237
|
+
() => Boolean(customIntro.value || customBody.value || customSections.value.length),
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const consentStatusText = computed(() => {
|
|
241
|
+
switch (consentStatus.value) {
|
|
242
|
+
case ConsentStatus.ACCEPTED:
|
|
243
|
+
return 'Accepted';
|
|
244
|
+
case ConsentStatus.DECLINED:
|
|
245
|
+
return 'Declined';
|
|
246
|
+
default:
|
|
247
|
+
return 'Pending';
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const consentStatusClass = computed(() => {
|
|
252
|
+
switch (consentStatus.value) {
|
|
253
|
+
case ConsentStatus.ACCEPTED:
|
|
254
|
+
return 'consent-status--accepted';
|
|
255
|
+
case ConsentStatus.DECLINED:
|
|
256
|
+
return 'consent-status--declined';
|
|
257
|
+
default:
|
|
258
|
+
return 'consent-status--pending';
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const isAccepted = computed(() => consentStatus.value === ConsentStatus.ACCEPTED);
|
|
263
|
+
const isDeclined = computed(() => consentStatus.value === ConsentStatus.DECLINED);
|
|
264
|
+
|
|
265
|
+
let consentChangeHandler;
|
|
266
|
+
|
|
267
|
+
async function initializeAnalyticsIfNeeded() {
|
|
268
|
+
if (!googleId.value) return;
|
|
269
|
+
try {
|
|
270
|
+
await scheduleAnalyticsLoad(googleId.value);
|
|
271
|
+
} catch (error) {
|
|
272
|
+
console.error('Failed to initialize analytics after consent change:', error);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function applyConsent(status) {
|
|
277
|
+
consentStatus.value = status;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function handleAccept() {
|
|
281
|
+
if (isAccepted.value) return;
|
|
282
|
+
acceptConsent();
|
|
283
|
+
applyConsent(ConsentStatus.ACCEPTED);
|
|
284
|
+
trackEvent('cookie_consent_choice', {
|
|
285
|
+
token: TOKEN_TICKER || 'unknown',
|
|
286
|
+
choice: 'accept',
|
|
287
|
+
source: 'settings'
|
|
288
|
+
});
|
|
289
|
+
await initializeAnalyticsIfNeeded();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function handleDecline() {
|
|
293
|
+
if (isDeclined.value) return;
|
|
294
|
+
declineConsent();
|
|
295
|
+
applyConsent(ConsentStatus.DECLINED);
|
|
296
|
+
trackEvent('cookie_consent_choice', {
|
|
297
|
+
token: TOKEN_TICKER || 'unknown',
|
|
298
|
+
choice: 'decline',
|
|
299
|
+
source: 'settings'
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
onMounted(() => {
|
|
304
|
+
consentStatus.value = getConsentStatus();
|
|
305
|
+
consentChangeHandler = (event) => {
|
|
306
|
+
const nextStatus = event?.detail?.status;
|
|
307
|
+
if (nextStatus && Object.values(ConsentStatus).includes(nextStatus)) {
|
|
308
|
+
consentStatus.value = nextStatus;
|
|
309
|
+
} else {
|
|
310
|
+
consentStatus.value = getConsentStatus();
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
window.addEventListener('consentChanged', consentChangeHandler);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
onUnmounted(() => {
|
|
317
|
+
if (consentChangeHandler) {
|
|
318
|
+
window.removeEventListener('consentChanged', consentChangeHandler);
|
|
319
|
+
consentChangeHandler = undefined;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
</script>
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
<style scoped>
|
|
327
|
+
.legal-page {
|
|
328
|
+
background: var(
|
|
329
|
+
--legal-page-bg,
|
|
330
|
+
color-mix(in srgb, var(--brand-bg-900, #04050a) 90%, #020207 10%)
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.legal-page__container {
|
|
335
|
+
max-width: 920px;
|
|
336
|
+
margin: 0 auto;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.legal-card {
|
|
340
|
+
--legal-card-color: var(
|
|
341
|
+
--legal-card-text,
|
|
342
|
+
var(--brand-card-text, var(--ui-text-primary, #1f2a44))
|
|
343
|
+
);
|
|
344
|
+
--legal-card-color-muted: color-mix(in srgb, var(--legal-card-color) 70%, transparent);
|
|
345
|
+
--legal-card-color-subtle: color-mix(in srgb, var(--legal-card-color) 55%, transparent);
|
|
346
|
+
background: var(--legal-card-bg, var(--brand-surface-card-bg, #ffffff));
|
|
347
|
+
color: var(--legal-card-color);
|
|
348
|
+
border-radius: var(--brand-card-radius, 28px);
|
|
349
|
+
border: 1px solid
|
|
350
|
+
color-mix(in srgb, var(--brand-surface-card-border, rgba(78, 105, 155, 0.35)) 80%, transparent);
|
|
351
|
+
box-shadow: var(--legal-card-shadow, 0 35px 70px rgba(4, 6, 15, 0.35));
|
|
352
|
+
padding: clamp(28px, 6vw, 56px);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.legal-card__header {
|
|
356
|
+
text-align: center;
|
|
357
|
+
margin-bottom: clamp(20px, 4vw, 36px);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.legal-card__title {
|
|
361
|
+
margin: 0;
|
|
362
|
+
font-size: clamp(1.6rem, 4vw, 2.4rem);
|
|
363
|
+
letter-spacing: 0.14em;
|
|
364
|
+
text-transform: uppercase;
|
|
365
|
+
color: var(--legal-card-color, currentColor);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.legal-card__meta {
|
|
369
|
+
margin: 14px 0 0;
|
|
370
|
+
font-size: 0.78rem;
|
|
371
|
+
letter-spacing: 0.2em;
|
|
372
|
+
text-transform: uppercase;
|
|
373
|
+
color: var(
|
|
374
|
+
--legal-card-meta-color,
|
|
375
|
+
var(--legal-card-color-subtle, color-mix(in srgb, var(--ui-text-muted, rgba(31, 42, 68, 0.7)) 90%, transparent))
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.legal-divider {
|
|
380
|
+
display: block;
|
|
381
|
+
width: 72px;
|
|
382
|
+
height: 2px;
|
|
383
|
+
margin: 18px auto 0;
|
|
384
|
+
background: var(
|
|
385
|
+
--legal-divider-color,
|
|
386
|
+
color-mix(in srgb, var(--brand-border-highlight, rgba(79, 108, 240, 0.6)) 90%, transparent)
|
|
387
|
+
);
|
|
388
|
+
border-radius: 999px;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.legal-content {
|
|
392
|
+
display: flex;
|
|
393
|
+
flex-direction: column;
|
|
394
|
+
gap: 1rem;
|
|
395
|
+
line-height: 1.7;
|
|
396
|
+
color: inherit;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.legal-content p {
|
|
400
|
+
margin: 0;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.legal-content strong {
|
|
404
|
+
color: var(--legal-strong-color, var(--legal-card-color));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.legal-content ul {
|
|
408
|
+
margin: 0;
|
|
409
|
+
padding-left: 1.2rem;
|
|
410
|
+
line-height: 1.6;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.legal-content li + li {
|
|
414
|
+
margin-top: 0.35rem;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.consent-panel {
|
|
418
|
+
background: var(
|
|
419
|
+
--consent-panel-bg,
|
|
420
|
+
color-mix(in srgb, var(--brand-card-soft, rgba(10, 12, 18, 0.92)) 92%, transparent)
|
|
421
|
+
);
|
|
422
|
+
color: var(--consent-panel-color, var(--legal-card-color));
|
|
423
|
+
border: 1px solid
|
|
424
|
+
var(
|
|
425
|
+
--consent-panel-border,
|
|
426
|
+
color-mix(
|
|
427
|
+
in srgb,
|
|
428
|
+
var(--brand-surface-card-border, rgba(79, 108, 240, 0.3)) 85%,
|
|
429
|
+
transparent
|
|
430
|
+
)
|
|
431
|
+
);
|
|
432
|
+
border-radius: clamp(18px, 3vw, 26px);
|
|
433
|
+
padding: clamp(20px, 4vw, 32px);
|
|
434
|
+
margin-bottom: clamp(24px, 4vw, 40px);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.consent-panel__title {
|
|
438
|
+
margin-top: 0;
|
|
439
|
+
margin-bottom: 0.75rem;
|
|
440
|
+
font-size: 1.05rem;
|
|
441
|
+
letter-spacing: 0.08em;
|
|
442
|
+
text-transform: uppercase;
|
|
443
|
+
color: var(--consent-panel-title-color, var(--consent-panel-color));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.consent-status {
|
|
447
|
+
font-weight: 700;
|
|
448
|
+
letter-spacing: 0.1em;
|
|
449
|
+
text-transform: uppercase;
|
|
450
|
+
margin-left: 6px;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
.consent-status--accepted {
|
|
454
|
+
color: var(--brand-status-success, #1eb980);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.consent-status--declined {
|
|
458
|
+
color: var(--brand-status-error, #e45865);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.consent-status--pending {
|
|
462
|
+
color: var(--ui-text-muted, rgba(31, 42, 68, 0.72));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.consent-controls {
|
|
466
|
+
display: flex;
|
|
467
|
+
flex-wrap: wrap;
|
|
468
|
+
gap: 12px;
|
|
469
|
+
margin-top: 16px;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.consent-button {
|
|
473
|
+
border-radius: 999px;
|
|
474
|
+
padding: 0.85rem 1.5rem;
|
|
475
|
+
font-size: 0.85rem;
|
|
476
|
+
letter-spacing: 0.12em;
|
|
477
|
+
text-transform: uppercase;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.consent-button:disabled {
|
|
481
|
+
opacity: 0.55;
|
|
482
|
+
cursor: not-allowed;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.consent-button--decline {
|
|
486
|
+
border: 1px solid color-mix(in srgb, var(--brand-border-highlight, rgba(79, 108, 240, 0.45)) 90%, transparent);
|
|
487
|
+
color: var(--consent-panel-color, var(--legal-card-color));
|
|
488
|
+
background: transparent;
|
|
489
|
+
transition: color 0.2s ease, border-color 0.2s ease;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.consent-button--decline:hover,
|
|
493
|
+
.consent-button--decline:focus-visible {
|
|
494
|
+
border-color: var(--brand-border-highlight, rgba(79, 108, 240, 0.8));
|
|
495
|
+
color: var(--brand-cta-text, #ffffff);
|
|
496
|
+
}
|
|
497
|
+
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section class="legal-bar ui-legal">
|
|
3
|
+
<div class="container ui-legal__content">
|
|
4
|
+
<div class="legal-bar__copy">
|
|
5
|
+
<small>{{ copyrightText }}</small>
|
|
6
|
+
</div>
|
|
7
|
+
<ul class="ui-legal__links">
|
|
8
|
+
<li v-for="link in legalLinks" :key="link.href">
|
|
9
|
+
<a class="ui-legal__link" :href="link.href" rel="nofollow">
|
|
10
|
+
{{ link.label }}
|
|
11
|
+
</a>
|
|
12
|
+
</li>
|
|
13
|
+
</ul>
|
|
14
|
+
</div>
|
|
15
|
+
</section>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup>
|
|
19
|
+
import { computed, inject, ref } from 'vue';
|
|
20
|
+
|
|
21
|
+
const injectedSiteData = inject('siteData', ref({}));
|
|
22
|
+
|
|
23
|
+
const siteName = computed(() => injectedSiteData.value?.site?.title || '');
|
|
24
|
+
const currentYear = new Date().getFullYear();
|
|
25
|
+
const copyrightText = computed(() => {
|
|
26
|
+
const brand = siteName.value ? ` ${siteName.value}` : '';
|
|
27
|
+
return `© ${currentYear}${brand} - All rights reserved.`;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const legalLinks = [
|
|
31
|
+
{ href: '/terms', label: 'Terms' },
|
|
32
|
+
{ href: '/privacy', label: 'Privacy' },
|
|
33
|
+
{ href: '/cookies', label: 'Cookies' },
|
|
34
|
+
];
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<style scoped>
|
|
38
|
+
.legal-bar__copy {
|
|
39
|
+
font-size: 0.85rem;
|
|
40
|
+
color: inherit;
|
|
41
|
+
}
|
|
42
|
+
</style>
|