@freshjuice/zest 1.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +178 -78
  2. package/dist/zest.d.ts +214 -0
  3. package/dist/zest.de.js +692 -305
  4. package/dist/zest.de.js.map +1 -1
  5. package/dist/zest.de.min.js +1 -1
  6. package/dist/zest.en.js +692 -305
  7. package/dist/zest.en.js.map +1 -1
  8. package/dist/zest.en.min.js +1 -1
  9. package/dist/zest.es.js +692 -305
  10. package/dist/zest.es.js.map +1 -1
  11. package/dist/zest.es.min.js +1 -1
  12. package/dist/zest.esm.js +692 -305
  13. package/dist/zest.esm.js.map +1 -1
  14. package/dist/zest.esm.min.js +1 -1
  15. package/dist/zest.fr.js +692 -305
  16. package/dist/zest.fr.js.map +1 -1
  17. package/dist/zest.fr.min.js +1 -1
  18. package/dist/zest.headless.d.ts +178 -0
  19. package/dist/zest.headless.esm.js +2299 -0
  20. package/dist/zest.headless.esm.js.map +1 -0
  21. package/dist/zest.headless.esm.min.js +1 -0
  22. package/dist/zest.it.js +692 -305
  23. package/dist/zest.it.js.map +1 -1
  24. package/dist/zest.it.min.js +1 -1
  25. package/dist/zest.ja.js +692 -305
  26. package/dist/zest.ja.js.map +1 -1
  27. package/dist/zest.ja.min.js +1 -1
  28. package/dist/zest.js +692 -305
  29. package/dist/zest.js.map +1 -1
  30. package/dist/zest.min.js +1 -1
  31. package/dist/zest.nl.js +692 -305
  32. package/dist/zest.nl.js.map +1 -1
  33. package/dist/zest.nl.min.js +1 -1
  34. package/dist/zest.pl.js +692 -305
  35. package/dist/zest.pl.js.map +1 -1
  36. package/dist/zest.pl.min.js +1 -1
  37. package/dist/zest.pt.js +692 -305
  38. package/dist/zest.pt.js.map +1 -1
  39. package/dist/zest.pt.min.js +1 -1
  40. package/dist/zest.ru.js +692 -305
  41. package/dist/zest.ru.js.map +1 -1
  42. package/dist/zest.ru.min.js +1 -1
  43. package/dist/zest.uk.js +692 -305
  44. package/dist/zest.uk.js.map +1 -1
  45. package/dist/zest.uk.min.js +1 -1
  46. package/dist/zest.zh.js +692 -305
  47. package/dist/zest.zh.js.map +1 -1
  48. package/dist/zest.zh.min.js +1 -1
  49. package/package.json +23 -4
  50. package/src/core/cookie-interceptor.js +20 -5
  51. package/src/core/known-trackers.js +41 -14
  52. package/src/core/pattern-matcher.js +20 -5
  53. package/src/core/script-blocker.js +85 -79
  54. package/src/core/security.js +204 -0
  55. package/src/core/storage-interceptor.js +5 -1
  56. package/src/core-lifecycle.js +192 -0
  57. package/src/headless.js +133 -0
  58. package/src/index.js +73 -184
  59. package/src/storage/consent-store.js +32 -8
  60. package/src/types/zest.d.ts +214 -0
  61. package/src/types/zest.headless.d.ts +178 -0
  62. package/src/ui/banner.js +11 -7
  63. package/src/ui/modal.js +16 -12
  64. package/src/ui/styles.js +25 -4
  65. package/src/ui/widget.js +3 -1
package/README.md CHANGED
@@ -7,11 +7,12 @@
7
7
 
8
8
  A lightweight cookie consent toolkit for GDPR/CCPA compliance.
9
9
 
10
- - **Lightweight** - ~9KB gzipped (single language) / ~14KB (all 12 languages)
11
- - **Zero dependencies** - Vanilla JavaScript
12
- - **Shadow DOM** - Styles isolated from your site
13
- - **Modern browsers** - No IE11 polyfills needed
14
- - **Privacy-first** - Respects Do Not Track / Global Privacy Control
10
+ - **Lightweight** ~9KB gzipped (single language) / ~16KB (all 12 languages) / ~11KB (headless)
11
+ - **Zero dependencies** Vanilla JavaScript
12
+ - **Shadow DOM** Styles isolated from your site
13
+ - **Headless mode** Bring your own UI & CSS, use only the consent engine
14
+ - **Privacy-first** Respects Do Not Track / Global Privacy Control
15
+ - **Security-hardened** — XSS-safe templating, URL/color/regex validation, locked interceptors
15
16
 
16
17
  ## Quick Start
17
18
 
@@ -23,7 +24,7 @@ A lightweight cookie consent toolkit for GDPR/CCPA compliance.
23
24
  <script src="https://cdn.jsdelivr.net/npm/@freshjuice/zest"></script>
24
25
  ```
25
26
 
26
- Or with configuration:
27
+ With configuration:
27
28
 
28
29
  ```html
29
30
  <script>
@@ -37,6 +38,23 @@ Or with configuration:
37
38
  <script src="https://unpkg.com/@freshjuice/zest"></script>
38
39
  ```
39
40
 
41
+ As an npm dependency:
42
+
43
+ ```js
44
+ import Zest from '@freshjuice/zest';
45
+
46
+ Zest.init({ mode: 'safe', policyUrl: '/privacy' });
47
+ ```
48
+
49
+ ## Two build flavors
50
+
51
+ | Entry | What you get | Min / Gzip |
52
+ |---|---|---|
53
+ | `@freshjuice/zest` | Consent engine **+ Shadow DOM UI** (banner, modal, widget) | ~50 KB / **~16 KB** |
54
+ | `@freshjuice/zest/headless` | Consent engine only, **no UI / no CSS** — you build the UI | ~31 KB / **~11 KB** |
55
+
56
+ Use **headless** when you want full control over markup and styling.
57
+
40
58
  ## Configuration
41
59
 
42
60
  ### Via `window.ZestConfig`
@@ -49,10 +67,10 @@ window.ZestConfig = {
49
67
  // Theme: 'light' | 'dark' | 'auto' (default: 'auto' follows system)
50
68
  theme: 'auto',
51
69
 
52
- // Accent color for buttons
70
+ // Accent color must be a valid CSS color (hex, named, rgb/rgba, hsl/hsla)
53
71
  accentColor: '#0071e3',
54
72
 
55
- // Link to privacy policy
73
+ // Link to privacy policy — only http:/https:/mailto:/tel:/relative allowed
56
74
  policyUrl: '/privacy',
57
75
 
58
76
  // Show floating widget after consent
@@ -61,7 +79,7 @@ window.ZestConfig = {
61
79
  // Consent expiration in days
62
80
  expiration: 365,
63
81
 
64
- // Callbacks
82
+ // Callbacks — wrapped in try/catch internally, safe to throw
65
83
  callbacks: {
66
84
  onAccept: (consent) => {},
67
85
  onReject: () => {},
@@ -86,26 +104,85 @@ window.ZestConfig = {
86
104
  ## API
87
105
 
88
106
  ```javascript
89
- // Show/hide UI
90
- Zest.show() // Show banner
91
- Zest.hide() // Hide banner
92
- Zest.showSettings() // Show settings modal
93
- Zest.reset() // Clear consent, show banner
94
-
95
- // Consent management
96
- Zest.getConsent() // Get current consent state
97
- Zest.hasConsent('analytics') // Check specific category
98
- Zest.acceptAll() // Accept all categories
99
- Zest.rejectAll() // Reject all (except essential)
107
+ // Show/hide UI (full build only)
108
+ Zest.show() // Show banner
109
+ Zest.hide() // Hide banner
110
+ Zest.showSettings() // Show settings modal
111
+ Zest.hideSettings() // Close settings modal
112
+ Zest.reset() // Clear consent + reshow banner
113
+
114
+ // Consent state
115
+ Zest.getConsent() // { essential, functional, analytics, marketing }
116
+ Zest.hasConsent('analytics') // boolean
117
+ Zest.hasConsentDecision() // boolean has the user made a choice yet?
118
+ Zest.getConsentProof() // full consent cookie payload (compliance audit)
119
+
120
+ // Programmatic actions
121
+ Zest.acceptAll()
122
+ Zest.rejectAll()
123
+ Zest.updateConsent({ analytics: true, marketing: false }) // headless only
124
+
125
+ // DNT / GPC
126
+ Zest.isDoNotTrackEnabled()
127
+ Zest.getDNTDetails() // { enabled, source: 'dnt'|'gpc'|null }
128
+
129
+ // Events — subscribe helpers (also work with addEventListener)
130
+ Zest.on('zest:change', (e) => {})
131
+ Zest.once('zest:ready', (e) => {})
132
+ Zest.EVENTS // { READY, CONSENT, REJECT, CHANGE, SHOW, HIDE }
133
+ ```
134
+
135
+ ## Headless mode — bring your own UI
136
+
137
+ Full control over markup and styling, no Shadow DOM, no inline CSS.
138
+
139
+ ```js
140
+ import Zest from '@freshjuice/zest/headless';
141
+
142
+ Zest.init({
143
+ mode: 'safe',
144
+ respectDNT: true,
145
+ consentModeGoogle: true
146
+ });
147
+
148
+ // Decide when to show YOUR banner
149
+ if (!Zest.hasConsentDecision()) {
150
+ document.querySelector('#my-banner').classList.add('open');
151
+ }
152
+
153
+ // Wire your buttons
154
+ document.querySelector('#accept').onclick = () => Zest.acceptAll();
155
+ document.querySelector('#reject').onclick = () => Zest.rejectAll();
156
+
157
+ document.querySelector('#save').onclick = () => {
158
+ Zest.updateConsent({
159
+ analytics: analyticsCheckbox.checked,
160
+ marketing: marketingCheckbox.checked,
161
+ functional: functionalCheckbox.checked
162
+ });
163
+ };
164
+
165
+ // Listen for changes
166
+ Zest.on(Zest.EVENTS.CHANGE, (e) => {
167
+ console.log('consent changed', e.detail.consent);
168
+ });
100
169
  ```
101
170
 
171
+ What headless gives you:
172
+ - All interceptors (cookies, storage, scripts) still work — just skip the built-in UI
173
+ - Same config surface (`mode`, `respectDNT`, `consentModeGoogle`, `blockedDomains`, `patterns`, etc.)
174
+ - **Does NOT auto-init** — you call `Zest.init()` when ready
175
+ - **Does NOT set `window.Zest`** — you import and use the module directly
176
+
177
+ See `examples/headless.html` for a complete working example.
178
+
102
179
  ## Do Not Track (DNT) / Global Privacy Control (GPC)
103
180
 
104
181
  Zest respects browser privacy signals by default:
105
182
 
106
183
  ```javascript
107
184
  window.ZestConfig = {
108
- respectDNT: true, // Respect DNT/GPC signals (default: true)
185
+ respectDNT: true, // Respect DNT/GPC signals (default: true)
109
186
  dntBehavior: 'reject' // What to do when DNT is enabled
110
187
  };
111
188
  ```
@@ -116,10 +193,9 @@ window.ZestConfig = {
116
193
  | `preselect` | Show banner with non-essential options unchecked |
117
194
  | `ignore` | Ignore DNT/GPC signals completely |
118
195
 
119
- **API methods:**
120
196
  ```javascript
121
- Zest.isDoNotTrackEnabled() // Returns true if DNT or GPC is enabled
122
- Zest.getDNTDetails() // Returns { enabled: boolean, source: 'dnt' | 'gpc' | null }
197
+ Zest.isDoNotTrackEnabled() // true if DNT or GPC is enabled
198
+ Zest.getDNTDetails() // { enabled: boolean, source: 'dnt' | 'gpc' | null }
123
199
  ```
124
200
 
125
201
  ## Blocking Modes
@@ -141,8 +217,6 @@ window.ZestConfig = {
141
217
 
142
218
  ### Custom Blocked Domains
143
219
 
144
- Add your own domains to block:
145
-
146
220
  ```javascript
147
221
  window.ZestConfig = {
148
222
  mode: 'safe',
@@ -155,8 +229,6 @@ window.ZestConfig = {
155
229
 
156
230
  ### Manual Script Tagging
157
231
 
158
- For any mode, you can explicitly tag scripts:
159
-
160
232
  ```html
161
233
  <script data-consent-category="analytics" src="https://..."></script>
162
234
  <script data-consent-category="marketing">
@@ -164,9 +236,11 @@ For any mode, you can explicitly tag scripts:
164
236
  </script>
165
237
  ```
166
238
 
167
- ### Allow Specific Scripts
239
+ > **Note:** `data-consent-category="essential"` on third-party scripts is
240
+ > ignored — self-labeling as essential is a known bypass. Only
241
+ > `functional`, `analytics`, and `marketing` self-labels are honored.
168
242
 
169
- Prevent a script from being blocked (useful in strict/doomsday modes):
243
+ ### Allow Specific Scripts
170
244
 
171
245
  ```html
172
246
  <script data-zest-allow src="https://cdn.example.com/library.js"></script>
@@ -190,18 +264,22 @@ document.addEventListener('zest:change', (e) => {
190
264
  document.addEventListener('zest:ready', (e) => {
191
265
  console.log('Zest initialized:', e.detail.consent);
192
266
  });
267
+
268
+ // Or via the helpers
269
+ Zest.on(Zest.EVENTS.CHANGE, (e) => { /* ... */ });
270
+ Zest.once(Zest.EVENTS.READY, (e) => { /* ... */ });
193
271
  ```
194
272
 
195
273
  ## Google Consent Mode v2 / Microsoft UET Consent Mode
196
274
 
197
- Zest can optionally push consent signals to Google and Microsoft advertising APIs. Both are **disabled by default**.
275
+ Optional push consent state to Google and Microsoft advertising APIs.
198
276
 
199
277
  ### Enable via JavaScript
200
278
 
201
279
  ```javascript
202
280
  window.ZestConfig = {
203
- consentModeGoogle: true, // Google Consent Mode v2
204
- consentModeMicrosoft: true // Microsoft UET Consent Mode
281
+ consentModeGoogle: true,
282
+ consentModeMicrosoft: true
205
283
  };
206
284
  ```
207
285
 
@@ -215,12 +293,10 @@ window.ZestConfig = {
215
293
  ></script>
216
294
  ```
217
295
 
218
- ### How it works
219
-
220
296
  When enabled, Zest automatically:
221
297
 
222
298
  1. Pushes a `'default'` denied state on page load (before any tracking scripts fire)
223
- 2. Pushes an `'update'` with the correct consent state whenever the user makes a choice
299
+ 2. Pushes an `'update'` whenever the user makes a choice
224
300
 
225
301
  ### Category mapping
226
302
 
@@ -231,11 +307,9 @@ When enabled, Zest automatically:
231
307
  | `analytics` | `analytics_storage` | — |
232
308
  | `marketing` | `ad_storage`, `ad_user_data`, `ad_personalization` | `ad_storage` |
233
309
 
234
- No additional setup is needed — just include your Google Analytics/GTM or Microsoft UET tags as usual and Zest will handle the consent signaling.
235
-
236
310
  ## Localization
237
311
 
238
- Zest has **built-in translations** with auto-detection.
312
+ Built-in translations with auto-detection.
239
313
 
240
314
  **Supported languages:** `en`, `de`, `es`, `fr`, `it`, `pt`, `nl`, `pl`, `uk`, `ru`, `ja`, `zh`
241
315
 
@@ -243,8 +317,9 @@ Zest has **built-in translations** with auto-detection.
243
317
 
244
318
  | Bundle | Size (gzip) | Description |
245
319
  |--------|-------------|-------------|
246
- | `zest.min.js` | ~14KB | All 12 languages, auto-detects |
247
- | `zest.{lang}.min.js` | ~9KB | Single language (e.g., `zest.de.min.js`) |
320
+ | `zest.min.js` | ~16 KB | All 12 languages, auto-detects |
321
+ | `zest.{lang}.min.js` | ~9 KB | Single language (e.g. `zest.de.min.js`) |
322
+ | `zest.headless.esm.min.js` | ~11 KB | Logic only, no UI / no translations (ESM import) |
248
323
 
249
324
  ```html
250
325
  <!-- Full bundle - auto-detects language -->
@@ -257,61 +332,40 @@ Zest has **built-in translations** with auto-detection.
257
332
  ### Language Detection
258
333
 
259
334
  ```javascript
260
- window.ZestConfig = {
261
- lang: 'auto' // Auto-detect (default)
262
- };
335
+ window.ZestConfig = { lang: 'auto' }; // default
263
336
  ```
264
337
 
265
- **Detection priority:**
266
- 1. `lang` config option (if not 'auto')
267
- 2. `<html lang="de">` attribute
268
- 3. Browser language (`navigator.language`)
269
- 4. Fallback to English
338
+ Priority: `lang` config → `<html lang="...">` → `navigator.language` → English.
270
339
 
271
340
  ### Force Specific Language
272
341
 
273
342
  ```javascript
274
- window.ZestConfig = {
275
- lang: 'de' // Force German
276
- };
343
+ window.ZestConfig = { lang: 'de' };
277
344
  ```
278
345
 
279
346
  ### Override Labels
280
347
 
281
348
  ```javascript
282
349
  window.ZestConfig = {
283
- lang: 'de', // Use German as base
350
+ lang: 'de',
284
351
  labels: {
285
352
  banner: {
286
- title: 'Custom German Title' // Override just this
353
+ title: 'Custom German Title'
287
354
  }
288
355
  }
289
356
  };
290
357
  ```
291
358
 
292
- Standalone JSON translation files also available in `/locales/` for external loading.
359
+ Standalone JSON translation files are in `/locales/`.
293
360
 
294
- ## Custom Styling
361
+ ## Styling the UI (full build)
295
362
 
296
- Override default styles by passing custom CSS:
363
+ The UI is rendered inside a Shadow DOM with `mode: 'open'`, so your global
364
+ CSS can't reach inside the component. You have three options:
297
365
 
298
- ```javascript
299
- window.ZestConfig = {
300
- customStyles: `
301
- .zest-banner {
302
- max-width: 600px;
303
- }
304
- .zest-btn--primary {
305
- border-radius: 20px;
306
- }
307
- .zest-modal {
308
- max-width: 600px;
309
- }
310
- `
311
- };
312
- ```
366
+ ### 1. CSS custom properties (inheritable through Shadow DOM)
313
367
 
314
- Available CSS custom properties (can also be set via parent CSS):
368
+ The following custom properties are exposed on the host elements:
315
369
 
316
370
  ```css
317
371
  zest-banner, zest-modal, zest-widget {
@@ -326,6 +380,32 @@ zest-banner, zest-modal, zest-widget {
326
380
  }
327
381
  ```
328
382
 
383
+ ### 2. `customStyles` config option
384
+
385
+ ```javascript
386
+ window.ZestConfig = {
387
+ customStyles: `
388
+ .zest-banner { max-width: 600px; }
389
+ .zest-btn--primary { border-radius: 20px; }
390
+ .zest-modal { max-width: 600px; }
391
+ `
392
+ };
393
+ ```
394
+
395
+ > **Security note:** `customStyles` is sanitized — `@import`, `expression()`,
396
+ > external `url()` values, and selectors targeting the accept/reject
397
+ > buttons are stripped. This prevents clickjacking via invisible-button
398
+ > CSS attacks. Payloads over 20 KB are dropped entirely.
399
+
400
+ ### 3. Style the host elements directly
401
+
402
+ The custom elements `zest-banner`, `zest-modal`, `zest-widget` live in the
403
+ light DOM — you can position, hide, or z-index them from your global CSS.
404
+
405
+ ### Want full CSS control?
406
+
407
+ Use the **headless** entry and style your own markup however you like.
408
+
329
409
  ## Categories
330
410
 
331
411
  | Category | ID | Default | Description |
@@ -335,18 +415,38 @@ zest-banner, zest-modal, zest-widget {
335
415
  | Analytics | `analytics` | OFF | Usage tracking |
336
416
  | Marketing | `marketing` | OFF | Advertising cookies |
337
417
 
418
+ Unknown cookies default to `marketing` (strictest).
419
+
420
+ ## Security
421
+
422
+ Zest takes a defense-in-depth approach to security.
423
+
424
+ Highlights:
425
+
426
+ - All config-driven HTML is escaped via an internal `escapeHTML` pass
427
+ - `policyUrl` is validated against an allowlist (`http:`, `https:`,
428
+ `mailto:`, `tel:`, relative)
429
+ - `accentColor` must pass a strict color validator
430
+ - `customStyles` is sanitized (see above)
431
+ - Consent cookie JSON is schema-validated on read (prototype pollution safe)
432
+ - On HTTPS, the consent cookie is written with the `Secure` flag
433
+ - `window.Zest` is frozen and non-configurable once installed
434
+ - User callbacks are wrapped in try/catch so a throwing handler can't
435
+ break the consent flow
436
+ - Cookie / storage / script queues are size-capped (DoS prevention)
437
+
438
+ To report a vulnerability, open a private security advisory on GitHub.
439
+
338
440
  ## Config Schema
339
441
 
340
- JSON Schema available for IDE autocompletion: `zest.config.schema.json`
442
+ JSON Schema for IDE autocompletion: [`zest.config.schema.json`](zest.config.schema.json)
341
443
 
342
444
  ## Contributing
343
445
 
344
- Contributions are welcome! Please feel free to submit a Pull Request.
345
-
346
446
  1. Fork the repository
347
447
  2. Create your feature branch (`git checkout -b feature/amazing-feature`)
348
- 3. Commit your changes (`git commit -m 'Add amazing feature'`)
349
- 4. Push to the branch (`git push origin feature/amazing-feature`)
448
+ 3. Commit your changes
449
+ 4. Push to the branch
350
450
  5. Open a Pull Request
351
451
 
352
452
  ## Credits
package/dist/zest.d.ts ADDED
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Type definitions for `@freshjuice/zest` (full build with UI).
3
+ *
4
+ * The full build ships the consent engine plus a Shadow-DOM banner,
5
+ * settings modal, and floating widget. It auto-initialises on script
6
+ * load when included via `<script>`, or you can drive it manually via
7
+ * `Zest.init()`.
8
+ *
9
+ * For a logic-only build without UI, import from
10
+ * `@freshjuice/zest/headless` instead.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import Zest from '@freshjuice/zest';
15
+ *
16
+ * Zest.init({
17
+ * position: 'bottom-right',
18
+ * theme: 'auto',
19
+ * accentColor: '#0071e3',
20
+ * policyUrl: '/privacy'
21
+ * });
22
+ * ```
23
+ */
24
+
25
+ /** Built-in consent categories. */
26
+ export type ConsentCategory =
27
+ | 'essential'
28
+ | 'functional'
29
+ | 'analytics'
30
+ | 'marketing';
31
+
32
+ /**
33
+ * Per-category boolean consent state. `essential` is always `true` —
34
+ * consent for it cannot be revoked because it covers strictly-necessary
35
+ * processing.
36
+ */
37
+ export type ConsentState =
38
+ & Partial<Record<ConsentCategory, boolean>>
39
+ & { essential: true };
40
+
41
+ /** Snapshot returned by `init()`. */
42
+ export interface InitSnapshot {
43
+ consent: ConsentState;
44
+ hasDecision: boolean;
45
+ dntApplied: boolean;
46
+ }
47
+
48
+ /** Tamper-evident proof of the user's last consent decision. */
49
+ export interface ConsentProof {
50
+ version: string;
51
+ timestamp: number;
52
+ categories: ConsentState;
53
+ }
54
+
55
+ /** Output of `getDNTDetails()`. */
56
+ export interface DNTDetails {
57
+ dnt: boolean;
58
+ gpc: boolean;
59
+ doNotTrack: string | null;
60
+ globalPrivacyControl: boolean;
61
+ }
62
+
63
+ /** Behaviour when DNT / GPC is detected at init time. */
64
+ export type DNTBehavior = 'reject' | 'preselect' | 'ignore';
65
+
66
+ /** Banner position on the page. */
67
+ export type BannerPosition = 'bottom' | 'bottom-left' | 'bottom-right' | 'top';
68
+
69
+ /** UI theme. `auto` follows `prefers-color-scheme`. */
70
+ export type ZestTheme = 'light' | 'dark' | 'auto';
71
+
72
+ /** Script-blocking strictness. */
73
+ export type ZestMode = 'manual' | 'safe' | 'strict' | 'doomsday';
74
+
75
+ /**
76
+ * Optional consumer callbacks. Each is wrapped in a try/catch internally
77
+ * so a thrown error never breaks the consent pipeline.
78
+ */
79
+ export interface ZestCallbacks {
80
+ onAccept?: (consent: ConsentState) => void;
81
+ onReject?: (consent: ConsentState) => void;
82
+ onChange?: (consent: ConsentState) => void;
83
+ onReady?: (consent: ConsentState) => void;
84
+ }
85
+
86
+ /** Configuration accepted by `init()` and `window.ZestConfig`. */
87
+ export interface InitOptions {
88
+ /** Display language. `'auto'` detects from `<html lang>` / browser. */
89
+ lang?:
90
+ | 'auto'
91
+ | 'en' | 'de' | 'es' | 'fr' | 'it' | 'pt'
92
+ | 'nl' | 'pl' | 'uk' | 'ru' | 'ja' | 'zh';
93
+ /** Banner position. Default `'bottom'`. */
94
+ position?: BannerPosition;
95
+ /** UI theme. Default `'auto'`. */
96
+ theme?: ZestTheme;
97
+ /** Hex accent color for buttons (e.g. `'#0071e3'`). */
98
+ accentColor?: string;
99
+ /** Link to the host site's privacy policy. */
100
+ policyUrl?: string;
101
+ /** Show floating "manage cookies" widget after a decision. Default `true`. */
102
+ showWidget?: boolean;
103
+ /** Cookie expiration in days. Default `365`. */
104
+ expiration?: number;
105
+ /** Script-blocking mode. Default `'safe'`. */
106
+ mode?: ZestMode;
107
+ /** Auto-initialise on script load. Default `true` for the UI build. */
108
+ autoInit?: boolean;
109
+ /** Respect Do Not Track / Global Privacy Control. Default `true`. */
110
+ respectDNT?: boolean;
111
+ /** What to do when DNT/GPC is on. Default `'reject'`. */
112
+ dntBehavior?: DNTBehavior;
113
+ /** Consumer callbacks. */
114
+ callbacks?: ZestCallbacks;
115
+ /** Anything else — Zest tolerates unknown keys at runtime. */
116
+ [key: string]: unknown;
117
+ }
118
+
119
+ /** Event names emitted on `document.documentElement`. */
120
+ export interface ZestEvents {
121
+ READY: 'zest:ready';
122
+ CONSENT: 'zest:consent';
123
+ REJECT: 'zest:reject';
124
+ CHANGE: 'zest:change';
125
+ SHOW: 'zest:show';
126
+ HIDE: 'zest:hide';
127
+ }
128
+
129
+ export type ZestEventName = ZestEvents[keyof ZestEvents];
130
+
131
+ /** Detail payload of consent events. */
132
+ export interface ZestEventDetail {
133
+ consent: ConsentState;
134
+ previous?: ConsentState;
135
+ }
136
+
137
+ declare const Zest: {
138
+ /** Initialise. Auto-called when the script loads unless `autoInit: false`. */
139
+ init(options?: InitOptions): InitSnapshot;
140
+
141
+ /** Show the consent banner. */
142
+ show(): void;
143
+
144
+ /** Hide the consent banner. */
145
+ hide(): void;
146
+
147
+ /** Open the per-category settings modal. */
148
+ showSettings(): void;
149
+
150
+ /** Close the settings modal. */
151
+ hideSettings(): void;
152
+
153
+ /** Show the persistent "manage cookies" widget. */
154
+ showWidget(): void;
155
+
156
+ /** Hide the widget without removing it. */
157
+ hideWidget(): void;
158
+
159
+ /** Current consent state (clone, safe to mutate). */
160
+ getConsent(): ConsentState;
161
+
162
+ /** Has the user granted consent for `category`? */
163
+ hasConsent(category: ConsentCategory): boolean;
164
+
165
+ /** Has the user made any consent decision yet? */
166
+ hasConsentDecision(): boolean;
167
+
168
+ /** Tamper-evident snapshot of the last consent decision. */
169
+ getConsentProof(): ConsentProof | null;
170
+
171
+ /** Grant consent for every category and run accept callbacks. */
172
+ acceptAll(): void;
173
+
174
+ /** Revoke consent for every non-essential category and run reject callbacks. */
175
+ rejectAll(): void;
176
+
177
+ /** Wipe all consent state and reshow the banner. */
178
+ reset(): void;
179
+
180
+ /** True if the browser is sending DNT or GPC. */
181
+ isDoNotTrackEnabled(): boolean;
182
+
183
+ /** Why `isDoNotTrackEnabled()` returned what it did. */
184
+ getDNTDetails(): DNTDetails;
185
+
186
+ /** Subscribe to a consent event. Returns an unsubscribe function. */
187
+ on(
188
+ eventName: ZestEventName,
189
+ handler: (event: CustomEvent<ZestEventDetail>) => void
190
+ ): () => void;
191
+
192
+ /** Subscribe once; auto-unsubscribes after the first call. */
193
+ once(
194
+ eventName: ZestEventName,
195
+ handler: (event: CustomEvent<ZestEventDetail>) => void
196
+ ): () => void;
197
+
198
+ /** Constants for `on()` / `once()`. */
199
+ EVENTS: ZestEvents;
200
+
201
+ /** Active configuration after `init()`. */
202
+ getConfig(): InitOptions | null;
203
+ };
204
+
205
+ export default Zest;
206
+
207
+ declare global {
208
+ interface Window {
209
+ /** Set before loading `zest.min.js` to configure auto-initialisation. */
210
+ ZestConfig?: InitOptions;
211
+ /** The Zest singleton, attached after auto-init. */
212
+ Zest?: typeof Zest;
213
+ }
214
+ }