|
@@ -0,0 +1,139 @@
|
|
|
|
|
+<script>
|
|
|
|
|
+ import { page } from '$app/state';
|
|
|
|
|
+ import logoMark from '$lib/images/logo_white.svg';
|
|
|
|
|
+
|
|
|
|
|
+ const status = $derived(page.status);
|
|
|
|
|
+ const isNotFound = $derived(status === 404);
|
|
|
|
|
+ const title = $derived(isNotFound ? 'Page not found' : 'Something broke');
|
|
|
|
|
+ const message = $derived(
|
|
|
|
|
+ isNotFound
|
|
|
|
|
+ ? 'That route is not on the board. Head back and keep training.'
|
|
|
|
|
+ : (page.error?.message ?? 'Unexpected error. Try again.')
|
|
|
|
|
+ );
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<svelte:head>
|
|
|
|
|
+ <title>{status} — Torus</title>
|
|
|
|
|
+</svelte:head>
|
|
|
|
|
+
|
|
|
|
|
+<div class="page">
|
|
|
|
|
+ <header class="top">
|
|
|
|
|
+ <a class="brand" href="/">
|
|
|
|
|
+ <img class="brand-mark" src={logoMark} alt="" width="28" height="28" />
|
|
|
|
|
+ <span class="brand-name">Torus</span>
|
|
|
|
|
+ </a>
|
|
|
|
|
+ <a class="btn btn-ghost" href="/register">Create account</a>
|
|
|
|
|
+ </header>
|
|
|
|
|
+
|
|
|
|
|
+ <main>
|
|
|
|
|
+ <section class="panel" aria-labelledby="error-title">
|
|
|
|
|
+ <p class="code">{status}</p>
|
|
|
|
|
+ <h1 id="error-title">{title}</h1>
|
|
|
|
|
+ <p class="lede">{message}</p>
|
|
|
|
|
+ <div class="actions">
|
|
|
|
|
+ <a class="btn btn-primary" href="/">Back home</a>
|
|
|
|
|
+ {#if isNotFound}
|
|
|
|
|
+ <a class="btn btn-ghost" href="/register">Create account</a>
|
|
|
|
|
+ {/if}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </section>
|
|
|
|
|
+ </main>
|
|
|
|
|
+</div>
|
|
|
|
|
+
|
|
|
|
|
+<style>
|
|
|
|
|
+ .page {
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .top {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ gap: 16px;
|
|
|
|
|
+ padding: 16px 20px;
|
|
|
|
|
+ border-bottom: 1px solid var(--border);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .brand {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ letter-spacing: -0.02em;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .brand-mark {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ width: 28px;
|
|
|
|
|
+ height: 28px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .brand-name {
|
|
|
|
|
+ font-size: 15px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ main {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding: 48px 20px 64px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .panel {
|
|
|
|
|
+ width: min(440px, 100%);
|
|
|
|
|
+ background: var(--surface);
|
|
|
|
|
+ border: 1px solid var(--border);
|
|
|
|
|
+ border-radius: var(--radius-lg);
|
|
|
|
|
+ padding: 28px 24px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .code {
|
|
|
|
|
+ margin: 0 0 12px;
|
|
|
|
|
+ font-size: 0.8rem;
|
|
|
|
|
+ font-weight: 750;
|
|
|
|
|
+ letter-spacing: 0.08em;
|
|
|
|
|
+ text-transform: uppercase;
|
|
|
|
|
+ color: var(--heat);
|
|
|
|
|
+ font-variant-numeric: tabular-nums;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ h1 {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: clamp(1.6rem, 4vw, 2rem);
|
|
|
|
|
+ letter-spacing: -0.03em;
|
|
|
|
|
+ font-weight: 750;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .lede {
|
|
|
|
|
+ margin: 10px 0 0;
|
|
|
|
|
+ color: var(--text-muted);
|
|
|
|
|
+ font-size: 0.98rem;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ margin-top: 24px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @media (min-width: 720px) {
|
|
|
|
|
+ .top {
|
|
|
|
|
+ padding: 16px 28px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ main {
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 64px 28px 80px;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .panel {
|
|
|
|
|
+ padding: 32px 28px;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+</style>
|