Files
alexei.dolgolyov 5dcadd1c20 feat(ui): migrate entire UI to "Cozy Home" design
Warm, friendly redesign replacing the generic cold-shadcn look. Built as a
swappable token bundle so other presets can be added later; dark mode and the
user-tunable accent hue are retained.

Foundation
- app.css: warm cream (light) + "dusk" (dark) token system; terracotta accent
  (default hue 16); pastel --room-* palette; vivid --status-* (dots/bars) plus
  AA-legible --status-*-ink (text); soft warm shadows; --radius 1rem; font tokens
- Fonts: Fraunces (display) + Figtree (body), self-hosted in static/fonts
  (no Google CDN) so offline/LAN installs work; system-ui fallbacks kept
- h1/h2/h3 render in Fraunces via base layer

Chrome and surfaces
- Sidebar, Header, home, AppCard/BoardCard, BoardHeader, sections, favorites
- 29 widgets + integration renderers: cozy card shells, room-palette charts
- Default background is a static warm "cozy" glow (mesh demoted, rAF gated on
  prefers-reduced-motion)

System-wide
- Status colors tokenized (no raw bg/text-*-500 or status hex); success/warning
  to status tokens, categorical to room palette, errors to destructive
- Inputs rounded-xl; buttons rounded-xl; cards/dialogs rounded-[1.4rem];
  soft-shadow vocabulary only; focus-visible:ring-primary/30
- Forms, admin tables (now cozy cards), dialogs, popovers, auth screens

a11y: reduced-motion guards; darker status "ink" text for AA on cream.
Known tradeoff: terracotta primary + white button text ~2.96:1 (signature color,
user-tunable).

Verified: svelte-check 0/0, build ok, 274 tests pass, eslint 0 errors.
Design refs + system sheet in design-mockups/.
2026-05-27 23:04:47 +03:00

640 lines
16 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Cozy Home — Design System Reference</title>
<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=Fraunces:opsz,wght@9..144,500;9..144,600;9..144,700&family=Figtree:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<style>
/* === The exact tokens now living in src/app.css (light/cream) === */
:root {
--background: hsl(35 56% 97%);
--foreground: hsl(33 18% 18%);
--muted: hsl(36 42% 93%);
--muted-foreground: hsl(34 12% 47%);
--card: hsl(40 60% 99%);
--border: hsl(36 35% 88%);
--primary: hsl(16 72% 56%);
--primary-foreground: hsl(40 60% 99%);
--accent: hsl(34 44% 90%);
--status-online: #5fa86c;
--status-offline: #e0685f;
--status-degraded: #d99a2b;
--status-unknown: #b3a899;
--room-sage: #7fb069;
--room-sky: #6ca9d6;
--room-butter: #f3c969;
--room-lav: #b09fd6;
--room-peach: #ff9a76;
--room-terra: #e8754f;
--radius: 1rem;
--shadow-soft: 0 10px 26px -20px rgba(80, 50, 20, 0.45);
--shadow-lift: 0 22px 40px -22px rgba(80, 50, 20, 0.4);
--font-sans: 'Figtree', system-ui, sans-serif;
--font-display: 'Fraunces', serif;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: var(--background);
color: var(--foreground);
font-family: var(--font-sans);
padding: 40px;
line-height: 1.5;
}
h1,
h2,
h3 {
font-family: var(--font-display);
letter-spacing: -0.01em;
}
.wrap {
max-width: 1080px;
margin: 0 auto;
}
.title {
font-size: 34px;
font-weight: 600;
}
.lede {
color: var(--muted-foreground);
margin: 6px 0 8px;
max-width: 64ch;
}
.path {
font-family: ui-monospace, monospace;
font-size: 12px;
color: var(--room-terra);
background: color-mix(in srgb, var(--room-terra) 12%, transparent);
padding: 3px 9px;
border-radius: 8px;
display: inline-block;
}
section {
margin-top: 42px;
}
.h {
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--muted-foreground);
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border);
}
.row {
display: flex;
flex-wrap: wrap;
gap: 14px;
align-items: center;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 14px;
}
/* tokens */
.swatch {
border-radius: var(--radius);
border: 1px solid var(--border);
overflow: hidden;
background: var(--card);
box-shadow: var(--shadow-soft);
}
.swatch .c {
height: 56px;
}
.swatch .n {
padding: 9px 12px;
font-size: 12px;
}
.swatch .n b {
display: block;
font-weight: 600;
}
.swatch .n span {
color: var(--muted-foreground);
font-family: ui-monospace, monospace;
font-size: 10.5px;
}
/* buttons */
.btn {
font-family: var(--font-sans);
font-weight: 600;
font-size: 14px;
padding: 11px 18px;
border-radius: 14px;
border: 1px solid transparent;
cursor: pointer;
transition: 0.18s;
}
.btn-primary {
background: var(--primary);
color: var(--primary-foreground);
box-shadow: var(--shadow-soft);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lift);
}
.btn-secondary {
background: var(--card);
color: var(--foreground);
border-color: var(--border);
box-shadow: var(--shadow-soft);
}
.btn-secondary:hover {
transform: translateY(-2px);
border-color: color-mix(in srgb, var(--primary) 40%, transparent);
}
.btn-ghost {
background: transparent;
color: var(--foreground);
}
.btn-ghost:hover {
background: var(--accent);
}
.btn-icon {
width: 42px;
height: 42px;
border-radius: 14px;
display: grid;
place-items: center;
padding: 0;
}
/* inputs */
.field {
display: flex;
flex-direction: column;
gap: 7px;
max-width: 340px;
}
.field label {
font-size: 13px;
font-weight: 600;
}
.input {
font-family: var(--font-sans);
font-size: 14px;
padding: 11px 14px;
border-radius: 14px;
border: 1px solid var(--border);
background: var(--card);
color: var(--foreground);
box-shadow: var(--shadow-soft);
transition: 0.16s;
}
.input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary) 18%, transparent);
}
.hint {
font-size: 12px;
color: var(--muted-foreground);
}
/* badges / status pills */
.pill {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: 12.5px;
font-weight: 600;
padding: 5px 11px;
border-radius: 999px;
}
.pill .b {
width: 8px;
height: 8px;
border-radius: 50%;
}
.pill.ok {
color: var(--status-online);
background: color-mix(in srgb, var(--status-online) 14%, transparent);
}
.pill.warn {
color: var(--status-degraded);
background: color-mix(in srgb, var(--status-degraded) 14%, transparent);
}
.pill.bad {
color: var(--status-offline);
background: color-mix(in srgb, var(--status-offline) 14%, transparent);
}
.pill .b {
background: currentColor;
}
.room-pill {
font-size: 11px;
font-weight: 700;
padding: 4px 10px;
border-radius: 999px;
}
/* tabs */
.tabs {
display: inline-flex;
gap: 4px;
background: var(--muted);
padding: 5px;
border-radius: 16px;
}
.tab {
font-weight: 600;
font-size: 13.5px;
padding: 8px 16px;
border-radius: 11px;
cursor: pointer;
color: var(--muted-foreground);
}
.tab.on {
background: var(--card);
color: var(--foreground);
box-shadow: var(--shadow-soft);
}
/* card */
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 1.4rem;
padding: 20px;
box-shadow: var(--shadow-soft);
max-width: 300px;
position: relative;
overflow: hidden;
}
.card .blob {
position: absolute;
width: 120px;
height: 120px;
border-radius: 50%;
filter: blur(30px);
opacity: 0.45;
top: -50px;
right: -40px;
}
.card .ic {
width: 52px;
height: 52px;
border-radius: 18px;
display: grid;
place-items: center;
font-size: 25px;
position: relative;
}
.card h3 {
font-size: 17px;
font-weight: 600;
margin-top: 14px;
}
.card .ct {
font-size: 13px;
color: var(--muted-foreground);
}
.card .f {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16px;
}
/* dialog */
.dialog {
background: var(--card);
border: 1px solid var(--border);
border-radius: 1.4rem;
padding: 24px;
box-shadow: var(--shadow-lift);
max-width: 380px;
}
.dialog h3 {
font-size: 20px;
font-weight: 600;
}
.dialog p {
color: var(--muted-foreground);
font-size: 14px;
margin: 8px 0 20px;
}
/* table */
table {
width: 100%;
border-collapse: collapse;
background: var(--card);
border: 1px solid var(--border);
border-radius: 1.2rem;
overflow: hidden;
box-shadow: var(--shadow-soft);
}
th {
text-align: left;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--muted-foreground);
padding: 14px 18px;
border-bottom: 1px solid var(--border);
}
td {
padding: 14px 18px;
font-size: 14px;
border-bottom: 1px solid var(--border);
}
tr:last-child td {
border-bottom: 0;
}
tr:hover td {
background: var(--accent);
}
/* empty state */
.empty {
text-align: center;
border: 2px dashed var(--border);
border-radius: 1.4rem;
padding: 48px 24px;
background: color-mix(in srgb, var(--card) 50%, transparent);
}
.empty .e-ic {
width: 64px;
height: 64px;
border-radius: 22px;
display: grid;
place-items: center;
margin: 0 auto 16px;
background: color-mix(in srgb, var(--room-peach) 20%, transparent);
color: var(--room-terra);
}
.empty h3 {
font-size: 19px;
font-weight: 600;
}
.empty p {
color: var(--muted-foreground);
font-size: 14px;
margin: 6px auto 18px;
max-width: 36ch;
}
</style>
</head>
<body>
<div class="wrap">
<h1 class="title">Cozy Home — Design System</h1>
<p class="lede">
The component pattern sheet for the migration. Every phase styles its components to match
these primitives. Tokens here mirror what now lives in
<span class="path">src/app.css</span> — change them there and the whole app follows.
</p>
<section>
<div class="h">Color tokens</div>
<div class="grid">
<div class="swatch">
<div class="c" style="background: var(--background)"></div>
<div class="n"><b>background</b><span>cream #fdf8f2</span></div>
</div>
<div class="swatch">
<div class="c" style="background: var(--card)"></div>
<div class="n"><b>card</b><span>#fffdfa</span></div>
</div>
<div class="swatch">
<div class="c" style="background: var(--primary)"></div>
<div class="n"><b>primary</b><span>terracotta · tunable</span></div>
</div>
<div class="swatch">
<div class="c" style="background: var(--foreground)"></div>
<div class="n"><b>foreground</b><span>warm ink</span></div>
</div>
<div class="swatch">
<div class="c" style="background: var(--muted)"></div>
<div class="n"><b>muted</b><span>#f3ecde</span></div>
</div>
<div class="swatch">
<div class="c" style="background: var(--border)"></div>
<div class="n"><b>border</b><span>#ece2d3</span></div>
</div>
</div>
<div style="margin-top: 14px" class="grid">
<div class="swatch">
<div class="c" style="background: var(--room-terra)"></div>
<div class="n"><b>room · terra</b></div>
</div>
<div class="swatch">
<div class="c" style="background: var(--room-peach)"></div>
<div class="n"><b>room · peach</b></div>
</div>
<div class="swatch">
<div class="c" style="background: var(--room-butter)"></div>
<div class="n"><b>room · butter</b></div>
</div>
<div class="swatch">
<div class="c" style="background: var(--room-sage)"></div>
<div class="n"><b>room · sage</b></div>
</div>
<div class="swatch">
<div class="c" style="background: var(--room-sky)"></div>
<div class="n"><b>room · sky</b></div>
</div>
<div class="swatch">
<div class="c" style="background: var(--room-lav)"></div>
<div class="n"><b>room · lav</b></div>
</div>
</div>
</section>
<section>
<div class="h">Typography — Fraunces (display) · Figtree (body)</div>
<h1 style="font-size: 46px; font-weight: 600">Good evening, Alexei</h1>
<h2 style="font-size: 28px; font-weight: 600; margin-top: 10px">Your favorites</h2>
<h3 style="font-size: 19px; font-weight: 600; margin-top: 10px">Jellyfin</h3>
<p style="margin-top: 8px; max-width: 60ch">
Body copy is Figtree — friendly, legible, rounded. It carries descriptions, hints, and
plain-language status like “a bit slow” or “asleep”.
</p>
</section>
<section>
<div class="h">Buttons</div>
<div class="row">
<button class="btn btn-primary">Open a board</button>
<button class="btn btn-secondary">Add an app</button>
<button class="btn btn-ghost">Cancel</button>
<button class="btn btn-secondary btn-icon" title="icon">🔔</button>
</div>
</section>
<section>
<div class="h">Form fields</div>
<div class="row" style="align-items: flex-start">
<div class="field">
<label>App name</label><input class="input" value="Jellyfin" /><span class="hint"
>Shown on the card and in search.</span
>
</div>
<div class="field"><label>URL</label><input class="input" placeholder="https://…" /></div>
</div>
</section>
<section>
<div class="h">Status pills &amp; room tags</div>
<div class="row">
<span class="pill ok"><span class="b"></span>Online</span>
<span class="pill warn"><span class="b"></span>A bit slow</span>
<span class="pill bad"><span class="b"></span>Asleep</span>
<span
class="room-pill"
style="
color: var(--room-terra);
background: color-mix(in srgb, var(--room-terra) 16%, transparent);
"
>Media</span
>
<span
class="room-pill"
style="
color: var(--room-sky);
background: color-mix(in srgb, var(--room-sky) 16%, transparent);
"
>Network</span
>
<span
class="room-pill"
style="
color: var(--room-sage);
background: color-mix(in srgb, var(--room-sage) 16%, transparent);
"
>Git</span
>
</div>
</section>
<section>
<div class="h">Tabs</div>
<div class="tabs">
<div class="tab on">Overview</div>
<div class="tab">Activity</div>
<div class="tab">Settings</div>
</div>
</section>
<section>
<div class="h">App card</div>
<div class="card">
<div class="blob" style="background: var(--room-terra)"></div>
<div
class="ic"
style="
background: color-mix(in srgb, var(--room-terra) 18%, transparent);
color: var(--room-terra);
"
>
🎬
</div>
<h3>Jellyfin</h3>
<div class="ct">Movies &amp; shows</div>
<div class="f">
<span class="pill ok"><span class="b"></span>Online</span
><span class="hint">99.9%</span>
</div>
</div>
</section>
<section>
<div class="h">Dialog</div>
<div class="dialog">
<h3>Remove Deluge?</h3>
<p>This deletes the app and its uptime history. This cant be undone.</p>
<div class="row" style="justify-content: flex-end">
<button class="btn btn-ghost">Keep it</button
><button class="btn btn-primary" style="background: var(--status-offline)">
Remove
</button>
</div>
</div>
</section>
<section>
<div class="h">Table (admin)</div>
<table>
<thead>
<tr>
<th>User</th>
<th>Role</th>
<th>Status</th>
<th>Last seen</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alexei</td>
<td>Admin</td>
<td>
<span class="pill ok"><span class="b"></span>Active</span>
</td>
<td>just now</td>
</tr>
<tr>
<td>Guest</td>
<td>Viewer</td>
<td>
<span class="pill warn"><span class="b"></span>Idle</span>
</td>
<td>2h ago</td>
</tr>
</tbody>
</table>
</section>
<section>
<div class="h">Empty state</div>
<div class="empty">
<div class="e-ic">
<svg
width="30"
height="30"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.6"
>
<circle cx="12" cy="12" r="9" />
<path d="M3 12h18M12 3a14 14 0 0 1 0 18 14 14 0 0 1 0-18z" />
</svg>
</div>
<h3>No apps yet</h3>
<p>Add your first service and itll show up here with live status.</p>
<button class="btn btn-primary">+ Add an app</button>
</div>
</section>
<p
style="
margin: 48px 0 20px;
text-align: center;
font-family: var(--font-display);
font-style: italic;
color: var(--muted-foreground);
"
>
Cozy Home design system · mirrors src/app.css · use as the pattern for every migrated
component
</p>
</div>
</body>
</html>