@zerocost/sdk 0.17.0 → 0.19.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/core/client.js +63 -0
- package/dist/core/config.js +5 -0
- package/dist/core/consent-ui.js +490 -0
- package/dist/core/consent.js +222 -0
- package/dist/core/constants.js +6 -0
- package/dist/core/widget-render.js +155 -0
- package/dist/index.cjs +16 -2
- package/dist/index.js +16 -2
- package/dist/modules/ads.js +16 -0
- package/dist/modules/llm-data.js +674 -0
- package/dist/modules/recording.js +187 -0
- package/dist/modules/trackers.js +27 -0
- package/dist/modules/widget.js +301 -0
- package/dist/types/index.js +2 -0
- package/package.json +13 -5
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { getBaseUrl } from './config';
|
|
2
|
+
export class ZerocostClient {
|
|
3
|
+
config;
|
|
4
|
+
baseUrl;
|
|
5
|
+
isInitialized = false;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
if (!config.appId) {
|
|
8
|
+
throw new Error('ZerocostSDK: appId is required');
|
|
9
|
+
}
|
|
10
|
+
if (!config.apiKey) {
|
|
11
|
+
throw new Error('ZerocostSDK: apiKey is required');
|
|
12
|
+
}
|
|
13
|
+
this.config = {
|
|
14
|
+
environment: 'production',
|
|
15
|
+
debug: false,
|
|
16
|
+
...config,
|
|
17
|
+
};
|
|
18
|
+
this.baseUrl = getBaseUrl(config.baseUrl);
|
|
19
|
+
}
|
|
20
|
+
init() {
|
|
21
|
+
if (this.isInitialized) {
|
|
22
|
+
this.log('ZerocostSDK is already initialized.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
this.isInitialized = true;
|
|
26
|
+
this.log(`ZerocostSDK initialized for ${this.config.appId} in ${this.config.environment} mode.`);
|
|
27
|
+
}
|
|
28
|
+
getConfig() {
|
|
29
|
+
return this.config;
|
|
30
|
+
}
|
|
31
|
+
async request(path, body) {
|
|
32
|
+
const url = `${this.baseUrl}${path}`;
|
|
33
|
+
const payload = {
|
|
34
|
+
...(body || {}),
|
|
35
|
+
app_id: this.config.appId,
|
|
36
|
+
};
|
|
37
|
+
this.log(`→ ${path}`, payload);
|
|
38
|
+
const res = await fetch(url, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
'x-api-key': this.config.apiKey,
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify(payload),
|
|
45
|
+
});
|
|
46
|
+
const data = await res.json();
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
this.log(`✗ ${res.status}`, data);
|
|
49
|
+
throw new Error(data.error || `Request failed with status ${res.status}`);
|
|
50
|
+
}
|
|
51
|
+
this.log(`✓ ${res.status}`, data);
|
|
52
|
+
return data;
|
|
53
|
+
}
|
|
54
|
+
log(message, data) {
|
|
55
|
+
if (this.config.debug) {
|
|
56
|
+
// Sanitize message to hide internal URLs/infra
|
|
57
|
+
const sanitizedMessage = typeof message === 'string'
|
|
58
|
+
? message.replace(/https:\/\/[a-z0-9.-]+\.supabase\.co/gi, '[INFRA]')
|
|
59
|
+
: message;
|
|
60
|
+
console.log(`[Zerocost] ${sanitizedMessage}`, data ?? '');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* consent-ui.ts — Pure DOM consent popup for the Zerocost SDK.
|
|
3
|
+
*
|
|
4
|
+
* - Desktop: centered card modal (max 480px)
|
|
5
|
+
* - Mobile (≤640px): bottom-sheet modal
|
|
6
|
+
* - Themes: light / dark / auto
|
|
7
|
+
* - Non-dismissable (no Escape, no backdrop click)
|
|
8
|
+
* - Returns a Promise that resolves with the user's toggle selections
|
|
9
|
+
*/
|
|
10
|
+
import { ZEROCOST_BASE_URL } from './config';
|
|
11
|
+
// ── CSS ──────────────────────────────────────────────────────────────
|
|
12
|
+
const STYLE_ID = 'zerocost-consent-styles';
|
|
13
|
+
export function injectStyles(theme) {
|
|
14
|
+
if (document.getElementById(STYLE_ID))
|
|
15
|
+
return;
|
|
16
|
+
const darkVars = `
|
|
17
|
+
--zc-bg: #111111;
|
|
18
|
+
--zc-surface: #1a1a1a;
|
|
19
|
+
--zc-border: #2a2a2a;
|
|
20
|
+
--zc-text: #ffffff;
|
|
21
|
+
--zc-text-secondary: #999999;
|
|
22
|
+
--zc-accent: #ffffff;
|
|
23
|
+
--zc-accent-bg: #ffffff;
|
|
24
|
+
--zc-accent-fg: #000000;
|
|
25
|
+
--zc-toggle-off-bg: #333333;
|
|
26
|
+
--zc-toggle-on-bg: #00e599;
|
|
27
|
+
--zc-toggle-knob: #ffffff;
|
|
28
|
+
--zc-backdrop: rgba(0,0,0,0.65);
|
|
29
|
+
--zc-link: #888888;
|
|
30
|
+
--zc-link-hover: #cccccc;
|
|
31
|
+
`;
|
|
32
|
+
const lightVars = `
|
|
33
|
+
--zc-bg: #ffffff;
|
|
34
|
+
--zc-surface: #f5f5f5;
|
|
35
|
+
--zc-border: #e0e0e0;
|
|
36
|
+
--zc-text: #111111;
|
|
37
|
+
--zc-text-secondary: #666666;
|
|
38
|
+
--zc-accent: #111111;
|
|
39
|
+
--zc-accent-bg: #111111;
|
|
40
|
+
--zc-accent-fg: #ffffff;
|
|
41
|
+
--zc-toggle-off-bg: #cccccc;
|
|
42
|
+
--zc-toggle-on-bg: #00c77d;
|
|
43
|
+
--zc-toggle-knob: #ffffff;
|
|
44
|
+
--zc-backdrop: rgba(0,0,0,0.45);
|
|
45
|
+
--zc-link: #666666;
|
|
46
|
+
--zc-link-hover: #111111;
|
|
47
|
+
`;
|
|
48
|
+
let themeRule;
|
|
49
|
+
if (theme === 'dark') {
|
|
50
|
+
themeRule = `.zc-consent-root, .zc-settings-btn { ${darkVars} }`;
|
|
51
|
+
}
|
|
52
|
+
else if (theme === 'light') {
|
|
53
|
+
themeRule = `.zc-consent-root, .zc-settings-btn { ${lightVars} }`;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
themeRule = `
|
|
57
|
+
.zc-consent-root, .zc-settings-btn { ${lightVars} }
|
|
58
|
+
@media (prefers-color-scheme: dark) {
|
|
59
|
+
.zc-consent-root, .zc-settings-btn { ${darkVars} }
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
|
+
const css = `
|
|
64
|
+
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap');
|
|
65
|
+
${themeRule}
|
|
66
|
+
|
|
67
|
+
.zc-consent-root * {
|
|
68
|
+
box-sizing: border-box;
|
|
69
|
+
margin: 0;
|
|
70
|
+
padding: 0;
|
|
71
|
+
font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, Roboto, sans-serif;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.zc-consent-backdrop {
|
|
75
|
+
position: fixed;
|
|
76
|
+
inset: 0;
|
|
77
|
+
z-index: 999999;
|
|
78
|
+
background: var(--zc-backdrop);
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
justify-content: center;
|
|
82
|
+
animation: zc-fade-in 200ms ease;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@keyframes zc-fade-in {
|
|
86
|
+
from { opacity: 0; }
|
|
87
|
+
to { opacity: 1; }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@keyframes zc-slide-up {
|
|
91
|
+
from { transform: translateY(100%); }
|
|
92
|
+
to { transform: translateY(0); }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.zc-consent-card {
|
|
96
|
+
background: var(--zc-bg);
|
|
97
|
+
border: 1px solid var(--zc-border);
|
|
98
|
+
border-radius: 16px;
|
|
99
|
+
width: 100%;
|
|
100
|
+
max-width: 440px;
|
|
101
|
+
max-height: 90vh;
|
|
102
|
+
overflow-y: auto;
|
|
103
|
+
padding: 24px 20px 20px;
|
|
104
|
+
animation: zc-fade-in 200ms ease;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Mobile: bottom-sheet style */
|
|
108
|
+
@media (max-width: 640px) {
|
|
109
|
+
.zc-consent-backdrop {
|
|
110
|
+
align-items: flex-end;
|
|
111
|
+
}
|
|
112
|
+
.zc-consent-card {
|
|
113
|
+
border-radius: 20px 20px 0 0;
|
|
114
|
+
max-width: 100%;
|
|
115
|
+
animation: zc-slide-up 200ms ease;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Scrollbar */
|
|
120
|
+
.zc-consent-card::-webkit-scrollbar { width: 4px; }
|
|
121
|
+
.zc-consent-card::-webkit-scrollbar-thumb { background: var(--zc-border); border-radius: 4px; }
|
|
122
|
+
|
|
123
|
+
.zc-consent-header {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
gap: 10px;
|
|
127
|
+
margin-bottom: 4px;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.zc-consent-logo {
|
|
131
|
+
width: 28px;
|
|
132
|
+
height: 28px;
|
|
133
|
+
border-radius: 6px;
|
|
134
|
+
background: var(--zc-accent-bg);
|
|
135
|
+
display: flex;
|
|
136
|
+
align-items: center;
|
|
137
|
+
justify-content: center;
|
|
138
|
+
flex-shrink: 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.zc-consent-logo svg {
|
|
142
|
+
width: 16px;
|
|
143
|
+
height: 16px;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.zc-consent-title {
|
|
147
|
+
font-size: 16px;
|
|
148
|
+
font-weight: 700;
|
|
149
|
+
color: var(--zc-text);
|
|
150
|
+
letter-spacing: -0.02em;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.zc-consent-subtitle {
|
|
154
|
+
font-size: 13px;
|
|
155
|
+
color: var(--zc-text-secondary);
|
|
156
|
+
line-height: 1.5;
|
|
157
|
+
margin-bottom: 16px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.zc-consent-toggles {
|
|
161
|
+
display: flex;
|
|
162
|
+
flex-direction: column;
|
|
163
|
+
gap: 10px;
|
|
164
|
+
margin-bottom: 16px;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.zc-consent-toggle-card {
|
|
168
|
+
background: var(--zc-surface);
|
|
169
|
+
border: 1px solid var(--zc-border);
|
|
170
|
+
border-radius: 12px;
|
|
171
|
+
padding: 12px 14px;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.zc-consent-toggle-row {
|
|
175
|
+
display: flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
justify-content: space-between;
|
|
178
|
+
margin-bottom: 6px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.zc-consent-toggle-label {
|
|
182
|
+
font-size: 14px;
|
|
183
|
+
font-weight: 600;
|
|
184
|
+
color: var(--zc-text);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.zc-consent-toggle-desc {
|
|
188
|
+
font-size: 12px;
|
|
189
|
+
color: var(--zc-text-secondary);
|
|
190
|
+
line-height: 1.5;
|
|
191
|
+
margin-bottom: 4px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.zc-consent-learn-more {
|
|
195
|
+
font-size: 11px;
|
|
196
|
+
color: var(--zc-link);
|
|
197
|
+
text-decoration: none;
|
|
198
|
+
cursor: pointer;
|
|
199
|
+
transition: color 150ms;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.zc-consent-learn-more:hover {
|
|
203
|
+
color: var(--zc-link-hover);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* Toggle switch */
|
|
207
|
+
.zc-toggle {
|
|
208
|
+
position: relative;
|
|
209
|
+
width: 40px;
|
|
210
|
+
height: 22px;
|
|
211
|
+
flex-shrink: 0;
|
|
212
|
+
cursor: pointer;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.zc-toggle input {
|
|
216
|
+
opacity: 0;
|
|
217
|
+
width: 0;
|
|
218
|
+
height: 0;
|
|
219
|
+
position: absolute;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.zc-toggle-track {
|
|
223
|
+
position: absolute;
|
|
224
|
+
inset: 0;
|
|
225
|
+
background: var(--zc-toggle-off-bg);
|
|
226
|
+
border-radius: 11px;
|
|
227
|
+
transition: background 200ms ease;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.zc-toggle input:checked + .zc-toggle-track {
|
|
231
|
+
background: var(--zc-toggle-on-bg);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.zc-toggle-knob {
|
|
235
|
+
position: absolute;
|
|
236
|
+
top: 2px;
|
|
237
|
+
left: 2px;
|
|
238
|
+
width: 18px;
|
|
239
|
+
height: 18px;
|
|
240
|
+
background: var(--zc-toggle-knob);
|
|
241
|
+
border-radius: 50%;
|
|
242
|
+
transition: transform 200ms ease;
|
|
243
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.zc-toggle input:checked ~ .zc-toggle-knob {
|
|
247
|
+
transform: translateX(18px);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* Footer */
|
|
251
|
+
.zc-consent-footer {
|
|
252
|
+
display: flex;
|
|
253
|
+
flex-wrap: wrap;
|
|
254
|
+
gap: 4px 12px;
|
|
255
|
+
justify-content: center;
|
|
256
|
+
margin-bottom: 14px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.zc-consent-footer a {
|
|
260
|
+
font-size: 11px;
|
|
261
|
+
color: var(--zc-link);
|
|
262
|
+
text-decoration: none;
|
|
263
|
+
transition: color 150ms;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.zc-consent-footer a:hover {
|
|
267
|
+
color: var(--zc-link-hover);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.zc-consent-footer-sep {
|
|
271
|
+
font-size: 11px;
|
|
272
|
+
color: var(--zc-link);
|
|
273
|
+
opacity: 0.5;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Confirm button */
|
|
277
|
+
.zc-consent-confirm {
|
|
278
|
+
display: block;
|
|
279
|
+
width: 100%;
|
|
280
|
+
padding: 12px;
|
|
281
|
+
font-size: 14px;
|
|
282
|
+
font-weight: 600;
|
|
283
|
+
border: none;
|
|
284
|
+
border-radius: 10px;
|
|
285
|
+
cursor: pointer;
|
|
286
|
+
background: var(--zc-accent-bg);
|
|
287
|
+
color: var(--zc-accent-fg);
|
|
288
|
+
letter-spacing: -0.01em;
|
|
289
|
+
transition: opacity 150ms;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.zc-consent-confirm:hover {
|
|
293
|
+
opacity: 0.88;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.zc-consent-confirm:active {
|
|
297
|
+
opacity: 0.75;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* Floating settings button */
|
|
301
|
+
.zc-settings-btn {
|
|
302
|
+
position: fixed;
|
|
303
|
+
width: 44px;
|
|
304
|
+
height: 44px;
|
|
305
|
+
border-radius: 50%;
|
|
306
|
+
background: var(--zc-bg);
|
|
307
|
+
border: 1px solid var(--zc-border);
|
|
308
|
+
color: var(--zc-text);
|
|
309
|
+
display: flex;
|
|
310
|
+
align-items: center;
|
|
311
|
+
justify-content: center;
|
|
312
|
+
cursor: pointer;
|
|
313
|
+
z-index: 999998;
|
|
314
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
315
|
+
transition: transform 200ms, border-color 200ms;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.zc-settings-btn:hover {
|
|
319
|
+
transform: scale(1.05);
|
|
320
|
+
border-color: var(--zc-text-secondary);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.zc-settings-btn svg {
|
|
324
|
+
width: 20px;
|
|
325
|
+
height: 20px;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.zc-settings-bottom-left { bottom: 20px; left: 20px; }
|
|
329
|
+
.zc-settings-bottom-right { bottom: 20px; right: 20px; }
|
|
330
|
+
.zc-settings-top-left { top: 20px; left: 20px; }
|
|
331
|
+
.zc-settings-top-right { top: 20px; right: 20px; }
|
|
332
|
+
|
|
333
|
+
@media (max-width: 640px) {
|
|
334
|
+
.zc-settings-btn { width: 40px; height: 40px; }
|
|
335
|
+
.zc-settings-bottom-left, .zc-settings-bottom-right { bottom: 16px; }
|
|
336
|
+
}
|
|
337
|
+
`;
|
|
338
|
+
const style = document.createElement('style');
|
|
339
|
+
style.id = STYLE_ID;
|
|
340
|
+
style.textContent = css;
|
|
341
|
+
document.head.appendChild(style);
|
|
342
|
+
}
|
|
343
|
+
// ── Toggle helper ────────────────────────────────────────────────────
|
|
344
|
+
function createToggle(id, checked) {
|
|
345
|
+
const label = document.createElement('label');
|
|
346
|
+
label.className = 'zc-toggle';
|
|
347
|
+
const input = document.createElement('input');
|
|
348
|
+
input.type = 'checkbox';
|
|
349
|
+
input.checked = checked;
|
|
350
|
+
input.id = id;
|
|
351
|
+
const track = document.createElement('span');
|
|
352
|
+
track.className = 'zc-toggle-track';
|
|
353
|
+
const knob = document.createElement('span');
|
|
354
|
+
knob.className = 'zc-toggle-knob';
|
|
355
|
+
label.appendChild(input);
|
|
356
|
+
label.appendChild(track);
|
|
357
|
+
label.appendChild(knob);
|
|
358
|
+
return label;
|
|
359
|
+
}
|
|
360
|
+
// ── Toggle card helper ───────────────────────────────────────────────
|
|
361
|
+
function createToggleCard(toggleId, title, description, learnMoreUrl, defaultOn) {
|
|
362
|
+
const card = document.createElement('div');
|
|
363
|
+
card.className = 'zc-consent-toggle-card';
|
|
364
|
+
// Row: label + toggle
|
|
365
|
+
const row = document.createElement('div');
|
|
366
|
+
row.className = 'zc-consent-toggle-row';
|
|
367
|
+
const labelSpan = document.createElement('span');
|
|
368
|
+
labelSpan.className = 'zc-consent-toggle-label';
|
|
369
|
+
labelSpan.textContent = title;
|
|
370
|
+
const toggle = createToggle(toggleId, defaultOn);
|
|
371
|
+
row.appendChild(labelSpan);
|
|
372
|
+
row.appendChild(toggle);
|
|
373
|
+
card.appendChild(row);
|
|
374
|
+
// Description
|
|
375
|
+
const desc = document.createElement('div');
|
|
376
|
+
desc.className = 'zc-consent-toggle-desc';
|
|
377
|
+
desc.textContent = description;
|
|
378
|
+
card.appendChild(desc);
|
|
379
|
+
// Learn more
|
|
380
|
+
const link = document.createElement('a');
|
|
381
|
+
link.className = 'zc-consent-learn-more';
|
|
382
|
+
link.href = learnMoreUrl;
|
|
383
|
+
link.target = '_blank';
|
|
384
|
+
link.rel = 'noopener noreferrer';
|
|
385
|
+
link.textContent = 'Learn more ↗';
|
|
386
|
+
card.appendChild(link);
|
|
387
|
+
return card;
|
|
388
|
+
}
|
|
389
|
+
// ── Public: show consent popup ───────────────────────────────────────
|
|
390
|
+
export function showConsentUI(options) {
|
|
391
|
+
return new Promise((resolve) => {
|
|
392
|
+
const { appName, theme, privacyPolicyUrl } = options;
|
|
393
|
+
const defaults = options.defaults ?? { ads: true, usageData: false, aiInteractions: false };
|
|
394
|
+
injectStyles(theme);
|
|
395
|
+
// Root wrapper
|
|
396
|
+
const root = document.createElement('div');
|
|
397
|
+
root.className = 'zc-consent-root';
|
|
398
|
+
// Backdrop (non-dismissable)
|
|
399
|
+
const backdrop = document.createElement('div');
|
|
400
|
+
backdrop.className = 'zc-consent-backdrop';
|
|
401
|
+
// Block Escape key
|
|
402
|
+
const blockEscape = (e) => {
|
|
403
|
+
if (e.key === 'Escape') {
|
|
404
|
+
e.preventDefault();
|
|
405
|
+
e.stopPropagation();
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
document.addEventListener('keydown', blockEscape, true);
|
|
409
|
+
// Card
|
|
410
|
+
const card = document.createElement('div');
|
|
411
|
+
card.className = 'zc-consent-card';
|
|
412
|
+
// Header
|
|
413
|
+
const header = document.createElement('div');
|
|
414
|
+
header.className = 'zc-consent-header';
|
|
415
|
+
const logo = document.createElement('div');
|
|
416
|
+
logo.className = 'zc-consent-logo';
|
|
417
|
+
logo.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="color:var(--zc-accent-fg)"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>`;
|
|
418
|
+
const title = document.createElement('div');
|
|
419
|
+
title.className = 'zc-consent-title';
|
|
420
|
+
title.textContent = `${appName || 'This app'} uses Zerocost`;
|
|
421
|
+
header.appendChild(logo);
|
|
422
|
+
header.appendChild(title);
|
|
423
|
+
card.appendChild(header);
|
|
424
|
+
// Subtitle
|
|
425
|
+
const subtitle = document.createElement('div');
|
|
426
|
+
subtitle.className = 'zc-consent-subtitle';
|
|
427
|
+
subtitle.textContent = 'Manage your preferences below. You can update these anytime.';
|
|
428
|
+
card.appendChild(subtitle);
|
|
429
|
+
// Toggles
|
|
430
|
+
const toggles = document.createElement('div');
|
|
431
|
+
toggles.className = 'zc-consent-toggles';
|
|
432
|
+
// Base URL for Zerocost documentation/legal links
|
|
433
|
+
const zerocostBaseUrl = ZEROCOST_BASE_URL;
|
|
434
|
+
toggles.appendChild(createToggleCard('zc-toggle-ads', 'Ads', 'Contextual, non-intrusive ads. No cookies or browsing history used.', `${zerocostBaseUrl}/docs/ads`, defaults.ads));
|
|
435
|
+
toggles.appendChild(createToggleCard('zc-toggle-usage', 'Usage data', 'Anonymized usage patterns. No personal information is shared.', `${zerocostBaseUrl}/docs/usage-data`, defaults.usageData));
|
|
436
|
+
toggles.appendChild(createToggleCard('zc-toggle-ai', 'AI interactions', 'Anonymized conversation data used for AI research.', `${zerocostBaseUrl}/docs/ai-interactions`, defaults.aiInteractions));
|
|
437
|
+
card.appendChild(toggles);
|
|
438
|
+
// Footer links
|
|
439
|
+
const footer = document.createElement('div');
|
|
440
|
+
footer.className = 'zc-consent-footer';
|
|
441
|
+
const ppLink = document.createElement('a');
|
|
442
|
+
ppLink.href = privacyPolicyUrl || `${zerocostBaseUrl}/privacy`;
|
|
443
|
+
ppLink.target = '_blank';
|
|
444
|
+
ppLink.rel = 'noopener noreferrer';
|
|
445
|
+
ppLink.textContent = 'Privacy Policy';
|
|
446
|
+
footer.appendChild(ppLink);
|
|
447
|
+
const sep1 = document.createElement('span');
|
|
448
|
+
sep1.className = 'zc-consent-footer-sep';
|
|
449
|
+
sep1.textContent = '·';
|
|
450
|
+
footer.appendChild(sep1);
|
|
451
|
+
const termsLink = document.createElement('a');
|
|
452
|
+
termsLink.href = `${zerocostBaseUrl}/terms`;
|
|
453
|
+
termsLink.target = '_blank';
|
|
454
|
+
termsLink.rel = 'noopener noreferrer';
|
|
455
|
+
termsLink.textContent = 'Terms';
|
|
456
|
+
footer.appendChild(termsLink);
|
|
457
|
+
const sep2 = document.createElement('span');
|
|
458
|
+
sep2.className = 'zc-consent-footer-sep';
|
|
459
|
+
sep2.textContent = '·';
|
|
460
|
+
footer.appendChild(sep2);
|
|
461
|
+
const dnsLink = document.createElement('a');
|
|
462
|
+
dnsLink.href = `${zerocostBaseUrl}/docs/do-not-sell`;
|
|
463
|
+
dnsLink.target = '_blank';
|
|
464
|
+
dnsLink.rel = 'noopener noreferrer';
|
|
465
|
+
dnsLink.textContent = 'Do Not Sell My Data';
|
|
466
|
+
footer.appendChild(dnsLink);
|
|
467
|
+
card.appendChild(footer);
|
|
468
|
+
// Confirm button
|
|
469
|
+
const confirmBtn = document.createElement('button');
|
|
470
|
+
confirmBtn.className = 'zc-consent-confirm';
|
|
471
|
+
confirmBtn.textContent = 'Confirm';
|
|
472
|
+
confirmBtn.addEventListener('click', () => {
|
|
473
|
+
const ads = document.getElementById('zc-toggle-ads')?.checked ?? defaults.ads;
|
|
474
|
+
const usageData = document.getElementById('zc-toggle-usage')?.checked ?? defaults.usageData;
|
|
475
|
+
const aiInteractions = document.getElementById('zc-toggle-ai')?.checked ?? defaults.aiInteractions;
|
|
476
|
+
// Cleanup
|
|
477
|
+
document.removeEventListener('keydown', blockEscape, true);
|
|
478
|
+
root.remove();
|
|
479
|
+
resolve({ ads, usageData, aiInteractions });
|
|
480
|
+
});
|
|
481
|
+
card.appendChild(confirmBtn);
|
|
482
|
+
backdrop.appendChild(card);
|
|
483
|
+
root.appendChild(backdrop);
|
|
484
|
+
document.body.appendChild(root);
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
/** Remove any existing consent popup from the DOM */
|
|
488
|
+
export function removeConsentUI() {
|
|
489
|
+
document.querySelector('.zc-consent-root')?.remove();
|
|
490
|
+
}
|