diff --git a/apps/website/README.md b/apps/website/README.md
index 881715a72..b85179cda 100644
--- a/apps/website/README.md
+++ b/apps/website/README.md
@@ -7,6 +7,14 @@ The website is an Astro static site deployed to GitHub Pages at `https://4gray.g
Blog posts render Giscus comments from `apps/website/src/components/GiscusComments.astro`.
Giscus stores comments in GitHub Discussions for `4gray/iptvnator` and maps each page to a discussion by `pathname`, including the GitHub Pages base path such as `/iptvnator/blog/why-external-players-help/`.
+The embed is **click-to-load**: the configuration sits as `data-*` attributes on a
+"Show comments" button and the `giscus.app` script is created only when a reader
+presses it, so opening a post requests nothing from giscus.app or github.com.
+Keep it that way — pasting the upstream `
+
+
+
+
+
+ Comments are hosted in GitHub Discussions. Loading them contacts giscus.app and github.com;
+ nothing is requested from them until you choose to.
+
+
+
+
diff --git a/apps/website/src/layouts/BaseLayout.astro b/apps/website/src/layouts/BaseLayout.astro
index 4a6ba3dc4..e03101067 100644
--- a/apps/website/src/layouts/BaseLayout.astro
+++ b/apps/website/src/layouts/BaseLayout.astro
@@ -2,6 +2,17 @@
import BaseHead from '../components/BaseHead.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
+// Fonts ship with the site instead of being fetched from a font CDN, so a
+// page load makes no third-party request and the faces are already cached
+// with the rest of the assets.
+import '@fontsource-variable/bricolage-grotesque/wght.css';
+import '@fontsource/dm-sans/400.css';
+import '@fontsource/dm-sans/400-italic.css';
+import '@fontsource/dm-sans/500.css';
+import '@fontsource/dm-sans/600.css';
+import '@fontsource/dm-sans/700.css';
+import '@fontsource/ibm-plex-mono/400.css';
+import '@fontsource/ibm-plex-mono/500.css';
import '../styles/global.css';
interface Props {
diff --git a/apps/website/src/layouts/BlogPost.astro b/apps/website/src/layouts/BlogPost.astro
index a2c704752..41113abfd 100644
--- a/apps/website/src/layouts/BlogPost.astro
+++ b/apps/website/src/layouts/BlogPost.astro
@@ -36,7 +36,8 @@ const formattedUpdatedDate = updatedDate?.toLocaleDateString('en-US', {
day: 'numeric',
});
-const AUTHOR_AVATAR_URL = 'https://avatars.githubusercontent.com/u/1503032';
+/** Served from the site: an avatar fetched from GitHub would report every reader to them. */
+const AUTHOR_AVATAR_URL = '/iptvnator/author-4gray.jpg';
const canonicalURL = new URL(Astro.url.pathname, Astro.site).toString();
const blogPostSchema = {
'@context': 'https://schema.org',
diff --git a/apps/website/tailwind.config.mjs b/apps/website/tailwind.config.mjs
index 26dc719c6..adb9df23e 100644
--- a/apps/website/tailwind.config.mjs
+++ b/apps/website/tailwind.config.mjs
@@ -41,7 +41,13 @@ export default {
},
},
fontFamily: {
- display: ['"Bricolage Grotesque"', '"DM Sans"', 'system-ui', 'sans-serif'],
+ display: [
+ '"Bricolage Grotesque Variable"',
+ '"Bricolage Grotesque"',
+ '"DM Sans"',
+ 'system-ui',
+ 'sans-serif',
+ ],
body: ['"DM Sans"', 'system-ui', 'sans-serif'],
mono: ['"IBM Plex Mono"', 'monospace'],
},
diff --git a/package.json b/package.json
index 12e252250..54995798a 100644
--- a/package.json
+++ b/package.json
@@ -161,8 +161,10 @@
"@eslint/eslintrc": "3.3.7",
"@eslint/js": "^9.38.0",
"@faker-js/faker": "10.6.0",
+ "@fontsource-variable/bricolage-grotesque": "5.3.0",
"@fontsource/crimson-pro": "5.3.0",
"@fontsource/dm-sans": "5.3.0",
+ "@fontsource/ibm-plex-mono": "5.3.0",
"@fontsource/jetbrains-mono": "5.3.0",
"@fontsource/roboto": "5.3.0",
"@ngrx/eslint-plugin": "^21.1.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d5f465ac5..1051e1de2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -241,12 +241,18 @@ importers:
'@faker-js/faker':
specifier: 10.6.0
version: 10.6.0
+ '@fontsource-variable/bricolage-grotesque':
+ specifier: 5.3.0
+ version: 5.3.0
'@fontsource/crimson-pro':
specifier: 5.3.0
version: 5.3.0
'@fontsource/dm-sans':
specifier: 5.3.0
version: 5.3.0
+ '@fontsource/ibm-plex-mono':
+ specifier: 5.3.0
+ version: 5.3.0
'@fontsource/jetbrains-mono':
specifier: 5.3.0
version: 5.3.0
@@ -2272,12 +2278,18 @@ packages:
resolution: {integrity: sha512-3RQHgEtvL1Frl/d1cSreo7qhJ3Gk1OdNUai/CtZ8G+wYeRQnJih3s9xJ9/kgYekPQRdwgh0HXRPqMlzWGwivIQ==}
engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'}
+ '@fontsource-variable/bricolage-grotesque@5.3.0':
+ resolution: {integrity: sha512-TLi9Q4hJjS2UvoTMRSS2nHu6c4R56lAw60NR9QYtVRCHn0XtsFpiEhNffZ8Glsoxu6wEEwLKBP8lb94J52PNBA==}
+
'@fontsource/crimson-pro@5.3.0':
resolution: {integrity: sha512-PXQH0NGma2wwskZAySBH+s/ezAYBwyDH0I4oRURBu9u81nNUCm5DcNCJaUS9H0UR1y8qlslLfVRU6mnDBpffrA==}
'@fontsource/dm-sans@5.3.0':
resolution: {integrity: sha512-lYJtMXXO28q1z+yz+z8XKd0s4hXaa9QdkETzkyD760sidCv5heI86weYA0sx0Nc4pAMAQTUuyf4gO44cYKKS9g==}
+ '@fontsource/ibm-plex-mono@5.3.0':
+ resolution: {integrity: sha512-eTgnZjZEGk1QtD3ZstF+Vclo2HLAni8YMy34/DxllwZvyz1lR/1RF/xTiAquOBO7MvqBx8D2Ig2WCPMVfdZu7Q==}
+
'@fontsource/jetbrains-mono@5.3.0':
resolution: {integrity: sha512-fqDfB5I9f1p1TV486aUgB9t8zP84P0O1FtQR5Ol9vjwPy+S+EIGlVYm1cvj2W5shcZMTg2nZFdVMoH5wFu8a1A==}
@@ -10255,7 +10267,6 @@ packages:
shaka-player@5.2.4:
resolution: {integrity: sha512-vf81av2EIcb03jRpeeZBhrPT3PMyggXnZA9k4sxGBxpr/H6v0iEL6b51T2Kwz5YNrQG/yDYoadgBW+oW40LeoA==}
- engines: {node: '>=18'}
shallow-clone@3.0.1:
resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
@@ -13617,10 +13628,14 @@ snapshots:
'@faker-js/faker@10.6.0': {}
+ '@fontsource-variable/bricolage-grotesque@5.3.0': {}
+
'@fontsource/crimson-pro@5.3.0': {}
'@fontsource/dm-sans@5.3.0': {}
+ '@fontsource/ibm-plex-mono@5.3.0': {}
+
'@fontsource/jetbrains-mono@5.3.0': {}
'@fontsource/roboto@5.3.0': {}
diff --git a/tools/testing/website-giscus-comments.test.mjs b/tools/testing/website-giscus-comments.test.mjs
index 585507acc..34b7c78d8 100644
--- a/tools/testing/website-giscus-comments.test.mjs
+++ b/tools/testing/website-giscus-comments.test.mjs
@@ -5,7 +5,6 @@ import assert from 'node:assert/strict';
const postHtmlPath = new URL('../../dist/apps/website/blog/why-external-players-help/index.html', import.meta.url);
const expectedGiscusAttributes = {
- src: 'https://giscus.app/client.js',
'data-repo': '4gray/iptvnator',
'data-repo-id': 'MDEwOlJlcG9zaXRvcnkyMTMxOTQ3Mzg=',
'data-category': 'Blog comments',
@@ -19,18 +18,28 @@ const expectedGiscusAttributes = {
'data-lang': 'en',
};
-test('published blog posts include the Giscus comments embed configuration', async () => {
+test('published blog posts carry the Giscus configuration on a click-to-load button', async () => {
const html = await readFile(postHtmlPath, 'utf8');
- const scriptMatch = html.match(/