@networkpro/web 1.10.0 → 1.11.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.
@@ -15,9 +15,9 @@ This file is part of Network Pro.
15
15
  // Log the base path to verify its value
16
16
  //console.log("Base path:", base);
17
17
 
18
- console.log(CONSTANTS.APP_NAME);
18
+ console.log(CONSTANTS.COMPANY_INFO.APP_NAME);
19
19
 
20
- const { COMPANY } = CONSTANTS;
20
+ const { COMPANY_INFO, PAGE, NAV } = CONSTANTS;
21
21
 
22
22
  /**
23
23
  * URL to Terms and Conditions page, using the base path
@@ -62,15 +62,11 @@ This file is part of Network Pro.
62
62
 
63
63
  /**
64
64
  * Constants for reusable content
65
- * @type {{ effectiveDate: string, classSmall: string, rel: string, hrefTop: string, targetBlank: string, targetSelf: string }}
65
+ * @type {{ effectiveDate: string, classSmall: string }}
66
66
  */
67
67
  const constants = {
68
68
  effectiveDate: "May 21, 2025",
69
69
  classSmall: "small-text",
70
- rel: "noopener noreferrer",
71
- hrefTop: "#top",
72
- targetBlank: "_blank",
73
- targetSelf: "_self",
74
70
  };
75
71
  </script>
76
72
 
@@ -78,9 +74,9 @@ This file is part of Network Pro.
78
74
  <section id="top">
79
75
  <span class={constants.classSmall}>
80
76
  <a
81
- rel={constants.rel}
77
+ rel={PAGE.REL}
82
78
  href="https://spdx.dev/learn/handling-license-info"
83
- target={constants.targetBlank}>
79
+ target={PAGE.BLANK}>
84
80
  SPDX License Identifier
85
81
  </a>: &nbsp;<code>CC-BY-4.0 OR GPL-3.0-or-later</code>
86
82
  </span>
@@ -89,7 +85,7 @@ This file is part of Network Pro.
89
85
  <section id="page-title">
90
86
  <h1>Website Terms of Use</h1>
91
87
  <p>
92
- <strong>{COMPANY}</strong><br />
88
+ <strong>{COMPANY_INFO.NAME}</strong><br />
93
89
  <strong>Effective Date:</strong>
94
90
  {constants.effectiveDate}
95
91
  </p>
@@ -113,7 +109,7 @@ This file is part of Network Pro.
113
109
  available via this website and its associated web properties. For provisions
114
110
  governing our consulting and implementation services, please refer to the
115
111
  applicable
116
- <a href={tandcLink} target={constants.targetSelf}>Terms and Conditions</a>.
112
+ <a href={tandcLink} target={PAGE.SELF}>Terms and Conditions</a>.
117
113
  </p>
118
114
 
119
115
  <hr />
@@ -124,8 +120,7 @@ This file is part of Network Pro.
124
120
  <strong>Formats Available:</strong> &nbsp;<span class="visited"
125
121
  >HTML</span>
126
122
  |
127
- <a rel={constants.rel} href={termsLink} target={constants.targetSelf}
128
- >Docs</a>
123
+ <a rel={PAGE.REL} href={termsLink} target={PAGE.SELF}>Docs</a>
129
124
  </sup>
130
125
  </p>
131
126
  </section>
@@ -137,7 +132,7 @@ This file is part of Network Pro.
137
132
 
138
133
  {#if link.id === "introduction"}
139
134
  <p>
140
- Welcome! By accessing or using any of the platforms operated by {COMPANY}
135
+ Welcome! By accessing or using any of the platforms operated by {COMPANY_INFO.NAME}
141
136
  ("Company," "we," "us," or "our"), you agree to be bound by these Terms of
142
137
  Use ("Terms"). If you do not agree to these Terms, please refrain from using
143
138
  our services.
@@ -202,10 +197,10 @@ This file is part of Network Pro.
202
197
  </p>
203
198
  {:else if link.id === "jurisdiction"}
204
199
  <p>
205
- {COMPANY} is based in Maricopa County, Arizona. Any legal action or dispute
206
- arising from these Terms of Use shall be subject to the exclusive jurisdiction
207
- of the state and federal courts located in Maricopa County, Arizona. These
208
- Terms shall be governed by the
200
+ {COMPANY_INFO.NAME} is based in Maricopa County, Arizona. Any legal action
201
+ or dispute arising from these Terms of Use shall be subject to the exclusive
202
+ jurisdiction of the state and federal courts located in Maricopa County,
203
+ Arizona. These Terms shall be governed by the
209
204
  <strong>Arizona Revised Statutes (A.R.S.)</strong> and applicable
210
205
  provisions of the <strong>United States Code (U.S.C.)</strong>.
211
206
  </p>
@@ -224,7 +219,7 @@ This file is part of Network Pro.
224
219
  {/if}
225
220
 
226
221
  <span class={constants.classSmall}
227
- ><a href={constants.hrefTop}>Back to top</a></span>
222
+ ><a href={NAV.HREFTOP}>{NAV.BACKTOP}</a></span>
228
223
  </section>
229
224
  {/each}
230
225
 
@@ -232,10 +227,9 @@ This file is part of Network Pro.
232
227
  <p class="bquote">
233
228
  <strong>Note:</strong> For more details regarding our privacy practices,
234
229
  refer to our
235
- <a rel={constants.rel} href={privacyLink} target={constants.targetSelf}
236
- >Privacy Policy</a
237
- >. For licensing terms and content usage rights, please visit our
238
- <a rel={constants.rel} href={licenseLink} target={constants.targetSelf}
230
+ <a rel={PAGE.REL} href={privacyLink} target={PAGE.SELF}>Privacy Policy</a>.
231
+ For licensing terms and content usage rights, please visit our
232
+ <a rel={PAGE.REL} href={licenseLink} target={PAGE.SELF}
239
233
  >Legal, Copyright, and Licensing</a>
240
234
  page.
241
235
  </p>
@@ -0,0 +1,115 @@
1
+ /* ==========================================================================
2
+ src/lib/stores/posthog.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
+ ========================================================================== */
7
+
8
+ /**
9
+ * @file posthog.js
10
+ * @description Privacy-aware PostHog tracking store with reactive state and safe API surface.
11
+ * @module src/lib/stores
12
+ */
13
+
14
+ import {
15
+ shouldRemindUserToReconsent,
16
+ shouldTrackUser,
17
+ } from "$lib/utils/privacy.js";
18
+ import { get, writable } from "svelte/store";
19
+
20
+ /**
21
+ * Tracks whether tracking is allowed based on cookies, DNT/GPC, and user preference.
22
+ * @type {import("svelte/store").Writable<boolean>}
23
+ */
24
+ export const trackingEnabled = writable(false);
25
+
26
+ /**
27
+ * Determines if the user should be reminded to re-consent (after 6 months).
28
+ * @type {import("svelte/store").Writable<boolean>}
29
+ */
30
+ export const showReminder = writable(false);
31
+
32
+ /** @type {boolean} Internal one-time init guard */
33
+ let initialized = false;
34
+
35
+ /** @type {import("posthog-js").PostHog | null} Loaded PostHog instance */
36
+ let ph = null;
37
+
38
+ /**
39
+ * Initializes the PostHog analytics client if tracking is permitted.
40
+ * Uses dynamic import to avoid SSR failures.
41
+ * @returns {Promise<void>}
42
+ */
43
+ export async function initPostHog() {
44
+ if (initialized || typeof window === "undefined") return;
45
+ initialized = true;
46
+
47
+ const allowTracking = shouldTrackUser();
48
+ trackingEnabled.set(allowTracking);
49
+ showReminder.set(shouldRemindUserToReconsent());
50
+
51
+ if (!allowTracking) {
52
+ console.log("[PostHog] Tracking is disabled — skipping init.");
53
+ return;
54
+ }
55
+
56
+ const posthogModule = await import("posthog-js");
57
+ ph = posthogModule.default;
58
+
59
+ // cspell:disable-next-line
60
+ ph.init("phc_Qshfo6AXzh4pS7aPigfqyeo4qj1qlyh7gDuHDeVMSR0", {
61
+ api_host: "https://us.i.posthog.com",
62
+ autocapture: true,
63
+ capture_pageview: false,
64
+ person_profiles: "identified_only",
65
+ loaded: (phInstance) => {
66
+ if (!allowTracking) {
67
+ console.log(
68
+ "[PostHog] ⛔ User opted out — calling opt_out_capturing()",
69
+ );
70
+ phInstance.opt_out_capturing();
71
+ } else {
72
+ console.log("[PostHog] ✅ Tracking enabled");
73
+ }
74
+ },
75
+ });
76
+
77
+ ph.capture("$pageview");
78
+ }
79
+
80
+ /**
81
+ * Conditionally captures an event if tracking is enabled.
82
+ * @param {string} event - The event name to track
83
+ * @param {Record<string, any>} [properties={}] - Optional event properties
84
+ */
85
+ export function capture(event, properties = {}) {
86
+ if (ph !== null && get(trackingEnabled)) {
87
+ ph.capture(event, properties);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Conditionally identifies a user if tracking is enabled.
93
+ * @param {string} id - Unique user identifier
94
+ * @param {Record<string, any>} [properties={}] - Optional user traits
95
+ */
96
+ export function identify(id, properties = {}) {
97
+ if (ph !== null && get(trackingEnabled)) {
98
+ ph.identify(id, properties);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * For test cleanup only — resets internal state.
104
+ * No-op in production.
105
+ * @returns {void}
106
+ */
107
+ export function _resetPostHog() {
108
+ if (import.meta.env.MODE === "production") {
109
+ console.warn("[PostHog] _resetPostHog() called in production — no-op");
110
+ return;
111
+ }
112
+
113
+ initialized = false;
114
+ ph = null;
115
+ }
@@ -0,0 +1,30 @@
1
+ /* ==========================================================================
2
+ src/lib/stores/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
+ ========================================================================== */
7
+
8
+ /**
9
+ * @file trackingStatus.js
10
+ * @description Tracks state of PostHog tracking status for instant updates
11
+ * in Privacy Policy and Privacy Dashboard.
12
+ * @module src/lib/stores
13
+ */
14
+
15
+ import { writable } from "svelte/store";
16
+
17
+ /**
18
+ * Writable tracking status store.
19
+ * Initialized with fallback value and updated in browser context.
20
+ * @type {import("svelte/store").Writable<string>}
21
+ */
22
+ export const trackingStatus = writable("⏳ Checking tracking preferences...");
23
+
24
+ // Dynamically import browser-only logic after mount
25
+ if (typeof window !== "undefined") {
26
+ import("$lib/utils/trackingStatus.js").then(({ getTrackingPreferences }) => {
27
+ const prefs = getTrackingPreferences();
28
+ trackingStatus.set(prefs.status);
29
+ });
30
+ }
@@ -0,0 +1,60 @@
1
+ /* ==========================================================================
2
+ src/lib/types/appConstants.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 appConstants.js
11
+ * @description Type definitions for app constants in src/lib/index.js
12
+ * @module src/lib/types
13
+ * @author SunDevil311
14
+ * @updated 2025-06-03
15
+ */
16
+
17
+ /**
18
+ * @typedef {object} CompanyInfo
19
+ * @property {string} NAME - Full company name
20
+ * @property {string} APP_NAME - Application name
21
+ * @property {string} YEAR - Current copyright year
22
+ */
23
+
24
+ /**
25
+ * @typedef {object} ContactInfo
26
+ * @property {string} EMAIL - Primary contact email
27
+ * @property {string} SECURE - Secure contact email
28
+ * @property {string} PRIVACY - Privacy policy email
29
+ * @property {string} PHONE - Support phone number
30
+ */
31
+
32
+ /**
33
+ * @typedef {object} PageTargets
34
+ * @property {string} BLANK - Value for `target="_blank"`
35
+ * @property {string} SELF - Value for `target="_self"`
36
+ * @property {string} REL - Value for `rel="noopener noreferrer"`
37
+ */
38
+
39
+ /**
40
+ * @typedef {object} NavigationLabels
41
+ * @property {string} BACKTOP
42
+ * @property {string} HREFTOP
43
+ */
44
+
45
+ /**
46
+ * @typedef {object} Links
47
+ * @property {string} HOME - Main website URL
48
+ * @property {string} BLOG - External blog URL
49
+ */
50
+
51
+ /**
52
+ * @typedef {object} AppConstants
53
+ * @property {CompanyInfo} COMPANY_INFO
54
+ * @property {ContactInfo} CONTACT
55
+ * @property {PageTargets} PAGE
56
+ * @property {NavigationLabels} NAV
57
+ * @property {Links} LINKS
58
+ */
59
+
60
+ export {};
@@ -1,3 +1,20 @@
1
+ /* ==========================================================================
2
+ src/lib/types/fossTypes.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 fossTypes.js
11
+ * @description Type definitions for `fossItem` in
12
+ * src/lib/components/foss/FossItemContent.svelte
13
+ * @module src/lib/types
14
+ * @author SunDevil311
15
+ * @updated 2025-06-03
16
+ */
17
+
1
18
  /**
2
19
  * @typedef {object} FossLink
3
20
  * @property {string} [label]
@@ -11,28 +11,71 @@ This file is part of Network Pro.
11
11
  * @description Determines whether the user allows tracking based on DNT, GPC, or manual opt-out.
12
12
  * @module src/lib/utils/
13
13
  * @author SunDevil311
14
- * @updated 2025-05-28
14
+ * @updated 2025-06-03
15
15
  */
16
16
 
17
17
  /**
18
+ * Check if user has manually set tracking preference.
19
+ * @returns {boolean}
20
+ */
21
+ export function hasUserManuallySetTrackingPreference() {
22
+ if (typeof document === "undefined") return false;
23
+
24
+ const cookies = document.cookie;
25
+ return (
26
+ cookies.includes("enable_tracking=true") ||
27
+ cookies.includes("disable_tracking=true")
28
+ );
29
+ }
30
+
31
+ /**
32
+ * Determine if the user allows tracking based on cookies, DNT, and GPC.
18
33
  * @returns {boolean}
19
34
  */
20
35
  export function shouldTrackUser() {
36
+ if (
37
+ typeof window === "undefined" ||
38
+ typeof navigator === "undefined" ||
39
+ typeof document === "undefined"
40
+ ) {
41
+ return false;
42
+ }
43
+
44
+ const cookies = document.cookie;
21
45
  const windowDNT = /** @type {any} */ (window).doNotTrack;
22
46
  const navigatorGPC = /** @type {any} */ (navigator).globalPrivacyControl;
23
47
 
24
48
  const dnt = navigator.doNotTrack === "1" || windowDNT === "1";
25
49
  const gpc = navigatorGPC === true;
26
50
 
27
- const manualOptOut = document.cookie.includes("disable_tracking=true");
28
- const manualOptIn = document.cookie.includes("enable_tracking=true");
51
+ const manualOptOut = cookies.includes("disable_tracking=true");
52
+ const manualOptIn = cookies.includes("enable_tracking=true");
29
53
 
30
54
  console.log("[Privacy] Opt-in cookie present:", manualOptIn);
31
55
  console.log("[Privacy] Opt-out cookie present:", manualOptOut);
32
56
 
33
- // Opt-in overrides everything; opt-out disables regardless of DNT/GPC
34
57
  if (manualOptIn) return true;
35
58
  if (manualOptOut) return false;
36
59
 
37
60
  return !dnt && !gpc;
38
61
  }
62
+
63
+ /**
64
+ * Determines if user should be reminded to reconsent (after 6 months).
65
+ * @returns {boolean}
66
+ */
67
+ export function shouldRemindUserToReconsent() {
68
+ if (typeof document === "undefined") return false;
69
+
70
+ if (!hasUserManuallySetTrackingPreference()) return false;
71
+
72
+ const match = document.cookie.match(/tracking_consent_timestamp=(\d+)/);
73
+ if (!match) return true;
74
+
75
+ const timestamp = Number(match[1]);
76
+ if (isNaN(timestamp)) return true;
77
+
78
+ const age = Date.now() - timestamp;
79
+
80
+ return age > 1000 * 60 * 60 * 24 * 180; // 6 months
81
+ }
@@ -11,30 +11,60 @@ This file is part of Network Pro.
11
11
  * @description Handles setting, clearing, and toggling tracking preference cookies.
12
12
  * @module src/lib/utils/
13
13
  * @author SunDevil311
14
- * @updated 2025-05-28
14
+ * @updated 2025-06-04
15
15
  */
16
16
 
17
+ // 6 months (in seconds). Will be centralized later.
18
+ const DEFAULT_COOKIE_MAX_AGE = 60 * 60 * 24 * 180;
19
+
20
+ /**
21
+ * Builds a standard cookie string for use in all tracking cookies.
22
+ * @param {number} maxAge
23
+ * @returns {string}
24
+ */
25
+ function buildCookieSettings(maxAge) {
26
+ return `path=/; max-age=${maxAge}; expires=${new Date(Date.now() + maxAge * 1000).toUTCString()}; SameSite=Lax; Secure`;
27
+ }
28
+
17
29
  /**
18
- * Set a tracking preference cookie.
30
+ * Sets tracking preference cookies based on type.
19
31
  * @param {"enable" | "disable"} type
32
+ * @param {number} [maxAge=DEFAULT_COOKIE_MAX_AGE]
20
33
  */
21
- export function setTrackingPreference(type) {
22
- const maxAge = 60 * 60 * 24 * 365 * 10; // 10 years
23
- const cookieSettings = `path=/; max-age=${maxAge}; SameSite=Lax`;
34
+ export function setTrackingPreference(type, maxAge = DEFAULT_COOKIE_MAX_AGE) {
35
+ if (typeof document === "undefined") return; // SSR guard
36
+
37
+ const cookieSettings = buildCookieSettings(maxAge);
38
+ const now = Date.now();
24
39
 
25
40
  if (type === "enable") {
26
41
  document.cookie = `enable_tracking=true; ${cookieSettings}`;
27
- document.cookie = `disable_tracking=; path=/; max-age=0; SameSite=Lax`;
42
+ document.cookie = `tracking_consent_timestamp=${now}; ${cookieSettings}`;
43
+ clearCookie("disable_tracking");
28
44
  } else if (type === "disable") {
29
45
  document.cookie = `disable_tracking=true; ${cookieSettings}`;
30
- document.cookie = `enable_tracking=; path=/; max-age=0; SameSite=Lax`;
46
+ document.cookie = `tracking_consent_timestamp=${now}; ${cookieSettings}`;
47
+ clearCookie("enable_tracking");
31
48
  }
32
49
  }
33
50
 
34
51
  /**
35
- * Clear both tracking cookies.
52
+ * Clears all tracking-related cookies.
36
53
  */
37
54
  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`;
55
+ if (typeof document === "undefined") return; // SSR guard
56
+
57
+ clearCookie("enable_tracking");
58
+ clearCookie("disable_tracking");
59
+ clearCookie("tracking_consent_timestamp");
60
+ }
61
+
62
+ /**
63
+ * Clears an individual cookie.
64
+ * @param {string} name
65
+ */
66
+ function clearCookie(name) {
67
+ if (typeof document === "undefined") return; // SSR guard
68
+
69
+ document.cookie = `${name}=; path=/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax; Secure`;
40
70
  }
@@ -9,12 +9,16 @@ This file is part of Network Pro.
9
9
  /**
10
10
  * @file trackingStatus.js
11
11
  * @description Get tracking preferences based on cookies and browser privacy signals.
12
- * @module src/lib/utils/
12
+ * @module src/lib/utils
13
13
  * @author SunDevil311
14
14
  * @updated 2025-05-28
15
15
  */
16
16
 
17
+ import { browser } from "$app/environment";
18
+
17
19
  /**
20
+ * Gets the current tracking preferences based on browser cookies and signals.
21
+ *
18
22
  * @returns {{
19
23
  * optedOut: boolean,
20
24
  * optedIn: boolean,
@@ -24,12 +28,23 @@ This file is part of Network Pro.
24
28
  * }}
25
29
  */
26
30
  export function getTrackingPreferences() {
31
+ // Prevent errors during SSR (no document or navigator)
32
+ if (!browser) {
33
+ return {
34
+ optedOut: false,
35
+ optedIn: false,
36
+ dnt: false,
37
+ gpc: false,
38
+ status: "⏳ Checking tracking preferences...",
39
+ };
40
+ }
41
+
27
42
  const cookies = document.cookie;
28
43
  const optedOut = cookies.includes("disable_tracking=true");
29
44
  const optedIn = cookies.includes("enable_tracking=true");
30
- const dnt = navigator.doNotTrack === "1";
31
45
 
32
- // @ts-expect-error: 'globalPrivacyControl' is a non-standard property
46
+ const dnt = navigator.doNotTrack === "1";
47
+ // @ts-expect-error: 'globalPrivacyControl' is non-standard
33
48
  const gpc = navigator.globalPrivacyControl === true;
34
49
 
35
50
  let status = "⚙️ Using default settings (tracking enabled)";
@@ -42,5 +42,3 @@ export function load({ url }) {
42
42
  meta: currentMeta, // Return the meta data (either from the route or the fallback)
43
43
  };
44
44
  }
45
-
46
- // cspell:ignore posthog
@@ -9,61 +9,61 @@ This file is part of Network Pro.
9
9
  <script>
10
10
  export let data;
11
11
 
12
+ import { onMount } from "svelte";
13
+ import { afterNavigate } from "$app/navigation";
14
+ import { initPostHog, showReminder, capture } from "$lib/stores/posthog";
15
+ import { registerServiceWorker } from "$lib/registerServiceWorker.js";
16
+ import { browser } from "$app/environment";
17
+ import { shouldTrackUser } from "$lib/utils/privacy.js";
18
+
12
19
  import ContainerSection from "$lib/components/ContainerSection.svelte";
13
20
  import Footer from "$lib/components/layout/Footer.svelte";
14
21
  import HeaderDefault from "$lib/components/layout/HeaderDefault.svelte";
15
22
  import HeaderHome from "$lib/components/layout/HeaderHome.svelte";
16
23
  import PWAInstallButton from "$lib/components/PWAInstallButton.svelte";
17
- import { shouldTrackUser } from "$lib/utils/privacy.js";
18
- import { onMount } from "svelte";
19
- import { registerServiceWorker } from "$lib/registerServiceWorker.js";
20
- import { browser } from "$app/environment";
24
+
21
25
  import "$lib/styles/global.min.css";
22
26
  import "$lib/styles/fa-global.css";
23
27
 
24
- // Import favicon images
25
28
  import logoPng from "$lib/img/logo-web.png";
26
29
  import logoWbp from "$lib/img/logo-web.webp";
27
30
  import faviconSvg from "$lib/img/favicon.svg";
28
31
  import appleTouchIcon from "$lib/img/icon-180x180.png";
29
32
 
30
- // Declare PostHog as null initially
31
- /** @type {typeof import('$lib/components/PostHog.svelte').default | null} */
32
- let PostHog = null;
33
+ $: shouldShowReminder = $showReminder;
33
34
 
34
- if (browser) {
35
- // Preload all core images (logos + apple touch)
36
- [logoPng, logoWbp, appleTouchIcon].forEach((src) => {
37
- const img = new Image();
38
- img.src = src;
39
- });
35
+ onMount(() => {
36
+ console.log("[APP] onMount triggered in +layout.svelte");
40
37
 
41
- // Run setup when component mounts (only in browser)
42
- onMount(() => {
43
- console.log("[APP] onMount triggered in +layout.svelte");
44
- registerServiceWorker();
38
+ registerServiceWorker();
39
+ initPostHog();
40
+
41
+ // Register navigation tracking only on client
42
+ afterNavigate(() => {
43
+ capture("$pageview");
44
+ });
45
45
 
46
+ if (browser) {
46
47
  const isDev = import.meta.env.MODE === "development";
47
48
 
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
- );
49
+ // Check for ?debug=true in URL (no persistence)
50
+ const urlParams = new URLSearchParams(window.location.search);
51
+ const debug = urlParams.get("debug") === "true";
52
+
53
+ if (isDev || debug) {
54
+ console.log("ENV MODE =", import.meta.env.MODE);
55
+ console.log("isDev =", isDev);
56
+ console.log("debug param =", debug);
57
+ console.log("shouldTrackUser =", shouldTrackUser()); // Now called statically
64
58
  }
65
- });
66
- }
59
+
60
+ // Preload logo assets
61
+ [logoPng, logoWbp, appleTouchIcon].forEach((src) => {
62
+ const img = new Image();
63
+ img.src = src;
64
+ });
65
+ }
66
+ });
67
67
 
68
68
  // fallback values if data.meta not set
69
69
  const metaTitle =
@@ -103,10 +103,6 @@ This file is part of Network Pro.
103
103
  </header>
104
104
  <!-- END HEADER -->
105
105
 
106
- {#if PostHog}
107
- <PostHog /> <!-- Add PostHog component when it's loaded -->
108
- {/if}
109
-
110
106
  <main>
111
107
  <slot />
112
108
  </main>
@@ -1,7 +1,6 @@
1
1
  /*! ==========================================================================
2
- src/lib/styles/css/offline.css
3
-
2
+ Copyright © 2025 Network Pro Strategies (Network Pro™)
4
3
  SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
5
4
  This file is part of Network Pro.
6
5
  =========================================================================== */
7
- html,body{height:100%;margin:0;padding:0}body{color:#fafafa;text-align:center;background-color:#191919;flex-direction:column;justify-content:center;align-items:center;margin:10px;padding:0 1rem;font-family:Arial,Helvetica,sans-serif;display:flex}.container{max-width:600px;margin:0 auto}h1{color:#fafafa;margin-bottom:1rem;font-size:2rem}p{margin-bottom:1.5rem;font-size:1.1rem;line-height:1.5}.icon{color:#ff5252;margin-bottom:1rem;font-size:4rem}.retry-button{color:#fafafa;cursor:pointer;background-color:#2e7d32;border:none;border-radius:4px;margin-top:1rem;padding:12px 24px;font-family:Arial,Helvetica,sans-serif;font-size:1rem;transition:background-color .2s}.retry-button:hover{background-color:#388e3c}.status{color:#bdbdbd;background-color:#ffffff0d;border-radius:4px;margin:1rem 0;padding:1rem;font-size:.9rem}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}.help-text{color:#bdbdbd;margin-top:2rem;font-size:.9rem}
6
+ html,body{height:100%;margin:0;padding:0}body{color:#fafafa;text-align:center;background-color:#191919;flex-direction:column;justify-content:center;align-items:center;margin:10px;padding:0 1rem;font-family:Arial,Helvetica,sans-serif;display:flex}.container{max-width:600px;margin:0 auto}h1{color:#fafafa;margin-bottom:1rem;font-size:2rem}p{margin-bottom:1.5rem;font-size:1.1rem;line-height:1.5}.icon{color:#ff5252;margin-bottom:1rem;font-size:4rem}.retry-button{color:#fafafa;cursor:pointer;background-color:#2e7d32;border:none;border-radius:4px;margin-top:1rem;padding:12px 24px;font-family:Arial,Helvetica,sans-serif;font-size:1rem;transition:background-color .2s}.retry-button:hover{background-color:#388e3c}.status{color:#bdbdbd;background-color:#ffffff0d;border-radius:4px;margin:1rem 0;padding:1rem;font-size:.9rem}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}.help-text{color:#bdbdbd;margin-top:2rem;font-size:.9rem}