diff --git a/public/404.html b/public/404.html
index 5614acd..ae2646e 100644
--- a/public/404.html
+++ b/public/404.html
@@ -17,9 +17,9 @@
-
-
-
+
+
+
@@ -31,6 +31,15 @@
+
@@ -51,9 +60,12 @@
diff --git a/public/assets/homo-reseticus-404-276.0fde8f3e.webp b/public/assets/homo-reseticus-404-276.0fde8f3e.webp
new file mode 100644
index 0000000..078fbc1
Binary files /dev/null and b/public/assets/homo-reseticus-404-276.0fde8f3e.webp differ
diff --git a/public/assets/homo-reseticus-404-552.f829379a.webp b/public/assets/homo-reseticus-404-552.f829379a.webp
new file mode 100644
index 0000000..2012e5b
Binary files /dev/null and b/public/assets/homo-reseticus-404-552.f829379a.webp differ
diff --git a/public/assets/homo-reseticus-404-828.68be2119.webp b/public/assets/homo-reseticus-404-828.68be2119.webp
new file mode 100644
index 0000000..ab95dec
Binary files /dev/null and b/public/assets/homo-reseticus-404-828.68be2119.webp differ
diff --git a/public/assets/homo-reseticus-404.png b/public/assets/homo-reseticus-404.png
deleted file mode 100644
index feced4d..0000000
Binary files a/public/assets/homo-reseticus-404.png and /dev/null differ
diff --git a/public/assets/styles.css b/public/assets/styles.css
index e17fa74..275035f 100644
--- a/public/assets/styles.css
+++ b/public/assets/styles.css
@@ -957,7 +957,7 @@ dialog::backdrop {
.not-found-visual {
position: relative;
isolation: isolate;
- width: clamp(190px, 39vmin, 414px);
+ width: clamp(127px, 26vmin, 276px);
max-width: 100%;
}
@@ -974,14 +974,14 @@ dialog::backdrop {
background:
radial-gradient(
ellipse at 50% 47%,
- rgb(255 255 255 / 58%) 0%,
- rgb(244 248 244 / 36%) 36%,
- rgb(211 224 213 / 16%) 58%,
+ rgb(0 255 65 / 48%) 0%,
+ rgb(0 230 57 / 30%) 36%,
+ rgb(0 128 33 / 14%) 58%,
transparent 76%
),
radial-gradient(
ellipse at 50% 48%,
- rgb(192 216 198 / 12%) 0%,
+ rgb(0 255 65 / 14%) 0%,
transparent 72%
);
content: "";
@@ -993,7 +993,7 @@ dialog::backdrop {
display: block;
width: 100%;
height: auto;
- filter: contrast(1.06) drop-shadow(0 0 18px rgb(229 226 225 / 14%));
+ filter: contrast(1.06) drop-shadow(0 0 16px rgb(0 255 65 / 16%));
}
.not-found-action {
diff --git a/server.js b/server.js
index 0982421..4638649 100644
--- a/server.js
+++ b/server.js
@@ -24,6 +24,9 @@ const REQUEST_MAX_BYTES = 6 * 1024 * 1024;
const API_READ_LIMIT_PER_MINUTE = 120;
const MEDIA_READ_LIMIT_PER_MINUTE = 600;
const SSE_HEARTBEAT_MS = 25_000;
+const IMMUTABLE_CACHE_CONTROL = 'public, max-age=31536000, immutable';
+const REVALIDATING_CACHE_CONTROL = 'public, max-age=300, must-revalidate';
+const FINGERPRINTED_ASSET = /\.[a-f0-9]{8,}\.[a-z0-9]+$/i;
const events = new Set();
const DISCOVERY_ROUTES = new Set([
'/openapi.json',
@@ -152,6 +155,8 @@ const server = http.createServer(async (req, res) => {
}
if (req.method === 'GET' && url.pathname.startsWith('/assets/')) {
+ const isVersioned = url.searchParams.has('v') || FINGERPRINTED_ASSET.test(url.pathname);
+ res.setHeader('Cache-Control', isVersioned ? IMMUTABLE_CACHE_CONTROL : REVALIDATING_CACHE_CONTROL);
return sendFile(res, `./public${url.pathname}`);
}
diff --git a/src/seo.js b/src/seo.js
index c91aca2..1aaaf69 100644
--- a/src/seo.js
+++ b/src/seo.js
@@ -2,7 +2,7 @@ const SITE_NAME = 'The Meme Protocol';
const SITE_DESCRIPTION = 'A live, moderated meme stream for The Meme Protocol: WebP memes, MEME_CONSENSUS_SCORE ranking, and community review.';
const REPO_URL = 'https://git.yoonect.com/Nautilus/bitsforfree';
const SOCIAL_IMAGE_PATH = '/assets/social-preview-v1.png';
-const NOT_FOUND_IMAGE_PATH = '/assets/homo-reseticus-404.png';
+const NOT_FOUND_IMAGE_PATH = '/assets/homo-reseticus-404-828.68be2119.webp';
const FAVICON_PATH = '/assets/favicon';
const FAVICON_VERSION = '20260814';
diff --git a/tests/notFound.test.js b/tests/notFound.test.js
index 8558e92..545b720 100644
--- a/tests/notFound.test.js
+++ b/tests/notFound.test.js
@@ -1,6 +1,7 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
+import sharp from 'sharp';
import { renderNotFound } from '../src/seo.js';
const template = fs.readFileSync(new URL('../public/404.html', import.meta.url), 'utf8');
@@ -10,7 +11,11 @@ const page = renderNotFound({
pathname: '/missing-node'
});
const styles = fs.readFileSync(new URL('../public/assets/styles.css', import.meta.url), 'utf8');
-const artwork = fs.readFileSync(new URL('../public/assets/homo-reseticus-404.png', import.meta.url));
+const artworkPaths = [
+ new URL('../public/assets/homo-reseticus-404-276.0fde8f3e.webp', import.meta.url),
+ new URL('../public/assets/homo-reseticus-404-552.f829379a.webp', import.meta.url),
+ new URL('../public/assets/homo-reseticus-404-828.68be2119.webp', import.meta.url)
+];
test('404 page is noindexed and links visitors back to the live feed', () => {
assert.match(page, /404 \| The Meme Protocol<\/title>/);
@@ -26,7 +31,8 @@ test('404 page carries adjusted site discovery and social metadata', () => {
assert.match(page, /]+href="\/openapi\.json">/);
assert.match(page, /]+href="\/meme-api\.skill\.md">/);
assert.match(page, //);
- assert.match(page, //);
+ assert.match(page, //);
+ assert.match(page, //);
assert.match(page, //);
assert.doesNotMatch(page, / {
- assert.match(page, /src="\/assets\/homo-reseticus-404\.png"/);
+test('404 page uses responsive artwork with a diffused green backdrop', () => {
+ assert.match(page, /src="\/assets\/homo-reseticus-404-552\.f829379a\.webp"/);
+ assert.match(page, /srcset="[^"]+276\.0fde8f3e\.webp 276w,[^"]+552\.f829379a\.webp 552w,[^"]+828\.68be2119\.webp 828w"/);
+ assert.match(page, /fetchpriority="high"/);
assert.match(page, /alt="Homo Reseticus surrounded by an unreachable node pattern"/);
assert.match(styles, /\.not-found-visual::before/);
- assert.match(styles, /radial-gradient\(/);
+ assert.match(styles, /rgb\(0 255 65 \/ 48%\)/);
assert.match(styles, /filter: blur\(36px\)/);
assert.match(styles, /transform: scale\(1\.24\)/);
+ assert.match(styles, /width: clamp\(127px, 26vmin, 276px\)/);
assert.match(styles, /\.not-found-page \.pulse/);
- assert.equal(artwork.subarray(1, 4).toString('ascii'), 'PNG');
+});
+
+test('404 artwork variants retain lossless WebP transparency', async () => {
+ const expectedSizes = [[276, 271], [552, 542], [828, 813]];
+
+ for (const [index, artworkPath] of artworkPaths.entries()) {
+ const artwork = fs.readFileSync(artworkPath);
+ const metadata = await sharp(artwork).metadata();
+ assert.equal(artwork.subarray(0, 4).toString('ascii'), 'RIFF');
+ assert.equal(artwork.subarray(8, 12).toString('ascii'), 'WEBP');
+ assert.equal(artwork.subarray(12, 16).toString('ascii'), 'VP8L');
+ assert.equal(metadata.format, 'webp');
+ assert.equal(metadata.hasAlpha, true);
+ assert.deepEqual([metadata.width, metadata.height], expectedSizes[index]);
+ }
});