-
Notifications
You must be signed in to change notification settings - Fork 42
Expand file tree
/
Copy pathprivacy-policy-generator.html
More file actions
584 lines (539 loc) · 21.9 KB
/
Copy pathprivacy-policy-generator.html
File metadata and controls
584 lines (539 loc) · 21.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Privacy policy generator</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Source+Serif+4:opsz,wght@8..60,400;8..60,600&family=Inter:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet" />
<style>
:root {
--ink: #1c2b38;
--ink-2: #2c3f4f;
--paper: #f8f7f2;
--paper-line: #e4e1d6;
--teal: #0f6e56;
--teal-dark: #084234;
--stamp-red: #a5382b;
--muted: #6d7580;
--text: #1c2b38;
--border: #d8d4c6;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: "Inter", sans-serif;
color: var(--text);
background: var(--ink);
}
.app {
display: grid;
grid-template-columns: 380px 1fr;
min-height: 100vh;
}
@media (max-width: 900px) {
.app {
grid-template-columns: 1fr;
}
}
/* ---- Sidebar / form ---- */
.sidebar {
background: var(--ink);
color: #dfe6ea;
padding: 32px 28px 60px;
overflow-y: auto;
}
.brand {
font-family: "Source Serif 4", serif;
font-size: 22px;
color: #fff;
margin: 0 0 2px;
}
.brand-sub {
font-size: 13px;
color: #93a3ad;
margin: 0 0 28px;
}
.field-group {
margin-bottom: 26px;
padding-bottom: 22px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.field-group:last-child {
border-bottom: none;
}
.group-label {
font-size: 11px;
letter-spacing: 0.06em;
text-transform: uppercase;
color: #7d8e98;
margin: 0 0 12px;
font-weight: 500;
}
label.field-label {
display: block;
font-size: 13px;
color: #c7d1d7;
margin: 12px 0 5px;
}
input[type="text"],
input[type="email"],
input[type="date"],
select {
width: 100%;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.14);
color: #fff;
border-radius: 6px;
padding: 9px 10px;
font-size: 14px;
font-family: "Inter", sans-serif;
}
input[type="text"]:focus,
input[type="email"]:focus,
input[type="date"]:focus,
select:focus {
outline: none;
border-color: var(--teal);
background: rgba(255, 255, 255, 0.09);
}
input::placeholder {
color: #66727a;
}
select option {
color: #000;
}
.check-row {
display: flex;
align-items: flex-start;
gap: 9px;
margin: 9px 0;
cursor: pointer;
}
.check-row input[type="checkbox"] {
margin-top: 2px;
accent-color: var(--teal);
width: 15px;
height: 15px;
flex-shrink: 0;
}
.check-row span {
font-size: 13.5px;
color: #d6dee2;
line-height: 1.4;
}
/* ---- Preview pane ---- */
.preview-pane {
background: var(--paper);
padding: 48px 56px 100px;
overflow-y: auto;
}
@media (max-width: 900px) {
.preview-pane {
padding: 32px 20px 80px;
}
}
.toolbar {
display: flex;
justify-content: flex-end;
gap: 10px;
max-width: 640px;
margin: 0 auto 20px;
}
.toolbar button {
font-family: "Inter", sans-serif;
font-size: 13px;
font-weight: 500;
padding: 9px 14px;
border-radius: 6px;
border: 1px solid var(--border);
background: #fff;
color: var(--ink);
cursor: pointer;
}
.toolbar button:hover {
border-color: var(--teal);
color: var(--teal-dark);
}
.toolbar button.primary {
background: var(--teal);
border-color: var(--teal);
color: #fff;
}
.toolbar button.primary:hover {
background: var(--teal-dark);
}
.doc {
max-width: 640px;
margin: 0 auto;
background: #fff;
border: 1px solid var(--border);
box-shadow: 0 1px 0 var(--border);
position: relative;
padding: 52px 56px 60px;
}
.stamp {
position: absolute;
top: 40px;
right: 44px;
font-family: "IBM Plex Mono", monospace;
font-size: 12px;
letter-spacing: 0.12em;
font-weight: 500;
padding: 5px 10px;
border: 2px solid var(--stamp-red);
color: var(--stamp-red);
transform: rotate(6deg);
border-radius: 3px;
opacity: 0.85;
}
.stamp.ready {
border-color: var(--teal);
color: var(--teal-dark);
transform: rotate(-4deg);
}
.doc h1 {
font-family: "Source Serif 4", serif;
font-size: 28px;
margin: 0 0 4px;
color: var(--ink);
}
.doc .updated {
font-family: "IBM Plex Mono", monospace;
font-size: 12.5px;
color: var(--muted);
margin: 0 0 34px;
}
.doc h2 {
font-family: "Source Serif 4", serif;
font-size: 16.5px;
font-weight: 600;
color: var(--ink);
margin: 30px 0 8px;
padding-bottom: 6px;
border-bottom: 1px solid var(--paper-line);
}
.doc p,
.doc li {
font-size: 14.5px;
line-height: 1.68;
color: #33404a;
}
.doc ul {
margin: 8px 0;
padding-left: 20px;
}
.doc li {
margin: 4px 0;
}
.fill {
background: #fdf1d8;
border-bottom: 1px solid #e9c877;
padding: 0 2px;
color: #7a5a12;
font-family: "IBM Plex Mono", monospace;
font-size: 0.92em;
}
.doc .contact-block {
background: var(--paper);
border: 1px solid var(--paper-line);
border-radius: 6px;
padding: 14px 18px;
margin-top: 8px;
font-size: 14px;
}
.doc hr {
border: none;
border-top: 1px solid var(--paper-line);
margin: 36px 0 20px;
}
.doc .disclaimer {
font-size: 12.5px;
color: var(--muted);
font-style: italic;
}
.toast {
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%) translateY(20px);
background: var(--ink);
color: #fff;
padding: 10px 18px;
border-radius: 6px;
font-size: 13.5px;
font-family: "Inter", sans-serif;
opacity: 0;
transition:
opacity 0.2s,
transform 0.2s;
pointer-events: none;
}
.toast.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
</style>
</head>
<body>
<div class="app">
<div class="sidebar">
<p class="brand">Privacy policy generator</p>
<p class="brand-sub">Fill in the fields — the document on the right updates as you go.</p>
<div class="field-group">
<p class="group-label">Business details</p>
<label class="field-label">Company name</label>
<input type="text" id="companyName" placeholder="Acme Inc." />
<label class="field-label">Website or app name</label>
<input type="text" id="serviceName" placeholder="Acme App" />
<label class="field-label">Contact email</label>
<input type="email" id="contactEmail" placeholder="privacy@acme.com" />
<label class="field-label">Business address</label>
<input type="text" id="address" placeholder="123 Main St, City, Country" />
<label class="field-label">Last updated date</label>
<input type="date" id="updatedDate" />
</div>
<div class="field-group">
<p class="group-label">Data you collect</p>
<label class="check-row"><input type="checkbox" id="dNameEmail" checked /><span>Name, email, phone number</span></label>
<label class="check-row"><input type="checkbox" id="dPayment" /><span>Payment / billing information</span></label>
<label class="check-row"><input type="checkbox" id="dAccount" checked /><span>Account credentials</span></label>
<label class="check-row"><input type="checkbox" id="dLocation" /><span>Location data</span></label>
<label class="check-row"><input type="checkbox" id="dContent" /><span>User-submitted content / uploads</span></label>
<label class="check-row"><input type="checkbox" id="dUsage" checked /><span>Device & usage data (IP, browser, pages visited)</span></label>
<label class="check-row"><input type="checkbox" id="dCookies" checked /><span>Cookies & tracking technologies</span></label>
</div>
<div class="field-group">
<p class="group-label">Sharing</p>
<label class="check-row"><input type="checkbox" id="sAnalytics" checked /><span>Analytics providers</span></label>
<label class="check-row"><input type="checkbox" id="sPayment" /><span>Payment processors</span></label>
<label class="check-row"><input type="checkbox" id="sAds" /><span>Advertising partners</span></label>
<label class="check-row"><input type="checkbox" id="sLogin" /><span>Social login providers</span></label>
<label class="check-row"><input type="checkbox" id="noSell" checked /><span>We do not sell personal information</span></label>
</div>
<div class="field-group">
<p class="group-label">Audience & law</p>
<label class="field-label">Minimum age for use</label>
<select id="minAge">
<option value="13">13</option>
<option value="16">16</option>
<option value="18">18</option>
<option value="na">Not child-directed / not applicable</option>
</select>
<label class="check-row" style="margin-top: 14px"><input type="checkbox" id="gdpr" /><span>Include GDPR section (EU/UK users)</span></label>
<label class="check-row"><input type="checkbox" id="ccpa" /><span>Include CCPA/CPRA section (California users)</span></label>
</div>
</div>
<div class="preview-pane">
<div class="toolbar">
<button id="copyBtn">Copy text</button>
<button id="downloadTxtBtn">Download .txt</button>
<button id="downloadHtmlBtn" class="primary">Download .html</button>
</div>
<div class="doc" id="doc">
<div class="stamp" id="stamp">draft</div>
<h1 id="outTitle">Privacy policy</h1>
<p class="updated" id="outUpdated">Last updated: —</p>
<div id="outBody"></div>
<hr />
<p class="disclaimer">This document is a general template and does not constitute legal advice. Consult a qualified attorney to confirm compliance with laws applicable to your business.</p>
</div>
</div>
</div>
<div class="toast" id="toast">Copied to clipboard</div>
<script>
const ids = ["companyName", "serviceName", "contactEmail", "address", "updatedDate", "dNameEmail", "dPayment", "dAccount", "dLocation", "dContent", "dUsage", "dCookies", "sAnalytics", "sPayment", "sAds", "sLogin", "noSell", "minAge", "gdpr", "ccpa"];
const els = {};
ids.forEach((id) => (els[id] = document.getElementById(id)));
const today = new Date();
els.updatedDate.value = today.toISOString().slice(0, 10);
function fmtDate(str) {
if (!str) return "[Month Day, Year]";
const d = new Date(str + "T00:00:00");
return d.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" });
}
function fill(val, placeholder) {
const v = (val || "").trim();
if (v) return { text: v, isFilled: true };
return { text: placeholder, isFilled: false };
}
function span(val, placeholder) {
const f = fill(val, placeholder);
return f.isFilled ? f.text : '<span class="fill">' + f.text + "</span>";
}
function buildState() {
return {
company: els.companyName.value,
service: els.serviceName.value,
email: els.contactEmail.value,
address: els.address.value,
date: els.updatedDate.value,
collect: {
nameEmail: els.dNameEmail.checked,
payment: els.dPayment.checked,
account: els.dAccount.checked,
location: els.dLocation.checked,
content: els.dContent.checked,
usage: els.dUsage.checked,
cookies: els.dCookies.checked
},
share: {
analytics: els.sAnalytics.checked,
payment: els.sPayment.checked,
ads: els.sAds.checked,
login: els.sLogin.checked,
noSell: els.noSell.checked
},
minAge: els.minAge.value,
gdpr: els.gdpr.checked,
ccpa: els.ccpa.checked
};
}
function isComplete(s) {
return s.company.trim() && s.service.trim() && s.email.trim() && s.address.trim();
}
function renderDoc(s, forHtml) {
const companyDisp = span(s.company, "[Company Name]");
const serviceDisp = span(s.service, "[website/app name]");
const emailDisp = span(s.email, "[contact email]");
const addressDisp = span(s.address, "[Address]");
let collectItems = [];
if (s.collect.nameEmail) collectItems.push("Name, email address, and phone number");
if (s.collect.payment) collectItems.push("Billing and payment information");
if (s.collect.account) collectItems.push("Account credentials (username, password)");
if (s.collect.content) collectItems.push("Content you submit, upload, or post through the Service");
if (s.collect.location) collectItems.push("Location data");
if (s.collect.usage) collectItems.push("IP address, browser type, device information, pages visited, and usage patterns");
if (s.collect.cookies) collectItems.push("Cookies and similar tracking technologies (see the Cookies section below)");
if (collectItems.length === 0) collectItems.push('<span class="fill">[No data categories selected — add at least one]</span>');
let shareItems = [];
if (s.share.analytics) shareItems.push("<strong>Analytics providers</strong> to help us understand usage of the Service");
if (s.share.payment) shareItems.push("<strong>Payment processors</strong> to complete transactions");
if (s.share.ads) shareItems.push("<strong>Advertising partners</strong> to deliver relevant advertising");
if (s.share.login) shareItems.push("<strong>Social login providers</strong> you choose to sign in with");
shareItems.push("<strong>Legal authorities</strong>, when required by law or to protect rights and safety");
shareItems.push("<strong>Business transfers</strong>, in connection with a merger, acquisition, or sale of assets");
const ageText = s.minAge === "na" ? "Our Service is not directed to children, and we do not knowingly collect personal information from children." : `Our Service is not directed to children under ${s.minAge}. We do not knowingly collect personal information from children under ${s.minAge}.`;
let html = "";
html += "<h2>1. Introduction</h2>";
html += `<p>${companyDisp} ("we," "us," or "our") operates ${serviceDisp} (the "Service"). This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you use our Service.</p>`;
html += "<p>By using the Service, you agree to the collection and use of information in accordance with this policy.</p>";
html += "<h2>2. Information we collect</h2><ul>";
collectItems.forEach((i) => (html += `<li>${i}</li>`));
html += "</ul>";
html += "<h2>3. How we use your information</h2><ul>";
html += "<li>Provide, operate, and maintain the Service</li>";
html += "<li>Communicate with you, including support and updates</li>";
html += "<li>Improve and personalize the Service</li>";
html += "<li>Detect, prevent, and address technical issues or fraud</li>";
html += "<li>Comply with legal obligations</li>";
if (s.share.ads) html += "<li>Deliver and measure advertising</li>";
html += "</ul>";
html += "<h2>4. How we share your information</h2><ul>";
shareItems.forEach((i) => (html += `<li>${i}</li>`));
html += "</ul>";
if (s.share.noSell) {
html += "<p>We do not sell your personal information to third parties.</p>";
}
if (s.collect.cookies) {
html += "<h2>5. Cookies and tracking technologies</h2>";
html += "<p>We use cookies and similar technologies to keep you logged in, remember your preferences, and analyze site traffic. You can control cookies through your browser settings.</p>";
}
html += "<h2>6. Data security</h2>";
html += "<p>We implement reasonable technical and organizational measures to protect your information. No method of transmission or storage is 100% secure, and we cannot guarantee absolute security.</p>";
html += "<h2>7. Your privacy rights</h2>";
html += `<p>Depending on your location, you may have the right to access, correct, or delete your information, object to certain processing, request data portability, or withdraw consent. To exercise these rights, contact us at ${emailDisp}.</p>`;
if (s.gdpr) {
html += "<h2>8. GDPR (EU / UK users)</h2>";
html += "<p>If you are located in the European Economic Area or the United Kingdom, we process your data on the basis of consent, contract, or legitimate interest. You have the right to lodge a complaint with your local data protection authority.</p>";
}
if (s.ccpa) {
html += "<h2>9. CCPA / CPRA (California users)</h2>";
html += "<p>California residents have the right to know, delete, and correct their personal information, and to opt out of the sale or sharing of personal information. To exercise these rights, contact us at " + emailDisp + ".</p>";
}
html += "<h2>10. Children's privacy</h2>";
html += `<p>${ageText} If you believe we have collected such information, contact us at ${emailDisp} so we can delete it.</p>`;
html += "<h2>11. Changes to this policy</h2>";
html += "<p>We may update this Privacy Policy from time to time. We will notify you of changes by posting the new policy on this page and updating the date above.</p>";
html += "<h2>12. Contact us</h2>";
html += `<div class="contact-block">${companyDisp}<br>${addressDisp}<br>Email: ${emailDisp}</div>`;
return html;
}
function update() {
const s = buildState();
document.getElementById("outTitle").textContent = (s.service.trim() || "Your service") + " — Privacy policy";
document.getElementById("outUpdated").textContent = "Last updated: " + fmtDate(s.date);
document.getElementById("outBody").innerHTML = renderDoc(s);
const stamp = document.getElementById("stamp");
const complete = isComplete(s);
stamp.textContent = complete ? "ready" : "draft";
stamp.classList.toggle("ready", complete);
}
document.querySelectorAll(".sidebar input, .sidebar select").forEach((el) => {
el.addEventListener("input", update);
});
update();
function getPlainText() {
const doc = document.getElementById("doc").cloneNode(true);
doc.querySelectorAll(".stamp").forEach((n) => n.remove());
return doc.innerText.trim();
}
function getFullHtml() {
const bodyHtml = document.getElementById("doc").outerHTML.replace(/<div class="stamp[^>]*>.*?<\/div>/, "");
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Privacy Policy</title>
<style>
body { font-family: Georgia, 'Times New Roman', serif; max-width: 720px; margin: 40px auto; padding: 0 24px; color: #1c2b38; line-height: 1.6; }
h1 { font-size: 28px; }
h2 { font-size: 18px; margin-top: 28px; border-bottom: 1px solid #e0e0e0; padding-bottom: 6px; }
.updated { color: #6d7580; font-size: 13px; }
.contact-block { background: #f8f7f2; border: 1px solid #e4e1d6; border-radius: 6px; padding: 14px 18px; }
.fill { background: #fdf1d8; padding: 0 2px; }
.disclaimer { font-size: 12.5px; color: #6d7580; font-style: italic; }
hr { border: none; border-top: 1px solid #e4e1d6; margin: 36px 0 20px; }
</style>
</head>
<body>
${bodyHtml}
</body>
</html>`;
}
function showToast(msg) {
const t = document.getElementById("toast");
t.textContent = msg;
t.classList.add("show");
setTimeout(() => t.classList.remove("show"), 1800);
}
document.getElementById("copyBtn").addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(getPlainText());
showToast("Copied to clipboard");
} catch (e) {
showToast("Could not copy — select the text manually");
}
});
function download(filename, content, mime) {
const blob = new Blob([content], { type: mime });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
document.getElementById("downloadTxtBtn").addEventListener("click", () => {
download("privacy-policy.txt", getPlainText(), "text/plain");
});
document.getElementById("downloadHtmlBtn").addEventListener("click", () => {
download("privacy-policy.html", getFullHtml(), "text/html");
});
</script>
</body>
</html>