Files
com.gregkilmer.www/services/site/src/pages/404.astro
T
Claude 4614cd25a0 Add a dark and light mode toggle to the header
A sun and moon button at the end of the nav (and the Store's nav) swaps
the mode and remembers the choice in the browser. Until it is pressed the
site follows the device setting. A head script sets html[data-theme]
before first paint, so light styles now key off data-theme instead of a
media query, and motifs render both images with CSS showing one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
2026-10-18 19:57:05 +00:00

168 lines
4.5 KiB
Plaintext

---
/**
* Off the star chart. Renders even when the content service is down: the
* only data it reads (an optional Page text note for /404) is wrapped.
*/
import Button from "@gk/ui/components/Button.astro";
import PageShell from "@gk/ui/components/PageShell.astro";
import Seal from "@gk/ui/components/Seal.astro";
import Star from "@gk/ui/components/Star.astro";
import StarPath from "@gk/ui/components/StarPath.astro";
import TickScale from "@gk/ui/components/TickScale.astro";
import { site } from "../lib/page.ts";
Astro.response.status = 404;
let lede = "This page isn't here. It may have moved when the site was rebuilt.";
let timer: ReturnType<typeof setTimeout> | undefined;
try {
const s = await Promise.race([site(), new Promise<never>((_, reject) => (timer = setTimeout(() => reject(new Error("timeout")), 2000)))]);
lede = s.pageText("/404")?.data.lede ?? lede;
} catch {
// The content service is unreachable; the page needs nothing from it.
} finally {
clearTimeout(timer);
}
const groups = [
{ id: "nf-work", label: "Work with me", links: [["Resume", "/resume"], ["Work", "/work"], ["Inquiries", "/inquiries"]] },
{ id: "nf-world", label: "My world", links: [["Library", "/library"], ["Workshop", "/workshop"], ["Tabletop", "/tabletop"], ["Now", "/now"]] },
{ id: "nf-also", label: "Also", links: [["Blog", "/blog"], ["Store", "/store"], ["About", "/about"]] },
];
---
<PageShell title="Off the star chart" family="notFound" description="This page isn't here.">
<meta slot="head" name="robots" content="noindex" />
<section class="lost" aria-labelledby="nf-title">
<Seal name="zodiac" size={180} class="seal" foil />
<span class="eyebrow">404 · Page not found</span>
<h1 id="nf-title">Off the star chart</h1>
<p class="lede">{lede}</p>
<div class="actions">
<Button href="/">Back home</Button>
<Button href="/blog" variant="secondary">Blog</Button>
</div>
</section>
<div class="path" aria-hidden="true"><StarPath seed="not-found" strength="calm" centerStar={false} /></div>
<nav class="groups" aria-label="Elsewhere on the site">
{
groups.map((g) => (
<section class="group" aria-labelledby={g.id}>
<h2 id={g.id} class="kicker">
<Star size={11} />
{g.label}
</h2>
<TickScale width={150} height={10} count={30} class="ticks" />
<ul>
{g.links.map(([label, href]) => (
<li>
<a href={href}>{label}</a>
</li>
))}
</ul>
</section>
))
}
</nav>
</PageShell>
<style>
.lost {
max-width: 680px;
margin: 0 auto;
padding: 40px 0 24px;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
text-align: center;
}
.lost :global(.seal) {
opacity: 0.85;
filter: drop-shadow(0 0 22px rgba(var(--gk-teal-rgb), 0.35));
}
/* Glow is for the night sky; on Starlight the foil stands on its own. */
:global(:root[data-theme="light"]) .lost :global(.seal) {
opacity: 1;
filter: none;
}
.eyebrow {
font-size: 11px;
letter-spacing: 0.3em;
font-weight: 600;
color: var(--gk-gold-bright);
text-transform: uppercase;
}
h1 {
margin: 0;
font-size: clamp(32px, 6vw, 46px);
letter-spacing: 0.06em;
}
.lede {
margin: 0;
font-size: 17px;
line-height: 1.6;
color: var(--gk-soft);
}
.actions {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
margin-top: 6px;
}
.path {
max-width: 680px;
margin: 0 auto;
padding: 16px 0;
}
.groups {
max-width: 680px;
margin: 0 auto;
padding: 8px 0 32px;
display: flex;
flex-wrap: wrap;
gap: 28px;
}
.group {
flex: 1 1 180px;
display: flex;
flex-direction: column;
gap: 8px;
}
.kicker {
margin: 0;
display: flex;
align-items: center;
gap: 8px;
font-family: var(--gk-font-body);
font-size: 11px;
letter-spacing: 0.3em;
font-weight: 600;
color: var(--gk-gold-bright);
text-transform: uppercase;
}
.group :global(.ticks) {
margin: -4px 0 2px;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
li a {
display: block;
padding: 6px 0;
border-top: 1px solid var(--gk-lines);
font-size: 15px;
font-weight: 600;
color: var(--gk-text);
text-decoration: none;
transition: color var(--gk-ease);
}
li a:hover {
color: var(--gk-teal-bright);
}
</style>