5dcadd1c20
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/.
640 lines
16 KiB
HTML
640 lines
16 KiB
HTML
<!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 & 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 & 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 can’t 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 it’ll 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>
|