From 6afbbc5d9903415168dc7128909703db9509016d Mon Sep 17 00:00:00 2001 From: John Gatward Date: Fri, 20 Mar 2026 19:05:39 +0000 Subject: [PATCH] 404 --- 404.css | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 404.html | 84 +++++++++++++++++++++++++++ 404.js | 22 +++++++ 3 files changed, 277 insertions(+) create mode 100644 404.css create mode 100644 404.html create mode 100644 404.js diff --git a/404.css b/404.css new file mode 100644 index 0000000..f2a4648 --- /dev/null +++ b/404.css @@ -0,0 +1,171 @@ +body { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.not-found { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + padding: 7rem 2rem 5rem; + gap: 0; + position: relative; +} + +.nf-code { + font-family: 'Fraunces', serif; + font-size: clamp(7rem, 22vw, 16rem); + font-weight: 600; + line-height: 1; + color: var(--surface0); + letter-spacing: -0.02em; + user-select: none; + position: relative; + opacity: 0; + animation: fadeUp 0.6s 0.1s forwards; +} + + +.nf-eyebrow { + font-size: 0.7rem; + letter-spacing: 0.2em; + text-transform: uppercase; + color: var(--mauve); + margin-bottom: 0.75rem; + display: flex; + align-items: center; + gap: 0.75rem; + opacity: 0; + animation: fadeUp 0.6s 0.3s forwards; +} + +.nf-eyebrow::before, +.nf-eyebrow::after { + content: ''; + display: block; + height: 1px; + width: 3rem; + background: var(--mauve); + opacity: 0.5; +} + +.nf-heading { + font-family: 'Fraunces', serif; + font-size: clamp(1.6rem, 4vw, 2.8rem); + font-weight: 600; + color: var(--text); + line-height: 1.15; + margin-bottom: 1rem; + opacity: 0; + animation: fadeUp 0.6s 0.4s forwards; +} + +.nf-heading em { + font-style: italic; + color: var(--mauve); +} + +.nf-desc { + font-size: 0.88rem; + color: var(--subtext0); + max-width: 420px; + line-height: 1.8; + margin-bottom: 2.5rem; + opacity: 0; + animation: fadeUp 0.6s 0.5s forwards; +} + +.nf-terminal { + background: var(--mantle); + border: 1px solid var(--surface0); + border-radius: 8px; + padding: 1rem 1.5rem; + font-size: 0.78rem; + color: var(--subtext1); + text-align: left; + max-width: 440px; + width: 100%; + margin-bottom: 2.5rem; + opacity: 0; + animation: fadeUp 0.6s 0.6s forwards; +} + +.nf-terminal-bar { + display: flex; + gap: 0.5rem; + margin-bottom: 0.75rem; +} + +.nf-dot { + width: 10px; + height: 10px; + border-radius: 50%; +} + +.nf-dot--red { + background: var(--red); + opacity: 0.7; +} + +.nf-dot--yellow { + background: var(--yellow); + opacity: 0.7; +} + +.nf-dot--green { + background: var(--green); + opacity: 0.7; +} + +.nf-terminal-line { + display: flex; + gap: 0.6rem; + line-height: 1.9; +} + +.nf-prompt { + color: var(--green); +} + +.nf-cmd { + color: var(--text); +} + +.nf-err { + color: var(--red); +} + +.nf-comment { + color: var(--overlay1); +} + +.nf-cursor { + display: inline-block; + width: 0.55em; + height: 1em; + background: var(--mauve); + vertical-align: text-bottom; + animation: blink 1.1s step-end infinite; +} + +@keyframes blink { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0; + } +} + +.nf-actions { + display: flex; + gap: 1.25rem; + flex-wrap: wrap; + justify-content: center; + opacity: 0; + animation: fadeUp 0.6s 0.7s forwards; +} diff --git a/404.html b/404.html new file mode 100644 index 0000000..2cc9ff6 --- /dev/null +++ b/404.html @@ -0,0 +1,84 @@ + + + + + + + 404 — Havox + + + + + + + + + + + + + +
+ +
404
+ +

page not found

+

This page doesn't exist

+

+ Either this URL was wrong, something got moved, or you've stumbled onto a + dead link I haven't cleaned up yet. Wouldn't be the first time. +

+ + + + + +
+ + + + + + + + + diff --git a/404.js b/404.js new file mode 100644 index 0000000..94e6d9d --- /dev/null +++ b/404.js @@ -0,0 +1,22 @@ +(function () { + const pathEl = document.getElementById('typed-path'); + const responseEl = document.getElementById('nf-response'); + const hintEl = document.getElementById('nf-hint'); + const href = globalThis.location.href; + const path = (href.length >= 45) ? href.substring(0, 45) : href; + + let i = 0; + const interval = setInterval(() => { + if (i < path.length) { + pathEl.textContent += path[i++]; + } else { + clearInterval(interval); + setTimeout(() => { + responseEl.style.opacity = '1'; + setTimeout(() => { + hintEl.style.opacity = '1'; + }, 400); + }, 300); + } + }, 45); +})(); \ No newline at end of file