@networkpro/web 1.6.5 → 1.7.2

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 (41) hide show
  1. package/CODE_OF_CONDUCT.md +2 -2
  2. package/LICENSE.md +20 -9
  3. package/README.md +57 -13
  4. package/_redirects +1 -0
  5. package/cspell.json +2 -0
  6. package/package.json +7 -7
  7. package/playwright.config.js +1 -0
  8. package/src/lib/components/FullWidthSection.svelte +19 -4
  9. package/src/lib/components/LegalNav.svelte +31 -29
  10. package/src/lib/components/PostHog.svelte +20 -8
  11. package/src/lib/components/layout/Footer.svelte +1 -1
  12. package/src/lib/components/layout/HeaderDefault.svelte +2 -2
  13. package/src/lib/components/layout/HeaderHome.svelte +2 -2
  14. package/src/lib/images.js +3 -2
  15. package/src/lib/index.js +2 -1
  16. package/src/lib/meta.js +6 -1
  17. package/src/lib/pages/LicenseContent.svelte +17 -17
  18. package/src/lib/pages/PrivacyContent.svelte +116 -6
  19. package/src/lib/pages/PrivacyDashboard.svelte +240 -0
  20. package/src/lib/pages/TermsUseContent.svelte +1 -1
  21. package/src/lib/styles/css/default.css +23 -10
  22. package/src/lib/styles/global.min.css +1 -3
  23. package/src/lib/utils/privacy.js +18 -3
  24. package/src/lib/utils/trackingCookies.js +40 -0
  25. package/src/lib/utils/trackingStatus.js +46 -0
  26. package/src/lib/utils/utm.js +8 -1
  27. package/src/routes/about/+page.svelte +1 -7
  28. package/src/routes/foss-spotlight/+page.svelte +1 -7
  29. package/src/routes/license/+page.svelte +2 -8
  30. package/src/routes/privacy/+page.server.js +18 -0
  31. package/src/routes/{privacy-policy → privacy}/+page.svelte +5 -11
  32. package/src/routes/{privacy-policy → privacy-dashboard}/+page.server.js +2 -2
  33. package/src/routes/privacy-dashboard/+page.svelte +69 -0
  34. package/src/routes/terms-conditions/+page.svelte +2 -8
  35. package/src/routes/terms-of-use/+page.svelte +2 -8
  36. package/src/service-worker.js +31 -6
  37. package/static/robots.txt +2 -1
  38. package/static/sitemap.xml +10 -22
  39. package/tests/e2e/app.spec.js +50 -68
  40. package/tests/e2e/mobile.spec.js +32 -42
  41. package/tests/e2e/shared/helpers.js +57 -0
@@ -6,10 +6,17 @@ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
6
6
  This file is part of Network Pro.
7
7
  ========================================================================== -->
8
8
 
9
- <!-- cspell:ignore prights -->
9
+ <!-- cspell:ignore prights prefs pdash -->
10
10
 
11
11
  <script>
12
12
  import { base } from "$app/paths";
13
+ import { onMount } from "svelte";
14
+ import { getTrackingPreferences } from "$lib/utils/trackingStatus.js";
15
+ /** @type {(type: 'enable' | 'disable') => void} */
16
+ import {
17
+ setTrackingPreference,
18
+ clearTrackingPreferences,
19
+ } from "$lib/utils/trackingCookies.js";
13
20
 
14
21
  // Log the base path to verify its value
15
22
  //console.log("Base path:", base);
@@ -17,16 +24,20 @@ This file is part of Network Pro.
17
24
  /**
18
25
  * URL to the Privacy Rights Request Form redirect route, using the base path
19
26
  * URL to the Contact Form redirect route, using the base path
27
+ * URL to the Privacy Dashboard using the base path
20
28
  * @type {string}
21
29
  */
22
30
  const prightsLink = `${base}/privacy-rights`;
23
31
  const contactLink = `${base}/contact`;
32
+ const pdashLink = `${base}/privacy-dashboard`;
24
33
 
25
34
  /**
26
35
  * URL to the privacy policy in Markdown format
36
+ * External URL to the GPC website
27
37
  * @type {string}
28
38
  */
29
39
  const privacyLink = "https://docs.netwk.pro/privacy";
40
+ const gpcLink = "https://globalprivacycontrol.org/";
30
41
 
31
42
  /**
32
43
  * Table of Contents Links
@@ -56,7 +67,7 @@ This file is part of Network Pro.
56
67
  email: "support (at) neteng.pro",
57
68
  secure: "contact (at) s.neteng.pro",
58
69
  phone: "(623) 252-4350",
59
- effectiveDate: "May 23, 2025",
70
+ effectiveDate: "May 28, 2025",
60
71
  };
61
72
 
62
73
  /**
@@ -78,6 +89,48 @@ This file is part of Network Pro.
78
89
  targetSelf: "_self",
79
90
  targetBlank: "_blank",
80
91
  };
92
+
93
+ let optedOut = false;
94
+ let optedIn = false;
95
+ let trackingStatus = "";
96
+
97
+ onMount(() => {
98
+ const prefs = getTrackingPreferences();
99
+ optedOut = prefs.optedOut;
100
+ optedIn = prefs.optedIn;
101
+ trackingStatus = prefs.status;
102
+ console.log("[Tracking] Status:", trackingStatus);
103
+ });
104
+
105
+ /**
106
+ * Toggle tracking opt-out.
107
+ * @param {boolean} value
108
+ */
109
+ function toggleTracking(value) {
110
+ optedOut = value;
111
+ if (optedOut) {
112
+ console.log("[Tracking] User opted out");
113
+ setTrackingPreference("disable");
114
+ } else {
115
+ console.log("[Tracking] User cleared opt-out");
116
+ clearTrackingPreferences();
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Toggle tracking opt-in.
122
+ * @param {boolean} value
123
+ */
124
+ function toggleOptIn(value) {
125
+ optedIn = value;
126
+ if (optedIn) {
127
+ console.log("[Tracking] User opted in");
128
+ setTrackingPreference("enable");
129
+ } else {
130
+ console.log("[Tracking] User cleared opt-in");
131
+ clearTrackingPreferences();
132
+ }
133
+ }
81
134
  </script>
82
135
 
83
136
  <!-- BEGIN TITLE -->
@@ -181,10 +234,66 @@ This file is part of Network Pro.
181
234
  <p>
182
235
  We configure PostHog to prioritize user privacy. <strong
183
236
  >Analytics tracking is automatically disabled when a user's browser
184
- sends a “Do Not Track” (DNT) or “Global Privacy Control” (GPC /
185
- Sec-GPC) signal.</strong> No further action is required—your browser settings
186
- are honored by default.
237
+ sends a “Do Not Track” (DNT) or <a
238
+ rel={constants.rel}
239
+ href={gpcLink}
240
+ target={constants.targetBlank}
241
+ >“Global Privacy Control” (GPC / Sec-GPC)</a> signal.</strong> No further
242
+ action is required—your browser settings are honored by default.
243
+ </p>
244
+ <p>
245
+ You can view your current tracking status below, along with manual
246
+ opt-out and opt-in settings stored as browser cookies. These settings
247
+ override any Do Not Track (DNT) or Global Privacy Control (GPC) signals. <strong
248
+ >If you opt out, analytics tracking via PostHog is disabled entirely
249
+ until you change your preference.</strong> Your selection will persist
250
+ until manually updated.
187
251
  </p>
252
+ <p class="emphasis">
253
+ For convenient access, you can manage these settings through our <a
254
+ href={pdashLink}
255
+ target={constants.targetSelf}>Privacy Dashboard</a
256
+ >.
257
+ </p>
258
+
259
+ <div class="spacer"></div>
260
+
261
+ <h3>Tracking Preferences</h3>
262
+ <p id="tracking-status" aria-live="polite">
263
+ <strong>Tracking Status:</strong>
264
+ {trackingStatus}
265
+ </p>
266
+
267
+ <!-- Opt-out checkbox -->
268
+ <label>
269
+ <input
270
+ type="checkbox"
271
+ checked={optedOut}
272
+ disabled={optedIn}
273
+ aria-describedby="tracking-status"
274
+ on:change={(e) =>
275
+ toggleTracking(
276
+ /** @type {HTMLInputElement} */ (e.target).checked,
277
+ )} />
278
+ <strong>&nbsp;Disable analytics tracking (opt-out)</strong>
279
+ </label>
280
+
281
+ <br />
282
+
283
+ <!-- Opt-in checkbox -->
284
+ <label>
285
+ <input
286
+ type="checkbox"
287
+ checked={optedIn}
288
+ disabled={optedOut}
289
+ aria-describedby="tracking-status"
290
+ on:change={(e) =>
291
+ toggleOptIn(/** @type {HTMLInputElement} */ (e.target).checked)} />
292
+ <strong>&nbsp;Enable analytics tracking (opt-in)</strong>
293
+ </label>
294
+
295
+ <div class="spacer"></div>
296
+
188
297
  <p>
189
298
  PostHog Cloud is a third-party service, but we deploy it in a
190
299
  privacy-conscious manner that avoids intrusive profiling and aligns with
@@ -245,7 +354,8 @@ This file is part of Network Pro.
245
354
  information.
246
355
  </p>
247
356
  {:else if link.id === "rights"}
248
- <p><strong>Your Rights and Choices</strong></p>
357
+ <h3>Your Rights and Choices</h3>
358
+ <p> Under applicable state and federal law, you may have rights to: </p>
249
359
  <ul>
250
360
  <li
251
361
  ><strong>Access, update, or delete</strong> your personal information,
@@ -0,0 +1,240 @@
1
+ <!-- ==========================================================================
2
+ src/lib/pages/PrivacyDashboard.svelte
3
+
4
+ Copyright © 2025 Network Pro Strategies (Network Pro™)
5
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
6
+ This file is part of Network Pro.
7
+ ========================================================================== -->
8
+
9
+ <script>
10
+ import { base } from "$app/paths";
11
+ import { onMount } from "svelte";
12
+ import { getTrackingPreferences } from "$lib/utils/trackingStatus.js";
13
+ /** @type {(type: 'enable' | 'disable') => void} */
14
+ import {
15
+ setTrackingPreference,
16
+ clearTrackingPreferences,
17
+ } from "$lib/utils/trackingCookies.js";
18
+
19
+ /**
20
+ * @type {string}
21
+ * Style class for the div element.
22
+ */
23
+ const spaceStyle = "spacer";
24
+
25
+ /**
26
+ * URL to the full Privacy Policy using the base path
27
+ * @type {string}
28
+ */
29
+ const privacyPolicy = `${base}/privacy`;
30
+ const prightsLink = `${base}/privacy-rights`;
31
+
32
+ /**
33
+ * Constants used throughout the component for consistent styling and behavior
34
+ * @type {{
35
+ * classSmall: string,
36
+ * rel: string,
37
+ * backTop: string,
38
+ * hrefTop: string,
39
+ * targetSelf: string,
40
+ * targetBlank: string
41
+ * }}
42
+ */
43
+ const constants = {
44
+ classSmall: "small-text",
45
+ rel: "noopener noreferrer",
46
+ backTop: "Back to top",
47
+ hrefTop: "#top",
48
+ targetSelf: "_self",
49
+ targetBlank: "_blank",
50
+ };
51
+
52
+ let optedOut = false;
53
+ let optedIn = false;
54
+ let trackingStatus = "";
55
+
56
+ onMount(() => {
57
+ const prefs = getTrackingPreferences();
58
+ optedOut = prefs.optedOut;
59
+ optedIn = prefs.optedIn;
60
+ trackingStatus = prefs.status;
61
+ console.log("[Tracking] Status:", trackingStatus);
62
+ });
63
+
64
+ /**
65
+ * Toggle tracking opt-out.
66
+ * @param {boolean} value
67
+ */
68
+ function toggleTracking(value) {
69
+ optedOut = value;
70
+ if (optedOut) {
71
+ console.log("[Tracking] User opted out");
72
+ setTrackingPreference("disable");
73
+ } else {
74
+ console.log("[Tracking] User cleared opt-out");
75
+ clearTrackingPreferences();
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Toggle tracking opt-in.
81
+ * @param {boolean} value
82
+ */
83
+ function toggleOptIn(value) {
84
+ optedIn = value;
85
+ if (optedIn) {
86
+ console.log("[Tracking] User opted in");
87
+ setTrackingPreference("enable");
88
+ } else {
89
+ console.log("[Tracking] User cleared opt-in");
90
+ clearTrackingPreferences();
91
+ }
92
+ }
93
+ </script>
94
+
95
+ <section id="top">
96
+ <span class={constants.classSmall}>
97
+ <a
98
+ rel={constants.rel}
99
+ href="https://spdx.dev/learn/handling-license-info"
100
+ target={constants.targetBlank}>
101
+ SPDX License Identifier
102
+ </a>: &nbsp;<code>CC-BY-4.0 OR GPL-3.0-or-later</code>
103
+ </span>
104
+ </section>
105
+
106
+ <h1>Privacy Dashboard</h1>
107
+
108
+ <div class={spaceStyle}></div>
109
+
110
+ <h2>Take Control of Your Data</h2>
111
+
112
+ <nav class="tracking-nav">
113
+ <ul>
114
+ <li
115
+ ><a href="#tracking" target={constants.targetSelf}>Tracking Preferences</a
116
+ ></li>
117
+ <li
118
+ ><a href="#rights" target={constants.targetSelf}
119
+ >Your Rights and Choices</a
120
+ ></li>
121
+ </ul>
122
+ </nav>
123
+
124
+ <p class="bquote">
125
+ For full details, please see our <a href={privacyPolicy} target="_self"
126
+ >Privacy Policy</a
127
+ >.
128
+ </p>
129
+
130
+ <div class={spaceStyle}></div>
131
+
132
+ <hr />
133
+
134
+ <div class={spaceStyle}></div>
135
+
136
+ <section id="tracking">
137
+ <h3>Tracking Preferences</h3>
138
+
139
+ <p>
140
+ <strong
141
+ >Analytics tracking is automatically disabled when a user's browser sends
142
+ a “Do Not Track” (DNT) or <a
143
+ rel={constants.rel}
144
+ href="https://globalprivacycontrol.org/"
145
+ target={constants.targetBlank}
146
+ >“Global Privacy Control” (GPC / Sec-GPC)</a> signal.</strong>
147
+ No further action is required—your browser settings are honored by default.
148
+ </p>
149
+
150
+ <p>
151
+ You can view your current tracking status below, along with manual opt-out
152
+ and opt-in settings stored as browser cookies. These settings override any
153
+ Do Not Track (DNT) or Global Privacy Control (GPC) signals. <strong
154
+ >If you opt out, analytics tracking via PostHog is disabled entirely until
155
+ you change your preference.</strong> Your selection will persist until manually
156
+ updated.
157
+ </p>
158
+
159
+ &nbsp;
160
+
161
+ <p id="tracking-status" aria-live="polite">
162
+ <strong>Tracking Status:</strong>
163
+ {trackingStatus}
164
+ </p>
165
+
166
+ <!-- Opt-out checkbox -->
167
+ <label>
168
+ <input
169
+ type="checkbox"
170
+ checked={optedOut}
171
+ disabled={optedIn}
172
+ aria-describedby="tracking-status"
173
+ on:change={(e) =>
174
+ toggleTracking(/** @type {HTMLInputElement} */ (e.target).checked)} />
175
+ <strong>&nbsp;Disable analytics tracking (opt-out)</strong>
176
+ </label>
177
+
178
+ <br />
179
+
180
+ <!-- Opt-in checkbox -->
181
+ <label>
182
+ <input
183
+ type="checkbox"
184
+ checked={optedIn}
185
+ disabled={optedOut}
186
+ aria-describedby="tracking-status"
187
+ on:change={(e) =>
188
+ toggleOptIn(/** @type {HTMLInputElement} */ (e.target).checked)} />
189
+ <strong>&nbsp;Enable analytics tracking (opt-in)</strong>
190
+ </label>
191
+
192
+ <div class={spaceStyle}></div>
193
+
194
+ <p>
195
+ Analytics are used to understand how the site is used. No personally
196
+ identifiable information is tracked.
197
+ </p>
198
+ </section>
199
+
200
+ <span class={constants.classSmall}>
201
+ <a href={constants.hrefTop} target={constants.targetSelf}
202
+ >{constants.backTop}</a>
203
+ </span>
204
+
205
+ <div class={spaceStyle}></div>
206
+
207
+ <hr class="hr-styled" />
208
+
209
+ <div class={spaceStyle}></div>
210
+
211
+ <section id="rights">
212
+ <h3>Your Rights and Choices</h3>
213
+
214
+ <p> Under applicable state and federal law, you may have rights to: </p>
215
+
216
+ <ul>
217
+ <li
218
+ ><strong>Access, update, or delete</strong> your personal information, subject
219
+ to legal and contractual limitations.</li>
220
+ <li
221
+ ><strong>Restrict or object to processing</strong> under certain conditions,
222
+ as permitted by law.</li>
223
+ <li><strong>Opt out of direct marketing</strong></li>
224
+ </ul>
225
+
226
+ <p>
227
+ To exercise these rights, please use our <a
228
+ rel={constants.rel}
229
+ href={prightsLink}
230
+ target={constants.targetBlank}>Privacy Rights Request Form</a>
231
+ or email us at <code>contact (at) s.neteng.pro</code>.
232
+ </p>
233
+ </section>
234
+
235
+ <span class={constants.classSmall}>
236
+ <a href={constants.hrefTop} target={constants.targetSelf}
237
+ >{constants.backTop}</a>
238
+ </span>
239
+
240
+ <!-- cspell:ignore prefs prights -->
@@ -24,7 +24,7 @@ This file is part of Network Pro.
24
24
  * URL to Privacy Policy page, using the base path
25
25
  * @type {string}
26
26
  */
27
- const privacyLink = `${base}/privacy-policy`;
27
+ const privacyLink = `${base}/privacy`;
28
28
 
29
29
  /**
30
30
  * URL to License page, using the base path
@@ -26,6 +26,12 @@ This file is part of Network Pro.
26
26
  max-width: 1200px;
27
27
  }
28
28
 
29
+ /* Constrain paragraph/text-heavy content to 900px */
30
+ .readable {
31
+ margin: 0 auto;
32
+ max-width: 900px;
33
+ }
34
+
29
35
  /* Header & Footer follow the same container width */
30
36
  header,
31
37
  footer {
@@ -168,26 +174,21 @@ footer .container {
168
174
 
169
175
  .bnav {
170
176
  margin: 0 auto;
177
+ text-align: center;
171
178
  border-collapse: collapse;
172
179
  border-spacing: 0;
173
180
  }
174
181
 
175
- .bnav td {
176
- padding: 10px;
177
- font-size: 0.875rem;
178
- font-weight: bold;
179
- line-height: 1.125rem;
180
- border-style: none;
181
- overflow: hidden;
182
- word-break: normal;
183
- }
184
-
182
+ .bnav td,
185
183
  .bnav th {
186
184
  padding: 10px;
187
185
  font-size: 0.875rem;
186
+ font-weight: bold; /* Only needed for <td>, if desired */
188
187
  line-height: 1.125rem;
188
+ text-align: center; /* Center horizontally */
189
189
  border-style: none;
190
190
  overflow: hidden;
191
+ vertical-align: middle; /* Center vertically */
191
192
  word-break: normal;
192
193
  }
193
194
 
@@ -377,6 +378,18 @@ footer .container {
377
378
  font-variant: small-caps;
378
379
  }
379
380
 
381
+ .bold {
382
+ font-weight: bold;
383
+ }
384
+
385
+ .emphasis {
386
+ font-style: italic;
387
+ }
388
+
389
+ .uline {
390
+ text-decoration: underline;
391
+ }
392
+
380
393
  .bolditalic {
381
394
  font-weight: bold;
382
395
  font-style: italic;
@@ -1,8 +1,6 @@
1
1
  /*! ==========================================================================
2
- src/lib/styles/css/global.min.css
3
-
4
2
  Copyright © 2025 Network Pro Strategies (Network Pro™)
5
3
  SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
6
4
  This file is part of Network Pro.
7
5
  ========================================================================== */
8
- html{-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{margin:.67em 0;font-size:2em}hr{box-sizing:content-box}pre{font-family:monospace;font-size:1em}a{background-color:#0000}abbr[title]{border-bottom:none;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted buttontext}fieldset{padding:.35em .75em .625em}legend{color:inherit;box-sizing:border-box;white-space:normal;max-width:100%;padding:0;display:table}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}details{display:block}summary{display:list-item}template{display:none}html{color:#222;scroll-behavior:smooth;font-size:1em;line-height:1.4}::-moz-selection{text-shadow:none;background:#191919}::selection{text-shadow:none;background:#191919}hr{border:0;border-top:1px solid #ccc;height:1px;margin:1em 0;padding:0;display:block;overflow:visible}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}body{color:#fafafa;background-color:#191919;margin:10px;font-family:Arial,Helvetica,sans-serif}a{text-decoration:none}a:link{color:#ffc627}a:hover,a:active{color:#ffc627;text-decoration:underline}a:focus{color:#191919;background-color:#ffc627}a:visited,a:visited:hover{color:#7f6227}.hidden,[hidden]{display:none!important}.visually-hidden{clip:rect(0,0,0,0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.visually-hidden.focusable:active,.visually-hidden.focusable:focus{clip:auto;width:auto;height:auto;white-space:inherit;margin:0;position:static;overflow:visible}.invisible{visibility:hidden}.clearfix:before,.clearfix:after{content:"";display:table}.clearfix:after{clear:both}@media print{*,:before,:after{color:#000!important;box-shadow:none!important;text-shadow:none!important;background:#fff!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^=\#]:after,a[href^=javascript\:]:after{content:""}pre{white-space:pre-wrap!important}pre,blockquote{page-break-inside:avoid;border:1px solid #999}tr,img{page-break-inside:avoid}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.full-width-section{background-position:50%;background-size:cover;width:100%;max-width:1920px;margin:0 auto}.container{max-width:1200px;margin:0 auto;padding:0 12px}header,footer{width:100%}header .container,footer .container{max-width:1200px;margin:0 auto;padding:20px 12px}.gh{border-collapse:collapse;border-spacing:0;margin:0 auto}.gh td,.gh th{border-collapse:collapse;word-break:normal;padding:10px 5px;overflow:hidden}.gh .gh-tcell{text-align:center;vertical-align:middle}@media screen and (width<=767px){.gh,.gh col{width:auto!important}.gh-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.soc{border-collapse:collapse;border-spacing:0;margin:0 auto}.soc td,.soc th{border-collapse:collapse;word-break:normal;padding:8px;overflow:hidden}.soc .soc-fa{text-align:center;vertical-align:middle}@media screen and (width<=767px){.soc,.soc col{width:auto!important}.soc-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.foss{border-collapse:collapse;border-spacing:0}.foss td,.foss th{border-collapse:collapse;word-break:normal;padding:10px 5px;overflow:hidden}.foss .foss-cell{text-align:center;vertical-align:middle}@media screen and (width<=767px){.foss,.foss col{width:auto!important}.foss-wrap{-webkit-overflow-scrolling:touch;overflow-x:auto}}.bnav{border-collapse:collapse;border-spacing:0;margin:0 auto}.bnav td{word-break:normal;border-style:none;padding:10px;font-size:.875rem;font-weight:700;line-height:1.125rem;overflow:hidden}.bnav th{word-break:normal;border-style:none;padding:10px;font-size:.875rem;line-height:1.125rem;overflow:hidden}.bnav .bnav-cell{text-align:center;vertical-align:middle;align-content:center}@media screen and (width<=767px){.bnav,.bnav col{width:auto!important}.bnav-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.bnav2{border-collapse:collapse;border-spacing:0;margin:0 auto}.bnav2 td{word-break:normal;border-style:none;padding:10px;font-size:.875rem;font-weight:700;line-height:1.125rem;overflow:hidden}.bnav2 th{word-break:normal;border-style:none;padding:12px;font-size:.875rem;line-height:1.125rem;overflow:hidden}.bnav2 .bnav2-cell{text-align:center;vertical-align:middle;align-content:center}@media screen and (width<=767px){.bnav2,.bnav2 col{width:auto!important}.bnav2-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.pgp{border-collapse:collapse;border-spacing:0;margin:0 auto}.pgp td{word-break:normal;border-style:none;padding:10px;font-size:.875rem;line-height:1.125rem;overflow:hidden}.pgp th{word-break:normal;border:1px solid #000;padding:10px;font-size:.875rem;line-height:1.125rem;overflow:hidden}.pgp .pgp-col1{text-align:right;vertical-align:middle;padding-right:1rem}.pgp .pgp-col2{text-align:left;vertical-align:middle;padding-left:1rem}@media screen and (width<=767px){.pgp,.pgp col{width:auto!important}.pgp-wrap{-webkit-overflow-scrolling:touch;margin:2rem 0 auto;overflow-x:auto}}.logo{margin-left:auto;margin-right:auto;display:block}.index-title1{text-align:center;font-style:italic;font-weight:700}.index-title2{letter-spacing:-.015em;text-align:center;font-variant:small-caps;font-size:1.25rem;line-height:1.625rem}.index1{letter-spacing:-.035em;text-align:center;font-style:italic;font-weight:700;line-height:2.125rem}.index2{letter-spacing:-.035em;text-align:center;font-variant:small-caps;font-size:1.5rem;line-height:1.75rem}.index3{letter-spacing:-.035em;text-align:center;font-size:1.5rem;line-height:1.75rem}.index4{letter-spacing:-.035em;text-align:center;font-size:1.5rem;line-height:1.75rem;text-decoration:underline}.subhead{letter-spacing:-.035em;font-variant:small-caps;font-size:1.5rem;line-height:1.75rem}.bolditalic{font-style:italic;font-weight:700}.bquote{border-left:3px solid #9e9e9e;margin-left:30px;padding-left:10px;font-style:italic}.small-text{font-size:.75rem;line-height:1.125rem}.large-text-center{text-align:center;font-size:1.25rem;line-height:1.75rem}.prewrap{white-space:pre-wrap;display:block}.hr-styled{width:75%;margin:auto}.center-text{text-align:center}.copyright{text-align:center;font-size:.75rem;line-height:1.125rem}.visited{color:#7f6227}.center-nav{text-align:center;padding:5px;font-size:.875rem;line-height:1.125rem}.block{resize:none;background:0 0;border:none;border-radius:0;outline:none;width:100%;font-size:.75rem;line-height:1.125rem}.fingerprint{white-space:pre-line;font-weight:700;display:block}.pgp-image{width:125px;height:125px}.spacer{margin:2rem 0}.separator{margin:0 .5rem}
6
+ html{-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{margin:.67em 0;font-size:2em}hr{box-sizing:content-box}pre{font-family:monospace;font-size:1em}a{background-color:#0000}abbr[title]{border-bottom:none;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted buttontext}fieldset{padding:.35em .75em .625em}legend{color:inherit;box-sizing:border-box;white-space:normal;max-width:100%;padding:0;display:table}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}details{display:block}summary{display:list-item}template{display:none}html{color:#222;scroll-behavior:smooth;font-size:1em;line-height:1.4}::-moz-selection{text-shadow:none;background:#191919}::selection{text-shadow:none;background:#191919}hr{border:0;border-top:1px solid #ccc;height:1px;margin:1em 0;padding:0;display:block;overflow:visible}audio,canvas,iframe,img,svg,video{vertical-align:middle}fieldset{border:0;margin:0;padding:0}textarea{resize:vertical}body{color:#fafafa;background-color:#191919;margin:10px;font-family:Arial,Helvetica,sans-serif}a{text-decoration:none}a:link{color:#ffc627}a:hover,a:active{color:#ffc627;text-decoration:underline}a:focus{color:#191919;background-color:#ffc627}a:visited,a:visited:hover{color:#7f6227}.hidden,[hidden]{display:none!important}.visually-hidden{clip:rect(0,0,0,0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.visually-hidden.focusable:active,.visually-hidden.focusable:focus{clip:auto;width:auto;height:auto;white-space:inherit;margin:0;position:static;overflow:visible}.invisible{visibility:hidden}.clearfix:before,.clearfix:after{content:"";display:table}.clearfix:after{clear:both}@media print{*,:before,:after{color:#000!important;box-shadow:none!important;text-shadow:none!important;background:#fff!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href)")"}abbr[title]:after{content:" (" attr(title)")"}a[href^=\#]:after,a[href^=javascript\:]:after{content:""}pre{white-space:pre-wrap!important}pre,blockquote{page-break-inside:avoid;border:1px solid #999}tr,img{page-break-inside:avoid}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.full-width-section{background-position:50%;background-size:cover;width:100%;max-width:1920px;margin:0 auto}.container{max-width:1200px;margin:0 auto;padding:0 12px}.readable{max-width:900px;margin:0 auto}header,footer{width:100%}header .container,footer .container{max-width:1200px;margin:0 auto;padding:20px 12px}.gh{border-collapse:collapse;border-spacing:0;margin:0 auto}.gh td,.gh th{border-collapse:collapse;word-break:normal;padding:10px 5px;overflow:hidden}.gh .gh-tcell{text-align:center;vertical-align:middle}@media screen and (width<=767px){.gh,.gh col{width:auto!important}.gh-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.soc{border-collapse:collapse;border-spacing:0;margin:0 auto}.soc td,.soc th{border-collapse:collapse;word-break:normal;padding:8px;overflow:hidden}.soc .soc-fa{text-align:center;vertical-align:middle}@media screen and (width<=767px){.soc,.soc col{width:auto!important}.soc-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.foss{border-collapse:collapse;border-spacing:0}.foss td,.foss th{border-collapse:collapse;word-break:normal;padding:10px 5px;overflow:hidden}.foss .foss-cell{text-align:center;vertical-align:middle}@media screen and (width<=767px){.foss,.foss col{width:auto!important}.foss-wrap{-webkit-overflow-scrolling:touch;overflow-x:auto}}.bnav{text-align:center;border-collapse:collapse;border-spacing:0;margin:0 auto}.bnav td,.bnav th{text-align:center;vertical-align:middle;word-break:normal;border-style:none;padding:10px;font-size:.875rem;font-weight:700;line-height:1.125rem;overflow:hidden}.bnav .bnav-cell{text-align:center;vertical-align:middle;align-content:center}@media screen and (width<=767px){.bnav,.bnav col{width:auto!important}.bnav-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.bnav2{border-collapse:collapse;border-spacing:0;margin:0 auto}.bnav2 td{word-break:normal;border-style:none;padding:10px;font-size:.875rem;font-weight:700;line-height:1.125rem;overflow:hidden}.bnav2 th{word-break:normal;border-style:none;padding:12px;font-size:.875rem;line-height:1.125rem;overflow:hidden}.bnav2 .bnav2-cell{text-align:center;vertical-align:middle;align-content:center}@media screen and (width<=767px){.bnav2,.bnav2 col{width:auto!important}.bnav2-wrap{-webkit-overflow-scrolling:touch;margin:auto 0;overflow-x:auto}}.pgp{border-collapse:collapse;border-spacing:0;margin:0 auto}.pgp td{word-break:normal;border-style:none;padding:10px;font-size:.875rem;line-height:1.125rem;overflow:hidden}.pgp th{word-break:normal;border:1px solid #000;padding:10px;font-size:.875rem;line-height:1.125rem;overflow:hidden}.pgp .pgp-col1{text-align:right;vertical-align:middle;padding-right:1rem}.pgp .pgp-col2{text-align:left;vertical-align:middle;padding-left:1rem}@media screen and (width<=767px){.pgp,.pgp col{width:auto!important}.pgp-wrap{-webkit-overflow-scrolling:touch;margin:2rem 0 auto;overflow-x:auto}}.logo{margin-left:auto;margin-right:auto;display:block}.index-title1{text-align:center;font-style:italic;font-weight:700}.index-title2{letter-spacing:-.015em;text-align:center;font-variant:small-caps;font-size:1.25rem;line-height:1.625rem}.index1{letter-spacing:-.035em;text-align:center;font-style:italic;font-weight:700;line-height:2.125rem}.index2{letter-spacing:-.035em;text-align:center;font-variant:small-caps;font-size:1.5rem;line-height:1.75rem}.index3{letter-spacing:-.035em;text-align:center;font-size:1.5rem;line-height:1.75rem}.index4{letter-spacing:-.035em;text-align:center;font-size:1.5rem;line-height:1.75rem;text-decoration:underline}.subhead{letter-spacing:-.035em;font-variant:small-caps;font-size:1.5rem;line-height:1.75rem}.bold{font-weight:700}.emphasis{font-style:italic}.uline{text-decoration:underline}.bolditalic{font-style:italic;font-weight:700}.bquote{border-left:3px solid #9e9e9e;margin-left:30px;padding-left:10px;font-style:italic}.small-text{font-size:.75rem;line-height:1.125rem}.large-text-center{text-align:center;font-size:1.25rem;line-height:1.75rem}.prewrap{white-space:pre-wrap;display:block}.hr-styled{width:75%;margin:auto}.center-text{text-align:center}.copyright{text-align:center;font-size:.75rem;line-height:1.125rem}.visited{color:#7f6227}.center-nav{text-align:center;padding:5px;font-size:.875rem;line-height:1.125rem}.block{resize:none;background:0 0;border:none;border-radius:0;outline:none;width:100%;font-size:.75rem;line-height:1.125rem}.fingerprint{white-space:pre-line;font-weight:700;display:block}.pgp-image{width:125px;height:125px}.spacer{margin:2rem 0}.separator{margin:0 .5rem}
@@ -7,17 +7,32 @@ This file is part of Network Pro.
7
7
  ========================================================================== */
8
8
 
9
9
  /**
10
- * Determines whether the user allows tracking based on DNT or GPC signals.
10
+ * @file privacy.js
11
+ * @description Determines whether the user allows tracking based on DNT, GPC, or manual opt-out.
12
+ * @module src/lib/utils/
13
+ * @author SunDevil311
14
+ * @updated 2025-05-28
15
+ */
16
+
17
+ /**
11
18
  * @returns {boolean}
12
19
  */
13
20
  export function shouldTrackUser() {
14
- /** @type {string | undefined} */
15
21
  const windowDNT = /** @type {any} */ (window).doNotTrack;
16
- /** @type {boolean | undefined} */
17
22
  const navigatorGPC = /** @type {any} */ (navigator).globalPrivacyControl;
18
23
 
19
24
  const dnt = navigator.doNotTrack === "1" || windowDNT === "1";
20
25
  const gpc = navigatorGPC === true;
21
26
 
27
+ const manualOptOut = document.cookie.includes("disable_tracking=true");
28
+ const manualOptIn = document.cookie.includes("enable_tracking=true");
29
+
30
+ console.log("[Privacy] Opt-in cookie present:", manualOptIn);
31
+ console.log("[Privacy] Opt-out cookie present:", manualOptOut);
32
+
33
+ // Opt-in overrides everything; opt-out disables regardless of DNT/GPC
34
+ if (manualOptIn) return true;
35
+ if (manualOptOut) return false;
36
+
22
37
  return !dnt && !gpc;
23
38
  }
@@ -0,0 +1,40 @@
1
+ /* ==========================================================================
2
+ src/lib/utils/trackingCookies.js
3
+
4
+ Copyright © 2025 Network Pro Strategies (Network Pro™)
5
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
6
+ This file is part of Network Pro.
7
+ ========================================================================== */
8
+
9
+ /**
10
+ * @file trackingCookies.js
11
+ * @description Handles setting, clearing, and toggling tracking preference cookies.
12
+ * @module src/lib/utils/
13
+ * @author SunDevil311
14
+ * @updated 2025-05-28
15
+ */
16
+
17
+ /**
18
+ * Set a tracking preference cookie.
19
+ * @param {"enable" | "disable"} type
20
+ */
21
+ export function setTrackingPreference(type) {
22
+ const maxAge = 60 * 60 * 24 * 365 * 10; // 10 years
23
+ const cookieSettings = `path=/; max-age=${maxAge}; SameSite=Lax`;
24
+
25
+ if (type === "enable") {
26
+ document.cookie = `enable_tracking=true; ${cookieSettings}`;
27
+ document.cookie = `disable_tracking=; path=/; max-age=0; SameSite=Lax`;
28
+ } else if (type === "disable") {
29
+ document.cookie = `disable_tracking=true; ${cookieSettings}`;
30
+ document.cookie = `enable_tracking=; path=/; max-age=0; SameSite=Lax`;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Clear both tracking cookies.
36
+ */
37
+ export function clearTrackingPreferences() {
38
+ document.cookie = `enable_tracking=; path=/; max-age=0; SameSite=Lax`;
39
+ document.cookie = `disable_tracking=; path=/; max-age=0; SameSite=Lax`;
40
+ }
@@ -0,0 +1,46 @@
1
+ /* ==========================================================================
2
+ src/lib/utils/trackingStatus.js
3
+
4
+ Copyright © 2025 Network Pro Strategies (Network Pro™)
5
+ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
6
+ This file is part of Network Pro.
7
+ ========================================================================== */
8
+
9
+ /**
10
+ * @file trackingStatus.js
11
+ * @description Get tracking preferences based on cookies and browser privacy signals.
12
+ * @module src/lib/utils/
13
+ * @author SunDevil311
14
+ * @updated 2025-05-28
15
+ */
16
+
17
+ /**
18
+ * @returns {{
19
+ * optedOut: boolean,
20
+ * optedIn: boolean,
21
+ * dnt: boolean,
22
+ * gpc: boolean,
23
+ * status: string
24
+ * }}
25
+ */
26
+ export function getTrackingPreferences() {
27
+ const cookies = document.cookie;
28
+ const optedOut = cookies.includes("disable_tracking=true");
29
+ const optedIn = cookies.includes("enable_tracking=true");
30
+ const dnt = navigator.doNotTrack === "1";
31
+
32
+ // @ts-expect-error: 'globalPrivacyControl' is a non-standard property
33
+ const gpc = navigator.globalPrivacyControl === true;
34
+
35
+ let status = "⚙️ Using default settings (tracking enabled)";
36
+
37
+ if (optedOut) {
38
+ status = "🔒 Tracking disabled (manual opt-out)";
39
+ } else if (optedIn) {
40
+ status = "✅ Tracking enabled (manual opt-in)";
41
+ } else if (dnt || gpc) {
42
+ status = "🛑 Tracking disabled (via browser signal)";
43
+ }
44
+
45
+ return { optedOut, optedIn, dnt, gpc, status };
46
+ }
@@ -7,7 +7,14 @@ This file is part of Network Pro.
7
7
  ========================================================================== */
8
8
 
9
9
  /**
10
- * Append UTM parameter from window.location to a given URL.
10
+ * @file utm.js
11
+ * @description Append UTM parameter from window.location to a given URL.
12
+ * @module src/lib/utils/
13
+ * @author SunDevil311
14
+ * @updated 2025-05-28
15
+ */
16
+
17
+ /**
11
18
  * Returns `null` if not in a browser context.
12
19
  * @param {string} url - The base URL to append to
13
20
  * @returns {string | null}