@consenti/ui 0.1.0 → 0.3.0

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.
Files changed (43) hide show
  1. package/README.md +548 -151
  2. package/dist/angular.d.cts +486 -111
  3. package/dist/angular.d.ts +486 -111
  4. package/dist/angular.js +1 -73
  5. package/dist/angular.js.map +1 -1
  6. package/dist/angular.mjs +1 -52
  7. package/dist/angular.mjs.map +1 -1
  8. package/dist/chunk-3EXCD24H.mjs +2 -0
  9. package/dist/{chunk-ARALKXMB.mjs.map → chunk-3EXCD24H.mjs.map} +1 -1
  10. package/dist/chunk-J4BKXUD6.mjs +2 -0
  11. package/dist/chunk-J4BKXUD6.mjs.map +1 -0
  12. package/dist/hmac-UKMRMKN6.mjs +2 -0
  13. package/dist/{hmac-N6H7F2K2.mjs.map → hmac-UKMRMKN6.mjs.map} +1 -1
  14. package/dist/http-AQVVUCX6.mjs +2 -0
  15. package/dist/index.css +1 -1
  16. package/dist/index.d.ts +806 -103
  17. package/dist/index.mjs +1 -2220
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/index.umd.js +1 -2304
  20. package/dist/index.umd.js.map +1 -1
  21. package/dist/profiles-OEZAKEWL.mjs +2 -0
  22. package/dist/profiles-OEZAKEWL.mjs.map +1 -0
  23. package/dist/react.d.cts +470 -89
  24. package/dist/react.d.ts +470 -89
  25. package/dist/react.js +1 -60
  26. package/dist/react.js.map +1 -1
  27. package/dist/react.mjs +1 -39
  28. package/dist/react.mjs.map +1 -1
  29. package/dist/testing.js +1 -94
  30. package/dist/testing.js.map +1 -1
  31. package/dist/testing.mjs +1 -73
  32. package/dist/testing.mjs.map +1 -1
  33. package/dist/vue.d.cts +473 -82
  34. package/dist/vue.d.ts +473 -82
  35. package/dist/vue.js +1 -72
  36. package/dist/vue.js.map +1 -1
  37. package/dist/vue.mjs +1 -51
  38. package/dist/vue.mjs.map +1 -1
  39. package/package.json +15 -6
  40. package/dist/chunk-ARALKXMB.mjs +0 -20
  41. package/dist/hmac-N6H7F2K2.mjs +0 -26
  42. package/dist/http-VHOGZ6ZJ.mjs +0 -7
  43. /package/dist/{http-VHOGZ6ZJ.mjs.map → http-AQVVUCX6.mjs.map} +0 -0
package/dist/index.mjs CHANGED
@@ -1,2221 +1,2 @@
1
- import {
2
- httpRequest
3
- } from "./chunk-ARALKXMB.mjs";
4
-
5
- // src/styles/consenti-css.ts
6
- var CONSENTI_CSS = `:root{--consenti-color-bg: #ffffff;--consenti-color-text: #1a2e4a;--consenti-color-primary: #1565c0;--consenti-color-primary-text: #ffffff;--consenti-color-secondary: #f0f4f8;--consenti-color-secondary-text: #1a2e4a;--consenti-color-border: #dbe4ee;--consenti-color-overlay: rgba(26, 52, 96, 0.55);--consenti-font-family: system-ui, -apple-system, sans-serif;--consenti-font-size-base: 14px;--consenti-font-size-heading: 16px;--consenti-font-weight-heading: 600;--consenti-line-height: 1.5;--consenti-spacing-sm: 8px;--consenti-spacing-md: 16px;--consenti-spacing-lg: 24px;--consenti-border-radius: 8px;--consenti-border-radius-btn: 4px;--consenti-shadow: 0 4px 24px rgba(21, 101, 192, 0.14);--consenti-color-accent: #d32f2f;--consenti-color-accent-text: #ffffff;--consenti-toggle-bg-on: #43a047;--consenti-toggle-bg-off: #c8d6e8;--consenti-toggle-knob: #ffffff;--consenti-toggle-width: 44px;--consenti-toggle-height: 24px;--consenti-z-banner: 9999;--consenti-z-overlay: 9998;--consenti-z-modal: 10000}.consenti-root--dark{--consenti-color-bg: #1e2535;--consenti-color-text: #e2e8f0;--consenti-color-primary: #4a9eff;--consenti-color-primary-text: #ffffff;--consenti-color-secondary: #2a3447;--consenti-color-secondary-text: #cbd5e1;--consenti-color-border: #374151;--consenti-color-overlay: rgba(0, 0, 0, 0.65);--consenti-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);--consenti-toggle-bg-off: #4b5563}.consenti-overlay{position:fixed;inset:0;background:var(--consenti-color-overlay);z-index:var(--consenti-z-overlay)}.consenti-btn{display:inline-flex;align-items:center;justify-content:center;padding:var(--consenti-spacing-sm) var(--consenti-spacing-md);border:none;border-radius:var(--consenti-border-radius-btn);font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);font-weight:500;line-height:1;cursor:pointer;text-decoration:none;transition:opacity .15s ease}.consenti-btn:focus-visible{outline:2px solid var(--consenti-color-primary);outline-offset:2px}.consenti-btn:hover{opacity:.9}.consenti-btn--primary{background:var(--consenti-color-primary);color:var(--consenti-color-primary-text)}.consenti-btn--secondary{background:var(--consenti-color-secondary);color:var(--consenti-color-secondary-text)}.consenti-btn--text{background:rgba(0,0,0,0);color:var(--consenti-color-primary);padding-left:0;padding-right:0;text-decoration:underline}.consenti-btn--accent{background:var(--consenti-color-accent);color:var(--consenti-color-accent-text)}.consenti-btn--close{background:rgba(0,0,0,0);color:var(--consenti-color-text);font-size:20px;line-height:1;padding:4px 8px;border-radius:50%}.consenti-btn--close:hover{background:var(--consenti-color-secondary);opacity:1}.consenti-banner{position:fixed;left:0;right:0;z-index:var(--consenti-z-banner);background:var(--consenti-color-bg);color:var(--consenti-color-text);font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);line-height:var(--consenti-line-height);box-shadow:var(--consenti-shadow);padding:var(--consenti-spacing-lg);display:flex;flex-wrap:wrap;align-items:center;gap:var(--consenti-spacing-md)}.consenti-banner--top{top:0;border-bottom:1px solid var(--consenti-color-border)}@media(prefers-reduced-motion: no-preference){.consenti-banner--top{animation:consenti-slide-down .2s ease-out}}.consenti-banner--bottom{bottom:0;border-top:1px solid var(--consenti-color-border)}@media(prefers-reduced-motion: no-preference){.consenti-banner--bottom{animation:consenti-slide-up .2s ease-out}}.consenti-banner--middle{top:50%;transform:translateY(-50%);left:50%;right:auto;transform:translate(-50%, -50%);width:min(600px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner--left-bottom{bottom:var(--consenti-spacing-lg);left:var(--consenti-spacing-lg);right:auto;width:min(400px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner--right-bottom{bottom:var(--consenti-spacing-lg);right:var(--consenti-spacing-lg);left:auto;width:min(400px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner__text-container{flex:1;min-width:200px}.consenti-banner__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0 0 var(--consenti-spacing-sm);width:100%}.consenti-banner__text{margin:0}.consenti-banner__links{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);margin-top:var(--consenti-spacing-sm)}.consenti-banner__link{font-size:.85em;text-decoration:underline;text-underline-offset:2px}.consenti-banner__controls{position:absolute;top:var(--consenti-spacing-sm);right:var(--consenti-spacing-sm);display:flex;align-items:center;gap:4px}.consenti-banner__buttons{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);align-items:center}.consenti-banner--top .consenti-banner__buttons,.consenti-banner--bottom .consenti-banner__buttons{margin-left:2vw}@keyframes consenti-slide-up{from{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes consenti-slide-down{from{transform:translateY(-100%)}to{transform:translateY(0)}}.consenti-modal{position:fixed;inset:0;z-index:var(--consenti-z-modal);display:flex;font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);color:var(--consenti-color-text);line-height:var(--consenti-line-height)}.consenti-modal--passthrough{pointer-events:none}.consenti-modal--passthrough .consenti-modal__container{pointer-events:auto}.consenti-modal--center{align-items:center;justify-content:center}.consenti-modal--left{align-items:stretch;justify-content:flex-start}.consenti-modal--left .consenti-modal__container{border-radius:0 var(--consenti-border-radius) var(--consenti-border-radius) 0;height:100%;max-height:100vh}.consenti-modal--right{align-items:stretch;justify-content:flex-end}.consenti-modal--right .consenti-modal__container{border-radius:0;height:100%;max-height:100vh}.consenti-modal__overlay{position:absolute;inset:0;background:var(--consenti-color-overlay)}.consenti-modal__container{position:relative;z-index:1;background:var(--consenti-color-bg);border-radius:var(--consenti-border-radius);box-shadow:var(--consenti-shadow);width:min(560px,90vw);max-height:85vh;display:flex;flex-direction:column;overflow:hidden}@media(prefers-reduced-motion: no-preference){.consenti-modal__container{animation:consenti-fade-in .15s ease-out}}.consenti-modal__header{display:flex;align-items:center;justify-content:space-between;padding:var(--consenti-spacing-md) var(--consenti-spacing-lg);border-bottom:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0;flex:1}.consenti-modal__header-controls{display:flex;align-items:center;gap:4px;flex-shrink:0}.consenti-modal__close{flex-shrink:0}.consenti-modal__links{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg)}.consenti-modal__link{font-size:.85em;text-decoration:underline;text-underline-offset:2px}.consenti-modal__subheading{padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg) 0;margin:0;color:#6b7280;font-size:13px}.consenti-modal__text{padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg)}.consenti-modal__body{overflow-y:auto;flex:1;padding:0 var(--consenti-spacing-lg)}.consenti-modal__categories{display:flex;flex-direction:column;gap:var(--consenti-spacing-md);padding:var(--consenti-spacing-md) 0}.consenti-modal__buttons{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);padding:var(--consenti-spacing-md) var(--consenti-spacing-lg);border-top:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__receipt-option{margin:0 var(--consenti-spacing-lg);padding-top:var(--consenti-spacing-md);border-top:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__receipt-label{display:flex;align-items:center;gap:var(--consenti-spacing-sm);font-size:var(--consenti-font-size-base);cursor:pointer}.consenti-modal__receipt-checkbox{width:16px;height:16px;flex-shrink:0;cursor:pointer}.consenti-modal__receipt-desc{margin:4px 0 0 24px;font-size:12px;color:#6b7280}@keyframes consenti-fade-in{from{opacity:0;transform:scale(0.97)}to{opacity:1;transform:scale(1)}}.consenti-category{padding:var(--consenti-spacing-md) 0;border-bottom:1px solid var(--consenti-color-border)}.consenti-category:last-child{border-bottom:none}.consenti-category--mandatory .consenti-category__toggle{opacity:.6;cursor:default}.consenti-category__header{display:flex;align-items:center;justify-content:space-between;gap:var(--consenti-spacing-md);margin-bottom:var(--consenti-spacing-sm)}.consenti-category__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0}.consenti-category__text{color:#4b5563;font-size:13px;margin:0}.consenti-category__li-desc{margin:var(--consenti-spacing-sm) 0 0;font-size:12px;color:#6b7280;font-style:italic}.consenti-category__toggle{flex-shrink:0;position:relative;width:var(--consenti-toggle-width);height:var(--consenti-toggle-height);border-radius:calc(var(--consenti-toggle-height)/2);background:var(--consenti-toggle-bg-off);border:none;cursor:pointer;transition:background .2s ease;padding:0}.consenti-category__toggle:focus-visible{outline:2px solid var(--consenti-color-primary);outline-offset:2px}.consenti-category__toggle[aria-checked=true]{background:var(--consenti-toggle-bg-on)}.consenti-category__toggle--mandatory{cursor:default}.consenti-category__toggle-knob{position:absolute;top:2px;left:2px;width:calc(var(--consenti-toggle-height) - 4px);height:calc(var(--consenti-toggle-height) - 4px);border-radius:50%;background:var(--consenti-toggle-knob);transition:transform .2s ease;display:block}[aria-checked=true] .consenti-category__toggle-knob{transform:translateX(calc(var(--consenti-toggle-width) - var(--consenti-toggle-height)))}.consenti-locale-switcher{position:relative;display:inline-flex}.consenti-locale-switcher__btn{font-size:1rem;line-height:1;cursor:pointer}.consenti-locale-switcher__list{position:absolute;top:calc(100% + 4px);right:0;z-index:calc(var(--consenti-z-modal) + 1);background:var(--consenti-color-bg);border:1px solid var(--consenti-color-border);border-radius:var(--consenti-border-radius);box-shadow:var(--consenti-shadow);list-style:none;margin:0;padding:4px 0;min-width:80px}.consenti-locale-switcher__list li{display:block}.consenti-locale-switcher__option{display:block;width:100%;padding:6px 12px;text-align:left;font-size:var(--consenti-font-size-base);color:var(--consenti-color-text);background:none;border:none;cursor:pointer}.consenti-locale-switcher__option:hover{background:var(--consenti-color-secondary)}.consenti-locale-switcher__option--active{font-weight:600;color:var(--consenti-color-primary)}.consenti-locale-switcher--modal .consenti-locale-switcher__list{right:0}`;
7
-
8
- // src/utils/ssr.ts
9
- var isClient = () => typeof window !== "undefined" && typeof document !== "undefined";
10
- var safeWindow = () => isClient() ? window : null;
11
- var safeNavigator = () => isClient() && typeof navigator !== "undefined" ? navigator : null;
12
-
13
- // src/utils/broadcast.ts
14
- var ConsentiChannel = class {
15
- constructor() {
16
- this.channel = null;
17
- }
18
- /** `true` when `BroadcastChannel` is available in the current environment. */
19
- get isSupported() {
20
- return isClient() && typeof BroadcastChannel !== "undefined";
21
- }
22
- /**
23
- * Opens the `BroadcastChannel` and registers a message handler.
24
- * Silently no-ops if `BroadcastChannel` is unsupported.
25
- *
26
- * @param onMessage - Callback invoked with each received `ConsentiMessage`.
27
- */
28
- connect(onMessage) {
29
- if (!this.isSupported) return;
30
- this.onMessageCallback = onMessage;
31
- this.channel = new BroadcastChannel("consenti");
32
- this.channel.onmessage = (event) => {
33
- if (event.data?.type) {
34
- onMessage(event.data);
35
- }
36
- };
37
- this.channel.onmessageerror = () => {
38
- console.warn("[Consenti] BroadcastChannel message error \u2014 ignoring");
39
- };
40
- }
41
- /**
42
- * Posts a message to all other tabs connected to the `'consenti'` channel.
43
- * The sending tab does not receive its own message.
44
- *
45
- * @param message - The `ConsentiMessage` to broadcast.
46
- */
47
- send(message) {
48
- this.channel?.postMessage(message);
49
- }
50
- /** Closes the channel and removes the message callback. */
51
- disconnect() {
52
- this.channel?.close();
53
- this.channel = null;
54
- this.onMessageCallback = void 0;
55
- }
56
- };
57
-
58
- // src/utils/receipt.ts
59
- function generateReceipt(params) {
60
- const receipt = {
61
- version: "1.0",
62
- issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
63
- visitorId: params.visitorId,
64
- profileId: params.profileId,
65
- profileVersion: params.profileVersion,
66
- locale: params.locale,
67
- consent: params.consent
68
- };
69
- if (params.signature) receipt.signature = params.signature;
70
- return receipt;
71
- }
72
- function downloadReceipt(receipt) {
73
- if (typeof window === "undefined" || typeof document === "undefined") return;
74
- const json = JSON.stringify(receipt, null, 2);
75
- const blob = new Blob([json], { type: "application/json" });
76
- const url = URL.createObjectURL(blob);
77
- const anchor = document.createElement("a");
78
- anchor.href = url;
79
- anchor.download = `consenti-receipt-${receipt.issuedAt.slice(0, 10)}.json`;
80
- anchor.style.display = "none";
81
- document.body.appendChild(anchor);
82
- anchor.click();
83
- document.body.removeChild(anchor);
84
- setTimeout(() => URL.revokeObjectURL(url), 1e3);
85
- }
86
-
87
- // src/utils/cookie.ts
88
- function getCookie(name) {
89
- if (!isClient()) return null;
90
- const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${escapeRegExp(name)}=([^;]*)`));
91
- return match ? decodeURIComponent(match[1] ?? "") : null;
92
- }
93
- function setCookie(name, value, opts = {}) {
94
- if (!isClient()) return;
95
- const secure = opts.secure ?? (typeof window !== "undefined" && typeof window.location !== "undefined" && window.location.protocol === "https:");
96
- const parts = [
97
- `${encodeURIComponent(name)}=${encodeURIComponent(value)}`,
98
- `Path=${opts.path ?? "/"}`,
99
- `SameSite=${opts.sameSite ?? "Lax"}`,
100
- opts.maxAge != null ? `Max-Age=${opts.maxAge}` : "",
101
- secure ? "Secure" : "",
102
- opts.domain ? `Domain=${opts.domain}` : ""
103
- ];
104
- document.cookie = parts.filter(Boolean).join("; ");
105
- }
106
- function deleteCookie(name, opts = {}) {
107
- setCookie(name, "", { ...opts, maxAge: 0 });
108
- }
109
- function escapeRegExp(str) {
110
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
111
- }
112
-
113
- // src/utils/storage.ts
114
- var ConsentStorage = class {
115
- /**
116
- * @param mode - `'cookie'` (default) or `'localStorage'`. Falls back to `'cookie'` if
117
- * `localStorage` is requested but unavailable in the current environment.
118
- */
119
- constructor(mode) {
120
- this.mode = mode;
121
- if (mode === "localStorage" && isClient() && typeof localStorage === "undefined") {
122
- console.warn("[Consenti] localStorage is not available, falling back to cookie storage");
123
- this.mode = "cookie";
124
- }
125
- }
126
- /**
127
- * Reads a value from storage.
128
- *
129
- * @param key - Cookie name or localStorage key.
130
- * @returns The stored string value, or `null` if absent or SSR context.
131
- */
132
- read(key) {
133
- if (!isClient()) return null;
134
- if (this.mode === "localStorage") {
135
- return localStorage.getItem(key);
136
- }
137
- return getCookie(key);
138
- }
139
- /**
140
- * Writes a value to storage.
141
- *
142
- * @param key - Cookie name or localStorage key.
143
- * @param value - String value to persist.
144
- * @param opts - Cookie attributes (ignored in localStorage mode).
145
- */
146
- write(key, value, opts = {}) {
147
- if (!isClient()) return;
148
- if (this.mode === "localStorage") {
149
- localStorage.setItem(key, value);
150
- } else {
151
- setCookie(key, value, opts);
152
- }
153
- }
154
- /**
155
- * Removes a value from storage.
156
- *
157
- * @param key - Cookie name or localStorage key.
158
- * @param opts - Cookie `path` / `domain` attributes (must match the original write call).
159
- */
160
- remove(key, opts = {}) {
161
- if (!isClient()) return;
162
- if (this.mode === "localStorage") {
163
- localStorage.removeItem(key);
164
- } else {
165
- deleteCookie(key, opts);
166
- }
167
- }
168
- };
169
-
170
- // src/core/consent-store.ts
171
- var COOKIE_NAME_PREFIX = "consenti";
172
- function buildConsentCookieName(userId, profileId) {
173
- return `${COOKIE_NAME_PREFIX}_${userId}_${profileId}`;
174
- }
175
- function buildCookieValue(consent) {
176
- const epochSeconds = Math.floor(Date.now() / 1e3);
177
- const pairs = Object.entries(consent).map(([id, status]) => `${id}:${status}`).join("|");
178
- return `t:${epochSeconds}::${pairs}`;
179
- }
180
- function parseConsentCookie(value) {
181
- const parts = value.split("::");
182
- if (parts.length < 2) return null;
183
- const tsPart = parts[0] ?? "";
184
- let timestamp;
185
- if (tsPart.startsWith("t:")) {
186
- const epochStr = tsPart.slice(2);
187
- const epoch = parseInt(epochStr, 10);
188
- if (!epochStr || isNaN(epoch)) return null;
189
- timestamp = new Date(epoch * 1e3).toISOString();
190
- } else if (tsPart.startsWith("ConsentTimestamp:")) {
191
- timestamp = tsPart.replace("ConsentTimestamp:", "");
192
- if (!timestamp) return null;
193
- } else {
194
- return null;
195
- }
196
- const consentPart = parts[1] ?? "";
197
- const sigPart = parts[2];
198
- const consent = {};
199
- for (const pair of consentPart.split("|")) {
200
- const colonIdx = pair.indexOf(":");
201
- if (colonIdx === -1) return null;
202
- const id = pair.slice(0, colonIdx);
203
- const status = pair.slice(colonIdx + 1);
204
- if (!id || status !== "granted" && status !== "denied" && status !== "objected") {
205
- return null;
206
- }
207
- consent[id] = status;
208
- }
209
- const result = { timestamp, consent };
210
- if (sigPart?.startsWith("sig:")) {
211
- result.signature = sigPart.slice(4);
212
- }
213
- return result;
214
- }
215
- var ConsentStore = class _ConsentStore {
216
- /**
217
- * @param userId - Visitor UUID (from `consenti_uid` cookie).
218
- * @param profileId - Numeric profile ID.
219
- * @param storageMode - `'cookie'` (default, cross-subdomain) or `'localStorage'` (origin-scoped).
220
- * @param cookieDomains - Comma-separated domain list. First entry used as `Domain` attribute.
221
- */
222
- constructor(userId, profileId, storageMode, cookieDomains) {
223
- this.userId = userId;
224
- this.profileId = profileId;
225
- this.cookieDomains = cookieDomains;
226
- this.cachedConsent = null;
227
- this.cachedTimestamp = null;
228
- this.storage = new ConsentStorage(storageMode);
229
- }
230
- /** The full cookie / localStorage key for this visitor + profile combination. */
231
- get cookieName() {
232
- return buildConsentCookieName(this.userId, this.profileId);
233
- }
234
- /**
235
- * Reads and parses the stored consent record.
236
- * Updates the in-memory cache on success.
237
- *
238
- * @returns The parsed consent or `null` if absent / malformed.
239
- */
240
- read() {
241
- const raw = this.storage.read(this.cookieName);
242
- if (!raw) return null;
243
- const parsed = parseConsentCookie(raw);
244
- if (parsed) {
245
- this.cachedConsent = parsed.consent;
246
- this.cachedTimestamp = parsed.timestamp;
247
- }
248
- return parsed;
249
- }
250
- /**
251
- * Reads, parses, and cryptographically verifies the stored consent record.
252
- *
253
- * If the cookie has a `::sig:{hmac}` segment, the signature is verified against
254
- * the raw value using HMAC-SHA256. A mismatch means the cookie was tampered with —
255
- * the cookie is deleted and `null` is returned so the widget re-prompts for consent.
256
- *
257
- * If the cookie has no signature segment, it is treated as valid (backwards compatible
258
- * with cookies written before `signCookies` was enabled).
259
- *
260
- * @param signingKey - The HMAC key (from profile API response or `core.cookieSigningKey`).
261
- * @returns The parsed consent, or `null` if absent / malformed / signature invalid.
262
- */
263
- async readAndVerify(signingKey) {
264
- const raw = this.storage.read(this.cookieName);
265
- if (!raw) return null;
266
- const parts = raw.split("::");
267
- if (parts.length < 2) return null;
268
- const parsed = parseConsentCookie(raw);
269
- if (!parsed) return null;
270
- const hasSig = parts.length === 3 && (parts[2] ?? "").startsWith("sig:");
271
- if (hasSig) {
272
- const { verifyValue } = await import("./hmac-N6H7F2K2.mjs");
273
- const valueToVerify = (parts[0] ?? "") + "::" + (parts[1] ?? "");
274
- const sig = (parts[2] ?? "").slice(4);
275
- const valid = await verifyValue(valueToVerify, sig, signingKey);
276
- if (!valid) {
277
- console.warn("[Consenti] Cookie signature mismatch \u2014 consent cleared and banner will re-appear");
278
- this.delete();
279
- return null;
280
- }
281
- }
282
- this.cachedConsent = parsed.consent;
283
- this.cachedTimestamp = parsed.timestamp;
284
- return parsed;
285
- }
286
- /**
287
- * Writes a consent record to storage.
288
- *
289
- * @param consent - The consent map to persist.
290
- * @param signature - Optional pre-computed HMAC hex string. Use `writeAndSign()` instead
291
- * if you need signing — it guarantees the signature covers the exact
292
- * value that gets written (timestamps match).
293
- */
294
- write(consent, signature) {
295
- let value = buildCookieValue(consent);
296
- if (signature) {
297
- value = `${value}::sig:${signature}`;
298
- }
299
- const opts = this.buildCookieOpts();
300
- this.storage.write(this.cookieName, value, opts);
301
- this.cachedConsent = consent;
302
- this.cachedTimestamp = (/* @__PURE__ */ new Date()).toISOString();
303
- }
304
- /**
305
- * Builds the cookie value, signs it with HMAC-SHA256 in a single atomic step,
306
- * and writes the signed value to storage.
307
- *
308
- * Using this instead of `write(consent, signature)` ensures the signature
309
- * covers exactly the value that is written — there is no timestamp drift
310
- * between building, signing, and writing.
311
- *
312
- * @param consent - The consent map to persist.
313
- * @param signingKey - The HMAC key string.
314
- * @returns The hex-encoded signature, stored in `this.lastSignature` by the caller.
315
- */
316
- async writeAndSign(consent, signingKey) {
317
- const { signValue } = await import("./hmac-N6H7F2K2.mjs");
318
- const rawValue = buildCookieValue(consent);
319
- const sig = await signValue(rawValue, signingKey);
320
- const finalValue = `${rawValue}::sig:${sig}`;
321
- const opts = this.buildCookieOpts();
322
- this.storage.write(this.cookieName, finalValue, opts);
323
- this.cachedConsent = consent;
324
- this.cachedTimestamp = (/* @__PURE__ */ new Date()).toISOString();
325
- return sig;
326
- }
327
- /** Removes the consent record from storage and clears the in-memory cache. */
328
- delete() {
329
- const opts = this.buildCookieOpts();
330
- this.storage.remove(this.cookieName, opts);
331
- this.cachedConsent = null;
332
- this.cachedTimestamp = null;
333
- }
334
- /** Returns `true` if a valid consent record exists (uses cache when available). */
335
- hasConsent() {
336
- if (this.cachedConsent) return true;
337
- return this.read() !== null;
338
- }
339
- /** Returns the stored consent map, or `null` if no consent has been given. */
340
- getConsent() {
341
- if (this.cachedConsent) return this.cachedConsent;
342
- return this.read()?.consent ?? null;
343
- }
344
- /** Returns the `Date` of the last consent submission, or `false` if no consent exists. */
345
- getConsentDate() {
346
- if (this.cachedTimestamp) return new Date(this.cachedTimestamp);
347
- const parsed = this.read();
348
- if (!parsed) return false;
349
- return new Date(parsed.timestamp);
350
- }
351
- /**
352
- * Updates the in-memory cache without writing to storage.
353
- * Used by the cross-tab BroadcastChannel handler to keep other tabs in sync.
354
- */
355
- setInMemory(consent) {
356
- this.cachedConsent = consent;
357
- this.cachedTimestamp = (/* @__PURE__ */ new Date()).toISOString();
358
- }
359
- /** Clears the in-memory cache. Used when consent is deleted from another tab. */
360
- clearInMemory() {
361
- this.cachedConsent = null;
362
- this.cachedTimestamp = null;
363
- }
364
- /**
365
- * Derives the visitor UUID from an existing consent cookie, or generates a new one
366
- * in memory.
367
- *
368
- * No separate identifier cookie is written before consent. Storing a tracking
369
- * identifier before the user has given consent violates TTDSG §25 (Germany) and
370
- * equivalent ePrivacy laws in the EU. The UUID is embedded in the consent cookie
371
- * name and only persisted when consent is first saved (via `write()` / `writeAndSign()`).
372
- *
373
- * @param profileId - The active profile ID, used to locate an existing consent cookie.
374
- * @param storageMode - The configured storage mode (`'cookie'` or `'localStorage'`).
375
- */
376
- static getOrCreateUserId(profileId, storageMode) {
377
- if (typeof document === "undefined") return crypto.randomUUID();
378
- const prefix = `${COOKIE_NAME_PREFIX}_`;
379
- const suffix = `_${profileId}`;
380
- const all = storageMode === "localStorage" ? _ConsentStore._localStorageKeys() : _ConsentStore._cookieNames();
381
- for (const name of all) {
382
- if (name.startsWith(prefix) && name.endsWith(suffix)) {
383
- const inner = name.slice(prefix.length, name.length - suffix.length);
384
- if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(inner)) {
385
- return inner;
386
- }
387
- }
388
- }
389
- return crypto.randomUUID();
390
- }
391
- /** Returns all document.cookie names. */
392
- static _cookieNames() {
393
- return document.cookie.split(";").map((c) => c.split("=")[0]?.trim() ?? "");
394
- }
395
- /** Returns all localStorage keys (empty array when unavailable). */
396
- static _localStorageKeys() {
397
- if (typeof localStorage === "undefined") return [];
398
- return Object.keys(localStorage);
399
- }
400
- buildCookieOpts() {
401
- const opts = {
402
- path: "/",
403
- sameSite: "Lax",
404
- maxAge: 365 * 24 * 60 * 60
405
- };
406
- if (this.cookieDomains) {
407
- const domain = this.cookieDomains.split(",")[0]?.trim();
408
- if (domain) opts.domain = domain;
409
- }
410
- return opts;
411
- }
412
- };
413
-
414
- // src/core/event-bus.ts
415
- var EventBus = class {
416
- /**
417
- * @param profileId - Profile ID included in all GTM pushes.
418
- * @param gtmConfig - Optional GTM integration settings.
419
- */
420
- constructor(profileId, gtmConfig) {
421
- this.profileId = profileId;
422
- this.gtmConfig = gtmConfig;
423
- }
424
- /**
425
- * Dispatches a `consenti:*` custom event on `window` and, if GTM is configured,
426
- * pushes the event to `window.dataLayer`.
427
- *
428
- * Silently no-ops in SSR contexts.
429
- *
430
- * @param name - The event name.
431
- * @param detail - Event payload attached to `event.detail` and included in the GTM push.
432
- */
433
- dispatch(name, detail = {}) {
434
- if (!isClient()) return;
435
- const event = new CustomEvent(name, { detail, bubbles: true });
436
- window.dispatchEvent(event);
437
- this.pushToGTM(name, detail);
438
- }
439
- pushToGTM(name, detail) {
440
- const gtm = this.gtmConfig;
441
- if (!gtm) return;
442
- const win = safeWindow();
443
- if (!win) return;
444
- const layerName = gtm.dataLayer ?? "dataLayer";
445
- const layer = win[layerName];
446
- if (!Array.isArray(layer)) return;
447
- const shouldPush = !gtm.events || gtm.events.length === 0 || gtm.events.includes(name);
448
- if (!shouldPush) return;
449
- layer.push({
450
- event: name,
451
- consenti: {
452
- profileId: this.profileId,
453
- consent: detail.consent,
454
- consentId: detail.consentId,
455
- consentAction: detail.consentAction
456
- }
457
- });
458
- if (name === "consenti:consentSubmitted" && this.hasGtag(win)) {
459
- if (gtm.urlPassthrough) {
460
- layer.push({ url_passthrough: true });
461
- }
462
- if (gtm.adsDataRedaction) {
463
- const adConsentDenied = detail.consent?.["ad_storage"] !== "granted";
464
- layer.push({ ads_data_redaction: adConsentDenied });
465
- }
466
- }
467
- }
468
- /**
469
- * Returns `true` when gtag.js or Google Tag Manager is detected on the page.
470
- * Used to gate GCM v2 cookieless-ping pushes so they are not emitted when
471
- * Google services are absent.
472
- */
473
- hasGtag(win) {
474
- return typeof win["gtag"] === "function" || typeof win["google_tag_manager"] !== "undefined";
475
- }
476
- };
477
-
478
- // src/utils/locale.ts
479
- function resolveLocale(translations, requested, defaultLocale) {
480
- const base = translations[defaultLocale];
481
- if (!base) {
482
- throw new Error(`[Consenti] No translations found for defaultLocale "${defaultLocale}"`);
483
- }
484
- if (translations[requested]) {
485
- return deepMerge(base, translations[requested]);
486
- }
487
- const lang = requested.split("-")[0] ?? "";
488
- if (lang !== requested && translations[lang]) {
489
- return deepMerge(base, translations[lang]);
490
- }
491
- return base;
492
- }
493
- function deepMerge(base, override) {
494
- const result = { ...base };
495
- for (const key in override) {
496
- const overrideVal = override[key];
497
- if (overrideVal === void 0 || overrideVal === null) continue;
498
- if (typeof overrideVal === "object" && !Array.isArray(overrideVal) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) {
499
- result[key] = deepMerge(result[key], overrideVal);
500
- } else {
501
- result[key] = overrideVal;
502
- }
503
- }
504
- return result;
505
- }
506
-
507
- // src/core/profile-resolver.ts
508
- var profileRegistry = /* @__PURE__ */ new Map();
509
- function registerProfile(id, config) {
510
- profileRegistry.set(id, config);
511
- }
512
- var DEFAULT_PROFILE = {
513
- id: 0,
514
- version: 1,
515
- defaultLocale: "en",
516
- cookies: [
517
- { id: "functionality_storage", mandatory: true },
518
- {
519
- id: "analytics_storage",
520
- listenGpc: true,
521
- expiry: 365
522
- },
523
- {
524
- id: "ad_storage",
525
- listenGpc: true,
526
- expiry: 365
527
- },
528
- {
529
- id: "ad_user_data",
530
- listenGpc: true,
531
- expiry: 365
532
- },
533
- {
534
- id: "ad_personalization",
535
- listenGpc: true,
536
- expiry: 365
537
- }
538
- ],
539
- mainBanner: {
540
- position: "bottom",
541
- heading: "We value your privacy",
542
- htmlText: "We use cookies to enhance your browsing experience, serve personalised ads or content, and analyse our traffic. By clicking <strong>Accept All</strong> you consent to our use of cookies. You may change your preferences at any time.",
543
- buttons: [
544
- { text: "Accept All", cookies: "*", style: "primary", action: "custom" },
545
- { text: "Reject All", cookies: "!", style: "primary", action: "custom" },
546
- { text: "Manage Preferences", style: "secondary", action: "manage" }
547
- ]
548
- },
549
- preferenceModal: {
550
- heading: "Cookie Preferences",
551
- subheading: "Choose which cookies you allow us to use.",
552
- htmlText: "We use different types of cookies to optimise your experience. You can choose to enable or disable each category below. Necessary cookies cannot be disabled as they are required for core site functionality.",
553
- showClose: true,
554
- overlayOpacity: 50,
555
- categories: [
556
- {
557
- id: "cat-necessary",
558
- heading: "Strictly Necessary",
559
- htmlText: "These cookies are essential for the website to function correctly. They include authentication, security, and accessibility features. They cannot be disabled.",
560
- mandatory: true,
561
- cookies: ["functionality_storage"]
562
- },
563
- {
564
- id: "cat-analytics",
565
- heading: "Analytics",
566
- htmlText: "These cookies help us understand how visitors interact with our website by collecting and reporting information anonymously. This allows us to improve the site over time. (e.g. Google Analytics)",
567
- cookies: ["analytics_storage"]
568
- },
569
- {
570
- id: "cat-advertising",
571
- heading: "Advertising",
572
- htmlText: "These cookies are used to deliver advertisements more relevant to you and your interests. They are also used to limit the number of times you see an ad and to help measure the effectiveness of advertising campaigns. (e.g. Google Ads, Meta Pixel)",
573
- cookies: ["ad_storage", "ad_user_data", "ad_personalization"]
574
- }
575
- ],
576
- buttons: [
577
- { text: "Accept All", cookies: "*", style: "primary", action: "custom" },
578
- { text: "Save Preferences", style: "primary", action: "submit" },
579
- { text: "Reject All", cookies: "!", style: "primary", action: "custom" }
580
- ]
581
- }
582
- };
583
- async function resolveProfile(config) {
584
- const locale = config.core.locale ?? "en";
585
- const profileId = config.core.profileId ?? 0;
586
- if (config.api?.enabled) {
587
- try {
588
- const base = config.api.baseUrl ?? (typeof window !== "undefined" ? window.location.origin : "");
589
- const url = `${base}/consenti/api/v1/profiles/${profileId}/${encodeURIComponent(locale)}`;
590
- const apiProfile = await httpRequest(url, {}, config.api.authToken);
591
- return {
592
- id: profileId,
593
- version: apiProfile.version,
594
- defaultLocale: apiProfile.defaultLocale,
595
- locales: apiProfile.locales,
596
- cookies: apiProfile.cookies,
597
- mainBanner: apiProfile.mainBanner,
598
- preferenceModal: apiProfile.preferenceModal,
599
- ...apiProfile.gpcBanner != null ? { gpcBanner: apiProfile.gpcBanner } : {}
600
- };
601
- } catch {
602
- console.warn("[Consenti] API profile fetch failed, falling back to local profile");
603
- }
604
- }
605
- if (profileId > 0) {
606
- const local = profileRegistry.get(profileId);
607
- if (local) {
608
- const resolved = resolveLocale(local.translations ?? {}, locale, local.defaultLocale);
609
- return {
610
- id: profileId,
611
- version: 1,
612
- defaultLocale: local.defaultLocale,
613
- locales: Object.keys(local.translations ?? {}),
614
- cookies: local.cookies ?? [],
615
- mainBanner: resolved.mainBanner,
616
- ...resolved.gpcBanner ? { gpcBanner: resolved.gpcBanner } : {},
617
- preferenceModal: resolved.preferenceModal,
618
- ...local.darkMode !== void 0 ? { darkMode: local.darkMode } : {}
619
- };
620
- }
621
- throw new Error(
622
- `[Consenti] Profile ${profileId} not found. Register it with ConsentiProfile or enable the API.`
623
- );
624
- }
625
- return DEFAULT_PROFILE;
626
- }
627
-
628
- // src/core/gpc.ts
629
- function detectGPC() {
630
- const nav = safeNavigator();
631
- if (!nav) return false;
632
- return "globalPrivacyControl" in nav && nav.globalPrivacyControl === true;
633
- }
634
- function applyGPCToConsent(cookies, currentConsent, mode) {
635
- if (!mode || !detectGPC()) return { ...currentConsent };
636
- const result = { ...currentConsent };
637
- for (const cookie of cookies) {
638
- if (cookie.mandatory) continue;
639
- if (cookie.listenGpc) {
640
- result[cookie.id] = cookie.type === "legitimate_interest" ? "objected" : "denied";
641
- }
642
- }
643
- for (const cookie of cookies) {
644
- if (cookie.mandatory) result[cookie.id] = "granted";
645
- }
646
- return result;
647
- }
648
-
649
- // src/ui/buttons.ts
650
- function buildButton(btn, handlers) {
651
- const el = document.createElement("button");
652
- el.type = "button";
653
- el.className = `consenti-btn consenti-btn--${btn.style}`;
654
- el.textContent = btn.text;
655
- el.addEventListener("click", () => {
656
- switch (btn.action) {
657
- case "custom": {
658
- const cookies = btn.cookies;
659
- if (cookies === "*") {
660
- handlers.onGrantAll();
661
- } else if (cookies === "!") {
662
- handlers.onDenyAll();
663
- } else if (Array.isArray(cookies) && cookies.length > 0) {
664
- handlers.onGrantSpecific(cookies);
665
- }
666
- break;
667
- }
668
- case "submit":
669
- handlers.onSubmit();
670
- break;
671
- case "manage":
672
- handlers.onManage();
673
- break;
674
- case "close":
675
- handlers.onClose();
676
- break;
677
- }
678
- });
679
- return el;
680
- }
681
-
682
- // src/ui/overlay.ts
683
- function createOverlay(opacity, onClick) {
684
- const el = document.createElement("div");
685
- el.className = "consenti-overlay";
686
- el.style.opacity = String(opacity / 100);
687
- el.setAttribute("aria-hidden", "true");
688
- if (onClick) {
689
- el.addEventListener("click", onClick);
690
- }
691
- return el;
692
- }
693
-
694
- // src/ui/banner.ts
695
- var Banner = class {
696
- constructor() {
697
- this.el = null;
698
- this.overlayEl = null;
699
- }
700
- /**
701
- * Constructs the banner DOM from the given config and returns the root element.
702
- * The element is not yet inserted into the document — the caller appends it to
703
- * the mount root via `Renderer.mount()`.
704
- *
705
- * @param config - Banner layout and content config from the resolved profile.
706
- * @param handlers - Click handlers wired to each button.
707
- * @param privacyPolicyUrl - Optional URL appended as a "Privacy Policy" link after the body text.
708
- * @param locales - All locale codes available on the profile (for the locale switcher).
709
- * @param activeLocale - The currently active locale code.
710
- * @param onLocaleSwitch - Callback invoked when the user selects a different locale.
711
- * @returns The constructed banner `<div>` element.
712
- */
713
- build(config, handlers, privacyPolicyUrl, locales, activeLocale, onLocaleSwitch) {
714
- const banner = document.createElement("div");
715
- banner.id = "consenti-banner";
716
- banner.className = `consenti-banner consenti-banner--${config.position}`;
717
- banner.setAttribute("role", "region");
718
- banner.setAttribute("aria-label", "Cookie consent");
719
- banner.setAttribute("aria-live", "polite");
720
- const textContainer = document.createElement("div");
721
- textContainer.className = `consenti-banner__text-container`;
722
- const linksContainer = document.createElement("div");
723
- linksContainer.className = "consenti-banner__links";
724
- if (config.heading) {
725
- const headingTag = config.headingTag ?? "h2";
726
- const heading = document.createElement(headingTag);
727
- heading.id = "consenti-banner-heading";
728
- heading.className = "consenti-banner__heading";
729
- heading.textContent = config.heading;
730
- textContainer.appendChild(heading);
731
- }
732
- const text = document.createElement("div");
733
- text.className = "consenti-banner__text";
734
- text.innerHTML = config.htmlText;
735
- if (privacyPolicyUrl) {
736
- const link = document.createElement("a");
737
- link.href = privacyPolicyUrl;
738
- link.className = "consenti-banner__link consenti-banner__link--privacy consenti-btn consenti-btn--text";
739
- link.textContent = "Privacy Policy";
740
- link.setAttribute("target", "_blank");
741
- link.setAttribute("rel", "noopener noreferrer");
742
- linksContainer.appendChild(link);
743
- }
744
- textContainer.appendChild(text);
745
- const linkButtons = config.buttons.filter((b) => b.action === "link");
746
- if (linkButtons.length > 0) {
747
- for (const btn of linkButtons) {
748
- if (!btn.url) continue;
749
- const anchor = document.createElement("a");
750
- anchor.href = btn.url;
751
- anchor.className = `consenti-banner__link consenti-btn consenti-btn--${btn.style ?? "text"}`;
752
- anchor.textContent = btn.text;
753
- anchor.setAttribute("target", "_blank");
754
- anchor.setAttribute("rel", "noopener noreferrer");
755
- linksContainer.appendChild(anchor);
756
- }
757
- }
758
- if (linkButtons.length > 0 || privacyPolicyUrl) {
759
- textContainer.appendChild(linksContainer);
760
- }
761
- banner.appendChild(textContainer);
762
- const actionButtons = config.buttons.filter((b) => b.action !== "link");
763
- if (actionButtons.length > 0) {
764
- const btns = document.createElement("div");
765
- btns.className = "consenti-banner__buttons";
766
- for (const btn of actionButtons) {
767
- btns.appendChild(buildButton(btn, handlers));
768
- }
769
- banner.appendChild(btns);
770
- }
771
- const hasClose = config.showClose;
772
- const hasLocaleSwitcher = config.showLocaleSwitcher && locales && locales.length > 1;
773
- if (hasClose || hasLocaleSwitcher) {
774
- const controls = document.createElement("div");
775
- controls.className = "consenti-banner__controls";
776
- if (hasLocaleSwitcher && locales && onLocaleSwitch) {
777
- controls.appendChild(buildLocaleSwitcher(locales, activeLocale, onLocaleSwitch));
778
- }
779
- if (hasClose) {
780
- const close = document.createElement("button");
781
- close.type = "button";
782
- close.className = "consenti-banner__close consenti-btn consenti-btn--close";
783
- close.setAttribute("aria-label", "Close cookie banner");
784
- close.textContent = "\xD7";
785
- close.addEventListener("click", handlers.onClose);
786
- controls.appendChild(close);
787
- }
788
- banner.appendChild(controls);
789
- }
790
- if (config.overlayOpacity && config.overlayOpacity > 0) {
791
- this.overlayEl = createOverlay(config.overlayOpacity);
792
- }
793
- this.el = banner;
794
- return banner;
795
- }
796
- /** Returns the overlay element (if `overlayOpacity > 0`), or `null`. */
797
- getOverlay() {
798
- return this.overlayEl;
799
- }
800
- /** Makes the banner and its overlay visible after a `hide()` call. */
801
- show() {
802
- if (this.el) this.el.style.display = "";
803
- if (this.overlayEl) this.overlayEl.style.display = "";
804
- }
805
- /** Hides the banner and its overlay without removing them from the DOM. */
806
- hide() {
807
- if (this.el) this.el.style.display = "none";
808
- if (this.overlayEl) this.overlayEl.style.display = "none";
809
- }
810
- /** Removes the banner and overlay from the DOM entirely. */
811
- remove() {
812
- this.el?.remove();
813
- this.overlayEl?.remove();
814
- this.el = null;
815
- this.overlayEl = null;
816
- }
817
- };
818
- function buildLocaleSwitcher(locales, activeLocale, onSwitch) {
819
- const wrapper = document.createElement("div");
820
- wrapper.className = "consenti-locale-switcher";
821
- const btn = document.createElement("button");
822
- btn.type = "button";
823
- btn.className = "consenti-locale-switcher__btn consenti-btn consenti-btn--close";
824
- btn.setAttribute("aria-label", "Switch language");
825
- btn.setAttribute("aria-haspopup", "listbox");
826
- btn.textContent = "\u{1F310}";
827
- const list = document.createElement("ul");
828
- list.className = "consenti-locale-switcher__list";
829
- list.setAttribute("role", "listbox");
830
- list.setAttribute("aria-label", "Select language");
831
- list.hidden = true;
832
- for (const locale of locales) {
833
- const item = document.createElement("li");
834
- item.setAttribute("role", "option");
835
- item.setAttribute("aria-selected", String(locale === activeLocale));
836
- const localeBtn = document.createElement("button");
837
- localeBtn.type = "button";
838
- localeBtn.className = `consenti-locale-switcher__option${locale === activeLocale ? " consenti-locale-switcher__option--active" : ""}`;
839
- localeBtn.textContent = locale;
840
- localeBtn.addEventListener("click", (e) => {
841
- e.stopPropagation();
842
- list.hidden = true;
843
- if (locale !== activeLocale) onSwitch(locale);
844
- });
845
- item.appendChild(localeBtn);
846
- list.appendChild(item);
847
- }
848
- btn.addEventListener("click", (e) => {
849
- e.stopPropagation();
850
- list.hidden = !list.hidden;
851
- });
852
- document.addEventListener("click", () => {
853
- list.hidden = true;
854
- }, { capture: true, once: false });
855
- wrapper.appendChild(btn);
856
- wrapper.appendChild(list);
857
- return wrapper;
858
- }
859
-
860
- // src/ui/focus-trap.ts
861
- var FOCUSABLE_SELECTORS = [
862
- "a[href]",
863
- "button:not([disabled])",
864
- "input:not([disabled])",
865
- "select:not([disabled])",
866
- "textarea:not([disabled])",
867
- '[tabindex]:not([tabindex="-1"])',
868
- '[contenteditable="true"]'
869
- ].join(", ");
870
- function trapFocus(container) {
871
- const getFocusable = () => Array.from(container.querySelectorAll(FOCUSABLE_SELECTORS)).filter(
872
- (el) => !el.closest('[aria-hidden="true"]')
873
- );
874
- const handleKeydown = (e) => {
875
- if (e.key !== "Tab") return;
876
- const els = getFocusable();
877
- if (els.length === 0) return;
878
- const first = els[0];
879
- const last = els[els.length - 1];
880
- if (e.shiftKey) {
881
- if (document.activeElement === first || document.activeElement === container) {
882
- e.preventDefault();
883
- last.focus();
884
- }
885
- } else {
886
- if (document.activeElement === last) {
887
- e.preventDefault();
888
- first.focus();
889
- }
890
- }
891
- };
892
- const handleEscape = (e) => {
893
- if (e.key === "Escape") {
894
- container.dispatchEvent(new CustomEvent("consenti:closeRequest", { bubbles: true }));
895
- }
896
- };
897
- document.addEventListener("keydown", handleKeydown);
898
- document.addEventListener("keydown", handleEscape);
899
- getFocusable()[0]?.focus();
900
- return () => {
901
- document.removeEventListener("keydown", handleKeydown);
902
- document.removeEventListener("keydown", handleEscape);
903
- };
904
- }
905
-
906
- // src/ui/modal.ts
907
- var Modal = class {
908
- constructor() {
909
- this.el = null;
910
- this.triggerEl = null;
911
- this.trapCleanup = null;
912
- /** Map of category ID → toggle button element, used to read state on submit. */
913
- this.toggleMap = /* @__PURE__ */ new Map();
914
- this.receiptCheckbox = null;
915
- /** Live toggle state — updated on each click so `getToggleConsent()` reads the latest. */
916
- this.toggleState = /* @__PURE__ */ new Map();
917
- /** When `true`, the modal wrapper is passthrough (overlayOpacity === 0). */
918
- this.passthrough = false;
919
- }
920
- /**
921
- * Constructs the modal DOM from the given config.
922
- * The modal is not inserted into the document until `open()` is called.
923
- *
924
- * @param config - Modal layout and content config from the resolved profile.
925
- * @param initialConsent - Current stored consent, used to pre-set toggle states.
926
- * @param handlers - Click handlers wired to each button.
927
- * @param allowReceipt - When `true`, renders the consent receipt download checkbox.
928
- * @param dpdpaConfig - DPDPA grievance notice config.
929
- * @param locales - All available locale codes (for locale switcher).
930
- * @param activeLocale - The currently active locale code.
931
- * @param onLocaleSwitch - Callback invoked when the user selects a different locale.
932
- */
933
- build(config, initialConsent, handlers, allowReceipt, dpdpaConfig, locales, activeLocale, onLocaleSwitch) {
934
- const position = config.position ?? "center";
935
- const wrapper = document.createElement("div");
936
- wrapper.id = "consenti-modal";
937
- wrapper.className = `consenti-modal consenti-modal--${position}`;
938
- wrapper.setAttribute("role", "dialog");
939
- wrapper.setAttribute("aria-modal", "true");
940
- const overlayOpacity = config.overlayOpacity ?? 50;
941
- this.passthrough = overlayOpacity === 0;
942
- if (overlayOpacity > 0) {
943
- const overlayClickHandler = config.persistent ? void 0 : () => handlers.onClose();
944
- const overlay = createOverlay(overlayOpacity, overlayClickHandler);
945
- overlay.className = "consenti-modal__overlay";
946
- wrapper.appendChild(overlay);
947
- } else {
948
- wrapper.classList.add("consenti-modal--passthrough");
949
- }
950
- const container = document.createElement("div");
951
- container.className = "consenti-modal__container";
952
- const header = document.createElement("div");
953
- header.className = "consenti-modal__header";
954
- if (config.heading) {
955
- const headingTag = config.headingTag ?? "h2";
956
- const heading = document.createElement(headingTag);
957
- heading.id = "consenti-modal-heading";
958
- heading.className = "consenti-modal__heading";
959
- heading.textContent = config.heading;
960
- header.appendChild(heading);
961
- wrapper.setAttribute("aria-labelledby", "consenti-modal-heading");
962
- }
963
- const hasClose = config.showClose !== false;
964
- const hasLocaleSwitcher = config.showLocaleSwitcher && locales && locales.length > 1;
965
- if (hasLocaleSwitcher || hasClose) {
966
- const headerControls = document.createElement("div");
967
- headerControls.className = "consenti-modal__header-controls";
968
- if (hasLocaleSwitcher && locales && onLocaleSwitch) {
969
- headerControls.appendChild(buildModalLocaleSwitcher(locales, activeLocale, onLocaleSwitch));
970
- }
971
- if (hasClose) {
972
- const close = document.createElement("button");
973
- close.type = "button";
974
- close.className = "consenti-modal__close consenti-btn consenti-btn--close";
975
- close.setAttribute("aria-label", "Close preference modal");
976
- close.textContent = "\xD7";
977
- close.addEventListener("click", handlers.onClose);
978
- headerControls.appendChild(close);
979
- }
980
- header.appendChild(headerControls);
981
- }
982
- container.appendChild(header);
983
- if (config.subheading) {
984
- const sub = document.createElement("p");
985
- sub.className = "consenti-modal__subheading";
986
- sub.textContent = config.subheading;
987
- container.appendChild(sub);
988
- }
989
- if (config.htmlText) {
990
- const desc = document.createElement("div");
991
- desc.className = "consenti-modal__text";
992
- desc.innerHTML = config.htmlText;
993
- container.appendChild(desc);
994
- }
995
- const linkButtons = config.buttons.filter((b) => b.action === "link");
996
- if (linkButtons.length > 0) {
997
- const linksContainer = document.createElement("div");
998
- linksContainer.className = "consenti-modal__links";
999
- for (const btn of linkButtons) {
1000
- if (!btn.url) continue;
1001
- const anchor = document.createElement("a");
1002
- anchor.href = btn.url;
1003
- anchor.className = `consenti-modal__link consenti-btn consenti-btn--${btn.style ?? "text"}`;
1004
- anchor.textContent = btn.text;
1005
- anchor.setAttribute("target", "_blank");
1006
- anchor.setAttribute("rel", "noopener noreferrer");
1007
- linksContainer.appendChild(anchor);
1008
- }
1009
- container.appendChild(linksContainer);
1010
- }
1011
- const body = document.createElement("div");
1012
- body.className = "consenti-modal__body";
1013
- const categories = document.createElement("div");
1014
- categories.className = "consenti-modal__categories";
1015
- for (const cat of config.categories) {
1016
- const catEl = this.buildCategory(cat, initialConsent);
1017
- categories.appendChild(catEl);
1018
- }
1019
- body.appendChild(categories);
1020
- container.appendChild(body);
1021
- if (allowReceipt) {
1022
- container.appendChild(this.buildReceiptOption());
1023
- }
1024
- if (dpdpaConfig) {
1025
- const notice = document.createElement("div");
1026
- notice.className = "consenti-modal__dpdpa-notice";
1027
- notice.innerHTML = [
1028
- `<strong>Data Fiduciary:</strong> ${dpdpaConfig.dataFiduciary}.`,
1029
- dpdpaConfig.purposeDescription ? ` <strong>Purpose:</strong> ${dpdpaConfig.purposeDescription}.` : "",
1030
- ` You may withdraw consent at any time or contact our Grievance Officer at`,
1031
- ` <a href="mailto:${dpdpaConfig.grievanceEmail}">${dpdpaConfig.grievanceEmail}</a>.`
1032
- ].join("");
1033
- container.appendChild(notice);
1034
- }
1035
- const btns = document.createElement("div");
1036
- btns.className = "consenti-modal__buttons";
1037
- for (const btn of config.buttons.filter((b) => b.action !== "link")) {
1038
- btns.appendChild(buildButton(btn, handlers));
1039
- }
1040
- container.appendChild(btns);
1041
- wrapper.appendChild(container);
1042
- wrapper.addEventListener("consenti:closeRequest", () => handlers.onClose());
1043
- this.el = wrapper;
1044
- }
1045
- buildCategory(cat, initialConsent) {
1046
- const isLI = cat.type === "legitimate_interest";
1047
- const isMandatory = cat.mandatory === true;
1048
- let defaultOn;
1049
- if (isMandatory) {
1050
- defaultOn = true;
1051
- } else if (isLI) {
1052
- defaultOn = initialConsent[cat.cookies[0] ?? ""] !== "objected";
1053
- } else {
1054
- defaultOn = initialConsent[cat.cookies[0] ?? ""] === "granted";
1055
- }
1056
- this.toggleState.set(cat.id, defaultOn);
1057
- const wrapper = document.createElement("div");
1058
- wrapper.className = `consenti-category${isMandatory ? " consenti-category--mandatory" : ""}`;
1059
- const header = document.createElement("div");
1060
- header.className = "consenti-category__header";
1061
- const headingTag = cat.headingTag ?? "h3";
1062
- const heading = document.createElement(headingTag);
1063
- heading.id = `consenti-cat-${cat.id}-heading`;
1064
- heading.className = "consenti-category__heading";
1065
- heading.textContent = cat.heading;
1066
- header.appendChild(heading);
1067
- const toggle = document.createElement("button");
1068
- toggle.type = "button";
1069
- toggle.className = `consenti-category__toggle${isMandatory ? " consenti-category__toggle--mandatory" : ""}`;
1070
- toggle.setAttribute("role", "switch");
1071
- toggle.setAttribute("aria-checked", String(defaultOn));
1072
- toggle.setAttribute("aria-labelledby", `consenti-cat-${cat.id}-heading`);
1073
- if (isMandatory) toggle.setAttribute("aria-disabled", "true");
1074
- const knob = document.createElement("span");
1075
- knob.className = "consenti-category__toggle-knob";
1076
- knob.setAttribute("aria-hidden", "true");
1077
- toggle.appendChild(knob);
1078
- if (!isMandatory) {
1079
- toggle.addEventListener("click", () => {
1080
- const current = this.toggleState.get(cat.id) ?? false;
1081
- const next = !current;
1082
- this.toggleState.set(cat.id, next);
1083
- toggle.setAttribute("aria-checked", String(next));
1084
- });
1085
- }
1086
- this.toggleMap.set(cat.id, toggle);
1087
- header.appendChild(toggle);
1088
- wrapper.appendChild(header);
1089
- const text = document.createElement("div");
1090
- text.className = "consenti-category__text";
1091
- text.innerHTML = cat.htmlText;
1092
- wrapper.appendChild(text);
1093
- if (isLI && cat.legitimateInterest?.description) {
1094
- const li = document.createElement("p");
1095
- li.className = "consenti-category__li-desc";
1096
- li.textContent = cat.legitimateInterest.description;
1097
- wrapper.appendChild(li);
1098
- }
1099
- return wrapper;
1100
- }
1101
- buildReceiptOption() {
1102
- const wrapper = document.createElement("div");
1103
- wrapper.className = "consenti-modal__receipt-option";
1104
- const label = document.createElement("label");
1105
- label.className = "consenti-modal__receipt-label";
1106
- const checkbox = document.createElement("input");
1107
- checkbox.type = "checkbox";
1108
- checkbox.className = "consenti-modal__receipt-checkbox";
1109
- checkbox.id = "consenti-receipt-checkbox";
1110
- checkbox.setAttribute("aria-describedby", "consenti-receipt-desc");
1111
- this.receiptCheckbox = checkbox;
1112
- label.appendChild(checkbox);
1113
- label.append(" Get a copy of my consent choices (JSON)");
1114
- wrapper.appendChild(label);
1115
- const desc = document.createElement("p");
1116
- desc.id = "consenti-receipt-desc";
1117
- desc.className = "consenti-modal__receipt-desc";
1118
- desc.textContent = "A JSON file will be downloaded to your device when you save your preferences.";
1119
- wrapper.appendChild(desc);
1120
- return wrapper;
1121
- }
1122
- /**
1123
- * Appends the modal to `document.body` and activates the focus trap.
1124
- * The receipt checkbox (if present) is reset to unchecked.
1125
- *
1126
- * @param triggerEl - The element that opened the modal. Focus returns here on `close()`.
1127
- */
1128
- open(triggerEl) {
1129
- if (!this.el) return;
1130
- this.triggerEl = triggerEl ?? document.activeElement;
1131
- if (this.receiptCheckbox) this.receiptCheckbox.checked = false;
1132
- document.body.appendChild(this.el);
1133
- if (!this.passthrough) {
1134
- this.trapCleanup = trapFocus(this.el);
1135
- }
1136
- }
1137
- /**
1138
- * Removes the modal from `document.body`, deactivates the focus trap,
1139
- * and returns focus to `triggerEl`.
1140
- */
1141
- close() {
1142
- this.trapCleanup?.();
1143
- this.trapCleanup = null;
1144
- this.el?.remove();
1145
- this.triggerEl?.focus();
1146
- this.triggerEl = null;
1147
- }
1148
- /** Returns `true` if the modal is currently mounted in the document. */
1149
- isOpen() {
1150
- return this.el?.isConnected === true;
1151
- }
1152
- /** Returns `true` if the receipt download checkbox is checked at the time of calling. */
1153
- isReceiptRequested() {
1154
- return this.receiptCheckbox?.checked ?? false;
1155
- }
1156
- /**
1157
- * Reads the current toggle state map and converts it to a `ConsentValue`.
1158
- *
1159
- * Called by the submit handler to build the consent object before writing to storage.
1160
- *
1161
- * @param categories - The category list from the resolved profile (provides cookie IDs and type).
1162
- */
1163
- getToggleConsent(categories) {
1164
- const consent = {};
1165
- for (const cat of categories) {
1166
- const isLI = cat.type === "legitimate_interest";
1167
- const isMandatory = cat.mandatory === true;
1168
- const on = this.toggleState.get(cat.id) ?? false;
1169
- for (const cookieId of cat.cookies) {
1170
- if (isMandatory) {
1171
- consent[cookieId] = "granted";
1172
- } else if (isLI) {
1173
- consent[cookieId] = on ? "granted" : "objected";
1174
- } else {
1175
- consent[cookieId] = on ? "granted" : "denied";
1176
- }
1177
- }
1178
- }
1179
- return consent;
1180
- }
1181
- /** Removes the modal element from the DOM and clears all internal state. */
1182
- destroy() {
1183
- this.trapCleanup?.();
1184
- this.el?.remove();
1185
- this.el = null;
1186
- this.toggleMap.clear();
1187
- this.toggleState.clear();
1188
- this.receiptCheckbox = null;
1189
- }
1190
- };
1191
- function buildModalLocaleSwitcher(locales, activeLocale, onSwitch) {
1192
- const wrapper = document.createElement("div");
1193
- wrapper.className = "consenti-locale-switcher consenti-locale-switcher--modal";
1194
- const btn = document.createElement("button");
1195
- btn.type = "button";
1196
- btn.className = "consenti-locale-switcher__btn consenti-btn consenti-btn--close";
1197
- btn.setAttribute("aria-label", "Switch language");
1198
- btn.setAttribute("aria-haspopup", "listbox");
1199
- btn.textContent = "\u{1F310}";
1200
- const list = document.createElement("ul");
1201
- list.className = "consenti-locale-switcher__list";
1202
- list.setAttribute("role", "listbox");
1203
- list.setAttribute("aria-label", "Select language");
1204
- list.hidden = true;
1205
- for (const locale of locales) {
1206
- const item = document.createElement("li");
1207
- item.setAttribute("role", "option");
1208
- item.setAttribute("aria-selected", String(locale === activeLocale));
1209
- const localeBtn = document.createElement("button");
1210
- localeBtn.type = "button";
1211
- localeBtn.className = `consenti-locale-switcher__option${locale === activeLocale ? " consenti-locale-switcher__option--active" : ""}`;
1212
- localeBtn.textContent = locale;
1213
- localeBtn.addEventListener("click", (e) => {
1214
- e.stopPropagation();
1215
- list.hidden = true;
1216
- if (locale !== activeLocale) onSwitch(locale);
1217
- });
1218
- item.appendChild(localeBtn);
1219
- list.appendChild(item);
1220
- }
1221
- btn.addEventListener("click", (e) => {
1222
- e.stopPropagation();
1223
- list.hidden = !list.hidden;
1224
- });
1225
- document.addEventListener("click", () => {
1226
- list.hidden = true;
1227
- }, { capture: true, once: false });
1228
- wrapper.appendChild(btn);
1229
- wrapper.appendChild(list);
1230
- return wrapper;
1231
- }
1232
-
1233
- // src/ui/renderer.ts
1234
- var ROOT_ID = "consenti-root";
1235
- var Renderer = class {
1236
- constructor() {
1237
- this.root = null;
1238
- this.styleEl = null;
1239
- }
1240
- /**
1241
- * Returns the mount-point element, creating `#consenti-root` if needed.
1242
- *
1243
- * When `rootEl` is supplied:
1244
- * - `string` → resolved via `document.querySelector`. Throws if no match.
1245
- * - `HTMLElement` → used directly. Throws if not attached to the document.
1246
- * On subsequent calls the previously stored root is returned immediately.
1247
- *
1248
- * @throws `Error` if called in SSR or if a supplied `rootEl` cannot be resolved.
1249
- */
1250
- mount(rootEl) {
1251
- if (!isClient()) throw new Error("[Consenti] Renderer.mount() called in SSR context");
1252
- if (this.root) return this.root;
1253
- if (rootEl !== void 0) {
1254
- if (typeof rootEl === "string") {
1255
- const found = document.querySelector(rootEl);
1256
- if (!found) throw new Error(`[Consenti] rootEl selector "${rootEl}" did not match any element.`);
1257
- this.root = found;
1258
- } else {
1259
- if (!document.contains(rootEl)) throw new Error("[Consenti] rootEl element is not attached to the document.");
1260
- this.root = rootEl;
1261
- }
1262
- return this.root;
1263
- }
1264
- let root = document.getElementById(ROOT_ID);
1265
- if (!root) {
1266
- root = document.createElement("div");
1267
- root.id = ROOT_ID;
1268
- document.body.appendChild(root);
1269
- }
1270
- this.root = root;
1271
- return root;
1272
- }
1273
- /**
1274
- * Injects a `<style id="consenti-styles">` tag into `<head>` with the given CSS string.
1275
- * No-ops if the style element already exists (idempotent).
1276
- *
1277
- * @param css - The CSS content to inject.
1278
- */
1279
- injectStyles(css) {
1280
- if (!isClient()) return;
1281
- if (this.styleEl) return;
1282
- const style = document.createElement("style");
1283
- style.id = "consenti-styles";
1284
- style.textContent = css;
1285
- document.head.appendChild(style);
1286
- this.styleEl = style;
1287
- }
1288
- /** Removes `#consenti-root` and the injected `<style>` tag from the document. */
1289
- unmount() {
1290
- this.root?.remove();
1291
- this.styleEl?.remove();
1292
- this.root = null;
1293
- this.styleEl = null;
1294
- }
1295
- /**
1296
- * Announces a message to screen readers via a visually-hidden `aria-live="polite"` region.
1297
- *
1298
- * The region is created on first call and reused. The message is cleared then
1299
- * re-set via `requestAnimationFrame` to guarantee the change is detected by
1300
- * AT even if the same string is announced twice in a row.
1301
- *
1302
- * @param message - The message to announce (plain text, not HTML).
1303
- */
1304
- announceToScreenReader(message) {
1305
- if (!isClient()) return;
1306
- let announcer = document.getElementById("consenti-announcer");
1307
- if (!announcer) {
1308
- announcer = document.createElement("div");
1309
- announcer.id = "consenti-announcer";
1310
- announcer.setAttribute("role", "status");
1311
- announcer.setAttribute("aria-live", "polite");
1312
- announcer.setAttribute("aria-atomic", "true");
1313
- announcer.style.cssText = "position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;";
1314
- document.body.appendChild(announcer);
1315
- }
1316
- announcer.textContent = "";
1317
- requestAnimationFrame(() => {
1318
- if (announcer) announcer.textContent = message;
1319
- });
1320
- }
1321
- };
1322
-
1323
- // src/core/consenti-setup.ts
1324
- var ConsentiSetup = class {
1325
- /**
1326
- * Creates and initialises the consent widget.
1327
- *
1328
- * Construction is synchronous; the async `init()` is launched in the background.
1329
- * Use `await widget.ready` or `widget.onReady(cb)` to run code after init completes.
1330
- *
1331
- * @param config - Widget configuration. See `ConsentiConfig` for all options.
1332
- */
1333
- constructor(config) {
1334
- this.config = config;
1335
- this.initialized = false;
1336
- this.initializing = false;
1337
- this.profile = null;
1338
- this.consentStore = null;
1339
- this.eventBus = null;
1340
- this.banner = null;
1341
- this.modal = null;
1342
- this.renderer = null;
1343
- this.broadcastChannel = new ConsentiChannel();
1344
- this.plugins = [];
1345
- /** Tracks which banner variant is currently visible, or null when hidden. */
1346
- this._bannerVariant = null;
1347
- this.readyPromise = new Promise((resolve) => {
1348
- this.readyResolve = resolve;
1349
- });
1350
- if (!isClient()) return;
1351
- if (config.api?.enabled && config.core.storage === "localStorage") {
1352
- console.warn(
1353
- "[Consenti] localStorage storage mode cannot be used with API mode (API sets cookies via Set-Cookie header). Falling back to cookie storage."
1354
- );
1355
- config.core.storage = "cookie";
1356
- }
1357
- if (config.autoInit !== false) {
1358
- this.init().catch((err) => console.warn("[Consenti] Init error:", err));
1359
- }
1360
- }
1361
- profileId() {
1362
- return this.config.core.profileId ?? 0;
1363
- }
1364
- /**
1365
- * Starts widget initialisation. Called automatically unless `autoInit: false` is set.
1366
- * Can be called explicitly after construction when `autoInit: false`, or after `destroy()`
1367
- * to re-initialise the same instance.
1368
- */
1369
- async init() {
1370
- if (!isClient()) return;
1371
- if (this.initializing) return;
1372
- this.initializing = true;
1373
- this.initialized = false;
1374
- this.readyPromise = new Promise((resolve) => {
1375
- this.readyResolve = resolve;
1376
- });
1377
- try {
1378
- const storageMode = this.config.core.storage ?? "cookie";
1379
- const userId = this.config.core.userId ?? ConsentStore.getOrCreateUserId(this.profileId(), storageMode);
1380
- this.profile = await resolveProfile(this.config);
1381
- if (this.config.profileOverride) {
1382
- this.profile = { ...this.profile, ...this.config.profileOverride };
1383
- }
1384
- this.eventBus = new EventBus(this.profileId(), this.config.utils?.gtm);
1385
- this.consentStore = new ConsentStore(
1386
- userId,
1387
- this.profileId(),
1388
- storageMode,
1389
- this.config.core.cookieDomains
1390
- );
1391
- this.renderer = new Renderer();
1392
- if (!this.config.core.disableCssTemplate && CONSENTI_CSS) {
1393
- this.renderer.injectStyles(CONSENTI_CSS);
1394
- }
1395
- this.banner = new Banner();
1396
- this.modal = new Modal();
1397
- this.applyTheme();
1398
- this.applyDarkMode();
1399
- this.broadcastChannel.connect((msg) => this.handleBroadcastMessage(msg));
1400
- await this.runPlugins();
1401
- const signingKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
1402
- const existing = this.config.core.signCookies && signingKey ? await this.consentStore.readAndVerify(signingKey) : this.consentStore.read();
1403
- const hasExisting = existing !== null;
1404
- this.eventBus.dispatch("consenti:bannerInitialized", {
1405
- profileId: this.profileId(),
1406
- hasExistingConsent: hasExisting
1407
- });
1408
- const gpcMode = this.config.core.autoHonorGPC ?? false;
1409
- if (gpcMode && detectGPC()) {
1410
- const gpcConsent = applyGPCToConsent(this.profile.cookies, {}, gpcMode);
1411
- if (gpcMode === "strict") {
1412
- await this.submitConsent(gpcConsent, true);
1413
- this.initialized = true;
1414
- this.readyResolve();
1415
- return;
1416
- }
1417
- if (!hasExisting) {
1418
- const gpcSigningKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
1419
- if (this.config.core.signCookies && gpcSigningKey && this.consentStore) {
1420
- this.lastSignature = await this.consentStore.writeAndSign(gpcConsent, gpcSigningKey);
1421
- } else {
1422
- this.consentStore?.write(gpcConsent);
1423
- }
1424
- this.broadcastChannel.send({
1425
- type: "consentUpdated",
1426
- consent: gpcConsent,
1427
- profileId: this.profileId()
1428
- });
1429
- this.eventBus?.dispatch("consenti:consentSubmitted", {
1430
- consent: gpcConsent,
1431
- gpcDetected: true,
1432
- consentId: crypto.randomUUID(),
1433
- consentAction: this.deriveAction(gpcConsent, false),
1434
- pageUrl: window.location.href
1435
- });
1436
- this.showBanner(true);
1437
- this.initialized = true;
1438
- this.readyResolve();
1439
- return;
1440
- }
1441
- }
1442
- const regulation = this.config.core.regulation ?? "gdpr";
1443
- if (regulation === "ccpa" && !hasExisting) {
1444
- const defaultConsent = {};
1445
- for (const cookie of this.profile.cookies) {
1446
- defaultConsent[cookie.id] = "granted";
1447
- }
1448
- const ccpaSigKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
1449
- if (this.config.core.signCookies && ccpaSigKey) {
1450
- await this.consentStore.writeAndSign(defaultConsent, ccpaSigKey);
1451
- } else {
1452
- this.consentStore.write(defaultConsent);
1453
- }
1454
- this.initialized = true;
1455
- this.readyResolve();
1456
- return;
1457
- }
1458
- if (regulation === "cpra" && !hasExisting) {
1459
- const gpcActive = this.config.core.autoHonorGPC && detectGPC();
1460
- const defaultConsent = {};
1461
- for (const cookie of this.profile.cookies) {
1462
- if (cookie.mandatory) {
1463
- defaultConsent[cookie.id] = "granted";
1464
- continue;
1465
- }
1466
- if (cookie.cpraCategory === "sensitive") {
1467
- defaultConsent[cookie.id] = "denied";
1468
- } else if (gpcActive && (cookie.cpraCategory === "sale" || cookie.cpraCategory === "sharing")) {
1469
- defaultConsent[cookie.id] = "denied";
1470
- } else {
1471
- defaultConsent[cookie.id] = "granted";
1472
- }
1473
- }
1474
- const sigKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
1475
- if (this.config.core.signCookies && sigKey) {
1476
- await this.consentStore.writeAndSign(defaultConsent, sigKey);
1477
- } else {
1478
- this.consentStore.write(defaultConsent);
1479
- }
1480
- this.initialized = true;
1481
- this.readyResolve();
1482
- return;
1483
- }
1484
- if (!hasExisting) {
1485
- this.showBanner();
1486
- }
1487
- this.initialized = true;
1488
- this.readyResolve();
1489
- } finally {
1490
- this.initializing = false;
1491
- }
1492
- }
1493
- /** Applies the `consenti-root--dark` class based on config or profile flag. */
1494
- applyDarkMode() {
1495
- if (!this.renderer) return;
1496
- const isDark = this.config.darkMode ?? this.profile?.darkMode ?? false;
1497
- const root = this.renderer.mount(this.config.rootEl);
1498
- if (isDark) {
1499
- root.classList.add("consenti-root--dark");
1500
- } else {
1501
- root.classList.remove("consenti-root--dark");
1502
- }
1503
- }
1504
- /**
1505
- * Injects CSS custom property overrides supplied via `core.theme` into the
1506
- * Consenti root element so they cascade down to all widget elements.
1507
- */
1508
- applyTheme() {
1509
- const theme = this.config.core.theme;
1510
- if (!theme || !this.renderer) return;
1511
- const map = {
1512
- "--consenti-color-bg": theme.bgColor,
1513
- "--consenti-color-text": theme.textColor,
1514
- "--consenti-color-primary": theme.primaryColor,
1515
- "--consenti-color-primary-text": theme.primaryTextColor,
1516
- "--consenti-color-secondary": theme.secondaryColor,
1517
- "--consenti-color-secondary-text": theme.secondaryTextColor,
1518
- "--consenti-color-border": theme.borderColor,
1519
- "--consenti-font-family": theme.fontFamily,
1520
- "--consenti-font-size-base": theme.fontSizeBase,
1521
- "--consenti-font-size-heading": theme.fontSizeHeading,
1522
- "--consenti-border-radius": theme.borderRadius,
1523
- "--consenti-border-radius-btn": theme.buttonBorderRadius,
1524
- "--consenti-toggle-bg-on": theme.toggleBgOn,
1525
- "--consenti-toggle-bg-off": theme.toggleBgOff,
1526
- "--consenti-color-accent": theme.accentColor,
1527
- "--consenti-color-accent-text": theme.accentTextColor
1528
- };
1529
- const root = this.renderer.mount(this.config.rootEl);
1530
- for (const [prop, value] of Object.entries(map)) {
1531
- if (value !== void 0) root.style.setProperty(prop, value);
1532
- }
1533
- if (theme.fontSizeMultiplier) {
1534
- const mult = parseFloat(theme.fontSizeMultiplier);
1535
- if (!isNaN(mult)) {
1536
- const baseSize = parseFloat(getComputedStyle(root).getPropertyValue("--consenti-font-size-base") || "14");
1537
- const headingSize = parseFloat(getComputedStyle(root).getPropertyValue("--consenti-font-size-heading") || "16");
1538
- root.style.setProperty("--consenti-font-size-base", `${Math.round(baseSize * mult)}px`);
1539
- root.style.setProperty("--consenti-font-size-heading", `${Math.round(headingSize * mult)}px`);
1540
- }
1541
- }
1542
- }
1543
- async runPlugins() {
1544
- this.plugins = this.config.plugins ?? [];
1545
- for (const plugin of this.plugins) {
1546
- try {
1547
- await plugin.initialize(this);
1548
- } catch (err) {
1549
- console.warn("[Consenti] Plugin initialize error:", err);
1550
- }
1551
- }
1552
- }
1553
- /**
1554
- * Invokes an optional plugin hook on all registered plugins.
1555
- * Async hooks are awaited; errors are caught and logged so a broken plugin
1556
- * never interferes with the consent flow.
1557
- *
1558
- * @param invoke - Function that calls the desired hook method on a single plugin.
1559
- */
1560
- async callPluginHook(invoke) {
1561
- for (const plugin of this.plugins) {
1562
- try {
1563
- await invoke(plugin, this);
1564
- } catch (err) {
1565
- console.warn("[Consenti] Plugin hook failed:", err);
1566
- }
1567
- }
1568
- }
1569
- buildHandlers(context) {
1570
- return {
1571
- onGrantAll: () => {
1572
- const consent = {};
1573
- for (const cookie of this.profile?.cookies ?? []) {
1574
- consent[cookie.id] = "granted";
1575
- }
1576
- void this.submitConsent(consent);
1577
- if (context === "banner") {
1578
- this.banner?.hide();
1579
- this._bannerVariant = null;
1580
- }
1581
- },
1582
- onDenyAll: () => {
1583
- const consent = {};
1584
- for (const cookie of this.profile?.cookies ?? []) {
1585
- consent[cookie.id] = cookie.mandatory ? "granted" : "denied";
1586
- }
1587
- void this.submitConsent(consent);
1588
- if (context === "banner") {
1589
- this.banner?.hide();
1590
- this._bannerVariant = null;
1591
- }
1592
- },
1593
- onSubmit: () => {
1594
- if (context === "modal" && this.modal && this.profile) {
1595
- const consent = this.modal.getToggleConsent(this.profile.preferenceModal.categories);
1596
- void this.submitConsent(consent);
1597
- this.hideModal();
1598
- } else {
1599
- const consent = {};
1600
- for (const cookie of this.profile?.cookies ?? []) {
1601
- if (cookie.mandatory) consent[cookie.id] = "granted";
1602
- }
1603
- void this.submitConsent(consent);
1604
- if (context === "banner") {
1605
- this.banner?.hide();
1606
- this._bannerVariant = null;
1607
- }
1608
- }
1609
- },
1610
- onManage: () => {
1611
- this.showModal();
1612
- },
1613
- onClose: () => {
1614
- if (context === "modal") {
1615
- this.hideModal();
1616
- } else {
1617
- this.banner?.hide();
1618
- this._bannerVariant = null;
1619
- this.broadcastChannel.send({ type: "bannerClosed" });
1620
- this.eventBus?.dispatch("consenti:bannerVisibility", { show: false, action: true });
1621
- void this.callPluginHook((p) => p.onBannerHide?.());
1622
- }
1623
- },
1624
- onGrantSpecific: (cookieIds) => {
1625
- const consent = {};
1626
- for (const cookie of this.profile?.cookies ?? []) {
1627
- if (cookie.mandatory || cookieIds.includes(cookie.id)) {
1628
- consent[cookie.id] = "granted";
1629
- } else {
1630
- consent[cookie.id] = "denied";
1631
- }
1632
- }
1633
- void this.submitConsent(consent);
1634
- if (context === "banner") this.hideBanner();
1635
- }
1636
- };
1637
- }
1638
- /**
1639
- * Classifies a consent submission as `accept_all`, `reject_all`, `custom`, or `update`.
1640
- * Used to enrich events and API payloads with intent metadata.
1641
- */
1642
- deriveAction(fullConsent, hadExistingConsent) {
1643
- if (hadExistingConsent) return "update";
1644
- const nonMandatory = this.profile?.cookies.filter((c) => !c.mandatory) ?? [];
1645
- if (nonMandatory.length === 0) return "custom";
1646
- const allGranted = nonMandatory.every((c) => fullConsent[c.id] === "granted");
1647
- const allDenied = nonMandatory.every(
1648
- (c) => fullConsent[c.id] === "denied" || fullConsent[c.id] === "objected"
1649
- );
1650
- if (allGranted) return "accept_all";
1651
- if (allDenied) return "reject_all";
1652
- return "custom";
1653
- }
1654
- handleBroadcastMessage(msg) {
1655
- switch (msg.type) {
1656
- case "consentUpdated":
1657
- if (msg.profileId !== this.profileId()) return;
1658
- this.consentStore?.setInMemory(msg.consent);
1659
- this.banner?.hide();
1660
- if (this.modal?.isOpen()) this.modal.close();
1661
- this.eventBus?.dispatch("consenti:consentSubmitted", {
1662
- consent: msg.consent,
1663
- fromBroadcast: true
1664
- });
1665
- break;
1666
- case "bannerClosed":
1667
- this.banner?.hide();
1668
- this._bannerVariant = null;
1669
- break;
1670
- case "consentDeleted":
1671
- this.consentStore?.clearInMemory();
1672
- break;
1673
- }
1674
- }
1675
- // ─── Public API ──────────────────────────────────────────────────────────────
1676
- /**
1677
- * Returns `true` if the visitor has saved consent for the active profile.
1678
- * Always returns `false` during SSR.
1679
- */
1680
- hasConsent() {
1681
- if (!isClient()) return false;
1682
- return this.consentStore?.hasConsent() ?? false;
1683
- }
1684
- /**
1685
- * Returns the stored consent map, or `null` if no consent has been saved.
1686
- * Always returns `null` during SSR.
1687
- */
1688
- getConsent() {
1689
- if (!isClient()) return null;
1690
- return this.consentStore?.getConsent() ?? null;
1691
- }
1692
- /**
1693
- * Returns the `Date` when consent was last saved, or `false` if no consent exists.
1694
- * Always returns `false` during SSR.
1695
- */
1696
- getConsentDate() {
1697
- if (!isClient()) return false;
1698
- return this.consentStore?.getConsentDate() ?? false;
1699
- }
1700
- /**
1701
- * Returns the consent map in Google Consent Mode v2 format, ready to pass to
1702
- * `gtag('consent', 'update', ...)`. Returns `null` if no consent has been saved.
1703
- *
1704
- * Maps Consenti cookie IDs to the seven Google Consent Mode v2 parameters.
1705
- * Cookies whose IDs exactly match a GCM v2 key are forwarded directly.
1706
- * Non-GCM cookies are ignored by this method (they appear in `getConsent()` only).
1707
- */
1708
- getGTMConsent() {
1709
- const consent = this.getConsent();
1710
- if (!consent) return null;
1711
- const gcmKeys = [
1712
- "ad_storage",
1713
- "analytics_storage",
1714
- "ad_user_data",
1715
- "ad_personalization",
1716
- "functionality_storage",
1717
- "personalization_storage",
1718
- "security_storage"
1719
- ];
1720
- const result = {};
1721
- for (const key of gcmKeys) {
1722
- const raw = consent[key];
1723
- if (raw === "granted") {
1724
- result[key] = "granted";
1725
- } else if (raw === "denied" || raw === "objected") {
1726
- result[key] = "denied";
1727
- } else {
1728
- const safeDefault = key === "functionality_storage" || key === "security_storage" ? "granted" : "denied";
1729
- result[key] = safeDefault;
1730
- }
1731
- }
1732
- return result;
1733
- }
1734
- /**
1735
- * Records and persists the user's consent choices.
1736
- *
1737
- * Mandatory cookies are always forced to `'granted'` regardless of what is passed.
1738
- * Non-mandatory cookies not present in `consent` default to `'denied'`.
1739
- *
1740
- * @param consent - Partial or full map of cookie ID → status.
1741
- * @param gpcDerived - Internal flag set to `true` when called from the GPC strict path.
1742
- */
1743
- async submitConsent(consent, gpcDerived = false) {
1744
- if (!isClient() || !this.profile || !this.consentStore) return;
1745
- const regulation = this.config.core.regulation ?? "gdpr";
1746
- const hadExistingConsent = this.consentStore.hasConsent();
1747
- const consentId = crypto.randomUUID();
1748
- const pageUrl = window.location.href;
1749
- const fullConsent = {};
1750
- for (const cookie of this.profile.cookies) {
1751
- if (cookie.mandatory) {
1752
- fullConsent[cookie.id] = "granted";
1753
- } else if (regulation === "cpra") {
1754
- const fallback = cookie.cpraCategory === "sensitive" ? "denied" : "granted";
1755
- fullConsent[cookie.id] = consent[cookie.id] ?? fallback;
1756
- } else {
1757
- fullConsent[cookie.id] = consent[cookie.id] ?? (regulation === "ccpa" ? "granted" : "denied");
1758
- }
1759
- }
1760
- const consentAction = this.deriveAction(fullConsent, hadExistingConsent);
1761
- this.eventBus?.dispatch("consenti:consentBeingSubmitted", { consent: fullConsent, consentId });
1762
- let apiResponse;
1763
- if (this.config.api?.enabled) {
1764
- try {
1765
- const base = this.config.api.baseUrl ?? window.location.origin;
1766
- const { httpRequest: httpRequest2 } = await import("./http-VHOGZ6ZJ.mjs");
1767
- apiResponse = await httpRequest2(
1768
- `${base}/consenti/api/v1/consent`,
1769
- {
1770
- method: "POST",
1771
- body: JSON.stringify({
1772
- profileId: this.profileId(),
1773
- consent: fullConsent,
1774
- locale: this.config.core.locale ?? "en",
1775
- gpcDetected: gpcDerived || detectGPC(),
1776
- consentId,
1777
- action: consentAction,
1778
- pageUrl
1779
- })
1780
- },
1781
- this.config.api.authToken
1782
- );
1783
- } catch (err) {
1784
- console.warn("[Consenti] API consent submission failed:", err);
1785
- }
1786
- }
1787
- const signingKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
1788
- if (this.config.core.signCookies && signingKey) {
1789
- this.lastSignature = await this.consentStore.writeAndSign(fullConsent, signingKey);
1790
- } else {
1791
- this.consentStore.write(fullConsent);
1792
- }
1793
- const receiptRequested = this.modal?.isReceiptRequested() ?? false;
1794
- if (receiptRequested && this.config.core.allowReceipt) {
1795
- const nameParts = this.consentStore.cookieName.split("_");
1796
- const visitorId = nameParts.slice(1, -1).join("_");
1797
- const receiptParams = {
1798
- visitorId,
1799
- profileId: this.profileId(),
1800
- profileVersion: this.profile.version,
1801
- locale: this.config.core.locale ?? "en",
1802
- consent: fullConsent,
1803
- ...this.lastSignature ? { signature: this.lastSignature } : {}
1804
- };
1805
- const receipt = generateReceipt(receiptParams);
1806
- downloadReceipt(receipt);
1807
- }
1808
- this.broadcastChannel.send({
1809
- type: "consentUpdated",
1810
- consent: fullConsent,
1811
- profileId: this.profileId()
1812
- });
1813
- this.eventBus?.dispatch("consenti:consentSubmitted", {
1814
- consent: fullConsent,
1815
- apiResponse,
1816
- gpcDetected: gpcDerived,
1817
- consentId,
1818
- consentAction,
1819
- pageUrl
1820
- });
1821
- await this.callPluginHook((p) => p.onConsentSubmit?.(fullConsent));
1822
- this.renderer?.announceToScreenReader("Your cookie preferences have been saved.");
1823
- this.hideBanner();
1824
- if (this.modal?.isOpen()) this.hideModal();
1825
- }
1826
- /**
1827
- * Returns the currently visible banner variant, or `false` if no banner is shown.
1828
- * Useful for programmatically checking state before calling `showBanner` / `hideBanner`.
1829
- */
1830
- bannerVisibility() {
1831
- return this._bannerVariant ?? false;
1832
- }
1833
- /**
1834
- * Returns `'preference'` when the preference modal is open, or `false` when closed.
1835
- */
1836
- modalVisibility() {
1837
- return this.modal?.isOpen() ? "preference" : false;
1838
- }
1839
- /**
1840
- * Mounts and shows the consent banner.
1841
- *
1842
- * No-ops if the requested variant is already visible. If a different variant is
1843
- * currently shown (e.g. switching from main to GPC), the existing banner is replaced.
1844
- *
1845
- * @param useGpcVariant - When `true` and the profile has a `gpcBanner`, that variant
1846
- * is shown instead of `mainBanner`. Used by the GPC flow.
1847
- */
1848
- showBanner(useGpcVariant = false) {
1849
- if (!isClient() || !this.profile || !this.banner || !this.renderer) return;
1850
- const variant = useGpcVariant && this.profile.gpcBanner ? "gpc" : "main";
1851
- if (this._bannerVariant === variant) return;
1852
- const bannerConfig = variant === "gpc" ? this.profile.gpcBanner : this.profile.mainBanner;
1853
- const root = this.renderer.mount(this.config.rootEl);
1854
- const handlers = this.buildHandlers("banner");
1855
- this.banner.remove();
1856
- const locales = this.profile.locales;
1857
- const activeLocale = this.config.core.locale ?? "en";
1858
- const bannerEl = this.banner.build(
1859
- bannerConfig,
1860
- handlers,
1861
- this.config.core.privacyPolicyUrl,
1862
- locales,
1863
- activeLocale,
1864
- (locale) => this.switchLocale(locale)
1865
- );
1866
- root.appendChild(bannerEl);
1867
- const overlay = this.banner.getOverlay();
1868
- if (overlay) root.appendChild(overlay);
1869
- this._bannerVariant = variant;
1870
- this.eventBus?.dispatch("consenti:bannerVisibility", { show: true, action: false });
1871
- void this.callPluginHook((p) => p.onBannerShow?.());
1872
- }
1873
- /** Hides the banner and notifies other tabs via BroadcastChannel. No-op if already hidden. */
1874
- hideBanner() {
1875
- if (this._bannerVariant === null) return;
1876
- this.banner?.hide();
1877
- this._bannerVariant = null;
1878
- this.broadcastChannel.send({ type: "bannerClosed" });
1879
- this.eventBus?.dispatch("consenti:bannerVisibility", { show: false, action: true });
1880
- this.renderer?.announceToScreenReader("Cookie banner dismissed.");
1881
- void this.callPluginHook((p) => p.onBannerHide?.());
1882
- }
1883
- /**
1884
- * Opens the preference modal and hides the banner while the modal is open.
1885
- * No-ops if the modal is already open.
1886
- * If the modal is closed without saving consent, the banner is re-shown automatically
1887
- * (see `hideModal()`). Only one banner or modal is visible at a time.
1888
- *
1889
- * @param triggerEl - The element that triggered the modal open. Focus returns here on close.
1890
- */
1891
- showModal(triggerEl) {
1892
- if (!isClient() || !this.profile || !this.modal) return;
1893
- if (this.modal.isOpen()) return;
1894
- const handlers = this.buildHandlers("modal");
1895
- const existing = this.consentStore?.getConsent() ?? {};
1896
- const regulation = this.config.core.regulation ?? "gdpr";
1897
- const dpdpaCfg = regulation === "dpdpa" ? this.profile.dpdpa : void 0;
1898
- const locales = this.profile.locales;
1899
- const activeLocale = this.config.core.locale ?? "en";
1900
- this.modal.build(
1901
- this.profile.preferenceModal,
1902
- existing,
1903
- handlers,
1904
- this.config.core.allowReceipt ?? false,
1905
- dpdpaCfg,
1906
- locales,
1907
- activeLocale,
1908
- (locale) => {
1909
- this.hideModal();
1910
- this.switchLocale(locale);
1911
- }
1912
- );
1913
- this.banner?.hide();
1914
- this.modal.open(triggerEl);
1915
- const isDark = this.config.darkMode ?? this.profile?.darkMode ?? false;
1916
- document.getElementById("consenti-modal")?.classList.toggle("consenti-root--dark", isDark);
1917
- this.eventBus?.dispatch("consenti:modalVisibility", { show: true, action: true });
1918
- void this.callPluginHook((p) => p.onModalShow?.());
1919
- }
1920
- /**
1921
- * Closes the preference modal and returns focus to the element that opened it.
1922
- * No-ops if the modal is already closed.
1923
- * If the user has not yet given consent, the banner is re-shown automatically.
1924
- */
1925
- hideModal() {
1926
- if (!this.modal?.isOpen()) return;
1927
- this.modal.close();
1928
- this.eventBus?.dispatch("consenti:modalVisibility", { show: false, action: true });
1929
- void this.callPluginHook((p) => p.onModalHide?.());
1930
- if (!this.consentStore?.hasConsent() && this._bannerVariant !== null) {
1931
- this.banner?.show();
1932
- } else if (!this.consentStore?.hasConsent()) {
1933
- this.showBanner();
1934
- }
1935
- }
1936
- /**
1937
- * Registers a callback to run once the widget has finished initialising.
1938
- * If the widget is already initialised, the callback is invoked synchronously.
1939
- *
1940
- * @param callback - Function to call when the widget is ready.
1941
- */
1942
- onReady(callback) {
1943
- if (this.initialized) {
1944
- callback();
1945
- } else {
1946
- void this.readyPromise.then(callback);
1947
- }
1948
- }
1949
- /**
1950
- * A `Promise` that resolves once the widget has finished initialising.
1951
- * Equivalent to wrapping `onReady()` in a `Promise`.
1952
- *
1953
- * @example
1954
- * ```ts
1955
- * await widget.ready
1956
- * console.log('Widget ready')
1957
- * ```
1958
- */
1959
- get ready() {
1960
- return this.readyPromise;
1961
- }
1962
- /**
1963
- * Deletes the stored consent record and notifies other tabs.
1964
- * The banner will reappear on the next page load (or immediately if the
1965
- * receiving tab calls `showBanner()` in response to `consentDeleted`).
1966
- */
1967
- async deleteConsent() {
1968
- if (!isClient() || !this.consentStore) return;
1969
- if (this.config.api?.enabled) {
1970
- try {
1971
- const base = this.config.api.baseUrl ?? window.location.origin;
1972
- const visitorId = this.consentStore.cookieName;
1973
- const { httpRequest: httpRequest2 } = await import("./http-VHOGZ6ZJ.mjs");
1974
- await httpRequest2(
1975
- `${base}/consenti/api/v1/consent/${visitorId}`,
1976
- { method: "DELETE" },
1977
- this.config.api?.authToken
1978
- );
1979
- } catch (err) {
1980
- console.warn("[Consenti] API consent deletion failed:", err);
1981
- }
1982
- }
1983
- this.consentStore.delete();
1984
- this.broadcastChannel.send({ type: "consentDeleted" });
1985
- }
1986
- /**
1987
- * Removes existing consent and immediately re-shows the consent banner so the user
1988
- * can review and update their preferences.
1989
- *
1990
- * Equivalent to calling `deleteConsent()` followed by `showBanner()`, but in a single
1991
- * method so callers do not need to coordinate the async deletion with the banner display.
1992
- *
1993
- * Use this to power "Review my consent" links in footers or privacy dashboards.
1994
- */
1995
- async reConsent() {
1996
- await this.deleteConsent();
1997
- this.showBanner();
1998
- }
1999
- /**
2000
- * Returns the fully resolved profile for the current session, or `null` during the
2001
- * async init phase (before the profile has been fetched/resolved).
2002
- *
2003
- * Plugins can use this to read cookie definitions, banner config, modal categories,
2004
- * and the profile version without accessing internal state.
2005
- */
2006
- getProfile() {
2007
- return this.profile;
2008
- }
2009
- /**
2010
- * Returns the `#consenti-root` container element that wraps all widget DOM,
2011
- * or `null` before the widget has mounted or after `destroy()`.
2012
- *
2013
- * Plugins can use this to append custom elements, apply extra CSS variables,
2014
- * or observe mutations within the widget container.
2015
- */
2016
- getRootElement() {
2017
- if (!isClient()) return null;
2018
- if (this.config.rootEl) {
2019
- if (typeof this.config.rootEl === "string") {
2020
- return document.querySelector(this.config.rootEl);
2021
- }
2022
- return this.config.rootEl;
2023
- }
2024
- return document.getElementById("consenti-root");
2025
- }
2026
- /**
2027
- * Returns the `#consenti-banner` element when it is present in the DOM, or `null`.
2028
- * The element may be hidden (`display: none`) — check `bannerVisibility()` for visibility state.
2029
- *
2030
- * Useful for adding extra content, custom event listeners, or mutation observers on the banner.
2031
- */
2032
- getBannerElement() {
2033
- if (!isClient()) return null;
2034
- return document.getElementById("consenti-banner");
2035
- }
2036
- /**
2037
- * Returns the `#consenti-modal` element when the modal has been built, or `null`.
2038
- * The element is built on first `showModal()` call and reused on subsequent opens.
2039
- *
2040
- * Useful for injecting extra content into the modal or listening to modal-specific events.
2041
- */
2042
- getModalElement() {
2043
- if (!isClient()) return null;
2044
- return document.getElementById("consenti-modal");
2045
- }
2046
- /**
2047
- * Switches the active locale, re-resolves the profile, and re-renders the widget.
2048
- * Called by the locale switcher UI when the user selects a different language.
2049
- *
2050
- * @param locale - BCP 47 locale code to switch to (e.g. `'fr'`, `'de-AT'`).
2051
- */
2052
- switchLocale(locale) {
2053
- if (!isClient()) return;
2054
- this.config.core.locale = locale;
2055
- this.banner?.remove();
2056
- if (this.modal?.isOpen()) this.modal.close();
2057
- this._bannerVariant = null;
2058
- this.profile = null;
2059
- this.initialized = false;
2060
- this.readyPromise = new Promise((resolve) => {
2061
- this.readyResolve = resolve;
2062
- });
2063
- this.init().catch((err) => console.warn("[Consenti] Locale switch error:", err));
2064
- }
2065
- /**
2066
- * Destroys the widget — removes all DOM elements, disconnects the BroadcastChannel,
2067
- * and calls `destroy()` on all registered plugins.
2068
- *
2069
- * After `destroy()`, the instance should not be used again.
2070
- */
2071
- destroy() {
2072
- for (const plugin of this.plugins) {
2073
- try {
2074
- plugin.destroy();
2075
- } catch {
2076
- }
2077
- }
2078
- this.modal?.destroy();
2079
- this.renderer?.unmount();
2080
- this.broadcastChannel.disconnect();
2081
- this.banner = null;
2082
- this.modal = null;
2083
- this.renderer = null;
2084
- this.consentStore = null;
2085
- this.eventBus = null;
2086
- this.initialized = false;
2087
- }
2088
- };
2089
-
2090
- // src/plugins/plugin.base.ts
2091
- var ConsentiPlugin = class {
2092
- };
2093
-
2094
- // src/profile/consenti-profile.ts
2095
- var nextLocalId = 1e3;
2096
- var ConsentiProfile = class {
2097
- /**
2098
- * @param config - Full profile configuration including cookies, locales, and UI text.
2099
- */
2100
- constructor(config) {
2101
- this.config = config;
2102
- this.id = nextLocalId++;
2103
- registerProfile(this.id, config);
2104
- }
2105
- /** Returns the auto-assigned numeric profile ID. Pass this to `core.profileId`. */
2106
- getId() {
2107
- return this.id;
2108
- }
2109
- /** Returns the raw `ProfileConfig` passed to the constructor. */
2110
- toJSON() {
2111
- return this.config;
2112
- }
2113
- };
2114
-
2115
- // src/utils/consent-script.ts
2116
- var ConsentScript = class {
2117
- constructor(options) {
2118
- this.options = options;
2119
- this.scriptEl = null;
2120
- this.handler = () => this.evaluate();
2121
- if (!isClient()) return;
2122
- window.addEventListener("consenti:consentSubmitted", this.handler);
2123
- this.evaluate();
2124
- }
2125
- evaluate() {
2126
- const consent = this.options.widget.getConsent();
2127
- const granted = consent?.[this.options.cookieId] === "granted";
2128
- if (granted && !this.scriptEl) {
2129
- this.inject();
2130
- } else if (!granted && this.scriptEl) {
2131
- this.revoke();
2132
- }
2133
- }
2134
- inject() {
2135
- const script = document.createElement("script");
2136
- if (this.options.src) script.src = this.options.src;
2137
- if (this.options.innerHTML) script.innerHTML = this.options.innerHTML;
2138
- for (const [key, value] of Object.entries(this.options.attributes ?? {})) {
2139
- script.setAttribute(key, value);
2140
- }
2141
- document.head.appendChild(script);
2142
- this.scriptEl = script;
2143
- this.options.onLoad?.();
2144
- }
2145
- revoke() {
2146
- this.scriptEl?.remove();
2147
- this.scriptEl = null;
2148
- this.options.onRevoke?.();
2149
- }
2150
- /**
2151
- * Removes the event listener and the injected `<script>` element (if present).
2152
- * Call this when the widget is destroyed or the script is no longer needed.
2153
- */
2154
- destroy() {
2155
- if (!isClient()) return;
2156
- window.removeEventListener("consenti:consentSubmitted", this.handler);
2157
- this.revoke();
2158
- }
2159
- };
2160
-
2161
- // src/utils/cookie-trigger.ts
2162
- var CookieTrigger = class {
2163
- constructor(options) {
2164
- const action = options.action ?? "modal";
2165
- this.clickHandler = () => {
2166
- if (action === "modal") options.widget.showModal();
2167
- else options.widget.showBanner();
2168
- };
2169
- if (!options.el) {
2170
- const btn = document.createElement("button");
2171
- btn.type = "button";
2172
- btn.className = "consenti-btn consenti-btn--text consenti-cookie-trigger";
2173
- btn.textContent = options.label ?? "Cookie Settings";
2174
- btn.setAttribute("aria-label", options.label ?? "Open cookie settings");
2175
- this.el = btn;
2176
- this.created = true;
2177
- } else if (typeof options.el === "string") {
2178
- if (!isClient()) {
2179
- this.el = document.createElement("span");
2180
- this.created = false;
2181
- return;
2182
- }
2183
- const found = document.querySelector(options.el);
2184
- if (!found) {
2185
- throw new Error(
2186
- `[Consenti] CookieTrigger: element not found for selector "${options.el}"`
2187
- );
2188
- }
2189
- this.el = found;
2190
- this.created = false;
2191
- } else {
2192
- this.el = options.el;
2193
- this.created = false;
2194
- }
2195
- this.el.addEventListener("click", this.clickHandler);
2196
- }
2197
- /**
2198
- * Returns the trigger element.
2199
- * When no `el` was provided, append the returned element to the desired location
2200
- * in the DOM after construction.
2201
- */
2202
- getElement() {
2203
- return this.el;
2204
- }
2205
- /**
2206
- * Removes the click handler and, if this instance auto-created the button,
2207
- * removes it from the DOM.
2208
- */
2209
- destroy() {
2210
- this.el.removeEventListener("click", this.clickHandler);
2211
- if (this.created) this.el.remove();
2212
- }
2213
- };
2214
- export {
2215
- ConsentScript,
2216
- ConsentiPlugin,
2217
- ConsentiProfile,
2218
- ConsentiSetup,
2219
- CookieTrigger
2220
- };
1
+ import"./chunk-J4BKXUD6.mjs";import{a as le}from"./chunk-3EXCD24H.mjs";function N(t){let e=new Map;for(let n of Object.values(t))for(let o of n.cookies)e.set(o,n);return e}function pe(t,e){return e.get(t)?.legalBasis??"consent"}function S(t,e){return pe(t,e)==="mandatory"}var z={version:"2026-07",countries:{AT:{name:"Austria",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Austria (GDPR)",locales:["de-AT"],compliance:["gdpr","eprivacy"],timezones:["Europe/Vienna"]},BE:{name:"Belgium",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Belgium (GDPR)",locales:["nl-BE","fr-BE"],compliance:["gdpr","eprivacy"],timezones:["Europe/Brussels"]},BG:{name:"Bulgaria",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Bulgaria (GDPR)",locales:["bg-BG"],compliance:["gdpr","eprivacy"],timezones:["Europe/Sofia"]},HR:{name:"Croatia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Croatia (GDPR)",locales:["hr-HR"],compliance:["gdpr","eprivacy"],timezones:["Europe/Zagreb"]},CY:{name:"Cyprus",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Cyprus (GDPR)",locales:["el-CY"],compliance:["gdpr","eprivacy"],timezones:["Asia/Nicosia"]},CZ:{name:"Czechia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Czechia (GDPR)",locales:["cs-CZ"],compliance:["gdpr","eprivacy"],timezones:["Europe/Prague"]},DK:{name:"Denmark",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Denmark (GDPR)",locales:["da-DK"],compliance:["gdpr","eprivacy"],timezones:["Europe/Copenhagen"]},EE:{name:"Estonia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Estonia (GDPR)",locales:["et-EE"],compliance:["gdpr","eprivacy"],timezones:["Europe/Tallinn"]},FI:{name:"Finland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Finland (GDPR)",locales:["fi-FI"],compliance:["gdpr","eprivacy"],timezones:["Europe/Helsinki"]},FR:{name:"France",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"France (GDPR)",locales:["fr-FR"],compliance:["gdpr","eprivacy"],timezones:["Europe/Paris"]},DE:{name:"Germany",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Germany (GDPR)",locales:["de-DE"],compliance:["gdpr","eprivacy"],timezones:["Europe/Berlin"]},GR:{name:"Greece",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Greece (GDPR)",locales:["el-GR"],compliance:["gdpr","eprivacy"],timezones:["Europe/Athens"]},HU:{name:"Hungary",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Hungary (GDPR)",locales:["hu-HU"],compliance:["gdpr","eprivacy"],timezones:["Europe/Budapest"]},IE:{name:"Ireland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Ireland (GDPR)",locales:["en"],compliance:["gdpr","eprivacy"],timezones:["Europe/Dublin"]},IT:{name:"Italy",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Italy (GDPR)",locales:["it-IT"],compliance:["gdpr","eprivacy"],timezones:["Europe/Rome"]},LV:{name:"Latvia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Latvia (GDPR)",locales:["lv-LV"],compliance:["gdpr","eprivacy"],timezones:["Europe/Riga"]},LT:{name:"Lithuania",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Lithuania (GDPR)",locales:["lt-LT"],compliance:["gdpr","eprivacy"],timezones:["Europe/Vilnius"]},LU:{name:"Luxembourg",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Luxembourg (GDPR)",locales:["fr-LU","de-LU"],compliance:["gdpr","eprivacy"],timezones:["Europe/Luxembourg"]},MT:{name:"Malta",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Malta (GDPR)",locales:["mt-MT","en"],compliance:["gdpr","eprivacy"],timezones:["Europe/Malta"]},NL:{name:"Netherlands",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Netherlands (GDPR)",locales:["nl-NL"],compliance:["gdpr","eprivacy"],timezones:["Europe/Amsterdam"]},PL:{name:"Poland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Poland (GDPR)",locales:["pl-PL"],compliance:["gdpr","eprivacy"],timezones:["Europe/Warsaw"]},PT:{name:"Portugal",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Portugal (GDPR)",locales:["pt-PT"],compliance:["gdpr","eprivacy"],timezones:["Europe/Lisbon","Atlantic/Madeira","Atlantic/Azores"]},RO:{name:"Romania",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Romania (GDPR)",locales:["ro-RO"],compliance:["gdpr","eprivacy"],timezones:["Europe/Bucharest"]},SK:{name:"Slovakia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Slovakia (GDPR)",locales:["sk-SK"],compliance:["gdpr","eprivacy"],timezones:["Europe/Bratislava"]},SI:{name:"Slovenia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Slovenia (GDPR)",locales:["sl-SI"],compliance:["gdpr","eprivacy"],timezones:["Europe/Ljubljana"]},ES:{name:"Spain",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Spain (GDPR)",locales:["es-ES"],compliance:["gdpr","eprivacy"],timezones:["Europe/Madrid","Africa/Ceuta","Atlantic/Canary"]},SE:{name:"Sweden",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Sweden (GDPR)",locales:["sv-SE"],compliance:["gdpr","eprivacy"],timezones:["Europe/Stockholm"]},IS:{name:"Iceland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Iceland (GDPR via EEA)",locales:["is-IS"],compliance:["gdpr","eprivacy"],timezones:["Atlantic/Reykjavik"]},LI:{name:"Liechtenstein",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Liechtenstein (GDPR via EEA)",locales:["de-LI"],compliance:["gdpr","eprivacy"],timezones:["Europe/Vaduz"]},NO:{name:"Norway",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Norway (GDPR via EEA)",locales:["nb-NO"],compliance:["gdpr","eprivacy"],timezones:["Europe/Oslo","Arctic/Longyearbyen"]},AD:{name:"Andorra",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Andorra (GDPR-aligned)",locales:["ca-AD"],compliance:["gdpr"],timezones:["Europe/Andorra"]},AL:{name:"Albania",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Albania (GDPR-aligned)",locales:["sq-AL"],compliance:["gdpr"],timezones:["Europe/Tirane"]},BA:{name:"Bosnia and Herzegovina",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Bosnia and Herzegovina (GDPR-aligned)",locales:["bs-BA","sr-Latn-BA"],compliance:["gdpr"],timezones:["Europe/Sarajevo"]},GG:{name:"Guernsey",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Guernsey (DPA based on GDPR)",locales:["en"],compliance:["gdpr"],timezones:["Europe/Guernsey"]},GI:{name:"Gibraltar",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Gibraltar (DPA based on UK GDPR)",locales:["en"],compliance:["uk-gdpr"],timezones:["Europe/Gibraltar"]},GL:{name:"Greenland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Greenland (Danish GDPR implementation)",locales:["da-GL"],compliance:["gdpr"],timezones:["America/Nuuk","America/Danmarkshavn","America/Scoresbysund","America/Thule"]},MC:{name:"Monaco",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Monaco (GDPR-aligned)",locales:["fr-MC"],compliance:["gdpr"],timezones:["Europe/Monaco"]},ME:{name:"Montenegro",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Montenegro (GDPR-aligned)",locales:["sr-Latn-ME"],compliance:["gdpr"],timezones:["Europe/Podgorica"]},MK:{name:"North Macedonia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"North Macedonia (GDPR-aligned)",locales:["mk-MK"],compliance:["gdpr"],timezones:["Europe/Skopje"]},RS:{name:"Serbia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Serbia (GDPR-aligned)",locales:["sr-RS"],compliance:["gdpr"],timezones:["Europe/Belgrade"]},SM:{name:"San Marino",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"San Marino (GDPR-aligned)",locales:["it-SM"],compliance:["gdpr"],timezones:["Europe/San_Marino"]},TR:{name:"T\xFCrkiye",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"T\xFCrkiye (KVKK)",locales:["tr-TR"],compliance:["kvkk"],timezones:["Europe/Istanbul"]},CA:{name:"Canada",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Canada (PIPEDA / Law 25)",locales:["en","fr-CA"],compliance:["pipeda","law25"],timezones:["America/St_Johns","America/Halifax","America/Toronto","America/Winnipeg","America/Edmonton","America/Vancouver"],overriddenRegions:{QC:{name:"Quebec",type:"region",complianceGroup:"opt-in",description:"Quebec \u2014 Law 25 (GDPR-style opt-in)"}}},US:{name:"United States",type:"country",complianceGroup:"opt-out",default:"opt-out-strict",description:"United States \u2014 opt-out-strict (CPRA) used when state cannot be detected",locales:["en","es-US"],compliance:["ccpa","cpra","vcdpa","cpa-co","ctdpa","ucpa","icdpa-ia","icdpa-in","tipa","mcdpa-mt","mcdpa-mn","ocpa","tdpsa","dpdpa-de","njdpa","nhpa","ndpa-ne","modpa","kcdpa","ridtpa"],timezones:["America/New_York","America/Chicago","America/Denver","America/Phoenix","America/Los_Angeles","America/Anchorage","Pacific/Honolulu"],overriddenRegions:{CA:{name:"California",type:"region",complianceGroup:"opt-out-strict",description:"California \u2014 CPRA/CCPA"},VA:{name:"Virginia",type:"region",complianceGroup:"opt-out",description:"Virginia \u2014 VCDPA"},CO:{name:"Colorado",type:"region",complianceGroup:"opt-out",description:"Colorado \u2014 CPA"},CT:{name:"Connecticut",type:"region",complianceGroup:"opt-out",description:"Connecticut \u2014 CTDPA"},UT:{name:"Utah",type:"region",complianceGroup:"opt-out",description:"Utah \u2014 UCPA"},TX:{name:"Texas",type:"region",complianceGroup:"opt-out",description:"Texas \u2014 TDPSA"},DE:{name:"Delaware",type:"region",complianceGroup:"opt-out",description:"Delaware \u2014 DPDPA"},NJ:{name:"New Jersey",type:"region",complianceGroup:"opt-out",description:"New Jersey \u2014 NJDPA"},OR:{name:"Oregon",type:"region",complianceGroup:"opt-out",description:"Oregon \u2014 OCPA"},MN:{name:"Minnesota",type:"region",complianceGroup:"opt-out",description:"Minnesota \u2014 MCDPA"},MD:{name:"Maryland",type:"region",complianceGroup:"opt-out",description:"Maryland \u2014 MODPA"},KY:{name:"Kentucky",type:"region",complianceGroup:"opt-out",description:"Kentucky \u2014 KCDPA"},TN:{name:"Tennessee",type:"region",complianceGroup:"opt-out",description:"Tennessee \u2014 TIPA"},NE:{name:"Nebraska",type:"region",complianceGroup:"opt-out",description:"Nebraska \u2014 NDPA"},IA:{name:"Iowa",type:"region",complianceGroup:"opt-out",description:"Iowa \u2014 ICDPA"},IN:{name:"Indiana",type:"region",complianceGroup:"opt-out",description:"Indiana \u2014 ICDPA"},MT:{name:"Montana",type:"region",complianceGroup:"opt-out",description:"Montana \u2014 MCDPA"},NH:{name:"New Hampshire",type:"region",complianceGroup:"opt-out",description:"New Hampshire \u2014 NHPA"},RI:{name:"Rhode Island",type:"region",complianceGroup:"opt-out",description:"Rhode Island \u2014 RIDTPA"}}},AR:{name:"Argentina",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Argentina (LPDP)",locales:["es-AR"],compliance:["lpdp-ar"],timezones:["America/Argentina/Buenos_Aires"]},BO:{name:"Bolivia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Bolivia",locales:["es-BO"],compliance:[],timezones:["America/La_Paz"]},BR:{name:"Brazil",type:"country",complianceGroup:"opt-in-brazil",default:"opt-in-brazil",description:"Brazil (LGPD)",locales:["pt-BR"],compliance:["lgpd"],timezones:["America/Noronha","America/Sao_Paulo","America/Manaus","America/Rio_Branco"]},CL:{name:"Chile",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Chile (Law 19.628)",locales:["es-CL"],compliance:["law19628-cl"],timezones:["America/Santiago","Pacific/Easter"]},CO:{name:"Colombia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Colombia (Law 1581)",locales:["es-CO"],compliance:["law1581-co"],timezones:["America/Bogota"]},CR:{name:"Costa Rica",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Costa Rica (LPDP)",locales:["es-CR"],compliance:["lpdp-cr"],timezones:["America/Costa_Rica"]},DO:{name:"Dominican Republic",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Dominican Republic",locales:["es-DO"],compliance:["data-protection-do"],timezones:["America/Santo_Domingo"]},EC:{name:"Ecuador",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Ecuador (LOPDP)",locales:["es-EC"],compliance:["lopdp-ec"],timezones:["America/Guayaquil","Pacific/Galapagos"]},GF:{name:"French Guiana",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"French Guiana",locales:["fr-GF"],compliance:[],timezones:["America/Cayenne"]},GT:{name:"Guatemala",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Guatemala",locales:["es-GT"],compliance:[],timezones:["America/Guatemala"]},GY:{name:"Guyana",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Guyana",locales:["en"],compliance:[],timezones:["America/Guyana"]},HN:{name:"Honduras",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Honduras",locales:["es-HN"],compliance:[],timezones:["America/Tegucigalpa"]},HT:{name:"Haiti",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Haiti",locales:["fr-HT"],compliance:[],timezones:["America/Port-au-Prince"]},JM:{name:"Jamaica",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Jamaica",locales:["en"],compliance:[],timezones:["America/Jamaica"]},MX:{name:"Mexico",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Mexico (LFPDPPP)",locales:["es-MX"],compliance:["lfpdppp"],timezones:["America/Mexico_City","America/Cancun","America/Chihuahua","America/Hermosillo","America/Tijuana"]},NI:{name:"Nicaragua",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Nicaragua",locales:["es-NI"],compliance:[],timezones:["America/Managua"]},PA:{name:"Panama",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Panama",locales:["es-PA"],compliance:["data-protection-pa"],timezones:["America/Panama"]},PE:{name:"Peru",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Peru (LPDP)",locales:["es-PE"],compliance:["lpdp-pe"],timezones:["America/Lima"]},PY:{name:"Paraguay",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Paraguay",locales:["es-PY"],compliance:[],timezones:["America/Asuncion"]},SR:{name:"Suriname",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Suriname",locales:["nl-SR"],compliance:[],timezones:["America/Paramaribo"]},SV:{name:"El Salvador",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"El Salvador",locales:["es-SV"],compliance:[],timezones:["America/El_Salvador"]},TT:{name:"Trinidad and Tobago",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Trinidad and Tobago",locales:["en"],compliance:[],timezones:["America/Port_of_Spain"]},UY:{name:"Uruguay",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Uruguay (Law 18.331)",locales:["es-UY"],compliance:["law18331-uy"],timezones:["America/Montevideo"]},VE:{name:"Venezuela",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Venezuela",locales:["es-VE"],compliance:[],timezones:["America/Caracas"]},AG:{name:"Antigua and Barbuda",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Antigua and Barbuda",locales:["en"],compliance:[],timezones:["America/Antigua"]},BB:{name:"Barbados",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Barbados",locales:["en"],compliance:[],timezones:["America/Barbados"]},BL:{name:"Saint Barth\xE9lemy",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Barth\xE9lemy",locales:["fr-BL"],compliance:[],timezones:["America/St_Barthelemy"]},BM:{name:"Bermuda",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Bermuda",locales:["en"],compliance:[],timezones:["Atlantic/Bermuda"]},BS:{name:"Bahamas",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Bahamas",locales:["en"],compliance:[],timezones:["America/Nassau"]},CU:{name:"Cuba",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Cuba",locales:["es-CU"],compliance:[],timezones:["America/Havana"]},DM:{name:"Dominica",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Dominica",locales:["en"],compliance:[],timezones:["America/Dominica"]},GD:{name:"Grenada",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Grenada",locales:["en"],compliance:[],timezones:["America/Grenada"]},KN:{name:"Saint Kitts and Nevis",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Kitts and Nevis",locales:["en"],compliance:[],timezones:["America/St_Kitts"]},LC:{name:"Saint Lucia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Lucia",locales:["en"],compliance:[],timezones:["America/St_Lucia"]},PM:{name:"Saint Pierre and Miquelon",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Pierre and Miquelon",locales:["fr-PM"],compliance:[],timezones:["America/Miquelon"]},PR:{name:"Puerto Rico",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Puerto Rico",locales:["es-PR","en"],compliance:[],timezones:["America/Puerto_Rico"]},VC:{name:"Saint Vincent and the Grenadines",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Vincent and the Grenadines",locales:["en"],compliance:[],timezones:["America/St_Vincent"]},IN:{name:"India",type:"country",complianceGroup:"opt-in-dpdpa",default:"opt-in-dpdpa",description:"India (DPDPA)",locales:["hi-IN","en","bn-IN","ta-IN"],compliance:["dpdpa"],timezones:["Asia/Kolkata","Asia/Calcutta"]},CN:{name:"China",type:"country",complianceGroup:"opt-in-china",default:"opt-in-china",description:"China (PIPL + DSL + CSL)",locales:["zh-CN"],compliance:["pipl","dsl-cn","csl-cn"],timezones:["Asia/Shanghai"]},BN:{name:"Brunei",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Brunei (Personal Data Protection Order)",locales:["ms-BN","en"],compliance:["privacy-bn"],timezones:["Asia/Brunei"]},ID:{name:"Indonesia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Indonesia (PDP Law)",locales:["id-ID"],compliance:["pdp-id"],timezones:["Asia/Jakarta","Asia/Makassar","Asia/Jayapura"]},KH:{name:"Cambodia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Cambodia",locales:["km-KH"],compliance:[],timezones:["Asia/Phnom_Penh"]},LA:{name:"Laos",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Laos",locales:["lo-LA"],compliance:[],timezones:["Asia/Vientiane"]},MM:{name:"Myanmar",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Myanmar",locales:["my-MM"],compliance:[],timezones:["Asia/Yangon"]},MY:{name:"Malaysia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Malaysia (PDPA)",locales:["ms-MY","zh-MY","en"],compliance:["pdpa-my"],timezones:["Asia/Kuala_Lumpur","Asia/Kuching"]},PH:{name:"Philippines",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Philippines (DPA)",locales:["fil-PH","en"],compliance:["dpa-ph"],timezones:["Asia/Manila"]},SG:{name:"Singapore",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Singapore (PDPA)",locales:["zh-SG","ms-SG","ta-SG","en"],compliance:["pdpa-sg"],timezones:["Asia/Singapore"]},TH:{name:"Thailand",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Thailand (PDPA \u2014 GDPR-style opt-in)",locales:["th-TH"],compliance:["pdpa-th"],timezones:["Asia/Bangkok"]},TL:{name:"Timor-Leste",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Timor-Leste",locales:["pt-TL"],compliance:[],timezones:["Asia/Dili"]},VN:{name:"Vietnam",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Vietnam (PDPD)",locales:["vi-VN"],compliance:["pdpd-vn"],timezones:["Asia/Ho_Chi_Minh"]},HK:{name:"Hong Kong",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Hong Kong SAR (PDPO)",locales:["zh-HK"],compliance:["pdpl-hk"],timezones:["Asia/Hong_Kong"]},JP:{name:"Japan",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Japan (APPI)",locales:["ja-JP"],compliance:["appi"],timezones:["Asia/Tokyo"]},KP:{name:"North Korea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"North Korea",locales:["ko-KP"],compliance:[],timezones:["Asia/Pyongyang"]},KR:{name:"South Korea",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"South Korea (PIPA)",locales:["ko-KR"],compliance:["pipa-kr"],timezones:["Asia/Seoul"]},MN:{name:"Mongolia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Mongolia",locales:["mn-MN"],compliance:[],timezones:["Asia/Ulaanbaatar","Asia/Hovd"]},MO:{name:"Macau",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Macau SAR (PDPA)",locales:["zh-MO","pt-MO"],compliance:["pdpa-mo"],timezones:["Asia/Macau"]},TW:{name:"Taiwan",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Taiwan (PDPL)",locales:["zh-TW"],compliance:["pdpl-tw"],timezones:["Asia/Taipei"]},AF:{name:"Afghanistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Afghanistan",locales:["fa-AF"],compliance:[],timezones:["Asia/Kabul"]},BD:{name:"Bangladesh",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Bangladesh",locales:["bn-BD"],compliance:[],timezones:["Asia/Dhaka"]},BT:{name:"Bhutan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Bhutan",locales:["dz-BT"],compliance:[],timezones:["Asia/Thimphu"]},IO:{name:"British Indian Ocean Territory",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"British Indian Ocean Territory",locales:["en"],compliance:[],timezones:["Indian/Chagos"]},LK:{name:"Sri Lanka",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Sri Lanka",locales:["si-LK","ta-LK"],compliance:["privacy-lk"],timezones:["Asia/Colombo"]},MV:{name:"Maldives",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Maldives",locales:["dv-MV"],compliance:[],timezones:["Indian/Maldives"]},NP:{name:"Nepal",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Nepal",locales:["ne-NP"],compliance:["privacy-np"],timezones:["Asia/Kathmandu"]},PK:{name:"Pakistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Pakistan",locales:["ur-PK","en"],compliance:[],timezones:["Asia/Karachi"]},AM:{name:"Armenia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Armenia",locales:["hy-AM"],compliance:[],timezones:["Asia/Yerevan"]},AZ:{name:"Azerbaijan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Azerbaijan",locales:["az-AZ"],compliance:[],timezones:["Asia/Baku"]},GE:{name:"Georgia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Georgia",locales:["ka-GE"],compliance:[],timezones:["Asia/Tbilisi"]},KG:{name:"Kyrgyzstan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Kyrgyzstan",locales:["ky-KG","ru-KG"],compliance:[],timezones:["Asia/Bishkek"]},KZ:{name:"Kazakhstan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Kazakhstan",locales:["kk-KZ","ru-KZ"],compliance:[],timezones:["Asia/Almaty","Asia/Aqtobe"]},TJ:{name:"Tajikistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Tajikistan",locales:["tg-TJ"],compliance:[],timezones:["Asia/Dushanbe"]},TM:{name:"Turkmenistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Turkmenistan",locales:["tk-TM"],compliance:[],timezones:["Asia/Ashgabat"]},UZ:{name:"Uzbekistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Uzbekistan",locales:["uz-UZ"],compliance:[],timezones:["Asia/Tashkent","Asia/Samarkand"]},AE:{name:"United Arab Emirates",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"United Arab Emirates (UAE PDPL / DIFC DPL / ADGM DPR)",locales:["ar-AE"],compliance:["uae-pdpl","difc-dpl","adgm-dpr"],timezones:["Asia/Dubai"]},BH:{name:"Bahrain",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Bahrain (PDPL)",locales:["ar-BH"],compliance:["pdpl-bh"],timezones:["Asia/Bahrain"]},IL:{name:"Israel",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Israel (Privacy Protection Law)",locales:["he-IL","ar-IL"],compliance:["privacy-il"],timezones:["Asia/Jerusalem"]},IQ:{name:"Iraq",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Iraq",locales:["ar-IQ"],compliance:[],timezones:["Asia/Baghdad"]},IR:{name:"Iran",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Iran",locales:["fa-IR"],compliance:[],timezones:["Asia/Tehran"]},JO:{name:"Jordan",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Jordan",locales:["ar-JO"],compliance:[],timezones:["Asia/Amman"]},KW:{name:"Kuwait",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Kuwait",locales:["ar-KW"],compliance:["privacy-kw"],timezones:["Asia/Kuwait"]},LB:{name:"Lebanon",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Lebanon",locales:["ar-LB"],compliance:[],timezones:["Asia/Beirut"]},OM:{name:"Oman",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Oman (PDPL)",locales:["ar-OM"],compliance:["pdpl-om"],timezones:["Asia/Muscat"]},PS:{name:"Palestine",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Palestine",locales:["ar-PS"],compliance:[],timezones:["Asia/Gaza","Asia/Hebron"]},QA:{name:"Qatar",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Qatar (PDPL)",locales:["ar-QA"],compliance:["qpdl-qa"],timezones:["Asia/Qatar"]},SA:{name:"Saudi Arabia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Saudi Arabia (PDPL)",locales:["ar-SA"],compliance:["pdpl-sa"],timezones:["Asia/Riyadh"]},SY:{name:"Syria",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Syria",locales:["ar-SY"],compliance:[],timezones:["Asia/Damascus"]},YE:{name:"Yemen",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Yemen",locales:["ar-YE"],compliance:[],timezones:["Asia/Aden"]},BF:{name:"Burkina Faso",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Burkina Faso",locales:["fr-BF"],compliance:[],timezones:["Africa/Ouagadougou"]},BJ:{name:"Benin",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Benin",locales:["fr-BJ"],compliance:[],timezones:["Africa/Porto-Novo"]},CI:{name:"C\xF4te d'Ivoire",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"C\xF4te d'Ivoire",locales:["fr-CI"],compliance:[],timezones:["Africa/Abidjan"]},CM:{name:"Cameroon",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Cameroon",locales:["fr-CM","en"],compliance:[],timezones:["Africa/Douala"]},CV:{name:"Cabo Verde",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Cabo Verde",locales:["pt-CV"],compliance:[],timezones:["Atlantic/Cape_Verde"]},GA:{name:"Gabon",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Gabon",locales:["fr-GA"],compliance:[],timezones:["Africa/Libreville"]},GH:{name:"Ghana",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Ghana (DPA)",locales:["en"],compliance:["dpa-gh"],timezones:["Africa/Accra"]},GM:{name:"Gambia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Gambia",locales:["en"],compliance:[],timezones:["Africa/Banjul"]},GN:{name:"Guinea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Guinea",locales:["fr-GN"],compliance:[],timezones:["Africa/Conakry"]},GQ:{name:"Equatorial Guinea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Equatorial Guinea",locales:["es-GQ","fr-GQ"],compliance:[],timezones:["Africa/Malabo"]},GW:{name:"Guinea-Bissau",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Guinea-Bissau",locales:["pt-GW"],compliance:[],timezones:["Africa/Bissau"]},LR:{name:"Liberia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Liberia",locales:["en"],compliance:[],timezones:["Africa/Monrovia"]},ML:{name:"Mali",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Mali",locales:["fr-ML"],compliance:[],timezones:["Africa/Bamako"]},MR:{name:"Mauritania",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Mauritania",locales:["ar-MR"],compliance:[],timezones:["Africa/Nouakchott"]},NE:{name:"Niger",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Niger",locales:["fr-NE"],compliance:[],timezones:["Africa/Niamey"]},NG:{name:"Nigeria",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Nigeria (NDPA)",locales:["en"],compliance:["ndpa-ng"],timezones:["Africa/Lagos"]},SL:{name:"Sierra Leone",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Sierra Leone",locales:["en"],compliance:[],timezones:["Africa/Freetown"]},SN:{name:"Senegal",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Senegal",locales:["fr-SN"],compliance:[],timezones:["Africa/Dakar"]},TG:{name:"Togo",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Togo",locales:["fr-TG"],compliance:[],timezones:["Africa/Lome"]},BI:{name:"Burundi",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Burundi",locales:["fr-BI"],compliance:[],timezones:["Africa/Bujumbura"]},DJ:{name:"Djibouti",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Djibouti",locales:["fr-DJ","ar-DJ"],compliance:[],timezones:["Africa/Djibouti"]},ER:{name:"Eritrea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Eritrea",locales:["ti-ER"],compliance:[],timezones:["Africa/Asmara"]},ET:{name:"Ethiopia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Ethiopia",locales:["am-ET"],compliance:[],timezones:["Africa/Addis_Ababa"]},KE:{name:"Kenya",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Kenya (DPA)",locales:["sw-KE","en"],compliance:["dpa-ke"],timezones:["Africa/Nairobi"]},KM:{name:"Comoros",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Comoros",locales:["ar-KM","fr-KM"],compliance:[],timezones:["Indian/Comoro"]},MG:{name:"Madagascar",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Madagascar",locales:["fr-MG"],compliance:[],timezones:["Indian/Antananarivo"]},MU:{name:"Mauritius",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Mauritius (DPA)",locales:["en","fr-MU"],compliance:["dpa-mu"],timezones:["Indian/Mauritius"]},MW:{name:"Malawi",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Malawi",locales:["en"],compliance:[],timezones:["Africa/Blantyre"]},MZ:{name:"Mozambique",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Mozambique",locales:["pt-MZ"],compliance:[],timezones:["Africa/Maputo"]},RW:{name:"Rwanda",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Rwanda (DPA)",locales:["rw-RW","en"],compliance:["dpa-rw"],timezones:["Africa/Kigali"]},SC:{name:"Seychelles",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Seychelles",locales:["fr-SC","en"],compliance:[],timezones:["Indian/Mahe"]},SO:{name:"Somalia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Somalia",locales:["so-SO"],compliance:[],timezones:["Africa/Mogadishu"]},SS:{name:"South Sudan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"South Sudan",locales:["en"],compliance:[],timezones:["Africa/Juba"]},ST:{name:"S\xE3o Tom\xE9 and Pr\xEDncipe",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"S\xE3o Tom\xE9 and Pr\xEDncipe",locales:["pt-ST"],compliance:[],timezones:["Africa/Sao_Tome"]},TZ:{name:"Tanzania",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Tanzania",locales:["sw-TZ","en"],compliance:[],timezones:["Africa/Dar_es_Salaam"]},UG:{name:"Uganda",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Uganda (DPA)",locales:["en"],compliance:["dpa-ug"],timezones:["Africa/Kampala"]},AO:{name:"Angola",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Angola",locales:["pt-AO"],compliance:[],timezones:["Africa/Luanda"]},BW:{name:"Botswana",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Botswana (DPA)",locales:["en"],compliance:["dpa-bw"],timezones:["Africa/Gaborone"]},LS:{name:"Lesotho",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Lesotho",locales:["en"],compliance:[],timezones:["Africa/Maseru"]},NA:{name:"Namibia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Namibia",locales:["en"],compliance:[],timezones:["Africa/Windhoek"]},SZ:{name:"Eswatini",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Eswatini",locales:["en"],compliance:[],timezones:["Africa/Mbabane"]},ZA:{name:"South Africa",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"South Africa (POPIA)",locales:["en","af-ZA"],compliance:["popia"],timezones:["Africa/Johannesburg"]},ZM:{name:"Zambia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Zambia (DPA)",locales:["en"],compliance:["dpa-zm"],timezones:["Africa/Lusaka"]},ZW:{name:"Zimbabwe",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Zimbabwe (DPA)",locales:["en"],compliance:["dpa-zw"],timezones:["Africa/Harare"]},DZ:{name:"Algeria",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Algeria",locales:["ar-DZ","fr-DZ"],compliance:["dpa-dz"],timezones:["Africa/Algiers"]},EG:{name:"Egypt",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Egypt (DPA)",locales:["ar-EG"],compliance:["dpa-eg"],timezones:["Africa/Cairo"]},LY:{name:"Libya",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Libya",locales:["ar-LY"],compliance:[],timezones:["Africa/Tripoli"]},MA:{name:"Morocco",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Morocco",locales:["ar-MA","fr-MA"],compliance:["dpa-ma"],timezones:["Africa/Casablanca"]},SD:{name:"Sudan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Sudan",locales:["ar-SD"],compliance:[],timezones:["Africa/Khartoum"]},TN:{name:"Tunisia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Tunisia (DPA)",locales:["ar-TN","fr-TN"],compliance:["dpa-tn"],timezones:["Africa/Tunis"]},CD:{name:"DR Congo",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"DR Congo",locales:["fr-CD"],compliance:[],timezones:["Africa/Kinshasa","Africa/Lubumbashi"]},CF:{name:"Central African Republic",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Central African Republic",locales:["fr-CF"],compliance:[],timezones:["Africa/Bangui"]},CG:{name:"Republic of Congo",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Republic of Congo",locales:["fr-CG"],compliance:[],timezones:["Africa/Brazzaville"]},TD:{name:"Chad",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Chad",locales:["fr-TD","ar-TD"],compliance:[],timezones:["Africa/Ndjamena"]},BY:{name:"Belarus",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Belarus",locales:["be-BY","ru-BY"],compliance:[],timezones:["Europe/Minsk"]},MD:{name:"Moldova",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Moldova",locales:["ro-MD"],compliance:[],timezones:["Europe/Chisinau"]},RU:{name:"Russia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Russia",locales:["ru-RU"],compliance:[],timezones:["Europe/Kaliningrad","Europe/Moscow","Europe/Samara","Asia/Yekaterinburg","Asia/Omsk","Asia/Krasnoyarsk","Asia/Irkutsk","Asia/Yakutsk","Asia/Vladivostok","Asia/Magadan","Asia/Kamchatka"]},UA:{name:"Ukraine",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Ukraine",locales:["uk-UA"],compliance:[],timezones:["Europe/Kyiv"]},VA:{name:"Holy See",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Holy See",locales:["it-VA"],compliance:[],timezones:["Europe/Vatican"]},AU:{name:"Australia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Australia (Privacy Act)",locales:["en"],compliance:["privacy-au"],timezones:["Australia/Sydney","Australia/Brisbane","Australia/Adelaide","Australia/Darwin","Australia/Perth","Australia/Hobart"]},FJ:{name:"Fiji",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Fiji",locales:["en"],compliance:["privacy-fj"],timezones:["Pacific/Fiji"]},FM:{name:"Micronesia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Micronesia",locales:["en"],compliance:[],timezones:["Pacific/Chuuk","Pacific/Pohnpei","Pacific/Kosrae"]},KI:{name:"Kiribati",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Kiribati",locales:["en"],compliance:[],timezones:["Pacific/Tarawa","Pacific/Enderbury","Pacific/Kiritimati"]},MH:{name:"Marshall Islands",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Marshall Islands",locales:["en"],compliance:[],timezones:["Pacific/Majuro","Pacific/Kwajalein"]},MP:{name:"Northern Mariana Islands",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Northern Mariana Islands",locales:["en"],compliance:[],timezones:["Pacific/Saipan"]},NC:{name:"New Caledonia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"New Caledonia",locales:["fr-NC"],compliance:[],timezones:["Pacific/Noumea"]},NF:{name:"Norfolk Island",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Norfolk Island",locales:["en"],compliance:[],timezones:["Pacific/Norfolk"]},NR:{name:"Nauru",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Nauru",locales:["en"],compliance:[],timezones:["Pacific/Nauru"]},NU:{name:"Niue",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Niue",locales:["en"],compliance:[],timezones:["Pacific/Niue"]},NZ:{name:"New Zealand",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"New Zealand (Privacy Act)",locales:["en"],compliance:["privacy-nz"],timezones:["Pacific/Auckland","Pacific/Chatham"]},PF:{name:"French Polynesia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"French Polynesia",locales:["fr-PF"],compliance:[],timezones:["Pacific/Tahiti","Pacific/Marquesas","Pacific/Gambier"]},PG:{name:"Papua New Guinea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Papua New Guinea",locales:["en"],compliance:["privacy-pg"],timezones:["Pacific/Port_Moresby","Pacific/Bougainville"]},PW:{name:"Palau",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Palau",locales:["en"],compliance:[],timezones:["Pacific/Palau"]},SB:{name:"Solomon Islands",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Solomon Islands",locales:["en"],compliance:[],timezones:["Pacific/Guadalcanal"]},TO:{name:"Tonga",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Tonga",locales:["en"],compliance:[],timezones:["Pacific/Tongatapu"]},TV:{name:"Tuvalu",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Tuvalu",locales:["en"],compliance:[],timezones:["Pacific/Funafuti"]},VU:{name:"Vanuatu",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Vanuatu",locales:["fr-VU","en"],compliance:[],timezones:["Pacific/Efate"]},WS:{name:"Samoa",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Samoa",locales:["en"],compliance:[],timezones:["Pacific/Apia"]},UK:{name:"United Kingdom",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"United Kingdom (UK GDPR + PECR)",locales:["en"],compliance:["uk-gdpr","pecr"],timezones:["Europe/London"]},CH:{name:"Switzerland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Switzerland (revFADP)",locales:["de-CH","fr-CH","it-CH"],compliance:["revfadp"],timezones:["Europe/Zurich"]}}},Xn=Object.entries(z.countries).reduce(function(t,[e,n]){return t[e]=n.name,t},{}),et=Object.values(z.countries).reduce(function(t,e){return e.locales.forEach(n=>{t[n]=e.name}),t},{});var Y="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";function fn(t){return new TextEncoder().encode(t)}function yn(t){let e="";for(let n=0;n<t.length;n+=3){let o=t[n]??0,i=n+1<t.length?t[n+1]??0:0,a=n+2<t.length?t[n+2]??0:0;e+=Y[o>>2],e+=Y[(o&3)<<4|i>>4],n+1<t.length&&(e+=Y[(i&15)<<2|a>>6]),n+2<t.length&&(e+=Y[a&63])}return e}function Me(t){let e={v:2,ts:Math.floor(Date.now()/100),cmpId:t.cmpId,cmpV:t.cmpVersion,cs:t.consentScreen,lang:t.consentLanguage.toUpperCase().slice(0,2),vlV:t.vendorListVersion,pc:t.purposeConsents,vc:t.vendorConsents};return yn(fn(JSON.stringify(e)))}var hn=["ar","he","fa","ur","ps","sd","ug","yi"];function Ie(t,e){if(e==="ltr"||e==="rtl")return e;let n=t.slice(0,2).toLowerCase();return hn.includes(n)?"rtl":"ltr"}var p=()=>typeof window<"u"&&typeof document<"u",H=()=>p()?window:null;var Le=()=>p()&&typeof navigator<"u"?navigator:null;var V=["error"];function de(t){V=t}var w={info:(...t)=>{V.includes("info")&&console.info("[Consenti]",...t)},log:(...t)=>{V.includes("log")&&console.log("[Consenti]",...t)},warn:(...t)=>{V.includes("warning")&&console.warn("[Consenti]",...t)},error:(...t)=>{V.includes("error")&&console.error("[Consenti]",...t)}};function R(t){let e=document.createElement("script");if(t.src){let n=t.src.trim();if(/^javascript:/i.test(n))return w.warn(`${t.warnPrefix}: rejected src with javascript: protocol`),null;e.src=n}t.unsafeInnerHTML&&(e.innerHTML=t.unsafeInnerHTML);for(let[n,o]of Object.entries(t.attributes??{})){if(/^on/i.test(n)){w.warn(`${t.warnPrefix}: rejected attribute "${n}" (inline event handlers are not allowed)`);continue}e.setAttribute(n,o)}return document.head.appendChild(e),e}function Z(t){t?.remove()}var j=class{constructor(e){this.options=e;this.scriptEl=null;this.handler=()=>this.evaluate(),p()&&(e.bind!==!1&&window.addEventListener("consenti:consentSubmitted",this.handler),this.evaluate())}evaluate(){let n=this.options.widget.getConsent()?.[this.options.cookieId]==="granted";n&&!this.scriptEl?this.inject():!n&&this.scriptEl&&this.revoke()}inject(){let e=R({...this.options.src!==void 0?{src:this.options.src}:{},...this.options.unsafeInnerHTML!==void 0?{unsafeInnerHTML:this.options.unsafeInnerHTML}:{},...this.options.attributes!==void 0?{attributes:this.options.attributes}:{},warnPrefix:"ConsentScript"});e&&(this.scriptEl=e,this.options.onLoad?.())}revoke(){Z(this.scriptEl),this.scriptEl=null,this.options.onRevoke?.()}destroy(){p()&&(this.options.bind!==!1&&window.removeEventListener("consenti:consentSubmitted",this.handler),this.revoke())}};var U=class{constructor(e){this.options=e;this.scriptEl=null;this.handler=()=>this.evaluate(),p()&&(e.bind!==!1&&window.addEventListener("consenti:consentSubmitted",this.handler),this.evaluate())}evaluate(){let n=this.options.widget.getProfile()?.preferenceModal.categories[this.options.categoryId]?.cookies??[],o=this.options.widget.getConsent(),i=n.length>0&&n.every(a=>o?.[a]==="granted");i&&!this.scriptEl?this.inject():!i&&this.scriptEl&&this.revoke()}inject(){let e=R({...this.options.src!==void 0?{src:this.options.src}:{},...this.options.unsafeInnerHTML!==void 0?{unsafeInnerHTML:this.options.unsafeInnerHTML}:{},...this.options.attributes!==void 0?{attributes:this.options.attributes}:{},warnPrefix:"CategoryScript"});e&&(this.scriptEl=e,this.options.onLoad?.())}revoke(){Z(this.scriptEl),this.scriptEl=null,this.options.onRevoke?.()}destroy(){p()&&(this.options.bind!==!1&&window.removeEventListener("consenti:consentSubmitted",this.handler),this.revoke())}};var De="data-consenti-consent-script",Te="data-consenti-category-script",vn="data-consenti-bind",ue="data-consenti-scanned";function bn(t){return t.getAttribute(vn)!=="false"}function me(t){if(!p())return[];let e=document.querySelectorAll(`script[type="text/plain"][${De}]:not([${ue}]), script[type="text/plain"][${Te}]:not([${ue}])`),n=[];for(let o of e){o.setAttribute(ue,"");let i=o.getAttribute("src")??void 0,a=i?void 0:o.textContent?.trim()||void 0,r=bn(o),c=o.getAttribute(De),s=o.getAttribute(Te);c?n.push(new j({cookieId:c,widget:t,...i!==void 0?{src:i}:{},...a!==void 0?{unsafeInnerHTML:a}:{},bind:r})):s&&n.push(new U({categoryId:s,widget:t,...i!==void 0?{src:i}:{},...a!==void 0?{unsafeInnerHTML:a}:{},bind:r}))}return n}function k(t,e,n){let o="denied";for(let[i,a]of Object.entries(e)){if(a.purpose!==n)continue;let r=t[i];if(r==="granted")return"granted";r==="objected"&&o==="denied"&&(o="objected")}return o}var Cn=["ad_storage","analytics_storage","ad_user_data","ad_personalization","functionality_storage","personalization_storage","security_storage"];function K(t){let e={};for(let n of Cn){let o=t[n];o==="granted"?e[n]="granted":o==="denied"||o==="objected"?e[n]="denied":e[n]=n==="functionality_storage"||n==="security_storage"?"granted":"denied"}return e.ads_data_redaction=e.ad_storage==="denied"?"true":"false",e.url_passthrough="false",e}function ze(t,e){return{necessary:"granted",functional:k(t,e,"functional"),preferences:k(t,e,"preferences"),analytics:k(t,e,"analytics"),marketing:k(t,e,"marketing")}}function Re(t,e){return{analytics:k(t,e,"analytics"),target:k(t,e,"preferences"),manager:k(t,e,"marketing"),optimizer:k(t,e,"marketing")}}function Be(t,e){return{pixel:k(t,e,"marketing"),api:k(t,e,"marketing"),plugins:k(t,e,"functional"),facebookLogin:k(t,e,"functional")}}function Oe(t,e){return{session:k(t,e,"analytics"),heatmaps:k(t,e,"analytics"),performance:k(t,e,"analytics")}}function Ne(t,e){return{identify:k(t,e,"preferences"),page:k(t,e,"analytics"),track:k(t,e,"analytics"),group:k(t,e,"analytics"),alias:k(t,e,"analytics")}}var ge='.consenti-root{position:relative}.consenti-d-none{display:none !important}:root{--consenti-color-bg: #ffffff;--consenti-color-text: #1a2e4a;--consenti-color-text-muted: #949dab;--consenti-color-primary: #04111f;--consenti-color-primary-text: #ffffff;--consenti-color-secondary: #f0f4f8;--consenti-color-secondary-text: #1a2e4a;--consenti-color-border: #dbe4ee;--consenti-color-secondary-border: #1a2e4a;--consenti-color-overlay: #04111f;--consenti-color-accent: #d32f2f;--consenti-color-accent-text: #ffffff;--consenti-font-family: system-ui, -apple-system, sans-serif;--consenti-font-size-base: 14px;--consenti-font-size-heading: 16px;--consenti-font-weight-heading: 600;--consenti-line-height: 1.5;--consenti-spacing-xs: 5px;--consenti-spacing-sm: 8px;--consenti-spacing-md: 16px;--consenti-spacing-lg: 24px;--consenti-border-radius: 8px;--consenti-border-radius-btn: 0;--consenti-shadow: 0 4px 24px rgba(21, 101, 192, 0.14);--consenti-toggle-bg-on: #43a047;--consenti-toggle-bg-partial: #97c098;--consenti-toggle-bg-off: #9ca3af;--consenti-toggle-knob: #ffffff;--consenti-toggle-width: 52px;--consenti-toggle-height: 28px;--consenti-z-banner: 9999;--consenti-z-overlay: 9998;--consenti-z-modal: 10000}.consenti-root--dark{--consenti-color-bg: #1e2535;--consenti-color-text: #e2e8f0;--consenti-color-text-muted: #a4acb9;--consenti-color-primary: #e2e8f0;--consenti-color-primary-text: #2a3447;--consenti-color-secondary: #2a3447;--consenti-color-secondary-text: #cbd5e1;--consenti-color-border: #374151;--consenti-color-overlay: rgba(0, 0, 0, 0.65);--consenti-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);--consenti-toggle-bg-off: #6b7280}.consenti-overlay{position:fixed;inset:0;background:var(--consenti-color-overlay);z-index:var(--consenti-z-overlay)}.consenti-btn{display:inline-flex;align-items:center;justify-content:center;padding:var(--consenti-spacing-sm) var(--consenti-spacing-md);border:none;border-radius:var(--consenti-border-radius-btn);font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);font-weight:500;line-height:1;cursor:pointer;text-decoration:none;transition:opacity .15s ease}.consenti-btn:focus-visible{outline:2px solid var(--consenti-color-primary);outline-offset:2px}.consenti-btn:hover{opacity:.9}.consenti-btn--primary{background:var(--consenti-color-primary);color:var(--consenti-color-primary-text);border:1px solid var(--consenti-color-primary)}.consenti-btn--secondary{background:var(--consenti-color-secondary);color:var(--consenti-color-primary);border:1px solid var(--consenti-color-primary)}.consenti-btn--secondary:hover{background:var(--consenti-color-primary);color:var(--consenti-color-secondary)}.consenti-btn--text{background:rgba(0,0,0,0);color:var(--consenti-color-primary);padding-left:0;padding-right:0;text-decoration:underline;text-underline-offset:4px;border:1px solid rgba(0,0,0,0)}.consenti-btn--accent{background:var(--consenti-color-accent);color:var(--consenti-color-accent-text)}.consenti-btn--action{background:rgba(0,0,0,0);color:var(--consenti-color-text);font-size:20px;line-height:1;border-radius:50%;height:24px;width:24px;font-size:24px;padding:0}.consenti-banner{position:fixed;left:0;right:0;z-index:var(--consenti-z-banner);background:var(--consenti-color-bg);color:var(--consenti-color-text);font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);line-height:var(--consenti-line-height);box-shadow:var(--consenti-shadow);padding:var(--consenti-spacing-lg);display:flex;flex-wrap:wrap;align-items:center;gap:var(--consenti-spacing-md)}.consenti-banner--top{top:0;border-bottom:1px solid var(--consenti-color-border)}@media(prefers-reduced-motion: no-preference){.consenti-banner--top{animation:consenti-slide-down .2s ease-out}}.consenti-banner--bottom{bottom:0;border-top:1px solid var(--consenti-color-border)}@media(prefers-reduced-motion: no-preference){.consenti-banner--bottom{animation:consenti-slide-up .2s ease-out}}.consenti-banner--top .consenti-banner__text-container,.consenti-banner--bottom .consenti-banner__text-container{min-width:60vw}.consenti-banner--middle{top:50%;transform:translateY(-50%);left:50%;right:auto;transform:translate(-50%, -50%);width:min(600px,90vw);border-radius:var(--consenti-border-radius);flex-direction:column;align-items:flex-start}.consenti-banner--left-bottom{bottom:var(--consenti-spacing-lg);left:var(--consenti-spacing-lg);right:auto;width:min(400px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner--right-bottom{bottom:var(--consenti-spacing-lg);right:var(--consenti-spacing-lg);left:auto;width:min(400px,90vw);border-radius:var(--consenti-border-radius)}.consenti-banner__text-container{flex:1}.consenti-banner__text-container a:not(.consenti-banner__link){color:var(--consenti-color-text)}.consenti-banner__text-container a:not(.consenti-banner__link):hover{color:var(--consenti-color-primary)}.consenti-banner__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0 0 var(--consenti-spacing-sm);width:calc(100% - 56px)}.consenti-banner__text{margin:0}.consenti-banner__links{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);margin-top:var(--consenti-spacing-sm)}.consenti-banner__link{font-size:.87em;padding:var(--consenti-spacing-xs) var(--consenti-spacing-sm);border-radius:var(--consenti-spacing-xs)}.consenti-banner__controls{position:absolute;top:var(--consenti-spacing-sm);right:var(--consenti-spacing-sm);display:flex;align-items:center;gap:4px}.consenti-banner__buttons{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);align-items:center;margin-top:var(--consenti-spacing-sm)}.consenti-banner__close{margin-top:-5px}.consenti-banner .consenti__powered-by{bottom:0}@media(max-width: 576px){.consenti-banner{padding:var(--consenti-spacing-md);gap:var(--consenti-spacing-sm)}.consenti-banner--left-bottom,.consenti-banner--right-bottom{left:0;right:0;bottom:0;width:100%;border-radius:0;border-top:1px solid var(--consenti-color-border)}.consenti-banner--middle{width:95vw}.consenti-banner__buttons{width:100%;flex-direction:column;align-items:stretch}.consenti-banner__text-container{min-width:0;width:100%}}@keyframes consenti-slide-up{from{transform:translateY(100%)}to{transform:translateY(0)}}@keyframes consenti-slide-down{from{transform:translateY(-100%)}to{transform:translateY(0)}}.consenti-modal{position:fixed;inset:0;z-index:var(--consenti-z-modal);display:flex;font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);color:var(--consenti-color-text);line-height:var(--consenti-line-height)}.consenti-modal--passthrough{pointer-events:none}.consenti-modal--passthrough .consenti-modal__container{pointer-events:auto}.consenti-modal--center{align-items:center;justify-content:center}.consenti-modal--left{align-items:stretch;justify-content:flex-start}.consenti-modal--left .consenti-modal__container{border-radius:0 var(--consenti-border-radius) var(--consenti-border-radius) 0;height:100%;max-height:100vh}.consenti-modal--right{align-items:stretch;justify-content:flex-end}.consenti-modal--right .consenti-modal__container{border-radius:0;height:100%;max-height:100vh}.consenti-modal--fullscreen{align-items:flex-start;justify-content:flex-start}.consenti-modal--fullscreen .consenti-modal__container{width:100vw;max-width:100vw;height:100dvh;max-height:100dvh;border-radius:0;margin:0}.consenti-modal__overlay{position:absolute;inset:0;background:var(--consenti-color-overlay)}.consenti-modal__container{position:relative;z-index:1;background:var(--consenti-color-bg);border-radius:var(--consenti-border-radius);box-shadow:var(--consenti-shadow);width:min(560px,90vw);max-height:85vh;display:flex;flex-direction:column;overflow:hidden}@media(prefers-reduced-motion: no-preference){.consenti-modal__container{animation:consenti-fade-in .15s ease-out}}.consenti-modal__header{display:flex;justify-content:space-between;padding:var(--consenti-spacing-md) var(--consenti-spacing-lg);border-bottom:1px solid var(--consenti-color-border);flex-shrink:0;position:relative;flex-direction:column}.consenti-modal__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0;flex:1;width:calc(100% - 56px)}.consenti-modal__header-controls{display:flex;align-items:center;gap:6px;flex-shrink:0;position:absolute;inset-inline-end:15px;top:10px}.consenti-modal__close{flex-shrink:0;margin-top:-5px}.consenti-modal__links{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);margin-top:var(--consenti-spacing-md)}.consenti-modal__link{font-size:.87em;padding:var(--consenti-spacing-xs) var(--consenti-spacing-sm);border-radius:var(--consenti-spacing-xs)}.consenti-modal__subheading{padding-top:var(--consenti-spacing-sm);margin:0;color:var(--consenti-color-text-muted);font-size:13px}.consenti-modal__text{padding:var(--consenti-spacing-sm) 0}.consenti-modal__body{overflow-y:auto;flex:1;padding:var(--consenti-spacing-sm) var(--consenti-spacing-lg);position:relative}.consenti-modal__categories{display:flex;flex-direction:column;gap:var(--consenti-spacing-md);padding:var(--consenti-spacing-md) 0}.consenti-modal__buttons{display:flex;flex-wrap:wrap;gap:var(--consenti-spacing-sm);padding:var(--consenti-spacing-md);border-top:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__receipt-option{padding-top:var(--consenti-spacing-md);border-top:1px solid var(--consenti-color-border);flex-shrink:0}.consenti-modal__receipt-label{display:flex;align-items:center;gap:var(--consenti-spacing-sm);font-size:var(--consenti-font-size-base);cursor:pointer}.consenti-modal__receipt-checkbox{width:16px;height:16px;flex-shrink:0;cursor:pointer;accent-color:var(--consenti-toggle-bg-on)}.consenti-modal__receipt-desc{margin:4px 0 0 0;margin-inline-start:24px;font-size:12px;color:#6b7280}@keyframes consenti-fade-in{from{opacity:0;transform:scale(0.97)}to{opacity:1;transform:scale(1)}}.consenti-category{padding:var(--consenti-spacing-md) 0;border-bottom:1px solid var(--consenti-color-border)}.consenti-category:last-child{border-bottom:none}.consenti-category--mandatory .consenti-category__toggle{opacity:.6;cursor:default}.consenti-category__header{display:flex;align-items:center;justify-content:space-between;gap:var(--consenti-spacing-md);margin-bottom:var(--consenti-spacing-sm)}.consenti-category__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin:0}.consenti-category__text{color:var(--consenti-color-text-muted);font-size:13px;margin:0}.consenti-category__text--clamped{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.consenti-category__text-wrap{display:flex;flex-direction:column;align-items:flex-start;gap:4px}.consenti-category__show-more{background:none;border:none;padding:0;color:var(--consenti-color-primary);cursor:pointer;font-size:12px;text-decoration:underline;text-underline-offset:2px}.consenti-category__show-more:hover{opacity:.8}.consenti-category__li-desc{margin-bottom:var(--consenti-spacing-sm);font-size:12px;color:#6b7280;font-style:italic}.consenti-category__toggle{flex-shrink:0;position:relative;width:var(--consenti-toggle-width);height:var(--consenti-toggle-height);border-radius:calc(var(--consenti-toggle-height)/2);background:var(--consenti-toggle-bg-off);border:none;cursor:pointer;transition:background .2s ease;padding:0}.consenti-category__toggle:focus-visible{outline:2px solid var(--consenti-color-primary);outline-offset:2px}.consenti-category__toggle[aria-checked=true]{background:var(--consenti-toggle-bg-on)}.consenti-category__toggle--mandatory{cursor:default}.consenti-category__toggle--partial{background:var(--consenti-toggle-bg-partial, var(--consenti-color-accent, #f59e0b))}.consenti-category__toggle--partial .consenti-category__toggle-icon--check,.consenti-category__toggle--partial .consenti-category__toggle-icon--cross,.consenti-category__toggle--partial .consenti-category__toggle-icon--objected{opacity:0}.consenti-category__toggle--partial.consenti-toggle--li .consenti-category__toggle-icon--objected{opacity:0}.consenti-category__toggle--partial .consenti-category__toggle-knob{transform:translateX(calc((var(--consenti-toggle-width) - var(--consenti-toggle-height)) / 2))}.consenti-category__toggle--partial::after{content:"";position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:8px;height:2px;border-radius:1px;background:hsla(0,0%,100%,.9);pointer-events:none;z-index:1}.consenti-category__toggle-icon{position:absolute;top:50%;transform:translateY(-50%);width:10px;height:10px;display:flex;align-items:center;justify-content:center;pointer-events:none;transition:opacity .15s ease;color:hsla(0,0%,100%,.9)}.consenti-category__toggle-icon svg{width:10px;height:10px;overflow:visible}.consenti-category__toggle-icon--check{left:8px;opacity:0}[aria-checked=true] .consenti-category__toggle-icon--check{opacity:1}.consenti-category__toggle-icon--cross{right:8px;opacity:1}[aria-checked=true] .consenti-category__toggle-icon--cross{opacity:0}.consenti-category__toggle-icon--objected{right:8px;opacity:0}.consenti-category__toggle-knob{position:absolute;top:2px;left:2px;width:calc(var(--consenti-toggle-height) - 4px);height:calc(var(--consenti-toggle-height) - 4px);border-radius:50%;background:var(--consenti-toggle-knob);transition:transform .2s ease;display:block}[aria-checked=true] .consenti-category__toggle-knob{transform:translateX(calc(var(--consenti-toggle-width) - var(--consenti-toggle-height)))}.consenti-category__params{display:flex;flex-direction:column;gap:var(--consenti-spacing-xs, 6px);margin-top:var(--consenti-spacing-sm);padding-top:var(--consenti-spacing-sm);border-top:1px dashed var(--consenti-color-border)}.consenti-category__param{display:flex;align-items:center;justify-content:space-between;gap:var(--consenti-spacing-sm)}.consenti-category__param-info{display:flex;flex-direction:column;gap:2px;min-width:0}.consenti-category__param-label{font-size:12px;font-family:var(--consenti-font-family-mono, monospace);color:var(--consenti-color-text);overflow-wrap:anywhere}.consenti-category__param-meta{font-size:11px;color:var(--consenti-color-text-muted)}.consenti-category__param-toggle{flex-shrink:0;position:relative;width:calc(var(--consenti-toggle-width)*.8);height:calc(var(--consenti-toggle-height)*.8);border-radius:calc(var(--consenti-toggle-height)*.4);background:var(--consenti-toggle-bg-off);border:none;cursor:pointer;transition:background .2s ease;padding:0}.consenti-category__param-toggle:focus-visible{outline:2px solid var(--consenti-color-primary);outline-offset:2px}.consenti-category__param-toggle[aria-checked=true]{background:var(--consenti-toggle-bg-on)}.consenti-category__param-toggle--mandatory{cursor:default;opacity:.6}.consenti-category__param-toggle .consenti-category__toggle-icon{width:8px;height:8px}.consenti-category__param-toggle .consenti-category__toggle-icon svg{width:8px;height:8px}.consenti-category__param-toggle .consenti-category__toggle-icon--check{left:6px}.consenti-category__param-toggle .consenti-category__toggle-icon--cross{right:6px}.consenti-category__param-toggle .consenti-category__toggle-icon--objected{right:6px}.consenti-category__param-toggle .consenti-category__toggle-knob{width:calc(var(--consenti-toggle-height)*.8 - 4px);height:calc(var(--consenti-toggle-height)*.8 - 4px)}.consenti-category__param-toggle[aria-checked=true] .consenti-category__toggle-knob{transform:translateX(calc(var(--consenti-toggle-width) * 0.8 - var(--consenti-toggle-height) * 0.8))}.consenti-toggle--li .consenti-category__toggle-icon--cross{opacity:0}.consenti-toggle--li .consenti-category__toggle-icon--objected{opacity:1}.consenti-toggle--li[aria-checked=true] .consenti-category__toggle-icon--objected{opacity:0}[dir=rtl] .consenti-category__toggle-knob{left:auto;right:2px}[dir=rtl] .consenti-category__toggle[aria-checked=true] .consenti-category__toggle-knob{transform:translateX(calc(-1 * (var(--consenti-toggle-width) - var(--consenti-toggle-height))))}[dir=rtl] .consenti-category__toggle--partial .consenti-category__toggle-knob{transform:translateX(calc(-1 * (var(--consenti-toggle-width) - var(--consenti-toggle-height)) / 2))}[dir=rtl] .consenti-category__param-toggle[aria-checked=true] .consenti-category__toggle-knob{transform:translateX(calc(-1 * (var(--consenti-toggle-width) * 0.8 - var(--consenti-toggle-height) * 0.8)))}[dir=rtl] .consenti-category__toggle-icon--check{left:auto;right:8px}[dir=rtl] .consenti-category__toggle-icon--cross,[dir=rtl] .consenti-category__toggle-icon--objected{right:auto;left:8px}[dir=rtl] .consenti-category__param-toggle .consenti-category__toggle-icon--check{left:auto;right:6px}[dir=rtl] .consenti-category__param-toggle .consenti-category__toggle-icon--cross,[dir=rtl] .consenti-category__param-toggle .consenti-category__toggle-icon--objected{right:auto;left:6px}.consenti-locale-switcher{position:relative;display:inline-flex}.consenti-locale-switcher__btn{font-size:1rem;line-height:1;cursor:pointer}.consenti-locale-switcher__list{position:absolute;top:calc(100% + 4px);inset-inline-end:0;z-index:calc(var(--consenti-z-modal) + 1);background:var(--consenti-color-bg);border:1px solid var(--consenti-color-border);border-radius:var(--consenti-border-radius);box-shadow:var(--consenti-shadow);list-style:none;margin:0;padding:4px 0;min-width:80px}.consenti-locale-switcher__list li{display:block}.consenti-locale-switcher__option{display:block;width:100%;padding:6px 12px;text-align:start;font-size:var(--consenti-font-size-base);color:var(--consenti-color-text);background:none;border:none;cursor:pointer}.consenti-locale-switcher__option:hover{background:var(--consenti-color-secondary)}.consenti-locale-switcher__option--active{font-weight:600;color:var(--consenti-color-primary)}.consenti-locale-switcher--modal .consenti-locale-switcher__list{inset-inline-end:0}.consenti-meta-footer{display:flex;flex-wrap:wrap;gap:.75rem;font-size:.7rem;color:var(--consenti-color-text);opacity:.6;border-top:1px solid var(--consenti-color-border);padding-top:.5rem;margin-top:.75rem}.consenti-meta-footer__item{display:inline-flex;align-items:center;gap:.25rem}.consenti-meta-footer__id{font-family:monospace;font-size:.65rem}.consenti-meta-footer__settings{background:none;border:none;padding:0;color:var(--consenti-color-primary);cursor:pointer;font-size:.7rem;text-decoration:underline;text-underline-offset:2px}.consenti-meta-footer__settings:hover{opacity:.8}.consenti--enhanced-a11y .consenti-btn{min-height:44px;min-width:44px}.consenti--enhanced-a11y .consenti-category__toggle{min-width:52px;min-height:28px}.consenti--enhanced-a11y .consenti-category__param-toggle{min-width:44px;min-height:24px}.consenti--enhanced-a11y *:focus-visible{outline:3px solid var(--consenti-color-primary);outline-offset:2px}.consenti-age-gate{position:fixed;inset:0;z-index:var(--consenti-z-modal);margin:auto;width:min(400px,90vw);height:fit-content;background:var(--consenti-color-bg);color:var(--consenti-color-text);border-radius:var(--consenti-border-radius);box-shadow:var(--consenti-shadow);padding:var(--consenti-spacing-lg);font-family:var(--consenti-font-family);font-size:var(--consenti-font-size-base);line-height:var(--consenti-line-height)}.consenti-age-gate__heading{font-size:var(--consenti-font-size-heading);font-weight:var(--consenti-font-weight-heading);margin-bottom:var(--consenti-spacing-sm)}.consenti-age-gate__text{color:var(--consenti-color-text-muted);margin-bottom:var(--consenti-spacing-md)}.consenti-age-gate__buttons{display:flex;gap:var(--consenti-spacing-sm)}.consenti__powered-by{position:absolute;inset-inline-end:var(--consenti-spacing-md);opacity:.3;color:var(--consenti-color-primary);font-size:70%}.consenti-transition-1_2x{transition:transform 300ms ease-in-out}.consenti-transition-1_2x:hover{transform:scale(1.2)}';var Q=class{constructor(){this.channel=null}get isSupported(){return p()&&typeof BroadcastChannel<"u"}connect(e){this.isSupported&&(this.onMessageCallback=e,this.channel=new BroadcastChannel("consenti"),this.channel.onmessage=n=>{n.data?.type&&e(n.data)},this.channel.onmessageerror=()=>{w.warn("BroadcastChannel message error \u2014 ignoring")})}send(e){this.channel?.postMessage(e)}disconnect(){this.channel?.close(),this.channel=null,this.onMessageCallback=void 0}};function He(t){let e={version:t.version,issuedAt:new Date().toISOString(),visitorId:t.visitorId,profileId:t.profileId,locale:t.locale,consent:t.consent};return t.signature&&(e.signature=t.signature),e}function Ve(t){if(typeof window>"u"||typeof document>"u")return;let e=JSON.stringify(t,null,2),n=new Blob([e],{type:"application/json"}),o=URL.createObjectURL(n),i=document.createElement("a");i.href=o,i.download=`consenti-receipt-${t.issuedAt.slice(0,10)}.json`,i.style.display="none",document.body.appendChild(i),i.click(),document.body.removeChild(i),setTimeout(()=>URL.revokeObjectURL(o),1e3)}function E(t){return`${t}_${crypto.randomUUID().replace(/-/g,"")}`}var je="consenti_config";function wn(){if(!p())return{};try{let t=localStorage.getItem(je);return t?JSON.parse(t):{}}catch{return{}}}function _n(t){if(p())try{localStorage.setItem(je,JSON.stringify(t))}catch{}}function Ue(){let t=wn();if(t.visitorId)return t.visitorId;let e=E("visi");return _n({...t,visitorId:e}),e}function Ke(t){if(!p())return null;let e=document.cookie.match(new RegExp(`(?:^|;\\s*)${kn(t)}=([^;]*)`));return e?decodeURIComponent(e[1]??""):null}function fe(t,e,n={}){if(!p())return;let o=n.secure??(typeof window<"u"&&typeof window.location<"u"&&window.location.protocol==="https:"),i=[`${encodeURIComponent(t)}=${encodeURIComponent(e)}`,`Path=${n.path??"/"}`,`SameSite=${n.sameSite??"Lax"}`,n.maxAge!=null?`Max-Age=${n.maxAge}`:"",o?"Secure":"",n.domain?`Domain=${n.domain}`:""];document.cookie=i.filter(Boolean).join("; ")}function $e(t,e={}){fe(t,"",{...e,maxAge:0})}function kn(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function An(t){if(typeof t!="object"||t===null)return!1;let e=t;return typeof e.s=="string"&&typeof e.v=="number"&&typeof e.i=="string"&&typeof e.u=="string"&&typeof e.t=="number"&&(e.g===0||e.g===1)&&typeof e.p=="number"&&typeof e.c=="object"&&e.c!==null&&typeof e.l=="string"}function X(t){return JSON.stringify(t)}function B(t){let e=t.lastIndexOf("::sig:"),n=e!==-1?t.slice(0,e):t;try{let o=JSON.parse(n);return An(o)?o:null}catch{return null}}function ye(t){let e={};for(let[n,o]of Object.entries(t.c))o==="g"?e[n]="granted":o==="o"?e[n]="objected":e[n]="denied";return e}function ee(t){let e={};for(let[n,o]of Object.entries(t))o==="granted"?e[n]="g":o==="objected"?e[n]="o":e[n]="d";return e}var $=class{constructor(e){this.mode=e;e==="localStorage"&&p()&&typeof localStorage>"u"&&(w.warn("localStorage is not available, falling back to cookie storage"),this.mode="cookie")}read(e){return p()?this.mode==="localStorage"?localStorage.getItem(e):Ke(e):null}write(e,n,o={}){p()&&(this.mode==="localStorage"?localStorage.setItem(e,n):fe(e,n,o))}remove(e,n={}){p()&&(this.mode==="localStorage"?localStorage.removeItem(e):$e(e,n))}};function xn(t){return t*24*60*60}var M="consenti_data";function Fe(t="cookie"){let n=new $(t).read(M);return n&&B(n)?.l||null}var he="consenti";function Pn(t){let e=t.split("::");if(e.length<2)return null;let n=e[0]??"",o;if(n.startsWith("t:")){let s=n.slice(2),l=parseInt(s,10);if(!s||isNaN(l))return null;o=new Date(l*1e3).toISOString()}else if(n.startsWith("ConsentTimestamp:")){if(o=n.replace("ConsentTimestamp:",""),!o)return null}else return null;let i=e[1]??"",a=e[2],r={};for(let s of i.split("|")){let l=s.indexOf(":");if(l===-1)return null;let m=s.slice(0,l),d=s.slice(l+1);if(!m||d!=="granted"&&d!=="denied"&&d!=="objected")return null;r[m]=d}let c={timestamp:o,consent:r};return a?.startsWith("sig:")&&(c.signature=a.slice(4)),c}var ne=class{constructor(e,n,o,i){this.profileId=e;this.storageMode=n;this.cookieDomains=o;this.appUserId=i;this.cachedData=null;this.expiryDays=365;this.profileVersion=0;this.storage=new $(n)}setProfileExpiry(e){this.expiryDays=e}setProfileVersion(e){this.profileVersion=e}get cookieName(){return M}read(){let e=this.storage.read(M);if(e){let o=B(e);if(o)return this.cachedData=o,this.toParseConsent(o)}let n=this.migrateLegacyCookie();return n?(this.cachedData=n,this.toParseConsent(n)):null}async readAndVerify(e){let n=this.storage.read(M);if(!n){let s=this.migrateLegacyCookie();return s?(this.cachedData=s,this.toParseConsent(s)):null}let o=n.lastIndexOf("::sig:"),i=o!==-1?n.slice(0,o):n,a=o!==-1?n.slice(o+6):void 0,r=B(n);if(!r)return null;if(a){let{verifyValue:s}=await import("./hmac-UKMRMKN6.mjs");if(!await s(i,a,e))return w.warn("Cookie signature mismatch \u2014 consent cleared and banner will re-appear"),this.delete(),null}this.cachedData=r;let c=this.toParseConsent(r);return a?{...c,signature:a}:c}write(e,n={},o){let i=this.buildCookieData(e,n),a=X(i);o&&(a=`${a}::sig:${o}`),this.storage.write(M,a,this.buildCookieOpts()),this.cachedData=i}async writeAndSign(e,n,o={}){let{signValue:i}=await import("./hmac-UKMRMKN6.mjs"),a=this.buildCookieData(e,o),r=X(a),c=await i(r,n);return this.storage.write(M,`${r}::sig:${c}`,this.buildCookieOpts()),this.cachedData=a,c}delete(){let e=this.buildCookieOpts();this.storage.remove(M,e),this.deleteLegacyCookies(),this.cachedData=null}hasConsent(){return this.cachedData?!0:this.read()!==null}getConsent(){return this.cachedData?ye(this.cachedData):this.read()?.consent??null}getConsentDate(){if(this.cachedData)return new Date(this.cachedData.t*1e3);let e=this.read();return e?new Date(e.timestamp):!1}getConsentId(){if(this.cachedData)return this.cachedData.i;let e=this.storage.read(M);return e?B(e)?.i??null:null}getConsentLocale(){if(this.cachedData)return this.cachedData.l||null;let e=this.storage.read(M);return e&&B(e)?.l||null}setInMemory(e){this.cachedData={s:this.profileId,v:this.profileVersion,i:this.cachedData?.i??E("cons"),t:Math.round(Date.now()/1e3),u:this.appUserId??"",g:0,p:0,c:ee(e),l:this.cachedData?.l??""}}clearInMemory(){this.cachedData=null}toParseConsent(e){return{timestamp:new Date(e.t*1e3).toISOString(),consent:ye(e)}}buildCookieData(e,n={}){return{s:this.profileId,v:this.profileVersion,i:n.consentId??E("cons"),t:Math.round(Date.now()/1e3),u:this.appUserId??"",g:n.gpcDetected?1:0,p:n.source??0,c:ee(e),l:n.locale??""}}migrateLegacyCookie(){if(typeof document>"u")return null;let e=`_${this.profileId}`,n=this.storageMode==="localStorage"?typeof localStorage<"u"?Object.keys(localStorage):[]:document.cookie.split(";").map(o=>o.split("=")[0]?.trim()??"");for(let o of n){if(!o.startsWith(`${he}_`)||!o.endsWith(e))continue;let i=this.storage.read(o);if(!i)continue;let a=Pn(i);if(!a)continue;let r=o.slice(he.length+1,o.length-e.length),c=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(r)?r:"",s={s:this.profileId,v:0,i:E("cons"),t:Math.round(new Date(a.timestamp).getTime()/1e3),u:c,g:0,p:0,c:ee(a.consent),l:""};return this.storage.write(M,X(s),this.buildCookieOpts()),this.storage.remove(o,this.buildCookieOpts()),w.info(`Migrated consent cookie from "${o}" to "${M}"`),s}return null}deleteLegacyCookies(){if(typeof document>"u"||this.storageMode==="localStorage")return;let e=`_${this.profileId}`,n=this.buildCookieOpts();for(let o of document.cookie.split(";")){let i=o.split("=")[0]?.trim()??"";i.startsWith(`${he}_`)&&i.endsWith(e)&&this.storage.remove(i,n)}}buildCookieOpts(){let e={path:"/",sameSite:"Lax",maxAge:xn(this.expiryDays)};if(this.cookieDomains){let n=this.cookieDomains.split(",")[0]?.trim();n&&(e.domain=n)}return e}};var te=class{constructor(e,n){this.profileId=e;this.gtmConfig=n;this.gtmConfig&&this.initConsentMode()}initConsentMode(){if(!p())return;let e=H();if(!e)return;let n=this.gtmConfig,o=n.dataLayer??"dataLayer";Array.isArray(e[o])||(e[o]=[]),typeof e.gtag!="function"&&(e.gtag=((...a)=>{e[o].push(a)}));let i=e.gtag;if(i("consent","default",K({})),n.containerId){let a=o!=="dataLayer"?`&l=${encodeURIComponent(o)}`:"";R({src:`https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(n.containerId)}${a}`,warnPrefix:"GTM"})}}dispatch(e,n){if(!p())return;let o=new CustomEvent(e,{detail:n,bubbles:!0});window.dispatchEvent(o),this.pushToGTM(e,n)}pushToGTM(e,n){let o=this.gtmConfig;if(!o)return;let i=H();if(!i)return;let a=o.dataLayer??"dataLayer",r=i[a];if(Array.isArray(r)&&(o.verbose&&(!o.events||o.events.length===0||o.events.includes(e))&&r.push({event:e,content:n}),e==="consenti:consentSubmitted")){let c=n.consentJson??{},s=typeof i.gtag=="function"?i.gtag:(...l)=>r.push(l);s("consent","update",K(c)),o.urlPassthrough&&s("set","url_passthrough",!0),o.adsDataRedaction&&s("set","ads_data_redaction",c.ad_storage!=="granted")}}};function qe(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function ve(t,e){if(e===void 0||e===null)return t;if(Array.isArray(t)&&Array.isArray(e)){let n=Math.max(t.length,e.length);return Array.from({length:n},(o,i)=>i>=e.length?t[i]:i>=t.length?e[i]:ve(t[i],e[i]))}if(qe(t)&&qe(e)){let n={...t};for(let o of Object.keys(e)){let i=e[o];if(i===null){delete n[o];continue}n[o]=ve(t[o],i)}return n}return e}function I(t,e){return ve(t,e)}function We(){return{tz:Intl.DateTimeFormat().resolvedOptions().timeZone,lang:navigator.language,langs:Array.from(navigator.languages)}}function Je(t){let e=Intl.DateTimeFormat().resolvedOptions();return btoa(JSON.stringify({timezone:e.timeZone,languages:Array.from(navigator.languages??[]),language:navigator.language??"",locale:t??e.locale??navigator.language??""}))}function Gn(t){return{purpose:t.purpose,...t.listenGpc?{listenGpc:!0}:{},...t.preGrant?{preGrant:!0}:{},...t.cpraCategory?{cpraCategory:t.cpraCategory}:{}}}function Sn(t){let e={};for(let[n,o]of Object.entries(t))e[n]=Gn(o);return e}function En(t){return{text:t.text,style:t.style,action:t.action,...t.cookies!==void 0?{cookies:t.cookies}:{}}}function Ze(t){let e={};for(let[n,o]of Object.entries(t))e[n]=En(o);return e}function Mn(t){return{heading:t.heading,htmlText:t.htmlText,legalBasis:t.legalBasis,...t.legitimateInterestDescription!==void 0?{legitimateInterestDescription:t.legitimateInterestDescription}:{},cookies:t.cookies}}function In(t){let e={};for(let[n,o]of Object.entries(t))e[n]=Mn(o);return e}function Ye(t){return{position:t.position,htmlText:t.htmlText,buttons:Ze(t.buttons),...t.heading?{heading:t.heading}:{},...t.showClose!==void 0?{showClose:t.showClose}:{},...t.showLocaleSwitcher!==void 0?{showLocaleSwitcher:t.showLocaleSwitcher}:{}}}function Ln(t){return{heading:t.heading,htmlText:t.htmlText??"",buttons:Ze(t.buttons),categories:In(t.categories),...t.position?{position:t.position}:{},...t.subheading?{subheading:t.subheading}:{},...t.persistent!==void 0?{persistent:t.persistent}:{}}}function Dn(t,e,n){if(e){if(t[e])return t[e];let o=e.split("-")[0]??"",i=Object.keys(t).find(a=>a===o||a.startsWith(`${o}-`));if(i)return t[i]}return t[n]??Object.values(t)[0]}function Qe(t,e){let n=Dn(t.translations,e,t.defaultLocale);return{id:"0",defaultLocale:t.defaultLocale,locales:Object.keys(t.translations),cookies:Sn(t.cookies),...t.expiryDays?{expiryDays:t.expiryDays}:{},mainBanner:Ye(n.mainBanner),preferenceModal:Ln(n.preferenceModal),gpcMode:t.gpcMode,complianceGroup:t.complianceGroup,...n.gpcBanner?{gpcBanner:Ye(n.gpcBanner)}:{}}}var ke=new Map;function Xe(t,e){ke.set(t,e)}function Tn(t){return ke.get(t)}function zn(t){for(let e of ke.values())if(e.complianceGroup===t)return e}function Ce(t){let e=t.gpcMode,n="ignore";return typeof e=="boolean"?n=e?"honor":"ignore":e&&(n=e),{id:t.id,...t.version!==void 0?{version:t.version}:{},defaultLocale:t.defaultLocale,locales:t.locales,cookies:t.cookies,mainBanner:t.mainBanner,allowReceipt:t.allowReceipt??!1,preferenceModal:t.preferenceModal,...t.expiryDays?{expiryDays:t.expiryDays}:{},...t.gpcBanner?{gpcBanner:t.gpcBanner}:{},...n?{gpcMode:n}:{},...t.darkMode!==void 0?{darkMode:t.darkMode}:{},...t.complianceGroup?{complianceGroup:t.complianceGroup}:{},...t.complianceConfig?{complianceConfig:t.complianceConfig}:{},...t.dpdpa?{dpdpa:t.dpdpa}:{},...t.hidePoweredBy!==void 0?{hidePoweredBy:t.hidePoweredBy}:{},...t.showFooterMetadata!==void 0?{showFooterMetadata:t.showFooterMetadata}:{},...t.enhanceAccessibility!==void 0?{enhanceAccessibility:t.enhanceAccessibility}:{}}}var en="__consenti_resolved_",Rn=3600*1e3;function Bn(t,e,n){if(!n)return null;let o=`${en}${t}_${e}`;try{let i=sessionStorage.getItem(o);if(!i)return null;let a=JSON.parse(i);return Date.now()>a.expiresAt?(sessionStorage.removeItem(o),null):a}catch{return null}}function On(t,e,n,o){if(!n)return;let i=`${en}${t}_${e}`;try{sessionStorage.setItem(i,JSON.stringify({...o,expiresAt:Date.now()+Rn}))}catch{}}function we(t,e){if(e)return!0;let n=t.allowedOrigins;if(!n||n.length===0)return!0;let o=window.location.origin;return n.some(i=>i===o||i==="*")}async function q(t,e){let{DEFAULT_PROFILES:n}=await import("./profiles-OEZAKEWL.mjs"),o=n[t];return o?Qe(o,"en"):null}function nn(t,e){let n=e.core?.usePrebuiltProfiles??"all";return n==="all"?!0:n.includes(t)}var be;function Nn(){if(be)return be;let t=new Map,e=z.countries;for(let[n,o]of Object.entries(e))for(let i of o.timezones??[]){let a=t.get(i)??[];a.push(n),t.set(i,a)}return be=t,t}function Hn(t){let e=t.replace(/_/g,"-").split("-"),n=e.length>=2?e[e.length-1]?.toUpperCase():void 0;return n&&/^[A-Z]{2}$/.test(n)&&n in z.countries?n:void 0}function Vn(t,e){let n=Nn().get(t)??[],o=e.map(Hn).filter(i=>!!i);return n.length===1?n[0]:n.length>1?o.find(i=>n.includes(i))??n[0]:o[0]}function jn(t,e){let n=Vn(t,e),o=z.countries,i=n?o[n]:void 0;if(i?.default)return i.default;let a=t.split("/");return a[0]==="Europe"?"opt-in":a[0]==="America"?"opt-out":"opt-in"}function W(t){let e=t.core?.locale;if(e&&e!=="auto")return e;let n=Fe(t.core?.storage??"cookie");return n||navigator.language}function tn(t){return t.api?.tenantId??t.core?.tenantId??"default"}async function oe(t,e){try{return await le(t,{},e)}catch{return null}}function on(t){let e=t;return{...e.id!==void 0?{id:e.id}:{},...e.defaultLocale!==void 0?{defaultLocale:e.defaultLocale}:{},...e.expiryDays!==void 0?{expiryDays:e.expiryDays}:{},...e.cookies!==void 0?{cookies:e.cookies}:{},...e.mainBanner!==void 0?{mainBanner:e.mainBanner}:{},...e.gpcBanner!==void 0?{gpcBanner:e.gpcBanner}:{},...e.preferenceModal!==void 0?{preferenceModal:e.preferenceModal}:{},...e.darkMode!==void 0?{darkMode:e.darkMode}:{},...e.allowedOrigins!==void 0?{allowedOrigins:e.allowedOrigins}:{},...e.dpdpa!==void 0?{dpdpa:e.dpdpa}:{},...e.complianceGroup!==void 0?{complianceGroup:e.complianceGroup}:{},...e.gpcMode!==void 0?{gpcMode:e.gpcMode}:{},...e.hidePoweredBy!==void 0?{hidePoweredBy:e.hidePoweredBy}:{},...e.allowReceipt!==void 0?{allowReceipt:e.allowReceipt}:{},...e.complianceConfig!==void 0?{complianceConfig:e.complianceConfig}:{},...e.showFooterMetadata!==void 0?{showFooterMetadata:e.showFooterMetadata}:{},...e.enhanceAccessibility!==void 0?{enhanceAccessibility:e.enhanceAccessibility}:{}}}async function an(t,e){let n=zn(t);if(n){if(n.deepMerge){let o=await q(t,e),i=on(n);return o?I(o,i):i}return sn(n,e)}}async function F(t){let{tz:e,lang:n,langs:o}=We(),i=jn(e,o.length?o:[n]),a=W(t),r=await an(i,a);if(r)return r;if(!nn(i,t))throw new Error(`Pre-built profile for complianceGroup "${i}" is disallowed by usePrebuiltProfiles config.`);let c=await q(i,a);if(!c)throw new Error(`No embedded profile found for complianceGroup "${i}".`);return c}async function _e(t,e){let n=W(e),o=await an(t,n);if(o)return o;if(!nn(t,e))throw new Error(`Pre-built profile for complianceGroup "${t}" is disallowed by usePrebuiltProfiles config.`);let i=await q(t,n);if(!i)throw new Error(`No embedded profile found for complianceGroup "${t}".`);return i}async function rn(t){let e=W(t),n=tn(t),o=t.core?.cacheResolvedProfiles!==!1,i=t.api?.baseUrl??window.location.origin,a=t.api?.trustDomain??!1,r=Bn(n,e,o);if(r){let d=await oe(r.filePath,t.api?.authToken);if(d)return we(d,a)?Ce(d):(w.error(`Domain ${window.location.origin} is not in allowedOrigins for this profile. Falling back to pre-built profile.`),F(t))}let c=`${i}/consenti/api/v1/resolve-profile?data=${encodeURIComponent(Je(e))}`,s,l,m;try{let d=await le(c,{},t.api?.authToken);s=d.path,l=d.complianceGroup,m=d.locale,d.found&&s&&On(n,e,o,{filePath:`${i}${s}`,version:0,complianceGroup:l,tenantId:n,locale:m})}catch{return w.warn("resolve-profile failed; falling back to pre-built profile."),F(t)}if(s){let d=await oe(`${i}${s}`,t.api?.authToken);if(d&&we(d,a))return Ce(d)}if(l){let d=await q(l,m);if(d)return d}return F(t)}async function Un(t,e){let n=W(e),o=tn(e),i=e.api?.baseUrl??window.location.origin,a=e.api?.trustDomain??!1,r=e.api?.complianceGroup??t,c=await oe(`${i}/consenti/api/v1/profiles/${o}/${r}/${encodeURIComponent(n)}`,e.api?.authToken)??await oe(`${i}/consenti/api/v1/profiles/${o}/${r}/default`,e.api?.authToken);return c?we(c,a)?Ce(c):(w.error(`Domain ${window.location.origin} is not in allowedOrigins for this profile. Falling back to pre-built profile.`),_e(r,e)):(w.warn(`Could not fetch profile for ${r}; falling back to pre-built.`),_e(r,e))}function sn(t,e,n){let o=t.translations?.[e];if(o===void 0)return e!==t.defaultLocale&&w.warn(`Unable to resolve requested locale "${e}" of profile "${n??t.id}", Using default "${t.defaultLocale}".`),{id:t.id,gpcMode:t.gpcMode??"ignore",allowReceipt:t.allowReceipt??!1,defaultLocale:t.defaultLocale,locales:[t.defaultLocale,...Object.keys(t.translations??{})],cookies:t.cookies??{},...t.expiryDays?{expiryDays:t.expiryDays}:{},mainBanner:t.mainBanner,...t.gpcBanner?{gpcBanner:t.gpcBanner}:{},preferenceModal:t.preferenceModal,...t.darkMode!==void 0?{darkMode:t.darkMode}:{},...t.complianceGroup?{complianceGroup:t.complianceGroup}:{}};let i=I(t.mainBanner,o.mainBanner),a=t.gpcBanner?I(t.gpcBanner,o.gpcBanner??{}):!1,r=I(t.preferenceModal??{},o.preferenceModal??{});return{id:t.id,gpcMode:t.gpcMode??"ignore",allowReceipt:t.allowReceipt??!1,defaultLocale:t.defaultLocale,locales:[t.defaultLocale,...Object.keys(t.translations??{})],cookies:t.cookies??{},...t.expiryDays?{expiryDays:t.expiryDays}:{},mainBanner:i,...a?{gpcBanner:a}:{},preferenceModal:r,...t.darkMode!==void 0?{darkMode:t.darkMode}:{},...t.complianceGroup?{complianceGroup:t.complianceGroup}:{}}}async function Kn(t,e){e.api?.enabled&&w.warn("Local profile used instead of server profile. Check compliance.type config.");let n=W(e),o=Tn(t);if(o){if(o.deepMerge&&o.complianceGroup){let i=await q(o.complianceGroup,n),a=on(o);return i?I(i,a):a}return sn(o,n,t.description)}return w.error(`Unable to resolve locally registered profile "${t.description}", Using compliance.type: 'auto'.`),e.api?.enabled?rn(e):F(e)}async function cn(t){let e=t.compliance?.type??"auto",n=t.api?.enabled??!1;return e==="auto"?n?rn(t):F(t):typeof e=="symbol"?Kn(e,t):n?Un(e,t):_e(e,t)}function T(){let t=Le();return t?"globalPrivacyControl"in t&&t.globalPrivacyControl===!0:!1}function ln(t,e,n,o){if(o==="ignore"||!T())return{...n};let i={...n},a=N(e);for(let[r,c]of Object.entries(t))S(r,a)||c.listenGpc&&(i[r]=pe(r,a)==="legitimate_interest"?"objected":"denied");for(let r of Object.keys(t))S(r,a)&&(i[r]="granted");return i}var pn=4,$n="2.2";function Fn(t,e){let n=new Set,o=new Set;if(t&&e){for(let[i,a]of Object.entries(t.cookies))if(!(e[i]!=="granted"||!a.tcfVendorId)){o.add(a.tcfVendorId);for(let r of a.tcfPurposes??[])n.add(r)}}return{purposeConsents:Array.from(n).sort((i,a)=>i-a),vendorConsents:Array.from(o).sort((i,a)=>i-a)}}function ie(t,e){let n={};for(let o=1;o<=e;o++)n[o]=t.includes(o);return n}function dn(t){if(!p())return;let e=H();if(!e||!t().tcfConfig?.enabled||e.__tcfapi)return;let n=new Map,o=0;function i(l){let{profile:m,consent:d,tcfConfig:y}=t(),f=y??{enabled:!1,cmpId:0,cmpVersion:0},{purposeConsents:u,vendorConsents:v}=Fn(m,d);return{tcString:Me({cmpId:f.cmpId,cmpVersion:f.cmpVersion,consentScreen:1,consentLanguage:"en",vendorListVersion:0,purposeConsents:u,vendorConsents:v}),tcfPolicyVersion:pn,cmpId:f.cmpId,cmpVersion:f.cmpVersion,gdprApplies:!0,eventStatus:d?"tcloaded":"cmpuishown",cmpStatus:"loaded",isServiceSpecific:!0,useNonStandardStacks:!1,publisherCC:"AA",purposeOneTreatment:!1,...l!==void 0?{listenerId:l}:{},purpose:{consents:ie(u,10),legitimateInterests:ie([],10)},vendor:{consents:ie(v,Math.max(0,...v)),legitimateInterests:ie([],0)}}}function a(){for(let[l,m]of n)m(i(l),!0)}function r(l,m,d,y){switch(l){case"ping":{let{consent:f,tcfConfig:u}=t();d({gdprApplies:!0,cmpLoaded:!0,cmpStatus:"loaded",displayStatus:f?"hidden":"visible",apiVersion:$n,cmpVersion:u?.cmpVersion??0,cmpId:u?.cmpId??0,gvlVersion:0,tcfPolicyVersion:pn},!0);return}case"getTCData":d(i(),!0);return;case"addEventListener":{let f=++o;n.set(f,d),d(i(f),!0);return}case"removeEventListener":{let f=typeof y=="number"?y:void 0,u=f!==void 0&&n.delete(f);d(u,u);return}}}let c=e.__tcfapi?.a??[],s=((l,m,d,y)=>{r(l,m,d,y)});s.a=[],e.__tcfapi=s,window.addEventListener("consenti:consentSubmitted",a);for(let[l,m,d,y]of c)r(l,m,d,y)}function ae(t,e,n){let o=document.createElement("button");return o.type="button",t&&(o.id=t),o.className=`consenti-btn consenti-btn--${e.style}`,o.textContent=e.text,o.addEventListener("click",()=>{switch(e.action){case"custom":{let i=e.cookies;i==="*"?n.onGrantAll():i==="!"?n.onDenyAll():Array.isArray(i)&&i.length>0&&n.onGrantSpecific(i);break}case"submit":n.onSubmit();break;case"manage":n.onManage();break;case"close":n.onClose();break}}),o}function O(t,e){let n=document.createElement("div");return n.className="consenti-overlay",n.style.opacity=String(t/100),n.setAttribute("aria-hidden","true"),e&&n.addEventListener("click",e),n}var re=class{constructor(){this.el=null;this.overlayEl=null}build(e,n,o,i,a,r,c=!0,s="main"){let l=document.createElement("div");l.id="consenti-banner",l.className=`consenti-banner consenti-banner--${s} consenti-banner--${e.position}`,l.setAttribute("role","region"),l.setAttribute("aria-label","Cookie consent"),l.setAttribute("aria-live","polite");let m=document.createElement("div");m.className="consenti-banner__text-container";let d=document.createElement("div");if(d.className="consenti-banner__links",e.heading){let g=e.headingTag??"div",b=document.createElement(g);b.id="consenti-banner-heading",b.className="consenti-banner__heading",b.textContent=e.heading,m.appendChild(b)}let y=document.createElement("div");if(y.className="consenti-banner__text",y.innerHTML=e.htmlText,o){let g=document.createElement("a");g.href=o,g.className="consenti-banner__link consenti-banner__link--privacy consenti-btn consenti-btn--text",g.textContent="Privacy Policy",g.setAttribute("target","_blank"),g.setAttribute("rel","noopener noreferrer"),d.appendChild(g)}m.appendChild(y);let f=Object.entries(e.buttons),u=f.filter(g=>g[1].action==="link");if(u.length>0)for(let[,g]of u){if(!g.url)continue;let b=document.createElement("a");b.href=g.url,b.className=`consenti-banner__link consenti-btn consenti-btn--${g.style??"text"}`,b.textContent=g.text,b.setAttribute("target","_blank"),b.setAttribute("rel","noopener noreferrer"),d.appendChild(b)}(u.length>0||o)&&m.appendChild(d),l.appendChild(m);let v=f.filter(([,g])=>g.action!=="link");if(v.length>0){let g=document.createElement("div");g.className="consenti-banner__buttons";for(let[b,x]of v)g.appendChild(ae(b,x,n));l.appendChild(g)}let _=e.showClose,A=e.showLocaleSwitcher&&i&&i.length>0;if(_||A){let g=document.createElement("div");if(g.className="consenti-banner__controls",A&&i&&r&&g.appendChild(qn(i,a,r)),_){let b=document.createElement("button");b.type="button",b.className="consenti-banner__close consenti-btn consenti-transition-1_2x consenti-btn--action",b.setAttribute("aria-label","Close cookie banner"),b.textContent="\xD7",b.addEventListener("click",n.onClose),g.appendChild(b)}l.appendChild(g)}e.overlayOpacity&&e.overlayOpacity>0&&(this.overlayEl=O(e.overlayOpacity));let G=document.createElement("a");return G.href="https://consenti.dev/?utm_source=widget&utm_medium=powered-by&utm_campaign=consenti-ui",G.className="consenti__powered-by",G.textContent="Powered by Consenti \u2197",G.setAttribute("target","_blank"),G.setAttribute("rel","noopener noreferrer"),c&&G.classList.add("consenti-d-none"),l.appendChild(G),this.el=l,l}getOverlay(){return this.overlayEl}show(){this.el&&(this.el.style.display=""),this.overlayEl&&(this.overlayEl.style.display="")}hide(){this.el&&(this.el.style.display="none"),this.overlayEl&&(this.overlayEl.style.display="none")}remove(){this.el?.remove(),this.overlayEl?.remove(),this.el=null,this.overlayEl=null}};function qn(t,e,n){let o=document.createElement("div");o.className="consenti-locale-switcher";let i=document.createElement("button");i.type="button",i.className="consenti-locale-switcher__btn consenti-btn consenti-transition-1_2x consenti-btn--action",i.setAttribute("aria-label","Switch language"),i.setAttribute("aria-haspopup","listbox"),i.textContent="\u{1F310}";let a=document.createElement("ul");a.className="consenti-locale-switcher__list",a.setAttribute("role","listbox"),a.setAttribute("aria-label","Select language"),a.hidden=!0;for(let r of t){let c=document.createElement("li");c.setAttribute("role","option"),c.setAttribute("aria-selected",String(r===e));let s=document.createElement("button");s.type="button",s.className=`consenti-locale-switcher__option${r===e?" consenti-locale-switcher__option--active":""}`,s.textContent=r,s.addEventListener("click",l=>{l.stopPropagation(),a.hidden=!0,r!==e&&n(r)}),c.appendChild(s),a.appendChild(c)}return i.addEventListener("click",r=>{r.stopPropagation(),a.hidden=!a.hidden}),document.addEventListener("click",()=>{a.hidden=!0},{capture:!0,once:!1}),o.appendChild(i),o.appendChild(a),o}var Wn=["a[href]","button:not([disabled])","input:not([disabled])","select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])','[contenteditable="true"]'].join(", ");function un(t){let e=()=>Array.from(t.querySelectorAll(Wn)).filter(i=>!i.closest('[aria-hidden="true"]')),n=i=>{if(i.key!=="Tab")return;let a=e();if(a.length===0)return;let r=a[0],c=a[a.length-1];i.shiftKey?(document.activeElement===r||document.activeElement===t)&&(i.preventDefault(),c.focus()):document.activeElement===c&&(i.preventDefault(),r.focus())},o=i=>{i.key==="Escape"&&t.dispatchEvent(new CustomEvent("consenti:closeRequest",{bubbles:!0}))};return document.addEventListener("keydown",n),document.addEventListener("keydown",o),e()[0]?.focus(),()=>{document.removeEventListener("keydown",n),document.removeEventListener("keydown",o)}}var se=class t{constructor(){this.el=null;this.triggerEl=null;this.trapCleanup=null;this.toggleState=new Map;this.paramToggles=new Map;this.categoryToggles=new Map;this.categoryDisclosures=new Map;this.receiptCheckbox=null;this.passthrough=!1;this.mobileBreakpoint=576;this.savedBodyOverflow=void 0}build(e,n,o,i,a,r,c,s,l,m=!0){this.mobileBreakpoint=e.mobileFullScreenBreakpoint??576;let d=e.position??"center",y=document.createElement("div");y.id="consenti-modal",y.className=`consenti-modal consenti-modal--${d}`,y.setAttribute("role","dialog"),y.setAttribute("aria-modal","true");let f=e.overlayOpacity??50;if(this.passthrough=f===0,f>0){let h=e.persistent?void 0:()=>i.onClose(),C=O(f,h);C.className="consenti-modal__overlay",y.appendChild(C)}else y.classList.add("consenti-modal--passthrough");let u=document.createElement("div");u.className="consenti-modal__container";let v=document.createElement("div");v.className="consenti-modal__header";let _=document.createElement("div");if(_.className="consenti-modal__body",e.heading){let h=e.headingTag??"div",C=document.createElement(h);C.id="consenti-modal-heading",C.className="consenti-modal__heading",C.textContent=e.heading,v.appendChild(C),y.setAttribute("aria-labelledby","consenti-modal-heading")}if(e.subheading){let h=document.createElement("p");h.className="consenti-modal__subheading",h.textContent=e.subheading,v.appendChild(h)}let A=e.showClose!==!1,G=e.showLocaleSwitcher&&c&&c.length>0;if(G||A){let h=document.createElement("div");if(h.className="consenti-modal__header-controls",G&&c&&l&&h.appendChild(Jn(c,s,l)),A){let C=document.createElement("button");C.type="button",C.className="consenti-modal__close consenti-transition-1_2x consenti-btn consenti-btn--action",C.setAttribute("aria-label","Close preference modal"),C.textContent="\xD7",C.addEventListener("click",i.onClose),h.appendChild(C)}v.appendChild(h)}if(u.appendChild(v),e.htmlText){let h=document.createElement("div");h.className="consenti-modal__text",h.innerHTML=e.htmlText,_.appendChild(h)}let g=Object.entries(e.buttons),b=g.filter(h=>h[1].action==="link");if(b.length>0){let h=document.createElement("div");h.className="consenti-modal__links";for(let[,C]of b){if(!C.url)continue;let P=document.createElement("a");P.href=C.url,P.className=`consenti-modal__link consenti-btn consenti-btn--${C.style??"text"}`,P.textContent=C.text,P.setAttribute("target","_blank"),P.setAttribute("rel","noopener noreferrer"),h.appendChild(P)}v.appendChild(h)}let x=document.createElement("div");x.className="consenti-modal__categories";for(let[h,C]of Object.entries(e.categories)){let P=this.buildCategory(h,C,n,o);x.appendChild(P)}if(_.appendChild(x),u.appendChild(_),a&&_.appendChild(this.buildReceiptOption(e.receiptLabel,e.receiptDescription)),r){let h=document.createElement("div");h.className="consenti-modal__dpdpa-notice";let C=gn=>gn.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;"),P=r.grievanceEmail.includes("@")&&!r.grievanceEmail.toLowerCase().startsWith("javascript:")?r.grievanceEmail:"";h.innerHTML=[`<strong>Data Fiduciary:</strong> ${C(r.dataFiduciary)}.`,r.purposeDescription?` <strong>Purpose:</strong> ${C(r.purposeDescription)}.`:""," You may withdraw consent at any time or contact our Grievance Officer at",P?` <a href="mailto:${C(P)}">${C(P)}</a>.`:"."].join(""),u.appendChild(h)}let L=document.createElement("div");L.className="consenti-modal__buttons";for(let[h,C]of g.filter(([,P])=>P.action!=="link"))L.appendChild(ae(h,C,i));u.appendChild(L);let D=document.createElement("a");D.href="https://consenti.dev/?utm_source=widget&utm_medium=powered-by&utm_campaign=consenti-ui",D.className="consenti__powered-by",D.textContent="Powered by Consenti \u2197",D.setAttribute("target","_blank"),D.setAttribute("rel","noopener noreferrer"),m&&D.classList.add("consenti-d-none"),_.appendChild(D),y.appendChild(u),y.addEventListener("consenti:closeRequest",()=>i.onClose()),this.el=y}static appendToggleIcons(e,n){n&&e.classList.add("consenti-toggle--li");let o=document.createElement("span");o.className="consenti-category__toggle-icon consenti-category__toggle-icon--check",o.setAttribute("aria-hidden","true"),o.innerHTML='<svg viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1.5 5L4 7.5L8.5 2.5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',e.appendChild(o);let i=document.createElement("span");if(i.className="consenti-category__toggle-icon consenti-category__toggle-icon--cross",i.setAttribute("aria-hidden","true"),i.innerHTML='<svg viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2 2L8 8M8 2L2 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>',e.appendChild(i),n){let r=document.createElement("span");r.className="consenti-category__toggle-icon consenti-category__toggle-icon--objected",r.setAttribute("aria-hidden","true"),r.innerHTML='<svg viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="5" cy="5" r="3.5" stroke="currentColor" stroke-width="1.4"/><path d="M2.5 2.5L7.5 7.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>',e.appendChild(r)}let a=document.createElement("span");a.className="consenti-category__toggle-knob",a.setAttribute("aria-hidden","true"),e.appendChild(a)}computeRollup(e,n){let o=n?"objected":"denied",i=e.map(a=>this.toggleState.get(a));return i.every(a=>a==="granted")?"allowed":i.every(a=>a===o||a===void 0)?"denied":"mixed"}updateCategoryToggleVisual(e){let n=this.categoryToggles.get(e);if(!n)return;let o=this.computeRollup(n.cookieIds,n.isLI),i=o==="allowed"?"true":o==="denied"?"false":"mixed";n.el.setAttribute("aria-checked",i),n.el.classList.toggle("consenti-category__toggle--partial",o==="mixed")}buildParamRow(e,n,o,i,a){let r=document.createElement("div");r.className="consenti-category__param";let c=document.createElement("div");c.className="consenti-category__param-info";let s=document.createElement("span");s.className="consenti-category__param-label",s.textContent=n,c.appendChild(s);let l=document.createElement("span");l.className="consenti-category__param-meta";let m=[];o?.purpose&&m.push(o.purpose),o?.cpraCategory&&m.push(`CPRA: ${o.cpraCategory}`),o?.tcfVendorId!=null&&m.push(`TCF Vendor #${o.tcfVendorId}`),l.textContent=m.join(" \xB7 "),m.length&&c.appendChild(l),r.appendChild(c);let d=a?"objected":"denied",f=(this.toggleState.get(n)??d)==="granted",u=document.createElement("button");return u.type="button",u.className=`consenti-category__param-toggle${i?" consenti-category__param-toggle--mandatory":""}`,u.setAttribute("role","switch"),u.setAttribute("aria-checked",String(f)),u.setAttribute("aria-label",n),i&&(u.setAttribute("aria-disabled","true"),u.disabled=!0),t.appendToggleIcons(u,a),i||u.addEventListener("click",()=>{let v=this.toggleState.get(n)==="granted"?d:"granted";this.toggleState.set(n,v),u.setAttribute("aria-checked",String(v==="granted")),this.updateCategoryToggleVisual(e)}),this.paramToggles.set(n,u),r.appendChild(u),r}buildCategory(e,n,o,i){let a=n.legalBasis==="legitimate_interest",r=n.legalBasis==="mandatory",c=a?"objected":"denied";for(let g of n.cookies){let b=i[g],x;b!==void 0?x=b:r||a?x="granted":x=o[g]?.preGrant?"granted":"denied",this.toggleState.set(g,x)}let s=document.createElement("div");s.className=`consenti-category${r?" consenti-category--mandatory":""}`;let l=document.createElement("div");l.className="consenti-category__header";let m=n.headingTag??"div",d=document.createElement(m);d.id=`consenti-cat-${e}-heading`,d.className="consenti-category__heading",d.textContent=n.heading,l.appendChild(d);let y=this.computeRollup(n.cookies,a),f=document.createElement("button");if(f.type="button",f.className=`consenti-category__toggle${r?" consenti-category__toggle--mandatory":""}${y==="mixed"?" consenti-category__toggle--partial":""}`,f.setAttribute("role","checkbox"),f.setAttribute("aria-checked",y==="allowed"?"true":y==="denied"?"false":"mixed"),f.setAttribute("aria-labelledby",`consenti-cat-${e}-heading`),r&&(f.setAttribute("aria-disabled","true"),f.disabled=!0),t.appendToggleIcons(f,a),this.categoryToggles.set(e,{el:f,cookieIds:n.cookies,isLI:a}),r||f.addEventListener("click",()=>{let b=this.computeRollup(n.cookies,a)==="allowed"?c:"granted";for(let x of n.cookies){this.toggleState.set(x,b);let L=this.paramToggles.get(x);L&&!L.disabled&&L.setAttribute("aria-checked",String(b==="granted"))}this.updateCategoryToggleVisual(e)}),l.appendChild(f),s.appendChild(l),a&&n.legitimateInterestDescription){let g=document.createElement("p");g.className="consenti-category__li-desc",g.textContent=n.legitimateInterestDescription,s.appendChild(g)}let u=document.createElement("div");u.className="consenti-category__text-wrap";let v=document.createElement("div");v.className="consenti-category__text consenti-category__text--clamped",v.innerHTML=n.htmlText,u.appendChild(v);let _=document.createElement("button");_.type="button",_.className="consenti-category__show-more consenti-d-none",_.setAttribute("aria-expanded","false"),_.textContent="Show more",u.appendChild(_),s.appendChild(u);let A=document.createElement("div");A.className="consenti-category__params";for(let g of n.cookies)A.appendChild(this.buildParamRow(e,g,o[g],r,a));let G=n.cookies.length>1;return A.classList.add("consenti-d-none"),s.appendChild(A),_.addEventListener("click",()=>{let g=!v.classList.toggle("consenti-category__text--clamped");G&&A.classList.toggle("consenti-d-none",!g),_.setAttribute("aria-expanded",String(g)),_.textContent=g?"Show less":"Show more"}),this.categoryDisclosures.set(e,{text:v,toggle:_,params:G?A:null}),s}buildReceiptOption(e,n){let o=document.createElement("div");o.className="consenti-modal__receipt-option";let i=document.createElement("label");i.className="consenti-modal__receipt-label";let a=document.createElement("input");a.type="checkbox",a.className="consenti-modal__receipt-checkbox",a.id="consenti-receipt-checkbox",a.setAttribute("aria-describedby","consenti-receipt-desc"),this.receiptCheckbox=a,i.appendChild(a),i.append(" "+(e??"Get a copy of my consent choices (JSON)")),o.appendChild(i);let r=document.createElement("p");return r.id="consenti-receipt-desc",r.className="consenti-modal__receipt-desc",r.textContent=n??"A JSON file will be downloaded to your device when you save your preferences.",o.appendChild(r),o}open(e,n){if(!this.el)return;this.triggerEl=e??document.activeElement,this.receiptCheckbox&&(this.receiptCheckbox.checked=!1);let o=this.mobileBreakpoint>0&&window.innerWidth<=this.mobileBreakpoint;o?this.el.classList.add("consenti-modal--fullscreen"):this.el.classList.remove("consenti-modal--fullscreen"),(n??document.body).appendChild(this.el),(!this.passthrough||o)&&(this.savedBodyOverflow=document.body.style.overflow,document.body.style.overflow="hidden"),this.passthrough||(this.trapCleanup=un(this.el)),this.resetCategoryDisclosures()}resetCategoryDisclosures(){for(let{text:e,toggle:n,params:o}of this.categoryDisclosures.values()){e.classList.add("consenti-category__text--clamped"),o?.classList.add("consenti-d-none"),n.setAttribute("aria-expanded","false"),n.textContent="Show more";let i=e.scrollHeight>e.clientHeight+1;n.classList.toggle("consenti-d-none",!(i||o!==null))}}close(){this.trapCleanup?.(),this.trapCleanup=null,this.savedBodyOverflow!==void 0&&(document.body.style.overflow=this.savedBodyOverflow,this.savedBodyOverflow=void 0),this.el?.remove(),this.triggerEl?.focus(),this.triggerEl=null}isOpen(){return this.el?.isConnected===!0}isReceiptRequested(){return this.receiptCheckbox?.checked??!1}getToggleConsent(e){let n={};for(let o of Object.values(e)){let i=o.legalBasis==="legitimate_interest",a=o.legalBasis==="mandatory",r=a?"granted":i?"objected":"denied";for(let c of o.cookies)n[c]=a?"granted":this.toggleState.get(c)??r}return n}destroy(){this.trapCleanup?.(),this.savedBodyOverflow!==void 0&&(document.body.style.overflow=this.savedBodyOverflow,this.savedBodyOverflow=void 0),this.el?.remove(),this.el=null,this.paramToggles.clear(),this.categoryToggles.clear(),this.categoryDisclosures.clear(),this.toggleState.clear(),this.receiptCheckbox=null}};function Jn(t,e,n){let o=document.createElement("div");o.className="consenti-locale-switcher consenti-locale-switcher--modal";let i=document.createElement("button");i.type="button",i.className="consenti-locale-switcher__btn consenti-btn consenti-btn--action consenti-transition-1_2x",i.setAttribute("aria-label","Switch language"),i.setAttribute("aria-haspopup","listbox"),i.textContent="\u{1F310}";let a=document.createElement("ul");a.className="consenti-locale-switcher__list",a.setAttribute("role","listbox"),a.setAttribute("aria-label","Select language"),a.hidden=!0;for(let r of t){let c=document.createElement("li");c.setAttribute("role","option"),c.setAttribute("aria-selected",String(r===e));let s=document.createElement("button");s.type="button",s.className=`consenti-locale-switcher__option${r===e?" consenti-locale-switcher__option--active":""}`,s.textContent=r,s.addEventListener("click",l=>{l.stopPropagation(),a.hidden=!0,r!==e&&n(r)}),c.appendChild(s),a.appendChild(c)}return i.addEventListener("click",r=>{r.stopPropagation(),a.hidden=!a.hidden}),document.addEventListener("click",()=>{a.hidden=!0},{capture:!0,once:!1}),o.appendChild(i),o.appendChild(a),o}var J=class{constructor(){this.el=null;this.overlayEl=null}buildPrompt(e,n,o){let i=document.createElement("div");i.id="consenti-age-gate",i.className="consenti-age-gate",i.setAttribute("role","dialog"),i.setAttribute("aria-modal","true"),i.setAttribute("aria-label","Age verification");let a=document.createElement("div");a.className="consenti-age-gate__heading",a.textContent=`Are you ${e} or older?`,i.appendChild(a);let r=document.createElement("div");r.className="consenti-age-gate__text",r.textContent="Please confirm your age before we show you cookie preferences.",i.appendChild(r);let c=document.createElement("div");c.className="consenti-age-gate__buttons";let s=document.createElement("button");s.type="button",s.className="consenti-btn consenti-btn--primary",s.textContent="Yes",s.addEventListener("click",n),c.appendChild(s);let l=document.createElement("button");return l.type="button",l.className="consenti-btn consenti-btn--secondary",l.textContent="No",l.addEventListener("click",o),c.appendChild(l),i.appendChild(c),this.overlayEl=O(60),this.el=i,i}buildParentalConsentMessage(e){let n=document.createElement("div");n.id="consenti-age-gate",n.className="consenti-age-gate",n.setAttribute("role","dialog"),n.setAttribute("aria-modal","true"),n.setAttribute("aria-label","Parental consent required");let o=document.createElement("div");o.className="consenti-age-gate__heading",o.textContent="Parental consent required",n.appendChild(o);let i=document.createElement("div");i.className="consenti-age-gate__text",i.textContent="Only strictly necessary cookies will be used until a parent or guardian verifies consent on your behalf.",n.appendChild(i);let a=document.createElement("div");a.className="consenti-age-gate__buttons";let r=document.createElement("button");return r.type="button",r.className="consenti-btn consenti-btn--primary",r.textContent="OK",r.addEventListener("click",e),a.appendChild(r),n.appendChild(a),this.el=n,n}getOverlay(){return this.overlayEl}remove(){this.el?.remove(),this.overlayEl?.remove(),this.el=null,this.overlayEl=null}};var mn="consenti-root",ce=class{constructor(){this.root=null;this.styleEl=null}mount(e){if(!p())throw new Error("[Consenti] Renderer.mount() called in SSR context");if(this.root)return this.root;if(e!==void 0){if(typeof e=="string"){let o=document.querySelector(e);if(!o)throw new Error(`[Consenti] rootEl selector "${e}" did not match any element.`);this.root=o}else{if(!document.contains(e))throw new Error("[Consenti] rootEl element is not attached to the document.");this.root=e}return this.root.classList.add("consenti-root"),this.root}let n=document.getElementById(mn);return n||(n=document.createElement("div"),n.id=mn,document.body.appendChild(n)),n.classList.add("consenti-root"),this.root=n,n}injectStyles(e){if(!p()||this.styleEl)return;let n=document.createElement("style");n.id="consenti-styles",n.textContent=e,document.head.appendChild(n),this.styleEl=n}unmount(){this.root?.remove(),this.styleEl?.remove(),this.root=null,this.styleEl=null}announceToScreenReader(e){if(!p())return;let n=document.getElementById("consenti-announcer");n||(n=document.createElement("div"),n.id="consenti-announcer",n.setAttribute("role","status"),n.setAttribute("aria-live","polite"),n.setAttribute("aria-atomic","true"),n.style.cssText="position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;",document.body.appendChild(n)),n.textContent="",requestAnimationFrame(()=>{n&&(n.textContent=e)})}};var Ae=class{constructor(e){this.initialized=!1;this.initializing=!1;this.profile=null;this.resolvedBase=null;this.categoryIndex=new Map;this.scannedScripts=[];this.consentStore=null;this.eventBus=null;this.banner=null;this.modal=null;this.ageGate=null;this.renderer=null;this._ageVerified=void 0;this._parentalConsentToken=void 0;this.broadcastChannel=new Q;this.plugins=[];this._bannerVariant=null;this._bannerMounted=null;this._focusTrapListener=null;this._eventHandlers=new Map;this.config={...e,core:e.core??{}},this.readyPromise=new Promise(n=>{this.readyResolve=n}),p()&&(e.verbose?de(["error","info","warning","log"]):de(this.config.core.console??["error"]),e.api?.enabled&&this.config.core.storage==="localStorage"&&(w.warn("localStorage storage mode cannot be used with API mode (API sets cookies via Set-Cookie header). Falling back to cookie storage."),this.config.core.storage="cookie"),e.autoInit!==!1&&this.init().catch(n=>w.warn("Init error:",n)))}visitorId(){return Ue()}profileId(){return this.profile?.id??"0"}async init(){if(p()&&!this.initializing){this.initializing=!0,this.initialized=!1,this.readyPromise=new Promise(e=>{this.readyResolve=e});try{let e=this.config.core.storage??"cookie",n=await cn(this.config);this.resolvedBase=n,this.config.darkMode!==void 0&&(n.darkMode=this.config.darkMode);let o=this.config.profileOverride?I(n,this.config.profileOverride):n,i=T();o.gpcMode=o.gpcMode??"ignore",this.profile=o,this.categoryIndex=N(o.preferenceModal.categories),this.eventBus=new te(this.profileId(),this.config.utils?.gtm),this.consentStore=new ne(this.profileId(),e,this.config.core.cookieDomains,this.config.core.userId),this.consentStore.setProfileExpiry(o.expiryDays??365),this.consentStore.setProfileVersion(o.version??0),dn(()=>({profile:this.profile,consent:this.getConsent(),tcfConfig:this.config.compliance?.tcf})),this.renderer=new ce,!this.config.core.disableCssTemplate&&ge&&this.renderer.injectStyles(ge),this.banner=new re,this.modal=new se,this.applyTheme(),this.applyDarkMode(),this.applyDirection(),this.applyAccessibility(),this.applyStackButtonsBreakpoint(),this.broadcastChannel.connect(l=>this.handleBroadcastMessage(l)),await this.runPlugins();let a=o.cookieSigningKey??this.config.core.cookieSigningKey,c=(a?await this.consentStore.readAndVerify(a):this.consentStore.read())!==null,s=this.config.compliance?.ageGate;if(s?.enabled&&!c){this.showAgeGate(s,o);return}await this.continueInit(o,i,c)}finally{this.initializing=!1}}}async continueInit(e,n,o){if(!this.consentStore||!this.eventBus)return;if(this.eventBus.dispatch("consenti:bannerInitialized",{profileId:this.profileId(),hasExistingConsent:o,complianceGroup:e.complianceGroup??"auto",gpcDetected:n,willShow:!o&&!(n&&e.gpcMode==="strict")}),e.gpcMode!=="ignore"&&n){let c=ln(e.cookies,e.preferenceModal.categories,{},e.gpcMode);if(e.gpcMode==="strict"){await this.submitConsent(c,!0),this.finishInit();return}if(!o){let s=e.cookieSigningKey??this.config.core.cookieSigningKey,l={gpcDetected:!0,locale:this.config.core.locale??"en"};s?this.lastSignature=await this.consentStore.writeAndSign(c,s,l):this.consentStore.write(c,l),this.broadcastChannel.send({type:"consentUpdated",consent:c,profileId:this.profileId()}),this.eventBus.dispatch("consenti:consentSubmitted",{consentJson:c,gpcDetected:!0,consentId:E("cons"),consentAction:this.deriveAction(c,!1),pageUrl:window.location.href,visitorId:this.visitorId(),timestamp:Math.round(Date.now()/1e3),profileId:this.profileId()}),this.showBanner(!0),this.finishInit();return}}let i=e.complianceGroup??"",a=i==="opt-out",r=i==="opt-out-strict";if(this.getRootElement()?.setAttribute("data-consenti-cg",i),this.config.verbose&&this.getRootElement()?.setAttribute("data-consenti-gpc",e.gpcMode??""),a&&!o){let c={};for(let m of Object.keys(e.cookies))c[m]="granted";let s=e.cookieSigningKey??this.config.core.cookieSigningKey,l={locale:this.config.core.locale??"en"};s?await this.consentStore.writeAndSign(c,s,l):this.consentStore.write(c,l),this.finishInit();return}if(r&&!o){let c={};for(let[m,d]of Object.entries(e.cookies)){if(S(m,this.categoryIndex)){c[m]="granted";continue}d.cpraCategory==="sensitive"||n&&(d.cpraCategory==="sale"||d.cpraCategory==="sharing")?c[m]="denied":c[m]="granted"}let s=e.cookieSigningKey??this.config.core.cookieSigningKey,l={locale:this.config.core.locale??"en"};s?await this.consentStore.writeAndSign(c,s,l):this.consentStore.write(c,l),this.finishInit();return}o||this.showBanner(),this.finishInit()}showAgeGate(e,n){if(!this.renderer)return;let o=e.minimumAge,i=this.renderer.mount(this.config.rootEl);this.ageGate=new J;let a=this.ageGate.buildPrompt(o,()=>{this.resolveAgeGate(!0,e,n)},()=>{this.resolveAgeGate(!1,e,n)}),r=this.ageGate.getOverlay();r&&i.appendChild(r),i.appendChild(a)}async resolveAgeGate(e,n,o){if(this.ageGate?.remove(),this.ageGate=null,this._ageVerified=e,e){await this.continueInit(o,T(),!1);return}if(n.requireParentalConsent){if(this._parentalConsentToken=E("pcon"),await this.submitConsent(this._buildDenyAllConsent()),this.eventBus?.dispatch("consenti:parentalConsentRequired",{parentalConsentToken:this._parentalConsentToken,profileId:this.profileId(),visitorId:this.visitorId(),timestamp:Math.round(Date.now()/1e3)}),this.renderer){let i=this.renderer.mount(this.config.rootEl);this.ageGate=new J;let a=this.ageGate.buildParentalConsentMessage(()=>{this.ageGate?.remove(),this.ageGate=null});i.appendChild(a)}}else await this.submitConsent(this._buildDenyAllConsent());this.finishInit()}finishInit(){this.initialized=!0,this.readyResolve(),this.scannedScripts.push(...me(this))}applyDirection(){if(!this.renderer)return;this.renderer.mount(this.config.rootEl).setAttribute("dir",Ie(this.config.core.locale??"en",this.config.core.dir))}applyDarkMode(){if(!this.renderer)return;let e=this.config.darkMode??this.profile?.darkMode??!1,n=this.renderer.mount(this.config.rootEl);e?n.classList.add("consenti-root--dark"):n.classList.remove("consenti-root--dark")}applyAccessibility(){if(!this.renderer)return;let e=this.renderer.mount(this.config.rootEl);this.profile?.enhanceAccessibility?e.classList.add("consenti--enhanced-a11y"):e.classList.remove("consenti--enhanced-a11y")}_appendMetaFooter(e,n=!1){let o=document.createElement("div");o.className="consenti-meta-footer";let i=this.consentStore?.getConsentId();if(i){let r=document.createElement("span");r.className="consenti-meta-footer__item",r.innerHTML=`ID: <code class="consenti-meta-footer__id">${i.slice(0,8)}&hellip;</code>`,o.appendChild(r)}let a=this.consentStore?.getConsentDate();if(a){let r=document.createElement("span");r.className="consenti-meta-footer__item",r.textContent=`Date: ${a.toISOString().slice(0,10)}`,o.appendChild(r)}if(this.profile?.id){let r=document.createElement("span");r.className="consenti-meta-footer__item",r.textContent=`v${this.profile.id}`,o.appendChild(r)}if(!n){let r=document.createElement("button");r.type="button",r.className="consenti-meta-footer__settings",r.textContent="Privacy Settings",r.addEventListener("click",()=>this.showModal()),o.appendChild(r)}e.appendChild(o)}applyStackButtonsBreakpoint(){if(!this.renderer||!this.profile)return;let e=this.profile.mainBanner?.stackButtonsOnBreakpoint;if(!e||e<=0)return;let n=this.renderer.mount(this.config.rootEl);n.querySelector("#consenti-stack-btn-style")?.remove();let i=document.createElement("style");i.id="consenti-stack-btn-style",i.textContent=`@media(max-width:${e}px){.consenti-banner__buttons{flex-direction:column;}.consenti-banner__btn{width:100%;}}`,n.appendChild(i)}_installFocusTrap(e){this._removeFocusTrap();let n='button:not([disabled]),[href],input:not([disabled]),select,textarea,[tabindex]:not([tabindex="-1"])',o=i=>{if(i.key!=="Tab")return;let a=Array.from(e.querySelectorAll(n)).filter(s=>!s.closest('[aria-hidden="true"]'));if(a.length===0)return;let r=a[0],c=a[a.length-1];i.shiftKey?document.activeElement===r&&(i.preventDefault(),c.focus()):document.activeElement===c&&(i.preventDefault(),r.focus())};this._focusTrapListener=o,document.addEventListener("keydown",o)}_removeFocusTrap(){this._focusTrapListener&&(document.removeEventListener("keydown",this._focusTrapListener),this._focusTrapListener=null)}applyTheme(){let e=this.config.core.theme;if(!e||!this.renderer)return;let n={"--consenti-color-bg":e.bgColor,"--consenti-color-text":e.textColor,"--consenti-color-primary":e.primaryColor,"--consenti-color-primary-text":e.primaryTextColor,"--consenti-color-secondary":e.secondaryColor,"--consenti-color-secondary-text":e.secondaryTextColor,"--consenti-color-border":e.borderColor,"--consenti-font-family":e.fontFamily,"--consenti-font-size-base":e.fontSizeBase,"--consenti-font-size-heading":e.fontSizeHeading,"--consenti-border-radius":e.borderRadius,"--consenti-border-radius-btn":e.buttonBorderRadius,"--consenti-toggle-bg-on":e.toggleBgOn,"--consenti-toggle-bg-off":e.toggleBgOff,"--consenti-color-accent":e.accentColor,"--consenti-color-accent-text":e.accentTextColor},o=this.renderer.mount(this.config.rootEl);for(let[i,a]of Object.entries(n))a!==void 0&&o.style.setProperty(i,a);if(e.fontSizeMultiplier){let i=parseFloat(e.fontSizeMultiplier);if(!isNaN(i)){let a=parseFloat(getComputedStyle(o).getPropertyValue("--consenti-font-size-base")||"14"),r=parseFloat(getComputedStyle(o).getPropertyValue("--consenti-font-size-heading")||"16");o.style.setProperty("--consenti-font-size-base",`${Math.round(a*i)}px`),o.style.setProperty("--consenti-font-size-heading",`${Math.round(r*i)}px`)}}}async runPlugins(){this.plugins=this.config.plugins??[];for(let e of this.plugins)try{await e.initialize(this)}catch(n){w.warn("Plugin initialize error:",n)}}async callPluginHook(e){for(let n of this.plugins)try{await e(n,this)}catch(o){w.warn("Plugin hook failed:",o)}}_buildGrantAllConsent(e=!1){let n={};for(let o of Object.keys(this.profile?.cookies??{}))n[o]=e&&!S(o,this.categoryIndex)?"denied":"granted";return n}_buildDenyAllConsent(e=!1){let n={};for(let o of Object.keys(this.profile?.cookies??{}))n[o]=!e&&S(o,this.categoryIndex)?"granted":"denied";return n}buildHandlers(e){return{onGrantAll:()=>{this.submitConsent(this._buildGrantAllConsent()),e==="banner"&&(this.banner?.hide(),this._bannerVariant=null)},onDenyAll:()=>{this.submitConsent(this._buildDenyAllConsent()),e==="banner"&&(this.banner?.hide(),this._bannerVariant=null)},onSubmit:()=>{if(e==="modal"&&this.modal&&this.profile){let n=this.modal.getToggleConsent(this.profile.preferenceModal.categories);this.submitConsent(n),this.hideModal()}else{let n={};for(let o of Object.keys(this.profile?.cookies??{}))S(o,this.categoryIndex)&&(n[o]="granted");this.submitConsent(n),e==="banner"&&(this.banner?.hide(),this._bannerVariant=null)}},onManage:()=>{this.showModal()},onClose:()=>{e==="modal"?this.hideModal():(this.banner?.hide(),this.eventBus?.dispatch("consenti:bannerVisibility",{visible:!1,action:!0,variant:this._bannerVariant??"main"}),this._bannerVariant=null,this.broadcastChannel.send({type:"bannerClosed"}),this.callPluginHook(n=>n.onBannerHide?.()))},onGrantSpecific:n=>{let o={};for(let i of Object.keys(this.profile?.cookies??{}))S(i,this.categoryIndex)||n.includes(i)?o[i]="granted":o[i]="denied";this.submitConsent(o),e==="banner"&&this.hideBanner()}}}deriveAction(e,n){if(n)return"update";let o=Object.keys(this.profile?.cookies??{}).filter(r=>!S(r,this.categoryIndex));if(o.length===0)return"custom";let i=o.every(r=>e[r]==="granted"),a=o.every(r=>e[r]==="denied"||e[r]==="objected");return i?"accept_all":a?"reject_all":"custom"}handleBroadcastMessage(e){switch(e.type){case"consentUpdated":if(e.profileId!==this.profileId())return;this.consentStore?.setInMemory(e.consent),this.banner?.hide(),this.modal?.isOpen()&&this.modal.close(),this.eventBus?.dispatch("consenti:consentSubmitted",{consentJson:e.consent,fromBroadcast:!0,gpcDetected:T(),consentId:E("cons"),consentAction:this.deriveAction(e.consent,!1),pageUrl:window.location.href,visitorId:this.visitorId(),timestamp:Math.round(Date.now()/1e3),profileId:this.profileId()});break;case"bannerClosed":this.banner?.hide(),this._bannerVariant=null;break;case"consentDeleted":this.consentStore?.clearInMemory();break}}hasConsent(){return p()?this.consentStore?.hasConsent()??!1:!1}getConsent(e){if(!p())return null;let n=this.consentStore?.getConsent()??null;if(!n)return null;let o=this.profile?.cookies??{};switch(e){case"google-gtm":return K(n);case"category":return ze(n,o);case"adobe":return Re(n,o);case"meta":return Be(n,o);case"microsoft-clarity":return Oe(n,o);case"twilio-segment":return Ne(n,o);default:return n}}getConsentDate(){return p()?this.consentStore?.getConsentDate()??!1:!1}getGTMConsent(){return this.getConsent("google-gtm")}async submitConsent(e,n=!1,o=0){if(!p()||!this.profile||!this.consentStore)return;let i=this.profile.complianceGroup??"",a=this.consentStore.hasConsent(),r=this.consentStore.getConsentId()??E("cons"),c=window.location.href,s={};for(let[u,v]of Object.entries(this.profile.cookies))if(S(u,this.categoryIndex))s[u]="granted";else if(i==="opt-out-strict"){let _=v.cpraCategory==="sensitive"?"denied":"granted";s[u]=e[u]??_}else{let _=i!=="opt-out"&&i!=="notice-only",A=v.preGrant?"granted":_?"denied":"granted";s[u]=e[u]??A}let l=this.deriveAction(s,a);this.eventBus?.dispatch("consenti:consentBeingSubmitted",{consentJson:s,consentId:r,fromBroadcast:!1,gpcDetected:T(),consentAction:this.deriveAction(s,!1),pageUrl:window.location.href,visitorId:this.visitorId(),timestamp:Math.round(Date.now()/1e3),profileId:this.profileId()});let m=null;if(this.config.api?.enabled)try{let u=this.config.api.baseUrl??window.location.origin,{httpRequest:v}=await import("./http-AQVVUCX6.mjs");m=await v(`${u}/consenti/api/v1/consent`,{method:"POST",body:JSON.stringify({profileId:this.profileId(),visitorId:this.visitorId(),consentJson:s,locale:this.config.core.locale??"en",gpcDetected:n||T(),consentId:r,action:l,pageUrl:c,...this._ageVerified!==void 0?{ageVerified:this._ageVerified}:{},...this._parentalConsentToken?{parentalConsentToken:this._parentalConsentToken}:{}})},this.config.api.authToken)}catch(u){w.warn("API consent submission failed:",u)}let d=this.profile.cookieSigningKey??this.config.core.cookieSigningKey,y={source:o,gpcDetected:n,consentId:r,locale:this.config.core.locale??"en"};if(d?this.lastSignature=await this.consentStore.writeAndSign(s,d,y):this.consentStore.write(s,y),(this.modal?.isReceiptRequested()??!1)&&this.profile.allowReceipt){let u=this.visitorId(),v=this.consentStore.getConsent();console.log({currentConsentVersion:v});let _={version:"1.0",visitorId:u,profileId:this.profileId(),locale:this.config.core.locale??"en",consent:s,...this.lastSignature?{signature:this.lastSignature}:{}},A=He(_);Ve(A)}return this.broadcastChannel.send({type:"consentUpdated",consent:s,profileId:this.profileId()}),this.eventBus?.dispatch("consenti:consentSubmitted",{consentJson:s,apiResponse:m,gpcDetected:n,consentId:r,consentAction:l,pageUrl:c,fromBroadcast:!1,visitorId:this.visitorId(),timestamp:Math.round(Date.now()/1e3),profileId:this.profileId()}),await this.callPluginHook(u=>u.onConsentSubmit?.(s)),this.renderer?.announceToScreenReader("Your cookie preferences have been saved."),this.hideBanner(),this.modal?.isOpen()&&this.hideModal(),m}bannerVisibility(){return this._bannerVariant??!1}modalVisibility(){return this.modal?.isOpen()?"preference":!1}showBanner(e=!1){if(!p()||!this.profile||!this.banner||!this.renderer)return;let n=e&&this.profile.gpcBanner?"gpc":"main";if(this._bannerVariant===n)return;if(this._bannerMounted===n){this.banner.show(),this._bannerVariant=n,this.eventBus?.dispatch("consenti:bannerVisibility",{visible:!0,action:!1,variant:n}),this.callPluginHook(m=>m.onBannerShow?.());return}let o=n==="gpc"?this.profile.gpcBanner:this.profile.mainBanner,i=this.renderer.mount(this.config.rootEl),a=this.buildHandlers("banner");this.banner.remove();let r=this.profile.locales,c=this.config.core.locale??"en",s=this.banner.build(o,a,void 0,r,c,m=>this.switchLocale(m),this.config.hidePoweredBy??this.profile?.hidePoweredBy??!0,n);this.profile.showFooterMetadata&&this._appendMetaFooter(s),i.appendChild(s);let l=this.banner.getOverlay();l&&i.appendChild(l),this._bannerVariant=n,this._bannerMounted=n,this.eventBus?.dispatch("consenti:bannerVisibility",{visible:!0,action:!1,variant:n}),this.callPluginHook(m=>m.onBannerShow?.())}hideBanner(){this._bannerVariant!==null&&(this.banner?.hide(),this.eventBus?.dispatch("consenti:bannerVisibility",{visible:!1,action:!0,variant:this._bannerVariant}),this._bannerVariant=null,this.broadcastChannel.send({type:"bannerClosed"}),this.renderer?.announceToScreenReader("Cookie banner dismissed."),this.callPluginHook(e=>e.onBannerHide?.()))}showModal(e){if(!p()||!this.profile||!this.modal||this.modal.isOpen())return;let n=this.buildHandlers("modal"),o=this.consentStore?.getConsent()??{},i=this.profile.complianceGroup==="opt-in-dpdpa"?this.profile.dpdpa:void 0,a=this.profile.locales,r=this.config.core.locale??"en";this.modal.build(this.profile.preferenceModal,this.profile.cookies,o,n,this.profile.allowReceipt??!1,i,a,r,s=>{this.hideModal(),this.switchLocale(s)},this.config.hidePoweredBy??!0),this.banner?.hide(),this.modal.open(e,this.renderer?.mount(this.config.rootEl));let c=document.getElementById("consenti-modal");if(this.profile.showFooterMetadata&&c){let s=c.querySelector(".consenti-modal__container");s&&this._appendMetaFooter(s,!0)}this.profile.preferenceModal?.trapFocus&&c&&this._installFocusTrap(c),this._bannerVariant!==null&&this.eventBus?.dispatch("consenti:bannerVisibility",{visible:!1,action:!!e,variant:this._bannerVariant}),this.eventBus?.dispatch("consenti:modalVisibility",{visible:!0,action:!!e}),this.callPluginHook(s=>s.onModalShow?.())}hideModal(){this.modal?.isOpen()&&(this._removeFocusTrap(),this.modal.close(),this.eventBus?.dispatch("consenti:modalVisibility",{visible:!1,action:!0}),this.callPluginHook(e=>e.onModalHide?.()),!this.consentStore?.hasConsent()&&this._bannerVariant!==null?(this.eventBus?.dispatch("consenti:bannerVisibility",{visible:!0,action:!0,variant:this._bannerVariant}),this.banner?.show()):this.consentStore?.hasConsent()||this.showBanner())}onReady(e){this.initialized?e():this.readyPromise.then(e)}get ready(){return this.readyPromise}async deleteConsent(){if(!(!p()||!this.consentStore)){if(this.config.api?.enabled)try{let e=this.config.api.baseUrl??window.location.origin,{httpRequest:n}=await import("./http-AQVVUCX6.mjs");await n(`${e}/consenti/api/v1/consent/${this.visitorId()}`,{method:"DELETE"},this.config.api?.authToken)}catch(e){w.warn("API consent deletion failed:",e)}this.consentStore.delete(),this.broadcastChannel.send({type:"consentDeleted"})}}async reConsent(){await this.deleteConsent(),this.showBanner()}getProfile(){return this.profile}getRootElement(){return p()?this.config.rootEl?typeof this.config.rootEl=="string"?document.querySelector(this.config.rootEl):this.config.rootEl:document.getElementById("consenti-root"):null}getBannerElement(){return p()?document.getElementById("consenti-banner"):null}getModalElement(){return p()?document.getElementById("consenti-modal"):null}switchLocale(e){p()&&(this.config.core.locale=e,this.banner?.remove(),this.modal?.isOpen()&&this.modal.close(),this._bannerVariant=null,this._bannerMounted=null,this.profile=null,this.initialized=!1,this.readyPromise=new Promise(n=>{this.readyResolve=n}),this.init().catch(n=>w.warn("Locale switch error:",n)))}isCookieGranted(e,n=!1){if(!p())return!1;let o=this.getConsent()?.[e];return n?o??!1:o==="granted"}isCategoryGranted(e,n=!1){if(!p()||!this.profile)return n?[]:!1;let o=this.profile.preferenceModal.categories[e];if(!o)return n?[]:!1;let i=this.getConsent();return n?o.cookies.map(a=>({[a]:i?.[a]??"denied"})):o.cookies.every(a=>i?.[a]==="granted")}async grantAll(e=!1){!p()||!this.profile||await this.submitConsent(this._buildGrantAllConsent(e),!1,1)}async denyAll(e=!1){!p()||!this.profile||(e&&w.warn("denyAll called with includingMandatory=true \u2014 mandatory cookies will be denied. Ensure this is intentional."),await this.submitConsent(this._buildDenyAllConsent(e),!1,1))}on(e,n){if(!p())return;let o=e.startsWith("consenti:")?e:`consenti:${e}`,i=r=>n(r.detail),a=this._eventHandlers.get(n)??[];a.push({name:o,wrapped:i}),this._eventHandlers.set(n,a),window.addEventListener(o,i)}off(e,n){if(!p())return;let o=e.startsWith("consenti:")?e:`consenti:${e}`,i=this._eventHandlers.get(n);if(!i)return;let a=i.findIndex(c=>c.name===o);if(a===-1)return;let r=i[a];r&&window.removeEventListener(o,r.wrapped),i.splice(a,1),i.length===0&&this._eventHandlers.delete(n)}version(){return{package:"0.3.0",profileVersion:this.profile?.id??"0",consentVersion:null}}setDarkMode(e){p()&&(this.config.darkMode=e===void 0?!this.config.darkMode:e,this.applyDarkMode())}setTheme(e){p()&&(this.config.core.theme=Object.assign({},this.config.core.theme,e),this.applyTheme())}setConfig(e){let n=I(this.config,e);this.config={...n,core:n.core??{}},p()&&(this.applyTheme(),this.applyDarkMode())}setProfile(e){if(!p()||!this.resolvedBase)return;this.config.profileOverride=this.config.profileOverride?I(this.config.profileOverride,e):e,this.profile=I(this.resolvedBase,this.config.profileOverride),this.categoryIndex=N(this.profile.preferenceModal.categories);let n=this.modal?.isOpen()??!1;if(n&&this.modal?.close(),this._bannerVariant!==null){let o=this._bannerVariant==="gpc";this._bannerVariant=null,this._bannerMounted=null,this.showBanner(o)}n&&this.showModal()}destroy(){this._removeFocusTrap();for(let e of this.plugins)try{e.destroy()}catch{}for(let e of this.scannedScripts)e.destroy();if(this.scannedScripts=[],this.modal?.destroy(),this.renderer?.unmount(),this.broadcastChannel.disconnect(),p()){for(let e of this._eventHandlers.values())for(let{name:n,wrapped:o}of e)window.removeEventListener(n,o);this._eventHandlers.clear()}this.banner=null,this.modal=null,this.renderer=null,this.consentStore=null,this.eventBus=null,this.initialized=!1}};var xe=class{};var Yn=1e3,Pe=class{constructor(e){this.config=e;this.type=Symbol(e.id??Yn++),Xe(this.type,e)}getType(){return this.type}toJSON(){return this.config}};var Ge=class{constructor(e){this.options=e;this.granted=null;this.handler=()=>this.evaluate(),p()&&(e.bind!==!1&&window.addEventListener("consenti:consentSubmitted",this.handler),this.evaluate())}evaluate(){let e=this.options.widget.getConsent(),n=e?.[this.options.id]??"denied",o=n==="granted";if(o===this.granted)return;this.granted=o;let i=this.options.widget.getProfile()?.cookies[this.options.id],a={cookieId:this.options.id,status:n,cookie:i,consent:e};o?this.options.onGrant?.(a):this.options.onDeny?.(a)}destroy(){p()&&this.options.bind!==!1&&window.removeEventListener("consenti:consentSubmitted",this.handler)}};var Se=class{constructor(e){this.options=e;this.granted=null;this.handler=()=>this.evaluate(),p()&&(e.bind!==!1&&window.addEventListener("consenti:consentSubmitted",this.handler),this.evaluate())}evaluate(){let n=this.options.widget.getProfile()?.preferenceModal.categories[this.options.id]?.cookies??[],o=this.options.widget.getConsent(),i=n.length>0&&n.every(r=>o?.[r]==="granted");if(i===this.granted)return;this.granted=i;let a={categoryId:this.options.id,cookieIds:n,consent:o};i?this.options.onGrant?.(a):this.options.onDeny?.(a)}destroy(){p()&&this.options.bind!==!1&&window.removeEventListener("consenti:consentSubmitted",this.handler)}};var Ee=class{constructor(e){let n=e.action??"modal";if(this.clickHandler=()=>{n==="modal"?e.widget.showModal():e.widget.showBanner()},e.el)if(typeof e.el=="string"){if(!p()){this.el=document.createElement("span"),this.created=!1;return}let o=document.querySelector(e.el);if(!o)throw new Error(`[Consenti] BannerTrigger: element not found for selector "${e.el}"`);this.el=o,this.created=!1}else this.el=e.el,this.created=!1;else{let o=document.createElement("button");o.type="button",o.className="consenti-btn consenti-btn--text consenti-banner-trigger",o.textContent=e.label??"Cookie Settings",o.setAttribute("aria-label",e.label??"Open cookie settings"),this.el=o,this.created=!0}this.el.addEventListener("click",this.clickHandler)}getElement(){return this.el}destroy(){this.el.removeEventListener("click",this.clickHandler),this.created&&this.el.remove()}};export{Ee as BannerTrigger,Se as CategoryAction,U as CategoryScript,Ge as ConsentAction,j as ConsentScript,xe as ConsentiPlugin,Pe as ConsentiProfile,Ae as ConsentiSetup,me as scanConsentScripts};
2221
2
  //# sourceMappingURL=index.mjs.map