Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions src/components/ui/TerminalPath.astro
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
---
interface Props {
command?: "cat" | "ls" | "exit";
value?: string;
}

const pathname = Astro.url.pathname;

const cleanPath = pathname.replace(/^\/|\/$/g, "");
const cleanPath = pathname.replace(/^\/+|\/+$/g, "");
const isDirectory = ["projects", "posts", "games"].includes(cleanPath);

const filePath = cleanPath === "" ? "home.hy" : `${cleanPath}.hy`;
const command = Astro.props.command ?? (isDirectory ? "ls" : "cat");
const value =
Astro.props.value ??
(isDirectory
? `${cleanPath}/`
: cleanPath === ""
? "home.hy"
: `${cleanPath}.hy`);
---

<div class="terminal-path shell" aria-label={`Current path: ${filePath}`}>
<div
class="terminal-path shell"
aria-label={`Terminal command: ${command} ${value}`}
>
<span class="terminal-path__command">&gt;</span>
<span class="terminal-path__command"> cat </span>
<span class="terminal-path__value">{filePath}</span>
<span class="terminal-path__command"> {command} </span>
<span class="terminal-path__value">{value}</span>
</div>

<style>
Expand Down
6 changes: 6 additions & 0 deletions src/data/ascii/404.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
██╗ ██╗ ██████╗ ██╗ ██╗
██║ ██║██╔═████╗██║ ██║
███████║██║██╔██║███████║
╚════██║████╔╝██║╚════██║
██║╚██████╔╝ ██║
╚═╝ ╚═════╝ ╚═╝
10 changes: 8 additions & 2 deletions src/layouts/BaseLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ interface Props {
title?: string;
description?: string;
index?: boolean;
terminalCommand?: "cat" | "ls" | "exit";
terminalValue?: string;
searchIndex?: boolean;
}

const {
title = "Hyde.dev",
description = "Personal developer portfolio.",
index = true,
terminalCommand,
terminalValue,
searchIndex = true,
} = Astro.props;

const canonical = new URL(Astro.url.pathname, Astro.site);
Expand All @@ -36,9 +42,9 @@ const canonical = new URL(Astro.url.pathname, Astro.site);
<title>{title}</title>
</head>

<body>
<body data-pagefind-ignore={searchIndex ? undefined : "all"}>
<Topbar />
<TerminalPath />
<TerminalPath command={terminalCommand} value={terminalValue} />
<slot />
</body>
</html>
Expand Down
158 changes: 158 additions & 0 deletions src/pages/404.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import artwork from "../data/ascii/404.txt?raw";

// Keep indentation within the artwork; only remove surrounding blank lines.
const ascii = artwork.replace(/\r\n/g, "\n").replace(/^\n+|\n+$/g, "");
const columns = Math.max(1, ...ascii.split("\n").map((line) => line.length));
const links = [
{ href: "/", label: "Go Home" },
{ href: "/projects", label: "View Projects" },
{ href: "/posts", label: "Read Posts" },
];
---

<BaseLayout
title="404 — Page not found | Hyde.dev"
description="No such file or directory. Find your way back to Hyde.dev."
index={false}
searchIndex={false}
terminalCommand="exit"
terminalValue="1"
>
<main class="shell not-found" aria-labelledby="not-found-title">
<div class="not-found__art" aria-hidden="true">
<pre style={`--ascii-columns: ${columns}`}>{ascii}</pre>
</div>

<div class="not-found__message">
<h1 id="not-found-title">
<span class="sr-only">404: </span>Page not found
</h1>
<p>No such file or directory.</p>
</div>

<nav class="not-found__links" aria-label="Find your way back">
{links.map(({ href, label }) => (
<a href={href}>
<span class="not-found__bracket" aria-hidden="true">
[
</span>
<span class="not-found__link-label">{label}</span>
<span class="not-found__arrow" aria-hidden="true">
&gt;
</span>
<span class="not-found__bracket" aria-hidden="true">
]
</span>
</a>
))}
</nav>
</main>
</BaseLayout>

<style>
.not-found {
padding-block: clamp(2rem, 6vw, 5rem) var(--space-12);
text-align: center;
}

.not-found__art {
container-type: inline-size;
margin-inline: auto;
max-width: 68rem;
color: var(--color-text);
mask-image: linear-gradient(
to right,
transparent,
black 12%,
black 88%,
transparent
);
}

.not-found__art pre {
margin: 0;
font-family: var(--font-mono);
font-size: min(14px, calc(100cqi / var(--ascii-columns) / 0.65));
line-height: 1.35;
letter-spacing: 0;
white-space: pre;
}

.not-found__message {
margin-top: clamp(1rem, 2vw, 4rem);
}

h1 {
margin: 0;
font-size: clamp(1.5rem, 3vw, 2.25rem);
font-weight: 400;
line-height: 1.4;
}

p {
margin: var(--space-1) 0 0;
font-size: clamp(0.875rem, 2vw, 1.25rem);
line-height: 1.6;
}

.not-found__links {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: var(--space-3) var(--space-8);
margin-top: var(--space-8);
padding-top: var(--space-3);
border-top: var(--border-width) solid var(--color-border);
}

.not-found__links a {
display: inline-flex;
align-items: center;
gap: var(--space-4);
min-height: 44px;
text-decoration: none;
}

.not-found__link-label {
text-decoration: underline;
text-underline-offset: 0.3em;
}

.not-found__bracket,
.not-found__arrow {
color: var(--color-text-muted);
}

.not-found__arrow {
transition: transform var(--duration-fast) var(--ease-standard);
}

.not-found__links a:hover .not-found__arrow {
color: var(--color-text);
transform: translateX(3px);
}

.not-found__links a:focus-visible {
outline: 2px solid var(--color-text);
outline-offset: 5px;
}

.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}

@media (max-width: 30rem) {
.not-found__links {
flex-direction: column;
align-items: center;
}
}
</style>
Loading