@networkpro/web 1.6.3 → 1.6.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,23 +30,23 @@ All infrastructure and data flows are designed with **maximum transparency, self
30
30
 
31
31
  ```bash
32
32
  .
33
- ├── .github/workflows # CI workflows and automation
34
- ├── .vscode/ # Recommended VS Code settings, extensions
33
+ ├── .github/workflows/ # CI workflows and automation
34
+ ├── .vscode/ # Recommended VS Code settings, extensions
35
35
  ├── netlify-functions/
36
- │ └── cspReport.js # Serverless function to receive and log CSP violation reports
37
- ├── scripts/ # Utility scripts
36
+ │ └── cspReport.js # Serverless function to receive and log CSP violation reports
37
+ ├── scripts/ # Utility scripts
38
38
  ├── src/
39
- │ ├── lib/ # Reusable components, styles, utilities
40
- │ ├── routes/ # SvelteKit routes (+page.svelte, +page.server.js)
41
- │ ├── hooks.client.ts # Handles PWA install prompt and logs client errors
42
- │ ├── hooks.server.js # Injects CSP headers and permissions policy
43
- │ ├── app.html # SvelteKit entry HTML with CSP/meta/bootentry
44
- │ └── service-worker.js # Custom Service Worker
45
- ├── static/ # Static assets served at root
39
+ │ ├── lib/ # Reusable components, styles, utilities
40
+ │ ├── routes/ # SvelteKit routes (+page.svelte, +page.server.js)
41
+ │ ├── hooks.client.ts # Handles PWA install prompt and logs client errors
42
+ │ ├── hooks.server.js # Injects CSP headers and permissions policy
43
+ │ ├── app.html # SvelteKit entry HTML with CSP/meta/bootentry
44
+ │ └── service-worker.js # Custom Service Worker
45
+ ├── static/ # Static assets served at root
46
46
  ├── tests/
47
- │ ├── e2e/ # End-to-end Playwright tests
48
- │ └── unit/ # Vite unit tests
49
- ├── netlify.toml # Netlify configuration
47
+ │ ├── e2e/ # End-to-end Playwright tests
48
+ │ └── unit/ # Vite unit tests
49
+ ├── netlify.toml # Netlify configuration
50
50
  └── ...
51
51
  ```
52
52
 
@@ -169,18 +169,18 @@ This project includes custom runtime configuration files for enhancing security,
169
169
 
170
170
  ### 🔐 `hooks.server.js`
171
171
 
172
- Located at src/hooks.server.js, this file is responsible for injecting dynamic security headers. It includes:
172
+ Located at `src/hooks.server.js`, this file is responsible for injecting dynamic security headers. It includes:
173
173
 
174
174
  - Content Security Policy (CSP) with support for relaxed directives (inline scripts allowed)
175
175
  - Permissions Policy to explicitly disable unnecessary browser APIs
176
176
  - X-Content-Type-Options, X-Frame-Options, and Referrer-Policy headers
177
177
 
178
- > 💡 The CSP nonce feature has been disabled. Inline scripts are now allowed through the policy using the "script-src 'self' 'unsafe-inline'" directive. If you wish to use nonces in the future, you can re-enable them by uncommenting the relevant sections in hooks.server.js and modifying your inline <script> tags.
178
+ > 💡 The CSP nonce feature has been disabled. Inline scripts are now allowed through the policy using the `"script-src 'self' 'unsafe-inline'"` directive. If you wish to use nonces in the future, you can re-enable them by uncommenting the relevant sections in `hooks.server.js` and modifying your inline `<script>` tags.
179
179
 
180
180
  To re-enable nonce generation for inline scripts in the future:
181
181
 
182
- 1. Uncomment the nonce generation and injection logic in hooks.server.js.
183
- 2. Add nonce="**cspNonce**" to inline <script> blocks in app.html or route templates.
182
+ 1. Uncomment the nonce generation and injection logic in `hooks.server.js`.
183
+ 2. Add `nonce="**cspNonce**"` to inline `<script>` blocks in `app.html` or route templates.
184
184
 
185
185
  > 💡 The `[headers]` block in `netlify.toml` has been deprecated — all headers are now set dynamically from within SvelteKit.
186
186
 
@@ -246,7 +246,7 @@ This project uses a mix of automated performance, accessibility, and end-to-end
246
246
 
247
247
  ### Running Tests
248
248
 
249
- Local testing via Playwright:
249
+ Local testing via Vitest and Playwright:
250
250
 
251
251
  ```bash
252
252
  npm run test:client # Run client-side unit tests with Vitest
@@ -254,6 +254,7 @@ npm run test:server # Run server-side unit tests with Vitest
254
254
  npm run test:all # Run full test suite
255
255
  npm run test:watch # Watch mode for client tests
256
256
  npm run test:coverage # Collect code coverage reports
257
+ npm run test:e2e # Runs Playwright E2E tests
257
258
  ```
258
259
 
259
260
  Audit your app using Lighthouse:
@@ -406,6 +407,7 @@ The following CLI commands are available via `npm run <script>` or `pnpm run <sc
406
407
  | `test:server` | Run server-side tests with Vitest |
407
408
  | `test:watch` | Watch mode for client tests |
408
409
  | `test:coverage` | Collect coverage from both client and server |
410
+ | `test:e2e` | Run Playwright E2E tests |
409
411
 
410
412
  ---
411
413
 
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "sideEffects": [
5
5
  "./.netlify/shims.js"
6
6
  ],
7
- "version": "1.6.3",
7
+ "version": "1.6.5",
8
8
  "description": "Locking Down Networks, Unlocking Confidence | Security, Networking, Privacy — Network Pro Strategies",
9
9
  "keywords": [
10
10
  "advisory",
@@ -36,7 +36,6 @@
36
36
  "node": ">=22.0.0 <25",
37
37
  "npm": ">=11.0.0 <12"
38
38
  },
39
- "style": "src/lib/styles/index.js",
40
39
  "scripts": {
41
40
  "dev": "vite dev",
42
41
  "start": "npm run dev",
@@ -61,6 +60,7 @@
61
60
  "test:server": "vitest --config vitest.config.server.js",
62
61
  "test:watch": "vitest --config vitest.config.client.js --watch",
63
62
  "test:coverage": "npm run test:client -- --run --coverage && npm run test:server -- --run --coverage",
63
+ "test:e2e": "npx playwright test",
64
64
  "lint": "eslint . --ext .mjs,.js,.svelte",
65
65
  "lint:fix": "eslint . --ext .mjs,.js,.svelte --fix",
66
66
  "lint:jsdoc": "eslint . --ext .js,.mjs,.svelte --max-warnings=0",
@@ -80,7 +80,7 @@
80
80
  "nodemailer": "^7.0.3",
81
81
  "posthog-js": "^1.246.0",
82
82
  "semver": "^7.7.2",
83
- "svelte": "5.33.2"
83
+ "svelte": "5.33.4"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@eslint/compat": "^1.2.9",
@@ -105,7 +105,6 @@
105
105
  "markdownlint": "^0.38.0",
106
106
  "markdownlint-cli2": "^0.18.1",
107
107
  "mdsvex": "^0.12.6",
108
- "normalize.css": "^8.0.1",
109
108
  "playwright": "^1.52.0",
110
109
  "postcss": "^8.5.3",
111
110
  "prettier": "^3.5.3",
@@ -20,10 +20,10 @@ import { bundle } from "lightningcss";
20
20
  import path from "path";
21
21
 
22
22
  // Define the path to your input CSS file
23
- const inputFilePath = path.resolve("src/lib/styles/css/offline.css");
23
+ const inputFilePath = path.resolve("src/lib/styles/css/global.css");
24
24
 
25
25
  // Define the path for the output CSS file
26
- const outputFilePath = path.resolve("static/offline.min.css");
26
+ const outputFilePath = path.resolve("src/lib/styles/css/global.min.css");
27
27
 
28
28
  // Bundle and minify the CSS
29
29
  const { code, map } = bundle({
package/src/app.html CHANGED
@@ -24,7 +24,24 @@
24
24
  sizes="any"
25
25
  type="image/x-icon" />
26
26
 
27
- <!-- LinkedIn preconnects removed 2025-05-26 by SunDevil311 -->
27
+ <!-- Preconnect to PostHog domains -->
28
+ <link
29
+ rel="preconnect"
30
+ href="https://us.i.posthog.com"
31
+ crossorigin="anonymous" />
32
+ <link
33
+ rel="preconnect"
34
+ href="https://us-assets.i.posthog.com"
35
+ crossorigin="anonymous" />
36
+
37
+ <!-- Preload the PostHog script -->
38
+ <link
39
+ rel="preload"
40
+ href="https://us-assets.i.posthog.com/array/phc_Qshfo6AXzh4pS7aPigfqyeo4qj1qlyh7gDuHDeVMSR0/config.js"
41
+ as="script" />
42
+ <script
43
+ src="https://us-assets.i.posthog.com/array/phc_Qshfo6AXzh4pS7aPigfqyeo4qj1qlyh7gDuHDeVMSR0/config.js"
44
+ defer></script>
28
45
 
29
46
  <!-- Preload FontAwesome webfonts -->
30
47
  <link
@@ -14,54 +14,52 @@ export async function handle({ event, resolve }) {
14
14
  // Create the response
15
15
  const response = await resolve(event);
16
16
 
17
- console.log("ENV_MODE:", process.env.ENV_MODE);
18
-
19
- // Check if the environment is for testing
17
+ // Determine environment flags
18
+ // Default to development policy if neither test nor prod
20
19
  const isTestEnvironment =
21
20
  process.env.NODE_ENV === "test" || process.env.ENV_MODE === "ci";
21
+ const isProdEnvironment =
22
+ process.env.NODE_ENV === "production" || process.env.ENV_MODE === "prod";
23
+
24
+ if (!isProdEnvironment) {
25
+ console.log("ENV_MODE:", process.env.ENV_MODE);
26
+ }
27
+
28
+ // Determine report URI
29
+ const reportUri = isProdEnvironment
30
+ ? "/.netlify/functions/cspReport"
31
+ : "/api/mock-csp";
32
+
33
+ // Construct base policy
34
+ const cspDirectives = [
35
+ "default-src 'self';",
36
+ "script-src 'self' 'unsafe-inline' https://us.i.posthog.com https://us-assets.i.posthog.com;",
37
+ "script-src-elem 'self' 'unsafe-inline' https://us.i.posthog.com https://us-assets.i.posthog.com;",
38
+ "style-src 'self' 'unsafe-inline';",
39
+ "img-src 'self' data:;",
40
+ "connect-src 'self' https://us.i.posthog.com https://us-assets.i.posthog.com;",
41
+ "font-src 'self' data:;",
42
+ "form-action 'self';",
43
+ "base-uri 'self';",
44
+ "object-src 'none';",
45
+ "frame-ancestors 'none';",
46
+ "upgrade-insecure-requests;",
47
+ `report-uri ${reportUri};`,
48
+ ];
22
49
 
23
- // Set the Content Security Policy header
50
+ // Loosen up CSP for test environments
24
51
  if (isTestEnvironment) {
25
- // Relaxed CSP for testing: Allow inline scripts and eval
26
- response.headers.set(
27
- "Content-Security-Policy",
28
- [
29
- "default-src 'self';",
30
- "script-src 'self' 'unsafe-inline' 'unsafe-eval' ws://localhost:*;", // Allow inline and eval scripts, and websockets for local testing
31
- "style-src 'self' 'unsafe-inline';", // Allow inline styles
32
- "img-src 'self' data:;", // Allow images from same origin and data URIs
33
- "connect-src 'self';", // Allow connections only to same origin
34
- "font-src 'self' data:;", // Allow fonts from same origin and data URIs
35
- "form-action 'self';", // Allow forms to post to same origin
36
- "base-uri 'self';", // Restrict base URIs to same origin
37
- "object-src 'none';", // Block all object sources
38
- "frame-ancestors 'none';", // Prevent framing of the site
39
- "upgrade-insecure-requests;", // Automatically upgrade HTTP to HTTPS
40
- "report-uri /api/mock-csp;", // Mock CSP reports for testing
41
- ].join(" "),
42
- );
43
- } else {
44
- // Production or development environment: use a more restrictive CSP
45
- response.headers.set(
46
- "Content-Security-Policy",
47
- [
48
- "default-src 'self';",
49
- "script-src 'self' 'unsafe-inline' https://us.i.posthog.com https://us-assets.i.posthog.com;", // Allow PostHog's script from both sources
50
- "script-src-elem 'self' 'unsafe-inline' https://us.i.posthog.com https://us-assets.i.posthog.com;", // Allow inline scripts from PostHog's sources
51
- "style-src 'self' 'unsafe-inline';",
52
- "img-src 'self' data:;",
53
- "connect-src 'self' https://us.i.posthog.com https://us-assets.i.posthog.com;", // Allow connections to both PostHog sources
54
- "font-src 'self' data:;",
55
- "form-action 'self';",
56
- "base-uri 'self';",
57
- "object-src 'none';",
58
- "frame-ancestors 'none';",
59
- "upgrade-insecure-requests;",
60
- `report-uri ${process.env.ENV_MODE === "prod" ? "/.netlify/functions/cspReport" : "/api/mock-csp"};`,
61
- ].join(" "),
62
- );
52
+ cspDirectives[1] =
53
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval' ws://localhost:*;";
54
+ cspDirectives[2] =
55
+ "script-src-elem 'self' 'unsafe-inline' 'unsafe-eval' ws://localhost:*;";
56
+ cspDirectives[3] = "style-src 'self' 'unsafe-inline';";
57
+ cspDirectives[4] = "img-src 'self' data:;";
58
+ cspDirectives[5] = "connect-src 'self';";
63
59
  }
64
60
 
61
+ response.headers.set("Content-Security-Policy", cspDirectives.join(" "));
62
+
65
63
  // Set other security headers
66
64
  response.headers.set(
67
65
  "Permissions-Policy",
@@ -79,8 +77,7 @@ export async function handle({ event, resolve }) {
79
77
  "gamepad=()",
80
78
  "serial=()",
81
79
  "publickey-credentials-get=()",
82
- "interest-cohort=()",
83
- "topics=()",
80
+ "browsing-topics=()",
84
81
  ].join(", "),
85
82
  );
86
83
 
@@ -91,7 +88,7 @@ export async function handle({ event, resolve }) {
91
88
  if (process.env.ENV_MODE !== "test" && process.env.ENV_MODE !== "ci") {
92
89
  response.headers.set(
93
90
  "Strict-Transport-Security",
94
- "max-age=2592000; includeSubDomains;", // Use max-age of 30 days
91
+ "max-age=31536000; includeSubDomains;", // No preload here
95
92
  );
96
93
  }
97
94
 
Binary file
Binary file
@@ -56,7 +56,7 @@ This file is part of Network Pro.
56
56
  text: "CC-BY-4.0 OR GPL-3.0-or-later",
57
57
  },
58
58
  {
59
- label: "Markdown",
59
+ label: "Docs",
60
60
  href: "https://docs.netwk.pro",
61
61
  target: "_self",
62
62
  },
@@ -103,9 +103,8 @@ This file is part of Network Pro.
103
103
  <section id="top">
104
104
  <span class="small-text">
105
105
  <a {rel} href={navLinks[0].href} target={navLinks[0].target}>
106
- {navLinks[0].label}
107
- </a>
108
- :&nbsp;<code>{navLinks[0].text}</code>
106
+ {navLinks[0].label}</a
107
+ >: &nbsp;<code>{navLinks[0].text}</code>
109
108
  </span>
110
109
  </section>
111
110
 
@@ -193,7 +192,7 @@ This file is part of Network Pro.
193
192
  <div class="spacer"></div>
194
193
 
195
194
  <p>
196
- <a {rel} href="https://contact.neteng.pro" target="_blank">Let's connect</a>
195
+ <a {rel} href="https://netwk.pro/contact" target="_blank">Let's connect</a>
197
196
  to discuss how we can help secure and strengthen your business today.
198
197
  </p>
199
198
 
@@ -90,7 +90,7 @@ This file is part of Network Pro.
90
90
 
91
91
  &nbsp;
92
92
 
93
- <h3 class={classIndex}>Featured</h3>
93
+ <h3 class="index4">Featured</h3>
94
94
 
95
95
  <p class={classLarge}>
96
96
  <strong>
@@ -79,8 +79,7 @@ This file is part of Network Pro.
79
79
  href="https://spdx.dev/learn/handling-license-info"
80
80
  target={constants.targetBlank}>
81
81
  SPDX License Identifier
82
- </a>
83
- : &nbsp;<code>CC-BY-4.0 OR GPL-3.0-or-later</code>
82
+ </a>: &nbsp;<code>CC-BY-4.0 OR GPL-3.0-or-later</code>
84
83
  </span>
85
84
  </section>
86
85
 
@@ -114,7 +113,7 @@ This file is part of Network Pro.
114
113
  <strong>Formats Available:</strong> &nbsp;<span class="visited"
115
114
  >HTML</span>
116
115
  |
117
- <a href={legalLink} target={constants.targetSelf}>Markdown</a>
116
+ <a href={legalLink} target={constants.targetSelf}>Docs</a>
118
117
  </sup>
119
118
  </p>
120
119
  </section>
@@ -35,6 +35,7 @@ This file is part of Network Pro.
35
35
  const tocLinks = [
36
36
  { id: "intro", text: "Introduction" },
37
37
  { id: "collect", text: "Information We Collect" },
38
+ { id: "tracking", text: "Web Analytics and Tracking" },
38
39
  { id: "payment", text: "Payment Information" },
39
40
  { id: "use", text: "Use of Information" },
40
41
  { id: "sharing", text: "Data Sharing" },
@@ -87,8 +88,7 @@ This file is part of Network Pro.
87
88
  href="https://spdx.dev/learn/handling-license-info"
88
89
  target={constants.targetBlank}>
89
90
  SPDX License Identifier
90
- </a>
91
- : &nbsp;<code>CC-BY-4.0 OR GPL-3.0-or-later</code>
91
+ </a>: &nbsp;<code>CC-BY-4.0 OR GPL-3.0-or-later</code>
92
92
  </span>
93
93
  </section>
94
94
 
@@ -122,7 +122,7 @@ This file is part of Network Pro.
122
122
  <strong>Formats Available:</strong> &nbsp;<span class="visited"
123
123
  >HTML</span>
124
124
  |
125
- <a href={privacyLink} target={constants.targetSelf}> Markdown </a>
125
+ <a href={privacyLink} target={constants.targetSelf}>Docs</a>
126
126
  </sup>
127
127
  </p>
128
128
  </section>
@@ -161,6 +161,39 @@ This file is part of Network Pro.
161
161
  <li
162
162
  ><strong>Client-Submitted Content</strong> related to our services</li>
163
163
  </ul>
164
+ {:else if link.id === "tracking"}
165
+ <p>
166
+ To better understand visitor behavior and optimize website
167
+ functionality, we use <strong>PostHog Cloud</strong>, a hosted version
168
+ of the open-source PostHog analytics platform. This tool helps us
169
+ evaluate site performance and user engagement through the collection of
170
+ non-personally identifiable technical data.
171
+ </p>
172
+ <p> PostHog Cloud may collect and process information such as: </p>
173
+ <ul>
174
+ <li>Pages visited and navigation behavior</li>
175
+ <li>Device type, browser version, and operating system</li>
176
+ <li
177
+ >Time spent on pages and interaction events (e.g., clicks, scrolls)</li>
178
+ <li>Referral URLs and outbound link activity</li>
179
+ <li>General geolocation (approximate, based on IP address)</li>
180
+ </ul>
181
+ <p>
182
+ We configure PostHog to prioritize user privacy. <strong
183
+ >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.
187
+ </p>
188
+ <p>
189
+ PostHog Cloud is a third-party service, but we deploy it in a
190
+ privacy-conscious manner that avoids intrusive profiling and aligns with
191
+ data protection best practices. For more information, please refer to <a
192
+ rel={constants.rel}
193
+ href="https://posthog.com/privacy"
194
+ target={constants.targetBlank}>PostHog's Privacy Policy</a
195
+ >.
196
+ </p>
164
197
  {:else if link.id === "payment"}
165
198
  <p>
166
199
  When processing payments, we may collect credit card details and billing
@@ -112,7 +112,7 @@ This file is part of Network Pro.
112
112
  <strong>Formats Available:</strong> &nbsp;<span class="visited"
113
113
  >HTML</span>
114
114
  |
115
- <a href={tandcLink} target={constants.targetSelf}>Markdown</a>
115
+ <a href={tandcLink} target={constants.targetSelf}>Docs</a>
116
116
  |
117
117
  <a
118
118
  rel={constants.rel}
@@ -78,8 +78,7 @@ This file is part of Network Pro.
78
78
  href="https://spdx.dev/learn/handling-license-info"
79
79
  target={constants.targetBlank}>
80
80
  SPDX License Identifier
81
- </a>
82
- : &nbsp;<code>CC-BY-4.0 OR GPL-3.0-or-later</code>
81
+ </a>: &nbsp;<code>CC-BY-4.0 OR GPL-3.0-or-later</code>
83
82
  </span>
84
83
  </section>
85
84
 
@@ -122,7 +121,7 @@ This file is part of Network Pro.
122
121
  >HTML</span>
123
122
  |
124
123
  <a rel={constants.rel} href={termsLink} target={constants.targetSelf}
125
- >Markdown</a>
124
+ >Docs</a>
126
125
  </sup>
127
126
  </p>
128
127
  </section>
@@ -1,4 +1,4 @@
1
- /*! ==========================================================================
1
+ /* ==========================================================================
2
2
  src/lib/styles/default.css
3
3
 
4
4
  Copyright © 2025 Network Pro Strategies (Network Pro™)
@@ -362,6 +362,14 @@ footer .container {
362
362
  text-align: center;
363
363
  }
364
364
 
365
+ .index4 {
366
+ font-size: 1.5rem;
367
+ line-height: 1.75rem;
368
+ letter-spacing: -0.035em;
369
+ text-align: center;
370
+ text-decoration: underline;
371
+ }
372
+
365
373
  .subhead {
366
374
  font-size: 1.5rem;
367
375
  line-height: 1.75rem;
@@ -1,10 +1,10 @@
1
- /*! ==========================================================================
1
+ /* ==========================================================================
2
2
  src/lib/styles/normalize.css
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
6
6
  This file is part of Network Pro.
7
- =========================================================================== */
7
+ ========================================================================== */
8
8
 
9
9
  /*! Modified from normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
10
10
 
@@ -1,10 +1,10 @@
1
- /*! ==========================================================================
1
+ /* ==========================================================================
2
2
  src/lib/styles/style.css
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
6
6
  This file is part of Network Pro.
7
- =========================================================================== */
7
+ ========================================================================== */
8
8
 
9
9
  /*! Modified from HTML5 Boilerplate v9.0.1 | MIT License | https://html5boilerplate.com/ */
10
10
 
@@ -1,10 +1,8 @@
1
1
  /*! ==========================================================================
2
- src/lib/styles/global.min.css
2
+ src/lib/styles/css/global.min.css
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
6
6
  This file is part of Network Pro.
7
- =========================================================================== */
8
- /*! Modified from normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css
9
- /*! Modified from HTML5 Boilerplate v9.0.1 | MIT License | https://html5boilerplate.com/ */
10
- 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}.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}
7
+ ========================================================================== */
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}
@@ -0,0 +1,23 @@
1
+ /* ==========================================================================
2
+ src/lib/utils/privacy.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
+ * Determines whether the user allows tracking based on DNT or GPC signals.
11
+ * @returns {boolean}
12
+ */
13
+ export function shouldTrackUser() {
14
+ /** @type {string | undefined} */
15
+ const windowDNT = /** @type {any} */ (window).doNotTrack;
16
+ /** @type {boolean | undefined} */
17
+ const navigatorGPC = /** @type {any} */ (navigator).globalPrivacyControl;
18
+
19
+ const dnt = navigator.doNotTrack === "1" || windowDNT === "1";
20
+ const gpc = navigatorGPC === true;
21
+
22
+ return !dnt && !gpc;
23
+ }
@@ -14,10 +14,12 @@ This file is part of Network Pro.
14
14
  import HeaderDefault from "$lib/components/layout/HeaderDefault.svelte";
15
15
  import HeaderHome from "$lib/components/layout/HeaderHome.svelte";
16
16
  import PWAInstallButton from "$lib/components/PWAInstallButton.svelte";
17
+ import { shouldTrackUser } from "$lib/utils/privacy.js";
17
18
  import { onMount } from "svelte";
18
19
  import { registerServiceWorker } from "$lib/registerServiceWorker.js";
19
20
  import { browser } from "$app/environment";
20
- import "$lib/styles";
21
+ import "$lib/styles/global.min.css";
22
+ import "$lib/styles/fa-global.css";
21
23
 
22
24
  // Import favicon images
23
25
  import logoPng from "$lib/img/logo-web.png";
@@ -30,25 +32,36 @@ This file is part of Network Pro.
30
32
  let PostHog = null;
31
33
 
32
34
  if (browser) {
33
- // Preload logo images
34
- [logoPng, logoWbp].forEach((src) => {
35
+ // Preload all core images (logos + apple touch)
36
+ [logoPng, logoWbp, appleTouchIcon].forEach((src) => {
35
37
  const img = new Image();
36
38
  img.src = src;
37
39
  });
38
40
 
39
- // Preload Apple Touch icon
40
- const touchImg = new Image();
41
- touchImg.src = appleTouchIcon;
42
-
43
- // Register the service worker only in the browser
41
+ // Run setup when component mounts (only in browser)
44
42
  onMount(() => {
45
43
  console.log("[APP] onMount triggered in +layout.svelte");
46
44
  registerServiceWorker();
47
45
 
48
- // Dynamically import PostHog component only in the browser
49
- import("$lib/components/PostHog.svelte").then((module) => {
50
- PostHog = module.default;
51
- });
46
+ const isDev = import.meta.env.MODE === "development";
47
+
48
+ console.log("ENV MODE =", import.meta.env.MODE); // Should be "development"
49
+ console.log("isDev =", isDev);
50
+ console.log("shouldTrackUser =", shouldTrackUser());
51
+
52
+ if (isDev || shouldTrackUser()) {
53
+ import("$lib/components/PostHog.svelte").then((module) => {
54
+ PostHog = module.default;
55
+
56
+ if (isDev) {
57
+ console.log("[Dev] ✅ PostHog component loaded (tracking enabled)");
58
+ }
59
+ });
60
+ } else {
61
+ console.log(
62
+ "[Privacy] ⛔ Skipping PostHog component due to DNT or GPC signal.",
63
+ );
64
+ }
52
65
  });
53
66
  }
54
67