Add components and improve error page

This commit is contained in:
2026-03-15 01:22:46 +00:00
parent 54ea6ebf59
commit 061598a0ad
12 changed files with 687 additions and 205 deletions

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 2496 3495"><path stroke="#000" stroke-miterlimit="10" d="M-29-28h2552v4439H-29z"/><path fill="#fff" stroke="#000" stroke-miterlimit="10" stroke-width=".9" d="M0 0h2495v3494H0z"/><path d="m814 2164-137 2q-2-28-11-46-28-54-120-54-52 0-83 21a65 65 0 0 0-32 56q0 27 18 40 21 17 97 26 158 19 213 65 61 51 61 137 0 97-71 154-71 56-193 56-124 0-199-62-77-62-80-167h140q3 52 41 82 37 29 102 29 56 0 90-23 33-22 33-61 0-17-9-31-20-31-93-40-122-16-167-33a175 175 0 0 1-117-164q0-92 68-146 69-54 181-54 122 0 192 57 72 57 76 156m104-216h126v154h108v101h-108v251q0 34 11 42 11 9 88 9h8v107l-36 1h-89q-61-4-88-37-12-17-16-41t-4-115v-217h-72v-101h72zm508 140q111 0 180 72t69 190q0 122-66 194-67 72-180 72-112 0-180-71-67-70-67-190 0-121 67-194 68-73 177-73m2 106q-57 0-91 44t-34 119q0 70 34 112 35 42 91 42 57 0 90-42 32-42 32-115t-33-117q-33-43-89-43m311-92h122v33q49-47 129-47 102 0 166 74 65 73 65 190 0 119-63 191-63 73-167 73-78 0-124-46v184h-128zm233 92q-66 0-91 51-16 31-16 121 0 61 16 94 25 50 95 50 56 0 88-41 32-42 32-115 0-74-34-117-33-43-90-43"/><circle cx="1247.7" cy="1049.7" r="708.7" fill="#e31837"/><path fill="#1a1a1a" d="M-17 2908h2530v42H-17z"/><path fill="#1a1a1a" d="M978 3051v46H857v77h94v44h-94v79h121v46H799v-292zm39 292v-219h33q9 0 12 3 4 4 5 11l3 22q10-18 23-29t30-11q15 0 24 7l-4 42-3 6-6 1-9-1-12-1-14 2-10 7q-5 4-9 10l-7 14v136zm161 0v-219h33q8 0 12 3 3 4 4 11l3 22q10-18 23-29t31-11q14 0 23 7l-4 42q0 4-3 6l-5 1-10-1-11-1q-8 0-14 2l-11 7q-5 4-8 10l-7 14v136zm252-222q24 0 44 8 20 7 34 22t21 36 8 46-8 47-21 36-34 22-44 8-44-8q-20-7-34-22-15-15-22-36-8-21-8-47t8-46 22-36 34-22 44-8m0 183q25 0 37-18t12-52-12-53-37-17-38 17-12 53 12 52 38 18m145 39v-219h33q8 0 12 3 3 4 4 11l3 22q10-18 23-29 14-11 31-11 14 0 24 7l-5 42q0 4-2 6l-6 1-10-1-11-1q-8 0-14 2l-11 7-8 10-7 14v136z" aria-label="Error" font-family="Carlito" font-size="451" font-weight="700" style="line-height:.9;-inkscape-font-specification:&quot;Carlito Bold&quot;;text-align:center;white-space:pre" text-anchor="middle"/></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,82 @@
<script lang="ts">
import type { HTMLButtonAttributes, HTMLAnchorAttributes } from 'svelte/elements';
type ButtonColor = 'brand' | 'accent' | 'bg';
interface Props {
children: import('svelte').Snippet;
href?: string;
color?: ButtonColor;
onclick?: (e: MouseEvent) => void;
[key: string]: any;
}
let { children, href, color = 'bg', onclick, ...rest }: Props = $props();
const isLink = $derived(!!href);
const isExternal = $derived(href?.startsWith('http'));
</script>
{#if isLink}
<a
{href}
class="btn {color}"
target={isExternal ? '_blank' : undefined}
rel={isExternal ? 'noopener noreferrer' : undefined}
{...rest}
>
{@render children?.()}
</a>
{:else}
<button class="btn {color}" {onclick} {...rest}>
{@render children?.()}
</button>
{/if}
<style>
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.4rem 1.2rem;
width: fit-content;
min-width: 90px;
min-height: 48px;
border: none;
border-radius: 20px;
cursor: pointer;
text-decoration: none;
font-family: 'URW Gothic', sans-serif;
letter-spacing: 0.05ch;
font-size: 1rem;
font-weight: 600;
transition: all 0.2s;
box-shadow: var(--shadow-std);
user-select: none;
-webkit-user-select: none;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
.accent {
background-color: var(--color-accent);
color: var(--color-title);
}
.brand {
background-color: var(--color-brand);
color: rgb(30, 30, 30);
}
.bg {
background-color: var(--color-bg-light);
color: var(--color-title);
}
.btn:hover {
filter: brightness(1.5);
}
.btn.active {
transform: scale(0.98);
}
</style>

View File

@@ -0,0 +1,94 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import type { HTLMInputAttributes } from 'svelte/elements';
interface Props extends HTMLInputAttributes {
value?: string;
label?: string;
placeholder?: string;
type?: 'text' | 'password' | 'email' | 'number' | 'search' | 'tel' | 'url';
error?: string;
uppercase?: boolean;
[key: string]: any;
}
let {
value = $bindable(''),
label,
placeholder = '',
type = 'text',
error = '',
uppercase = false,
...rest
}: Props = $props();
let isFocussed = $state(false);
</script>
<div class="input-wrapper" class:focussed={isFocussed} class:has-error={!!error}>
{#if label}
<label for="adaptive-input">{label}</label>
{/if}
<input
id="adaptive-input"
class:all-caps={uppercase}
{type}
{placeholder}
bind:value={value}
onfocus={() => isFocussed = true}
onblur={() => isFocussed = false}
{...rest}
/>
{#if error}
<span class="error-message" transition:fade>{error}</span>
{/if}
</div>
<style>
.input-wrapper {
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
font-family: 'URW Gothic', sans-serif;
}
label {
font-size: 0.9rem;
font-weight: 400;
color: var(--color-title)
}
input {
min-height: 48px;
padding: 0 16px;
background-color: var(--color-title);
border: 2px solid transparent;
border-radius: 20px;
color: var(--color-bg-dark);
font-size: 1.5rem;
transition: all 0.2s ease-in-out;
outline: none;
text-align: center;
}
.all-caps {
text-transform: uppercase;
}
.focussed input {
border-color: var(--color-bg-light);
}
.has-error input {
border-color: #ff4d4d;
}
.error-message {
color: #ff4d4d;
font-size: 1rem;
text-align: center;
}
</style>

View File

@@ -0,0 +1,94 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import { IconHelpCircle } from '@tabler/icons-svelte';
import { slide } from 'svelte/transition';
interface Props {
children: Snippet;
header?: string;
helpText?: string;
}
let {
children,
header = "",
helpText,
}: Props = $props();
let showHelp = $state(false);
</script>
<div class="card">
{#if header || helpText}
<header class="card-header">
<div class="header-content">
{header}
</div>
{#if helpText}
<button
type="button"
class="help-toggle"
onclick={() => showHelp = !showHelp}
aria-label="Show Help"
>
<IconHelpCircle size={26} stroke={2.25} color={showHelp ? 'var(--color-brand)' : 'var(--color-title)'} />
</button>
{/if}
</header>
{#if showHelp && helpText}
<div class="help-drawer" transition:slide={{ duration: 400 }}>
<p>{helpText}</p>
</div>
{/if}
{/if}
<div class="card-body">
{@render children?.()}
</div>
</div>
<style>
.card {
background: var(--color-accent);
position: relative;
border-radius: 20px;
overflow: hidden;
width: 95%;
max-width: 600px;
text-align: center;
font-family: 'URW Gothic', sans-serif;
color: var(--color-title);
}
.header-content { flex: 1;
font-size: 1.5rem; font-weight: 600; }
.help-toggle {
position: absolute;
top: 8px;
right: 8px;
background: none;
border: none;
padding: 4px;
cursor: help;
opacity: 0.6;
z-index: 2;
transition: opacity 0.2s, transform 0.2s;
}
.help-toggle:hover {
opacity: 1;
transform: scale(1.1);
}
.help-drawer {
background-color: var(--color-accent);
padding: 4px 16px;
font-size: 0.95rem;
line-height: 1.2;
margin: auto;
border-bottom: 1px solid rgba(255,255,255,0.05);
color: var(--color-title);
}
</style>

View File

@@ -4,78 +4,79 @@
* Copyright (c) 2014,2015 by (URW)++ Design & Development
*/
@font-face {
font-family: 'URW Gothic';
src: url('/type/urwgothic/urwgothic-book-webfont.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
font-family: 'URW Gothic';
src: url('/type/urwgothic/urwgothic-book-webfont.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
/* * URW Gothic is licensed under the SIL Open Font License, Version 1.1.
* Copyright (c) 2014,2015 by (URW)++ Design & Development
*/
@font-face {
font-family: 'URW Gothic';
src: url('/type/urwgothic/urwgothic-bookoblique-webfont.woff2') format('woff2');
font-weight: 400;
font-style: italic;
font-display: swap;
font-family: 'URW Gothic';
src: url('/type/urwgothic/urwgothic-bookoblique-webfont.woff2') format('woff2');
font-weight: 400;
font-style: italic;
font-display: swap;
}
/* * URW Gothic is licensed under the SIL Open Font License, Version 1.1.
* Copyright (c) 2014,2015 by (URW)++ Design & Development
*/
@font-face {
font-family: 'URW Gothic';
src: url('/type/urwgothic/urwgothic-demi-webfont.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
font-family: 'URW Gothic';
src: url('/type/urwgothic/urwgothic-demi-webfont.woff2') format('woff2');
font-weight: 600;
font-style: normal;
font-display: swap;
}
/* * URW Gothic is licensed under the SIL Open Font License, Version 1.1.
* Copyright (c) 2014,2015 by (URW)++ Design & Development
*/
@font-face {
font-family: 'URW Gothic';
src: url('/type/urwgothic/urwgothic-demioblique-webfont.woff2') format('woff2');
font-weight: 600;
font-style: italic;
font-display: swap;
font-family: 'URW Gothic';
src: url('/type/urwgothic/urwgothic-demioblique-webfont.woff2') format('woff2');
font-weight: 600;
font-style: italic;
font-display: swap;
}
:root {
/* Brand Colours */
--color-brand: #4fd1d1;
--color-accent: #3c6f79;
--color-title: #ebebeb;
--color-bg-light: #404c55;
--color-bg-dark: #2b343c;
/* Shadows */
--color-shadow: hsla(210, 20%, 5%, 0.35);
--shadow-std: 0 4px 12px var(--color-shadow);
--shadow-up: 0 -4px 12px var(--color-shadow);
}
:root {
/* Brand Colours */
--color-brand: #4fd1d1;
--color-accent: #3c6f79;
--color-title: #ebebeb;
--color-bg-light: #404c55;
--color-bg-dark: #2b343c;
/* Shadows */
--color-shadow: hsla(210, 20%, 5%, 0.35);
--shadow-std: 0 4px 12px var(--color-shadow);
--shadow-up: 0 -4px 12px var(--color-shadow);
--shadow-right: 4px 0 12px var(--color-shadow);
}
body {
margin: 0;
padding: 0;
background-color: var(--color-accent);
color: white;
-webkit-font-smoothing: antialiased;
-mos-osx-font-smoothing: grayscale;
overflow-x: hidden;
margin: 0;
padding: 0;
background-color: var(--color-accent);
color: white;
-webkit-font-smoothing: antialiased;
-mos-osx-font-smoothing: grayscale;
overflow-x: hidden;
}
a {
color: var(--color-brand);
color: var(--color-brand);
}
a:visited {
color: var(--color-brand);
color: var(--color-brand);
}
a:hover {
color: var(--color-accent);
}
color: var(--color-accent);
}