Redesign UI with new token system, primitives, and motion

Restraint-first design language: neutral gray surfaces + one blue accent
(cyan/amber/red reserved for state), 8pt spacing, softer radii, one
ambient + one raised shadow, standardized motion tokens. Alfa Slab One
demoted to BrandMark only; Inter carries all page copy.

New primitives (Button, Card, Badge, Stat, SectionHeader, Skeleton,
EmptyState, Tabs, Breadcrumbs, StatusPip, Field, Input, Select, Textarea)
plus motion utilities (tweenNumber, viewTransition) and a global toast
system. All eight route surfaces rewritten on top: Fleet gains an
overview hero, Server detail restructures into tabs, Deployments gets a
collapsible drift summary + per-server pipeline visual, and the
scripts/configs/plugins/betas flows adopt the new form primitives.

Sticky translucent nav with backdrop blur, view-transition cross-fades
between routes, staggered card entrance on Fleet, breathing status pip
halo, tabular-numeric player counts. Gmail-style g-key nav shortcuts.
Mobile stacks below 720px; prefers-reduced-motion neutralizes durations.
Legacy tokens preserved as aliases so any un-migrated code still renders.
This commit is contained in:
2026-09-14 19:48:48 -04:00
parent efd4a4fa66
commit a2d4a38925
34 changed files with 5093 additions and 3144 deletions
@@ -0,0 +1,102 @@
<script lang="ts">
interface Props {
value?: string;
type?: 'text' | 'search' | 'email' | 'number' | 'url' | 'password' | 'file';
placeholder?: string;
id?: string;
name?: string;
required?: boolean;
disabled?: boolean;
readonly?: boolean;
autocomplete?: import('svelte/elements').FullAutoFill;
mono?: boolean;
onchange?: (e: Event) => void;
oninput?: (e: Event) => void;
accept?: string;
}
let {
value = $bindable(''),
type = 'text',
placeholder,
id,
name,
required = false,
disabled = false,
readonly = false,
autocomplete,
mono = false,
onchange,
oninput,
accept
}: Props = $props();
</script>
<input
class="input"
class:mono
{type}
{id}
{name}
{placeholder}
{required}
{disabled}
{readonly}
{autocomplete}
{accept}
bind:value
{onchange}
{oninput}
/>
<style>
.input {
width: 100%;
padding: 9px 12px;
background: var(--surface-sunken);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
color: var(--text-primary);
font-family: var(--font-body);
font-size: var(--text-sm);
line-height: 1.4;
transition:
border-color var(--dur-fast) var(--ease-standard),
background-color var(--dur-fast) var(--ease-standard),
box-shadow var(--dur-fast) var(--ease-standard);
}
.input.mono { font-family: var(--font-mono); }
.input::placeholder { color: var(--text-quaternary); }
.input:hover { border-color: var(--border-strong); }
.input:focus {
outline: none;
border-color: var(--accent-primary);
background: var(--surface);
box-shadow: 0 0 0 3px rgba(46, 134, 209, 0.15);
}
.input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.input[type='file'] {
padding: 6px;
cursor: pointer;
}
.input[type='file']::file-selector-button {
margin-right: var(--space-3);
padding: 6px 12px;
background: var(--surface-raised);
color: var(--text-primary);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
cursor: pointer;
font-family: var(--font-body);
font-size: var(--text-xs);
font-weight: var(--weight-medium);
transition: background-color var(--dur-fast) var(--ease-standard);
}
.input[type='file']::file-selector-button:hover {
background: var(--surface-overlay);
}
</style>