@networkpro/web 1.14.2 → 1.15.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 (41) hide show
  1. package/CHANGELOG.md +78 -1
  2. package/README.md +6 -4
  3. package/_redirects +4 -2
  4. package/cspell.json +2 -0
  5. package/netlify/edge-functions/csp-report.js +22 -15
  6. package/package.json +3 -4
  7. package/scripts/testRedirects.js +84 -0
  8. package/src/app.html +1 -1
  9. package/src/lib/components/RedirectPage.svelte +2 -34
  10. package/src/lib/components/layout/HeaderDefault.svelte +8 -3
  11. package/src/lib/components/layout/HeaderHome.svelte +8 -3
  12. package/src/lib/pages/AboutContent.svelte +1 -1
  13. package/src/lib/pages/HomeContent.svelte +3 -9
  14. package/src/lib/pages/LicenseContent.svelte +1 -2
  15. package/src/lib/pages/PrivacyContent.svelte +45 -31
  16. package/src/lib/pages/PrivacyDashboard.svelte +0 -1
  17. package/src/lib/pages/TermsConditionsContent.svelte +2 -2
  18. package/src/lib/stores/trackingPreferences.js +6 -0
  19. package/src/lib/styles/css/default.css +31 -4
  20. package/src/lib/styles/global.min.css +1 -1
  21. package/src/lib/utils/getUTMParams.js +43 -0
  22. package/src/lib/utils/purify.js +1 -1
  23. package/src/lib/utils/redirect.js +52 -0
  24. package/src/lib/utils/utm.js +31 -8
  25. package/src/routes/consultation/+page.svelte +16 -2
  26. package/src/routes/contact/+page.svelte +17 -3
  27. package/src/routes/links/+page.svelte +24 -5
  28. package/src/routes/posts/+page.svelte +24 -5
  29. package/src/routes/privacy-rights/+page.svelte +19 -3
  30. package/tests/unit/{unregisterServiceWorker.test.js → client/lib/unregisterServiceWorker.test.js} +2 -2
  31. package/tests/unit/client/lib/utils/redirect.test.js +80 -0
  32. package/tests/unit/client/lib/utils/utm.test.js +59 -0
  33. package/tests/unit/{routes → client/routes}/page.svelte.test.js +2 -2
  34. package/tests/unit/{checkEnv.test.js → server/checkEnv.test.js} +2 -2
  35. package/tests/unit/{checkVersions.test.js → server/checkVersions.test.js} +2 -2
  36. package/tests/unit/{csp-report.test.js → server/csp-report.test.js} +2 -2
  37. package/tests/{internal → unit/server/internal}/auditCoverage.test.js +13 -6
  38. package/tests/unit/{lib → server/lib}/utils/purify.test.js +2 -2
  39. package/vitest.config.client.js +1 -4
  40. package/vitest.config.server.js +1 -1
  41. package/tests/unit/utm.test.js +0 -49
@@ -44,7 +44,7 @@ This file is part of Network Pro.
44
44
  { id: 'tracking', text: 'Web Analytics and Tracking' },
45
45
  { id: 'payment', text: 'Payment Information' },
46
46
  { id: 'use', text: 'Use of Information' },
47
- { id: 'sharing', text: 'Data Sharing' },
47
+ { id: 'legal', text: 'Legal Requests and Data Disclosure' },
48
48
  { id: 'security', text: 'Data Security' },
49
49
  { id: 'rights', text: 'User Rights' },
50
50
  { id: 'third-party', text: 'Third-Party Links' },
@@ -54,7 +54,7 @@ This file is part of Network Pro.
54
54
  ];
55
55
 
56
56
  /** @type {string} */
57
- const effectiveDate = 'June 2, 2025';
57
+ const effectiveDate = 'June 30, 2025';
58
58
 
59
59
  /** @type {string} */
60
60
  const classSmall = 'small-text';
@@ -159,12 +159,11 @@ This file is part of Network Pro.
159
159
  <!-- Dynamic Content for Each Section -->
160
160
  {#if link.id === 'intro'}
161
161
  <p>
162
- Network Pro Strategies ("Company," "we," "us," or "our") is committed to
163
- protecting the privacy of clients and website visitors. This Privacy
164
- Policy outlines how we collect, use, and safeguard your information when
165
- you interact with our website or services, consistent with applicable
166
- U.S. federal law and Arizona law, including Title 18, Chapter 5, Article
167
- 4 of the
162
+ {COMPANY_INFO.NAME} ("Company," "we," "us," or "our") is committed to protecting
163
+ the privacy of clients and website visitors. This Privacy Policy outlines
164
+ how we collect, use, and safeguard your information when you interact with
165
+ our website or services, consistent with applicable U.S. federal law and
166
+ Arizona law, including Title 18, Chapter 5, Article 4 of the
168
167
  <strong>
169
168
  <a
170
169
  rel={PAGE.REL}
@@ -306,33 +305,50 @@ This file is part of Network Pro.
306
305
  <li><strong>Conduct analytics and enhance user experience</strong></li>
307
306
  <li><strong>Ensure legal and regulatory compliance</strong></li>
308
307
  </ul>
309
- {:else if link.id === 'sharing'}
308
+ {:else if link.id === 'legal'}
310
309
  <p>
311
- We do not sell personal information. However, we may share personal and
312
- business information under the following circumstances:
310
+ We do not sell personal or business information. We disclose such data
311
+ only when required by law, and under limited, clearly defined
312
+ circumstances:
313
313
  </p>
314
314
  <ul>
315
315
  <li>
316
- <strong>With Service Providers:</strong> We may share your information
317
- with carefully selected third-party vendors. These providers support
318
- essential aspects of our operations&mdash;including, but not limited
319
- to, payment processing, data analytics, and customer support services.
320
- All such partnerships are structured to uphold our core principles of
321
- <em>transparency, self-hosting, and prioritizing user privacy</em> across
322
- all infrastructure and data flows.
316
+ <strong>Legal Compliance</strong>: We may disclose information in
317
+ response to a valid legal process—such as a subpoena, court order, or
318
+ other binding legal request issued under applicable law. We do not
319
+ voluntarily provide user data to government entities or third parties
320
+ without a legal obligation to do so.
323
321
  </li>
324
322
  <li>
325
- <strong>Legal Compliance:</strong> We may disclose information if required
326
- to do so by applicable law, regulation, legal process, or enforceable governmental
327
- request, including subpoenas or court orders.
323
+ <strong>Review and Notice</strong>: Each request for user data is
324
+ reviewed to ensure it is lawful, specific, and properly served. Unless
325
+ prohibited by law, we will notify affected users before disclosing any
326
+ information.
328
327
  </li>
329
328
  <li>
330
- <strong>Business Transfers:</strong> In connection with a merger, acquisition,
331
- asset sale, or similar corporate transaction, we may disclose or transfer
332
- personal information, provided that reasonable steps are taken to ensure
333
- continued confidentiality and compliance with applicable privacy laws.
329
+ <strong>Service Providers</strong>: We may share personal or business
330
+ information with trusted third-party vendors who support essential
331
+ business functions (e.g., payment processing, analytics, customer
332
+ support). All such partnerships are governed by strict data protection
333
+ terms and structured to uphold our core principles of
334
+ <em>transparency and user privacy</em>.
335
+ </li>
336
+ <li>
337
+ <strong>Business Transfers</strong>: In the event of a merger,
338
+ acquisition, or sale of assets, personal and business information may
339
+ be transferred
340
+ <em>only if the receiving party provides written assurances</em> that:
341
+ (a) the information will not be sold or misused, (b) it will be handled
342
+ in a manner consistent with our privacy commitments, and (c) appropriate
343
+ technical and contractual safeguards are in place to protect it.
334
344
  </li>
335
345
  </ul>
346
+ <p>
347
+ Our policy is to require proper legal documentation and to scrutinize
348
+ all requests on a case-by-case basis. Outside these clearly defined
349
+ situations, we do not share, sell, or otherwise provide access to user
350
+ information.
351
+ </p>
336
352
  {:else if link.id === 'security'}
337
353
  <p>
338
354
  We implement industry-standard security measures to protect your data.
@@ -383,7 +399,6 @@ This file is part of Network Pro.
383
399
  </p>
384
400
  <p>
385
401
  To exercise any of these rights, you may submit a request through our <a
386
- rel={PAGE.REL}
387
402
  href={prightsLink}
388
403
  target={PAGE.BLANK}>Privacy Rights Request Form</a
389
404
  >. Alternatively, you can email us at
@@ -397,10 +412,10 @@ This file is part of Network Pro.
397
412
  </p>
398
413
  {:else if link.id === 'disclaimers'}
399
414
  <p>
400
- Network Pro Strategies offers informational content as a public service.
401
- No warranties are made regarding the accuracy or completeness of such
402
- content. Consulting services are governed by separate contracts. We
403
- disclaim liability for third-party services integrated or referenced.
415
+ {COMPANY_INFO.NAME} offers informational content as a public service. No
416
+ warranties are made regarding the accuracy or completeness of such content.
417
+ Consulting services are governed by separate contracts. We disclaim liability
418
+ for third-party services integrated or referenced.
404
419
  </p>
405
420
  {:else if link.id === 'changes'}
406
421
  <p>
@@ -410,7 +425,6 @@ This file is part of Network Pro.
410
425
  {:else if link.id === 'contact'}
411
426
  <p>
412
427
  For questions, please utilize our <a
413
- rel={PAGE.REL}
414
428
  href={contactLink}
415
429
  target={PAGE.BLANK}>Contact Form</a> or contact us directly:
416
430
  </p>
@@ -237,7 +237,6 @@ This file is part of Network Pro.
237
237
  </p>
238
238
  <p>
239
239
  To exercise any of these rights, you may submit a request through our <a
240
- rel={PAGE.REL}
241
240
  href={prightsLink}
242
241
  target={PAGE.BLANK}>Privacy Rights Request Form</a
243
242
  >. Alternatively, you can email us at
@@ -6,8 +6,6 @@ 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 tandc -->
10
-
11
9
  <script>
12
10
  import { base } from '$app/paths';
13
11
  import { CONSTANTS } from '$lib';
@@ -236,3 +234,5 @@ This file is part of Network Pro.
236
234
  </section>
237
235
  {/each}
238
236
  <!-- END TERMS AND CONDITIONS -->
237
+
238
+ <!-- cspell:ignore tandc -->
@@ -180,6 +180,12 @@ export const remindUserToReconsent = derived(trackingPreferences, (_prefs) => {
180
180
  return age > 1000 * 60 * 60 * 24 * 180; // 6 months
181
181
  });
182
182
 
183
+ /** @type {import('svelte/store').Readable<boolean>} */
184
+ export const trackingEnabled = derived(
185
+ trackingPreferences,
186
+ ($prefs) => $prefs.enabled,
187
+ );
188
+
183
189
  /**
184
190
  * Force-refresh current preferences
185
191
  * @returns {void}
@@ -461,12 +461,12 @@ footer .container {
461
461
  line-height: 1.125rem;
462
462
  background: none;
463
463
  border-radius: 0;
464
- font-family: monospace; /* retain code look */
464
+ font-family: monospace; /* retain code look */
465
465
  outline: none;
466
- overflow-wrap: break-word; /* modern replacement */
466
+ overflow-wrap: break-word; /* modern replacement */
467
467
  resize: none;
468
- white-space: normal; /* allow wrapping */
469
- word-break: normal; /* avoid deprecated behavior */
468
+ white-space: normal; /* allow wrapping */
469
+ word-break: normal; /* avoid deprecated behavior */
470
470
  }
471
471
 
472
472
  .fingerprint {
@@ -575,3 +575,30 @@ footer .container {
575
575
  width: 168px;
576
576
  height: 24px;
577
577
  }
578
+
579
+ .redirect-text {
580
+ text-align: center;
581
+ margin-top: 4rem;
582
+ }
583
+
584
+ .redirect-content {
585
+ text-align: center;
586
+ font-family: system-ui, sans-serif;
587
+ margin-top: 2rem;
588
+ }
589
+
590
+ .loading-spinner {
591
+ width: 48px;
592
+ height: 48px;
593
+ margin: 2rem auto;
594
+ border: 4px solid #ddd;
595
+ animation: spin 1s linear infinite;
596
+ border-radius: 50%;
597
+ border-top: 4px solid #ffc627;
598
+ }
599
+
600
+ @keyframes spin {
601
+ to {
602
+ transform: rotate(360deg);
603
+ }
604
+ }
@@ -3,4 +3,4 @@ Copyright © 2025 Network Pro Strategies (Network Pro™)
3
3
  SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
4
4
  This file is part of Network Pro.
5
5
  ========================================================================== */
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:#111;background-color:#ffc627}a:visited,a:visited:hover{color:#cba557}a:visited:focus,a:visited:focus-visible{color:#111!important}.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}.gold{color:#ffc627}.visited{color:#cba557}.goldseparator{color:#ffc627;margin:0 .5rem}.center-nav{text-align:center;padding:5px;font-size:1rem;line-height:1.5rem}.block{overflow-wrap:break-word;resize:none;white-space:normal;word-break:normal;background:0 0;border:none;border-radius:0;outline:none;width:100%;font-family:monospace;font-size:.875rem;line-height:1.125rem}.fingerprint{white-space:pre-line;font-weight:700;display:block}.pgp-image{width:150px;height:150px}.spacer{margin:2rem 0}.separator{margin:0 .5rem}.emoji{margin-right:8px}.headline{margin-bottom:4px;font-style:italic;font-weight:700;display:block}.label{font-family:inherit;font-weight:700}.description{font-family:inherit;font-style:normal;font-weight:400;display:inline}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.pgp-entry{flex-wrap:wrap;align-items:center;gap:2rem;margin-bottom:2rem;display:flex}.pgp-text{flex:2;min-width:250px}.pgp-qr{flex:1;min-width:150px}.obtainium-direct-label{margin:.25rem 0 .75rem;font-weight:700}.obtainium-manual-label{margin-top:.75rem;font-weight:700}.obtainium-img{width:185px;height:55px;margin-bottom:.25rem}.obtainium-margin{margin-left:4px}.obtainium-fa-down{color:#ffc627;margin-left:4px}.obtainium-icon{width:50px;height:50px}.proton-img{width:168px;height:24px}
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:#111;background-color:#ffc627}a:visited,a:visited:hover{color:#cba557}a:visited:focus,a:visited:focus-visible{color:#111!important}.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}.gold{color:#ffc627}.visited{color:#cba557}.goldseparator{color:#ffc627;margin:0 .5rem}.center-nav{text-align:center;padding:5px;font-size:1rem;line-height:1.5rem}.block{overflow-wrap:break-word;resize:none;white-space:normal;word-break:normal;background:0 0;border:none;border-radius:0;outline:none;width:100%;font-family:monospace;font-size:.875rem;line-height:1.125rem}.fingerprint{white-space:pre-line;font-weight:700;display:block}.pgp-image{width:150px;height:150px}.spacer{margin:2rem 0}.separator{margin:0 .5rem}.emoji{margin-right:8px}.headline{margin-bottom:4px;font-style:italic;font-weight:700;display:block}.label{font-family:inherit;font-weight:700}.description{font-family:inherit;font-style:normal;font-weight:400;display:inline}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.pgp-entry{flex-wrap:wrap;align-items:center;gap:2rem;margin-bottom:2rem;display:flex}.pgp-text{flex:2;min-width:250px}.pgp-qr{flex:1;min-width:150px}.obtainium-direct-label{margin:.25rem 0 .75rem;font-weight:700}.obtainium-manual-label{margin-top:.75rem;font-weight:700}.obtainium-img{width:185px;height:55px;margin-bottom:.25rem}.obtainium-margin{margin-left:4px}.obtainium-fa-down{color:#ffc627;margin-left:4px}.obtainium-icon{width:50px;height:50px}.proton-img{width:168px;height:24px}.redirect-text{text-align:center;margin-top:4rem}.redirect-content{text-align:center;margin-top:2rem;font-family:system-ui,sans-serif}.loading-spinner{border:4px solid #ddd;border-top-color:#ffc627;border-radius:50%;width:48px;height:48px;margin:2rem auto;animation:1s linear infinite spin}@keyframes spin{to{transform:rotate(360deg)}}
@@ -0,0 +1,43 @@
1
+ /* ==========================================================================
2
+ src/lib/utils/getUTMParams.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 getUTMParams.js
11
+ * @description Utility to extract UTM parameters from a URL.
12
+ * Useful for analytics platforms like PostHog or GA4.
13
+ *
14
+ * @module src/lib/utils/
15
+ * @author SunDevil311
16
+ * @updated 2025-06-30
17
+ */
18
+
19
+ /**
20
+ * Extracts standard UTM parameters from a given URL string.
21
+ *
22
+ * @param {string} url - A full URL string (including query parameters)
23
+ * @returns {{
24
+ * utm_source?: string,
25
+ * utm_medium?: string,
26
+ * utm_campaign?: string
27
+ * }} An object containing the available UTM parameters, if present
28
+ */
29
+ export function getUTMParams(url) {
30
+ try {
31
+ const { searchParams } = new URL(url);
32
+
33
+ return {
34
+ utm_source: searchParams.get('utm_source') || undefined,
35
+ utm_medium: searchParams.get('utm_medium') || undefined,
36
+ utm_campaign: searchParams.get('utm_campaign') || undefined,
37
+ };
38
+ } catch (err) {
39
+ const message = err instanceof Error ? err.message : String(err);
40
+ console.warn('[getUTMParams] Invalid URL:', url, '-', message);
41
+ return {};
42
+ }
43
+ }
@@ -10,7 +10,7 @@ This file is part of Network Pro.
10
10
  * @file purify.js
11
11
  * @description Universal DOMPurify instance for SSR + client with safe build support.
12
12
  * Secures untrusted HTML before injecting it into the DOM.
13
- * @module src/lib/utils
13
+ * @module src/lib/utils/
14
14
  * @author SunDevil311
15
15
  * @updated 2025-06-01
16
16
  */
@@ -0,0 +1,52 @@
1
+ /* ==========================================================================
2
+ src/lib/utils/redirect.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 utm.js
11
+ * @description Determines if the current browser is Firefox and skips redirect
12
+ * visual if true.
13
+ *
14
+ * @module src/lib/utils/
15
+ * @author SunDevil311
16
+ * @updated 2025-07-01
17
+ */
18
+
19
+ /**
20
+ * Checks whether the current browser is Firefox.
21
+ * @returns {boolean} True if the browser is Firefox.
22
+ */
23
+ function isFirefox() {
24
+ return (
25
+ typeof navigator !== 'undefined' &&
26
+ navigator.userAgent.toLowerCase().includes('firefox')
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Redirects to a URL, immediately in Firefox (to avoid popup heuristics),
32
+ * and with a visual delay in all other browsers.
33
+ *
34
+ * @param {string} to - Destination URL
35
+ * @param {number} delay - Delay in seconds (ignored for Firefox)
36
+ */
37
+ export function redirectWithBrowserAwareness(to, delay = 2) {
38
+ if (!to) {
39
+ console.warn('⛔ No redirect target provided');
40
+ return;
41
+ }
42
+
43
+ if (isFirefox()) {
44
+ console.log('🦊 Firefox detected — redirecting immediately');
45
+ window.location.replace(to);
46
+ } else {
47
+ console.log(`✅ Delayed redirect to: ${to} after ${delay}s`);
48
+ setTimeout(() => {
49
+ window.location.replace(to);
50
+ }, delay * 1000);
51
+ }
52
+ }
@@ -8,20 +8,43 @@ This file is part of Network Pro.
8
8
 
9
9
  /**
10
10
  * @file utm.js
11
- * @description Append UTM parameter from window.location to a given URL.
11
+ * @description Appends standardized UTM parameters to a given URL.
12
12
  * @module src/lib/utils/
13
13
  * @author SunDevil311
14
- * @updated 2025-05-28
14
+ * @updated 2025-06-30
15
15
  */
16
16
 
17
+ import { browser } from '$app/environment';
18
+ import { getStores } from '$app/stores';
19
+ import { get } from 'svelte/store';
20
+
17
21
  /**
18
- * Returns `null` if not in a browser context.
19
- * @param {string} url - The base URL to append to
20
- * @returns {string | null}
22
+ * @param {string} url - The target URL to append UTM parameters to
23
+ * @returns {string} URL with appended UTM parameters
21
24
  */
22
25
  export function appendUTM(url) {
23
- if (typeof window === 'undefined') return null;
26
+ if (!browser) return url;
27
+
28
+ const { page } = getStores();
29
+ const pathname = get(page).url.pathname;
30
+
31
+ let campaign = 'internal'; // default fallback
24
32
 
25
- const utm = new URLSearchParams(window.location.search).get('utm_source');
26
- return utm ? `${url}?utm_source=${encodeURIComponent(utm)}` : url;
33
+ if (pathname.startsWith('/contact')) campaign = 'contact';
34
+ else if (pathname.startsWith('/links')) campaign = 'links';
35
+ else if (pathname.startsWith('/posts')) campaign = 'posts';
36
+ else if (pathname.startsWith('/privacy-rights')) campaign = 'prights';
37
+ else if (pathname.startsWith('/consultation')) campaign = 'consult';
38
+ // add more if needed
39
+
40
+ const utmParams = new URLSearchParams({
41
+ utm_source: 'netwk.pro',
42
+ utm_medium: 'redirect',
43
+ utm_campaign: campaign,
44
+ });
45
+
46
+ const separator = url.includes('?') ? '&' : '?';
47
+ return `${url}${separator}${utmParams.toString()}`;
27
48
  }
49
+
50
+ // cspell:ignore prights
@@ -9,6 +9,10 @@ This file is part of Network Pro.
9
9
  <script>
10
10
  import RedirectPage from '$lib/components/RedirectPage.svelte';
11
11
  import { appendUTM } from '$lib/utils/utm.js';
12
+ import { getUTMParams } from '$lib/utils/getUTMParams.js';
13
+ import { trackingEnabled } from '$lib/stores/trackingPreferences';
14
+ import posthog from 'posthog-js';
15
+ import { get } from 'svelte/store';
12
16
  import { onMount } from 'svelte';
13
17
  import { browser } from '$app/environment';
14
18
 
@@ -21,10 +25,20 @@ This file is part of Network Pro.
21
25
  onMount(() => {
22
26
  if (!browser) return;
23
27
 
24
- target = appendUTM(
28
+ const url = appendUTM(
25
29
  'https://cloud.neteng.pro/index.php/apps/appointments/pub/8clCqQrt3AtGbNrr/form',
26
30
  );
27
- show = true;
31
+
32
+ if (get(trackingEnabled)) {
33
+ const utm = getUTMParams(url);
34
+ posthog.capture('redirect_to_consult', {
35
+ target_url: url,
36
+ ...utm,
37
+ });
38
+ }
39
+
40
+ target = url;
41
+ show = true; // Immediately show RedirectPage
28
42
  });
29
43
  </script>
30
44
 
@@ -9,6 +9,10 @@ This file is part of Network Pro.
9
9
  <script>
10
10
  import RedirectPage from '$lib/components/RedirectPage.svelte';
11
11
  import { appendUTM } from '$lib/utils/utm.js';
12
+ import { getUTMParams } from '$lib/utils/getUTMParams.js';
13
+ import { trackingEnabled } from '$lib/stores/trackingPreferences';
14
+ import posthog from 'posthog-js';
15
+ import { get } from 'svelte/store';
12
16
  import { onMount } from 'svelte';
13
17
  import { browser } from '$app/environment';
14
18
 
@@ -21,15 +25,25 @@ This file is part of Network Pro.
21
25
  onMount(() => {
22
26
  if (!browser) return;
23
27
 
24
- target = appendUTM(
28
+ const url = appendUTM(
25
29
  'https://cloud.neteng.pro/index.php/apps/forms/s/nyWEq9fdE7kWAjqMtMySLqJc',
26
30
  );
27
- show = true;
31
+
32
+ if (get(trackingEnabled)) {
33
+ const utm = getUTMParams(url);
34
+ posthog.capture('redirect_to_contact', {
35
+ target_url: url,
36
+ ...utm,
37
+ });
38
+ }
39
+
40
+ target = url;
41
+ show = true; // Immediately show RedirectPage
28
42
  });
29
43
  </script>
30
44
 
31
45
  {#if show && target}
32
46
  <RedirectPage to={target} />
33
47
  {:else}
34
- <p style="text-align: center; margin-top: 4rem;">Preparing to redirect…</p>
48
+ <p class="redirect-text">Preparing to redirect…</p>
35
49
  {/if}
@@ -1,5 +1,5 @@
1
1
  <!-- ==========================================================================
2
- src/routes/contact/+page.svelte
2
+ src/routes/links/+page.svelte
3
3
 
4
4
  Copyright © 2025 Network Pro Strategies (Network Pro™)
5
5
  SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
@@ -9,8 +9,17 @@ This file is part of Network Pro.
9
9
  <script>
10
10
  import RedirectPage from '$lib/components/RedirectPage.svelte';
11
11
  import { appendUTM } from '$lib/utils/utm.js';
12
+ import { getUTMParams } from '$lib/utils/getUTMParams.js';
13
+ import { trackingEnabled } from '$lib/stores/trackingPreferences';
14
+ import posthog from 'posthog-js';
15
+ import { get } from 'svelte/store';
12
16
  import { onMount } from 'svelte';
13
17
  import { browser } from '$app/environment';
18
+ import { CONSTANTS } from '$lib';
19
+
20
+ //console.log(CONSTANTS.COMPANY_INFO.APP_NAME);
21
+
22
+ const { PAGE } = CONSTANTS;
14
23
 
15
24
  /** @type {string | null} */
16
25
  let target = null;
@@ -21,13 +30,23 @@ This file is part of Network Pro.
21
30
  onMount(() => {
22
31
  if (!browser) return;
23
32
 
24
- target = appendUTM('https://linktr.ee/neteng_pro');
25
- show = true;
33
+ const url = appendUTM('https://linktr.ee/neteng_pro');
34
+
35
+ if (get(trackingEnabled)) {
36
+ const utm = getUTMParams(url);
37
+ posthog.capture('redirect_to_linktree', {
38
+ target_url: url,
39
+ ...utm,
40
+ });
41
+ }
42
+
43
+ target = url;
44
+ show = true; // Immediately show RedirectPage
26
45
  });
27
46
  </script>
28
47
 
29
48
  {#if show && target}
30
- <RedirectPage to={target} rel="noopener noreferrer" />
49
+ <RedirectPage to={target} rel={PAGE.REL} />
31
50
  {:else}
32
- <p style="text-align: center; margin-top: 4rem;">Preparing to redirect…</p>
51
+ <p class="redirect-text">Preparing to redirect…</p>
33
52
  {/if}
@@ -1,5 +1,5 @@
1
1
  <!-- ==========================================================================
2
- src/routes/contact/+page.svelte
2
+ src/routes/posts/+page.svelte
3
3
 
4
4
  Copyright © 2025 Network Pro Strategies (Network Pro™)
5
5
  SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
@@ -9,8 +9,17 @@ This file is part of Network Pro.
9
9
  <script>
10
10
  import RedirectPage from '$lib/components/RedirectPage.svelte';
11
11
  import { appendUTM } from '$lib/utils/utm.js';
12
+ import { getUTMParams } from '$lib/utils/getUTMParams.js';
13
+ import { trackingEnabled } from '$lib/stores/trackingPreferences';
14
+ import posthog from 'posthog-js';
15
+ import { get } from 'svelte/store';
12
16
  import { onMount } from 'svelte';
13
17
  import { browser } from '$app/environment';
18
+ import { CONSTANTS } from '$lib';
19
+
20
+ //console.log(CONSTANTS.COMPANY_INFO.APP_NAME);
21
+
22
+ const { PAGE } = CONSTANTS;
14
23
 
15
24
  /** @type {string | null} */
16
25
  let target = null;
@@ -21,13 +30,23 @@ This file is part of Network Pro.
21
30
  onMount(() => {
22
31
  if (!browser) return;
23
32
 
24
- target = appendUTM('https://pal.bio/netwk-pro');
25
- show = true;
33
+ const url = appendUTM('https://pal.bio/netwk-pro');
34
+
35
+ if (get(trackingEnabled)) {
36
+ const utm = getUTMParams(url);
37
+ posthog.capture('redirect_to_pallyy', {
38
+ target_url: url,
39
+ ...utm,
40
+ });
41
+ }
42
+
43
+ target = url;
44
+ show = true; // Immediately show RedirectPage
26
45
  });
27
46
  </script>
28
47
 
29
48
  {#if show && target}
30
- <RedirectPage to={target} rel="noopener noreferrer" />
49
+ <RedirectPage to={target} rel={PAGE.REL} />
31
50
  {:else}
32
- <p style="text-align: center; margin-top: 4rem;">Preparing to redirect…</p>
51
+ <p class="redirect-text">Preparing to redirect…</p>
33
52
  {/if}
@@ -9,6 +9,10 @@ This file is part of Network Pro.
9
9
  <script>
10
10
  import RedirectPage from '$lib/components/RedirectPage.svelte';
11
11
  import { appendUTM } from '$lib/utils/utm.js';
12
+ import { getUTMParams } from '$lib/utils/getUTMParams.js';
13
+ import { trackingEnabled } from '$lib/stores/trackingPreferences';
14
+ import posthog from 'posthog-js';
15
+ import { get } from 'svelte/store';
12
16
  import { onMount } from 'svelte';
13
17
  import { browser } from '$app/environment';
14
18
 
@@ -21,15 +25,27 @@ This file is part of Network Pro.
21
25
  onMount(() => {
22
26
  if (!browser) return;
23
27
 
24
- target = appendUTM(
28
+ const url = appendUTM(
25
29
  'https://cloud.neteng.pro/index.php/apps/forms/s/6HpZKZCaLwb6TXYL99nLQM8t',
26
30
  );
27
- show = true;
31
+
32
+ if (get(trackingEnabled)) {
33
+ const utm = getUTMParams(url);
34
+ posthog.capture('redirect_to_prights', {
35
+ target_url: url,
36
+ ...utm,
37
+ });
38
+ }
39
+
40
+ target = url;
41
+ show = true; // Immediately show RedirectPage
28
42
  });
29
43
  </script>
30
44
 
31
45
  {#if show && target}
32
46
  <RedirectPage to={target} />
33
47
  {:else}
34
- <p style="text-align: center; margin-top: 4rem;">Preparing to redirect…</p>
48
+ <p class="redirect-text">Preparing to redirect…</p>
35
49
  {/if}
50
+
51
+ <!-- cspell:ignore prights -->