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:
@@ -0,0 +1,703 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>restic-manager · v1 Components</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>
|
||||
/* ============================================================
|
||||
v1 design tokens. Whatever the Go templates need, lives here.
|
||||
Anything not in this file does not exist in v1. New components
|
||||
get added here first, then templated.
|
||||
============================================================ */
|
||||
:root {
|
||||
/* surface */
|
||||
--bg: oklch(0.17 0.006 250);
|
||||
--panel: oklch(0.20 0.007 250);
|
||||
--panel-hi: oklch(0.23 0.008 250);
|
||||
|
||||
/* line */
|
||||
--line: oklch(0.27 0.010 250);
|
||||
--line-soft: oklch(0.23 0.008 250);
|
||||
|
||||
/* ink */
|
||||
--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);
|
||||
|
||||
/* state */
|
||||
--ok: oklch(0.78 0.14 155);
|
||||
--warn: oklch(0.82 0.13 80);
|
||||
--bad: oklch(0.70 0.20 25);
|
||||
--off: oklch(0.50 0.005 250);
|
||||
|
||||
/* one accent */
|
||||
--accent: oklch(0.82 0.12 195);
|
||||
--accent-dim:oklch(0.55 0.10 195);
|
||||
}
|
||||
html, body { background: var(--bg); color: var(--ink); }
|
||||
body { font-family: 'Inter', system-ui, sans-serif; }
|
||||
.mono { font-family: 'JetBrains Mono', monospace; font-variant-numeric: tabular-nums; }
|
||||
::selection { background: color-mix(in oklch, var(--accent), transparent 70%); }
|
||||
|
||||
/* ---------- LAYOUT PRIMITIVES ---------- */
|
||||
.doc { max-width: 1280px; margin: 0 auto; padding: 0 32px; }
|
||||
.panel { background: var(--panel); border: 1px solid var(--line-soft); }
|
||||
.hairline { box-shadow: inset 0 -1px 0 var(--line-soft); }
|
||||
|
||||
/* ---------- STATUS DOTS ---------- */
|
||||
.dot { width: 7px; height: 7px; border-radius: 9999px; display: inline-block; }
|
||||
.dot-online { background: var(--ok); box-shadow: 0 0 0 3px color-mix(in oklch, var(--ok), transparent 80%); }
|
||||
.dot-degraded { background: var(--warn); box-shadow: 0 0 0 3px color-mix(in oklch, var(--warn), transparent 80%); }
|
||||
.dot-offline { background: var(--off); }
|
||||
.dot-failed { background: var(--bad); box-shadow: 0 0 0 3px color-mix(in oklch, var(--bad), transparent 80%); }
|
||||
.pulse { animation: pulse 2.4s ease-in-out infinite; }
|
||||
@keyframes pulse {
|
||||
0%, 100% { box-shadow: 0 0 0 3px color-mix(in oklch, var(--accent), transparent 80%); }
|
||||
50% { box-shadow: 0 0 0 6px color-mix(in oklch, var(--accent), transparent 92%); }
|
||||
}
|
||||
|
||||
/* ---------- BUTTONS ---------- */
|
||||
.btn {
|
||||
font-size: 12px; font-weight: 500;
|
||||
padding: 6px 11px; border-radius: 5px;
|
||||
background: transparent; border: 1px solid var(--line);
|
||||
color: var(--ink-mid);
|
||||
transition: all 120ms ease; cursor: pointer;
|
||||
}
|
||||
.btn:hover { background: var(--panel-hi); color: var(--ink); }
|
||||
.btn:disabled, .btn[disabled] { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
|
||||
.btn-primary { color: oklch(0.18 0.01 195); background: var(--accent); border-color: var(--accent); }
|
||||
.btn-primary:hover { filter: brightness(1.08); }
|
||||
.btn-ghost { border-color: transparent; }
|
||||
.btn-ghost:hover { background: var(--panel-hi); border-color: transparent; }
|
||||
.btn-danger { color: var(--bad); border-color: color-mix(in oklch, var(--bad), transparent 70%); }
|
||||
.btn-danger:hover { background: color-mix(in oklch, var(--bad), transparent 88%); border-color: color-mix(in oklch, var(--bad), transparent 50%); color: oklch(0.85 0.10 25); }
|
||||
.btn-lg { font-size: 13px; padding: 9px 14px; }
|
||||
|
||||
/* ---------- TAGS / CHIPS ---------- */
|
||||
.tag {
|
||||
display: inline-flex; align-items: center; gap: 5px;
|
||||
font-size: 11px; line-height: 1; padding: 4px 7px;
|
||||
border: 1px solid var(--line); color: var(--ink-mid);
|
||||
border-radius: 3px; letter-spacing: 0.01em;
|
||||
}
|
||||
.tag-removable .x { color: var(--ink-fade); cursor: pointer; padding-left: 2px; }
|
||||
.tag-status {
|
||||
border: 1px solid color-mix(in oklch, var(--token), transparent 70%);
|
||||
color: var(--token);
|
||||
background: color-mix(in oklch, var(--token), transparent 88%);
|
||||
}
|
||||
|
||||
/* ---------- FORM FIELDS ---------- */
|
||||
.field-label { font-size: 12px; color: var(--ink-mid); margin-bottom: 6px; display: block; }
|
||||
.field-help { font-size: 12px; color: var(--ink-mute); margin-top: 6px; line-height: 1.55; text-wrap: pretty; }
|
||||
.field-error { font-size: 12px; color: oklch(0.85 0.10 25); margin-top: 6px; }
|
||||
.field {
|
||||
width: 100%; padding: 9px 12px;
|
||||
background: var(--bg); 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.invalid { border-color: color-mix(in oklch, var(--bad), transparent 50%); }
|
||||
.field.mono { font-family: 'JetBrains Mono', monospace; font-size: 12px; }
|
||||
.field.with-prefix { padding-left: 64px; }
|
||||
.field-prefix {
|
||||
position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
|
||||
font-family: 'JetBrains Mono', monospace; font-size: 12px;
|
||||
color: var(--ink-mute); pointer-events: none;
|
||||
}
|
||||
|
||||
/* ---------- TABS ---------- */
|
||||
.nav-tab { font-size: 13px; padding: 18px 0; color: var(--ink-mute); border-bottom: 2px solid transparent; margin-right: 28px; cursor: pointer; }
|
||||
.nav-tab.active { color: var(--ink); border-color: var(--accent); }
|
||||
.nav-tab:hover { color: var(--ink); }
|
||||
.sub-tab { font-size: 13px; padding: 12px 0; color: var(--ink-mute); border-bottom: 1.5px solid transparent; margin-right: 24px; cursor: pointer; }
|
||||
.sub-tab.active { color: var(--ink); border-color: var(--ink); }
|
||||
|
||||
/* ---------- HOST ROW (the dashboard's load-bearing component) ---------- */
|
||||
.host-row {
|
||||
display: grid; align-items: center;
|
||||
grid-template-columns: 24px 1.4fr 0.95fr 1.5fr 0.75fr 0.7fr 0.7fr 1.1fr 92px;
|
||||
padding: 11px 16px; font-size: 13px;
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
.host-row.head { padding-top: 10px; padding-bottom: 10px; font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em; border-left-width: 3px; }
|
||||
.host-row.degraded { border-left-color: color-mix(in oklch, var(--warn), transparent 50%); }
|
||||
.host-row.failed { border-left-color: color-mix(in oklch, var(--bad), transparent 50%); }
|
||||
.host-row.offline { border-left-color: color-mix(in oklch, var(--off), transparent 70%); }
|
||||
.host-row:hover { background: var(--panel-hi); }
|
||||
|
||||
/* ---------- LOG VIEWER ---------- */
|
||||
.log {
|
||||
background: var(--bg); border: 1px solid var(--line-soft);
|
||||
border-radius: 7px;
|
||||
font-family: 'JetBrains Mono', monospace; font-size: 12px; line-height: 1.7;
|
||||
overflow: hidden;
|
||||
}
|
||||
.log-line { display: grid; grid-template-columns: 14ch 8ch 1fr; column-gap: 14px; padding: 1px 16px; align-items: baseline; }
|
||||
.log-line:first-child { padding-top: 12px; }
|
||||
.log-line:last-child { padding-bottom: 12px; }
|
||||
.log-ts { color: var(--ink-fade); }
|
||||
.log-tag { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-fade); }
|
||||
.log-stream-stdout { color: var(--ink-mid); }
|
||||
.log-stream-stderr { color: oklch(0.78 0.13 50); }
|
||||
.log-stream-event { color: var(--accent); }
|
||||
|
||||
/* ---------- PROGRESS BAR ---------- */
|
||||
.progress-track { background: var(--bg); border: 1px solid var(--line-soft); height: 6px; border-radius: 9999px; overflow: hidden; }
|
||||
.progress-fill { height: 100%; background: var(--accent); border-radius: 9999px; transition: width 250ms ease; }
|
||||
.progress-fill.ok { background: var(--ok); }
|
||||
.progress-fill.bad { background: var(--bad); }
|
||||
|
||||
/* ---------- TOAST ---------- */
|
||||
.toast {
|
||||
display: flex; align-items: flex-start; gap: 12px;
|
||||
background: var(--panel); border: 1px solid var(--line-soft);
|
||||
padding: 12px 14px; border-radius: 7px;
|
||||
box-shadow: 0 8px 24px -12px rgba(0,0,0,0.4);
|
||||
max-width: 420px;
|
||||
}
|
||||
.toast-ok { border-left: 3px solid var(--ok); }
|
||||
.toast-bad { border-left: 3px solid var(--bad); }
|
||||
.toast .x { color: var(--ink-fade); cursor: pointer; padding: 2px 4px; margin-left: auto; }
|
||||
|
||||
/* ---------- MODAL ---------- */
|
||||
.modal-backdrop {
|
||||
position: relative; background: rgba(0,0,0,0.45);
|
||||
border: 1px dashed var(--line-soft); border-radius: 7px;
|
||||
padding: 48px;
|
||||
}
|
||||
.modal {
|
||||
background: var(--panel); border: 1px solid var(--line-soft);
|
||||
border-radius: 8px; max-width: 480px; margin: 0 auto;
|
||||
box-shadow: 0 24px 48px -16px rgba(0,0,0,0.6);
|
||||
}
|
||||
.modal-head { padding: 18px 22px; border-bottom: 1px solid var(--line-soft); }
|
||||
.modal-body { padding: 18px 22px; font-size: 13px; line-height: 1.65; color: var(--ink-mid); text-wrap: pretty; }
|
||||
.modal-foot { padding: 14px 22px; border-top: 1px solid var(--line-soft); display: flex; gap: 8px; justify-content: flex-end; }
|
||||
|
||||
/* ---------- INSTALL SNIPPET ---------- */
|
||||
.snippet { border: 1px solid var(--line-soft); border-radius: 6px; overflow: hidden; }
|
||||
.snippet-head { display: flex; justify-content: space-between; align-items: center; padding: 10px 14px; border-bottom: 1px solid var(--line-soft); font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.1em; }
|
||||
.snippet pre {
|
||||
margin: 0; padding: 14px; font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 12px; color: var(--ink-mid); line-height: 1.7;
|
||||
white-space: pre-wrap; word-break: break-all;
|
||||
}
|
||||
.snippet pre .var { color: var(--accent); }
|
||||
|
||||
/* ---------- DOCUMENT SHELL FOR THIS REFERENCE PAGE ---------- */
|
||||
.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; }
|
||||
.section { padding: 48px 0 24px; border-bottom: 1px solid var(--line-soft); }
|
||||
.section h2 {
|
||||
font-size: 11px; font-weight: 600; color: var(--ink-fade);
|
||||
text-transform: uppercase; letter-spacing: 0.16em;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.section h2 .num { color: var(--ink); margin-right: 8px; }
|
||||
.section h2 .name { color: var(--ink); letter-spacing: 0.04em; font-weight: 600; }
|
||||
.section h2 .desc { color: var(--ink-fade); font-weight: 400; margin-left: 12px; }
|
||||
.swatch-row { display: grid; grid-template-columns: repeat(8, 1fr); gap: 12px; }
|
||||
.swatch { background: var(--panel); border: 1px solid var(--line-soft); border-radius: 6px; overflow: hidden; }
|
||||
.swatch .chip { height: 56px; }
|
||||
.swatch .meta { padding: 8px 10px; }
|
||||
.swatch .name { font-size: 12px; color: var(--ink); font-weight: 500; }
|
||||
.swatch .var-name { font-size: 11px; color: var(--ink-fade); font-family: 'JetBrains Mono', monospace; margin-top: 2px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="doc">
|
||||
|
||||
<!-- Philosophy preamble -->
|
||||
<header class="philosophy">
|
||||
<div class="text-xs uppercase tracking-[0.18em] text-[color:var(--ink-fade)] mb-3">v1 · Component reference</div>
|
||||
<h1>The system, in one place.</h1>
|
||||
<p>
|
||||
Every reusable piece across the v1 mockups, lifted out and shown beside its
|
||||
states. If something appears in a screen but not here, it shouldn’t — it’s
|
||||
either drift or a candidate for promotion. The Go templates (P1-23
|
||||
onwards) lean on this file: a partial gets a name in here before it gets
|
||||
a <code class="mono" style="color: var(--ink); background: var(--panel); padding: 1px 6px; border-radius: 3px;">{{define}}</code> in the templates.
|
||||
</p>
|
||||
<p class="meta">
|
||||
Every CSS class on this page is real and copy-able into the Tailwind
|
||||
build. Anything inline is a one-off and shouldn’t be.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<!-- ============================================================
|
||||
1 · TOKENS
|
||||
============================================================ -->
|
||||
<section class="section">
|
||||
<h2><span class="num">01</span><span class="name">Tokens</span><span class="desc">colours · type · spacing · the alphabet of v1</span></h2>
|
||||
|
||||
<!-- surface + ink + line -->
|
||||
<div style="margin-bottom: 28px;">
|
||||
<div style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 12px;">Surface, line, ink</div>
|
||||
<div class="swatch-row">
|
||||
<div class="swatch"><div class="chip" style="background: var(--bg);"></div><div class="meta"><div class="name">bg</div><div class="var-name">--bg</div></div></div>
|
||||
<div class="swatch"><div class="chip" style="background: var(--panel);"></div><div class="meta"><div class="name">panel</div><div class="var-name">--panel</div></div></div>
|
||||
<div class="swatch"><div class="chip" style="background: var(--panel-hi);"></div><div class="meta"><div class="name">panel-hi</div><div class="var-name">--panel-hi</div></div></div>
|
||||
<div class="swatch"><div class="chip" style="background: var(--line);"></div><div class="meta"><div class="name">line</div><div class="var-name">--line</div></div></div>
|
||||
<div class="swatch"><div class="chip" style="background: var(--ink);"></div><div class="meta"><div class="name">ink</div><div class="var-name">--ink</div></div></div>
|
||||
<div class="swatch"><div class="chip" style="background: var(--ink-mid);"></div><div class="meta"><div class="name">ink-mid</div><div class="var-name">--ink-mid</div></div></div>
|
||||
<div class="swatch"><div class="chip" style="background: var(--ink-mute);"></div><div class="meta"><div class="name">ink-mute</div><div class="var-name">--ink-mute</div></div></div>
|
||||
<div class="swatch"><div class="chip" style="background: var(--ink-fade);"></div><div class="meta"><div class="name">ink-fade</div><div class="var-name">--ink-fade</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- state + accent -->
|
||||
<div>
|
||||
<div style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 12px;">State + accent</div>
|
||||
<div class="swatch-row" style="grid-template-columns: repeat(5, 1fr);">
|
||||
<div class="swatch"><div class="chip" style="background: var(--ok);"></div><div class="meta"><div class="name">ok</div><div class="var-name">--ok · success / online</div></div></div>
|
||||
<div class="swatch"><div class="chip" style="background: var(--warn);"></div><div class="meta"><div class="name">warn</div><div class="var-name">--warn · degraded / cache-warning</div></div></div>
|
||||
<div class="swatch"><div class="chip" style="background: var(--bad);"></div><div class="meta"><div class="name">bad</div><div class="var-name">--bad · failed / alert</div></div></div>
|
||||
<div class="swatch"><div class="chip" style="background: var(--off);"></div><div class="meta"><div class="name">off</div><div class="var-name">--off · offline (neutral)</div></div></div>
|
||||
<div class="swatch"><div class="chip" style="background: var(--accent);"></div><div class="meta"><div class="name">accent</div><div class="var-name">--accent · running, links</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- type scale -->
|
||||
<div style="margin-top: 32px;">
|
||||
<div style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 16px;">Type scale</div>
|
||||
<div class="panel" style="border-radius: 7px; padding: 16px 20px;">
|
||||
<div style="display: grid; grid-template-columns: 70px 1fr 200px; align-items: baseline; padding: 6px 0; border-bottom: 1px solid var(--line-soft);">
|
||||
<span class="mono" style="font-size: 11px; color: var(--ink-fade);">28 / 500</span>
|
||||
<span style="font-size: 28px; font-weight: 500; letter-spacing: -0.018em;">Hero / numeric stat</span>
|
||||
<span class="mono" style="font-size: 11px; color: var(--ink-fade);">stat tile values</span>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: 70px 1fr 200px; align-items: baseline; padding: 6px 0; border-bottom: 1px solid var(--line-soft);">
|
||||
<span class="mono" style="font-size: 11px; color: var(--ink-fade);">22 / 500</span>
|
||||
<span style="font-size: 22px; font-weight: 500; letter-spacing: -0.012em;">Page title</span>
|
||||
<span class="mono" style="font-size: 11px; color: var(--ink-fade);">h1 in body</span>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: 70px 1fr 200px; align-items: baseline; padding: 6px 0; border-bottom: 1px solid var(--line-soft);">
|
||||
<span class="mono" style="font-size: 11px; color: var(--ink-fade);">18 / 500</span>
|
||||
<span style="font-size: 18px; font-weight: 500;">Section header</span>
|
||||
<span class="mono" style="font-size: 11px; color: var(--ink-fade);">h2 above panels</span>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: 70px 1fr 200px; align-items: baseline; padding: 6px 0; border-bottom: 1px solid var(--line-soft);">
|
||||
<span class="mono" style="font-size: 11px; color: var(--ink-fade);">13 / 400</span>
|
||||
<span style="font-size: 13px;">Body & table cells</span>
|
||||
<span class="mono" style="font-size: 11px; color: var(--ink-fade);">default for prose</span>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: 70px 1fr 200px; align-items: baseline; padding: 6px 0; border-bottom: 1px solid var(--line-soft);">
|
||||
<span class="mono" style="font-size: 11px; color: var(--ink-fade);">12 / 400</span>
|
||||
<span style="font-size: 12px; color: var(--ink-mute);">Helper, captions, secondary detail</span>
|
||||
<span class="mono" style="font-size: 11px; color: var(--ink-fade);">.field-help, meta lines</span>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: 70px 1fr 200px; align-items: baseline; padding: 6px 0;">
|
||||
<span class="mono" style="font-size: 11px; color: var(--ink-fade);">11 / 600 · 0.08em</span>
|
||||
<span style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em;">Section heading · column header</span>
|
||||
<span class="mono" style="font-size: 11px; color: var(--ink-fade);">all caps tracking</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
2 · STATUS
|
||||
============================================================ -->
|
||||
<section class="section">
|
||||
<h2><span class="num">02</span><span class="name">Status</span><span class="desc">five states · only place colour is used as semantic</span></h2>
|
||||
<div class="panel" style="border-radius: 7px;">
|
||||
<div class="hairline" style="display: grid; grid-template-columns: 60px 200px 1fr 240px; padding: 14px 18px; align-items: center; font-size: 13px;">
|
||||
<span><span class="dot dot-online"></span></span>
|
||||
<span style="font-weight: 500;">online</span>
|
||||
<span style="color: var(--ink-mute); font-size: 12px;">heartbeat received within last 90 seconds</span>
|
||||
<span class="mono" style="color: var(--ink-fade); font-size: 11px;">.dot.dot-online</span>
|
||||
</div>
|
||||
<div class="hairline" style="display: grid; grid-template-columns: 60px 200px 1fr 240px; padding: 14px 18px; align-items: center; font-size: 13px;">
|
||||
<span><span class="dot dot-online pulse"></span></span>
|
||||
<span style="font-weight: 500;">online · running</span>
|
||||
<span style="color: var(--ink-mute); font-size: 12px;">a job is in flight on this host — pulse only when active</span>
|
||||
<span class="mono" style="color: var(--ink-fade); font-size: 11px;">.dot.dot-online.pulse</span>
|
||||
</div>
|
||||
<div class="hairline" style="display: grid; grid-template-columns: 60px 200px 1fr 240px; padding: 14px 18px; align-items: center; font-size: 13px;">
|
||||
<span><span class="dot dot-degraded"></span></span>
|
||||
<span style="font-weight: 500;">degraded</span>
|
||||
<span style="color: var(--ink-mute); font-size: 12px;">online, but open alerts > 0 — soft amber, not loud</span>
|
||||
<span class="mono" style="color: var(--ink-fade); font-size: 11px;">.dot.dot-degraded</span>
|
||||
</div>
|
||||
<div class="hairline" style="display: grid; grid-template-columns: 60px 200px 1fr 240px; padding: 14px 18px; align-items: center; font-size: 13px;">
|
||||
<span><span class="dot dot-offline"></span></span>
|
||||
<span style="font-weight: 500;">offline</span>
|
||||
<span style="color: var(--ink-mute); font-size: 12px;">no heartbeat for > 90 seconds — neutral, not alarming</span>
|
||||
<span class="mono" style="color: var(--ink-fade); font-size: 11px;">.dot.dot-offline</span>
|
||||
</div>
|
||||
<div style="display: grid; grid-template-columns: 60px 200px 1fr 240px; padding: 14px 18px; align-items: center; font-size: 13px;">
|
||||
<span><span class="dot dot-failed"></span></span>
|
||||
<span style="font-weight: 500;">last job failed</span>
|
||||
<span style="color: var(--ink-mute); font-size: 12px;">distinct from offline — host is up, but its last job did not succeed</span>
|
||||
<span class="mono" style="color: var(--ink-fade); font-size: 11px;">.dot.dot-failed</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
3 · BUTTONS
|
||||
============================================================ -->
|
||||
<section class="section">
|
||||
<h2><span class="num">03</span><span class="name">Buttons</span><span class="desc">one primary per page · everything else is the neutral secondary</span></h2>
|
||||
<div class="panel" style="border-radius: 7px; padding: 24px;">
|
||||
<div class="flex flex-wrap items-center gap-3" style="margin-bottom: 24px;">
|
||||
<button class="btn btn-primary btn-lg">+ Add host</button>
|
||||
<button class="btn btn-primary">Run backup now</button>
|
||||
<button class="btn">Run now</button>
|
||||
<button class="btn btn-ghost">View →</button>
|
||||
<button class="btn btn-danger">Cancel job</button>
|
||||
<button class="btn" disabled>Disabled</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-6" style="font-size: 12px; color: var(--ink-mute); line-height: 1.55;">
|
||||
<div><span style="color: var(--ink); font-weight: 500;">Primary</span> · the one verb the page is about. Use <span style="color: var(--ink);">at most once per page</span>.</div>
|
||||
<div><span style="color: var(--ink); font-weight: 500;">Secondary</span> · everything else. Run-now per row, all panel actions.</div>
|
||||
<div><span style="color: var(--ink); font-weight: 500;">Ghost</span> · inline / “View →” affordances inside cards and rows.</div>
|
||||
<div><span style="color: var(--ink); font-weight: 500;">Danger</span> · destructive verbs only. Always pair with a confirmation modal for irreversible ones.</div>
|
||||
<div><span style="color: var(--ink); font-weight: 500;">Disabled</span> · 0.4 opacity, not-allowed cursor, no hover.</div>
|
||||
<div><span style="color: var(--ink); font-weight: 500;">Sizes</span> · default 12px. <span class="mono" style="color: var(--ink);">.btn-lg</span> for the page-level primary (slightly bigger affordance).</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
4 · FORM FIELDS
|
||||
============================================================ -->
|
||||
<section class="section">
|
||||
<h2><span class="num">04</span><span class="name">Form fields</span><span class="desc">labels above · helper below · 0.005em letter-spacing</span></h2>
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
|
||||
<div class="panel" style="border-radius: 7px; padding: 22px 24px;">
|
||||
<div style="margin-bottom: 18px;">
|
||||
<label class="field-label">Default text</label>
|
||||
<input class="field" type="text" value="prod-redis-01">
|
||||
<div class="field-help">Helper text. Plain prose.</div>
|
||||
</div>
|
||||
<div style="margin-bottom: 18px;">
|
||||
<label class="field-label">Monospace text</label>
|
||||
<input class="field mono" type="text" value="rest:https://restic.unraid.lab/host/">
|
||||
<div class="field-help">Use <span class="mono" style="color: var(--ink);">.field.mono</span> for URLs, IDs, anything machine-shaped.</div>
|
||||
</div>
|
||||
<div style="margin-bottom: 18px;">
|
||||
<label class="field-label">With prefix</label>
|
||||
<div style="position: relative;">
|
||||
<input class="field mono with-prefix" type="text" value="https://restic.unraid.lab/host/">
|
||||
<span class="field-prefix">repo:</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="field-label">Password</label>
|
||||
<input class="field" type="password" value="••••••••••••••••">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel" style="border-radius: 7px; padding: 22px 24px;">
|
||||
<div style="margin-bottom: 18px;">
|
||||
<label class="field-label">Focus state</label>
|
||||
<input class="field" type="text" value="prod-redis-01" style="border-color: var(--accent);">
|
||||
<div class="field-help">Border becomes <span style="color: var(--accent);">accent</span> on focus.</div>
|
||||
</div>
|
||||
<div style="margin-bottom: 18px;">
|
||||
<label class="field-label">Invalid</label>
|
||||
<input class="field invalid" type="text" value="not a url">
|
||||
<div class="field-error">repo_url must look like <span class="mono">rest:</span> / <span class="mono">s3:</span> / <span class="mono">b2:</span> …</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="field-label">Tag chip input</label>
|
||||
<div class="flex items-center gap-1.5 flex-wrap" style="padding: 8px; background: var(--bg); border: 1px solid var(--line-soft); border-radius: 5px;">
|
||||
<span class="tag tag-removable">prod <span class="x">×</span></span>
|
||||
<span class="tag tag-removable">cache <span class="x">×</span></span>
|
||||
<input class="mono" placeholder="add tag…" style="flex: 1; min-width: 80px; background: transparent; border: none; color: var(--ink); font-size: 12px; padding: 4px 6px; outline: none;">
|
||||
</div>
|
||||
<div class="field-help">Free-form tags. Comma or Enter to commit.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
5 · TAGS / CHIPS
|
||||
============================================================ -->
|
||||
<section class="section">
|
||||
<h2><span class="num">05</span><span class="name">Tags & chips</span><span class="desc">labels for hosts · status pills · removable in form</span></h2>
|
||||
<div class="panel" style="border-radius: 7px; padding: 22px;">
|
||||
<div class="flex items-center gap-2 flex-wrap" style="margin-bottom: 16px;">
|
||||
<span class="tag">prod</span>
|
||||
<span class="tag">db</span>
|
||||
<span class="tag">homelab</span>
|
||||
<span class="tag">storage</span>
|
||||
<span class="tag">edge</span>
|
||||
<span class="tag">test</span>
|
||||
<span class="tag tag-removable">prod <span class="x">×</span></span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<span class="mono" style="font-size: 11px; padding: 3px 8px; background: color-mix(in oklch, var(--ok), transparent 88%); color: var(--ok); border: 1px solid color-mix(in oklch, var(--ok), transparent 70%); border-radius: 3px;">succeeded</span>
|
||||
<span class="mono" style="font-size: 11px; padding: 3px 8px; background: color-mix(in oklch, var(--accent), transparent 88%); color: var(--accent); border: 1px solid color-mix(in oklch, var(--accent), transparent 70%); border-radius: 3px;">running</span>
|
||||
<span class="mono" style="font-size: 11px; padding: 3px 8px; background: color-mix(in oklch, var(--bad), transparent 88%); color: var(--bad); border: 1px solid color-mix(in oklch, var(--bad), transparent 70%); border-radius: 3px;">failed</span>
|
||||
<span class="mono" style="font-size: 11px; padding: 3px 8px; background: color-mix(in oklch, var(--warn), transparent 88%); color: var(--warn); border: 1px solid color-mix(in oklch, var(--warn), transparent 70%); border-radius: 3px;">cancelled</span>
|
||||
<span class="mono" style="font-size: 11px; padding: 3px 8px; background: color-mix(in oklch, var(--ok), transparent 90%); color: var(--ok); border: 1px solid color-mix(in oklch, var(--ok), transparent 70%); border-radius: 3px;">expires in 59m</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
6 · TABS
|
||||
============================================================ -->
|
||||
<section class="section">
|
||||
<h2><span class="num">06</span><span class="name">Tabs</span><span class="desc">primary nav (accent underline) · sub-nav (ink underline)</span></h2>
|
||||
<div class="panel" style="border-radius: 7px; padding: 0 24px;">
|
||||
<nav class="flex items-end" style="border-bottom: 1px solid var(--line-soft);">
|
||||
<div class="nav-tab active">Dashboard</div>
|
||||
<div class="nav-tab">Repos</div>
|
||||
<div class="nav-tab">Alerts <span class="mono ml-1.5" style="font-size:11px; color: var(--bad);">5</span></div>
|
||||
<div class="nav-tab">Audit</div>
|
||||
<div class="nav-tab">Settings</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="panel" style="border-radius: 7px; padding: 0 24px; margin-top: 16px;">
|
||||
<nav class="flex items-end">
|
||||
<div class="sub-tab active">Snapshots <span class="mono" style="color: var(--ink-fade); font-size: 11px; margin-left: 4px;">1,847</span></div>
|
||||
<div class="sub-tab">Jobs <span class="mono" style="color: var(--ink-fade); font-size: 11px; margin-left: 4px;">47 in 24h</span></div>
|
||||
<div class="sub-tab">Repo</div>
|
||||
<div class="sub-tab">Settings</div>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
7 · HOST ROW (3 states)
|
||||
============================================================ -->
|
||||
<section class="section">
|
||||
<h2><span class="num">07</span><span class="name">Host row</span><span class="desc">the dashboard's load-bearing row · degraded/failed/offline get a left edge</span></h2>
|
||||
<div class="panel" style="border-radius: 7px;">
|
||||
<div class="host-row head hairline">
|
||||
<div></div>
|
||||
<div>Host</div>
|
||||
<div>OS · arch</div>
|
||||
<div>Last backup</div>
|
||||
<div class="text-right">Repo size</div>
|
||||
<div class="text-right">Snapshots</div>
|
||||
<div class="text-right">Alerts</div>
|
||||
<div>Tags</div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div class="host-row hairline">
|
||||
<div><span class="dot dot-online"></span></div>
|
||||
<div class="mono" style="color: var(--ink); font-weight: 500;">healthy-host</div>
|
||||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/amd64</div>
|
||||
<div class="text-xs" style="color: var(--ink-mid);"><span style="color: var(--ok);">succeeded</span> · <span class="mono">5m ago</span></div>
|
||||
<div class="text-right mono" style="color: var(--ink);">87 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||||
<div class="text-right mono" style="color: var(--ink-mid);">2,103</div>
|
||||
<div class="text-right mono" style="color: var(--ink-mute);">—</div>
|
||||
<div class="flex gap-1.5"><span class="tag">prod</span></div>
|
||||
<div class="text-right"><button class="btn">Run now</button></div>
|
||||
</div>
|
||||
<div class="host-row degraded hairline">
|
||||
<div><span class="dot dot-degraded"></span></div>
|
||||
<div class="mono" style="color: var(--ink); font-weight: 500;">degraded-host</div>
|
||||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/amd64</div>
|
||||
<div class="text-xs" style="color: var(--ink-mid);"><span style="color: var(--ok);">succeeded</span> · <span class="mono">1h ago</span></div>
|
||||
<div class="text-right mono" style="color: var(--ink);">128 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||||
<div class="text-right mono" style="color: var(--ink-mid);">1,402</div>
|
||||
<div class="text-right mono" style="color: var(--bad); font-weight: 500;">3</div>
|
||||
<div class="flex gap-1.5"><span class="tag">prod</span></div>
|
||||
<div class="text-right"><button class="btn">Run now</button></div>
|
||||
</div>
|
||||
<div class="host-row failed hairline">
|
||||
<div><span class="dot dot-online"></span></div>
|
||||
<div class="mono" style="color: var(--ink); font-weight: 500;">failed-host</div>
|
||||
<div class="mono" style="color: var(--ink-mid); font-size:12px;">linux/amd64</div>
|
||||
<div class="text-xs" style="color: var(--ink-mid);"><span style="color: var(--bad); font-weight:500;">failed</span> · <span class="mono">47m ago</span></div>
|
||||
<div class="text-right mono" style="color: var(--ink);">97 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||||
<div class="text-right mono" style="color: var(--ink-mid);">2,847</div>
|
||||
<div class="text-right mono" style="color: var(--bad); font-weight: 500;">1</div>
|
||||
<div class="flex gap-1.5"><span class="tag">ci</span></div>
|
||||
<div class="text-right"><button class="btn">Retry</button></div>
|
||||
</div>
|
||||
<div class="host-row offline">
|
||||
<div><span class="dot dot-offline"></span></div>
|
||||
<div class="mono" style="color: var(--ink-mid); font-weight: 500;">offline-host</div>
|
||||
<div class="mono" style="color: var(--ink-mute); font-size:12px;">linux/amd64</div>
|
||||
<div class="text-xs" style="color: var(--ink-mute);">last seen <span class="mono">2d ago</span></div>
|
||||
<div class="text-right mono" style="color: var(--ink-mid);">64 <span style="color: var(--ink-mute); font-size: 11px;">GB</span></div>
|
||||
<div class="text-right mono" style="color: var(--ink-mute);">127</div>
|
||||
<div class="text-right mono" style="color: var(--bad); font-weight: 500;">1</div>
|
||||
<div class="flex gap-1.5"><span class="tag">dev</span></div>
|
||||
<div class="text-right"><span class="mono text-xs" style="color: var(--ink-fade);">offline</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
8 · LOG VIEWER
|
||||
============================================================ -->
|
||||
<section class="section">
|
||||
<h2><span class="num">08</span><span class="name">Log viewer</span><span class="desc">live or replayed · ts · stream · payload · color reserved for events & stderr</span></h2>
|
||||
<div class="log">
|
||||
<div class="log-line"><span class="log-ts">11:43:21.039</span><span class="log-tag">EVENT</span><span class="log-stream-event">{"message_type":"status","percent_done":0.000,"total_files":21402,"files_done":0}</span></div>
|
||||
<div class="log-line"><span class="log-ts">11:43:21.412</span><span class="log-tag">OUT</span><span class="log-stream-stdout">scan finished in 0.371s</span></div>
|
||||
<div class="log-line"><span class="log-ts">11:43:25.812</span><span class="log-tag">ERR</span><span class="log-stream-stderr">warn: file changed during read: /var/lib/postgresql/13/main/pg_wal/000000010000007800000042</span></div>
|
||||
<div class="log-line"><span class="log-ts">11:43:31.625</span><span class="log-tag">EVENT</span><span class="log-stream-event">{"message_type":"status","percent_done":0.380,"files_done":8124,"bytes_done":1503870976}</span></div>
|
||||
<div class="log-line"><span class="log-ts" style="color: var(--accent);">11:43:32.122</span><span class="log-tag" style="color: var(--accent);">···</span><span style="color: var(--accent);">▏</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
9 · PROGRESS BAR
|
||||
============================================================ -->
|
||||
<section class="section">
|
||||
<h2><span class="num">09</span><span class="name">Progress bar</span><span class="desc">running · complete · failed</span></h2>
|
||||
<div class="panel" style="border-radius: 7px; padding: 22px 24px;">
|
||||
<div style="margin-bottom: 22px;">
|
||||
<div class="flex items-center justify-between mb-2 text-xs"><span style="color: var(--ink); font-weight:500;">running · 38%</span><span class="mono" style="color: var(--ink-mute);">42 MB/s · ETA 2m 14s</span></div>
|
||||
<div class="progress-track"><div class="progress-fill" style="width: 38%;"></div></div>
|
||||
</div>
|
||||
<div style="margin-bottom: 22px;">
|
||||
<div class="flex items-center justify-between mb-2 text-xs"><span style="color: var(--ok);">complete · 100%</span><span class="mono" style="color: var(--ink-mute);">finished in 5m 21s</span></div>
|
||||
<div class="progress-track"><div class="progress-fill ok" style="width: 100%;"></div></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-2 text-xs"><span style="color: var(--bad);">failed · 0%</span><span class="mono" style="color: var(--ink-mute);">repo locked</span></div>
|
||||
<div class="progress-track"><div class="progress-fill bad" style="width: 4%;"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
10 · STAT TILE
|
||||
============================================================ -->
|
||||
<section class="section">
|
||||
<h2><span class="num">10</span><span class="name">Stat tile</span><span class="desc">used in summary strips · fleet, host header, job summary</span></h2>
|
||||
<div class="panel" style="border-radius: 7px; padding: 22px;">
|
||||
<div class="grid grid-cols-4 gap-6">
|
||||
<div>
|
||||
<div style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em;">Hosts</div>
|
||||
<div class="mono" style="font-size: 28px; font-weight: 500; letter-spacing: -0.02em; margin-top: 4px;">12 <span style="font-size: 13px; color: var(--ink-mute); font-weight: 400;">total</span></div>
|
||||
<div style="font-size: 12px; color: var(--ink-mute); margin-top: 4px;"><span class="mono" style="color: var(--ok);">10</span> online · <span class="mono" style="color: var(--warn);">1</span> degraded</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em;">Backed up</div>
|
||||
<div class="mono" style="font-size: 28px; font-weight: 500; letter-spacing: -0.02em; margin-top: 4px;">4.9 <span style="font-size: 13px; color: var(--ink-mute); font-weight: 400;">TB</span></div>
|
||||
<div style="font-size: 12px; color: var(--ink-mute); margin-top: 4px;"><span class="mono">23,649</span> snapshots</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em;">Last 24h</div>
|
||||
<div class="mono" style="font-size: 28px; font-weight: 500; letter-spacing: -0.02em; margin-top: 4px;">147 <span style="font-size: 13px; color: var(--ink-mute); font-weight: 400;">jobs</span></div>
|
||||
<div style="font-size: 12px; color: var(--ink-mute); margin-top: 4px;"><span class="mono" style="color: var(--ok);">144</span> ok · <span class="mono" style="color: var(--bad);">2</span> failed</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size: 11px; color: var(--ink-fade); text-transform: uppercase; letter-spacing: 0.08em;">Open alerts</div>
|
||||
<div class="mono" style="font-size: 28px; font-weight: 500; letter-spacing: -0.02em; margin-top: 4px; color: var(--bad);">5</div>
|
||||
<div style="font-size: 12px; color: var(--ink-mute); margin-top: 4px;">oldest <span class="mono" style="color: var(--ink-mid);">3h</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
11 · MODAL
|
||||
============================================================ -->
|
||||
<section class="section">
|
||||
<h2><span class="num">11</span><span class="name">Modal</span><span class="desc">used for confirms · destructive actions · short prompts</span></h2>
|
||||
<div class="modal-backdrop">
|
||||
<div class="modal">
|
||||
<div class="modal-head">
|
||||
<h3 style="font-size: 16px; font-weight: 500; letter-spacing: -0.005em;">Remove host?</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Removes <span class="mono" style="color: var(--ink);">prod-cache-01</span> from
|
||||
the dashboard and revokes its agent token. Backup data on the
|
||||
rest-server is left intact — delete that yourself if you want it gone.
|
||||
</p>
|
||||
<p style="margin-top: 10px; color: var(--ink-mute); font-size: 12px;">This action is logged in the audit trail.</p>
|
||||
</div>
|
||||
<div class="modal-foot">
|
||||
<button class="btn">Cancel</button>
|
||||
<button class="btn btn-danger">Remove host</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
12 · TOAST
|
||||
============================================================ -->
|
||||
<section class="section">
|
||||
<h2><span class="num">12</span><span class="name">Toast</span><span class="desc">success and error variants · auto-dismiss after 4s</span></h2>
|
||||
<div style="display: flex; flex-direction: column; gap: 12px; align-items: flex-end;">
|
||||
<div class="toast toast-ok">
|
||||
<span class="dot dot-online" style="margin-top: 4px;"></span>
|
||||
<div style="flex: 1;">
|
||||
<div style="font-size: 13px; color: var(--ink); font-weight: 500;">Token minted</div>
|
||||
<div style="font-size: 12px; color: var(--ink-mute); margin-top: 2px;">install command shown below — expires in 1h</div>
|
||||
</div>
|
||||
<span class="x">×</span>
|
||||
</div>
|
||||
<div class="toast toast-bad">
|
||||
<span class="dot dot-failed" style="margin-top: 4px;"></span>
|
||||
<div style="flex: 1;">
|
||||
<div style="font-size: 13px; color: var(--ink); font-weight: 500;">Couldn’t reach the agent</div>
|
||||
<div style="font-size: 12px; color: var(--ink-mute); margin-top: 2px;"><span class="mono">prod-cache-01</span> didn’t respond within 5s. Try again or check the WS connection.</div>
|
||||
</div>
|
||||
<span class="x">×</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
13 · INSTALL SNIPPET
|
||||
============================================================ -->
|
||||
<section class="section">
|
||||
<h2><span class="num">13</span><span class="name">Install snippet</span><span class="desc">load-bearing affordance on Add host · Empty state · Settings → Agents</span></h2>
|
||||
<div class="snippet">
|
||||
<div class="snippet-head">
|
||||
<span>install command · 59m left</span>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn btn-ghost mono" style="font-size: 11px;">Download .sh</button>
|
||||
<button class="btn">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
<pre>curl -fsSL <span class="var">https://restic.lab.example/install.sh</span> | sudo \
|
||||
RM_SERVER=<span class="var">https://restic.lab.example</span> \
|
||||
RM_TOKEN=<span class="var">HdqFbQh8U-I1fb52iP1M8qxvoYS5t9VZ-T-yghr_CzA</span> sh</pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ============================================================
|
||||
14 · EMPTY STATE PATTERN
|
||||
============================================================ -->
|
||||
<section class="section" style="border-bottom: none;">
|
||||
<h2><span class="num">14</span><span class="name">Empty-state pattern</span><span class="desc">nothing-yet screens · centred prompt + the affordance that fixes it</span></h2>
|
||||
<div class="panel" style="border-radius: 7px; padding: 60px 40px; text-align: center; background: radial-gradient(ellipse at top, color-mix(in oklch, var(--accent), transparent 95%), transparent 60%), var(--panel);">
|
||||
<h3 style="font-size: 18px; font-weight: 500; letter-spacing: -0.005em;">Nothing here yet.</h3>
|
||||
<p style="color: var(--ink-mid); max-width: 480px; margin: 12px auto 22px; line-height: 1.65; font-size: 13px; text-wrap: pretty;">
|
||||
Empty states always pair a one-sentence explanation with a single
|
||||
primary affordance. Don’t fill the space with stats or graphics —
|
||||
the void <em>is</em> the message.
|
||||
</p>
|
||||
<button class="btn btn-primary">Take the action that fixes this</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- final note -->
|
||||
<div style="padding: 48px 0 96px; text-align: center; font-size: 12px; color: var(--ink-fade);">
|
||||
end of v1 component reference · 14 sections · ~all the pieces the Phase 1 templates need
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user