design: extend v1 to login / add-host / host-detail / job-log + lock components

Five hi-fi screens completing the Phase 1 surface, all in v1's dark
operator-console register.

  v1-login          Sparse centred card. Sign-in + first-error variant.
                    No marketing chrome; build version sits in footer
                    so a returning operator can spot agent drift.

  v1-add-host       Focused two-column page (form left, contextual
                    "what happens next" right) — not a modal. Two
                    states: form (state A) and minted-token result
                    with install command (state B). Backed by
                    POST /api/enrollment-tokens (P1-32).

  v1-host-detail    Persistent header (status dot, mono name, tags,
                    primary CTAs, vitals strip) over four sub-tabs
                    (Snapshots / Jobs / Repo / Settings). Snapshots
                    is the default — the thing 90% of operators
                    want when they click a host name. Right rail
                    holds Recent activity, run-now stack, and a
                    danger-zone panel.

  v1-job-log        WS-streamed log view. Three states: running (live
                    progress bar + auto-scroll cursor), succeeded
                    (summary stats + final lines), failed (error
                    panel + tail). Backed by WS /api/jobs/{id}/stream
                    (P1-21 remainder).

  v1-components     The load-bearing reference. 14 sections covering
                    tokens (colour + type scale), status, buttons,
                    form fields, tags, tabs, host row, log viewer,
                    progress bar, stat tile, modal, toast, install
                    snippet, empty-state pattern. Every CSS class is
                    real and copy-able into the Go template build.

This locks the visual register before P1-23 onwards. Each Phase 1
template gets a {{define}} matching a section in v1-components.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 19:05:39 +01:00
parent cca525a04d
commit 8b7b1479a1
5 changed files with 1995 additions and 0 deletions
+148
View File
@@ -0,0 +1,148 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>restic-manager · v1 Login</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--bg: oklch(0.17 0.006 250); --panel: oklch(0.20 0.007 250);
--panel-hi: oklch(0.23 0.008 250);
--line: oklch(0.27 0.010 250); --line-soft: oklch(0.23 0.008 250);
--ink: oklch(0.96 0.005 250); --ink-mid: oklch(0.78 0.005 250);
--ink-mute: oklch(0.58 0.006 250); --ink-fade: oklch(0.42 0.006 250);
--ok: oklch(0.78 0.14 155); --bad: oklch(0.70 0.20 25);
--accent: oklch(0.82 0.12 195);
}
html, body { background: var(--bg); color: var(--ink); }
body { font-family: 'Inter', system-ui, sans-serif; min-height: 100vh; }
.mono { font-family: 'JetBrains Mono', ui-monospace, monospace; font-variant-numeric: tabular-nums; }
::selection { background: color-mix(in oklch, var(--accent), transparent 70%); }
.text-pretty { text-wrap: pretty; }
.field-label { font-size: 12px; color: var(--ink-mid); margin-bottom: 6px; display: block; letter-spacing: 0.005em; }
.field {
width: 100%; padding: 10px 12px;
background: var(--panel); border: 1px solid var(--line-soft);
color: var(--ink); border-radius: 5px;
font-size: 13px; font-family: inherit; outline: none;
transition: border-color 120ms ease;
}
.field:focus { border-color: var(--accent); }
.field.mono { font-family: 'JetBrains Mono', monospace; font-size: 12px; letter-spacing: 0.01em; }
.btn {
font-size: 13px; font-weight: 500;
padding: 9px 14px; border-radius: 5px;
background: transparent; border: 1px solid var(--line); color: var(--ink-mid);
transition: all 120ms ease;
}
.btn:hover { background: var(--panel-hi); color: var(--ink); }
.btn-primary { color: oklch(0.18 0.01 195); background: var(--accent); border-color: var(--accent); }
.btn-primary:hover { filter: brightness(1.08); }
.btn-block { width: 100%; padding: 10px 14px; }
.doc { max-width: 1280px; margin: 0 auto; padding: 0 32px; }
.philosophy { padding: 56px 0 32px; border-bottom: 1px solid var(--line-soft); }
.philosophy h1 { font-size: 22px; font-weight: 600; letter-spacing: -0.01em; }
.philosophy p { color: var(--ink-mid); max-width: 680px; margin-top: 14px; line-height: 1.65; text-wrap: pretty; }
.philosophy .meta { color: var(--ink-fade); font-size: 12px; margin-top: 14px; }
.stage-frame { margin: 48px -32px; border-top: 1px solid var(--line-soft); border-bottom: 1px solid var(--line-soft); }
</style>
</head>
<body>
<div class="doc">
<header class="philosophy">
<div class="text-xs uppercase tracking-[0.18em] text-[color:var(--ink-fade)] mb-3">v1 · Login</div>
<h1>Sign in.</h1>
<p>
The first chrome an operator meets. Everything irrelevant to the act of
signing in is removed: no marketing, no “sign up”, no animated background.
The form lives where the cursor needs it; the build version sits in the
footer so a returning operator can spot a mismatch with the agents.
</p>
<p class="meta">
First-run operators land on the empty <span class="mono" style="color: var(--ink-mid);">/bootstrap</span>
page instead — see the dedicated mockup. A regular sign-in always has at
least one user already minted.
</p>
</header>
<div class="stage-frame">
<div style="background: var(--bg); min-height: 720px; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 48px 32px;">
<!-- centred card -->
<div style="width: 360px;">
<!-- wordmark -->
<div class="flex items-baseline gap-2 mb-10" style="justify-content: center;">
<div class="mono" style="font-size: 16px; color: var(--ink); font-weight: 500; letter-spacing: 0.01em;">restic-manager</div>
</div>
<h2 style="font-size: 18px; font-weight: 500; letter-spacing: -0.005em; margin-bottom: 28px; text-align: center;">Sign in to continue</h2>
<form>
<div style="margin-bottom: 14px;">
<label class="field-label" for="login-username">Username</label>
<input id="login-username" type="text" class="field mono" autocomplete="username" value="steve">
</div>
<div style="margin-bottom: 22px;">
<label class="field-label" for="login-password">Password</label>
<input id="login-password" type="password" class="field" autocomplete="current-password" value="••••••••••••••••">
</div>
<button type="submit" class="btn btn-primary btn-block">Sign in</button>
</form>
<div style="margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--line-soft); text-align: center;">
<p class="text-pretty" style="font-size: 12px; color: var(--ink-mute); line-height: 1.65;">
Forgot your password? An admin can reset it from
<span class="mono" style="color: var(--ink-mid);">Settings → Users</span>.
Theres no recovery email — this is self-hosted infrastructure.
</p>
</div>
</div>
<!-- footer -->
<div style="margin-top: 80px; display: flex; gap: 14px; align-items: center; font-size: 11px; color: var(--ink-fade);">
<span class="mono">restic-manager v0.1.0-alpha</span>
<span>·</span>
<span class="mono">build f9c2351</span>
<span>·</span>
<a class="underline underline-offset-4 decoration-1" style="text-decoration-color: var(--line);">docs</a>
<span>·</span>
<a class="underline underline-offset-4 decoration-1" style="text-decoration-color: var(--line);">source</a>
</div>
</div>
</div>
<!-- error variant -->
<section style="margin: 48px 0 96px;">
<h2 style="font-size: 14px; font-weight: 600; color: var(--ink-mute); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 20px;">Error variant</h2>
<div style="background: var(--panel); border: 1px solid var(--line-soft); border-radius: 7px; padding: 32px;">
<div style="width: 360px; margin: 0 auto;">
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 24px; text-align: center;">Sign in to continue</h3>
<div style="background: color-mix(in oklch, var(--bad), transparent 88%); border: 1px solid color-mix(in oklch, var(--bad), transparent 70%); padding: 10px 12px; border-radius: 5px; margin-bottom: 18px; font-size: 12px; color: oklch(0.85 0.10 25);">
Invalid username or password.
</div>
<div style="margin-bottom: 14px;">
<label class="field-label">Username</label>
<input type="text" class="field mono" value="steve">
</div>
<div style="margin-bottom: 22px;">
<label class="field-label">Password</label>
<input type="password" class="field" value="••••••••">
</div>
<button class="btn btn-primary btn-block">Sign in</button>
</div>
</div>
</section>
</div>
</body>
</html>