From 6c1384b661cd9e54dd1e0599542893aff3fed014 Mon Sep 17 00:00:00 2001 From: venus Date: Thu, 8 Oct 2026 01:13:18 -0500 Subject: [PATCH] feat: upadted 404 page --- src/404.njk | 25 +++++++ src/assets/css/components/not-found.css | 86 +++++++++++++++++++++++++ src/assets/css/main.css | 1 + test/not-found.test.js | 43 +++++++++++++ 4 files changed, 155 insertions(+) create mode 100644 src/404.njk create mode 100644 src/assets/css/components/not-found.css create mode 100644 test/not-found.test.js diff --git a/src/404.njk b/src/404.njk new file mode 100644 index 0000000..4b1590c --- /dev/null +++ b/src/404.njk @@ -0,0 +1,25 @@ +--- +layout: layouts/base.njk +permalink: /404.html +title: Page not found | River Rooks +excerpt: The requested page could not be found. +--- +
+
+ // ROUTE STATUS + HTTP 404 • NOT FOUND +
+ +
+

404

+
+

REQUEST FAILED / NO MATCHING ROUTE

+

Page not found.

+

The address you requested doesn't point to a page here. Check the URL, or choose a known route below.

+ +
+
+
diff --git a/src/assets/css/components/not-found.css b/src/assets/css/components/not-found.css new file mode 100644 index 0000000..146ecb4 --- /dev/null +++ b/src/assets/css/components/not-found.css @@ -0,0 +1,86 @@ +/* Missing route: a compact diagnostic panel in the site's tiling vocabulary. */ +.not-found { + gap: 0; + padding: 20px 24px 28px; +} + +.not-found-body { + display: grid; + grid-template-columns: minmax(0, 0.8fr) minmax(0, 1fr); + align-items: center; + gap: 24px; + min-height: 320px; +} + +.not-found-code { + font-family: var(--font-mono); + font-size: clamp(5rem, 14vw, 10rem); + line-height: 1; + letter-spacing: -0.09em; + color: var(--accent-amber); + white-space: nowrap; +} + +.not-found-cursor { + color: var(--text-muted); +} + +.not-found-message { + border-left: 1px solid var(--border-color); + padding: 16px 0 16px 24px; + max-width: 48ch; +} + +.not-found-label { + font: 500 0.75rem var(--font-mono); + letter-spacing: 0.06em; + color: var(--accent-amber); + margin-bottom: 12px; +} + +.not-found-message h2 { + font: 700 clamp(1.3rem, 3vw, 2rem) / 1.25 var(--font-mono); + margin-bottom: 12px; +} + +.not-found-message > p:not(.not-found-label) { + color: var(--text-muted); + max-width: 42ch; +} + +.not-found-links { + display: flex; + flex-wrap: wrap; + gap: 8px 24px; + margin-top: 24px; + font: 500 0.85rem var(--font-mono); +} + +.not-found-links a { + color: var(--accent-cyan); + text-decoration: none; +} + +.not-found-links a:hover { + color: var(--bg-canvas); + background: var(--accent-cyan); +} + +@media (max-width: 680px) { + .not-found { + padding: 16px; + } + + .not-found-body { + grid-template-columns: 1fr; + gap: 8px; + min-height: 0; + padding: 32px 0 8px; + } + + .not-found-message { + border-left: 0; + border-top: 1px solid var(--border-color); + padding: 20px 0 0; + } +} diff --git a/src/assets/css/main.css b/src/assets/css/main.css index cc44511..8c600fc 100644 --- a/src/assets/css/main.css +++ b/src/assets/css/main.css @@ -19,3 +19,4 @@ @import "./components/code-box.css"; @import "./components/article.css"; @import "./components/footer.css"; +@import "./components/not-found.css"; diff --git a/test/not-found.test.js b/test/not-found.test.js new file mode 100644 index 0000000..c7c5038 --- /dev/null +++ b/test/not-found.test.js @@ -0,0 +1,43 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { execFileSync } from "node:child_process"; +import { createServer } from "../server.js"; + +const siteDir = path.resolve("_site"); + +test("unknown routes render the themed 404 page with a real 404 status", async () => { + execFileSync("npm", ["run", "build"], { + stdio: "pipe", + env: { ...process.env, SITE_DOMAIN: process.env.SITE_DOMAIN || "riverrooks.dev" } + }); + + const html = fs.readFileSync(path.join(siteDir, "404.html"), "utf8"); + assert.match(html, /Page not found/); + assert.match(html, /<h1[^>]*>[^<]*404/); + assert.match(html, /href="\/"/); + assert.match(html, /href="\/dispatches\/"/); + assert.match(html, /href="\/assets\/css\/main\.css/); + + const css = fs.readFileSync(path.join(siteDir, "assets/css/main.css"), "utf8"); + assert.match(css, /components\/not-found\.css/); + const pageCss = fs.readFileSync(path.join(siteDir, "assets/css/components/not-found.css"), "utf8"); + assert.doesNotMatch(pageCss, /(?:backdrop-filter|box-shadow|text-shadow|linear-gradient|radial-gradient)\s*:/); + + const server = createServer(siteDir, path.resolve("src/posts"), path.resolve("src/assets/images/posts")); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + try { + const origin = `http://127.0.0.1:${server.address().port}`; + for (const route of ["/missing-page/", "/assets/missing.css"]) { + const response = await fetch(`${origin}${route}`); + assert.equal(response.status, 404); + assert.match(response.headers.get("content-type"), /text\/html/); + assert.match(await response.text(), /Page not found/); + } + const home = await fetch(origin); + assert.equal(home.status, 200); + } finally { + await new Promise((resolve) => server.close(resolve)); + } +});