feat: upadted 404 page
This commit is contained in:
+25
@@ -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.
|
||||||
|
---
|
||||||
|
<section class="not-found tile" aria-labelledby="not-found-title">
|
||||||
|
<header class="tile-header">
|
||||||
|
<span class="tile-title">// ROUTE STATUS</span>
|
||||||
|
<span class="tile-meta">HTTP 404 • NOT FOUND</span>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="not-found-body">
|
||||||
|
<h1 class="not-found-code" id="not-found-title">404<span class="not-found-cursor" aria-hidden="true">_</span></h1>
|
||||||
|
<div class="not-found-message">
|
||||||
|
<p class="not-found-label">REQUEST FAILED / NO MATCHING ROUTE</p>
|
||||||
|
<h2>Page not found.</h2>
|
||||||
|
<p>The address you requested doesn't point to a page here. Check the URL, or choose a known route below.</p>
|
||||||
|
<nav class="not-found-links" aria-label="Recovery links">
|
||||||
|
<a href="/">> Back to home</a>
|
||||||
|
<a href="/dispatches/">> Browse dispatches</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,3 +19,4 @@
|
|||||||
@import "./components/code-box.css";
|
@import "./components/code-box.css";
|
||||||
@import "./components/article.css";
|
@import "./components/article.css";
|
||||||
@import "./components/footer.css";
|
@import "./components/footer.css";
|
||||||
|
@import "./components/not-found.css";
|
||||||
|
|||||||
@@ -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, /<title>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));
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user