@networkpro/web 1.14.3 → 1.15.1
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/.node-version +1 -1
- package/.nvmrc +1 -1
- package/CHANGELOG.md +87 -2
- package/README.md +6 -4
- package/_redirects +4 -2
- package/netlify.toml +0 -5
- package/package.json +14 -15
- package/scripts/testRedirects.js +84 -0
- package/src/app.html +1 -1
- package/src/hooks.server.js +20 -1
- package/src/lib/components/RedirectPage.svelte +2 -34
- package/src/lib/components/layout/HeaderDefault.svelte +8 -3
- package/src/lib/components/layout/HeaderHome.svelte +8 -3
- package/src/lib/pages/AboutContent.svelte +1 -1
- package/src/lib/pages/LicenseContent.svelte +1 -2
- package/src/lib/pages/PrivacyDashboard.svelte +0 -1
- package/src/lib/styles/css/default.css +22 -0
- package/src/lib/styles/global.min.css +1 -1
- package/src/lib/utils/redirect.js +52 -0
- package/src/lib/utils/utm.js +1 -0
- package/src/routes/consultation/+page.svelte +16 -2
- package/src/routes/contact/+page.svelte +2 -4
- package/src/routes/links/+page.svelte +2 -4
- package/src/routes/posts/+page.svelte +2 -4
- package/src/routes/privacy-rights/+page.svelte +2 -4
- package/static/sitemap.xml +4 -4
- package/tests/unit/{unregisterServiceWorker.test.js → client/lib/unregisterServiceWorker.test.js} +2 -2
- package/tests/unit/client/lib/utils/redirect.test.js +80 -0
- package/tests/unit/{utm.test.js → client/lib/utils/utm.test.js} +1 -1
- package/tests/unit/{routes → client/routes}/page.svelte.test.js +2 -2
- package/tests/unit/{checkEnv.test.js → server/checkEnv.test.js} +2 -2
- package/tests/unit/{checkVersions.test.js → server/checkVersions.test.js} +2 -2
- package/tests/{internal → unit/server/internal}/auditCoverage.test.js +13 -6
- package/tests/unit/{lib → server/lib}/utils/purify.test.js +2 -2
- package/vitest.config.client.js +1 -4
- package/vitest.config.server.js +1 -1
- package/netlify/edge-functions/csp-report.js +0 -160
- package/tests/unit/csp-report.test.js +0 -81
package/.node-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
24.
|
|
1
|
+
24.4.0
|
package/.nvmrc
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
24.
|
|
1
|
+
24.4.0
|
package/CHANGELOG.md
CHANGED
|
@@ -22,6 +22,89 @@ This project attempts to follow [Keep a Changelog](https://keepachangelog.com/en
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
+
## [1.15.1] - 2025-07-12
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Added `Report-To` header in `src/hooks.server.js` to support modern CSP reporting
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Bumped project version to `1.15.1`
|
|
34
|
+
- Updated CSP report URL in `src/hooks.server.js` to use external endpoint
|
|
35
|
+
- Updated `sitemap.xml` to reflect latest site structure
|
|
36
|
+
- Updated `.node-version` and `.nvmrc` to `v24.4.0`
|
|
37
|
+
- Cleaned up `netlify.toml`:
|
|
38
|
+
- Removed `[[edge_functions]]` block
|
|
39
|
+
- Confirmed `ENV_MODE` remains for internal tooling
|
|
40
|
+
- Updated to latest versions:
|
|
41
|
+
- `@eslint/js` to `^9.31.0`
|
|
42
|
+
- `@playwright/test` to `^1.54.1`
|
|
43
|
+
- `@sveltejs/kit` to `2.22.5`
|
|
44
|
+
- `@sveltejs/vite-plugin-svelte` to `6.0.0`
|
|
45
|
+
- `eslint` to `^9.31.0`
|
|
46
|
+
- `eslint-plugin-jsdoc` to `^51.3.4`
|
|
47
|
+
- `playwright` to `^1.54.1`
|
|
48
|
+
- `posthog-js` to `^1.257.0`
|
|
49
|
+
- `stylelint` to `^16.21.1`
|
|
50
|
+
- `svelte` to `5.35.6`
|
|
51
|
+
- `vite` to `7.0.4`
|
|
52
|
+
|
|
53
|
+
### Removed
|
|
54
|
+
|
|
55
|
+
- Deleted `/netlify/edge-functions/` and `csp-report.js` (CSP report handling is now in its own project)
|
|
56
|
+
- Removed `tests/unit/server/csp-report.test.js` from project, as CSP endpoint has been relocated
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## [1.15.0] - 2025-07-01
|
|
61
|
+
|
|
62
|
+
### Added
|
|
63
|
+
|
|
64
|
+
- `redirect.js` utility to handle browser-aware redirects with fallback logic for Firefox.
|
|
65
|
+
- Unit test for `redirect.js` under `tests/unit/client/lib/utils/redirect.test.js`.
|
|
66
|
+
- `/consultation` redirect route to `utm.js` UTM-tracking logic.
|
|
67
|
+
- Redirect from `/foss` to `/foss-spotlight` in `_redirects`.
|
|
68
|
+
- `scripts/testRedirect.js` to verify Netlify/SvelteKit trailing slash redirect behavior.
|
|
69
|
+
- `test:redirects` script in package.json to trigger `scripts/testRedirects.js`
|
|
70
|
+
- Logic to suppress `rel="noopener noreferrer"` on internal redirects in `HeaderHome.svelte` and `HeaderDefault.svelte`
|
|
71
|
+
- `redirect` flag to navigation metadata to distinguish internal redirect behavior in `HeaderHome.svelte` and `HeaderDefault.svelte`
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- Bumped version to **v1.15.0**
|
|
76
|
+
- Restructured unit tests:
|
|
77
|
+
- Moved `purify.test.js` to `tests/unit/client/lib/utils/`
|
|
78
|
+
- Moved `utm.test.js` to `tests/unit/client/lib/utils/`
|
|
79
|
+
- Moved `unregisterServiceWorker.test.js` to `tests/unit/client/lib/utils/`
|
|
80
|
+
- Moved `page.svelte.test.js` to `tests/unit/client/routes/`
|
|
81
|
+
- Moved `checkEnv.test.js`, `checkVersions.test.js`, and `csp-report.test.js` to `tests/unit/server/`
|
|
82
|
+
- Moved `auditCoverage.test.js` to `tests/unit/server/internal/`
|
|
83
|
+
- Refactored `_redirects` file:
|
|
84
|
+
- Removed trailing slashes to match SvelteKit/Netlify conventions.
|
|
85
|
+
- Updated `RedirectPage.svelte` to use `redirectWithBrowserAwareness()` for better cross-browser redirect behavior.
|
|
86
|
+
- Refactored all relevant `+page.svelte` files to remove local redirect timeouts and centralize logic in `RedirectPage`.
|
|
87
|
+
- Updated `vitest.config.client.js` and `vitest.config.server.js` to:
|
|
88
|
+
- Reflect new directory structure
|
|
89
|
+
- Properly assign `jsdom` for client-side tests and `node` for server-side tests
|
|
90
|
+
- `/consultation`, `/contact`, `/links`, `/posts`, and `/privacy-rights` `+page.svelte` files updated to capture UTM parameters in a privacy-preserving manner.
|
|
91
|
+
- Moved inline styles from `RedirectPage.svelte` to `src/lib/styles/css/default.css`, including `@keyframes spin` animation used by `.loading-spinner`
|
|
92
|
+
- Removed unnecessary `rel` attribute from internal links in `AboutContent.svelte`, `LicenseContent.svelte`, and `PrivacyDashboard.svelte`
|
|
93
|
+
- Updated project README with revised directory structure reflecting separate client/server test folders.
|
|
94
|
+
- Upgraded dependencies:
|
|
95
|
+
- `globals` `^16.2.0` → `^16.3.0`
|
|
96
|
+
|
|
97
|
+
### Fixed
|
|
98
|
+
|
|
99
|
+
- Firefox-specific issue where delayed `window.location.replace()` triggered a new tab instead of redirecting in the same window — now handled by bypassing the delay in Firefox.
|
|
100
|
+
- Prevented server-context tests from breaking due to `window` usage by scoping them to client-only environments.
|
|
101
|
+
|
|
102
|
+
## Removed
|
|
103
|
+
|
|
104
|
+
- `head:flatten` and `head:validate` scripts in package.json, as the `_headers` file has been deprecated
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
25
108
|
## [1.14.3] - 2025-06-30
|
|
26
109
|
|
|
27
110
|
### Added
|
|
@@ -32,6 +115,7 @@ This project attempts to follow [Keep a Changelog](https://keepachangelog.com/en
|
|
|
32
115
|
|
|
33
116
|
### Changed
|
|
34
117
|
|
|
118
|
+
- Bumped version to **v1.14.3**
|
|
35
119
|
- Refactored redirect logic in multiple pages to integrate UTM-aware analytics
|
|
36
120
|
- All redirect pages now consistently open in a new browser tab using `<a>` fallback
|
|
37
121
|
- Enhanced `utm.js` logic to support campaign identification for `/contact`, `/links`, `/posts`, and `/privacy-rights`
|
|
@@ -566,8 +650,9 @@ This project attempts to follow [Keep a Changelog](https://keepachangelog.com/en
|
|
|
566
650
|
|
|
567
651
|
<!-- Link references -->
|
|
568
652
|
|
|
569
|
-
[Unreleased]: https://github.com/netwk-pro/netwk-pro.github.io/compare/v1.
|
|
570
|
-
[1.
|
|
653
|
+
[Unreleased]: https://github.com/netwk-pro/netwk-pro.github.io/compare/v1.15.1...HEAD
|
|
654
|
+
[1.15.1]: https://github.com/netwk-pro/netwk-pro.github.io/releases/tag/v1.15.1
|
|
655
|
+
[1.15.0]: https://github.com/netwk-pro/netwk-pro.github.io/releases/tag/v1.15.0
|
|
571
656
|
[1.14.2]: https://github.com/netwk-pro/netwk-pro.github.io/releases/tag/v1.14.2
|
|
572
657
|
[1.14.1]: https://github.com/netwk-pro/netwk-pro.github.io/releases/tag/v1.14.1
|
|
573
658
|
[1.14.0]: https://github.com/netwk-pro/netwk-pro.github.io/releases/tag/v1.14.0
|
package/README.md
CHANGED
|
@@ -92,10 +92,12 @@ This project follows the principles of [Keep a Changelog](https://keepachangelog
|
|
|
92
92
|
│ ├── robots.txt # SEO: allow/disallow crawlers
|
|
93
93
|
│ └── sitemap.xml # SEO: full site map
|
|
94
94
|
├── tests/
|
|
95
|
-
│ ├── e2e/
|
|
96
|
-
│
|
|
97
|
-
│
|
|
98
|
-
│
|
|
95
|
+
│ ├── e2e/ # Playwright end-to-end tests
|
|
96
|
+
│ └── unit/ # Vitest unit tests
|
|
97
|
+
│ ├── client/ # Client-side (jsdom) unit tests
|
|
98
|
+
│ ├── server/ # Server-side (node) unit tests
|
|
99
|
+
│ │ └── internal/ # Internal audit/test helpers
|
|
100
|
+
│ │ └── auditCoverage.test.js # Warns about untested source modules
|
|
99
101
|
├── _redirects # Netlify redirect rules
|
|
100
102
|
├── CHANGELOG.md # Chronological record of notable project changes
|
|
101
103
|
├── netlify.toml # Netlify configuration
|
package/_redirects
CHANGED
|
@@ -3,13 +3,15 @@ https://www.netwk.pro/* https://netwk.pro/:splat 301
|
|
|
3
3
|
|
|
4
4
|
# Redirect /privacy-policy (and variants) to /privacy
|
|
5
5
|
/privacy-policy /privacy 301
|
|
6
|
-
/privacy-policy/ /privacy 301
|
|
7
6
|
/privacy-policy/* /privacy/:splat 301
|
|
8
7
|
|
|
9
8
|
# Redirect /legal (and variants) to /license
|
|
10
9
|
/legal /license 301
|
|
11
|
-
/legal/ /license 301
|
|
12
10
|
/legal/* /license/:splat 301
|
|
13
11
|
|
|
12
|
+
# Redirect /foss (and variants) to /foss-spotlight
|
|
13
|
+
/foss /foss-spotlight 301
|
|
14
|
+
/foss/* /foss-spotlight/:splat 301
|
|
15
|
+
|
|
14
16
|
# Redirect for Netlify function proxy
|
|
15
17
|
/api/* /.netlify/functions/:splat 200
|
package/netlify.toml
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
command = "npm run build"
|
|
3
3
|
|
|
4
4
|
[build.environment]
|
|
5
|
-
NODE_VERSION = "22"
|
|
6
5
|
ENV_MODE = "prod"
|
|
7
6
|
|
|
8
7
|
[dev]
|
|
@@ -10,10 +9,6 @@
|
|
|
10
9
|
targetPort = 5173
|
|
11
10
|
port = 8888
|
|
12
11
|
|
|
13
|
-
[[edge_functions]]
|
|
14
|
-
path = "/api/csp-report"
|
|
15
|
-
function = "csp-report"
|
|
16
|
-
|
|
17
12
|
[[plugins]]
|
|
18
13
|
package = "netlify-plugin-submit-sitemap"
|
|
19
14
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@networkpro/web",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.15.1",
|
|
5
5
|
"description": "Locking Down Networks, Unlocking Confidence™ | Security, Networking, Privacy — Network Pro Strategies",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"advisory",
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"test:watch": "vitest --config vitest.config.client.js --watch",
|
|
61
61
|
"test:coverage": "npm run test:client -- --run --coverage && npm run test:server -- --run --coverage",
|
|
62
62
|
"test:e2e": "npx playwright test --retries=1",
|
|
63
|
+
"test:redirects": "node scripts/testRedirects.js",
|
|
63
64
|
"coverage:client": "node scripts/openReport.js client",
|
|
64
65
|
"coverage:server": "node scripts/openReport.js server",
|
|
65
66
|
"coverage:open": "npm run coverage:client && npm run coverage:server",
|
|
@@ -74,44 +75,42 @@
|
|
|
74
75
|
"lhci": "lhci",
|
|
75
76
|
"lhci:run": "lhci autorun --config=.lighthouserc.cjs",
|
|
76
77
|
"audit:coverage": "vitest run tests/internal/auditCoverage.test.js",
|
|
77
|
-
"head:flatten": "node scripts/flattenHeaders.js",
|
|
78
|
-
"head:validate": "node scripts/validateHeaders.js",
|
|
79
78
|
"postinstall": "npm run check:node"
|
|
80
79
|
},
|
|
81
80
|
"dependencies": {
|
|
82
81
|
"dompurify": "^3.2.6",
|
|
83
|
-
"posthog-js": "^1.
|
|
82
|
+
"posthog-js": "^1.257.0",
|
|
84
83
|
"semver": "^7.7.2",
|
|
85
|
-
"svelte": "5.
|
|
84
|
+
"svelte": "5.35.6"
|
|
86
85
|
},
|
|
87
86
|
"devDependencies": {
|
|
88
87
|
"@eslint/compat": "^1.3.1",
|
|
89
|
-
"@eslint/js": "^9.
|
|
88
|
+
"@eslint/js": "^9.31.0",
|
|
90
89
|
"@lhci/cli": "^0.15.1",
|
|
91
|
-
"@playwright/test": "^1.
|
|
90
|
+
"@playwright/test": "^1.54.1",
|
|
92
91
|
"@sveltejs/adapter-netlify": "^5.0.2",
|
|
93
|
-
"@sveltejs/kit": "2.22.
|
|
94
|
-
"@sveltejs/vite-plugin-svelte": "
|
|
92
|
+
"@sveltejs/kit": "2.22.5",
|
|
93
|
+
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
|
95
94
|
"@testing-library/jest-dom": "^6.6.3",
|
|
96
95
|
"@testing-library/svelte": "^5.2.8",
|
|
97
96
|
"@vitest/coverage-v8": "^3.2.4",
|
|
98
97
|
"autoprefixer": "^10.4.21",
|
|
99
98
|
"browserslist": "^4.25.1",
|
|
100
|
-
"eslint": "^9.
|
|
99
|
+
"eslint": "^9.31.0",
|
|
101
100
|
"eslint-config-prettier": "^10.1.5",
|
|
102
|
-
"eslint-plugin-jsdoc": "^51.3.
|
|
101
|
+
"eslint-plugin-jsdoc": "^51.3.4",
|
|
103
102
|
"eslint-plugin-svelte": "^3.10.1",
|
|
104
|
-
"globals": "^16.
|
|
103
|
+
"globals": "^16.3.0",
|
|
105
104
|
"jsdom": "^26.1.0",
|
|
106
105
|
"lightningcss": "^1.30.1",
|
|
107
106
|
"markdownlint": "^0.38.0",
|
|
108
107
|
"markdownlint-cli2": "^0.18.1",
|
|
109
108
|
"mdsvex": "^0.12.6",
|
|
110
|
-
"playwright": "^1.
|
|
109
|
+
"playwright": "^1.54.1",
|
|
111
110
|
"postcss": "^8.5.6",
|
|
112
111
|
"prettier": "^3.6.2",
|
|
113
112
|
"prettier-plugin-svelte": "^3.4.0",
|
|
114
|
-
"stylelint": "^16.21.
|
|
113
|
+
"stylelint": "^16.21.1",
|
|
115
114
|
"stylelint-config-html": "^1.1.0",
|
|
116
115
|
"stylelint-config-recommended": "^16.0.0",
|
|
117
116
|
"stylelint-order": "^7.0.0",
|
|
@@ -119,7 +118,7 @@
|
|
|
119
118
|
"svelte-eslint-parser": "^1.2.0",
|
|
120
119
|
"svelte-preprocess": "^6.0.3",
|
|
121
120
|
"typescript": "^5.8.3",
|
|
122
|
-
"vite": "
|
|
121
|
+
"vite": "^7.0.4",
|
|
123
122
|
"vite-plugin-lightningcss": "^0.0.5",
|
|
124
123
|
"vite-tsconfig-paths": "^5.1.4",
|
|
125
124
|
"vitest": "^3.2.4"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
scripts/testRedirects.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 testRedirects.js
|
|
11
|
+
* @description Tests Netlify redirects against actual endpoints to verify
|
|
12
|
+
* trailing-slash behavior.
|
|
13
|
+
*
|
|
14
|
+
* @module scripts/
|
|
15
|
+
* @author SunDevil311
|
|
16
|
+
* @updated 2025-07-01
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import https from 'https';
|
|
20
|
+
import { URL } from 'url';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {object} RedirectTest
|
|
24
|
+
* @property {string} from - The source URL to test.
|
|
25
|
+
* @property {string} to - The expected destination URL.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** @type {RedirectTest[]} */
|
|
29
|
+
const urls = [
|
|
30
|
+
{ from: 'https://netwk.pro/privacy-policy', to: 'https://netwk.pro/privacy' },
|
|
31
|
+
{
|
|
32
|
+
from: 'https://netwk.pro/privacy-policy/',
|
|
33
|
+
to: 'https://netwk.pro/privacy',
|
|
34
|
+
},
|
|
35
|
+
{ from: 'https://netwk.pro/foss', to: 'https://netwk.pro/foss-spotlight' },
|
|
36
|
+
{ from: 'https://netwk.pro/foss/', to: 'https://netwk.pro/foss-spotlight' },
|
|
37
|
+
{
|
|
38
|
+
from: 'https://www.netwk.pro/foss',
|
|
39
|
+
to: 'https://netwk.pro/foss-spotlight',
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Tests a single redirect by making a GET request and checking the status code and location header.
|
|
45
|
+
* @param {RedirectTest} redirect - The redirect configuration to test.
|
|
46
|
+
* @returns {Promise<boolean>} - Resolves to true if the redirect is correct, false otherwise.
|
|
47
|
+
*/
|
|
48
|
+
function testRedirect({ from, to }) {
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
const req = https.request(new URL(from), { method: 'GET' }, (res) => {
|
|
51
|
+
const location = res.headers.location;
|
|
52
|
+
const expectedPath = new URL(to).pathname;
|
|
53
|
+
|
|
54
|
+
if (res.statusCode === 301 && location === expectedPath) {
|
|
55
|
+
console.log(`✅ ${from} → ${location}`);
|
|
56
|
+
resolve(true);
|
|
57
|
+
} else {
|
|
58
|
+
console.error(
|
|
59
|
+
`❌ ${from} → Expected 301 to ${expectedPath}, got ${res.statusCode} to ${location}`,
|
|
60
|
+
);
|
|
61
|
+
resolve(false);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
req.on('error', (err) => {
|
|
66
|
+
console.error(`❌ ${from} → Network error: ${err.message}`);
|
|
67
|
+
resolve(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
req.end();
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Runs all redirect tests and exits the process with a status code reflecting success or failure.
|
|
76
|
+
* @returns {Promise<void>}
|
|
77
|
+
*/
|
|
78
|
+
const runTests = async () => {
|
|
79
|
+
const results = await Promise.all(urls.map(testRedirect));
|
|
80
|
+
const failed = results.filter((r) => !r).length;
|
|
81
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
runTests();
|
package/src/app.html
CHANGED
package/src/hooks.server.js
CHANGED
|
@@ -25,7 +25,10 @@ export async function handle({ event, resolve }) {
|
|
|
25
25
|
console.log('[CSP Debug] ENV_MODE:', process.env.ENV_MODE);
|
|
26
26
|
|
|
27
27
|
// Determine report URI
|
|
28
|
-
const reportUri =
|
|
28
|
+
const reportUri =
|
|
29
|
+
isProdEnvironment && !isTestEnvironment
|
|
30
|
+
? 'https://csp.netwk.pro/.netlify/functions/csp-report'
|
|
31
|
+
: '/api/mock-csp';
|
|
29
32
|
|
|
30
33
|
// Construct base policy
|
|
31
34
|
const cspDirectives = [
|
|
@@ -40,7 +43,9 @@ export async function handle({ event, resolve }) {
|
|
|
40
43
|
"object-src 'none';",
|
|
41
44
|
"frame-ancestors 'none';",
|
|
42
45
|
'upgrade-insecure-requests;',
|
|
46
|
+
// Report CSP violations to external endpoint hosted at csp.netwk.pro
|
|
43
47
|
`report-uri ${reportUri};`,
|
|
48
|
+
'report-to csp-endpoint;',
|
|
44
49
|
];
|
|
45
50
|
|
|
46
51
|
// Loosen up CSP for test environments
|
|
@@ -54,6 +59,20 @@ export async function handle({ event, resolve }) {
|
|
|
54
59
|
cspDirectives[5] = "connect-src 'self';";
|
|
55
60
|
}
|
|
56
61
|
|
|
62
|
+
response.headers.set(
|
|
63
|
+
'Report-To',
|
|
64
|
+
JSON.stringify({
|
|
65
|
+
group: 'csp-endpoint',
|
|
66
|
+
max_age: 10886400, // 18 weeks
|
|
67
|
+
endpoints: [
|
|
68
|
+
{
|
|
69
|
+
url: 'https://csp.netwk.pro/.netlify/functions/csp-report',
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
include_subdomains: true,
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
|
|
57
76
|
response.headers.set('Content-Security-Policy', cspDirectives.join(' '));
|
|
58
77
|
|
|
59
78
|
// Set other security headers
|
|
@@ -8,6 +8,7 @@ This file is part of Network Pro.
|
|
|
8
8
|
|
|
9
9
|
<script>
|
|
10
10
|
import { onMount } from 'svelte';
|
|
11
|
+
import { redirectWithBrowserAwareness } from '$lib/utils/redirect.js';
|
|
11
12
|
import FullWidthSection from '$lib/components/FullWidthSection.svelte';
|
|
12
13
|
|
|
13
14
|
export let to;
|
|
@@ -15,16 +16,7 @@ This file is part of Network Pro.
|
|
|
15
16
|
export let delay = 3;
|
|
16
17
|
|
|
17
18
|
onMount(() => {
|
|
18
|
-
|
|
19
|
-
console.warn('⛔ No redirect target provided');
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
console.log('✅ Starting redirect to:', to);
|
|
24
|
-
|
|
25
|
-
setTimeout(() => {
|
|
26
|
-
window.location.href = to;
|
|
27
|
-
}, delay * 1000);
|
|
19
|
+
redirectWithBrowserAwareness(to, delay);
|
|
28
20
|
});
|
|
29
21
|
</script>
|
|
30
22
|
|
|
@@ -47,27 +39,3 @@ This file is part of Network Pro.
|
|
|
47
39
|
</FullWidthSection>
|
|
48
40
|
|
|
49
41
|
<!-- END MAIN CONTENT -->
|
|
50
|
-
|
|
51
|
-
<style>
|
|
52
|
-
.redirect-content {
|
|
53
|
-
text-align: center;
|
|
54
|
-
font-family: system-ui, sans-serif;
|
|
55
|
-
margin-top: 2rem; /* adjust as needed */
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.loading-spinner {
|
|
59
|
-
width: 48px;
|
|
60
|
-
height: 48px;
|
|
61
|
-
margin: 2rem auto;
|
|
62
|
-
border: 4px solid #ddd;
|
|
63
|
-
animation: spin 1s linear infinite;
|
|
64
|
-
border-radius: 50%;
|
|
65
|
-
border-top: 4px solid #ffc627;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
@keyframes spin {
|
|
69
|
-
to {
|
|
70
|
-
transform: rotate(360deg);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
</style>
|
|
@@ -22,6 +22,8 @@ This file is part of Network Pro.
|
|
|
22
22
|
const lhubLink = `${base}/links`;
|
|
23
23
|
const fossLink = `${base}/foss-spotlight`;
|
|
24
24
|
const blogLink = 'https://blog.netwk.pro';
|
|
25
|
+
const discussLink =
|
|
26
|
+
'https://github.com/netwk-pro/netwk-pro.github.io/discussions';
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Navigation link object.
|
|
@@ -30,6 +32,7 @@ This file is part of Network Pro.
|
|
|
30
32
|
* @property {string} href - The URL or anchor the link points to.
|
|
31
33
|
* @property {string} target - Specifies where to open the link (e.g., "_self" or "_blank").
|
|
32
34
|
* @property {boolean} external - Whether the link points to an external resource.
|
|
35
|
+
* @property {boolean} [redirect=false] - Indicates whether the external link is actually an internal redirect.
|
|
33
36
|
*/
|
|
34
37
|
|
|
35
38
|
/**
|
|
@@ -48,15 +51,17 @@ This file is part of Network Pro.
|
|
|
48
51
|
},
|
|
49
52
|
{
|
|
50
53
|
label: 'discussions',
|
|
51
|
-
href:
|
|
54
|
+
href: discussLink,
|
|
52
55
|
target: PAGE.BLANK,
|
|
53
56
|
external: true,
|
|
57
|
+
redirect: false,
|
|
54
58
|
},
|
|
55
59
|
{
|
|
56
60
|
label: 'link hub',
|
|
57
61
|
href: lhubLink,
|
|
58
62
|
target: PAGE.BLANK,
|
|
59
63
|
external: true,
|
|
64
|
+
redirect: true,
|
|
60
65
|
},
|
|
61
66
|
];
|
|
62
67
|
|
|
@@ -69,8 +74,8 @@ This file is part of Network Pro.
|
|
|
69
74
|
|
|
70
75
|
<!-- BEGIN DEFAULT HEADER -->
|
|
71
76
|
<nav class="center-nav" aria-label="Site navigation">
|
|
72
|
-
{#each nav as { label, href, target, external }, index}
|
|
73
|
-
<a {href} {target} rel={external ? rel : undefined}>
|
|
77
|
+
{#each nav as { label, href, target, external, redirect }, index}
|
|
78
|
+
<a {href} {target} rel={!redirect && external ? rel : undefined}>
|
|
74
79
|
{label}
|
|
75
80
|
</a>
|
|
76
81
|
{#if external}
|
|
@@ -21,6 +21,8 @@ This file is part of Network Pro.
|
|
|
21
21
|
const fossLink = `${base}/foss-spotlight`;
|
|
22
22
|
const lhubLink = `${base}/links`;
|
|
23
23
|
const blogLink = 'https://blog.netwk.pro';
|
|
24
|
+
const discussLink =
|
|
25
|
+
'https://github.com/netwk-pro/netwk-pro.github.io/discussions';
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Navigation link object.
|
|
@@ -29,6 +31,7 @@ This file is part of Network Pro.
|
|
|
29
31
|
* @property {string} href - The URL or anchor the link points to.
|
|
30
32
|
* @property {string} target - Specifies where to open the link (e.g., "_self" or "_blank").
|
|
31
33
|
* @property {boolean} external - Whether the link points to an external resource.
|
|
34
|
+
* @property {boolean} [redirect=false] - Indicates whether the external link is actually an internal redirect.
|
|
32
35
|
*/
|
|
33
36
|
|
|
34
37
|
/**
|
|
@@ -46,15 +49,17 @@ This file is part of Network Pro.
|
|
|
46
49
|
},
|
|
47
50
|
{
|
|
48
51
|
label: 'discussions',
|
|
49
|
-
href:
|
|
52
|
+
href: discussLink,
|
|
50
53
|
target: PAGE.BLANK,
|
|
51
54
|
external: true,
|
|
55
|
+
redirect: false,
|
|
52
56
|
},
|
|
53
57
|
{
|
|
54
58
|
label: 'link hub',
|
|
55
59
|
href: lhubLink,
|
|
56
60
|
target: PAGE.BLANK,
|
|
57
61
|
external: true,
|
|
62
|
+
redirect: true,
|
|
58
63
|
},
|
|
59
64
|
];
|
|
60
65
|
|
|
@@ -67,8 +72,8 @@ This file is part of Network Pro.
|
|
|
67
72
|
|
|
68
73
|
<!-- BEGIN HOME HEADER -->
|
|
69
74
|
<nav class="center-nav" aria-label="Homepage navigation">
|
|
70
|
-
{#each nav as { label, href, target, external }, index}
|
|
71
|
-
<a {href} {target} rel={external ? rel : undefined}>
|
|
75
|
+
{#each nav as { label, href, target, external, redirect }, index}
|
|
76
|
+
<a {href} {target} rel={!redirect && external ? rel : undefined}>
|
|
72
77
|
{label}
|
|
73
78
|
</a>
|
|
74
79
|
{#if external}
|
|
@@ -217,7 +217,7 @@ This file is part of Network Pro.
|
|
|
217
217
|
<div class="spacer"></div>
|
|
218
218
|
|
|
219
219
|
<p>
|
|
220
|
-
<a
|
|
220
|
+
<a href={contactLink} target={PAGE.BLANK}>Let's connect</a>
|
|
221
221
|
to discuss how we can help secure and strengthen your business today.
|
|
222
222
|
</p>
|
|
223
223
|
|
|
@@ -487,8 +487,7 @@ This file is part of Network Pro.
|
|
|
487
487
|
{:else if link.id === 'contact'}
|
|
488
488
|
<p>
|
|
489
489
|
The Company can be contacted via our
|
|
490
|
-
<a
|
|
491
|
-
>Contact Form</a>
|
|
490
|
+
<a href={contactLink} target={PAGE.BLANK}>Contact Form</a>
|
|
492
491
|
or by email at:<br />
|
|
493
492
|
📧 <strong>{CONTACT.EMAIL}</strong>
|
|
494
493
|
</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
|
|
@@ -580,3 +580,25 @@ footer .container {
|
|
|
580
580
|
text-align: center;
|
|
581
581
|
margin-top: 4rem;
|
|
582
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}.redirect-text{text-align:center;margin-top:4rem}
|
|
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)}}
|