@freshjuice/zest 0.1.0 → 1.0.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 (76) hide show
  1. package/README.md +46 -0
  2. package/dist/zest.de.js +104 -1
  3. package/dist/zest.de.js.map +1 -1
  4. package/dist/zest.de.min.js +1 -1
  5. package/dist/zest.en.js +104 -1
  6. package/dist/zest.en.js.map +1 -1
  7. package/dist/zest.en.min.js +1 -1
  8. package/dist/zest.es.js +104 -1
  9. package/dist/zest.es.js.map +1 -1
  10. package/dist/zest.es.min.js +1 -1
  11. package/dist/zest.esm.js +104 -1
  12. package/dist/zest.esm.js.map +1 -1
  13. package/dist/zest.esm.min.js +1 -1
  14. package/dist/zest.fr.js +104 -1
  15. package/dist/zest.fr.js.map +1 -1
  16. package/dist/zest.fr.min.js +1 -1
  17. package/dist/zest.it.js +104 -1
  18. package/dist/zest.it.js.map +1 -1
  19. package/dist/zest.it.min.js +1 -1
  20. package/dist/zest.ja.js +104 -1
  21. package/dist/zest.ja.js.map +1 -1
  22. package/dist/zest.ja.min.js +1 -1
  23. package/dist/zest.js +104 -1
  24. package/dist/zest.js.map +1 -1
  25. package/dist/zest.min.js +1 -1
  26. package/dist/zest.nl.js +104 -1
  27. package/dist/zest.nl.js.map +1 -1
  28. package/dist/zest.nl.min.js +1 -1
  29. package/dist/zest.pl.js +104 -1
  30. package/dist/zest.pl.js.map +1 -1
  31. package/dist/zest.pl.min.js +1 -1
  32. package/dist/zest.pt.js +104 -1
  33. package/dist/zest.pt.js.map +1 -1
  34. package/dist/zest.pt.min.js +1 -1
  35. package/dist/zest.ru.js +104 -1
  36. package/dist/zest.ru.js.map +1 -1
  37. package/dist/zest.ru.min.js +1 -1
  38. package/dist/zest.uk.js +104 -1
  39. package/dist/zest.uk.js.map +1 -1
  40. package/dist/zest.uk.min.js +1 -1
  41. package/dist/zest.zh.js +104 -1
  42. package/dist/zest.zh.js.map +1 -1
  43. package/dist/zest.zh.min.js +1 -1
  44. package/package.json +5 -4
  45. package/src/api/public-api.js +97 -0
  46. package/src/config/defaults.js +150 -0
  47. package/src/config/parser.js +104 -0
  48. package/src/core/categories.js +52 -0
  49. package/src/core/cookie-interceptor.js +116 -0
  50. package/src/core/dnt.js +56 -0
  51. package/src/core/known-trackers.js +168 -0
  52. package/src/core/pattern-matcher.js +96 -0
  53. package/src/core/script-blocker.js +308 -0
  54. package/src/core/storage-interceptor.js +169 -0
  55. package/src/i18n/lang-en.js +54 -0
  56. package/src/i18n/single/lang-de.js +55 -0
  57. package/src/i18n/single/lang-en.js +55 -0
  58. package/src/i18n/single/lang-es.js +55 -0
  59. package/src/i18n/single/lang-fr.js +55 -0
  60. package/src/i18n/single/lang-it.js +55 -0
  61. package/src/i18n/single/lang-ja.js +55 -0
  62. package/src/i18n/single/lang-nl.js +55 -0
  63. package/src/i18n/single/lang-pl.js +55 -0
  64. package/src/i18n/single/lang-pt.js +55 -0
  65. package/src/i18n/single/lang-ru.js +55 -0
  66. package/src/i18n/single/lang-uk.js +55 -0
  67. package/src/i18n/single/lang-zh.js +55 -0
  68. package/src/i18n/translations.js +546 -0
  69. package/src/index.js +377 -0
  70. package/src/integrations/consent-signals.js +71 -0
  71. package/src/storage/consent-store.js +177 -0
  72. package/src/storage/events.js +84 -0
  73. package/src/ui/banner.js +130 -0
  74. package/src/ui/modal.js +211 -0
  75. package/src/ui/styles.js +498 -0
  76. package/src/ui/widget.js +103 -0
@@ -0,0 +1,498 @@
1
+ /**
2
+ * Styles - Shadow DOM encapsulated CSS with theming
3
+ */
4
+
5
+ /**
6
+ * Generate CSS with custom properties
7
+ */
8
+ export function generateStyles(config) {
9
+ const accentColor = config.accentColor || '#4F46E5';
10
+
11
+ return `
12
+ :host {
13
+ --zest-accent: ${accentColor};
14
+ --zest-accent-hover: ${adjustColor(accentColor, -15)};
15
+ --zest-bg: #ffffff;
16
+ --zest-bg-secondary: #f3f4f6;
17
+ --zest-text: #1f2937;
18
+ --zest-text-secondary: #6b7280;
19
+ --zest-border: #e5e7eb;
20
+ --zest-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
21
+ --zest-radius: 12px;
22
+ --zest-radius-sm: 8px;
23
+ --zest-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
24
+
25
+ font-family: var(--zest-font);
26
+ font-size: 14px;
27
+ line-height: 1.5;
28
+ color: var(--zest-text);
29
+ box-sizing: border-box;
30
+ }
31
+
32
+ :host([data-theme="dark"]) {
33
+ --zest-bg: #1f2937;
34
+ --zest-bg-secondary: #374151;
35
+ --zest-text: #f9fafb;
36
+ --zest-text-secondary: #9ca3af;
37
+ --zest-border: #4b5563;
38
+ --zest-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.4), 0 8px 10px -6px rgba(0, 0, 0, 0.3);
39
+ }
40
+
41
+ @media (prefers-color-scheme: dark) {
42
+ :host([data-theme="auto"]) {
43
+ --zest-bg: #1f2937;
44
+ --zest-bg-secondary: #374151;
45
+ --zest-text: #f9fafb;
46
+ --zest-text-secondary: #9ca3af;
47
+ --zest-border: #4b5563;
48
+ --zest-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.4), 0 8px 10px -6px rgba(0, 0, 0, 0.3);
49
+ }
50
+ }
51
+
52
+ *, *::before, *::after {
53
+ box-sizing: border-box;
54
+ }
55
+
56
+ /* Banner */
57
+ .zest-banner {
58
+ position: fixed;
59
+ z-index: 999999;
60
+ max-width: 480px;
61
+ padding: 20px;
62
+ background: var(--zest-bg);
63
+ border-radius: var(--zest-radius);
64
+ box-shadow: var(--zest-shadow);
65
+ animation: zest-slide-in 0.3s ease-out;
66
+ }
67
+
68
+ .zest-banner--bottom {
69
+ bottom: 20px;
70
+ left: 50%;
71
+ transform: translateX(-50%);
72
+ }
73
+
74
+ .zest-banner--bottom-left {
75
+ bottom: 20px;
76
+ left: 20px;
77
+ }
78
+
79
+ .zest-banner--bottom-right {
80
+ bottom: 20px;
81
+ right: 20px;
82
+ }
83
+
84
+ .zest-banner--top {
85
+ top: 20px;
86
+ left: 50%;
87
+ transform: translateX(-50%);
88
+ }
89
+
90
+ @keyframes zest-slide-in {
91
+ from {
92
+ opacity: 0;
93
+ transform: translateX(-50%) translateY(20px);
94
+ }
95
+ to {
96
+ opacity: 1;
97
+ transform: translateX(-50%) translateY(0);
98
+ }
99
+ }
100
+
101
+ .zest-banner--bottom-left {
102
+ animation-name: zest-slide-in-left;
103
+ }
104
+
105
+ @keyframes zest-slide-in-left {
106
+ from {
107
+ opacity: 0;
108
+ transform: translateY(20px);
109
+ }
110
+ to {
111
+ opacity: 1;
112
+ transform: translateY(0);
113
+ }
114
+ }
115
+
116
+ .zest-banner--bottom-right {
117
+ animation-name: zest-slide-in-right;
118
+ }
119
+
120
+ @keyframes zest-slide-in-right {
121
+ from {
122
+ opacity: 0;
123
+ transform: translateY(20px);
124
+ }
125
+ to {
126
+ opacity: 1;
127
+ transform: translateY(0);
128
+ }
129
+ }
130
+
131
+ @media (prefers-reduced-motion: reduce) {
132
+ .zest-banner,
133
+ .zest-modal {
134
+ animation: none;
135
+ }
136
+ }
137
+
138
+ .zest-banner__title {
139
+ margin: 0 0 8px 0;
140
+ font-size: 16px;
141
+ font-weight: 600;
142
+ color: var(--zest-text);
143
+ }
144
+
145
+ .zest-banner__description {
146
+ margin: 0 0 16px 0;
147
+ font-size: 14px;
148
+ color: var(--zest-text-secondary);
149
+ }
150
+
151
+ .zest-banner__buttons {
152
+ display: flex;
153
+ flex-wrap: wrap;
154
+ gap: 8px;
155
+ }
156
+
157
+ /* Buttons */
158
+ .zest-btn {
159
+ display: inline-flex;
160
+ align-items: center;
161
+ justify-content: center;
162
+ padding: 10px 16px;
163
+ font-size: 14px;
164
+ font-weight: 500;
165
+ font-family: inherit;
166
+ border: none;
167
+ border-radius: var(--zest-radius-sm);
168
+ cursor: pointer;
169
+ transition: background-color 0.15s ease, transform 0.1s ease;
170
+ }
171
+
172
+ .zest-btn:hover {
173
+ transform: translateY(-1px);
174
+ }
175
+
176
+ .zest-btn:active {
177
+ transform: translateY(0);
178
+ }
179
+
180
+ .zest-btn:focus-visible {
181
+ outline: 2px solid var(--zest-accent);
182
+ outline-offset: 2px;
183
+ }
184
+
185
+ .zest-btn--primary {
186
+ background: var(--zest-accent);
187
+ color: #ffffff;
188
+ }
189
+
190
+ .zest-btn--primary:hover {
191
+ background: var(--zest-accent-hover);
192
+ }
193
+
194
+ .zest-btn--secondary {
195
+ background: var(--zest-bg-secondary);
196
+ color: var(--zest-text);
197
+ }
198
+
199
+ .zest-btn--secondary:hover {
200
+ background: var(--zest-border);
201
+ }
202
+
203
+ .zest-btn--ghost {
204
+ background: transparent;
205
+ color: var(--zest-text-secondary);
206
+ }
207
+
208
+ .zest-btn--ghost:hover {
209
+ background: var(--zest-bg-secondary);
210
+ color: var(--zest-text);
211
+ }
212
+
213
+ /* Modal */
214
+ .zest-modal-overlay {
215
+ position: fixed;
216
+ inset: 0;
217
+ z-index: 999998;
218
+ display: flex;
219
+ align-items: center;
220
+ justify-content: center;
221
+ padding: 20px;
222
+ background: rgba(0, 0, 0, 0.5);
223
+ animation: zest-fade-in 0.2s ease-out;
224
+ }
225
+
226
+ @keyframes zest-fade-in {
227
+ from { opacity: 0; }
228
+ to { opacity: 1; }
229
+ }
230
+
231
+ .zest-modal {
232
+ width: 100%;
233
+ max-width: 500px;
234
+ max-height: 90vh;
235
+ overflow-y: auto;
236
+ background: var(--zest-bg);
237
+ border-radius: var(--zest-radius);
238
+ box-shadow: var(--zest-shadow);
239
+ animation: zest-modal-in 0.3s ease-out;
240
+ }
241
+
242
+ @keyframes zest-modal-in {
243
+ from {
244
+ opacity: 0;
245
+ transform: scale(0.95);
246
+ }
247
+ to {
248
+ opacity: 1;
249
+ transform: scale(1);
250
+ }
251
+ }
252
+
253
+ .zest-modal__header {
254
+ padding: 20px 20px 0;
255
+ }
256
+
257
+ .zest-modal__title {
258
+ margin: 0 0 8px 0;
259
+ font-size: 18px;
260
+ font-weight: 600;
261
+ color: var(--zest-text);
262
+ }
263
+
264
+ .zest-modal__description {
265
+ margin: 0;
266
+ font-size: 14px;
267
+ color: var(--zest-text-secondary);
268
+ }
269
+
270
+ .zest-modal__body {
271
+ padding: 20px;
272
+ }
273
+
274
+ .zest-modal__footer {
275
+ display: flex;
276
+ flex-wrap: wrap;
277
+ gap: 8px;
278
+ padding: 0 20px 20px;
279
+ }
280
+
281
+ /* Categories */
282
+ .zest-category {
283
+ padding: 16px;
284
+ margin-bottom: 12px;
285
+ background: var(--zest-bg-secondary);
286
+ border-radius: var(--zest-radius-sm);
287
+ }
288
+
289
+ .zest-category:last-child {
290
+ margin-bottom: 0;
291
+ }
292
+
293
+ .zest-category__header {
294
+ display: flex;
295
+ align-items: center;
296
+ justify-content: space-between;
297
+ gap: 12px;
298
+ }
299
+
300
+ .zest-category__info {
301
+ flex: 1;
302
+ }
303
+
304
+ .zest-category__label {
305
+ display: block;
306
+ font-size: 14px;
307
+ font-weight: 600;
308
+ color: var(--zest-text);
309
+ }
310
+
311
+ .zest-category__description {
312
+ margin: 4px 0 0;
313
+ font-size: 13px;
314
+ color: var(--zest-text-secondary);
315
+ }
316
+
317
+ /* Toggle Switch */
318
+ .zest-toggle {
319
+ position: relative;
320
+ width: 44px;
321
+ height: 24px;
322
+ flex-shrink: 0;
323
+ }
324
+
325
+ .zest-toggle__input {
326
+ position: absolute;
327
+ opacity: 0;
328
+ width: 100%;
329
+ height: 100%;
330
+ cursor: pointer;
331
+ margin: 0;
332
+ }
333
+
334
+ .zest-toggle__input:disabled {
335
+ cursor: not-allowed;
336
+ }
337
+
338
+ .zest-toggle__slider {
339
+ position: absolute;
340
+ inset: 0;
341
+ background: var(--zest-border);
342
+ border-radius: 12px;
343
+ transition: background-color 0.2s ease;
344
+ pointer-events: none;
345
+ }
346
+
347
+ .zest-toggle__slider::before {
348
+ content: '';
349
+ position: absolute;
350
+ top: 2px;
351
+ left: 2px;
352
+ width: 20px;
353
+ height: 20px;
354
+ background: #ffffff;
355
+ border-radius: 50%;
356
+ transition: transform 0.2s ease;
357
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
358
+ }
359
+
360
+ .zest-toggle__input:checked + .zest-toggle__slider {
361
+ background: var(--zest-accent);
362
+ }
363
+
364
+ .zest-toggle__input:checked + .zest-toggle__slider::before {
365
+ transform: translateX(20px);
366
+ }
367
+
368
+ .zest-toggle__input:focus-visible + .zest-toggle__slider {
369
+ outline: 2px solid var(--zest-accent);
370
+ outline-offset: 2px;
371
+ }
372
+
373
+ .zest-toggle__input:disabled + .zest-toggle__slider {
374
+ opacity: 0.6;
375
+ }
376
+
377
+ /* Widget */
378
+ .zest-widget {
379
+ position: fixed;
380
+ z-index: 999997;
381
+ bottom: 20px;
382
+ left: 20px;
383
+ }
384
+
385
+ .zest-widget__btn {
386
+ display: flex;
387
+ align-items: center;
388
+ justify-content: center;
389
+ width: 48px;
390
+ height: 48px;
391
+ padding: 0;
392
+ background: var(--zest-bg);
393
+ border: 1px solid var(--zest-border);
394
+ border-radius: 50%;
395
+ box-shadow: var(--zest-shadow);
396
+ cursor: pointer;
397
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
398
+ }
399
+
400
+ .zest-widget__btn:hover {
401
+ transform: scale(1.05);
402
+ box-shadow: 0 12px 28px -5px rgba(0, 0, 0, 0.15);
403
+ }
404
+
405
+ .zest-widget__btn:focus-visible {
406
+ outline: 2px solid var(--zest-accent);
407
+ outline-offset: 2px;
408
+ }
409
+
410
+ .zest-widget__icon {
411
+ width: 24px;
412
+ height: 24px;
413
+ fill: var(--zest-text);
414
+ }
415
+
416
+ /* Link */
417
+ .zest-link {
418
+ color: var(--zest-accent);
419
+ text-decoration: none;
420
+ }
421
+
422
+ .zest-link:hover {
423
+ text-decoration: underline;
424
+ }
425
+
426
+ /* Mobile */
427
+ @media (max-width: 480px) {
428
+ .zest-banner {
429
+ left: 10px;
430
+ right: 10px;
431
+ max-width: none;
432
+ transform: none;
433
+ }
434
+
435
+ .zest-banner--bottom,
436
+ .zest-banner--bottom-left,
437
+ .zest-banner--bottom-right {
438
+ bottom: 10px;
439
+ }
440
+
441
+ .zest-banner--top {
442
+ top: 10px;
443
+ transform: none;
444
+ }
445
+
446
+ @keyframes zest-slide-in {
447
+ from {
448
+ opacity: 0;
449
+ transform: translateY(20px);
450
+ }
451
+ to {
452
+ opacity: 1;
453
+ transform: translateY(0);
454
+ }
455
+ }
456
+
457
+ .zest-banner__buttons {
458
+ flex-direction: column;
459
+ }
460
+
461
+ .zest-btn {
462
+ width: 100%;
463
+ }
464
+
465
+ .zest-modal-overlay {
466
+ padding: 10px;
467
+ }
468
+
469
+ .zest-widget {
470
+ bottom: 10px;
471
+ left: 10px;
472
+ }
473
+ }
474
+
475
+ /* Hidden utility */
476
+ .zest-hidden {
477
+ display: none !important;
478
+ }
479
+ ${config.customStyles || ''}
480
+ `;
481
+ }
482
+
483
+ /**
484
+ * Adjust color brightness
485
+ */
486
+ function adjustColor(hex, percent) {
487
+ const num = parseInt(hex.replace('#', ''), 16);
488
+ const amt = Math.round(2.55 * percent);
489
+ const R = Math.min(255, Math.max(0, (num >> 16) + amt));
490
+ const G = Math.min(255, Math.max(0, ((num >> 8) & 0x00ff) + amt));
491
+ const B = Math.min(255, Math.max(0, (num & 0x0000ff) + amt));
492
+ return '#' + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1);
493
+ }
494
+
495
+ /**
496
+ * Cookie icon SVG
497
+ */
498
+ export const COOKIE_ICON = `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10c0-.728-.078-1.437-.225-2.12a1 1 0 0 0-1.482-.63 3 3 0 0 1-4.086-3.72 1 1 0 0 0-.793-1.263A10.05 10.05 0 0 0 12 2zm0 2c.178 0 .354.006.528.017a5 5 0 0 0 5.955 5.955c.011.174.017.35.017.528 0 4.418-3.582 8-8 8s-8-3.582-8-8 3.582-8 8-8zm-4 6a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm5 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2zm-2 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z"/></svg>`;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Widget - Minimal floating button to reopen settings
3
+ */
4
+
5
+ import { generateStyles, COOKIE_ICON } from './styles.js';
6
+ import { getCurrentConfig } from '../config/parser.js';
7
+
8
+ let widgetElement = null;
9
+ let shadowRoot = null;
10
+
11
+ /**
12
+ * Create the widget HTML
13
+ */
14
+ function createWidgetHTML(config) {
15
+ const labels = config.labels.widget;
16
+
17
+ return `
18
+ <div class="zest-widget">
19
+ <button type="button" class="zest-widget__btn" aria-label="${labels.label}" title="${labels.label}">
20
+ <span class="zest-widget__icon">${COOKIE_ICON}</span>
21
+ </button>
22
+ </div>
23
+ `;
24
+ }
25
+
26
+ /**
27
+ * Create and mount the widget
28
+ */
29
+ export function createWidget(callbacks = {}) {
30
+ if (widgetElement) {
31
+ return widgetElement;
32
+ }
33
+
34
+ const config = getCurrentConfig();
35
+
36
+ // Create host element
37
+ widgetElement = document.createElement('zest-widget');
38
+ widgetElement.setAttribute('data-theme', config.theme || 'light');
39
+
40
+ // Create shadow root
41
+ shadowRoot = widgetElement.attachShadow({ mode: 'open' });
42
+
43
+ // Add styles
44
+ const styleEl = document.createElement('style');
45
+ styleEl.textContent = generateStyles(config);
46
+ shadowRoot.appendChild(styleEl);
47
+
48
+ // Add widget HTML
49
+ const container = document.createElement('div');
50
+ container.innerHTML = createWidgetHTML(config);
51
+ shadowRoot.appendChild(container.firstElementChild);
52
+
53
+ // Add event listener
54
+ const button = shadowRoot.querySelector('.zest-widget__btn');
55
+ button.addEventListener('click', () => {
56
+ callbacks.onClick?.();
57
+ });
58
+
59
+ // Mount to document
60
+ document.body.appendChild(widgetElement);
61
+
62
+ return widgetElement;
63
+ }
64
+
65
+ /**
66
+ * Show the widget
67
+ */
68
+ export function showWidget(callbacks = {}) {
69
+ if (!widgetElement) {
70
+ createWidget(callbacks);
71
+ } else {
72
+ widgetElement.style.display = '';
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Hide the widget
78
+ */
79
+ export function hideWidget() {
80
+ if (widgetElement) {
81
+ widgetElement.style.display = 'none';
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Remove the widget completely
87
+ */
88
+ export function removeWidget() {
89
+ if (widgetElement) {
90
+ widgetElement.remove();
91
+ widgetElement = null;
92
+ shadowRoot = null;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Check if widget is visible
98
+ */
99
+ export function isWidgetVisible() {
100
+ return widgetElement !== null &&
101
+ document.body.contains(widgetElement) &&
102
+ widgetElement.style.display !== 'none';
103
+ }