@consenti/ui 0.1.0 → 0.2.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.
package/dist/index.umd.js CHANGED
@@ -1,2305 +1,2 @@
1
- "use strict";
2
- var Consenti = (() => {
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __esm = (fn, res) => function __init() {
8
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
- };
10
- var __export = (target, all) => {
11
- for (var name in all)
12
- __defProp(target, name, { get: all[name], enumerable: true });
13
- };
14
- var __copyProps = (to, from, except, desc) => {
15
- if (from && typeof from === "object" || typeof from === "function") {
16
- for (let key of __getOwnPropNames(from))
17
- if (!__hasOwnProp.call(to, key) && key !== except)
18
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
19
- }
20
- return to;
21
- };
22
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
23
-
24
- // src/utils/hmac.ts
25
- var hmac_exports = {};
26
- __export(hmac_exports, {
27
- signValue: () => signValue,
28
- verifyValue: () => verifyValue
29
- });
30
- async function importHmacKey(rawKey) {
31
- const keyData = new TextEncoder().encode(rawKey);
32
- return crypto.subtle.importKey(
33
- "raw",
34
- keyData,
35
- { name: "HMAC", hash: "SHA-256" },
36
- false,
37
- ["sign", "verify"]
38
- );
39
- }
40
- async function signValue(value, key) {
41
- const cryptoKey = await importHmacKey(key);
42
- const data = new TextEncoder().encode(value);
43
- const signature = await crypto.subtle.sign("HMAC", cryptoKey, data);
44
- return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
45
- }
46
- async function verifyValue(value, signature, key) {
47
- const expected = await signValue(value, key);
48
- return expected === signature;
49
- }
50
- var init_hmac = __esm({
51
- "src/utils/hmac.ts"() {
52
- "use strict";
53
- }
54
- });
55
-
56
- // src/utils/http.ts
57
- var http_exports = {};
58
- __export(http_exports, {
59
- httpRequest: () => httpRequest
60
- });
61
- async function httpRequest(url, options = {}, authToken) {
62
- const headers = {
63
- "Content-Type": "application/json",
64
- ...options.headers
65
- };
66
- if (authToken) {
67
- headers["Authorization"] = `Bearer ${authToken}`;
68
- }
69
- const res = await fetch(url, { ...options, headers });
70
- if (!res.ok) {
71
- throw new Error(`[Consenti] HTTP ${res.status} ${res.statusText} \u2014 ${url}`);
72
- }
73
- return res.json();
74
- }
75
- var init_http = __esm({
76
- "src/utils/http.ts"() {
77
- "use strict";
78
- }
79
- });
80
-
81
- // src/index.ts
82
- var src_exports = {};
83
- __export(src_exports, {
84
- ConsentScript: () => ConsentScript,
85
- ConsentiPlugin: () => ConsentiPlugin,
86
- ConsentiProfile: () => ConsentiProfile,
87
- ConsentiSetup: () => ConsentiSetup,
88
- CookieTrigger: () => CookieTrigger
89
- });
90
-
91
- // src/styles/consenti-css.ts
92
- 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}`;
93
-
94
- // src/utils/ssr.ts
95
- var isClient = () => typeof window !== "undefined" && typeof document !== "undefined";
96
- var safeWindow = () => isClient() ? window : null;
97
- var safeNavigator = () => isClient() && typeof navigator !== "undefined" ? navigator : null;
98
-
99
- // src/utils/broadcast.ts
100
- var ConsentiChannel = class {
101
- constructor() {
102
- this.channel = null;
103
- }
104
- /** `true` when `BroadcastChannel` is available in the current environment. */
105
- get isSupported() {
106
- return isClient() && typeof BroadcastChannel !== "undefined";
107
- }
108
- /**
109
- * Opens the `BroadcastChannel` and registers a message handler.
110
- * Silently no-ops if `BroadcastChannel` is unsupported.
111
- *
112
- * @param onMessage - Callback invoked with each received `ConsentiMessage`.
113
- */
114
- connect(onMessage) {
115
- if (!this.isSupported) return;
116
- this.onMessageCallback = onMessage;
117
- this.channel = new BroadcastChannel("consenti");
118
- this.channel.onmessage = (event) => {
119
- if (event.data?.type) {
120
- onMessage(event.data);
121
- }
122
- };
123
- this.channel.onmessageerror = () => {
124
- console.warn("[Consenti] BroadcastChannel message error \u2014 ignoring");
125
- };
126
- }
127
- /**
128
- * Posts a message to all other tabs connected to the `'consenti'` channel.
129
- * The sending tab does not receive its own message.
130
- *
131
- * @param message - The `ConsentiMessage` to broadcast.
132
- */
133
- send(message) {
134
- this.channel?.postMessage(message);
135
- }
136
- /** Closes the channel and removes the message callback. */
137
- disconnect() {
138
- this.channel?.close();
139
- this.channel = null;
140
- this.onMessageCallback = void 0;
141
- }
142
- };
143
-
144
- // src/utils/receipt.ts
145
- function generateReceipt(params) {
146
- const receipt = {
147
- version: "1.0",
148
- issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
149
- visitorId: params.visitorId,
150
- profileId: params.profileId,
151
- profileVersion: params.profileVersion,
152
- locale: params.locale,
153
- consent: params.consent
154
- };
155
- if (params.signature) receipt.signature = params.signature;
156
- return receipt;
157
- }
158
- function downloadReceipt(receipt) {
159
- if (typeof window === "undefined" || typeof document === "undefined") return;
160
- const json = JSON.stringify(receipt, null, 2);
161
- const blob = new Blob([json], { type: "application/json" });
162
- const url = URL.createObjectURL(blob);
163
- const anchor = document.createElement("a");
164
- anchor.href = url;
165
- anchor.download = `consenti-receipt-${receipt.issuedAt.slice(0, 10)}.json`;
166
- anchor.style.display = "none";
167
- document.body.appendChild(anchor);
168
- anchor.click();
169
- document.body.removeChild(anchor);
170
- setTimeout(() => URL.revokeObjectURL(url), 1e3);
171
- }
172
-
173
- // src/utils/cookie.ts
174
- function getCookie(name) {
175
- if (!isClient()) return null;
176
- const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${escapeRegExp(name)}=([^;]*)`));
177
- return match ? decodeURIComponent(match[1] ?? "") : null;
178
- }
179
- function setCookie(name, value, opts = {}) {
180
- if (!isClient()) return;
181
- const secure = opts.secure ?? (typeof window !== "undefined" && typeof window.location !== "undefined" && window.location.protocol === "https:");
182
- const parts = [
183
- `${encodeURIComponent(name)}=${encodeURIComponent(value)}`,
184
- `Path=${opts.path ?? "/"}`,
185
- `SameSite=${opts.sameSite ?? "Lax"}`,
186
- opts.maxAge != null ? `Max-Age=${opts.maxAge}` : "",
187
- secure ? "Secure" : "",
188
- opts.domain ? `Domain=${opts.domain}` : ""
189
- ];
190
- document.cookie = parts.filter(Boolean).join("; ");
191
- }
192
- function deleteCookie(name, opts = {}) {
193
- setCookie(name, "", { ...opts, maxAge: 0 });
194
- }
195
- function escapeRegExp(str) {
196
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
197
- }
198
-
199
- // src/utils/storage.ts
200
- var ConsentStorage = class {
201
- /**
202
- * @param mode - `'cookie'` (default) or `'localStorage'`. Falls back to `'cookie'` if
203
- * `localStorage` is requested but unavailable in the current environment.
204
- */
205
- constructor(mode) {
206
- this.mode = mode;
207
- if (mode === "localStorage" && isClient() && typeof localStorage === "undefined") {
208
- console.warn("[Consenti] localStorage is not available, falling back to cookie storage");
209
- this.mode = "cookie";
210
- }
211
- }
212
- /**
213
- * Reads a value from storage.
214
- *
215
- * @param key - Cookie name or localStorage key.
216
- * @returns The stored string value, or `null` if absent or SSR context.
217
- */
218
- read(key) {
219
- if (!isClient()) return null;
220
- if (this.mode === "localStorage") {
221
- return localStorage.getItem(key);
222
- }
223
- return getCookie(key);
224
- }
225
- /**
226
- * Writes a value to storage.
227
- *
228
- * @param key - Cookie name or localStorage key.
229
- * @param value - String value to persist.
230
- * @param opts - Cookie attributes (ignored in localStorage mode).
231
- */
232
- write(key, value, opts = {}) {
233
- if (!isClient()) return;
234
- if (this.mode === "localStorage") {
235
- localStorage.setItem(key, value);
236
- } else {
237
- setCookie(key, value, opts);
238
- }
239
- }
240
- /**
241
- * Removes a value from storage.
242
- *
243
- * @param key - Cookie name or localStorage key.
244
- * @param opts - Cookie `path` / `domain` attributes (must match the original write call).
245
- */
246
- remove(key, opts = {}) {
247
- if (!isClient()) return;
248
- if (this.mode === "localStorage") {
249
- localStorage.removeItem(key);
250
- } else {
251
- deleteCookie(key, opts);
252
- }
253
- }
254
- };
255
-
256
- // src/core/consent-store.ts
257
- var COOKIE_NAME_PREFIX = "consenti";
258
- function buildConsentCookieName(userId, profileId) {
259
- return `${COOKIE_NAME_PREFIX}_${userId}_${profileId}`;
260
- }
261
- function buildCookieValue(consent) {
262
- const epochSeconds = Math.floor(Date.now() / 1e3);
263
- const pairs = Object.entries(consent).map(([id, status]) => `${id}:${status}`).join("|");
264
- return `t:${epochSeconds}::${pairs}`;
265
- }
266
- function parseConsentCookie(value) {
267
- const parts = value.split("::");
268
- if (parts.length < 2) return null;
269
- const tsPart = parts[0] ?? "";
270
- let timestamp;
271
- if (tsPart.startsWith("t:")) {
272
- const epochStr = tsPart.slice(2);
273
- const epoch = parseInt(epochStr, 10);
274
- if (!epochStr || isNaN(epoch)) return null;
275
- timestamp = new Date(epoch * 1e3).toISOString();
276
- } else if (tsPart.startsWith("ConsentTimestamp:")) {
277
- timestamp = tsPart.replace("ConsentTimestamp:", "");
278
- if (!timestamp) return null;
279
- } else {
280
- return null;
281
- }
282
- const consentPart = parts[1] ?? "";
283
- const sigPart = parts[2];
284
- const consent = {};
285
- for (const pair of consentPart.split("|")) {
286
- const colonIdx = pair.indexOf(":");
287
- if (colonIdx === -1) return null;
288
- const id = pair.slice(0, colonIdx);
289
- const status = pair.slice(colonIdx + 1);
290
- if (!id || status !== "granted" && status !== "denied" && status !== "objected") {
291
- return null;
292
- }
293
- consent[id] = status;
294
- }
295
- const result = { timestamp, consent };
296
- if (sigPart?.startsWith("sig:")) {
297
- result.signature = sigPart.slice(4);
298
- }
299
- return result;
300
- }
301
- var ConsentStore = class _ConsentStore {
302
- /**
303
- * @param userId - Visitor UUID (from `consenti_uid` cookie).
304
- * @param profileId - Numeric profile ID.
305
- * @param storageMode - `'cookie'` (default, cross-subdomain) or `'localStorage'` (origin-scoped).
306
- * @param cookieDomains - Comma-separated domain list. First entry used as `Domain` attribute.
307
- */
308
- constructor(userId, profileId, storageMode, cookieDomains) {
309
- this.userId = userId;
310
- this.profileId = profileId;
311
- this.cookieDomains = cookieDomains;
312
- this.cachedConsent = null;
313
- this.cachedTimestamp = null;
314
- this.storage = new ConsentStorage(storageMode);
315
- }
316
- /** The full cookie / localStorage key for this visitor + profile combination. */
317
- get cookieName() {
318
- return buildConsentCookieName(this.userId, this.profileId);
319
- }
320
- /**
321
- * Reads and parses the stored consent record.
322
- * Updates the in-memory cache on success.
323
- *
324
- * @returns The parsed consent or `null` if absent / malformed.
325
- */
326
- read() {
327
- const raw = this.storage.read(this.cookieName);
328
- if (!raw) return null;
329
- const parsed = parseConsentCookie(raw);
330
- if (parsed) {
331
- this.cachedConsent = parsed.consent;
332
- this.cachedTimestamp = parsed.timestamp;
333
- }
334
- return parsed;
335
- }
336
- /**
337
- * Reads, parses, and cryptographically verifies the stored consent record.
338
- *
339
- * If the cookie has a `::sig:{hmac}` segment, the signature is verified against
340
- * the raw value using HMAC-SHA256. A mismatch means the cookie was tampered with —
341
- * the cookie is deleted and `null` is returned so the widget re-prompts for consent.
342
- *
343
- * If the cookie has no signature segment, it is treated as valid (backwards compatible
344
- * with cookies written before `signCookies` was enabled).
345
- *
346
- * @param signingKey - The HMAC key (from profile API response or `core.cookieSigningKey`).
347
- * @returns The parsed consent, or `null` if absent / malformed / signature invalid.
348
- */
349
- async readAndVerify(signingKey) {
350
- const raw = this.storage.read(this.cookieName);
351
- if (!raw) return null;
352
- const parts = raw.split("::");
353
- if (parts.length < 2) return null;
354
- const parsed = parseConsentCookie(raw);
355
- if (!parsed) return null;
356
- const hasSig = parts.length === 3 && (parts[2] ?? "").startsWith("sig:");
357
- if (hasSig) {
358
- const { verifyValue: verifyValue2 } = await Promise.resolve().then(() => (init_hmac(), hmac_exports));
359
- const valueToVerify = (parts[0] ?? "") + "::" + (parts[1] ?? "");
360
- const sig = (parts[2] ?? "").slice(4);
361
- const valid = await verifyValue2(valueToVerify, sig, signingKey);
362
- if (!valid) {
363
- console.warn("[Consenti] Cookie signature mismatch \u2014 consent cleared and banner will re-appear");
364
- this.delete();
365
- return null;
366
- }
367
- }
368
- this.cachedConsent = parsed.consent;
369
- this.cachedTimestamp = parsed.timestamp;
370
- return parsed;
371
- }
372
- /**
373
- * Writes a consent record to storage.
374
- *
375
- * @param consent - The consent map to persist.
376
- * @param signature - Optional pre-computed HMAC hex string. Use `writeAndSign()` instead
377
- * if you need signing — it guarantees the signature covers the exact
378
- * value that gets written (timestamps match).
379
- */
380
- write(consent, signature) {
381
- let value = buildCookieValue(consent);
382
- if (signature) {
383
- value = `${value}::sig:${signature}`;
384
- }
385
- const opts = this.buildCookieOpts();
386
- this.storage.write(this.cookieName, value, opts);
387
- this.cachedConsent = consent;
388
- this.cachedTimestamp = (/* @__PURE__ */ new Date()).toISOString();
389
- }
390
- /**
391
- * Builds the cookie value, signs it with HMAC-SHA256 in a single atomic step,
392
- * and writes the signed value to storage.
393
- *
394
- * Using this instead of `write(consent, signature)` ensures the signature
395
- * covers exactly the value that is written — there is no timestamp drift
396
- * between building, signing, and writing.
397
- *
398
- * @param consent - The consent map to persist.
399
- * @param signingKey - The HMAC key string.
400
- * @returns The hex-encoded signature, stored in `this.lastSignature` by the caller.
401
- */
402
- async writeAndSign(consent, signingKey) {
403
- const { signValue: signValue2 } = await Promise.resolve().then(() => (init_hmac(), hmac_exports));
404
- const rawValue = buildCookieValue(consent);
405
- const sig = await signValue2(rawValue, signingKey);
406
- const finalValue = `${rawValue}::sig:${sig}`;
407
- const opts = this.buildCookieOpts();
408
- this.storage.write(this.cookieName, finalValue, opts);
409
- this.cachedConsent = consent;
410
- this.cachedTimestamp = (/* @__PURE__ */ new Date()).toISOString();
411
- return sig;
412
- }
413
- /** Removes the consent record from storage and clears the in-memory cache. */
414
- delete() {
415
- const opts = this.buildCookieOpts();
416
- this.storage.remove(this.cookieName, opts);
417
- this.cachedConsent = null;
418
- this.cachedTimestamp = null;
419
- }
420
- /** Returns `true` if a valid consent record exists (uses cache when available). */
421
- hasConsent() {
422
- if (this.cachedConsent) return true;
423
- return this.read() !== null;
424
- }
425
- /** Returns the stored consent map, or `null` if no consent has been given. */
426
- getConsent() {
427
- if (this.cachedConsent) return this.cachedConsent;
428
- return this.read()?.consent ?? null;
429
- }
430
- /** Returns the `Date` of the last consent submission, or `false` if no consent exists. */
431
- getConsentDate() {
432
- if (this.cachedTimestamp) return new Date(this.cachedTimestamp);
433
- const parsed = this.read();
434
- if (!parsed) return false;
435
- return new Date(parsed.timestamp);
436
- }
437
- /**
438
- * Updates the in-memory cache without writing to storage.
439
- * Used by the cross-tab BroadcastChannel handler to keep other tabs in sync.
440
- */
441
- setInMemory(consent) {
442
- this.cachedConsent = consent;
443
- this.cachedTimestamp = (/* @__PURE__ */ new Date()).toISOString();
444
- }
445
- /** Clears the in-memory cache. Used when consent is deleted from another tab. */
446
- clearInMemory() {
447
- this.cachedConsent = null;
448
- this.cachedTimestamp = null;
449
- }
450
- /**
451
- * Derives the visitor UUID from an existing consent cookie, or generates a new one
452
- * in memory.
453
- *
454
- * No separate identifier cookie is written before consent. Storing a tracking
455
- * identifier before the user has given consent violates TTDSG §25 (Germany) and
456
- * equivalent ePrivacy laws in the EU. The UUID is embedded in the consent cookie
457
- * name and only persisted when consent is first saved (via `write()` / `writeAndSign()`).
458
- *
459
- * @param profileId - The active profile ID, used to locate an existing consent cookie.
460
- * @param storageMode - The configured storage mode (`'cookie'` or `'localStorage'`).
461
- */
462
- static getOrCreateUserId(profileId, storageMode) {
463
- if (typeof document === "undefined") return crypto.randomUUID();
464
- const prefix = `${COOKIE_NAME_PREFIX}_`;
465
- const suffix = `_${profileId}`;
466
- const all = storageMode === "localStorage" ? _ConsentStore._localStorageKeys() : _ConsentStore._cookieNames();
467
- for (const name of all) {
468
- if (name.startsWith(prefix) && name.endsWith(suffix)) {
469
- const inner = name.slice(prefix.length, name.length - suffix.length);
470
- if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(inner)) {
471
- return inner;
472
- }
473
- }
474
- }
475
- return crypto.randomUUID();
476
- }
477
- /** Returns all document.cookie names. */
478
- static _cookieNames() {
479
- return document.cookie.split(";").map((c) => c.split("=")[0]?.trim() ?? "");
480
- }
481
- /** Returns all localStorage keys (empty array when unavailable). */
482
- static _localStorageKeys() {
483
- if (typeof localStorage === "undefined") return [];
484
- return Object.keys(localStorage);
485
- }
486
- buildCookieOpts() {
487
- const opts = {
488
- path: "/",
489
- sameSite: "Lax",
490
- maxAge: 365 * 24 * 60 * 60
491
- };
492
- if (this.cookieDomains) {
493
- const domain = this.cookieDomains.split(",")[0]?.trim();
494
- if (domain) opts.domain = domain;
495
- }
496
- return opts;
497
- }
498
- };
499
-
500
- // src/core/event-bus.ts
501
- var EventBus = class {
502
- /**
503
- * @param profileId - Profile ID included in all GTM pushes.
504
- * @param gtmConfig - Optional GTM integration settings.
505
- */
506
- constructor(profileId, gtmConfig) {
507
- this.profileId = profileId;
508
- this.gtmConfig = gtmConfig;
509
- }
510
- /**
511
- * Dispatches a `consenti:*` custom event on `window` and, if GTM is configured,
512
- * pushes the event to `window.dataLayer`.
513
- *
514
- * Silently no-ops in SSR contexts.
515
- *
516
- * @param name - The event name.
517
- * @param detail - Event payload attached to `event.detail` and included in the GTM push.
518
- */
519
- dispatch(name, detail = {}) {
520
- if (!isClient()) return;
521
- const event = new CustomEvent(name, { detail, bubbles: true });
522
- window.dispatchEvent(event);
523
- this.pushToGTM(name, detail);
524
- }
525
- pushToGTM(name, detail) {
526
- const gtm = this.gtmConfig;
527
- if (!gtm) return;
528
- const win = safeWindow();
529
- if (!win) return;
530
- const layerName = gtm.dataLayer ?? "dataLayer";
531
- const layer = win[layerName];
532
- if (!Array.isArray(layer)) return;
533
- const shouldPush = !gtm.events || gtm.events.length === 0 || gtm.events.includes(name);
534
- if (!shouldPush) return;
535
- layer.push({
536
- event: name,
537
- consenti: {
538
- profileId: this.profileId,
539
- consent: detail.consent,
540
- consentId: detail.consentId,
541
- consentAction: detail.consentAction
542
- }
543
- });
544
- if (name === "consenti:consentSubmitted" && this.hasGtag(win)) {
545
- if (gtm.urlPassthrough) {
546
- layer.push({ url_passthrough: true });
547
- }
548
- if (gtm.adsDataRedaction) {
549
- const adConsentDenied = detail.consent?.["ad_storage"] !== "granted";
550
- layer.push({ ads_data_redaction: adConsentDenied });
551
- }
552
- }
553
- }
554
- /**
555
- * Returns `true` when gtag.js or Google Tag Manager is detected on the page.
556
- * Used to gate GCM v2 cookieless-ping pushes so they are not emitted when
557
- * Google services are absent.
558
- */
559
- hasGtag(win) {
560
- return typeof win["gtag"] === "function" || typeof win["google_tag_manager"] !== "undefined";
561
- }
562
- };
563
-
564
- // src/core/profile-resolver.ts
565
- init_http();
566
-
567
- // src/utils/locale.ts
568
- function resolveLocale(translations, requested, defaultLocale) {
569
- const base = translations[defaultLocale];
570
- if (!base) {
571
- throw new Error(`[Consenti] No translations found for defaultLocale "${defaultLocale}"`);
572
- }
573
- if (translations[requested]) {
574
- return deepMerge(base, translations[requested]);
575
- }
576
- const lang = requested.split("-")[0] ?? "";
577
- if (lang !== requested && translations[lang]) {
578
- return deepMerge(base, translations[lang]);
579
- }
580
- return base;
581
- }
582
- function deepMerge(base, override) {
583
- const result = { ...base };
584
- for (const key in override) {
585
- const overrideVal = override[key];
586
- if (overrideVal === void 0 || overrideVal === null) continue;
587
- if (typeof overrideVal === "object" && !Array.isArray(overrideVal) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) {
588
- result[key] = deepMerge(result[key], overrideVal);
589
- } else {
590
- result[key] = overrideVal;
591
- }
592
- }
593
- return result;
594
- }
595
-
596
- // src/core/profile-resolver.ts
597
- var profileRegistry = /* @__PURE__ */ new Map();
598
- function registerProfile(id, config) {
599
- profileRegistry.set(id, config);
600
- }
601
- var DEFAULT_PROFILE = {
602
- id: 0,
603
- version: 1,
604
- defaultLocale: "en",
605
- cookies: [
606
- { id: "functionality_storage", mandatory: true },
607
- {
608
- id: "analytics_storage",
609
- listenGpc: true,
610
- expiry: 365
611
- },
612
- {
613
- id: "ad_storage",
614
- listenGpc: true,
615
- expiry: 365
616
- },
617
- {
618
- id: "ad_user_data",
619
- listenGpc: true,
620
- expiry: 365
621
- },
622
- {
623
- id: "ad_personalization",
624
- listenGpc: true,
625
- expiry: 365
626
- }
627
- ],
628
- mainBanner: {
629
- position: "bottom",
630
- heading: "We value your privacy",
631
- 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.",
632
- buttons: [
633
- { text: "Accept All", cookies: "*", style: "primary", action: "custom" },
634
- { text: "Reject All", cookies: "!", style: "primary", action: "custom" },
635
- { text: "Manage Preferences", style: "secondary", action: "manage" }
636
- ]
637
- },
638
- preferenceModal: {
639
- heading: "Cookie Preferences",
640
- subheading: "Choose which cookies you allow us to use.",
641
- 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.",
642
- showClose: true,
643
- overlayOpacity: 50,
644
- categories: [
645
- {
646
- id: "cat-necessary",
647
- heading: "Strictly Necessary",
648
- htmlText: "These cookies are essential for the website to function correctly. They include authentication, security, and accessibility features. They cannot be disabled.",
649
- mandatory: true,
650
- cookies: ["functionality_storage"]
651
- },
652
- {
653
- id: "cat-analytics",
654
- heading: "Analytics",
655
- 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)",
656
- cookies: ["analytics_storage"]
657
- },
658
- {
659
- id: "cat-advertising",
660
- heading: "Advertising",
661
- 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)",
662
- cookies: ["ad_storage", "ad_user_data", "ad_personalization"]
663
- }
664
- ],
665
- buttons: [
666
- { text: "Accept All", cookies: "*", style: "primary", action: "custom" },
667
- { text: "Save Preferences", style: "primary", action: "submit" },
668
- { text: "Reject All", cookies: "!", style: "primary", action: "custom" }
669
- ]
670
- }
671
- };
672
- async function resolveProfile(config) {
673
- const locale = config.core.locale ?? "en";
674
- const profileId = config.core.profileId ?? 0;
675
- if (config.api?.enabled) {
676
- try {
677
- const base = config.api.baseUrl ?? (typeof window !== "undefined" ? window.location.origin : "");
678
- const url = `${base}/consenti/api/v1/profiles/${profileId}/${encodeURIComponent(locale)}`;
679
- const apiProfile = await httpRequest(url, {}, config.api.authToken);
680
- return {
681
- id: profileId,
682
- version: apiProfile.version,
683
- defaultLocale: apiProfile.defaultLocale,
684
- locales: apiProfile.locales,
685
- cookies: apiProfile.cookies,
686
- mainBanner: apiProfile.mainBanner,
687
- preferenceModal: apiProfile.preferenceModal,
688
- ...apiProfile.gpcBanner != null ? { gpcBanner: apiProfile.gpcBanner } : {}
689
- };
690
- } catch {
691
- console.warn("[Consenti] API profile fetch failed, falling back to local profile");
692
- }
693
- }
694
- if (profileId > 0) {
695
- const local = profileRegistry.get(profileId);
696
- if (local) {
697
- const resolved = resolveLocale(local.translations ?? {}, locale, local.defaultLocale);
698
- return {
699
- id: profileId,
700
- version: 1,
701
- defaultLocale: local.defaultLocale,
702
- locales: Object.keys(local.translations ?? {}),
703
- cookies: local.cookies ?? [],
704
- mainBanner: resolved.mainBanner,
705
- ...resolved.gpcBanner ? { gpcBanner: resolved.gpcBanner } : {},
706
- preferenceModal: resolved.preferenceModal,
707
- ...local.darkMode !== void 0 ? { darkMode: local.darkMode } : {}
708
- };
709
- }
710
- throw new Error(
711
- `[Consenti] Profile ${profileId} not found. Register it with ConsentiProfile or enable the API.`
712
- );
713
- }
714
- return DEFAULT_PROFILE;
715
- }
716
-
717
- // src/core/gpc.ts
718
- function detectGPC() {
719
- const nav = safeNavigator();
720
- if (!nav) return false;
721
- return "globalPrivacyControl" in nav && nav.globalPrivacyControl === true;
722
- }
723
- function applyGPCToConsent(cookies, currentConsent, mode) {
724
- if (!mode || !detectGPC()) return { ...currentConsent };
725
- const result = { ...currentConsent };
726
- for (const cookie of cookies) {
727
- if (cookie.mandatory) continue;
728
- if (cookie.listenGpc) {
729
- result[cookie.id] = cookie.type === "legitimate_interest" ? "objected" : "denied";
730
- }
731
- }
732
- for (const cookie of cookies) {
733
- if (cookie.mandatory) result[cookie.id] = "granted";
734
- }
735
- return result;
736
- }
737
-
738
- // src/ui/buttons.ts
739
- function buildButton(btn, handlers) {
740
- const el = document.createElement("button");
741
- el.type = "button";
742
- el.className = `consenti-btn consenti-btn--${btn.style}`;
743
- el.textContent = btn.text;
744
- el.addEventListener("click", () => {
745
- switch (btn.action) {
746
- case "custom": {
747
- const cookies = btn.cookies;
748
- if (cookies === "*") {
749
- handlers.onGrantAll();
750
- } else if (cookies === "!") {
751
- handlers.onDenyAll();
752
- } else if (Array.isArray(cookies) && cookies.length > 0) {
753
- handlers.onGrantSpecific(cookies);
754
- }
755
- break;
756
- }
757
- case "submit":
758
- handlers.onSubmit();
759
- break;
760
- case "manage":
761
- handlers.onManage();
762
- break;
763
- case "close":
764
- handlers.onClose();
765
- break;
766
- }
767
- });
768
- return el;
769
- }
770
-
771
- // src/ui/overlay.ts
772
- function createOverlay(opacity, onClick) {
773
- const el = document.createElement("div");
774
- el.className = "consenti-overlay";
775
- el.style.opacity = String(opacity / 100);
776
- el.setAttribute("aria-hidden", "true");
777
- if (onClick) {
778
- el.addEventListener("click", onClick);
779
- }
780
- return el;
781
- }
782
-
783
- // src/ui/banner.ts
784
- var Banner = class {
785
- constructor() {
786
- this.el = null;
787
- this.overlayEl = null;
788
- }
789
- /**
790
- * Constructs the banner DOM from the given config and returns the root element.
791
- * The element is not yet inserted into the document — the caller appends it to
792
- * the mount root via `Renderer.mount()`.
793
- *
794
- * @param config - Banner layout and content config from the resolved profile.
795
- * @param handlers - Click handlers wired to each button.
796
- * @param privacyPolicyUrl - Optional URL appended as a "Privacy Policy" link after the body text.
797
- * @param locales - All locale codes available on the profile (for the locale switcher).
798
- * @param activeLocale - The currently active locale code.
799
- * @param onLocaleSwitch - Callback invoked when the user selects a different locale.
800
- * @returns The constructed banner `<div>` element.
801
- */
802
- build(config, handlers, privacyPolicyUrl, locales, activeLocale, onLocaleSwitch) {
803
- const banner = document.createElement("div");
804
- banner.id = "consenti-banner";
805
- banner.className = `consenti-banner consenti-banner--${config.position}`;
806
- banner.setAttribute("role", "region");
807
- banner.setAttribute("aria-label", "Cookie consent");
808
- banner.setAttribute("aria-live", "polite");
809
- const textContainer = document.createElement("div");
810
- textContainer.className = `consenti-banner__text-container`;
811
- const linksContainer = document.createElement("div");
812
- linksContainer.className = "consenti-banner__links";
813
- if (config.heading) {
814
- const headingTag = config.headingTag ?? "h2";
815
- const heading = document.createElement(headingTag);
816
- heading.id = "consenti-banner-heading";
817
- heading.className = "consenti-banner__heading";
818
- heading.textContent = config.heading;
819
- textContainer.appendChild(heading);
820
- }
821
- const text = document.createElement("div");
822
- text.className = "consenti-banner__text";
823
- text.innerHTML = config.htmlText;
824
- if (privacyPolicyUrl) {
825
- const link = document.createElement("a");
826
- link.href = privacyPolicyUrl;
827
- link.className = "consenti-banner__link consenti-banner__link--privacy consenti-btn consenti-btn--text";
828
- link.textContent = "Privacy Policy";
829
- link.setAttribute("target", "_blank");
830
- link.setAttribute("rel", "noopener noreferrer");
831
- linksContainer.appendChild(link);
832
- }
833
- textContainer.appendChild(text);
834
- const linkButtons = config.buttons.filter((b) => b.action === "link");
835
- if (linkButtons.length > 0) {
836
- for (const btn of linkButtons) {
837
- if (!btn.url) continue;
838
- const anchor = document.createElement("a");
839
- anchor.href = btn.url;
840
- anchor.className = `consenti-banner__link consenti-btn consenti-btn--${btn.style ?? "text"}`;
841
- anchor.textContent = btn.text;
842
- anchor.setAttribute("target", "_blank");
843
- anchor.setAttribute("rel", "noopener noreferrer");
844
- linksContainer.appendChild(anchor);
845
- }
846
- }
847
- if (linkButtons.length > 0 || privacyPolicyUrl) {
848
- textContainer.appendChild(linksContainer);
849
- }
850
- banner.appendChild(textContainer);
851
- const actionButtons = config.buttons.filter((b) => b.action !== "link");
852
- if (actionButtons.length > 0) {
853
- const btns = document.createElement("div");
854
- btns.className = "consenti-banner__buttons";
855
- for (const btn of actionButtons) {
856
- btns.appendChild(buildButton(btn, handlers));
857
- }
858
- banner.appendChild(btns);
859
- }
860
- const hasClose = config.showClose;
861
- const hasLocaleSwitcher = config.showLocaleSwitcher && locales && locales.length > 1;
862
- if (hasClose || hasLocaleSwitcher) {
863
- const controls = document.createElement("div");
864
- controls.className = "consenti-banner__controls";
865
- if (hasLocaleSwitcher && locales && onLocaleSwitch) {
866
- controls.appendChild(buildLocaleSwitcher(locales, activeLocale, onLocaleSwitch));
867
- }
868
- if (hasClose) {
869
- const close = document.createElement("button");
870
- close.type = "button";
871
- close.className = "consenti-banner__close consenti-btn consenti-btn--close";
872
- close.setAttribute("aria-label", "Close cookie banner");
873
- close.textContent = "\xD7";
874
- close.addEventListener("click", handlers.onClose);
875
- controls.appendChild(close);
876
- }
877
- banner.appendChild(controls);
878
- }
879
- if (config.overlayOpacity && config.overlayOpacity > 0) {
880
- this.overlayEl = createOverlay(config.overlayOpacity);
881
- }
882
- this.el = banner;
883
- return banner;
884
- }
885
- /** Returns the overlay element (if `overlayOpacity > 0`), or `null`. */
886
- getOverlay() {
887
- return this.overlayEl;
888
- }
889
- /** Makes the banner and its overlay visible after a `hide()` call. */
890
- show() {
891
- if (this.el) this.el.style.display = "";
892
- if (this.overlayEl) this.overlayEl.style.display = "";
893
- }
894
- /** Hides the banner and its overlay without removing them from the DOM. */
895
- hide() {
896
- if (this.el) this.el.style.display = "none";
897
- if (this.overlayEl) this.overlayEl.style.display = "none";
898
- }
899
- /** Removes the banner and overlay from the DOM entirely. */
900
- remove() {
901
- this.el?.remove();
902
- this.overlayEl?.remove();
903
- this.el = null;
904
- this.overlayEl = null;
905
- }
906
- };
907
- function buildLocaleSwitcher(locales, activeLocale, onSwitch) {
908
- const wrapper = document.createElement("div");
909
- wrapper.className = "consenti-locale-switcher";
910
- const btn = document.createElement("button");
911
- btn.type = "button";
912
- btn.className = "consenti-locale-switcher__btn consenti-btn consenti-btn--close";
913
- btn.setAttribute("aria-label", "Switch language");
914
- btn.setAttribute("aria-haspopup", "listbox");
915
- btn.textContent = "\u{1F310}";
916
- const list = document.createElement("ul");
917
- list.className = "consenti-locale-switcher__list";
918
- list.setAttribute("role", "listbox");
919
- list.setAttribute("aria-label", "Select language");
920
- list.hidden = true;
921
- for (const locale of locales) {
922
- const item = document.createElement("li");
923
- item.setAttribute("role", "option");
924
- item.setAttribute("aria-selected", String(locale === activeLocale));
925
- const localeBtn = document.createElement("button");
926
- localeBtn.type = "button";
927
- localeBtn.className = `consenti-locale-switcher__option${locale === activeLocale ? " consenti-locale-switcher__option--active" : ""}`;
928
- localeBtn.textContent = locale;
929
- localeBtn.addEventListener("click", (e) => {
930
- e.stopPropagation();
931
- list.hidden = true;
932
- if (locale !== activeLocale) onSwitch(locale);
933
- });
934
- item.appendChild(localeBtn);
935
- list.appendChild(item);
936
- }
937
- btn.addEventListener("click", (e) => {
938
- e.stopPropagation();
939
- list.hidden = !list.hidden;
940
- });
941
- document.addEventListener("click", () => {
942
- list.hidden = true;
943
- }, { capture: true, once: false });
944
- wrapper.appendChild(btn);
945
- wrapper.appendChild(list);
946
- return wrapper;
947
- }
948
-
949
- // src/ui/focus-trap.ts
950
- var FOCUSABLE_SELECTORS = [
951
- "a[href]",
952
- "button:not([disabled])",
953
- "input:not([disabled])",
954
- "select:not([disabled])",
955
- "textarea:not([disabled])",
956
- '[tabindex]:not([tabindex="-1"])',
957
- '[contenteditable="true"]'
958
- ].join(", ");
959
- function trapFocus(container) {
960
- const getFocusable = () => Array.from(container.querySelectorAll(FOCUSABLE_SELECTORS)).filter(
961
- (el) => !el.closest('[aria-hidden="true"]')
962
- );
963
- const handleKeydown = (e) => {
964
- if (e.key !== "Tab") return;
965
- const els = getFocusable();
966
- if (els.length === 0) return;
967
- const first = els[0];
968
- const last = els[els.length - 1];
969
- if (e.shiftKey) {
970
- if (document.activeElement === first || document.activeElement === container) {
971
- e.preventDefault();
972
- last.focus();
973
- }
974
- } else {
975
- if (document.activeElement === last) {
976
- e.preventDefault();
977
- first.focus();
978
- }
979
- }
980
- };
981
- const handleEscape = (e) => {
982
- if (e.key === "Escape") {
983
- container.dispatchEvent(new CustomEvent("consenti:closeRequest", { bubbles: true }));
984
- }
985
- };
986
- document.addEventListener("keydown", handleKeydown);
987
- document.addEventListener("keydown", handleEscape);
988
- getFocusable()[0]?.focus();
989
- return () => {
990
- document.removeEventListener("keydown", handleKeydown);
991
- document.removeEventListener("keydown", handleEscape);
992
- };
993
- }
994
-
995
- // src/ui/modal.ts
996
- var Modal = class {
997
- constructor() {
998
- this.el = null;
999
- this.triggerEl = null;
1000
- this.trapCleanup = null;
1001
- /** Map of category ID → toggle button element, used to read state on submit. */
1002
- this.toggleMap = /* @__PURE__ */ new Map();
1003
- this.receiptCheckbox = null;
1004
- /** Live toggle state — updated on each click so `getToggleConsent()` reads the latest. */
1005
- this.toggleState = /* @__PURE__ */ new Map();
1006
- /** When `true`, the modal wrapper is passthrough (overlayOpacity === 0). */
1007
- this.passthrough = false;
1008
- }
1009
- /**
1010
- * Constructs the modal DOM from the given config.
1011
- * The modal is not inserted into the document until `open()` is called.
1012
- *
1013
- * @param config - Modal layout and content config from the resolved profile.
1014
- * @param initialConsent - Current stored consent, used to pre-set toggle states.
1015
- * @param handlers - Click handlers wired to each button.
1016
- * @param allowReceipt - When `true`, renders the consent receipt download checkbox.
1017
- * @param dpdpaConfig - DPDPA grievance notice config.
1018
- * @param locales - All available locale codes (for locale switcher).
1019
- * @param activeLocale - The currently active locale code.
1020
- * @param onLocaleSwitch - Callback invoked when the user selects a different locale.
1021
- */
1022
- build(config, initialConsent, handlers, allowReceipt, dpdpaConfig, locales, activeLocale, onLocaleSwitch) {
1023
- const position = config.position ?? "center";
1024
- const wrapper = document.createElement("div");
1025
- wrapper.id = "consenti-modal";
1026
- wrapper.className = `consenti-modal consenti-modal--${position}`;
1027
- wrapper.setAttribute("role", "dialog");
1028
- wrapper.setAttribute("aria-modal", "true");
1029
- const overlayOpacity = config.overlayOpacity ?? 50;
1030
- this.passthrough = overlayOpacity === 0;
1031
- if (overlayOpacity > 0) {
1032
- const overlayClickHandler = config.persistent ? void 0 : () => handlers.onClose();
1033
- const overlay = createOverlay(overlayOpacity, overlayClickHandler);
1034
- overlay.className = "consenti-modal__overlay";
1035
- wrapper.appendChild(overlay);
1036
- } else {
1037
- wrapper.classList.add("consenti-modal--passthrough");
1038
- }
1039
- const container = document.createElement("div");
1040
- container.className = "consenti-modal__container";
1041
- const header = document.createElement("div");
1042
- header.className = "consenti-modal__header";
1043
- if (config.heading) {
1044
- const headingTag = config.headingTag ?? "h2";
1045
- const heading = document.createElement(headingTag);
1046
- heading.id = "consenti-modal-heading";
1047
- heading.className = "consenti-modal__heading";
1048
- heading.textContent = config.heading;
1049
- header.appendChild(heading);
1050
- wrapper.setAttribute("aria-labelledby", "consenti-modal-heading");
1051
- }
1052
- const hasClose = config.showClose !== false;
1053
- const hasLocaleSwitcher = config.showLocaleSwitcher && locales && locales.length > 1;
1054
- if (hasLocaleSwitcher || hasClose) {
1055
- const headerControls = document.createElement("div");
1056
- headerControls.className = "consenti-modal__header-controls";
1057
- if (hasLocaleSwitcher && locales && onLocaleSwitch) {
1058
- headerControls.appendChild(buildModalLocaleSwitcher(locales, activeLocale, onLocaleSwitch));
1059
- }
1060
- if (hasClose) {
1061
- const close = document.createElement("button");
1062
- close.type = "button";
1063
- close.className = "consenti-modal__close consenti-btn consenti-btn--close";
1064
- close.setAttribute("aria-label", "Close preference modal");
1065
- close.textContent = "\xD7";
1066
- close.addEventListener("click", handlers.onClose);
1067
- headerControls.appendChild(close);
1068
- }
1069
- header.appendChild(headerControls);
1070
- }
1071
- container.appendChild(header);
1072
- if (config.subheading) {
1073
- const sub = document.createElement("p");
1074
- sub.className = "consenti-modal__subheading";
1075
- sub.textContent = config.subheading;
1076
- container.appendChild(sub);
1077
- }
1078
- if (config.htmlText) {
1079
- const desc = document.createElement("div");
1080
- desc.className = "consenti-modal__text";
1081
- desc.innerHTML = config.htmlText;
1082
- container.appendChild(desc);
1083
- }
1084
- const linkButtons = config.buttons.filter((b) => b.action === "link");
1085
- if (linkButtons.length > 0) {
1086
- const linksContainer = document.createElement("div");
1087
- linksContainer.className = "consenti-modal__links";
1088
- for (const btn of linkButtons) {
1089
- if (!btn.url) continue;
1090
- const anchor = document.createElement("a");
1091
- anchor.href = btn.url;
1092
- anchor.className = `consenti-modal__link consenti-btn consenti-btn--${btn.style ?? "text"}`;
1093
- anchor.textContent = btn.text;
1094
- anchor.setAttribute("target", "_blank");
1095
- anchor.setAttribute("rel", "noopener noreferrer");
1096
- linksContainer.appendChild(anchor);
1097
- }
1098
- container.appendChild(linksContainer);
1099
- }
1100
- const body = document.createElement("div");
1101
- body.className = "consenti-modal__body";
1102
- const categories = document.createElement("div");
1103
- categories.className = "consenti-modal__categories";
1104
- for (const cat of config.categories) {
1105
- const catEl = this.buildCategory(cat, initialConsent);
1106
- categories.appendChild(catEl);
1107
- }
1108
- body.appendChild(categories);
1109
- container.appendChild(body);
1110
- if (allowReceipt) {
1111
- container.appendChild(this.buildReceiptOption());
1112
- }
1113
- if (dpdpaConfig) {
1114
- const notice = document.createElement("div");
1115
- notice.className = "consenti-modal__dpdpa-notice";
1116
- notice.innerHTML = [
1117
- `<strong>Data Fiduciary:</strong> ${dpdpaConfig.dataFiduciary}.`,
1118
- dpdpaConfig.purposeDescription ? ` <strong>Purpose:</strong> ${dpdpaConfig.purposeDescription}.` : "",
1119
- ` You may withdraw consent at any time or contact our Grievance Officer at`,
1120
- ` <a href="mailto:${dpdpaConfig.grievanceEmail}">${dpdpaConfig.grievanceEmail}</a>.`
1121
- ].join("");
1122
- container.appendChild(notice);
1123
- }
1124
- const btns = document.createElement("div");
1125
- btns.className = "consenti-modal__buttons";
1126
- for (const btn of config.buttons.filter((b) => b.action !== "link")) {
1127
- btns.appendChild(buildButton(btn, handlers));
1128
- }
1129
- container.appendChild(btns);
1130
- wrapper.appendChild(container);
1131
- wrapper.addEventListener("consenti:closeRequest", () => handlers.onClose());
1132
- this.el = wrapper;
1133
- }
1134
- buildCategory(cat, initialConsent) {
1135
- const isLI = cat.type === "legitimate_interest";
1136
- const isMandatory = cat.mandatory === true;
1137
- let defaultOn;
1138
- if (isMandatory) {
1139
- defaultOn = true;
1140
- } else if (isLI) {
1141
- defaultOn = initialConsent[cat.cookies[0] ?? ""] !== "objected";
1142
- } else {
1143
- defaultOn = initialConsent[cat.cookies[0] ?? ""] === "granted";
1144
- }
1145
- this.toggleState.set(cat.id, defaultOn);
1146
- const wrapper = document.createElement("div");
1147
- wrapper.className = `consenti-category${isMandatory ? " consenti-category--mandatory" : ""}`;
1148
- const header = document.createElement("div");
1149
- header.className = "consenti-category__header";
1150
- const headingTag = cat.headingTag ?? "h3";
1151
- const heading = document.createElement(headingTag);
1152
- heading.id = `consenti-cat-${cat.id}-heading`;
1153
- heading.className = "consenti-category__heading";
1154
- heading.textContent = cat.heading;
1155
- header.appendChild(heading);
1156
- const toggle = document.createElement("button");
1157
- toggle.type = "button";
1158
- toggle.className = `consenti-category__toggle${isMandatory ? " consenti-category__toggle--mandatory" : ""}`;
1159
- toggle.setAttribute("role", "switch");
1160
- toggle.setAttribute("aria-checked", String(defaultOn));
1161
- toggle.setAttribute("aria-labelledby", `consenti-cat-${cat.id}-heading`);
1162
- if (isMandatory) toggle.setAttribute("aria-disabled", "true");
1163
- const knob = document.createElement("span");
1164
- knob.className = "consenti-category__toggle-knob";
1165
- knob.setAttribute("aria-hidden", "true");
1166
- toggle.appendChild(knob);
1167
- if (!isMandatory) {
1168
- toggle.addEventListener("click", () => {
1169
- const current = this.toggleState.get(cat.id) ?? false;
1170
- const next = !current;
1171
- this.toggleState.set(cat.id, next);
1172
- toggle.setAttribute("aria-checked", String(next));
1173
- });
1174
- }
1175
- this.toggleMap.set(cat.id, toggle);
1176
- header.appendChild(toggle);
1177
- wrapper.appendChild(header);
1178
- const text = document.createElement("div");
1179
- text.className = "consenti-category__text";
1180
- text.innerHTML = cat.htmlText;
1181
- wrapper.appendChild(text);
1182
- if (isLI && cat.legitimateInterest?.description) {
1183
- const li = document.createElement("p");
1184
- li.className = "consenti-category__li-desc";
1185
- li.textContent = cat.legitimateInterest.description;
1186
- wrapper.appendChild(li);
1187
- }
1188
- return wrapper;
1189
- }
1190
- buildReceiptOption() {
1191
- const wrapper = document.createElement("div");
1192
- wrapper.className = "consenti-modal__receipt-option";
1193
- const label = document.createElement("label");
1194
- label.className = "consenti-modal__receipt-label";
1195
- const checkbox = document.createElement("input");
1196
- checkbox.type = "checkbox";
1197
- checkbox.className = "consenti-modal__receipt-checkbox";
1198
- checkbox.id = "consenti-receipt-checkbox";
1199
- checkbox.setAttribute("aria-describedby", "consenti-receipt-desc");
1200
- this.receiptCheckbox = checkbox;
1201
- label.appendChild(checkbox);
1202
- label.append(" Get a copy of my consent choices (JSON)");
1203
- wrapper.appendChild(label);
1204
- const desc = document.createElement("p");
1205
- desc.id = "consenti-receipt-desc";
1206
- desc.className = "consenti-modal__receipt-desc";
1207
- desc.textContent = "A JSON file will be downloaded to your device when you save your preferences.";
1208
- wrapper.appendChild(desc);
1209
- return wrapper;
1210
- }
1211
- /**
1212
- * Appends the modal to `document.body` and activates the focus trap.
1213
- * The receipt checkbox (if present) is reset to unchecked.
1214
- *
1215
- * @param triggerEl - The element that opened the modal. Focus returns here on `close()`.
1216
- */
1217
- open(triggerEl) {
1218
- if (!this.el) return;
1219
- this.triggerEl = triggerEl ?? document.activeElement;
1220
- if (this.receiptCheckbox) this.receiptCheckbox.checked = false;
1221
- document.body.appendChild(this.el);
1222
- if (!this.passthrough) {
1223
- this.trapCleanup = trapFocus(this.el);
1224
- }
1225
- }
1226
- /**
1227
- * Removes the modal from `document.body`, deactivates the focus trap,
1228
- * and returns focus to `triggerEl`.
1229
- */
1230
- close() {
1231
- this.trapCleanup?.();
1232
- this.trapCleanup = null;
1233
- this.el?.remove();
1234
- this.triggerEl?.focus();
1235
- this.triggerEl = null;
1236
- }
1237
- /** Returns `true` if the modal is currently mounted in the document. */
1238
- isOpen() {
1239
- return this.el?.isConnected === true;
1240
- }
1241
- /** Returns `true` if the receipt download checkbox is checked at the time of calling. */
1242
- isReceiptRequested() {
1243
- return this.receiptCheckbox?.checked ?? false;
1244
- }
1245
- /**
1246
- * Reads the current toggle state map and converts it to a `ConsentValue`.
1247
- *
1248
- * Called by the submit handler to build the consent object before writing to storage.
1249
- *
1250
- * @param categories - The category list from the resolved profile (provides cookie IDs and type).
1251
- */
1252
- getToggleConsent(categories) {
1253
- const consent = {};
1254
- for (const cat of categories) {
1255
- const isLI = cat.type === "legitimate_interest";
1256
- const isMandatory = cat.mandatory === true;
1257
- const on = this.toggleState.get(cat.id) ?? false;
1258
- for (const cookieId of cat.cookies) {
1259
- if (isMandatory) {
1260
- consent[cookieId] = "granted";
1261
- } else if (isLI) {
1262
- consent[cookieId] = on ? "granted" : "objected";
1263
- } else {
1264
- consent[cookieId] = on ? "granted" : "denied";
1265
- }
1266
- }
1267
- }
1268
- return consent;
1269
- }
1270
- /** Removes the modal element from the DOM and clears all internal state. */
1271
- destroy() {
1272
- this.trapCleanup?.();
1273
- this.el?.remove();
1274
- this.el = null;
1275
- this.toggleMap.clear();
1276
- this.toggleState.clear();
1277
- this.receiptCheckbox = null;
1278
- }
1279
- };
1280
- function buildModalLocaleSwitcher(locales, activeLocale, onSwitch) {
1281
- const wrapper = document.createElement("div");
1282
- wrapper.className = "consenti-locale-switcher consenti-locale-switcher--modal";
1283
- const btn = document.createElement("button");
1284
- btn.type = "button";
1285
- btn.className = "consenti-locale-switcher__btn consenti-btn consenti-btn--close";
1286
- btn.setAttribute("aria-label", "Switch language");
1287
- btn.setAttribute("aria-haspopup", "listbox");
1288
- btn.textContent = "\u{1F310}";
1289
- const list = document.createElement("ul");
1290
- list.className = "consenti-locale-switcher__list";
1291
- list.setAttribute("role", "listbox");
1292
- list.setAttribute("aria-label", "Select language");
1293
- list.hidden = true;
1294
- for (const locale of locales) {
1295
- const item = document.createElement("li");
1296
- item.setAttribute("role", "option");
1297
- item.setAttribute("aria-selected", String(locale === activeLocale));
1298
- const localeBtn = document.createElement("button");
1299
- localeBtn.type = "button";
1300
- localeBtn.className = `consenti-locale-switcher__option${locale === activeLocale ? " consenti-locale-switcher__option--active" : ""}`;
1301
- localeBtn.textContent = locale;
1302
- localeBtn.addEventListener("click", (e) => {
1303
- e.stopPropagation();
1304
- list.hidden = true;
1305
- if (locale !== activeLocale) onSwitch(locale);
1306
- });
1307
- item.appendChild(localeBtn);
1308
- list.appendChild(item);
1309
- }
1310
- btn.addEventListener("click", (e) => {
1311
- e.stopPropagation();
1312
- list.hidden = !list.hidden;
1313
- });
1314
- document.addEventListener("click", () => {
1315
- list.hidden = true;
1316
- }, { capture: true, once: false });
1317
- wrapper.appendChild(btn);
1318
- wrapper.appendChild(list);
1319
- return wrapper;
1320
- }
1321
-
1322
- // src/ui/renderer.ts
1323
- var ROOT_ID = "consenti-root";
1324
- var Renderer = class {
1325
- constructor() {
1326
- this.root = null;
1327
- this.styleEl = null;
1328
- }
1329
- /**
1330
- * Returns the mount-point element, creating `#consenti-root` if needed.
1331
- *
1332
- * When `rootEl` is supplied:
1333
- * - `string` → resolved via `document.querySelector`. Throws if no match.
1334
- * - `HTMLElement` → used directly. Throws if not attached to the document.
1335
- * On subsequent calls the previously stored root is returned immediately.
1336
- *
1337
- * @throws `Error` if called in SSR or if a supplied `rootEl` cannot be resolved.
1338
- */
1339
- mount(rootEl) {
1340
- if (!isClient()) throw new Error("[Consenti] Renderer.mount() called in SSR context");
1341
- if (this.root) return this.root;
1342
- if (rootEl !== void 0) {
1343
- if (typeof rootEl === "string") {
1344
- const found = document.querySelector(rootEl);
1345
- if (!found) throw new Error(`[Consenti] rootEl selector "${rootEl}" did not match any element.`);
1346
- this.root = found;
1347
- } else {
1348
- if (!document.contains(rootEl)) throw new Error("[Consenti] rootEl element is not attached to the document.");
1349
- this.root = rootEl;
1350
- }
1351
- return this.root;
1352
- }
1353
- let root = document.getElementById(ROOT_ID);
1354
- if (!root) {
1355
- root = document.createElement("div");
1356
- root.id = ROOT_ID;
1357
- document.body.appendChild(root);
1358
- }
1359
- this.root = root;
1360
- return root;
1361
- }
1362
- /**
1363
- * Injects a `<style id="consenti-styles">` tag into `<head>` with the given CSS string.
1364
- * No-ops if the style element already exists (idempotent).
1365
- *
1366
- * @param css - The CSS content to inject.
1367
- */
1368
- injectStyles(css) {
1369
- if (!isClient()) return;
1370
- if (this.styleEl) return;
1371
- const style = document.createElement("style");
1372
- style.id = "consenti-styles";
1373
- style.textContent = css;
1374
- document.head.appendChild(style);
1375
- this.styleEl = style;
1376
- }
1377
- /** Removes `#consenti-root` and the injected `<style>` tag from the document. */
1378
- unmount() {
1379
- this.root?.remove();
1380
- this.styleEl?.remove();
1381
- this.root = null;
1382
- this.styleEl = null;
1383
- }
1384
- /**
1385
- * Announces a message to screen readers via a visually-hidden `aria-live="polite"` region.
1386
- *
1387
- * The region is created on first call and reused. The message is cleared then
1388
- * re-set via `requestAnimationFrame` to guarantee the change is detected by
1389
- * AT even if the same string is announced twice in a row.
1390
- *
1391
- * @param message - The message to announce (plain text, not HTML).
1392
- */
1393
- announceToScreenReader(message) {
1394
- if (!isClient()) return;
1395
- let announcer = document.getElementById("consenti-announcer");
1396
- if (!announcer) {
1397
- announcer = document.createElement("div");
1398
- announcer.id = "consenti-announcer";
1399
- announcer.setAttribute("role", "status");
1400
- announcer.setAttribute("aria-live", "polite");
1401
- announcer.setAttribute("aria-atomic", "true");
1402
- announcer.style.cssText = "position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;";
1403
- document.body.appendChild(announcer);
1404
- }
1405
- announcer.textContent = "";
1406
- requestAnimationFrame(() => {
1407
- if (announcer) announcer.textContent = message;
1408
- });
1409
- }
1410
- };
1411
-
1412
- // src/core/consenti-setup.ts
1413
- var ConsentiSetup = class {
1414
- /**
1415
- * Creates and initialises the consent widget.
1416
- *
1417
- * Construction is synchronous; the async `init()` is launched in the background.
1418
- * Use `await widget.ready` or `widget.onReady(cb)` to run code after init completes.
1419
- *
1420
- * @param config - Widget configuration. See `ConsentiConfig` for all options.
1421
- */
1422
- constructor(config) {
1423
- this.config = config;
1424
- this.initialized = false;
1425
- this.initializing = false;
1426
- this.profile = null;
1427
- this.consentStore = null;
1428
- this.eventBus = null;
1429
- this.banner = null;
1430
- this.modal = null;
1431
- this.renderer = null;
1432
- this.broadcastChannel = new ConsentiChannel();
1433
- this.plugins = [];
1434
- /** Tracks which banner variant is currently visible, or null when hidden. */
1435
- this._bannerVariant = null;
1436
- this.readyPromise = new Promise((resolve) => {
1437
- this.readyResolve = resolve;
1438
- });
1439
- if (!isClient()) return;
1440
- if (config.api?.enabled && config.core.storage === "localStorage") {
1441
- console.warn(
1442
- "[Consenti] localStorage storage mode cannot be used with API mode (API sets cookies via Set-Cookie header). Falling back to cookie storage."
1443
- );
1444
- config.core.storage = "cookie";
1445
- }
1446
- if (config.autoInit !== false) {
1447
- this.init().catch((err) => console.warn("[Consenti] Init error:", err));
1448
- }
1449
- }
1450
- profileId() {
1451
- return this.config.core.profileId ?? 0;
1452
- }
1453
- /**
1454
- * Starts widget initialisation. Called automatically unless `autoInit: false` is set.
1455
- * Can be called explicitly after construction when `autoInit: false`, or after `destroy()`
1456
- * to re-initialise the same instance.
1457
- */
1458
- async init() {
1459
- if (!isClient()) return;
1460
- if (this.initializing) return;
1461
- this.initializing = true;
1462
- this.initialized = false;
1463
- this.readyPromise = new Promise((resolve) => {
1464
- this.readyResolve = resolve;
1465
- });
1466
- try {
1467
- const storageMode = this.config.core.storage ?? "cookie";
1468
- const userId = this.config.core.userId ?? ConsentStore.getOrCreateUserId(this.profileId(), storageMode);
1469
- this.profile = await resolveProfile(this.config);
1470
- if (this.config.profileOverride) {
1471
- this.profile = { ...this.profile, ...this.config.profileOverride };
1472
- }
1473
- this.eventBus = new EventBus(this.profileId(), this.config.utils?.gtm);
1474
- this.consentStore = new ConsentStore(
1475
- userId,
1476
- this.profileId(),
1477
- storageMode,
1478
- this.config.core.cookieDomains
1479
- );
1480
- this.renderer = new Renderer();
1481
- if (!this.config.core.disableCssTemplate && CONSENTI_CSS) {
1482
- this.renderer.injectStyles(CONSENTI_CSS);
1483
- }
1484
- this.banner = new Banner();
1485
- this.modal = new Modal();
1486
- this.applyTheme();
1487
- this.applyDarkMode();
1488
- this.broadcastChannel.connect((msg) => this.handleBroadcastMessage(msg));
1489
- await this.runPlugins();
1490
- const signingKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
1491
- const existing = this.config.core.signCookies && signingKey ? await this.consentStore.readAndVerify(signingKey) : this.consentStore.read();
1492
- const hasExisting = existing !== null;
1493
- this.eventBus.dispatch("consenti:bannerInitialized", {
1494
- profileId: this.profileId(),
1495
- hasExistingConsent: hasExisting
1496
- });
1497
- const gpcMode = this.config.core.autoHonorGPC ?? false;
1498
- if (gpcMode && detectGPC()) {
1499
- const gpcConsent = applyGPCToConsent(this.profile.cookies, {}, gpcMode);
1500
- if (gpcMode === "strict") {
1501
- await this.submitConsent(gpcConsent, true);
1502
- this.initialized = true;
1503
- this.readyResolve();
1504
- return;
1505
- }
1506
- if (!hasExisting) {
1507
- const gpcSigningKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
1508
- if (this.config.core.signCookies && gpcSigningKey && this.consentStore) {
1509
- this.lastSignature = await this.consentStore.writeAndSign(gpcConsent, gpcSigningKey);
1510
- } else {
1511
- this.consentStore?.write(gpcConsent);
1512
- }
1513
- this.broadcastChannel.send({
1514
- type: "consentUpdated",
1515
- consent: gpcConsent,
1516
- profileId: this.profileId()
1517
- });
1518
- this.eventBus?.dispatch("consenti:consentSubmitted", {
1519
- consent: gpcConsent,
1520
- gpcDetected: true,
1521
- consentId: crypto.randomUUID(),
1522
- consentAction: this.deriveAction(gpcConsent, false),
1523
- pageUrl: window.location.href
1524
- });
1525
- this.showBanner(true);
1526
- this.initialized = true;
1527
- this.readyResolve();
1528
- return;
1529
- }
1530
- }
1531
- const regulation = this.config.core.regulation ?? "gdpr";
1532
- if (regulation === "ccpa" && !hasExisting) {
1533
- const defaultConsent = {};
1534
- for (const cookie of this.profile.cookies) {
1535
- defaultConsent[cookie.id] = "granted";
1536
- }
1537
- const ccpaSigKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
1538
- if (this.config.core.signCookies && ccpaSigKey) {
1539
- await this.consentStore.writeAndSign(defaultConsent, ccpaSigKey);
1540
- } else {
1541
- this.consentStore.write(defaultConsent);
1542
- }
1543
- this.initialized = true;
1544
- this.readyResolve();
1545
- return;
1546
- }
1547
- if (regulation === "cpra" && !hasExisting) {
1548
- const gpcActive = this.config.core.autoHonorGPC && detectGPC();
1549
- const defaultConsent = {};
1550
- for (const cookie of this.profile.cookies) {
1551
- if (cookie.mandatory) {
1552
- defaultConsent[cookie.id] = "granted";
1553
- continue;
1554
- }
1555
- if (cookie.cpraCategory === "sensitive") {
1556
- defaultConsent[cookie.id] = "denied";
1557
- } else if (gpcActive && (cookie.cpraCategory === "sale" || cookie.cpraCategory === "sharing")) {
1558
- defaultConsent[cookie.id] = "denied";
1559
- } else {
1560
- defaultConsent[cookie.id] = "granted";
1561
- }
1562
- }
1563
- const sigKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
1564
- if (this.config.core.signCookies && sigKey) {
1565
- await this.consentStore.writeAndSign(defaultConsent, sigKey);
1566
- } else {
1567
- this.consentStore.write(defaultConsent);
1568
- }
1569
- this.initialized = true;
1570
- this.readyResolve();
1571
- return;
1572
- }
1573
- if (!hasExisting) {
1574
- this.showBanner();
1575
- }
1576
- this.initialized = true;
1577
- this.readyResolve();
1578
- } finally {
1579
- this.initializing = false;
1580
- }
1581
- }
1582
- /** Applies the `consenti-root--dark` class based on config or profile flag. */
1583
- applyDarkMode() {
1584
- if (!this.renderer) return;
1585
- const isDark = this.config.darkMode ?? this.profile?.darkMode ?? false;
1586
- const root = this.renderer.mount(this.config.rootEl);
1587
- if (isDark) {
1588
- root.classList.add("consenti-root--dark");
1589
- } else {
1590
- root.classList.remove("consenti-root--dark");
1591
- }
1592
- }
1593
- /**
1594
- * Injects CSS custom property overrides supplied via `core.theme` into the
1595
- * Consenti root element so they cascade down to all widget elements.
1596
- */
1597
- applyTheme() {
1598
- const theme = this.config.core.theme;
1599
- if (!theme || !this.renderer) return;
1600
- const map = {
1601
- "--consenti-color-bg": theme.bgColor,
1602
- "--consenti-color-text": theme.textColor,
1603
- "--consenti-color-primary": theme.primaryColor,
1604
- "--consenti-color-primary-text": theme.primaryTextColor,
1605
- "--consenti-color-secondary": theme.secondaryColor,
1606
- "--consenti-color-secondary-text": theme.secondaryTextColor,
1607
- "--consenti-color-border": theme.borderColor,
1608
- "--consenti-font-family": theme.fontFamily,
1609
- "--consenti-font-size-base": theme.fontSizeBase,
1610
- "--consenti-font-size-heading": theme.fontSizeHeading,
1611
- "--consenti-border-radius": theme.borderRadius,
1612
- "--consenti-border-radius-btn": theme.buttonBorderRadius,
1613
- "--consenti-toggle-bg-on": theme.toggleBgOn,
1614
- "--consenti-toggle-bg-off": theme.toggleBgOff,
1615
- "--consenti-color-accent": theme.accentColor,
1616
- "--consenti-color-accent-text": theme.accentTextColor
1617
- };
1618
- const root = this.renderer.mount(this.config.rootEl);
1619
- for (const [prop, value] of Object.entries(map)) {
1620
- if (value !== void 0) root.style.setProperty(prop, value);
1621
- }
1622
- if (theme.fontSizeMultiplier) {
1623
- const mult = parseFloat(theme.fontSizeMultiplier);
1624
- if (!isNaN(mult)) {
1625
- const baseSize = parseFloat(getComputedStyle(root).getPropertyValue("--consenti-font-size-base") || "14");
1626
- const headingSize = parseFloat(getComputedStyle(root).getPropertyValue("--consenti-font-size-heading") || "16");
1627
- root.style.setProperty("--consenti-font-size-base", `${Math.round(baseSize * mult)}px`);
1628
- root.style.setProperty("--consenti-font-size-heading", `${Math.round(headingSize * mult)}px`);
1629
- }
1630
- }
1631
- }
1632
- async runPlugins() {
1633
- this.plugins = this.config.plugins ?? [];
1634
- for (const plugin of this.plugins) {
1635
- try {
1636
- await plugin.initialize(this);
1637
- } catch (err) {
1638
- console.warn("[Consenti] Plugin initialize error:", err);
1639
- }
1640
- }
1641
- }
1642
- /**
1643
- * Invokes an optional plugin hook on all registered plugins.
1644
- * Async hooks are awaited; errors are caught and logged so a broken plugin
1645
- * never interferes with the consent flow.
1646
- *
1647
- * @param invoke - Function that calls the desired hook method on a single plugin.
1648
- */
1649
- async callPluginHook(invoke) {
1650
- for (const plugin of this.plugins) {
1651
- try {
1652
- await invoke(plugin, this);
1653
- } catch (err) {
1654
- console.warn("[Consenti] Plugin hook failed:", err);
1655
- }
1656
- }
1657
- }
1658
- buildHandlers(context) {
1659
- return {
1660
- onGrantAll: () => {
1661
- const consent = {};
1662
- for (const cookie of this.profile?.cookies ?? []) {
1663
- consent[cookie.id] = "granted";
1664
- }
1665
- void this.submitConsent(consent);
1666
- if (context === "banner") {
1667
- this.banner?.hide();
1668
- this._bannerVariant = null;
1669
- }
1670
- },
1671
- onDenyAll: () => {
1672
- const consent = {};
1673
- for (const cookie of this.profile?.cookies ?? []) {
1674
- consent[cookie.id] = cookie.mandatory ? "granted" : "denied";
1675
- }
1676
- void this.submitConsent(consent);
1677
- if (context === "banner") {
1678
- this.banner?.hide();
1679
- this._bannerVariant = null;
1680
- }
1681
- },
1682
- onSubmit: () => {
1683
- if (context === "modal" && this.modal && this.profile) {
1684
- const consent = this.modal.getToggleConsent(this.profile.preferenceModal.categories);
1685
- void this.submitConsent(consent);
1686
- this.hideModal();
1687
- } else {
1688
- const consent = {};
1689
- for (const cookie of this.profile?.cookies ?? []) {
1690
- if (cookie.mandatory) consent[cookie.id] = "granted";
1691
- }
1692
- void this.submitConsent(consent);
1693
- if (context === "banner") {
1694
- this.banner?.hide();
1695
- this._bannerVariant = null;
1696
- }
1697
- }
1698
- },
1699
- onManage: () => {
1700
- this.showModal();
1701
- },
1702
- onClose: () => {
1703
- if (context === "modal") {
1704
- this.hideModal();
1705
- } else {
1706
- this.banner?.hide();
1707
- this._bannerVariant = null;
1708
- this.broadcastChannel.send({ type: "bannerClosed" });
1709
- this.eventBus?.dispatch("consenti:bannerVisibility", { show: false, action: true });
1710
- void this.callPluginHook((p) => p.onBannerHide?.());
1711
- }
1712
- },
1713
- onGrantSpecific: (cookieIds) => {
1714
- const consent = {};
1715
- for (const cookie of this.profile?.cookies ?? []) {
1716
- if (cookie.mandatory || cookieIds.includes(cookie.id)) {
1717
- consent[cookie.id] = "granted";
1718
- } else {
1719
- consent[cookie.id] = "denied";
1720
- }
1721
- }
1722
- void this.submitConsent(consent);
1723
- if (context === "banner") this.hideBanner();
1724
- }
1725
- };
1726
- }
1727
- /**
1728
- * Classifies a consent submission as `accept_all`, `reject_all`, `custom`, or `update`.
1729
- * Used to enrich events and API payloads with intent metadata.
1730
- */
1731
- deriveAction(fullConsent, hadExistingConsent) {
1732
- if (hadExistingConsent) return "update";
1733
- const nonMandatory = this.profile?.cookies.filter((c) => !c.mandatory) ?? [];
1734
- if (nonMandatory.length === 0) return "custom";
1735
- const allGranted = nonMandatory.every((c) => fullConsent[c.id] === "granted");
1736
- const allDenied = nonMandatory.every(
1737
- (c) => fullConsent[c.id] === "denied" || fullConsent[c.id] === "objected"
1738
- );
1739
- if (allGranted) return "accept_all";
1740
- if (allDenied) return "reject_all";
1741
- return "custom";
1742
- }
1743
- handleBroadcastMessage(msg) {
1744
- switch (msg.type) {
1745
- case "consentUpdated":
1746
- if (msg.profileId !== this.profileId()) return;
1747
- this.consentStore?.setInMemory(msg.consent);
1748
- this.banner?.hide();
1749
- if (this.modal?.isOpen()) this.modal.close();
1750
- this.eventBus?.dispatch("consenti:consentSubmitted", {
1751
- consent: msg.consent,
1752
- fromBroadcast: true
1753
- });
1754
- break;
1755
- case "bannerClosed":
1756
- this.banner?.hide();
1757
- this._bannerVariant = null;
1758
- break;
1759
- case "consentDeleted":
1760
- this.consentStore?.clearInMemory();
1761
- break;
1762
- }
1763
- }
1764
- // ─── Public API ──────────────────────────────────────────────────────────────
1765
- /**
1766
- * Returns `true` if the visitor has saved consent for the active profile.
1767
- * Always returns `false` during SSR.
1768
- */
1769
- hasConsent() {
1770
- if (!isClient()) return false;
1771
- return this.consentStore?.hasConsent() ?? false;
1772
- }
1773
- /**
1774
- * Returns the stored consent map, or `null` if no consent has been saved.
1775
- * Always returns `null` during SSR.
1776
- */
1777
- getConsent() {
1778
- if (!isClient()) return null;
1779
- return this.consentStore?.getConsent() ?? null;
1780
- }
1781
- /**
1782
- * Returns the `Date` when consent was last saved, or `false` if no consent exists.
1783
- * Always returns `false` during SSR.
1784
- */
1785
- getConsentDate() {
1786
- if (!isClient()) return false;
1787
- return this.consentStore?.getConsentDate() ?? false;
1788
- }
1789
- /**
1790
- * Returns the consent map in Google Consent Mode v2 format, ready to pass to
1791
- * `gtag('consent', 'update', ...)`. Returns `null` if no consent has been saved.
1792
- *
1793
- * Maps Consenti cookie IDs to the seven Google Consent Mode v2 parameters.
1794
- * Cookies whose IDs exactly match a GCM v2 key are forwarded directly.
1795
- * Non-GCM cookies are ignored by this method (they appear in `getConsent()` only).
1796
- */
1797
- getGTMConsent() {
1798
- const consent = this.getConsent();
1799
- if (!consent) return null;
1800
- const gcmKeys = [
1801
- "ad_storage",
1802
- "analytics_storage",
1803
- "ad_user_data",
1804
- "ad_personalization",
1805
- "functionality_storage",
1806
- "personalization_storage",
1807
- "security_storage"
1808
- ];
1809
- const result = {};
1810
- for (const key of gcmKeys) {
1811
- const raw = consent[key];
1812
- if (raw === "granted") {
1813
- result[key] = "granted";
1814
- } else if (raw === "denied" || raw === "objected") {
1815
- result[key] = "denied";
1816
- } else {
1817
- const safeDefault = key === "functionality_storage" || key === "security_storage" ? "granted" : "denied";
1818
- result[key] = safeDefault;
1819
- }
1820
- }
1821
- return result;
1822
- }
1823
- /**
1824
- * Records and persists the user's consent choices.
1825
- *
1826
- * Mandatory cookies are always forced to `'granted'` regardless of what is passed.
1827
- * Non-mandatory cookies not present in `consent` default to `'denied'`.
1828
- *
1829
- * @param consent - Partial or full map of cookie ID → status.
1830
- * @param gpcDerived - Internal flag set to `true` when called from the GPC strict path.
1831
- */
1832
- async submitConsent(consent, gpcDerived = false) {
1833
- if (!isClient() || !this.profile || !this.consentStore) return;
1834
- const regulation = this.config.core.regulation ?? "gdpr";
1835
- const hadExistingConsent = this.consentStore.hasConsent();
1836
- const consentId = crypto.randomUUID();
1837
- const pageUrl = window.location.href;
1838
- const fullConsent = {};
1839
- for (const cookie of this.profile.cookies) {
1840
- if (cookie.mandatory) {
1841
- fullConsent[cookie.id] = "granted";
1842
- } else if (regulation === "cpra") {
1843
- const fallback = cookie.cpraCategory === "sensitive" ? "denied" : "granted";
1844
- fullConsent[cookie.id] = consent[cookie.id] ?? fallback;
1845
- } else {
1846
- fullConsent[cookie.id] = consent[cookie.id] ?? (regulation === "ccpa" ? "granted" : "denied");
1847
- }
1848
- }
1849
- const consentAction = this.deriveAction(fullConsent, hadExistingConsent);
1850
- this.eventBus?.dispatch("consenti:consentBeingSubmitted", { consent: fullConsent, consentId });
1851
- let apiResponse;
1852
- if (this.config.api?.enabled) {
1853
- try {
1854
- const base = this.config.api.baseUrl ?? window.location.origin;
1855
- const { httpRequest: httpRequest2 } = await Promise.resolve().then(() => (init_http(), http_exports));
1856
- apiResponse = await httpRequest2(
1857
- `${base}/consenti/api/v1/consent`,
1858
- {
1859
- method: "POST",
1860
- body: JSON.stringify({
1861
- profileId: this.profileId(),
1862
- consent: fullConsent,
1863
- locale: this.config.core.locale ?? "en",
1864
- gpcDetected: gpcDerived || detectGPC(),
1865
- consentId,
1866
- action: consentAction,
1867
- pageUrl
1868
- })
1869
- },
1870
- this.config.api.authToken
1871
- );
1872
- } catch (err) {
1873
- console.warn("[Consenti] API consent submission failed:", err);
1874
- }
1875
- }
1876
- const signingKey = this.profile.cookieSigningKey ?? this.config.core.cookieSigningKey;
1877
- if (this.config.core.signCookies && signingKey) {
1878
- this.lastSignature = await this.consentStore.writeAndSign(fullConsent, signingKey);
1879
- } else {
1880
- this.consentStore.write(fullConsent);
1881
- }
1882
- const receiptRequested = this.modal?.isReceiptRequested() ?? false;
1883
- if (receiptRequested && this.config.core.allowReceipt) {
1884
- const nameParts = this.consentStore.cookieName.split("_");
1885
- const visitorId = nameParts.slice(1, -1).join("_");
1886
- const receiptParams = {
1887
- visitorId,
1888
- profileId: this.profileId(),
1889
- profileVersion: this.profile.version,
1890
- locale: this.config.core.locale ?? "en",
1891
- consent: fullConsent,
1892
- ...this.lastSignature ? { signature: this.lastSignature } : {}
1893
- };
1894
- const receipt = generateReceipt(receiptParams);
1895
- downloadReceipt(receipt);
1896
- }
1897
- this.broadcastChannel.send({
1898
- type: "consentUpdated",
1899
- consent: fullConsent,
1900
- profileId: this.profileId()
1901
- });
1902
- this.eventBus?.dispatch("consenti:consentSubmitted", {
1903
- consent: fullConsent,
1904
- apiResponse,
1905
- gpcDetected: gpcDerived,
1906
- consentId,
1907
- consentAction,
1908
- pageUrl
1909
- });
1910
- await this.callPluginHook((p) => p.onConsentSubmit?.(fullConsent));
1911
- this.renderer?.announceToScreenReader("Your cookie preferences have been saved.");
1912
- this.hideBanner();
1913
- if (this.modal?.isOpen()) this.hideModal();
1914
- }
1915
- /**
1916
- * Returns the currently visible banner variant, or `false` if no banner is shown.
1917
- * Useful for programmatically checking state before calling `showBanner` / `hideBanner`.
1918
- */
1919
- bannerVisibility() {
1920
- return this._bannerVariant ?? false;
1921
- }
1922
- /**
1923
- * Returns `'preference'` when the preference modal is open, or `false` when closed.
1924
- */
1925
- modalVisibility() {
1926
- return this.modal?.isOpen() ? "preference" : false;
1927
- }
1928
- /**
1929
- * Mounts and shows the consent banner.
1930
- *
1931
- * No-ops if the requested variant is already visible. If a different variant is
1932
- * currently shown (e.g. switching from main to GPC), the existing banner is replaced.
1933
- *
1934
- * @param useGpcVariant - When `true` and the profile has a `gpcBanner`, that variant
1935
- * is shown instead of `mainBanner`. Used by the GPC flow.
1936
- */
1937
- showBanner(useGpcVariant = false) {
1938
- if (!isClient() || !this.profile || !this.banner || !this.renderer) return;
1939
- const variant = useGpcVariant && this.profile.gpcBanner ? "gpc" : "main";
1940
- if (this._bannerVariant === variant) return;
1941
- const bannerConfig = variant === "gpc" ? this.profile.gpcBanner : this.profile.mainBanner;
1942
- const root = this.renderer.mount(this.config.rootEl);
1943
- const handlers = this.buildHandlers("banner");
1944
- this.banner.remove();
1945
- const locales = this.profile.locales;
1946
- const activeLocale = this.config.core.locale ?? "en";
1947
- const bannerEl = this.banner.build(
1948
- bannerConfig,
1949
- handlers,
1950
- this.config.core.privacyPolicyUrl,
1951
- locales,
1952
- activeLocale,
1953
- (locale) => this.switchLocale(locale)
1954
- );
1955
- root.appendChild(bannerEl);
1956
- const overlay = this.banner.getOverlay();
1957
- if (overlay) root.appendChild(overlay);
1958
- this._bannerVariant = variant;
1959
- this.eventBus?.dispatch("consenti:bannerVisibility", { show: true, action: false });
1960
- void this.callPluginHook((p) => p.onBannerShow?.());
1961
- }
1962
- /** Hides the banner and notifies other tabs via BroadcastChannel. No-op if already hidden. */
1963
- hideBanner() {
1964
- if (this._bannerVariant === null) return;
1965
- this.banner?.hide();
1966
- this._bannerVariant = null;
1967
- this.broadcastChannel.send({ type: "bannerClosed" });
1968
- this.eventBus?.dispatch("consenti:bannerVisibility", { show: false, action: true });
1969
- this.renderer?.announceToScreenReader("Cookie banner dismissed.");
1970
- void this.callPluginHook((p) => p.onBannerHide?.());
1971
- }
1972
- /**
1973
- * Opens the preference modal and hides the banner while the modal is open.
1974
- * No-ops if the modal is already open.
1975
- * If the modal is closed without saving consent, the banner is re-shown automatically
1976
- * (see `hideModal()`). Only one banner or modal is visible at a time.
1977
- *
1978
- * @param triggerEl - The element that triggered the modal open. Focus returns here on close.
1979
- */
1980
- showModal(triggerEl) {
1981
- if (!isClient() || !this.profile || !this.modal) return;
1982
- if (this.modal.isOpen()) return;
1983
- const handlers = this.buildHandlers("modal");
1984
- const existing = this.consentStore?.getConsent() ?? {};
1985
- const regulation = this.config.core.regulation ?? "gdpr";
1986
- const dpdpaCfg = regulation === "dpdpa" ? this.profile.dpdpa : void 0;
1987
- const locales = this.profile.locales;
1988
- const activeLocale = this.config.core.locale ?? "en";
1989
- this.modal.build(
1990
- this.profile.preferenceModal,
1991
- existing,
1992
- handlers,
1993
- this.config.core.allowReceipt ?? false,
1994
- dpdpaCfg,
1995
- locales,
1996
- activeLocale,
1997
- (locale) => {
1998
- this.hideModal();
1999
- this.switchLocale(locale);
2000
- }
2001
- );
2002
- this.banner?.hide();
2003
- this.modal.open(triggerEl);
2004
- const isDark = this.config.darkMode ?? this.profile?.darkMode ?? false;
2005
- document.getElementById("consenti-modal")?.classList.toggle("consenti-root--dark", isDark);
2006
- this.eventBus?.dispatch("consenti:modalVisibility", { show: true, action: true });
2007
- void this.callPluginHook((p) => p.onModalShow?.());
2008
- }
2009
- /**
2010
- * Closes the preference modal and returns focus to the element that opened it.
2011
- * No-ops if the modal is already closed.
2012
- * If the user has not yet given consent, the banner is re-shown automatically.
2013
- */
2014
- hideModal() {
2015
- if (!this.modal?.isOpen()) return;
2016
- this.modal.close();
2017
- this.eventBus?.dispatch("consenti:modalVisibility", { show: false, action: true });
2018
- void this.callPluginHook((p) => p.onModalHide?.());
2019
- if (!this.consentStore?.hasConsent() && this._bannerVariant !== null) {
2020
- this.banner?.show();
2021
- } else if (!this.consentStore?.hasConsent()) {
2022
- this.showBanner();
2023
- }
2024
- }
2025
- /**
2026
- * Registers a callback to run once the widget has finished initialising.
2027
- * If the widget is already initialised, the callback is invoked synchronously.
2028
- *
2029
- * @param callback - Function to call when the widget is ready.
2030
- */
2031
- onReady(callback) {
2032
- if (this.initialized) {
2033
- callback();
2034
- } else {
2035
- void this.readyPromise.then(callback);
2036
- }
2037
- }
2038
- /**
2039
- * A `Promise` that resolves once the widget has finished initialising.
2040
- * Equivalent to wrapping `onReady()` in a `Promise`.
2041
- *
2042
- * @example
2043
- * ```ts
2044
- * await widget.ready
2045
- * console.log('Widget ready')
2046
- * ```
2047
- */
2048
- get ready() {
2049
- return this.readyPromise;
2050
- }
2051
- /**
2052
- * Deletes the stored consent record and notifies other tabs.
2053
- * The banner will reappear on the next page load (or immediately if the
2054
- * receiving tab calls `showBanner()` in response to `consentDeleted`).
2055
- */
2056
- async deleteConsent() {
2057
- if (!isClient() || !this.consentStore) return;
2058
- if (this.config.api?.enabled) {
2059
- try {
2060
- const base = this.config.api.baseUrl ?? window.location.origin;
2061
- const visitorId = this.consentStore.cookieName;
2062
- const { httpRequest: httpRequest2 } = await Promise.resolve().then(() => (init_http(), http_exports));
2063
- await httpRequest2(
2064
- `${base}/consenti/api/v1/consent/${visitorId}`,
2065
- { method: "DELETE" },
2066
- this.config.api?.authToken
2067
- );
2068
- } catch (err) {
2069
- console.warn("[Consenti] API consent deletion failed:", err);
2070
- }
2071
- }
2072
- this.consentStore.delete();
2073
- this.broadcastChannel.send({ type: "consentDeleted" });
2074
- }
2075
- /**
2076
- * Removes existing consent and immediately re-shows the consent banner so the user
2077
- * can review and update their preferences.
2078
- *
2079
- * Equivalent to calling `deleteConsent()` followed by `showBanner()`, but in a single
2080
- * method so callers do not need to coordinate the async deletion with the banner display.
2081
- *
2082
- * Use this to power "Review my consent" links in footers or privacy dashboards.
2083
- */
2084
- async reConsent() {
2085
- await this.deleteConsent();
2086
- this.showBanner();
2087
- }
2088
- /**
2089
- * Returns the fully resolved profile for the current session, or `null` during the
2090
- * async init phase (before the profile has been fetched/resolved).
2091
- *
2092
- * Plugins can use this to read cookie definitions, banner config, modal categories,
2093
- * and the profile version without accessing internal state.
2094
- */
2095
- getProfile() {
2096
- return this.profile;
2097
- }
2098
- /**
2099
- * Returns the `#consenti-root` container element that wraps all widget DOM,
2100
- * or `null` before the widget has mounted or after `destroy()`.
2101
- *
2102
- * Plugins can use this to append custom elements, apply extra CSS variables,
2103
- * or observe mutations within the widget container.
2104
- */
2105
- getRootElement() {
2106
- if (!isClient()) return null;
2107
- if (this.config.rootEl) {
2108
- if (typeof this.config.rootEl === "string") {
2109
- return document.querySelector(this.config.rootEl);
2110
- }
2111
- return this.config.rootEl;
2112
- }
2113
- return document.getElementById("consenti-root");
2114
- }
2115
- /**
2116
- * Returns the `#consenti-banner` element when it is present in the DOM, or `null`.
2117
- * The element may be hidden (`display: none`) — check `bannerVisibility()` for visibility state.
2118
- *
2119
- * Useful for adding extra content, custom event listeners, or mutation observers on the banner.
2120
- */
2121
- getBannerElement() {
2122
- if (!isClient()) return null;
2123
- return document.getElementById("consenti-banner");
2124
- }
2125
- /**
2126
- * Returns the `#consenti-modal` element when the modal has been built, or `null`.
2127
- * The element is built on first `showModal()` call and reused on subsequent opens.
2128
- *
2129
- * Useful for injecting extra content into the modal or listening to modal-specific events.
2130
- */
2131
- getModalElement() {
2132
- if (!isClient()) return null;
2133
- return document.getElementById("consenti-modal");
2134
- }
2135
- /**
2136
- * Switches the active locale, re-resolves the profile, and re-renders the widget.
2137
- * Called by the locale switcher UI when the user selects a different language.
2138
- *
2139
- * @param locale - BCP 47 locale code to switch to (e.g. `'fr'`, `'de-AT'`).
2140
- */
2141
- switchLocale(locale) {
2142
- if (!isClient()) return;
2143
- this.config.core.locale = locale;
2144
- this.banner?.remove();
2145
- if (this.modal?.isOpen()) this.modal.close();
2146
- this._bannerVariant = null;
2147
- this.profile = null;
2148
- this.initialized = false;
2149
- this.readyPromise = new Promise((resolve) => {
2150
- this.readyResolve = resolve;
2151
- });
2152
- this.init().catch((err) => console.warn("[Consenti] Locale switch error:", err));
2153
- }
2154
- /**
2155
- * Destroys the widget — removes all DOM elements, disconnects the BroadcastChannel,
2156
- * and calls `destroy()` on all registered plugins.
2157
- *
2158
- * After `destroy()`, the instance should not be used again.
2159
- */
2160
- destroy() {
2161
- for (const plugin of this.plugins) {
2162
- try {
2163
- plugin.destroy();
2164
- } catch {
2165
- }
2166
- }
2167
- this.modal?.destroy();
2168
- this.renderer?.unmount();
2169
- this.broadcastChannel.disconnect();
2170
- this.banner = null;
2171
- this.modal = null;
2172
- this.renderer = null;
2173
- this.consentStore = null;
2174
- this.eventBus = null;
2175
- this.initialized = false;
2176
- }
2177
- };
2178
-
2179
- // src/plugins/plugin.base.ts
2180
- var ConsentiPlugin = class {
2181
- };
2182
-
2183
- // src/profile/consenti-profile.ts
2184
- var nextLocalId = 1e3;
2185
- var ConsentiProfile = class {
2186
- /**
2187
- * @param config - Full profile configuration including cookies, locales, and UI text.
2188
- */
2189
- constructor(config) {
2190
- this.config = config;
2191
- this.id = nextLocalId++;
2192
- registerProfile(this.id, config);
2193
- }
2194
- /** Returns the auto-assigned numeric profile ID. Pass this to `core.profileId`. */
2195
- getId() {
2196
- return this.id;
2197
- }
2198
- /** Returns the raw `ProfileConfig` passed to the constructor. */
2199
- toJSON() {
2200
- return this.config;
2201
- }
2202
- };
2203
-
2204
- // src/utils/consent-script.ts
2205
- var ConsentScript = class {
2206
- constructor(options) {
2207
- this.options = options;
2208
- this.scriptEl = null;
2209
- this.handler = () => this.evaluate();
2210
- if (!isClient()) return;
2211
- window.addEventListener("consenti:consentSubmitted", this.handler);
2212
- this.evaluate();
2213
- }
2214
- evaluate() {
2215
- const consent = this.options.widget.getConsent();
2216
- const granted = consent?.[this.options.cookieId] === "granted";
2217
- if (granted && !this.scriptEl) {
2218
- this.inject();
2219
- } else if (!granted && this.scriptEl) {
2220
- this.revoke();
2221
- }
2222
- }
2223
- inject() {
2224
- const script = document.createElement("script");
2225
- if (this.options.src) script.src = this.options.src;
2226
- if (this.options.innerHTML) script.innerHTML = this.options.innerHTML;
2227
- for (const [key, value] of Object.entries(this.options.attributes ?? {})) {
2228
- script.setAttribute(key, value);
2229
- }
2230
- document.head.appendChild(script);
2231
- this.scriptEl = script;
2232
- this.options.onLoad?.();
2233
- }
2234
- revoke() {
2235
- this.scriptEl?.remove();
2236
- this.scriptEl = null;
2237
- this.options.onRevoke?.();
2238
- }
2239
- /**
2240
- * Removes the event listener and the injected `<script>` element (if present).
2241
- * Call this when the widget is destroyed or the script is no longer needed.
2242
- */
2243
- destroy() {
2244
- if (!isClient()) return;
2245
- window.removeEventListener("consenti:consentSubmitted", this.handler);
2246
- this.revoke();
2247
- }
2248
- };
2249
-
2250
- // src/utils/cookie-trigger.ts
2251
- var CookieTrigger = class {
2252
- constructor(options) {
2253
- const action = options.action ?? "modal";
2254
- this.clickHandler = () => {
2255
- if (action === "modal") options.widget.showModal();
2256
- else options.widget.showBanner();
2257
- };
2258
- if (!options.el) {
2259
- const btn = document.createElement("button");
2260
- btn.type = "button";
2261
- btn.className = "consenti-btn consenti-btn--text consenti-cookie-trigger";
2262
- btn.textContent = options.label ?? "Cookie Settings";
2263
- btn.setAttribute("aria-label", options.label ?? "Open cookie settings");
2264
- this.el = btn;
2265
- this.created = true;
2266
- } else if (typeof options.el === "string") {
2267
- if (!isClient()) {
2268
- this.el = document.createElement("span");
2269
- this.created = false;
2270
- return;
2271
- }
2272
- const found = document.querySelector(options.el);
2273
- if (!found) {
2274
- throw new Error(
2275
- `[Consenti] CookieTrigger: element not found for selector "${options.el}"`
2276
- );
2277
- }
2278
- this.el = found;
2279
- this.created = false;
2280
- } else {
2281
- this.el = options.el;
2282
- this.created = false;
2283
- }
2284
- this.el.addEventListener("click", this.clickHandler);
2285
- }
2286
- /**
2287
- * Returns the trigger element.
2288
- * When no `el` was provided, append the returned element to the desired location
2289
- * in the DOM after construction.
2290
- */
2291
- getElement() {
2292
- return this.el;
2293
- }
2294
- /**
2295
- * Removes the click handler and, if this instance auto-created the button,
2296
- * removes it from the DOM.
2297
- */
2298
- destroy() {
2299
- this.el.removeEventListener("click", this.clickHandler);
2300
- if (this.created) this.el.remove();
2301
- }
2302
- };
2303
- return __toCommonJS(src_exports);
2304
- })();
1
+ "use strict";var Consenti=(()=>{var W=Object.defineProperty;var Ue=Object.getOwnPropertyDescriptor;var je=Object.getOwnPropertyNames;var Ke=Object.prototype.hasOwnProperty;var ce=(i,e)=>()=>(i&&(e=i(i=0)),e);var J=(i,e)=>{for(var t in e)W(i,t,{get:e[t],enumerable:!0})},$e=(i,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of je(e))!Ke.call(i,o)&&o!==t&&W(i,o,{get:()=>e[o],enumerable:!(n=Ue(e,o))||n.enumerable});return i};var qe=i=>$e(W({},"__esModule",{value:!0}),i);var X={};J(X,{signValue:()=>ge,verifyValue:()=>We});async function Fe(i){let e=new TextEncoder().encode(i);return crypto.subtle.importKey("raw",e,{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}async function ge(i,e){let t=await Fe(e),n=new TextEncoder().encode(i),o=await crypto.subtle.sign("HMAC",t,n);return Array.from(new Uint8Array(o)).map(a=>a.toString(16).padStart(2,"0")).join("")}async function We(i,e,t){return await ge(i,t)===e}var ee=ce(()=>{"use strict"});var te={};J(te,{httpRequest:()=>L});async function L(i,e={},t){let n={"Content-Type":"application/json",...e.headers};t&&(n.Authorization=`Bearer ${t}`);let o=await fetch(i,{...e,headers:n});if(!o.ok)throw new Error(`[Consenti] HTTP ${o.status} ${o.statusText} \u2014 ${i}`);return o.json()}var I=ce(()=>{"use strict"});var vt={};J(vt,{ConsentScript:()=>$,ConsentiPlugin:()=>j,ConsentiProfile:()=>K,ConsentiSetup:()=>U,CookieTrigger:()=>q});var Z="#consenti-root{position:relative}.consenti-d-none{display:none}: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-secondary-border: #1a2e4a;--consenti-color-overlay: rgba(26, 52, 96, 0.55);--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-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-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-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: #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)}.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);border:1px solid 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}.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:500px}.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}.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-block:var(--consenti-spacing-md)}.consenti-banner__powered-by{position:absolute;right:10px;bottom:2px;opacity:.2;color:var(--consenti-color-primary)}@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:row;align-items:stretch;margin-bottom:var(--consenti-spacing-md)}.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}.consenti-modal__header-controls{display:flex;align-items:center;gap:4px;flex-shrink:0;position:absolute;right:15px;top:10px}.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-top:var(--consenti-spacing-sm);margin:0;color:#6b7280;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}.consenti-modal__receipt-desc{margin:4px 0 0 24px;font-size:12px;color:#6b7280}.consenti-modal__powered-by{opacity:.5;color:var(--consenti-color-primary)}@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:#949dab;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-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-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}";var l=()=>typeof window<"u"&&typeof document<"u",le=()=>l()?window:null;var pe=()=>l()&&typeof navigator<"u"?navigator:null;var A=["error"];function de(i){A=i}var b={info:(...i)=>{A.includes("info")&&console.info("[Consenti]",...i)},log:(...i)=>{A.includes("log")&&console.log("[Consenti]",...i)},warn:(...i)=>{A.includes("warning")&&console.warn("[Consenti]",...i)},error:(...i)=>{A.includes("error")&&console.error("[Consenti]",...i)}};var E=class{constructor(){this.channel=null}get isSupported(){return l()&&typeof BroadcastChannel<"u"}connect(e){this.isSupported&&(this.onMessageCallback=e,this.channel=new BroadcastChannel("consenti"),this.channel.onmessage=t=>{t.data?.type&&e(t.data)},this.channel.onmessageerror=()=>{b.warn("BroadcastChannel message error \u2014 ignoring")})}send(e){this.channel?.postMessage(e)}disconnect(){this.channel?.close(),this.channel=null,this.onMessageCallback=void 0}};function ue(i){let e={version:"1.0",issuedAt:new Date().toISOString(),visitorId:i.visitorId,profileId:i.profileId,profileVersion:i.profileVersion,locale:i.locale,consent:i.consent};return i.signature&&(e.signature=i.signature),e}function me(i){if(typeof window>"u"||typeof document>"u")return;let e=JSON.stringify(i,null,2),t=new Blob([e],{type:"application/json"}),n=URL.createObjectURL(t),o=document.createElement("a");o.href=n,o.download=`consenti-receipt-${i.issuedAt.slice(0,10)}.json`,o.style.display="none",document.body.appendChild(o),o.click(),document.body.removeChild(o),setTimeout(()=>URL.revokeObjectURL(n),1e3)}function ye(i){if(!l())return null;let e=document.cookie.match(new RegExp(`(?:^|;\\s*)${Ye(i)}=([^;]*)`));return e?decodeURIComponent(e[1]??""):null}function Q(i,e,t={}){if(!l())return;let n=t.secure??(typeof window<"u"&&typeof window.location<"u"&&window.location.protocol==="https:"),o=[`${encodeURIComponent(i)}=${encodeURIComponent(e)}`,`Path=${t.path??"/"}`,`SameSite=${t.sameSite??"Lax"}`,t.maxAge!=null?`Max-Age=${t.maxAge}`:"",n?"Secure":"",t.domain?`Domain=${t.domain}`:""];document.cookie=o.filter(Boolean).join("; ")}function fe(i,e={}){Q(i,"",{...e,maxAge:0})}function Ye(i){return i.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var D=class{constructor(e){this.mode=e;e==="localStorage"&&l()&&typeof localStorage>"u"&&(b.warn("localStorage is not available, falling back to cookie storage"),this.mode="cookie")}read(e){return l()?this.mode==="localStorage"?localStorage.getItem(e):ye(e):null}write(e,t,n={}){l()&&(this.mode==="localStorage"?localStorage.setItem(e,t):Q(e,t,n))}remove(e,t={}){l()&&(this.mode==="localStorage"?localStorage.removeItem(e):fe(e,t))}};function Je(i){return i.map(t=>t.expiry??365).reduce((t,n)=>Math.min(t,n),365)*24*60*60}var be="consenti";function Ze(i,e){return`${be}_${i}_${e}`}function he(i){let e=Math.floor(Date.now()/1e3),t=Object.entries(i).map(([n,o])=>`${n}:${o}`).join("|");return`t:${e}::${t}`}function ve(i){let e=i.split("::");if(e.length<2)return null;let t=e[0]??"",n;if(t.startsWith("t:")){let c=t.slice(2),u=parseInt(c,10);if(!c||isNaN(u))return null;n=new Date(u*1e3).toISOString()}else if(t.startsWith("ConsentTimestamp:")){if(n=t.replace("ConsentTimestamp:",""),!n)return null}else return null;let o=e[1]??"",a=e[2],r={};for(let c of o.split("|")){let u=c.indexOf(":");if(u===-1)return null;let d=c.slice(0,u),f=c.slice(u+1);if(!d||f!=="granted"&&f!=="denied"&&f!=="objected")return null;r[d]=f}let s={timestamp:n,consent:r};return a?.startsWith("sig:")&&(s.signature=a.slice(4)),s}var T=class i{constructor(e,t,n,o){this.userId=e;this.profileId=t;this.cookieDomains=o;this.cachedConsent=null;this.cachedTimestamp=null;this.cookies=[];this.storage=new D(n)}setProfileCookies(e){this.cookies=e}get cookieName(){return Ze(this.userId,this.profileId)}read(){let e=this.storage.read(this.cookieName);if(!e)return null;let t=ve(e);return t&&(this.cachedConsent=t.consent,this.cachedTimestamp=t.timestamp),t}async readAndVerify(e){let t=this.storage.read(this.cookieName);if(!t)return null;let n=t.split("::");if(n.length<2)return null;let o=ve(t);if(!o)return null;if(n.length===3&&(n[2]??"").startsWith("sig:")){let{verifyValue:r}=await Promise.resolve().then(()=>(ee(),X)),s=(n[0]??"")+"::"+(n[1]??""),c=(n[2]??"").slice(4);if(!await r(s,c,e))return b.warn("Cookie signature mismatch \u2014 consent cleared and banner will re-appear"),this.delete(),null}return this.cachedConsent=o.consent,this.cachedTimestamp=o.timestamp,o}write(e,t){let n=he(e);t&&(n=`${n}::sig:${t}`);let o=this.buildCookieOpts();this.storage.write(this.cookieName,n,o),this.cachedConsent=e,this.cachedTimestamp=new Date().toISOString()}async writeAndSign(e,t){let{signValue:n}=await Promise.resolve().then(()=>(ee(),X)),o=he(e),a=await n(o,t),r=`${o}::sig:${a}`,s=this.buildCookieOpts();return this.storage.write(this.cookieName,r,s),this.cachedConsent=e,this.cachedTimestamp=new Date().toISOString(),a}delete(){let e=this.buildCookieOpts();this.storage.remove(this.cookieName,e),this.cachedConsent=null,this.cachedTimestamp=null}hasConsent(){return this.cachedConsent?!0:this.read()!==null}getConsent(){return this.cachedConsent?this.cachedConsent:this.read()?.consent??null}getConsentDate(){if(this.cachedTimestamp)return new Date(this.cachedTimestamp);let e=this.read();return e?new Date(e.timestamp):!1}setInMemory(e){this.cachedConsent=e,this.cachedTimestamp=new Date().toISOString()}clearInMemory(){this.cachedConsent=null,this.cachedTimestamp=null}static getOrCreateUserId(e,t){if(typeof document>"u")return crypto.randomUUID();let n=`${be}_`,o=`_${e}`,a=t==="localStorage"?i._localStorageKeys():i._cookieNames();for(let r of a)if(r.startsWith(n)&&r.endsWith(o)){let s=r.slice(n.length,r.length-o.length);if(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(s))return s}return crypto.randomUUID()}static _cookieNames(){return document.cookie.split(";").map(e=>e.split("=")[0]?.trim()??"")}static _localStorageKeys(){return typeof localStorage>"u"?[]:Object.keys(localStorage)}buildCookieOpts(){let e={path:"/",sameSite:"Lax",maxAge:this.cookies.length>0?Je(this.cookies):31536e3};if(this.cookieDomains){let t=this.cookieDomains.split(",")[0]?.trim();t&&(e.domain=t)}return e}};var R=class{constructor(e,t){this.profileId=e;this.gtmConfig=t}dispatch(e,t={}){if(!l())return;let n=new CustomEvent(e,{detail:t,bubbles:!0});window.dispatchEvent(n),this.pushToGTM(e,t)}pushToGTM(e,t){let n=this.gtmConfig;if(!n)return;let o=le();if(!o)return;let a=n.dataLayer??"dataLayer",r=o[a];if(!(!Array.isArray(r)||!(!n.events||n.events.length===0||n.events.includes(e)))&&(r.push({event:e,consenti:{profileId:this.profileId,consent:t.consent,consentId:t.consentId,consentAction:t.consentAction}}),e==="consenti:consentSubmitted"&&this.hasGtag(o)&&(n.urlPassthrough&&r.push({url_passthrough:!0}),n.adsDataRedaction))){let c=t.consent?.ad_storage!=="granted";r.push({ads_data_redaction:c})}}hasGtag(e){return typeof e.gtag=="function"||typeof e.google_tag_manager<"u"}};I();function Ce(i,e,t){let n=i[t];if(!n)throw new Error(`[Consenti] No translations found for defaultLocale "${t}"`);if(i[e])return x(n,i[e]);let o=e.split("-")[0]??"";return o!==e&&i[o]?x(n,i[o]):n}function x(i,e){let t={...i};for(let n in e){let o=e[n];if(o==null)continue;let a=t[n];typeof o=="object"&&!Array.isArray(o)&&typeof a=="object"&&a!==null&&!Array.isArray(a)?t[n]=x(a,o):t[n]=o}return t}function ne(){return{tz:Intl.DateTimeFormat().resolvedOptions().timeZone,lang:navigator.language,langs:Array.from(navigator.languages)}}var we=["opt-in","opt-out","opt-out-strict","opt-in-dpdpa","opt-in-china","opt-in-brazil","general-privacy-consent","notice-only"];var B={version:"2026-07",countries:{AT:{name:"Austria",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Austria (GDPR)",locales:["de-AT"]},BE:{name:"Belgium",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Belgium (GDPR)",locales:["nl-BE","fr-BE"]},BG:{name:"Bulgaria",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Bulgaria (GDPR)",locales:["bg-BG"]},HR:{name:"Croatia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Croatia (GDPR)",locales:["hr-HR"]},CY:{name:"Cyprus",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Cyprus (GDPR)",locales:["el-CY"]},CZ:{name:"Czechia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Czechia (GDPR)",locales:["cs-CZ"]},DK:{name:"Denmark",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Denmark (GDPR)",locales:["da-DK"]},EE:{name:"Estonia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Estonia (GDPR)",locales:["et-EE"]},FI:{name:"Finland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Finland (GDPR)",locales:["fi-FI"]},FR:{name:"France",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"France (GDPR)",locales:["fr-FR"]},DE:{name:"Germany",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Germany (GDPR)",locales:["de-DE"]},GR:{name:"Greece",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Greece (GDPR)",locales:["el-GR"]},HU:{name:"Hungary",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Hungary (GDPR)",locales:["hu-HU"]},IE:{name:"Ireland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Ireland (GDPR)",locales:["en"]},IT:{name:"Italy",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Italy (GDPR)",locales:["it-IT"]},LV:{name:"Latvia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Latvia (GDPR)",locales:["lv-LV"]},LT:{name:"Lithuania",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Lithuania (GDPR)",locales:["lt-LT"]},LU:{name:"Luxembourg",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Luxembourg (GDPR)",locales:["fr-LU","de-LU"]},MT:{name:"Malta",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Malta (GDPR)",locales:["mt-MT","en"]},NL:{name:"Netherlands",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Netherlands (GDPR)",locales:["nl-NL"]},PL:{name:"Poland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Poland (GDPR)",locales:["pl-PL"]},PT:{name:"Portugal",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Portugal (GDPR)",locales:["pt-PT"]},RO:{name:"Romania",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Romania (GDPR)",locales:["ro-RO"]},SK:{name:"Slovakia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Slovakia (GDPR)",locales:["sk-SK"]},SI:{name:"Slovenia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Slovenia (GDPR)",locales:["sl-SI"]},ES:{name:"Spain",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Spain (GDPR)",locales:["es-ES"]},SE:{name:"Sweden",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Sweden (GDPR)",locales:["sv-SE"]},IS:{name:"Iceland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Iceland (GDPR via EEA)",locales:["is-IS"]},LI:{name:"Liechtenstein",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Liechtenstein (GDPR via EEA)",locales:["de-LI"]},NO:{name:"Norway",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Norway (GDPR via EEA)",locales:["nb-NO"]},AD:{name:"Andorra",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Andorra (GDPR-aligned)",locales:["ca-AD"]},AL:{name:"Albania",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Albania (GDPR-aligned)",locales:["sq-AL"]},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"]},GG:{name:"Guernsey",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Guernsey (DPA based on GDPR)",locales:["en"]},GI:{name:"Gibraltar",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Gibraltar (DPA based on UK GDPR)",locales:["en"]},GL:{name:"Greenland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Greenland (Danish GDPR implementation)",locales:["da-GL"]},MC:{name:"Monaco",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Monaco (GDPR-aligned)",locales:["fr-MC"]},ME:{name:"Montenegro",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Montenegro (GDPR-aligned)",locales:["sr-Latn-ME"]},MK:{name:"North Macedonia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"North Macedonia (GDPR-aligned)",locales:["mk-MK"]},RS:{name:"Serbia",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Serbia (GDPR-aligned)",locales:["sr-RS"]},SM:{name:"San Marino",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"San Marino (GDPR-aligned)",locales:["it-SM"]},TR:{name:"T\xFCrkiye",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"T\xFCrkiye (KVKK)",locales:["tr-TR"]},CA:{name:"Canada",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Canada (PIPEDA / Law 25)",locales:["en","fr-CA"],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"],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"]},BO:{name:"Bolivia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Bolivia",locales:["es-BO"]},BR:{name:"Brazil",type:"country",complianceGroup:"opt-in-brazil",default:"opt-in-brazil",description:"Brazil (LGPD)",locales:["pt-BR"]},CL:{name:"Chile",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Chile (Law 19.628)",locales:["es-CL"]},CO:{name:"Colombia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Colombia (Law 1581)",locales:["es-CO"]},CR:{name:"Costa Rica",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Costa Rica (LPDP)",locales:["es-CR"]},DO:{name:"Dominican Republic",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Dominican Republic",locales:["es-DO"]},EC:{name:"Ecuador",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Ecuador (LOPDP)",locales:["es-EC"]},GF:{name:"French Guiana",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"French Guiana",locales:["fr-GF"]},GT:{name:"Guatemala",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Guatemala",locales:["es-GT"]},GY:{name:"Guyana",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Guyana",locales:["en"]},HN:{name:"Honduras",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Honduras",locales:["es-HN"]},HT:{name:"Haiti",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Haiti",locales:["fr-HT"]},JM:{name:"Jamaica",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Jamaica",locales:["en"]},MX:{name:"Mexico",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Mexico (LFPDPPP)",locales:["es-MX"]},NI:{name:"Nicaragua",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Nicaragua",locales:["es-NI"]},PA:{name:"Panama",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Panama",locales:["es-PA"]},PE:{name:"Peru",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Peru (LPDP)",locales:["es-PE"]},PY:{name:"Paraguay",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Paraguay",locales:["es-PY"]},SR:{name:"Suriname",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Suriname",locales:["nl-SR"]},SV:{name:"El Salvador",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"El Salvador",locales:["es-SV"]},TT:{name:"Trinidad and Tobago",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Trinidad and Tobago",locales:["en"]},UY:{name:"Uruguay",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Uruguay (Law 18.331)",locales:["es-UY"]},VE:{name:"Venezuela",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Venezuela",locales:["es-VE"]},AG:{name:"Antigua and Barbuda",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Antigua and Barbuda",locales:["en"]},BB:{name:"Barbados",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Barbados",locales:["en"]},BL:{name:"Saint Barth\xE9lemy",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Barth\xE9lemy",locales:["fr-BL"]},BM:{name:"Bermuda",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Bermuda",locales:["en"]},BS:{name:"Bahamas",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Bahamas",locales:["en"]},CU:{name:"Cuba",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Cuba",locales:["es-CU"]},DM:{name:"Dominica",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Dominica",locales:["en"]},GD:{name:"Grenada",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Grenada",locales:["en"]},KN:{name:"Saint Kitts and Nevis",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Kitts and Nevis",locales:["en"]},LC:{name:"Saint Lucia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Lucia",locales:["en"]},PM:{name:"Saint Pierre and Miquelon",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Pierre and Miquelon",locales:["fr-PM"]},PR:{name:"Puerto Rico",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Puerto Rico",locales:["es-PR","en"]},VC:{name:"Saint Vincent and the Grenadines",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Saint Vincent and the Grenadines",locales:["en"]},IN:{name:"India",type:"country",complianceGroup:"opt-in-dpdpa",default:"opt-in-dpdpa",description:"India (DPDPA)",locales:["hi-IN","en","bn-IN","ta-IN"]},CN:{name:"China",type:"country",complianceGroup:"opt-in-china",default:"opt-in-china",description:"China (PIPL + DSL + CSL)",locales:["zh-CN"]},BN:{name:"Brunei",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Brunei (Personal Data Protection Order)",locales:["ms-BN","en"]},ID:{name:"Indonesia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Indonesia (PDP Law)",locales:["id-ID"]},KH:{name:"Cambodia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Cambodia",locales:["km-KH"]},LA:{name:"Laos",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Laos",locales:["lo-LA"]},MM:{name:"Myanmar",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Myanmar",locales:["my-MM"]},MY:{name:"Malaysia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Malaysia (PDPA)",locales:["ms-MY","zh-MY","en"]},PH:{name:"Philippines",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Philippines (DPA)",locales:["fil-PH","en"]},SG:{name:"Singapore",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Singapore (PDPA)",locales:["zh-SG","ms-SG","ta-SG","en"]},TH:{name:"Thailand",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Thailand (PDPA \u2014 GDPR-style opt-in)",locales:["th-TH"]},TL:{name:"Timor-Leste",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Timor-Leste",locales:["pt-TL"]},VN:{name:"Vietnam",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Vietnam (PDPD)",locales:["vi-VN"]},HK:{name:"Hong Kong",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Hong Kong SAR (PDPO)",locales:["zh-HK"]},JP:{name:"Japan",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Japan (APPI)",locales:["ja-JP"]},KP:{name:"North Korea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"North Korea",locales:["ko-KP"]},KR:{name:"South Korea",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"South Korea (PIPA)",locales:["ko-KR"]},MN:{name:"Mongolia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Mongolia",locales:["mn-MN"]},MO:{name:"Macau",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Macau SAR (PDPA)",locales:["zh-MO","pt-MO"]},TW:{name:"Taiwan",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Taiwan (PDPL)",locales:["zh-TW"]},AF:{name:"Afghanistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Afghanistan",locales:["fa-AF"]},BD:{name:"Bangladesh",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Bangladesh",locales:["bn-BD"]},BT:{name:"Bhutan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Bhutan",locales:["dz-BT"]},IO:{name:"British Indian Ocean Territory",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"British Indian Ocean Territory",locales:["en"]},LK:{name:"Sri Lanka",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Sri Lanka",locales:["si-LK","ta-LK"]},MV:{name:"Maldives",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Maldives",locales:["dv-MV"]},NP:{name:"Nepal",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Nepal",locales:["ne-NP"]},PK:{name:"Pakistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Pakistan",locales:["ur-PK","en"]},AM:{name:"Armenia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Armenia",locales:["hy-AM"]},AZ:{name:"Azerbaijan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Azerbaijan",locales:["az-AZ"]},GE:{name:"Georgia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Georgia",locales:["ka-GE"]},KG:{name:"Kyrgyzstan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Kyrgyzstan",locales:["ky-KG","ru-KG"]},KZ:{name:"Kazakhstan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Kazakhstan",locales:["kk-KZ","ru-KZ"]},TJ:{name:"Tajikistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Tajikistan",locales:["tg-TJ"]},TM:{name:"Turkmenistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Turkmenistan",locales:["tk-TM"]},UZ:{name:"Uzbekistan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Uzbekistan",locales:["uz-UZ"]},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"]},BH:{name:"Bahrain",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Bahrain (PDPL)",locales:["ar-BH"]},IL:{name:"Israel",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Israel (Privacy Protection Law)",locales:["he-IL","ar-IL"]},IQ:{name:"Iraq",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Iraq",locales:["ar-IQ"]},IR:{name:"Iran",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Iran",locales:["fa-IR"]},JO:{name:"Jordan",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Jordan",locales:["ar-JO"]},KW:{name:"Kuwait",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Kuwait",locales:["ar-KW"]},LB:{name:"Lebanon",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Lebanon",locales:["ar-LB"]},OM:{name:"Oman",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Oman (PDPL)",locales:["ar-OM"]},PS:{name:"Palestine",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Palestine",locales:["ar-PS"]},QA:{name:"Qatar",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Qatar (PDPL)",locales:["ar-QA"]},SA:{name:"Saudi Arabia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Saudi Arabia (PDPL)",locales:["ar-SA"]},SY:{name:"Syria",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Syria",locales:["ar-SY"]},YE:{name:"Yemen",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Yemen",locales:["ar-YE"]},BF:{name:"Burkina Faso",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Burkina Faso",locales:["fr-BF"]},BJ:{name:"Benin",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Benin",locales:["fr-BJ"]},CI:{name:"C\xF4te d'Ivoire",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"C\xF4te d'Ivoire",locales:["fr-CI"]},CM:{name:"Cameroon",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Cameroon",locales:["fr-CM","en"]},CV:{name:"Cabo Verde",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Cabo Verde",locales:["pt-CV"]},GA:{name:"Gabon",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Gabon",locales:["fr-GA"]},GH:{name:"Ghana",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Ghana (DPA)",locales:["en"]},GM:{name:"Gambia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Gambia",locales:["en"]},GN:{name:"Guinea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Guinea",locales:["fr-GN"]},GQ:{name:"Equatorial Guinea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Equatorial Guinea",locales:["es-GQ","fr-GQ"]},GW:{name:"Guinea-Bissau",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Guinea-Bissau",locales:["pt-GW"]},LR:{name:"Liberia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Liberia",locales:["en"]},ML:{name:"Mali",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Mali",locales:["fr-ML"]},MR:{name:"Mauritania",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Mauritania",locales:["ar-MR"]},NE:{name:"Niger",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Niger",locales:["fr-NE"]},NG:{name:"Nigeria",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Nigeria (NDPA)",locales:["en"]},SL:{name:"Sierra Leone",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Sierra Leone",locales:["en"]},SN:{name:"Senegal",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Senegal",locales:["fr-SN"]},TG:{name:"Togo",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Togo",locales:["fr-TG"]},BI:{name:"Burundi",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Burundi",locales:["fr-BI"]},DJ:{name:"Djibouti",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Djibouti",locales:["fr-DJ","ar-DJ"]},ER:{name:"Eritrea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Eritrea",locales:["ti-ER"]},ET:{name:"Ethiopia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Ethiopia",locales:["am-ET"]},KE:{name:"Kenya",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Kenya (DPA)",locales:["sw-KE","en"]},KM:{name:"Comoros",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Comoros",locales:["ar-KM","fr-KM"]},MG:{name:"Madagascar",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Madagascar",locales:["fr-MG"]},MU:{name:"Mauritius",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Mauritius (DPA)",locales:["en","fr-MU"]},MW:{name:"Malawi",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Malawi",locales:["en"]},MZ:{name:"Mozambique",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Mozambique",locales:["pt-MZ"]},RW:{name:"Rwanda",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Rwanda (DPA)",locales:["rw-RW","en"]},SC:{name:"Seychelles",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Seychelles",locales:["fr-SC","en"]},SO:{name:"Somalia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Somalia",locales:["so-SO"]},SS:{name:"South Sudan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"South Sudan",locales:["en"]},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"]},TZ:{name:"Tanzania",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Tanzania",locales:["sw-TZ","en"]},UG:{name:"Uganda",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Uganda (DPA)",locales:["en"]},AO:{name:"Angola",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Angola",locales:["pt-AO"]},BW:{name:"Botswana",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Botswana (DPA)",locales:["en"]},LS:{name:"Lesotho",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Lesotho",locales:["en"]},NA:{name:"Namibia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Namibia",locales:["en"]},SZ:{name:"Eswatini",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Eswatini",locales:["en"]},ZA:{name:"South Africa",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"South Africa (POPIA)",locales:["en","af-ZA"]},ZM:{name:"Zambia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Zambia (DPA)",locales:["en"]},ZW:{name:"Zimbabwe",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Zimbabwe (DPA)",locales:["en"]},DZ:{name:"Algeria",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Algeria",locales:["ar-DZ","fr-DZ"]},EG:{name:"Egypt",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Egypt (DPA)",locales:["ar-EG"]},LY:{name:"Libya",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Libya",locales:["ar-LY"]},MA:{name:"Morocco",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Morocco",locales:["ar-MA","fr-MA"]},SD:{name:"Sudan",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Sudan",locales:["ar-SD"]},TN:{name:"Tunisia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Tunisia (DPA)",locales:["ar-TN","fr-TN"]},CD:{name:"DR Congo",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"DR Congo",locales:["fr-CD"]},CF:{name:"Central African Republic",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Central African Republic",locales:["fr-CF"]},CG:{name:"Republic of Congo",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Republic of Congo",locales:["fr-CG"]},TD:{name:"Chad",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Chad",locales:["fr-TD","ar-TD"]},BY:{name:"Belarus",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Belarus",locales:["be-BY","ru-BY"]},MD:{name:"Moldova",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Moldova",locales:["ro-MD"]},RU:{name:"Russia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Russia",locales:["ru-RU"]},UA:{name:"Ukraine",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Ukraine",locales:["uk-UA"]},VA:{name:"Holy See",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Holy See",locales:["it-VA"]},AU:{name:"Australia",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"Australia (Privacy Act)",locales:["en"]},FJ:{name:"Fiji",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Fiji",locales:["en"]},FM:{name:"Micronesia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Micronesia",locales:["en"]},KI:{name:"Kiribati",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Kiribati",locales:["en"]},MH:{name:"Marshall Islands",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Marshall Islands",locales:["en"]},MP:{name:"Northern Mariana Islands",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Northern Mariana Islands",locales:["en"]},NC:{name:"New Caledonia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"New Caledonia",locales:["fr-NC"]},NF:{name:"Norfolk Island",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Norfolk Island",locales:["en"]},NR:{name:"Nauru",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Nauru",locales:["en"]},NU:{name:"Niue",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Niue",locales:["en"]},NZ:{name:"New Zealand",type:"country",complianceGroup:"general-privacy-consent",default:"general-privacy-consent",description:"New Zealand (Privacy Act)",locales:["en"]},PF:{name:"French Polynesia",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"French Polynesia",locales:["fr-PF"]},PG:{name:"Papua New Guinea",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Papua New Guinea",locales:["en"]},PW:{name:"Palau",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Palau",locales:["en"]},SB:{name:"Solomon Islands",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Solomon Islands",locales:["en"]},TO:{name:"Tonga",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Tonga",locales:["en"]},TV:{name:"Tuvalu",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Tuvalu",locales:["en"]},VU:{name:"Vanuatu",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Vanuatu",locales:["fr-VU","en"]},WS:{name:"Samoa",type:"country",complianceGroup:"notice-only",default:"notice-only",description:"Samoa",locales:["en"]},UK:{name:"United Kingdom",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"United Kingdom (UK GDPR + PECR)",locales:["en"]},CH:{name:"Switzerland",type:"country",complianceGroup:"opt-in",default:"opt-in",description:"Switzerland (revFADP)",locales:["de-CH","fr-CH","it-CH"]}}},Ut=Object.entries(B.countries).reduce(function(i,[e,t]){return i[e]=t.name,i},{}),jt=Object.values(B.countries).reduce(function(i,e){return e.locales.forEach(t=>{i[t]=e.name}),i},{});var Qe={id:0,version:1,defaultLocale:"en",complianceGroup:"general-privacy-consent",gpcMode:"honor",cookies:[{id:"necessary",legalBasis:"mandatory"},{id:"analytics",legalBasis:"consent",listenGpc:!0,expiry:365},{id:"marketing",legalBasis:"consent",listenGpc:!0,expiry:365},{id:"functional",legalBasis:"consent",expiry:365}],mainBanner:{position:"bottom",heading:"We value your privacy",htmlText:'We use cookies and similar technologies to enhance your browsing experience, personalise content and ads, and analyse our traffic. By clicking <strong>Accept All</strong>, you consent to our use of these technologies. You can change your preferences at any time. See our <a href="/privacy-policy">Privacy Policy</a> for more information.',buttons:[{text:"Accept All",cookies:"*",style:"primary",action:"custom"},{text:"Reject Optional",cookies:"!",style:"secondary",action:"custom"},{text:"Manage Preferences",style:"text",action:"manage"}]},gpcBanner:{position:"bottom",heading:"Your Privacy Preference Has Been Recognized",htmlText:"Your browser sent a Global Privacy Control (GPC) signal indicating you prefer to limit data collection. We have applied your preference. You can review or update your choices at any time.",buttons:[{text:"Review Preferences",style:"primary",action:"manage"},{text:"Continue",style:"secondary",action:"submit"}]},preferenceModal:{position:"right",heading:"Cookie Preferences",subheading:"Choose which types of cookies you allow us to use. You can change these settings at any time.",htmlText:"We use different types of cookies to optimise your experience on our website. Click on the different category headings to find out more and change your default settings. Note that blocking some types of cookies may impact your experience of the site and the services we are able to offer.",showClose:!0,overlayOpacity:50,categories:[{id:"cat-necessary",heading:"Strictly Necessary",htmlText:"These cookies are necessary for the website to function and cannot be switched off. They are usually only set in response to actions you have taken such as setting your privacy preferences, logging in, or filling in forms.",mandatory:!0,cookies:["necessary"]},{id:"cat-functional",heading:"Functional",htmlText:"These cookies enable enhanced functionality and personalisation. They may be set by us or by third parties whose services we have added to our pages.",cookies:["functional"]},{id:"cat-analytics",heading:"Analytics",htmlText:"These cookies help us understand how our website is being used, or how effective our marketing campaigns are. All information these cookies collect is aggregated and therefore anonymous.",cookies:["analytics"]},{id:"cat-marketing",heading:"Marketing",htmlText:"These cookies are used by our advertising partners to build a profile of your interests and show you relevant advertisements on other sites. They work by uniquely identifying your browser and device. If you do not allow these cookies, you will not experience targeted advertising.",cookies:["marketing"]}],buttons:[{text:"Accept All",cookies:"*",style:"primary",action:"custom"},{text:"Reject Optional",cookies:"!",style:"secondary",action:"custom"},{text:"Save My Choices",style:"text",action:"submit"}]}},Pe=Qe;var Xe={id:0,version:1,defaultLocale:"en",complianceGroup:"notice-only",gpcMode:"ignore",cookies:[{id:"necessary",legalBasis:"mandatory"},{id:"analytics",legalBasis:"legitimate_interest",expiry:365},{id:"functional",legalBasis:"legitimate_interest",expiry:365}],mainBanner:{position:"bottom",heading:"Cookie Notice",htmlText:'This website uses cookies and similar technologies to ensure it works correctly and to improve your experience. By continuing to browse this site, you acknowledge the use of these technologies. For more information, please read our <a href="/privacy-policy">Privacy Policy</a> and <a href="/cookie-policy">Cookie Policy</a>.',buttons:[{text:"Got It",style:"primary",action:"submit"},{text:"Learn More",style:"text",action:"manage"}]},gpcBanner:{position:"bottom",heading:"Cookie Notice",htmlText:'This site uses cookies necessary for its operation and for improving your experience. No consent is required in this jurisdiction. See our <a href="/privacy-policy">Privacy Policy</a> for details.',buttons:[{text:"Acknowledge",style:"primary",action:"submit"}]},preferenceModal:{position:"right",heading:"Cookie Information",subheading:"This notice explains how and why we use cookies on this website.",htmlText:"This website uses cookies to function correctly and to improve your experience. In the jurisdiction where you are located, explicit consent for cookies may not be required. However, we believe in transparency and provide this information so you understand how we use cookies. Some cookies are strictly necessary for the site to work.",showClose:!0,overlayOpacity:40,categories:[{id:"cat-necessary",heading:"Strictly Necessary",htmlText:"These cookies are required for the website to function correctly. Without these cookies, some parts of the website will not work as intended. They cannot be disabled.",mandatory:!0,cookies:["necessary"]},{id:"cat-functional",heading:"Functional and Analytics",htmlText:"These cookies help us deliver a better experience by remembering your preferences and measuring how the site is used. They are used under a legitimate interest basis and do not require your consent in this jurisdiction.",type:"legitimate_interest",legitimateInterest:{enabled:!0,description:"We use functional and analytics cookies under a legitimate interest basis to improve our service and to understand how users interact with our website. This data is anonymised and does not identify you personally."},cookies:["functional","analytics"]}],buttons:[{text:"Acknowledge",style:"primary",action:"submit"},{text:"Close",style:"text",action:"close"}]}},ke=Xe;var et={id:0,version:1,defaultLocale:"en",complianceGroup:"opt-in",gpcMode:"honor",cookies:[{id:"necessary",legalBasis:"mandatory"},{id:"analytics",legalBasis:"consent",listenGpc:!0,expiry:395},{id:"marketing",legalBasis:"consent",listenGpc:!0,expiry:395},{id:"functional",legalBasis:"consent",expiry:395}],mainBanner:{position:"bottom",heading:"We use cookies",htmlText:'We and our partners use cookies and similar technologies to provide you with a better browsing experience, to analyse site traffic, and to personalise content and advertisements. Please give your consent to our use of cookies as described in our <a href="/privacy-policy">Privacy Policy</a>. You may withdraw your consent at any time.',buttons:[{text:"Accept All",cookies:"*",style:"primary",action:"custom"},{text:"Reject All",cookies:"!",style:"secondary",action:"custom"},{text:"Manage Preferences",style:"text",action:"manage"}]},gpcBanner:{position:"bottom",heading:"Your Privacy Preference Has Been Detected",htmlText:"Your browser or device has sent a Global Privacy Control (GPC) signal indicating you prefer to limit the sale or sharing of your personal data. As required under applicable law, we have applied your preference. You can review or update your choices at any time.",buttons:[{text:"Manage Preferences",style:"primary",action:"manage"},{text:"Continue",style:"secondary",action:"submit"}]},preferenceModal:{position:"right",heading:"Privacy Preferences",subheading:"Manage your cookie preferences below. Strictly necessary cookies cannot be disabled.",htmlText:"We use different types of cookies to optimise your experience on our website. You can choose which categories to allow. Your choices are saved for the duration indicated for each category and can be changed at any time.",showClose:!0,overlayOpacity:50,categories:[{id:"cat-necessary",heading:"Strictly Necessary",htmlText:"These cookies are essential for the website to function and cannot be disabled. They are set in response to actions you take such as logging in, filling in forms, or setting privacy preferences. They do not store personally identifiable information.",mandatory:!0,cookies:["necessary"]},{id:"cat-functional",heading:"Functional",htmlText:"These cookies enable enhanced functionality and personalisation, such as remembering your language preference or region. They may be set by us or by third-party providers whose services we have added to our pages.",cookies:["functional"]},{id:"cat-analytics",heading:"Analytics",htmlText:"These cookies help us understand how visitors interact with our site by collecting and reporting information anonymously. This data helps us improve our content and navigation. No personally identifiable information is collected.",cookies:["analytics"]},{id:"cat-marketing",heading:"Marketing",htmlText:"These cookies track your online activity to help advertisers deliver more relevant advertising to you, or to limit how many times you see an advertisement. They may be shared with other organisations or advertisers. Declining these cookies will not reduce the number of advertisements you see.",cookies:["marketing"]}],buttons:[{text:"Accept All",cookies:"*",style:"primary",action:"custom"},{text:"Reject All",cookies:"!",style:"secondary",action:"custom"},{text:"Save My Choices",style:"text",action:"submit"}]}},xe=et;var tt={id:0,version:1,defaultLocale:"en",complianceGroup:"opt-in-brazil",gpcMode:"honor",cookies:[{id:"necessary",legalBasis:"mandatory"},{id:"analytics",legalBasis:"consent",listenGpc:!0,expiry:365},{id:"marketing",legalBasis:"consent",listenGpc:!0,expiry:365},{id:"functional",legalBasis:"consent",expiry:365}],mainBanner:{position:"bottom",heading:"Your Privacy Matters \u2014 LGPD Notice",htmlText:'In accordance with the Lei Geral de Prote\xE7\xE3o de Dados (LGPD \u2014 Brazilian General Data Protection Law), we request your consent before collecting and processing personal data beyond what is strictly necessary. As a data subject (titular), you have the right to access, correct, delete, and withdraw consent at any time. Read our <a href="/privacy-policy">Privacy Policy</a> for full details.',buttons:[{text:"Accept All",cookies:"*",style:"primary",action:"custom"},{text:"Reject All",cookies:"!",style:"secondary",action:"custom"},{text:"Manage Preferences",style:"text",action:"manage"}]},gpcBanner:{position:"bottom",heading:"Your Privacy Signal Was Detected",htmlText:"A Global Privacy Control (GPC) signal was detected from your browser. We have applied your preference in accordance with applicable data protection law. You may manage your choices at any time.",buttons:[{text:"Manage Preferences",style:"primary",action:"manage"},{text:"Continue",style:"secondary",action:"submit"}]},preferenceModal:{position:"right",heading:"Privacy Preferences \u2014 LGPD",subheading:"Under the LGPD, you have the right to give, manage, or withdraw consent for each purpose of personal data processing. You may update your choices at any time.",htmlText:"As a titular (data subject) under the Lei Geral de Prote\xE7\xE3o de Dados (LGPD), you have the right to: confirm the existence of processing, access your data, correct incomplete or inaccurate data, anonymise or delete unnecessary data, and withdraw consent. Use the controls below to set your preferences.",showClose:!0,overlayOpacity:50,categories:[{id:"cat-necessary",heading:"Strictly Necessary",htmlText:"Processing under this purpose is indispensable for the operation of our services. This is based on the legal bases of contract performance and legal obligation under Article 7 of the LGPD.",mandatory:!0,cookies:["necessary"]},{id:"cat-functional",heading:"Functional",htmlText:"These cookies allow us to remember your preferences and provide a personalised experience. Your consent enables this functionality.",cookies:["functional"]},{id:"cat-analytics",heading:"Analytics",htmlText:"With your consent, we collect statistical data about your use of our services to improve our offering. Data is anonymised where possible and is not used to identify you.",cookies:["analytics"]},{id:"cat-marketing",heading:"Marketing",htmlText:"With your consent, we and our partners process your personal data to deliver personalised advertising content. You may withdraw this consent at any time, and withdrawal will not affect the lawfulness of prior processing.",cookies:["marketing"]}],buttons:[{text:"Accept All",cookies:"*",style:"primary",action:"custom"},{text:"Reject All",cookies:"!",style:"secondary",action:"custom"},{text:"Save My Choices",style:"text",action:"submit"}]}},Ge=tt;var nt={id:0,version:1,defaultLocale:"en",complianceGroup:"opt-in-china",gpcMode:"ignore",cookies:[{id:"necessary",legalBasis:"mandatory"},{id:"analytics",legalBasis:"consent",expiry:365},{id:"marketing",legalBasis:"consent",expiry:365},{id:"functional",legalBasis:"consent",expiry:365}],mainBanner:{position:"bottom",heading:"Personal Information Processing Notice",htmlText:`In accordance with the Personal Information Protection Law of the People's Republic of China (PIPL), we hereby inform you of the types of personal information we collect, the purposes for which it is used, and your rights as a personal information subject. Your consent is required before we may process your personal information beyond what is strictly necessary. Please read our <a href="/privacy-policy">Privacy Policy</a> carefully.`,buttons:[{text:"Agree to All",cookies:"*",style:"primary",action:"custom"},{text:"Reject Non-Necessary",cookies:"!",style:"secondary",action:"custom"},{text:"Manage Preferences",style:"text",action:"manage"}]},gpcBanner:{position:"bottom",heading:"Privacy Notice",htmlText:"We have detected a privacy preference signal. We have applied your preferences for the processing of your personal information. You may review and update your choices at any time.",buttons:[{text:"Manage Preferences",style:"primary",action:"manage"},{text:"Continue",style:"secondary",action:"submit"}]},preferenceModal:{position:"right",heading:"Personal Information Processing Preferences",subheading:"Under the PIPL, you have the right to know, decide, access, copy, correct, delete, and withdraw consent for the processing of your personal information. Please select your preferences.",htmlText:"We process personal information lawfully, legitimately, and necessarily in accordance with the PIPL. Your personal information is stored and processed within the territory of China. Cross-border transfer of your data, if any, is handled in accordance with Chapter III of the PIPL. You may withdraw your consent at any time by updating these settings.",showClose:!0,overlayOpacity:50,categories:[{id:"cat-necessary",heading:"Strictly Necessary",htmlText:"Processing under this purpose is required for the normal functioning of our services as requested by you. This is processed under the legitimate basis of contract performance and legal obligation under the PIPL.",mandatory:!0,cookies:["necessary"]},{id:"cat-functional",heading:"Functional",htmlText:"These cookies allow us to remember your preferences and settings to provide you with an enhanced experience. Your consent allows us to personalise our service for you.",cookies:["functional"]},{id:"cat-analytics",heading:"Statistical Analysis",htmlText:"With your consent, we collect usage data to analyse how our services are used, enabling us to improve performance and user experience. Data is anonymised where possible.",cookies:["analytics"]},{id:"cat-marketing",heading:"Marketing and Commercial Purposes",htmlText:"With your consent, we use your personal information to deliver personalised commercial information and advertisements. You may withdraw this consent at any time.",cookies:["marketing"]}],buttons:[{text:"Agree and Continue",cookies:"*",style:"primary",action:"custom"},{text:"Only Necessary",cookies:"!",style:"secondary",action:"custom"},{text:"Save My Choices",style:"text",action:"submit"}]}},Se=nt;var ot={id:0,version:1,defaultLocale:"en",complianceGroup:"opt-in-dpdpa",gpcMode:"honor",cookies:[{id:"necessary",legalBasis:"mandatory"},{id:"analytics",legalBasis:"consent",listenGpc:!0,expiry:365},{id:"marketing",legalBasis:"consent",listenGpc:!0,expiry:365},{id:"functional",legalBasis:"consent",expiry:365}],mainBanner:{position:"bottom",heading:"We Need Your Consent",htmlText:'In accordance with the Digital Personal Data Protection Act, 2023 (DPDPA) of India, we require your explicit consent before collecting and processing your personal data. You have the right to withdraw consent at any time and to access, correct, or erase your data. Please review our <a href="/privacy-policy">Privacy Policy</a> for details.',buttons:[{text:"Accept All",cookies:"*",style:"primary",action:"custom"},{text:"Reject All",cookies:"!",style:"secondary",action:"custom"},{text:"Manage Preferences",style:"text",action:"manage"}]},gpcBanner:{position:"bottom",heading:"Privacy Preference Detected",htmlText:"A privacy preference signal was detected from your browser. We have applied your opt-out preference in accordance with applicable privacy regulations. You may review and update your consent choices at any time.",buttons:[{text:"Manage Preferences",style:"primary",action:"manage"},{text:"Continue",style:"secondary",action:"submit"}]},preferenceModal:{position:"right",heading:"Consent Preferences \u2014 DPDPA",subheading:"Under the Digital Personal Data Protection Act (DPDPA), you have the right to give, withdraw, or manage your consent for each purpose of data processing. Please select your preferences below.",htmlText:"As a Data Principal under the DPDPA, you have the right to access, correct, erase, and withdraw consent for your personal data. This notice is provided by the Data Fiduciary as required under Section 5 of the DPDPA. You can update your preferences at any time.",showClose:!0,overlayOpacity:50,categories:[{id:"cat-necessary",heading:"Necessary Processing",htmlText:"Processing your personal data under this purpose is necessary for the delivery of the service you have requested. This processing is exempt from consent requirements under Section 7(a) of the DPDPA.",mandatory:!0,cookies:["necessary"]},{id:"cat-functional",heading:"Functional Enhancements",htmlText:"We process limited personal data to remember your preferences and settings, improving your experience on return visits. Your consent enables this personalisation.",cookies:["functional"]},{id:"cat-analytics",heading:"Analytics and Improvement",htmlText:"With your consent, we collect data about how you use our services in order to improve them. The data is anonymised where possible and is not shared with third parties for commercial purposes.",cookies:["analytics"]},{id:"cat-marketing",heading:"Marketing and Personalised Advertising",htmlText:"With your consent, we and our advertising partners process your personal data to deliver advertisements tailored to your interests. You may withdraw this consent at any time without affecting the lawfulness of prior processing.",cookies:["marketing"]}],buttons:[{text:"Give Consent to All",cookies:"*",style:"primary",action:"custom"},{text:"Withdraw All Consent",cookies:"!",style:"secondary",action:"custom"},{text:"Save My Choices",style:"text",action:"submit"}]}},Ae=ot;var it={id:0,version:1,defaultLocale:"en",complianceGroup:"opt-out",gpcMode:"honor",cookies:[{id:"necessary",legalBasis:"mandatory"},{id:"analytics",legalBasis:"consent",listenGpc:!0,expiry:365},{id:"marketing",legalBasis:"consent",listenGpc:!0,expiry:365,cpraCategory:"sale"},{id:"functional",legalBasis:"consent",expiry:365}],mainBanner:{position:"bottom",heading:"Your Privacy Choices",htmlText:'We use cookies and similar technologies on this site. Some help us operate the site, while others are used for analytics and advertising purposes. You may opt out of the sale or sharing of your personal information at any time. See our <a href="/privacy-policy">Privacy Policy</a> for more information.',buttons:[{text:"Accept All",cookies:"*",style:"primary",action:"custom"},{text:"Do Not Sell or Share My Personal Information",cookies:"!",style:"secondary",action:"custom"},{text:"Manage Preferences",style:"text",action:"manage"}]},gpcBanner:{position:"bottom",heading:"Your Opt-Out Preference Has Been Applied",htmlText:"We detected a Global Privacy Control (GPC) signal from your browser. In compliance with applicable privacy law, we have opted you out of the sale and sharing of your personal information. You can review or update your preferences below.",buttons:[{text:"Manage Preferences",style:"primary",action:"manage"},{text:"Continue",style:"secondary",action:"submit"}]},preferenceModal:{position:"right",heading:"Privacy Preferences",subheading:"You have the right to opt out of the sale and sharing of your personal information. Toggle the categories below to set your preferences.",htmlText:"We use different types of cookies and tracking technologies. Strictly necessary cookies are always active. For all other categories, you can choose to opt out at any time.",showClose:!0,overlayOpacity:50,categories:[{id:"cat-necessary",heading:"Strictly Necessary",htmlText:"These cookies are required for the website to operate. They include cookies that enable you to log in, use shopping carts, and access secure areas of the website.",mandatory:!0,cookies:["necessary"]},{id:"cat-functional",heading:"Functional",htmlText:"These cookies allow the website to remember choices you make (such as your user name, language, or the region you are in) and provide enhanced, more personal features.",cookies:["functional"]},{id:"cat-analytics",heading:"Analytics",htmlText:"These cookies collect information about how visitors use our website, such as which pages they visit most often. This data helps us improve the site and your experience.",cookies:["analytics"]},{id:"cat-marketing",heading:"Sale / Sharing of Personal Information",htmlText:'These cookies and trackers are used to deliver targeted advertising to you, and may constitute a "sale" or "sharing" of personal information under the California Consumer Privacy Act (CCPA). You have the right to opt out of this activity.',cookies:["marketing"]}],buttons:[{text:"Accept All",cookies:"*",style:"primary",action:"custom"},{text:"Opt Out of Sale/Sharing",cookies:"!",style:"secondary",action:"custom"},{text:"Save My Choices",style:"text",action:"submit"}]}},Te=it;var at={id:0,version:1,defaultLocale:"en",complianceGroup:"opt-out-strict",gpcMode:"strict",cookies:[{id:"necessary",legalBasis:"mandatory"},{id:"analytics",legalBasis:"consent",listenGpc:!0,expiry:365},{id:"marketing",legalBasis:"consent",listenGpc:!0,expiry:365,cpraCategory:"sale"},{id:"sensitive",legalBasis:"consent",listenGpc:!0,expiry:365,cpraCategory:"sensitive"},{id:"functional",legalBasis:"consent",expiry:365}],mainBanner:{position:"bottom",heading:"Your Privacy Rights (CPRA)",htmlText:'Under the California Privacy Rights Act (CPRA), you have the right to opt out of the sale, sharing, and use of your sensitive personal information. We also honor Global Privacy Control (GPC) signals automatically. See our <a href="/privacy-policy">Privacy Policy</a> and <a href="/do-not-sell">Do Not Sell or Share My Personal Information</a> page.',buttons:[{text:"Accept All",cookies:"*",style:"primary",action:"custom"},{text:"Opt Out of Sale, Sharing & Sensitive Use",cookies:"!",style:"secondary",action:"custom"},{text:"Manage Preferences",style:"text",action:"manage"}]},gpcBanner:{position:"bottom",heading:"GPC Signal Detected \u2014 Opt-Out Applied",htmlText:"A Global Privacy Control (GPC) opt-out signal was detected. As required by CPRA, we have automatically applied your opt-out of the sale and sharing of your personal information and the use of your sensitive personal information. Your preference has been saved.",buttons:[{text:"Manage Preferences",style:"primary",action:"manage"},{text:"Acknowledge",style:"secondary",action:"submit"}]},preferenceModal:{position:"right",heading:"Privacy Preferences (CPRA)",subheading:"Manage your privacy choices. You may opt out of the sale, sharing, and sensitive use of your personal information at any time.",htmlText:"The California Privacy Rights Act (CPRA) provides you with additional rights over your personal information. GPC signals are honored automatically. You can update your choices below.",showClose:!0,overlayOpacity:50,categories:[{id:"cat-necessary",heading:"Strictly Necessary",htmlText:"These cookies are essential for the website to function and are always active. They are exempt from CPRA opt-out requirements.",mandatory:!0,cookies:["necessary"]},{id:"cat-functional",heading:"Functional",htmlText:"These cookies personalise your experience (e.g. language, region preferences). They do not involve the sale or sharing of personal information.",cookies:["functional"]},{id:"cat-analytics",heading:"Analytics",htmlText:"These cookies measure site performance and visitor behaviour. Data is used internally to improve our services. Opting out stops this collection.",cookies:["analytics"]},{id:"cat-marketing",heading:"Sale and Sharing of Personal Information",htmlText:'These technologies may constitute a "sale" or "sharing" of your personal information under CPRA, including cross-context behavioural advertising. You have the right to opt out.',cookies:["marketing"]},{id:"cat-sensitive",heading:"Use of Sensitive Personal Information",htmlText:"Some technologies may use sensitive personal information as defined by CPRA (e.g. precise geolocation, health data). You have the right to limit such use.",cookies:["sensitive"]}],buttons:[{text:"Accept All",cookies:"*",style:"primary",action:"custom"},{text:"Opt Out of All Non-Necessary",cookies:"!",style:"secondary",action:"custom"},{text:"Save My Choices",style:"text",action:"submit"}]}},_e=at;var rt={"general-privacy-consent":Pe,"notice-only":ke,"opt-in":xe,"opt-in-brazil":Ge,"opt-in-china":Se,"opt-in-dpdpa":Ae,"opt-out":Te,"opt-out-strict":_e},Me=new Map;function Ee(i,e){Me.set(String(i),e)}var re={id:0,version:1,defaultLocale:"en",gpcMode:"honor",cookies:[{id:"functionality_storage",mandatory:!0},{id:"analytics_storage",listenGpc:!0,expiry:365},{id:"ad_storage",listenGpc:!0,expiry:365},{id:"ad_user_data",listenGpc:!0,expiry:365},{id:"ad_personalization",listenGpc:!0,expiry:365}],mainBanner:{position:"bottom",heading:"We value your privacy",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.",buttons:[{text:"Accept All",cookies:"*",style:"primary",action:"custom"},{text:"Reject Optional",cookies:"!",style:"primary",action:"custom"},{text:"Customize",style:"text",action:"manage"}]},gpcBanner:{position:"bottom",heading:"Your Privacy Preference Has Been Recognized",htmlText:"Your browser has sent a Global Privacy Control (GPC) signal. We've applied your privacy preference where required by applicable law. You can review or update your cookie choices at any time.",buttons:[{text:"Review Preferences",style:"primary",action:"manage"},{text:"Continue",style:"secondary",action:"submit"}]},preferenceModal:{heading:"Cookie Preferences",subheading:"Choose which cookies you allow us to use.",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.",showClose:!0,overlayOpacity:50,categories:[{id:"cat-necessary",heading:"Strictly Necessary",htmlText:"These cookies are essential for the website to function correctly. They include authentication, security, and accessibility features. They cannot be disabled.",mandatory:!0,cookies:["functionality_storage"]},{id:"cat-analytics",heading:"Analytics",htmlText:"These cookies help us understand how visitors interact with our website by collecting and reporting information anonymously. (e.g. Google Analytics)",cookies:["analytics_storage"]},{id:"cat-advertising",heading:"Advertising",htmlText:"These cookies are used to deliver advertisements more relevant to you and your interests. (e.g. Google Ads, Meta Pixel)",cookies:["ad_storage","ad_user_data","ad_personalization"]}],buttons:[{text:"Accept All",cookies:"*",style:"primary",action:"custom"},{text:"Reject Optional",cookies:"!",style:"primary",action:"custom"},{text:"Save Selection",style:"secondary",action:"submit"}]}};function oe(i){let e;return i.gpcMode===!0?e="honor":i.gpcMode===!1?e="ignore":i.gpcMode&&(e=i.gpcMode),{id:0,profileUuid:i.id,version:i.version,defaultLocale:i.defaultLocale,locales:i.locales,cookies:i.cookies,mainBanner:i.mainBanner,preferenceModal:i.preferenceModal,...i.gpcBanner?{gpcBanner:i.gpcBanner}:{},...e?{gpcMode:e}:{},...i.darkMode!==void 0?{darkMode:i.darkMode}:{},...i.complianceGroup?{complianceGroup:i.complianceGroup}:{},...i.complianceConfig?{complianceConfig:i.complianceConfig}:{}}}var De="__consenti_resolved_",st=3600*1e3;function ct(i,e,t){if(!t)return null;let n=`${De}${i}_${e}`;try{let o=sessionStorage.getItem(n);if(!o)return null;let a=JSON.parse(o);return Date.now()>a.expiresAt?(sessionStorage.removeItem(n),null):a}catch{return null}}function lt(i,e,t,n){if(!t)return;let o=`${De}${i}_${e}`;try{sessionStorage.setItem(o,JSON.stringify({...n,expiresAt:Date.now()+st}))}catch{}}function ie(i,e){if(e)return!0;let t=i.allowedOrigins;if(!t||t.length===0)return!0;let n=window.location.origin;return t.some(o=>o===n||o==="*")}function Re(i){return rt[i]??null}function Le(i,e){let t=e.core.usePrebuiltProfiles??"all";return t==="all"?!0:t.includes(i)}function pt(i,e){let t=e.replace(/_/g,"-").split("-"),n=t.length>=2?t[t.length-1]?.toUpperCase():void 0,o=n&&/^[A-Z]{2}$/.test(n)?n:void 0;if(o){let r=B.countries[o];if(r?.default)return r.default}let a=i.split("/");if(a[0]==="Europe")return"opt-in";if(a[0]==="America"){let r=a[1]??"";if(["Los_Angeles","Denver","Chicago","New_York","Phoenix","Anchorage","Honolulu"].includes(r))return"opt-out"}return"opt-in"}function se(i){let e=i.core.locale;return!e||e==="auto"?navigator.language:e}function Ie(i){return i.api?.tenantId??i.core.tenantId??"default"}function dt(i){return we.includes(i)}async function _(i,e){try{return await L(i,{},e)}catch{return null}}async function S(i){let{tz:e,lang:t}=ne(),n=pt(e,t);if(Le(n,i)){let o=Re(n);if(o)return o}return re}async function ae(i,e){if(Le(i,e)){let t=Re(i);if(t)return t}return re}async function Be(i){let e=se(i),t=Ie(i),n=i.core.cacheResolvedProfiles!==!1,o=i.api?.baseUrl??window.location.origin,a=i.api?.trustDomain??!1,r=ct(t,e,n);if(r){let m=await _(r.filePath,i.api?.authToken);if(m)return ie(m,a)?oe(m):(b.error(`Domain ${window.location.origin} is not in allowedOrigins for this profile. Falling back to pre-built profile.`),S(i))}let{tz:s,lang:c}=ne(),u=`${o}/consenti/api/v1/resolve-profile?tz=${encodeURIComponent(s)}&lang=${encodeURIComponent(c)}&locale=${encodeURIComponent(e)}&tenantId=${encodeURIComponent(t)}`,d,f,y;try{let m=await L(u,{},i.api?.authToken);m.warning==="locale_not_found"&&b.info("Locale not found; using default locale."),d=m.filePath,f=m.complianceGroup,y=m.version,lt(t,e,n,{filePath:d,version:y,complianceGroup:f,tenantId:t,locale:e})}catch{return b.warn("resolve-profile failed; falling back to pre-built profile."),S(i)}let p=await _(d,i.api?.authToken)??await _(`${o}/consenti/api/v1/profiles/${t}/${f}/default.json`,i.api?.authToken);return p?ie(p,a)?oe(p):(b.error(`Domain ${window.location.origin} is not in allowedOrigins for this profile. Falling back to pre-built profile.`),S(i)):(b.warn("Profile fetch failed; falling back to pre-built profile."),S(i))}async function ut(i,e){let t=se(e),n=Ie(e),o=e.api?.baseUrl??window.location.origin,a=e.api?.trustDomain??!1,r=e.api?.complianceGroup??i,s=await _(`${o}/consenti/api/v1/profiles/${n}/${r}/${encodeURIComponent(t)}.json`,e.api?.authToken)??await _(`${o}/consenti/api/v1/profiles/${n}/${r}/default.json`,e.api?.authToken);return s?ie(s,a)?oe(s):(b.error(`Domain ${window.location.origin} is not in allowedOrigins for this profile. Falling back to pre-built profile.`),ae(r,e)):(b.warn(`Could not fetch profile for ${r}; falling back to pre-built.`),ae(r,e))}async function mt(i,e){e.api?.enabled&&b.warn("Local profile used instead of server profile. Check compliance.type config.");let t=se(e),n=Array.from(Me.values()).find(o=>o.complianceGroup===i);if(n){let o=Ce(n.translations??{},t,n.defaultLocale);return{id:0,version:1,defaultLocale:n.defaultLocale,locales:Object.keys(n.translations??{}),cookies:n.cookies??[],mainBanner:o.mainBanner,...o.gpcBanner?{gpcBanner:o.gpcBanner}:{},preferenceModal:o.preferenceModal,...n.darkMode!==void 0?{darkMode:n.darkMode}:{},...n.complianceGroup?{complianceGroup:n.complianceGroup}:{}}}return e.api?.enabled?Be(e):S(e)}async function Ne(i){let e=i.compliance?.type??"auto",t=i.api?.enabled??!1;try{return e==="auto"?t?await Be(i):await S(i):dt(e)?t?await ut(e,i):await ae(e,i):await mt(e,i)}catch{return re}}function M(){let i=pe();return i?"globalPrivacyControl"in i&&i.globalPrivacyControl===!0:!1}function Oe(i,e,t){if(!t||!M())return{...e};let n={...e};for(let o of i)o.mandatory||o.listenGpc&&(n[o.id]=o.type==="legitimate_interest"?"objected":"denied");for(let o of i)o.mandatory&&(n[o.id]="granted");return n}function N(i,e){let t=document.createElement("button");return t.type="button",t.className=`consenti-btn consenti-btn--${i.style}`,t.textContent=i.text,t.addEventListener("click",()=>{switch(i.action){case"custom":{let n=i.cookies;n==="*"?e.onGrantAll():n==="!"?e.onDenyAll():Array.isArray(n)&&n.length>0&&e.onGrantSpecific(n);break}case"submit":e.onSubmit();break;case"manage":e.onManage();break;case"close":e.onClose();break}}),t}function O(i,e){let t=document.createElement("div");return t.className="consenti-overlay",t.style.opacity=String(i/100),t.setAttribute("aria-hidden","true"),e&&t.addEventListener("click",e),t}var H=class{constructor(){this.el=null;this.overlayEl=null}build(e,t,n,o,a,r,s=!1){let c=document.createElement("div");c.id="consenti-banner",c.className=`consenti-banner consenti-banner--${e.position}`,c.setAttribute("role","region"),c.setAttribute("aria-label","Cookie consent"),c.setAttribute("aria-live","polite");let u=document.createElement("div");u.className="consenti-banner__text-container";let d=document.createElement("div");if(d.className="consenti-banner__links",e.heading){let h=e.headingTag??"h2",C=document.createElement(h);C.id="consenti-banner-heading",C.className="consenti-banner__heading",C.textContent=e.heading,u.appendChild(C)}let f=document.createElement("div");if(f.className="consenti-banner__text",f.innerHTML=e.htmlText,n){let h=document.createElement("a");h.href=n,h.className="consenti-banner__link consenti-banner__link--privacy consenti-btn consenti-btn--text",h.textContent="Privacy Policy",h.setAttribute("target","_blank"),h.setAttribute("rel","noopener noreferrer"),d.appendChild(h)}u.appendChild(f);let y=e.buttons.filter(h=>h.action==="link");if(y.length>0)for(let h of y){if(!h.url)continue;let C=document.createElement("a");C.href=h.url,C.className=`consenti-banner__link consenti-btn consenti-btn--${h.style??"text"}`,C.textContent=h.text,C.setAttribute("target","_blank"),C.setAttribute("rel","noopener noreferrer"),d.appendChild(C)}(y.length>0||n)&&u.appendChild(d),c.appendChild(u);let p=e.buttons.filter(h=>h.action!=="link");if(p.length>0){let h=document.createElement("div");h.className="consenti-banner__buttons";for(let C of p)h.appendChild(N(C,t));c.appendChild(h)}let m=e.showClose,P=e.showLocaleSwitcher&&o&&o.length>1;if(m||P){let h=document.createElement("div");if(h.className="consenti-banner__controls",P&&o&&r&&h.appendChild(yt(o,a,r)),m){let C=document.createElement("button");C.type="button",C.className="consenti-banner__close consenti-btn consenti-btn--close",C.setAttribute("aria-label","Close cookie banner"),C.textContent="\xD7",C.addEventListener("click",t.onClose),h.appendChild(C)}c.appendChild(h)}e.overlayOpacity&&e.overlayOpacity>0&&(this.overlayEl=O(e.overlayOpacity));let w=document.createElement("a");return w.href="https://consenti.dev",w.className="consenti-banner__powered-by",w.textContent="Powered by Consenti",w.setAttribute("target","_blank"),w.setAttribute("rel","noopener noreferrer"),s&&w.classList.add("consenti-d-none"),c.appendChild(w),this.el=c,c}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 yt(i,e,t){let n=document.createElement("div");n.className="consenti-locale-switcher";let o=document.createElement("button");o.type="button",o.className="consenti-locale-switcher__btn consenti-btn consenti-btn--close",o.setAttribute("aria-label","Switch language"),o.setAttribute("aria-haspopup","listbox"),o.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 i){let s=document.createElement("li");s.setAttribute("role","option"),s.setAttribute("aria-selected",String(r===e));let c=document.createElement("button");c.type="button",c.className=`consenti-locale-switcher__option${r===e?" consenti-locale-switcher__option--active":""}`,c.textContent=r,c.addEventListener("click",u=>{u.stopPropagation(),a.hidden=!0,r!==e&&t(r)}),s.appendChild(c),a.appendChild(s)}return o.addEventListener("click",r=>{r.stopPropagation(),a.hidden=!a.hidden}),document.addEventListener("click",()=>{a.hidden=!0},{capture:!0,once:!1}),n.appendChild(o),n.appendChild(a),n}var ft=["a[href]","button:not([disabled])","input:not([disabled])","select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])','[contenteditable="true"]'].join(", ");function He(i){let e=()=>Array.from(i.querySelectorAll(ft)).filter(o=>!o.closest('[aria-hidden="true"]')),t=o=>{if(o.key!=="Tab")return;let a=e();if(a.length===0)return;let r=a[0],s=a[a.length-1];o.shiftKey?(document.activeElement===r||document.activeElement===i)&&(o.preventDefault(),s.focus()):document.activeElement===s&&(o.preventDefault(),r.focus())},n=o=>{o.key==="Escape"&&i.dispatchEvent(new CustomEvent("consenti:closeRequest",{bubbles:!0}))};return document.addEventListener("keydown",t),document.addEventListener("keydown",n),e()[0]?.focus(),()=>{document.removeEventListener("keydown",t),document.removeEventListener("keydown",n)}}var z=class{constructor(){this.el=null;this.triggerEl=null;this.trapCleanup=null;this.toggleMap=new Map;this.receiptCheckbox=null;this.toggleState=new Map;this.passthrough=!1;this.mobileBreakpoint=576;this.savedBodyOverflow=void 0}build(e,t,n,o,a,r,s,c,u=!1){this.mobileBreakpoint=e.mobileFullScreenBreakpoint??576;let d=e.position??"center",f=document.createElement("div");f.id="consenti-modal",f.className=`consenti-modal consenti-modal--${d}`,f.setAttribute("role","dialog"),f.setAttribute("aria-modal","true");let y=e.overlayOpacity??50;if(this.passthrough=y===0,y>0){let g=e.persistent?void 0:()=>n.onClose(),v=O(y,g);v.className="consenti-modal__overlay",f.appendChild(v)}else f.classList.add("consenti-modal--passthrough");let p=document.createElement("div");p.className="consenti-modal__container";let m=document.createElement("div");m.className="consenti-modal__header";let P=document.createElement("div");if(P.className="consenti-modal__body",e.heading){let g=e.headingTag??"h2",v=document.createElement(g);v.id="consenti-modal-heading",v.className="consenti-modal__heading",v.textContent=e.heading,m.appendChild(v),f.setAttribute("aria-labelledby","consenti-modal-heading")}if(e.subheading){let g=document.createElement("p");g.className="consenti-modal__subheading",g.textContent=e.subheading,m.appendChild(g)}let w=e.showClose!==!1,h=e.showLocaleSwitcher&&r&&r.length>1;if(h||w){let g=document.createElement("div");if(g.className="consenti-modal__header-controls",h&&r&&c&&g.appendChild(gt(r,s,c)),w){let v=document.createElement("button");v.type="button",v.className="consenti-modal__close consenti-btn consenti-btn--close",v.setAttribute("aria-label","Close preference modal"),v.textContent="\xD7",v.addEventListener("click",n.onClose),g.appendChild(v)}m.appendChild(g)}if(p.appendChild(m),e.htmlText){let g=document.createElement("div");g.className="consenti-modal__text",g.innerHTML=e.htmlText,P.appendChild(g)}let C=e.buttons.filter(g=>g.action==="link");if(C.length>0){let g=document.createElement("div");g.className="consenti-modal__links";for(let v of C){if(!v.url)continue;let k=document.createElement("a");k.href=v.url,k.className=`consenti-modal__link consenti-btn consenti-btn--${v.style??"text"}`,k.textContent=v.text,k.setAttribute("target","_blank"),k.setAttribute("rel","noopener noreferrer"),g.appendChild(k)}p.appendChild(g)}let Y=document.createElement("div");Y.className="consenti-modal__categories";for(let g of e.categories){let v=this.buildCategory(g,t);Y.appendChild(v)}if(P.appendChild(Y),p.appendChild(P),o&&P.appendChild(this.buildReceiptOption()),a){let g=document.createElement("div");g.className="consenti-modal__dpdpa-notice";let v=Ve=>Ve.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;"),k=a.grievanceEmail.includes("@")&&!a.grievanceEmail.toLowerCase().startsWith("javascript:")?a.grievanceEmail:"";g.innerHTML=[`<strong>Data Fiduciary:</strong> ${v(a.dataFiduciary)}.`,a.purposeDescription?` <strong>Purpose:</strong> ${v(a.purposeDescription)}.`:""," You may withdraw consent at any time or contact our Grievance Officer at",k?` <a href="mailto:${v(k)}">${v(k)}</a>.`:"."].join(""),p.appendChild(g)}let F=document.createElement("div");F.className="consenti-modal__buttons";for(let g of e.buttons.filter(v=>v.action!=="link"))F.appendChild(N(g,n));p.appendChild(F);let G=document.createElement("a");G.href="https://consenti.dev",G.className="consenti-modal__powered-by",G.textContent="Powered by Consenti",G.setAttribute("target","_blank"),G.setAttribute("rel","noopener noreferrer"),u&&G.classList.add("consenti-d-none"),P.appendChild(G),f.appendChild(p),f.addEventListener("consenti:closeRequest",()=>n.onClose()),this.el=f}buildCategory(e,t){let n=e.type==="legitimate_interest",o=e.mandatory===!0,a;o?a=!0:n?a=t[e.cookies[0]??""]!=="objected":a=t[e.cookies[0]??""]==="granted",this.toggleState.set(e.id,a);let r=document.createElement("div");r.className=`consenti-category${o?" consenti-category--mandatory":""}`;let s=document.createElement("div");s.className="consenti-category__header";let c=e.headingTag??"h3",u=document.createElement(c);u.id=`consenti-cat-${e.id}-heading`,u.className="consenti-category__heading",u.textContent=e.heading,s.appendChild(u);let d=document.createElement("button");d.type="button",d.className=`consenti-category__toggle${o?" consenti-category__toggle--mandatory":""}`,d.setAttribute("role","switch"),d.setAttribute("aria-checked",String(a)),d.setAttribute("aria-labelledby",`consenti-cat-${e.id}-heading`),o&&d.setAttribute("aria-disabled","true");let f=document.createElement("span");f.className="consenti-category__toggle-icon consenti-category__toggle-icon--check",f.setAttribute("aria-hidden","true"),f.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>',d.appendChild(f);let y=document.createElement("span");y.className="consenti-category__toggle-icon consenti-category__toggle-icon--cross",y.setAttribute("aria-hidden","true"),y.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>',d.appendChild(y);let p=document.createElement("span");p.className="consenti-category__toggle-knob",p.setAttribute("aria-hidden","true"),d.appendChild(p),o||d.addEventListener("click",()=>{let w=!(this.toggleState.get(e.id)??!1);this.toggleState.set(e.id,w),d.setAttribute("aria-checked",String(w))}),this.toggleMap.set(e.id,d),s.appendChild(d),r.appendChild(s);let m=document.createElement("div");if(m.className="consenti-category__text",m.innerHTML=e.htmlText,r.appendChild(m),n&&e.legitimateInterest?.description){let P=document.createElement("p");P.className="consenti-category__li-desc",P.textContent=e.legitimateInterest.description,r.appendChild(P)}return r}buildReceiptOption(){let e=document.createElement("div");e.className="consenti-modal__receipt-option";let t=document.createElement("label");t.className="consenti-modal__receipt-label";let n=document.createElement("input");n.type="checkbox",n.className="consenti-modal__receipt-checkbox",n.id="consenti-receipt-checkbox",n.setAttribute("aria-describedby","consenti-receipt-desc"),this.receiptCheckbox=n,t.appendChild(n),t.append(" Get a copy of my consent choices (JSON)"),e.appendChild(t);let o=document.createElement("p");return o.id="consenti-receipt-desc",o.className="consenti-modal__receipt-desc",o.textContent="A JSON file will be downloaded to your device when you save your preferences.",e.appendChild(o),e}open(e){if(!this.el)return;this.triggerEl=e??document.activeElement,this.receiptCheckbox&&(this.receiptCheckbox.checked=!1);let t=this.mobileBreakpoint>0&&window.innerWidth<=this.mobileBreakpoint;t?this.el.classList.add("consenti-modal--fullscreen"):this.el.classList.remove("consenti-modal--fullscreen"),document.body.appendChild(this.el),(!this.passthrough||t)&&(this.savedBodyOverflow=document.body.style.overflow,document.body.style.overflow="hidden"),this.passthrough||(this.trapCleanup=He(this.el))}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 t={};for(let n of e){let o=n.type==="legitimate_interest",a=n.mandatory===!0,r=this.toggleState.get(n.id)??!1;for(let s of n.cookies)a?t[s]="granted":o?t[s]=r?"granted":"objected":t[s]=r?"granted":"denied"}return t}destroy(){this.trapCleanup?.(),this.savedBodyOverflow!==void 0&&(document.body.style.overflow=this.savedBodyOverflow,this.savedBodyOverflow=void 0),this.el?.remove(),this.el=null,this.toggleMap.clear(),this.toggleState.clear(),this.receiptCheckbox=null}};function gt(i,e,t){let n=document.createElement("div");n.className="consenti-locale-switcher consenti-locale-switcher--modal";let o=document.createElement("button");o.type="button",o.className="consenti-locale-switcher__btn consenti-btn consenti-btn--close",o.setAttribute("aria-label","Switch language"),o.setAttribute("aria-haspopup","listbox"),o.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 i){let s=document.createElement("li");s.setAttribute("role","option"),s.setAttribute("aria-selected",String(r===e));let c=document.createElement("button");c.type="button",c.className=`consenti-locale-switcher__option${r===e?" consenti-locale-switcher__option--active":""}`,c.textContent=r,c.addEventListener("click",u=>{u.stopPropagation(),a.hidden=!0,r!==e&&t(r)}),s.appendChild(c),a.appendChild(s)}return o.addEventListener("click",r=>{r.stopPropagation(),a.hidden=!a.hidden}),document.addEventListener("click",()=>{a.hidden=!0},{capture:!0,once:!1}),n.appendChild(o),n.appendChild(a),n}var ze="consenti-root",V=class{constructor(){this.root=null;this.styleEl=null}mount(e){if(!l())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 n=document.querySelector(e);if(!n)throw new Error(`[Consenti] rootEl selector "${e}" did not match any element.`);this.root=n}else{if(!document.contains(e))throw new Error("[Consenti] rootEl element is not attached to the document.");this.root=e}return this.root}let t=document.getElementById(ze);return t||(t=document.createElement("div"),t.id=ze,document.body.appendChild(t)),this.root=t,t}injectStyles(e){if(!l()||this.styleEl)return;let t=document.createElement("style");t.id="consenti-styles",t.textContent=e,document.head.appendChild(t),this.styleEl=t}unmount(){this.root?.remove(),this.styleEl?.remove(),this.root=null,this.styleEl=null}announceToScreenReader(e){if(!l())return;let t=document.getElementById("consenti-announcer");t||(t=document.createElement("div"),t.id="consenti-announcer",t.setAttribute("role","status"),t.setAttribute("aria-live","polite"),t.setAttribute("aria-atomic","true"),t.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(t)),t.textContent="",requestAnimationFrame(()=>{t&&(t.textContent=e)})}};var U=class{constructor(e){this.config=e;this.initialized=!1;this.initializing=!1;this.profile=null;this.resolvedBase=null;this.consentStore=null;this.eventBus=null;this.banner=null;this.modal=null;this.renderer=null;this.broadcastChannel=new E;this.plugins=[];this._bannerVariant=null;this._eventHandlers=new Map;this.readyPromise=new Promise(t=>{this.readyResolve=t}),l()&&(de(e.core.console??["error"]),e.api?.enabled&&e.core.storage==="localStorage"&&(b.warn("localStorage storage mode cannot be used with API mode (API sets cookies via Set-Cookie header). Falling back to cookie storage."),e.core.storage="cookie"),e.autoInit!==!1&&this.init().catch(t=>b.warn("Init error:",t)))}profileId(){return this.profile?.id??0}async init(){if(l()&&!this.initializing){this.initializing=!0,this.initialized=!1,this.readyPromise=new Promise(e=>{this.readyResolve=e});try{let e=this.config.core.storage??"cookie",t=this.config.core.userId??T.getOrCreateUserId(this.profileId(),e),n=await Ne(this.config);this.resolvedBase=n,this.config.darkMode!==void 0&&(n.darkMode=this.config.darkMode);let o=this.config.profileOverride?x(n,this.config.profileOverride):n;this.profile=o,this.eventBus=new R(this.profileId(),this.config.utils?.gtm),this.consentStore=new T(t,this.profileId(),e,this.config.core.cookieDomains),this.consentStore.setProfileCookies(o.cookies),this.renderer=new V,!this.config.core.disableCssTemplate&&Z&&this.renderer.injectStyles(Z),this.banner=new H,this.modal=new z,this.applyTheme(),this.applyDarkMode(),this.broadcastChannel.connect(p=>this.handleBroadcastMessage(p)),await this.runPlugins();let a=o.cookieSigningKey??this.config.core.cookieSigningKey,s=(a?await this.consentStore.readAndVerify(a):this.consentStore.read())!==null;this.eventBus.dispatch("consenti:bannerInitialized",{profileId:this.profileId(),hasExistingConsent:s});let c=o.gpcMode,u=this.config.core.autoHonorGPC!==void 0?this.config.core.autoHonorGPC:c==="strict"?"strict":c==="honor";if(u&&M()){let p=Oe(o.cookies,{},u);if(u==="strict"){await this.submitConsent(p,!0),this.initialized=!0,this.readyResolve();return}if(!s){let m=o.cookieSigningKey??this.config.core.cookieSigningKey;m&&this.consentStore?this.lastSignature=await this.consentStore.writeAndSign(p,m):this.consentStore?.write(p),this.broadcastChannel.send({type:"consentUpdated",consent:p,profileId:this.profileId()}),this.eventBus?.dispatch("consenti:consentSubmitted",{consent:p,gpcDetected:!0,consentId:crypto.randomUUID(),consentAction:this.deriveAction(p,!1),pageUrl:window.location.href}),this.showBanner(!0),this.initialized=!0,this.readyResolve();return}}let d=o.complianceGroup??"",f=d==="opt-out",y=d==="opt-out-strict";if(f&&!s){let p={};for(let P of o.cookies)p[P.id]="granted";let m=o.cookieSigningKey??this.config.core.cookieSigningKey;m?await this.consentStore.writeAndSign(p,m):this.consentStore.write(p),this.initialized=!0,this.readyResolve();return}if(y&&!s){let p=this.config.core.autoHonorGPC&&M(),m={};for(let w of o.cookies){if(w.mandatory){m[w.id]="granted";continue}w.cpraCategory==="sensitive"||p&&(w.cpraCategory==="sale"||w.cpraCategory==="sharing")?m[w.id]="denied":m[w.id]="granted"}let P=o.cookieSigningKey??this.config.core.cookieSigningKey;P?await this.consentStore.writeAndSign(m,P):this.consentStore.write(m),this.initialized=!0,this.readyResolve();return}s||this.showBanner(),this.initialized=!0,this.readyResolve()}finally{this.initializing=!1}}}applyDarkMode(){if(!this.renderer)return;let e=this.config.darkMode??this.profile?.darkMode??!1,t=this.renderer.mount(this.config.rootEl);e?t.classList.add("consenti-root--dark"):t.classList.remove("consenti-root--dark")}applyTheme(){let e=this.config.core.theme;if(!e||!this.renderer)return;let t={"--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},n=this.renderer.mount(this.config.rootEl);for(let[o,a]of Object.entries(t))a!==void 0&&n.style.setProperty(o,a);if(e.fontSizeMultiplier){let o=parseFloat(e.fontSizeMultiplier);if(!isNaN(o)){let a=parseFloat(getComputedStyle(n).getPropertyValue("--consenti-font-size-base")||"14"),r=parseFloat(getComputedStyle(n).getPropertyValue("--consenti-font-size-heading")||"16");n.style.setProperty("--consenti-font-size-base",`${Math.round(a*o)}px`),n.style.setProperty("--consenti-font-size-heading",`${Math.round(r*o)}px`)}}}async runPlugins(){this.plugins=this.config.plugins??[];for(let e of this.plugins)try{await e.initialize(this)}catch(t){b.warn("Plugin initialize error:",t)}}async callPluginHook(e){for(let t of this.plugins)try{await e(t,this)}catch(n){b.warn("Plugin hook failed:",n)}}_buildGrantAllConsent(e=!1){let t={};for(let n of this.profile?.cookies??[])t[n.id]=e&&!n.mandatory?"denied":"granted";return t}_buildDenyAllConsent(e=!1){let t={};for(let n of this.profile?.cookies??[])t[n.id]=!e&&n.mandatory?"granted":"denied";return t}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 t=this.modal.getToggleConsent(this.profile.preferenceModal.categories);this.submitConsent(t),this.hideModal()}else{let t={};for(let n of this.profile?.cookies??[])n.mandatory&&(t[n.id]="granted");this.submitConsent(t),e==="banner"&&(this.banner?.hide(),this._bannerVariant=null)}},onManage:()=>{this.showModal()},onClose:()=>{e==="modal"?this.hideModal():(this.banner?.hide(),this._bannerVariant=null,this.broadcastChannel.send({type:"bannerClosed"}),this.eventBus?.dispatch("consenti:bannerVisibility",{show:!1,action:!0}),this.callPluginHook(t=>t.onBannerHide?.()))},onGrantSpecific:t=>{let n={};for(let o of this.profile?.cookies??[])o.mandatory||t.includes(o.id)?n[o.id]="granted":n[o.id]="denied";this.submitConsent(n),e==="banner"&&this.hideBanner()}}}deriveAction(e,t){if(t)return"update";let n=this.profile?.cookies.filter(r=>!r.mandatory)??[];if(n.length===0)return"custom";let o=n.every(r=>e[r.id]==="granted"),a=n.every(r=>e[r.id]==="denied"||e[r.id]==="objected");return o?"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",{consent:e.consent,fromBroadcast:!0});break;case"bannerClosed":this.banner?.hide(),this._bannerVariant=null;break;case"consentDeleted":this.consentStore?.clearInMemory();break}}hasConsent(){return l()?this.consentStore?.hasConsent()??!1:!1}getConsent(){return l()?this.consentStore?.getConsent()??null:null}getConsentDate(){return l()?this.consentStore?.getConsentDate()??!1:!1}getGTMConsent(){let e=this.getConsent();if(!e)return null;let t=["ad_storage","analytics_storage","ad_user_data","ad_personalization","functionality_storage","personalization_storage","security_storage"],n={};for(let o of t){let a=e[o];if(a==="granted")n[o]="granted";else if(a==="denied"||a==="objected")n[o]="denied";else{let r=o==="functionality_storage"||o==="security_storage"?"granted":"denied";n[o]=r}}return n}async submitConsent(e,t=!1){if(!l()||!this.profile||!this.consentStore)return;let n=this.profile.complianceGroup??"",o=this.consentStore.hasConsent(),a=crypto.randomUUID(),r=window.location.href,s={};for(let y of this.profile.cookies)if(y.mandatory)s[y.id]="granted";else if(n==="opt-out-strict"){let p=y.cpraCategory==="sensitive"?"denied":"granted";s[y.id]=e[y.id]??p}else{let p=n!=="opt-out"&&n!=="notice-only";s[y.id]=e[y.id]??(p?"denied":"granted")}let c=this.deriveAction(s,o);this.eventBus?.dispatch("consenti:consentBeingSubmitted",{consent:s,consentId:a});let u;if(this.config.api?.enabled)try{let y=this.config.api.baseUrl??window.location.origin,{httpRequest:p}=await Promise.resolve().then(()=>(I(),te));u=await p(`${y}/consenti/api/v1/consent`,{method:"POST",body:JSON.stringify({profileId:this.profileId(),profileUuid:this.profile?.profileUuid,consent:s,locale:this.config.core.locale??"en",gpcDetected:t||M(),consentId:a,action:c,pageUrl:r})},this.config.api.authToken)}catch(y){b.warn("API consent submission failed:",y)}let d=this.profile.cookieSigningKey??this.config.core.cookieSigningKey;if(d?this.lastSignature=await this.consentStore.writeAndSign(s,d):this.consentStore.write(s),(this.modal?.isReceiptRequested()??!1)&&this.config.core.allowReceipt){let m={visitorId:this.consentStore.cookieName.split("_").slice(1,-1).join("_"),profileId:this.profileId(),profileVersion:this.profile.version,locale:this.config.core.locale??"en",consent:s,...this.lastSignature?{signature:this.lastSignature}:{}},P=ue(m);me(P)}this.broadcastChannel.send({type:"consentUpdated",consent:s,profileId:this.profileId()}),this.eventBus?.dispatch("consenti:consentSubmitted",{consent:s,apiResponse:u,gpcDetected:t,consentId:a,consentAction:c,pageUrl:r}),await this.callPluginHook(y=>y.onConsentSubmit?.(s)),this.renderer?.announceToScreenReader("Your cookie preferences have been saved."),this.hideBanner(),this.modal?.isOpen()&&this.hideModal()}bannerVisibility(){return this._bannerVariant??!1}modalVisibility(){return this.modal?.isOpen()?"preference":!1}showBanner(e=!1){if(!l()||!this.profile||!this.banner||!this.renderer)return;let t=e&&this.profile.gpcBanner?"gpc":"main";if(this._bannerVariant===t)return;let n=t==="gpc"?this.profile.gpcBanner:this.profile.mainBanner,o=this.renderer.mount(this.config.rootEl),a=this.buildHandlers("banner");this.banner.remove();let r=this.profile.locales,s=this.config.core.locale??"en",c=this.banner.build(n,a,void 0,r,s,d=>this.switchLocale(d),this.config.hidePoweredBy??!1);o.appendChild(c);let u=this.banner.getOverlay();u&&o.appendChild(u),this._bannerVariant=t,this.eventBus?.dispatch("consenti:bannerVisibility",{show:!0,action:!1}),this.callPluginHook(d=>d.onBannerShow?.())}hideBanner(){this._bannerVariant!==null&&(this.banner?.hide(),this._bannerVariant=null,this.broadcastChannel.send({type:"bannerClosed"}),this.eventBus?.dispatch("consenti:bannerVisibility",{show:!1,action:!0}),this.renderer?.announceToScreenReader("Cookie banner dismissed."),this.callPluginHook(e=>e.onBannerHide?.()))}showModal(e){if(!l()||!this.profile||!this.modal||this.modal.isOpen())return;let t=this.buildHandlers("modal"),n=this.consentStore?.getConsent()??{},o=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,n,t,this.config.core.allowReceipt??!1,o,a,r,c=>{this.hideModal(),this.switchLocale(c)},!(this.config.hidePoweredBy??!1)),this.banner?.hide(),this.modal.open(e);let s=this.config.darkMode??this.profile?.darkMode??!1;document.getElementById("consenti-modal")?.classList.toggle("consenti-root--dark",s),this.eventBus?.dispatch("consenti:modalVisibility",{show:!0,action:!0}),this.callPluginHook(c=>c.onModalShow?.())}hideModal(){this.modal?.isOpen()&&(this.modal.close(),this.eventBus?.dispatch("consenti:modalVisibility",{show:!1,action:!0}),this.callPluginHook(e=>e.onModalHide?.()),!this.consentStore?.hasConsent()&&this._bannerVariant!==null?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(!(!l()||!this.consentStore)){if(this.config.api?.enabled)try{let e=this.config.api.baseUrl??window.location.origin,t=this.consentStore.cookieName,{httpRequest:n}=await Promise.resolve().then(()=>(I(),te));await n(`${e}/consenti/api/v1/consent/${t}`,{method:"DELETE"},this.config.api?.authToken)}catch(e){b.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 l()?this.config.rootEl?typeof this.config.rootEl=="string"?document.querySelector(this.config.rootEl):this.config.rootEl:document.getElementById("consenti-root"):null}getBannerElement(){return l()?document.getElementById("consenti-banner"):null}getModalElement(){return l()?document.getElementById("consenti-modal"):null}switchLocale(e){l()&&(this.config.core.locale=e,this.banner?.remove(),this.modal?.isOpen()&&this.modal.close(),this._bannerVariant=null,this.profile=null,this.initialized=!1,this.readyPromise=new Promise(t=>{this.readyResolve=t}),this.init().catch(t=>b.warn("Locale switch error:",t)))}isCookieGranted(e,t=!1){if(!l())return!1;let n=this.getConsent()?.[e];return t?n??!1:n==="granted"}isCategoryGranted(e,t=!1){if(!l()||!this.profile)return t?[]:!1;let n=this.profile.preferenceModal.categories.find(a=>a.id===e);if(!n)return t?[]:!1;let o=this.getConsent();return t?n.cookies.map(a=>({[a]:o?.[a]??"denied"})):n.cookies.every(a=>o?.[a]==="granted")}async grantAll(e=!1){!l()||!this.profile||await this.submitConsent(this._buildGrantAllConsent(e))}async denyAll(e=!1){!l()||!this.profile||(e&&b.warn("denyAll called with includingMandatory=true \u2014 mandatory cookies will be denied. Ensure this is intentional."),await this.submitConsent(this._buildDenyAllConsent(e)))}on(e,t){if(!l())return;let n=e.startsWith("consenti:")?e:`consenti:${e}`,o=r=>t(r.detail),a=this._eventHandlers.get(t)??[];a.push({name:n,wrapped:o}),this._eventHandlers.set(t,a),window.addEventListener(n,o)}off(e,t){if(!l())return;let n=e.startsWith("consenti:")?e:`consenti:${e}`,o=this._eventHandlers.get(t);if(!o)return;let a=o.findIndex(s=>s.name===n);if(a===-1)return;let r=o[a];r&&window.removeEventListener(n,r.wrapped),o.splice(a,1),o.length===0&&this._eventHandlers.delete(t)}version(){return{package:"0.2.0",profileVersion:this.profile?.version??null,consentVersion:1}}setDarkMode(e){if(!l())return;this.config.darkMode=e===void 0?!this.config.darkMode:e,this.applyDarkMode();let t=this.config.darkMode??!1;document.getElementById("consenti-modal")?.classList.toggle("consenti-root--dark",t)}setTheme(e){l()&&(this.config.core.theme=Object.assign({},this.config.core.theme,e),this.applyTheme())}setConfig(e){this.config=x(this.config,e),l()&&(this.applyTheme(),this.applyDarkMode())}setProfile(e){if(!l()||!this.resolvedBase)return;this.config.profileOverride=this.config.profileOverride?x(this.config.profileOverride,e):e,this.profile=x(this.resolvedBase,this.config.profileOverride);let t=this.modal?.isOpen()??!1;if(t&&this.modal?.close(),this._bannerVariant!==null){let n=this._bannerVariant==="gpc";this._bannerVariant=null,this.showBanner(n)}t&&this.showModal()}destroy(){for(let e of this.plugins)try{e.destroy()}catch{}if(this.modal?.destroy(),this.renderer?.unmount(),this.broadcastChannel.disconnect(),l()){for(let e of this._eventHandlers.values())for(let{name:t,wrapped:n}of e)window.removeEventListener(t,n);this._eventHandlers.clear()}this.banner=null,this.modal=null,this.renderer=null,this.consentStore=null,this.eventBus=null,this.initialized=!1}};var j=class{};var ht=1e3,K=class{constructor(e){this.config=e;this.id=ht++,Ee(this.id,e)}getId(){return this.id}toJSON(){return this.config}};var $=class{constructor(e){this.options=e;this.scriptEl=null;this.handler=()=>this.evaluate(),l()&&(e.bind!==!1&&window.addEventListener("consenti:consentSubmitted",this.handler),this.evaluate())}evaluate(){let t=this.options.widget.getConsent()?.[this.options.cookieId]==="granted";t&&!this.scriptEl?this.inject():!t&&this.scriptEl&&this.revoke()}inject(){let e=document.createElement("script");if(this.options.src){let t=this.options.src.trim();if(/^javascript:/i.test(t)){b.warn("ConsentScript: rejected src with javascript: protocol");return}e.src=t}this.options.unsafeInnerHTML&&(e.innerHTML=this.options.unsafeInnerHTML);for(let[t,n]of Object.entries(this.options.attributes??{})){if(/^on/i.test(t)){b.warn(`ConsentScript: rejected attribute "${t}" (inline event handlers are not allowed)`);continue}e.setAttribute(t,n)}document.head.appendChild(e),this.scriptEl=e,this.options.onLoad?.()}revoke(){this.scriptEl?.remove(),this.scriptEl=null,this.options.onRevoke?.()}destroy(){l()&&(this.options.bind!==!1&&window.removeEventListener("consenti:consentSubmitted",this.handler),this.revoke())}};var q=class{constructor(e){let t=e.action??"modal";if(this.clickHandler=()=>{t==="modal"?e.widget.showModal():e.widget.showBanner()},e.el)if(typeof e.el=="string"){if(!l()){this.el=document.createElement("span"),this.created=!1;return}let n=document.querySelector(e.el);if(!n)throw new Error(`[Consenti] CookieTrigger: element not found for selector "${e.el}"`);this.el=n,this.created=!1}else this.el=e.el,this.created=!1;else{let n=document.createElement("button");n.type="button",n.className="consenti-btn consenti-btn--text consenti-cookie-trigger",n.textContent=e.label??"Cookie Settings",n.setAttribute("aria-label",e.label??"Open cookie settings"),this.el=n,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()}};return qe(vt);})();
2305
2
  //# sourceMappingURL=index.umd.js.map