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/.
This commit is contained in:
@@ -0,0 +1,789 @@
|
||||
<!doctype html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Web App Launcher — Command Deck</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=Saira:wght@400;500;600;700&family=Saira+Condensed:wght@500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #070a0d;
|
||||
--panel: #0d1217;
|
||||
--panel-2: #10171e;
|
||||
--line: #1d2730;
|
||||
--line-bright: #2b3946;
|
||||
--ink: #e7eef3;
|
||||
--ink-dim: #7c8b97;
|
||||
--ink-faint: #4a5763;
|
||||
--accent: #36e0a4; /* tactical green */
|
||||
--accent-2: #ffb020; /* amber */
|
||||
--danger: #ff4d5e;
|
||||
--warn: #ffb020;
|
||||
--grid: rgba(54, 224, 164, 0.04);
|
||||
--radius: 4px;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: 'Saira', system-ui, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
/* subtle scanline grid backdrop */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background-image:
|
||||
linear-gradient(var(--grid) 1px, transparent 1px),
|
||||
linear-gradient(90deg, var(--grid) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
mask-image: radial-gradient(ellipse 80% 60% at 70% 0%, #000 30%, transparent 90%);
|
||||
}
|
||||
.app {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 74px 1fr;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.mono {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
.cond {
|
||||
font-family: 'Saira Condensed', sans-serif;
|
||||
}
|
||||
|
||||
/* ===== Rail ===== */
|
||||
.rail {
|
||||
border-right: 1px solid var(--line);
|
||||
background: linear-gradient(180deg, var(--panel), #080c10);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 16px 0;
|
||||
}
|
||||
.logo {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: var(--radius);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--accent);
|
||||
margin-bottom: 18px;
|
||||
box-shadow:
|
||||
0 0 0 1px #0a1f18,
|
||||
0 0 18px -4px var(--accent);
|
||||
position: relative;
|
||||
}
|
||||
.logo svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.rail-btn {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: var(--radius);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--ink-dim);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: 0.15s;
|
||||
}
|
||||
.rail-btn:hover {
|
||||
color: var(--ink);
|
||||
background: var(--panel-2);
|
||||
}
|
||||
.rail-btn.active {
|
||||
color: var(--accent);
|
||||
}
|
||||
.rail-btn.active::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -16px;
|
||||
top: 10px;
|
||||
bottom: 10px;
|
||||
width: 2px;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 8px var(--accent);
|
||||
}
|
||||
.rail-btn svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.rail-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
.rail-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: var(--radius);
|
||||
background: linear-gradient(135deg, #1a2a22, #0f1a14);
|
||||
border: 1px solid var(--line-bright);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* ===== Main ===== */
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
height: 60px;
|
||||
padding: 0 26px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: rgba(8, 12, 16, 0.6);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.crumbs {
|
||||
font-family: 'Saira Condensed';
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.14em;
|
||||
font-size: 12px;
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
.crumbs b {
|
||||
color: var(--ink-dim);
|
||||
font-weight: 600;
|
||||
}
|
||||
.search {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 340px;
|
||||
max-width: 38vw;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
padding: 9px 12px;
|
||||
color: var(--ink-dim);
|
||||
font-size: 13px;
|
||||
cursor: text;
|
||||
transition: 0.15s;
|
||||
}
|
||||
.search:hover {
|
||||
border-color: var(--line-bright);
|
||||
}
|
||||
.search svg {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
.search .kbd {
|
||||
margin-left: auto;
|
||||
font-family: 'JetBrains Mono';
|
||||
font-size: 10px;
|
||||
color: var(--ink-faint);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 3px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
.ico-btn {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--ink-dim);
|
||||
cursor: pointer;
|
||||
transition: 0.15s;
|
||||
background: var(--panel);
|
||||
}
|
||||
.ico-btn:hover {
|
||||
color: var(--ink);
|
||||
border-color: var(--line-bright);
|
||||
}
|
||||
.ico-btn svg {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 26px;
|
||||
max-width: 1320px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* status bar */
|
||||
.statline {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 1px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
margin-bottom: 24px;
|
||||
background: var(--line);
|
||||
}
|
||||
.stat {
|
||||
flex: 1;
|
||||
background: var(--panel);
|
||||
padding: 16px 18px;
|
||||
position: relative;
|
||||
}
|
||||
.stat .lbl {
|
||||
font-family: 'Saira Condensed';
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.16em;
|
||||
font-size: 11px;
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
.stat .val {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
margin-top: 6px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.stat .val.ok {
|
||||
color: var(--accent);
|
||||
}
|
||||
.stat .val.warn {
|
||||
color: var(--warn);
|
||||
}
|
||||
.stat .val.bad {
|
||||
color: var(--danger);
|
||||
}
|
||||
.stat .sub {
|
||||
font-size: 12px;
|
||||
color: var(--ink-dim);
|
||||
margin-top: 2px;
|
||||
}
|
||||
.stat::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 2px;
|
||||
width: 100%;
|
||||
background: linear-gradient(90deg, var(--accent), transparent);
|
||||
}
|
||||
.stat.s2::after {
|
||||
background: linear-gradient(90deg, var(--accent-2), transparent);
|
||||
}
|
||||
.stat.s3::after {
|
||||
background: linear-gradient(90deg, #3aa0ff, transparent);
|
||||
}
|
||||
.stat.s4::after {
|
||||
background: linear-gradient(90deg, var(--danger), transparent);
|
||||
}
|
||||
|
||||
.sec-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin: 30px 0 16px;
|
||||
}
|
||||
.sec-head h2 {
|
||||
font-family: 'Saira Condensed';
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
font-size: 15px;
|
||||
color: var(--ink);
|
||||
}
|
||||
.sec-head .rule {
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, var(--line-bright), transparent);
|
||||
}
|
||||
.sec-head .count {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-size: 11px;
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
|
||||
/* app grid */
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
.node {
|
||||
background: linear-gradient(180deg, var(--panel), var(--panel-2));
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
padding: 16px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition:
|
||||
transform 0.15s,
|
||||
border-color 0.15s,
|
||||
box-shadow 0.15s;
|
||||
overflow: hidden;
|
||||
}
|
||||
.node::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at 100% 0%, rgba(54, 224, 164, 0.08), transparent 60%);
|
||||
opacity: 0;
|
||||
transition: 0.2s;
|
||||
}
|
||||
.node:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--line-bright);
|
||||
box-shadow: 0 10px 30px -12px #000;
|
||||
}
|
||||
.node:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
.node-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.node-ico {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: var(--radius);
|
||||
background: #0a0f13;
|
||||
border: 1px solid var(--line);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.node-name {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.node-cat {
|
||||
font-family: 'Saira Condensed';
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
font-size: 10px;
|
||||
color: var(--ink-faint);
|
||||
margin-top: 3px;
|
||||
}
|
||||
.led {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
.led.ok {
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 10px var(--accent);
|
||||
}
|
||||
.led.ok::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -4px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--accent);
|
||||
opacity: 0.4;
|
||||
animation: ping 2s ease-out infinite;
|
||||
}
|
||||
.led.warn {
|
||||
background: var(--warn);
|
||||
box-shadow: 0 0 10px var(--warn);
|
||||
}
|
||||
.led.bad {
|
||||
background: var(--danger);
|
||||
box-shadow: 0 0 10px var(--danger);
|
||||
}
|
||||
@keyframes ping {
|
||||
0% {
|
||||
transform: scale(0.8);
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
transform: scale(2.2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.node-foot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 16px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
.node-foot .up {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-size: 11px;
|
||||
color: var(--ink-dim);
|
||||
}
|
||||
.node-foot .up b {
|
||||
color: var(--accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
.node-foot .up.bad b {
|
||||
color: var(--danger);
|
||||
}
|
||||
.spark {
|
||||
height: 22px;
|
||||
width: 96px;
|
||||
}
|
||||
|
||||
.footer-note {
|
||||
margin-top: 40px;
|
||||
text-align: center;
|
||||
font-family: 'JetBrains Mono';
|
||||
font-size: 11px;
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
|
||||
/* entrance */
|
||||
@keyframes rise {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
.node,
|
||||
.stat {
|
||||
animation: rise 0.5s both;
|
||||
}
|
||||
.stat:nth-child(2) {
|
||||
animation-delay: 0.05s;
|
||||
}
|
||||
.stat:nth-child(3) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.stat:nth-child(4) {
|
||||
animation-delay: 0.15s;
|
||||
}
|
||||
.grid .node:nth-child(1) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.grid .node:nth-child(2) {
|
||||
animation-delay: 0.16s;
|
||||
}
|
||||
.grid .node:nth-child(3) {
|
||||
animation-delay: 0.22s;
|
||||
}
|
||||
.grid .node:nth-child(4) {
|
||||
animation-delay: 0.28s;
|
||||
}
|
||||
.grid .node:nth-child(5) {
|
||||
animation-delay: 0.34s;
|
||||
}
|
||||
.grid .node:nth-child(6) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.grid .node:nth-child(7) {
|
||||
animation-delay: 0.46s;
|
||||
}
|
||||
.grid .node:nth-child(8) {
|
||||
animation-delay: 0.52s;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<!-- Rail -->
|
||||
<nav class="rail">
|
||||
<div class="logo" title="Launcher">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="7" height="7" />
|
||||
<rect x="14" y="3" width="7" height="7" />
|
||||
<rect x="14" y="14" width="7" height="7" />
|
||||
<rect x="3" y="14" width="7" height="7" />
|
||||
</svg>
|
||||
</div>
|
||||
<a class="rail-btn active" title="Overview"
|
||||
><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" />
|
||||
<line x1="3" y1="9" x2="21" y2="9" />
|
||||
<line x1="9" y1="21" x2="9" y2="9" /></svg
|
||||
></a>
|
||||
<a class="rail-btn" title="Apps"
|
||||
><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<line x1="3" y1="12" x2="21" y2="12" />
|
||||
<path d="M12 3a14 14 0 0 1 0 18 14 14 0 0 1 0-18" /></svg
|
||||
></a>
|
||||
<a class="rail-btn" title="Status"
|
||||
><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<path d="M22 12h-4l-3 9L9 3l-3 9H2" /></svg
|
||||
></a>
|
||||
<a class="rail-btn" title="Admin"
|
||||
><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path
|
||||
d="M19 12a7 7 0 0 0-.1-1l2-1.6-2-3.4-2.4 1a7 7 0 0 0-1.7-1l-.4-2.6h-4l-.4 2.6a7 7 0 0 0-1.7 1l-2.4-1-2 3.4 2 1.6a7 7 0 0 0 0 2l-2 1.6 2 3.4 2.4-1a7 7 0 0 0 1.7 1l.4 2.6h4l.4-2.6a7 7 0 0 0 1.7-1l2.4 1 2-3.4-2-1.6a7 7 0 0 0 .1-1z"
|
||||
/></svg
|
||||
></a>
|
||||
<div class="rail-spacer"></div>
|
||||
<div class="rail-avatar">AD</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main -->
|
||||
<div class="main">
|
||||
<header class="topbar">
|
||||
<div class="crumbs">SYSTEMS / <b>OVERVIEW</b></div>
|
||||
<div class="search">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="7" />
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
Search apps, boards, commands…
|
||||
<span class="kbd">⌘K</span>
|
||||
</div>
|
||||
<div class="ico-btn" title="Notifications">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9" />
|
||||
<path d="M13.7 21a2 2 0 0 1-3.4 0" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ico-btn" title="Theme">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path
|
||||
d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<!-- Status line -->
|
||||
<div class="statline">
|
||||
<div class="stat">
|
||||
<div class="lbl">Services Online</div>
|
||||
<div class="val ok">08 / 10</div>
|
||||
<div class="sub">2 require attention</div>
|
||||
</div>
|
||||
<div class="stat s2">
|
||||
<div class="lbl">Avg Response</div>
|
||||
<div class="val warn">
|
||||
142<span style="font-size: 14px; color: var(--ink-faint)"> ms</span>
|
||||
</div>
|
||||
<div class="sub">p95 over 24h</div>
|
||||
</div>
|
||||
<div class="stat s3">
|
||||
<div class="lbl">Fleet Uptime</div>
|
||||
<div class="val" style="color: #3aa0ff">99.4%</div>
|
||||
<div class="sub">rolling 30 days</div>
|
||||
</div>
|
||||
<div class="stat s4">
|
||||
<div class="lbl">UPS Load</div>
|
||||
<div class="val bad">61%</div>
|
||||
<div class="sub">est. 38 min on battery</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Favorites / pinned -->
|
||||
<div class="sec-head">
|
||||
<h2>Pinned Services</h2>
|
||||
<span class="rule"></span><span class="count">8 ACTIVE</span>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<!-- 1 Jellyfin -->
|
||||
<div class="node">
|
||||
<div class="node-top">
|
||||
<div class="node-ico">🎬</div>
|
||||
<div>
|
||||
<div class="node-name">Jellyfin</div>
|
||||
<div class="node-cat">Media</div>
|
||||
</div>
|
||||
<div class="led ok"></div>
|
||||
</div>
|
||||
<div class="node-foot">
|
||||
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="#36e0a4"
|
||||
stroke-width="1.5"
|
||||
points="0,16 12,14 24,17 36,9 48,12 60,7 72,10 84,5 96,8"
|
||||
/></svg
|
||||
><span class="up"><b>99.9%</b> 24h</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 2 Immich -->
|
||||
<div class="node">
|
||||
<div class="node-top">
|
||||
<div class="node-ico">📷</div>
|
||||
<div>
|
||||
<div class="node-name">Immich</div>
|
||||
<div class="node-cat">Photos</div>
|
||||
</div>
|
||||
<div class="led ok"></div>
|
||||
</div>
|
||||
<div class="node-foot">
|
||||
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="#36e0a4"
|
||||
stroke-width="1.5"
|
||||
points="0,12 12,13 24,11 36,12 48,10 60,11 72,9 84,11 96,10"
|
||||
/></svg
|
||||
><span class="up"><b>100%</b> 24h</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 3 Gitea -->
|
||||
<div class="node">
|
||||
<div class="node-top">
|
||||
<div class="node-ico">🌿</div>
|
||||
<div>
|
||||
<div class="node-name">Gitea</div>
|
||||
<div class="node-cat">Git</div>
|
||||
</div>
|
||||
<div class="led ok"></div>
|
||||
</div>
|
||||
<div class="node-foot">
|
||||
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="#36e0a4"
|
||||
stroke-width="1.5"
|
||||
points="0,14 12,10 24,12 36,8 48,11 60,9 72,13 84,8 96,9"
|
||||
/></svg
|
||||
><span class="up"><b>99.8%</b> 24h</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 4 Portainer -->
|
||||
<div class="node">
|
||||
<div class="node-top">
|
||||
<div class="node-ico">🐳</div>
|
||||
<div>
|
||||
<div class="node-name">Portainer</div>
|
||||
<div class="node-cat">Containers</div>
|
||||
</div>
|
||||
<div class="led warn"></div>
|
||||
</div>
|
||||
<div class="node-foot">
|
||||
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="#ffb020"
|
||||
stroke-width="1.5"
|
||||
points="0,10 12,12 24,9 36,15 48,11 60,18 72,12 84,16 96,13"
|
||||
/></svg
|
||||
><span class="up"><b style="color: var(--warn)">98.1%</b> 24h</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 5 Pi-hole -->
|
||||
<div class="node">
|
||||
<div class="node-top">
|
||||
<div class="node-ico">🛡️</div>
|
||||
<div>
|
||||
<div class="node-name">Pi-hole</div>
|
||||
<div class="node-cat">DNS</div>
|
||||
</div>
|
||||
<div class="led ok"></div>
|
||||
</div>
|
||||
<div class="node-foot">
|
||||
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="#36e0a4"
|
||||
stroke-width="1.5"
|
||||
points="0,13 12,11 24,12 36,10 48,11 60,9 72,10 84,8 96,9"
|
||||
/></svg
|
||||
><span class="up"><b>100%</b> 24h</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 6 Planka -->
|
||||
<div class="node">
|
||||
<div class="node-top">
|
||||
<div class="node-ico">📋</div>
|
||||
<div>
|
||||
<div class="node-name">Planka</div>
|
||||
<div class="node-cat">Kanban</div>
|
||||
</div>
|
||||
<div class="led ok"></div>
|
||||
</div>
|
||||
<div class="node-foot">
|
||||
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="#36e0a4"
|
||||
stroke-width="1.5"
|
||||
points="0,15 12,12 24,14 36,11 48,12 60,10 72,12 84,9 96,11"
|
||||
/></svg
|
||||
><span class="up"><b>99.5%</b> 24h</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 7 Deluge -->
|
||||
<div class="node">
|
||||
<div class="node-top">
|
||||
<div class="node-ico">⬇️</div>
|
||||
<div>
|
||||
<div class="node-name">Deluge</div>
|
||||
<div class="node-cat">Downloads</div>
|
||||
</div>
|
||||
<div class="led bad"></div>
|
||||
</div>
|
||||
<div class="node-foot">
|
||||
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="#ff4d5e"
|
||||
stroke-width="1.5"
|
||||
points="0,9 12,11 24,14 36,12 48,18 60,16 72,20 84,19 96,21"
|
||||
/></svg
|
||||
><span class="up bad"><b>OFFLINE</b></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 8 Pi-hole / NPM -->
|
||||
<div class="node">
|
||||
<div class="node-top">
|
||||
<div class="node-ico">🔀</div>
|
||||
<div>
|
||||
<div class="node-name">Nginx Proxy Mgr</div>
|
||||
<div class="node-cat">Network</div>
|
||||
</div>
|
||||
<div class="led ok"></div>
|
||||
</div>
|
||||
<div class="node-foot">
|
||||
<svg class="spark" viewBox="0 0 96 22" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="#36e0a4"
|
||||
stroke-width="1.5"
|
||||
points="0,12 12,11 24,12 36,10 48,11 60,11 72,9 84,10 96,9"
|
||||
/></svg
|
||||
><span class="up"><b>99.9%</b> 24h</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-note">
|
||||
// COMMAND DECK — Saira + JetBrains Mono · tactical dark · LED telemetry · monospace
|
||||
data
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,915 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Web App Launcher — Aurora Glass</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=Outfit:wght@300;400;500;600;700&family=Manrope:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0a0a14;
|
||||
--ink: #f3f2fb;
|
||||
--ink-dim: #a7a6c4;
|
||||
--ink-faint: #6f6e90;
|
||||
--accent-h: 265; /* user-tunable hue → this is the killer feature */
|
||||
--accent: hsl(var(--accent-h) 90% 66%);
|
||||
--accent-2: hsl(calc(var(--accent-h) + 60) 85% 64%);
|
||||
--accent-soft: hsl(var(--accent-h) 90% 66% / 0.14);
|
||||
--glass: rgba(255, 255, 255, 0.05);
|
||||
--glass-2: rgba(255, 255, 255, 0.07);
|
||||
--glass-line: rgba(255, 255, 255, 0.1);
|
||||
--ok: #34e0a1;
|
||||
--warn: #ffc24b;
|
||||
--bad: #ff5d73;
|
||||
--radius: 18px;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: 'Manrope', system-ui, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
}
|
||||
/* aurora mesh */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: -20%;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
filter: blur(60px);
|
||||
opacity: 0.9;
|
||||
background:
|
||||
radial-gradient(40% 40% at 18% 22%, hsl(var(--accent-h) 90% 60% / 0.55), transparent 70%),
|
||||
radial-gradient(
|
||||
38% 38% at 82% 18%,
|
||||
hsl(calc(var(--accent-h) + 70) 90% 60% / 0.42),
|
||||
transparent 70%
|
||||
),
|
||||
radial-gradient(
|
||||
45% 45% at 70% 85%,
|
||||
hsl(calc(var(--accent-h) - 40) 90% 58% / 0.4),
|
||||
transparent 72%
|
||||
),
|
||||
radial-gradient(
|
||||
40% 40% at 25% 90%,
|
||||
hsl(calc(var(--accent-h) + 120) 80% 55% / 0.3),
|
||||
transparent 72%
|
||||
);
|
||||
animation: drift 22s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes drift {
|
||||
0% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-3%, 2%) scale(1.08);
|
||||
}
|
||||
}
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background: radial-gradient(
|
||||
120% 120% at 50% -10%,
|
||||
transparent 40%,
|
||||
rgba(10, 10, 20, 0.6) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.shell {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 248px 1fr;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* sidebar (glass) */
|
||||
.side {
|
||||
margin: 16px 0 16px 16px;
|
||||
border-radius: 24px;
|
||||
padding: 20px;
|
||||
background: var(--glass);
|
||||
border: 1px solid var(--glass-line);
|
||||
backdrop-filter: blur(26px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(26px) saturate(160%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
margin-bottom: 26px;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
.brand .mark {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, var(--accent), var(--accent-2));
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: #fff;
|
||||
box-shadow: 0 8px 24px -6px var(--accent);
|
||||
}
|
||||
.brand .mark svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.brand .name {
|
||||
font-family: 'Outfit';
|
||||
font-weight: 600;
|
||||
font-size: 17px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.brand .name span {
|
||||
display: block;
|
||||
font-family: 'Manrope';
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
color: var(--ink-faint);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.nav-grp {
|
||||
font-family: 'Outfit';
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.13em;
|
||||
font-size: 10px;
|
||||
color: var(--ink-faint);
|
||||
margin: 14px 8px 8px;
|
||||
}
|
||||
.nav-i {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 11px 12px;
|
||||
border-radius: 12px;
|
||||
color: var(--ink-dim);
|
||||
font-weight: 500;
|
||||
font-size: 14.5px;
|
||||
cursor: pointer;
|
||||
transition: 0.18s;
|
||||
position: relative;
|
||||
}
|
||||
.nav-i svg {
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
.nav-i:hover {
|
||||
color: var(--ink);
|
||||
background: var(--glass-2);
|
||||
}
|
||||
.nav-i.on {
|
||||
color: var(--ink);
|
||||
background: var(--accent-soft);
|
||||
box-shadow: inset 0 0 0 1px hsl(var(--accent-h) 90% 66% / 0.35);
|
||||
}
|
||||
.nav-i.on::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -20px;
|
||||
top: 9px;
|
||||
bottom: 9px;
|
||||
width: 3px;
|
||||
border-radius: 3px;
|
||||
background: var(--accent);
|
||||
}
|
||||
.nav-i .dot {
|
||||
margin-left: auto;
|
||||
font-size: 11px;
|
||||
color: var(--ink-faint);
|
||||
font-weight: 600;
|
||||
}
|
||||
.side-foot {
|
||||
margin-top: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
padding: 10px;
|
||||
border-radius: 14px;
|
||||
background: var(--glass);
|
||||
border: 1px solid var(--glass-line);
|
||||
}
|
||||
.av {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, var(--accent), var(--accent-2));
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
}
|
||||
.side-foot .who {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.side-foot .who span {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: var(--ink-faint);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* main */
|
||||
.main {
|
||||
padding: 30px 34px;
|
||||
min-width: 0;
|
||||
}
|
||||
.head {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 20px;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
.hello {
|
||||
font-family: 'Outfit';
|
||||
font-weight: 600;
|
||||
font-size: 30px;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.05;
|
||||
}
|
||||
.hello em {
|
||||
font-style: normal;
|
||||
background: linear-gradient(120deg, var(--accent), var(--accent-2));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
.sub {
|
||||
color: var(--ink-dim);
|
||||
font-size: 14px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.searchwrap {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 300px;
|
||||
background: var(--glass);
|
||||
border: 1px solid var(--glass-line);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 14px;
|
||||
padding: 11px 14px;
|
||||
color: var(--ink-faint);
|
||||
font-size: 13.5px;
|
||||
cursor: text;
|
||||
}
|
||||
.search svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.search .k {
|
||||
margin-left: auto;
|
||||
font-size: 11px;
|
||||
background: var(--glass-2);
|
||||
border-radius: 6px;
|
||||
padding: 2px 7px;
|
||||
}
|
||||
.gbtn {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 14px;
|
||||
background: var(--glass);
|
||||
border: 1px solid var(--glass-line);
|
||||
backdrop-filter: blur(20px);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--ink-dim);
|
||||
cursor: pointer;
|
||||
transition: 0.18s;
|
||||
}
|
||||
.gbtn:hover {
|
||||
color: var(--ink);
|
||||
background: var(--glass-2);
|
||||
}
|
||||
.gbtn svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/* metric row */
|
||||
.metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 16px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.metric {
|
||||
background: var(--glass);
|
||||
border: 1px solid var(--glass-line);
|
||||
backdrop-filter: blur(22px) saturate(150%);
|
||||
border-radius: var(--radius);
|
||||
padding: 18px 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.metric .ic {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 11px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent);
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.metric .ic svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
.metric .v {
|
||||
font-family: 'Outfit';
|
||||
font-size: 27px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.metric .l {
|
||||
color: var(--ink-dim);
|
||||
font-size: 13px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.metric .trend {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
right: 18px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--ok);
|
||||
background: rgba(52, 224, 161, 0.12);
|
||||
padding: 3px 9px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
.metric .trend.dn {
|
||||
color: var(--bad);
|
||||
background: rgba(255, 93, 115, 0.12);
|
||||
}
|
||||
|
||||
.sectitle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 8px 0 16px;
|
||||
}
|
||||
.sectitle h2 {
|
||||
font-family: 'Outfit';
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.sectitle a {
|
||||
font-size: 13px;
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.apps {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(225px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.card {
|
||||
background: var(--glass);
|
||||
border: 1px solid var(--glass-line);
|
||||
backdrop-filter: blur(22px) saturate(150%);
|
||||
-webkit-backdrop-filter: blur(22px) saturate(150%);
|
||||
border-radius: var(--radius);
|
||||
padding: 18px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
transform 0.22s cubic-bezier(0.2, 0.7, 0.2, 1),
|
||||
box-shadow 0.22s,
|
||||
border-color 0.22s;
|
||||
}
|
||||
.card::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: var(--radius);
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), transparent 40%);
|
||||
pointer-events: none;
|
||||
}
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
border-color: hsl(var(--accent-h) 90% 66% / 0.5);
|
||||
box-shadow: 0 24px 50px -20px hsl(var(--accent-h) 90% 50% / 0.55);
|
||||
}
|
||||
.card .row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 13px;
|
||||
}
|
||||
.ico {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 13px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 22px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border: 1px solid var(--glass-line);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.nm {
|
||||
font-family: 'Outfit';
|
||||
font-weight: 600;
|
||||
font-size: 15.5px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.ct {
|
||||
font-size: 12px;
|
||||
color: var(--ink-faint);
|
||||
margin-top: 1px;
|
||||
}
|
||||
.pill {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
padding: 5px 10px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
.pill.ok {
|
||||
color: var(--ok);
|
||||
background: rgba(52, 224, 161, 0.13);
|
||||
}
|
||||
.pill.warn {
|
||||
color: var(--warn);
|
||||
background: rgba(255, 194, 75, 0.13);
|
||||
}
|
||||
.pill.bad {
|
||||
color: var(--bad);
|
||||
background: rgba(255, 93, 115, 0.13);
|
||||
}
|
||||
.pill .b {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
box-shadow: 0 0 8px currentColor;
|
||||
}
|
||||
.meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.meta .up {
|
||||
font-size: 12.5px;
|
||||
color: var(--ink-dim);
|
||||
}
|
||||
.meta .up b {
|
||||
color: var(--ink);
|
||||
font-weight: 600;
|
||||
}
|
||||
.spark {
|
||||
height: 24px;
|
||||
width: 84px;
|
||||
}
|
||||
|
||||
@keyframes rise {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(14px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
.metric,
|
||||
.card {
|
||||
animation: rise 0.55s both;
|
||||
}
|
||||
.metric:nth-child(2) {
|
||||
animation-delay: 0.06s;
|
||||
}
|
||||
.metric:nth-child(3) {
|
||||
animation-delay: 0.12s;
|
||||
}
|
||||
.metric:nth-child(4) {
|
||||
animation-delay: 0.18s;
|
||||
}
|
||||
.apps .card:nth-child(1) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.apps .card:nth-child(2) {
|
||||
animation-delay: 0.16s;
|
||||
}
|
||||
.apps .card:nth-child(3) {
|
||||
animation-delay: 0.22s;
|
||||
}
|
||||
.apps .card:nth-child(4) {
|
||||
animation-delay: 0.28s;
|
||||
}
|
||||
.apps .card:nth-child(5) {
|
||||
animation-delay: 0.34s;
|
||||
}
|
||||
.apps .card:nth-child(6) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.apps .card:nth-child(7) {
|
||||
animation-delay: 0.46s;
|
||||
}
|
||||
.apps .card:nth-child(8) {
|
||||
animation-delay: 0.52s;
|
||||
}
|
||||
|
||||
.swatches {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-top: 30px;
|
||||
justify-content: center;
|
||||
color: var(--ink-faint);
|
||||
font-size: 12px;
|
||||
}
|
||||
.sw {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--glass-line);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="shell">
|
||||
<!-- Sidebar -->
|
||||
<aside class="side">
|
||||
<div class="brand">
|
||||
<div class="mark">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="7" height="7" rx="1" />
|
||||
<rect x="14" y="3" width="7" height="7" rx="1" />
|
||||
<rect x="14" y="14" width="7" height="7" rx="1" />
|
||||
<rect x="3" y="14" width="7" height="7" rx="1" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="name">Launcher<span>home cloud</span></div>
|
||||
</div>
|
||||
<div class="nav-grp">Workspace</div>
|
||||
<div class="nav-i on">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<rect x="3" y="3" width="18" height="18" rx="3" />
|
||||
<path d="M3 9h18M9 21V9" />
|
||||
</svg>
|
||||
Overview
|
||||
</div>
|
||||
<div class="nav-i">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M3 12h18M12 3a14 14 0 0 1 0 18 14 14 0 0 1 0-18" />
|
||||
</svg>
|
||||
All Apps <span class="dot">10</span>
|
||||
</div>
|
||||
<div class="nav-i">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
|
||||
</svg>
|
||||
Status
|
||||
</div>
|
||||
<div class="nav-grp">Boards</div>
|
||||
<div class="nav-i">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<rect x="3" y="3" width="18" height="18" rx="3" />
|
||||
</svg>
|
||||
Media Center
|
||||
</div>
|
||||
<div class="nav-i">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<rect x="3" y="3" width="18" height="18" rx="3" />
|
||||
</svg>
|
||||
Infrastructure
|
||||
</div>
|
||||
<div class="nav-i">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<path d="M12 5v14M5 12h14" />
|
||||
</svg>
|
||||
New board…
|
||||
</div>
|
||||
<div class="side-foot">
|
||||
<div class="av">AD</div>
|
||||
<div class="who">Alexei<span>Administrator</span></div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main -->
|
||||
<main class="main">
|
||||
<div class="head">
|
||||
<div>
|
||||
<div class="hello">Good evening, <em>Alexei</em></div>
|
||||
<div class="sub">All systems nominal — 8 of 10 services responding</div>
|
||||
</div>
|
||||
<div class="searchwrap">
|
||||
<div class="search">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="7" />
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
Search… <span class="k">⌘K</span>
|
||||
</div>
|
||||
<div class="gbtn">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9" />
|
||||
<path d="M13.7 21a2 2 0 0 1-3.4 0" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="gbtn">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path
|
||||
d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- metrics -->
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="ic">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M20 6 9 17l-5-5" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="v">8<span style="color: var(--ink-faint); font-size: 18px">/10</span></div>
|
||||
<div class="l">Services online</div>
|
||||
<span class="trend">+2</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="ic">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M12 7v5l3 2" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="v">142<span style="color: var(--ink-faint); font-size: 16px">ms</span></div>
|
||||
<div class="l">Avg response</div>
|
||||
<span class="trend dn">+18ms</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="ic">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M3 12h4l3 8 4-16 3 8h4" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="v">99.4%</div>
|
||||
<div class="l">Uptime · 30d</div>
|
||||
<span class="trend">+0.2</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="ic">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M13 2 3 14h7l-1 8 10-12h-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="v">61%</div>
|
||||
<div class="l">UPS load · 38m</div>
|
||||
<span class="trend dn">batt</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sectitle">
|
||||
<h2>Favorites</h2>
|
||||
<a href="#">View all apps →</a>
|
||||
</div>
|
||||
<div class="apps">
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<div class="ico">🎬</div>
|
||||
<div>
|
||||
<div class="nm">Jellyfin</div>
|
||||
<div class="ct">Media</div>
|
||||
</div>
|
||||
<div class="pill ok"><span class="b"></span>Up</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<span class="up"><b>99.9%</b> uptime</span
|
||||
><svg class="spark" viewBox="0 0 84 24" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="var(--accent)"
|
||||
stroke-width="2"
|
||||
points="0,18 11,15 22,17 33,9 44,12 55,7 66,11 76,5 84,8"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<div class="ico">📷</div>
|
||||
<div>
|
||||
<div class="nm">Immich</div>
|
||||
<div class="ct">Photos</div>
|
||||
</div>
|
||||
<div class="pill ok"><span class="b"></span>Up</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<span class="up"><b>100%</b> uptime</span
|
||||
><svg class="spark" viewBox="0 0 84 24" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="var(--accent)"
|
||||
stroke-width="2"
|
||||
points="0,13 11,14 22,12 33,13 44,11 55,12 66,10 76,12 84,11"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<div class="ico">🌿</div>
|
||||
<div>
|
||||
<div class="nm">Gitea</div>
|
||||
<div class="ct">Git server</div>
|
||||
</div>
|
||||
<div class="pill ok"><span class="b"></span>Up</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<span class="up"><b>99.8%</b> uptime</span
|
||||
><svg class="spark" viewBox="0 0 84 24" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="var(--accent)"
|
||||
stroke-width="2"
|
||||
points="0,15 11,10 22,13 33,8 44,12 55,9 66,14 76,8 84,10"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<div class="ico">🐳</div>
|
||||
<div>
|
||||
<div class="nm">Portainer</div>
|
||||
<div class="ct">Containers</div>
|
||||
</div>
|
||||
<div class="pill warn"><span class="b"></span>Slow</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<span class="up"><b>98.1%</b> uptime</span
|
||||
><svg class="spark" viewBox="0 0 84 24" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="var(--warn)"
|
||||
stroke-width="2"
|
||||
points="0,11 11,13 22,9 33,16 44,11 55,19 66,12 76,17 84,13"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<div class="ico">🛡️</div>
|
||||
<div>
|
||||
<div class="nm">Pi-hole</div>
|
||||
<div class="ct">DNS · Ads</div>
|
||||
</div>
|
||||
<div class="pill ok"><span class="b"></span>Up</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<span class="up"><b>100%</b> uptime</span
|
||||
><svg class="spark" viewBox="0 0 84 24" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="var(--accent)"
|
||||
stroke-width="2"
|
||||
points="0,14 11,12 22,13 33,11 44,12 55,10 66,11 76,9 84,10"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<div class="ico">📋</div>
|
||||
<div>
|
||||
<div class="nm">Planka</div>
|
||||
<div class="ct">Kanban</div>
|
||||
</div>
|
||||
<div class="pill ok"><span class="b"></span>Up</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<span class="up"><b>99.5%</b> uptime</span
|
||||
><svg class="spark" viewBox="0 0 84 24" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="var(--accent)"
|
||||
stroke-width="2"
|
||||
points="0,16 11,13 22,15 33,12 44,13 55,11 66,13 76,10 84,12"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<div class="ico">⬇️</div>
|
||||
<div>
|
||||
<div class="nm">Deluge</div>
|
||||
<div class="ct">Downloads</div>
|
||||
</div>
|
||||
<div class="pill bad"><span class="b"></span>Down</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<span class="up" style="color: var(--bad)"
|
||||
><b style="color: var(--bad)">offline</b> · 4m</span
|
||||
><svg class="spark" viewBox="0 0 84 24" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="var(--bad)"
|
||||
stroke-width="2"
|
||||
points="0,10 11,12 22,15 33,13 44,19 55,17 66,21 76,20 84,22"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<div class="ico">🔀</div>
|
||||
<div>
|
||||
<div class="nm">Proxy Mgr</div>
|
||||
<div class="ct">Network</div>
|
||||
</div>
|
||||
<div class="pill ok"><span class="b"></span>Up</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<span class="up"><b>99.9%</b> uptime</span
|
||||
><svg class="spark" viewBox="0 0 84 24" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="var(--accent)"
|
||||
stroke-width="2"
|
||||
points="0,13 11,12 22,13 33,11 44,12 55,12 66,10 76,11 84,10"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="swatches">
|
||||
Accent (user-tunable):
|
||||
<span
|
||||
class="sw"
|
||||
style="background: hsl(265 90% 66%)"
|
||||
onclick="document.documentElement.style.setProperty('--accent-h', '265')"
|
||||
></span>
|
||||
<span
|
||||
class="sw"
|
||||
style="background: hsl(210 90% 60%)"
|
||||
onclick="document.documentElement.style.setProperty('--accent-h', '210')"
|
||||
></span>
|
||||
<span
|
||||
class="sw"
|
||||
style="background: hsl(150 80% 55%)"
|
||||
onclick="document.documentElement.style.setProperty('--accent-h', '150')"
|
||||
></span>
|
||||
<span
|
||||
class="sw"
|
||||
style="background: hsl(20 90% 62%)"
|
||||
onclick="document.documentElement.style.setProperty('--accent-h', '20')"
|
||||
></span>
|
||||
<span
|
||||
class="sw"
|
||||
style="background: hsl(330 85% 65%)"
|
||||
onclick="document.documentElement.style.setProperty('--accent-h', '330')"
|
||||
></span>
|
||||
— try clicking; the whole UI + aurora retints live
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,643 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Web App Launcher — Editorial</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=Bricolage+Grotesque:opsz,wght@12..96,500;12..96,600;12..96,700;12..96,800&family=Instrument+Serif:ital@0;1&family=Hanken+Grotesk:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<style>
|
||||
:root {
|
||||
--paper: #f4f1ea; /* warm paper */
|
||||
--paper-2: #ece7db;
|
||||
--card: #fbfaf6;
|
||||
--ink: #191712;
|
||||
--ink-2: #5a554a;
|
||||
--ink-faint: #9b9484;
|
||||
--line: #1a1712;
|
||||
--line-soft: #d8d2c4;
|
||||
--accent: #ff5436; /* vermilion */
|
||||
--accent-ink: #cf3a1f;
|
||||
--blue: #1f4ae0;
|
||||
--ok: #1f8a4c;
|
||||
--warn: #b8730a;
|
||||
--bad: #cf2020;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
font-family: 'Hanken Grotesk', system-ui, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background-image: radial-gradient(rgba(0, 0, 0, 0.022) 1px, transparent 1px);
|
||||
background-size: 5px 5px;
|
||||
}
|
||||
.wrap {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
padding: 0 26px;
|
||||
}
|
||||
|
||||
/* top bar */
|
||||
.masthead {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
padding: 22px 0 18px;
|
||||
border-bottom: 2.5px solid var(--line);
|
||||
}
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.logo .glyph {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
background: var(--ink);
|
||||
color: var(--paper);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.logo .glyph svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
.logo .tt {
|
||||
font-family: 'Bricolage Grotesque';
|
||||
font-weight: 800;
|
||||
font-size: 23px;
|
||||
letter-spacing: -0.03em;
|
||||
line-height: 0.9;
|
||||
}
|
||||
.logo .tt small {
|
||||
display: block;
|
||||
font-family: 'Instrument Serif';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0;
|
||||
color: var(--ink-2);
|
||||
}
|
||||
.nav {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-left: 18px;
|
||||
}
|
||||
.nav a {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
padding: 8px 14px;
|
||||
border-radius: 2px;
|
||||
transition: 0.15s;
|
||||
}
|
||||
.nav a:hover {
|
||||
background: var(--paper-2);
|
||||
}
|
||||
.nav a.on {
|
||||
background: var(--ink);
|
||||
color: var(--paper);
|
||||
}
|
||||
.tools {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
border: 2px solid var(--line);
|
||||
border-radius: 2px;
|
||||
padding: 9px 13px;
|
||||
font-size: 13px;
|
||||
color: var(--ink-2);
|
||||
cursor: text;
|
||||
background: var(--card);
|
||||
}
|
||||
.search svg {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
.search .k {
|
||||
margin-left: 8px;
|
||||
font-family: 'Hanken Grotesk';
|
||||
font-weight: 700;
|
||||
font-size: 10px;
|
||||
border: 1.5px solid var(--line-soft);
|
||||
border-radius: 3px;
|
||||
padding: 1px 6px;
|
||||
}
|
||||
.ib {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 2px solid var(--line);
|
||||
border-radius: 2px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
cursor: pointer;
|
||||
background: var(--card);
|
||||
transition: 0.15s;
|
||||
}
|
||||
.ib:hover {
|
||||
background: var(--ink);
|
||||
color: var(--paper);
|
||||
}
|
||||
.ib svg {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
}
|
||||
|
||||
/* hero */
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: 1.45fr 1fr;
|
||||
gap: 0;
|
||||
border-bottom: 2.5px solid var(--line);
|
||||
}
|
||||
.hero-l {
|
||||
padding: 46px 40px 46px 0;
|
||||
border-right: 2.5px solid var(--line);
|
||||
}
|
||||
.kicker {
|
||||
font-family: 'Hanken Grotesk';
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.22em;
|
||||
font-size: 12px;
|
||||
color: var(--accent-ink);
|
||||
}
|
||||
.hero h1 {
|
||||
font-family: 'Bricolage Grotesque';
|
||||
font-weight: 800;
|
||||
font-size: 62px;
|
||||
line-height: 0.95;
|
||||
letter-spacing: -0.035em;
|
||||
margin: 14px 0 0;
|
||||
}
|
||||
.hero h1 em {
|
||||
font-family: 'Instrument Serif';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
color: var(--accent);
|
||||
}
|
||||
.hero p {
|
||||
font-size: 16px;
|
||||
color: var(--ink-2);
|
||||
max-width: 30ch;
|
||||
margin-top: 18px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.hero-cta {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.btn {
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
padding: 12px 20px;
|
||||
border-radius: 2px;
|
||||
border: 2px solid var(--line);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.btn.solid {
|
||||
background: var(--ink);
|
||||
color: var(--paper);
|
||||
}
|
||||
.btn.solid:hover {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.btn.ghost {
|
||||
background: transparent;
|
||||
color: var(--ink);
|
||||
}
|
||||
.btn.ghost:hover {
|
||||
background: var(--paper-2);
|
||||
}
|
||||
.hero-r {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 1fr 1fr;
|
||||
}
|
||||
.figure {
|
||||
padding: 20px 0 20px 36px;
|
||||
border-bottom: 1.5px solid var(--line-soft);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 14px;
|
||||
}
|
||||
.figure:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.figure .num {
|
||||
font-family: 'Bricolage Grotesque';
|
||||
font-weight: 800;
|
||||
font-size: 46px;
|
||||
letter-spacing: -0.04em;
|
||||
line-height: 0.85;
|
||||
min-width: 120px;
|
||||
}
|
||||
.figure .num.acc {
|
||||
color: var(--accent);
|
||||
}
|
||||
.figure .num.bl {
|
||||
color: var(--blue);
|
||||
}
|
||||
.figure .desc {
|
||||
font-size: 13px;
|
||||
color: var(--ink-2);
|
||||
line-height: 1.35;
|
||||
}
|
||||
.figure .desc b {
|
||||
display: block;
|
||||
font-family: 'Bricolage Grotesque';
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
font-size: 15px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* section label */
|
||||
.slab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin: 36px 0 20px;
|
||||
}
|
||||
.slab h2 {
|
||||
font-family: 'Bricolage Grotesque';
|
||||
font-weight: 700;
|
||||
font-size: 22px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.slab .ln {
|
||||
flex: 1;
|
||||
height: 2px;
|
||||
background: var(--line);
|
||||
}
|
||||
.slab .meta {
|
||||
font-family: 'Instrument Serif';
|
||||
font-style: italic;
|
||||
font-size: 16px;
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
/* apps — asymmetric editorial grid */
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
gap: 14px;
|
||||
}
|
||||
.tile {
|
||||
grid-column: span 3;
|
||||
background: var(--card);
|
||||
border: 2px solid var(--line);
|
||||
border-radius: 3px;
|
||||
padding: 18px;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 0.15s,
|
||||
box-shadow 0.15s;
|
||||
position: relative;
|
||||
box-shadow: 4px 4px 0 var(--line);
|
||||
}
|
||||
.tile:hover {
|
||||
transform: translate(-2px, -2px);
|
||||
box-shadow: 7px 7px 0 var(--accent);
|
||||
}
|
||||
.tile.wide {
|
||||
grid-column: span 6;
|
||||
}
|
||||
.tile.tall {
|
||||
grid-column: span 3;
|
||||
}
|
||||
.t-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
.t-ico {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border: 2px solid var(--line);
|
||||
border-radius: 3px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 22px;
|
||||
background: var(--paper);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.t-name {
|
||||
font-family: 'Bricolage Grotesque';
|
||||
font-weight: 700;
|
||||
font-size: 18px;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1;
|
||||
}
|
||||
.t-cat {
|
||||
font-family: 'Instrument Serif';
|
||||
font-style: italic;
|
||||
font-size: 14px;
|
||||
color: var(--ink-2);
|
||||
margin-top: 3px;
|
||||
}
|
||||
.tag {
|
||||
margin-left: auto;
|
||||
font-weight: 700;
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
padding: 4px 8px;
|
||||
border-radius: 2px;
|
||||
border: 1.5px solid currentColor;
|
||||
}
|
||||
.tag.ok {
|
||||
color: var(--ok);
|
||||
}
|
||||
.tag.warn {
|
||||
color: var(--warn);
|
||||
}
|
||||
.tag.bad {
|
||||
color: var(--bad);
|
||||
background: var(--bad);
|
||||
color: #fff;
|
||||
border-color: var(--bad);
|
||||
}
|
||||
.t-foot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 18px;
|
||||
padding-top: 13px;
|
||||
border-top: 1.5px solid var(--line-soft);
|
||||
}
|
||||
.t-foot .up {
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
}
|
||||
.t-foot .up small {
|
||||
font-weight: 500;
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
.spark {
|
||||
height: 24px;
|
||||
width: 90px;
|
||||
}
|
||||
.tile.wide .blurb {
|
||||
font-size: 14px;
|
||||
color: var(--ink-2);
|
||||
line-height: 1.5;
|
||||
margin-top: 14px;
|
||||
max-width: 42ch;
|
||||
}
|
||||
|
||||
@keyframes pop {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(12px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
.tile,
|
||||
.figure {
|
||||
animation: pop 0.5s both;
|
||||
}
|
||||
.grid .tile:nth-child(1) {
|
||||
animation-delay: 0.05s;
|
||||
}
|
||||
.grid .tile:nth-child(2) {
|
||||
animation-delay: 0.11s;
|
||||
}
|
||||
.grid .tile:nth-child(3) {
|
||||
animation-delay: 0.17s;
|
||||
}
|
||||
.grid .tile:nth-child(4) {
|
||||
animation-delay: 0.23s;
|
||||
}
|
||||
.grid .tile:nth-child(5) {
|
||||
animation-delay: 0.29s;
|
||||
}
|
||||
.grid .tile:nth-child(6) {
|
||||
animation-delay: 0.35s;
|
||||
}
|
||||
.grid .tile:nth-child(7) {
|
||||
animation-delay: 0.41s;
|
||||
}
|
||||
|
||||
.colophon {
|
||||
margin: 46px 0 30px;
|
||||
padding-top: 18px;
|
||||
border-top: 2.5px solid var(--line);
|
||||
font-family: 'Instrument Serif';
|
||||
font-style: italic;
|
||||
font-size: 15px;
|
||||
color: var(--ink-2);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<!-- Masthead -->
|
||||
<div class="masthead">
|
||||
<div class="logo">
|
||||
<div class="glyph">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="7" height="7" />
|
||||
<rect x="14" y="3" width="7" height="7" />
|
||||
<rect x="14" y="14" width="7" height="7" />
|
||||
<rect x="3" y="14" width="7" height="7" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="tt">LAUNCHER<small>the home cloud edition</small></div>
|
||||
</div>
|
||||
<nav class="nav"><a class="on">Overview</a><a>Apps</a><a>Boards</a><a>Status</a></nav>
|
||||
<div class="tools">
|
||||
<div class="search">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="7" />
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
Search<span class="k">⌘K</span>
|
||||
</div>
|
||||
<div class="ib">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path
|
||||
d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="hero">
|
||||
<div class="hero-l">
|
||||
<div class="kicker">Tuesday · 27 May · 19:42</div>
|
||||
<h1>Your stack,<br />all in <em>one place.</em></h1>
|
||||
<p>
|
||||
Ten services under one roof. Eight humming, two asking for attention. Everything
|
||||
launches from here.
|
||||
</p>
|
||||
<div class="hero-cta">
|
||||
<a class="btn solid">Open a board →</a><a class="btn ghost">Add an app</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-r">
|
||||
<div class="figure">
|
||||
<div class="num acc">8/10</div>
|
||||
<div class="desc"><b>Services online</b>Deluge offline · Portainer slow to respond</div>
|
||||
</div>
|
||||
<div class="figure">
|
||||
<div class="num bl">99.4%</div>
|
||||
<div class="desc"><b>Fleet uptime</b>Rolling 30-day average across all monitors</div>
|
||||
</div>
|
||||
<div class="figure">
|
||||
<div class="num">142<span style="font-size: 20px">ms</span></div>
|
||||
<div class="desc"><b>Median response</b>p95 latency over the last 24 hours</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Apps -->
|
||||
<div class="slab">
|
||||
<h2>Favorites</h2>
|
||||
<div class="ln"></div>
|
||||
<div class="meta">eight pinned</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="tile wide">
|
||||
<div class="t-top">
|
||||
<div class="t-ico">🎬</div>
|
||||
<div>
|
||||
<div class="t-name">Jellyfin</div>
|
||||
<div class="t-cat">Media server · the crown jewel</div>
|
||||
</div>
|
||||
<span class="tag ok">Online</span>
|
||||
</div>
|
||||
<p class="blurb">
|
||||
Streaming to 3 devices right now. Library scan completed 2 hours ago — 4,212 movies, 318
|
||||
shows indexed and healthy.
|
||||
</p>
|
||||
<div class="t-foot">
|
||||
<div class="up">99.9% <small>uptime · 24h</small></div>
|
||||
<svg class="spark" viewBox="0 0 90 24" preserveAspectRatio="none">
|
||||
<polyline
|
||||
fill="none"
|
||||
stroke="#ff5436"
|
||||
stroke-width="2.2"
|
||||
points="0,18 12,15 24,17 36,9 48,12 60,7 72,11 82,5 90,8"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="t-top">
|
||||
<div class="t-ico">📷</div>
|
||||
<div>
|
||||
<div class="t-name">Immich</div>
|
||||
<div class="t-cat">Photos</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="t-foot">
|
||||
<div class="up">100% <small>24h</small></div>
|
||||
<span class="tag ok">Up</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="t-top">
|
||||
<div class="t-ico">🌿</div>
|
||||
<div>
|
||||
<div class="t-name">Gitea</div>
|
||||
<div class="t-cat">Git</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="t-foot">
|
||||
<div class="up">99.8% <small>24h</small></div>
|
||||
<span class="tag ok">Up</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="t-top">
|
||||
<div class="t-ico">🐳</div>
|
||||
<div>
|
||||
<div class="t-name">Portainer</div>
|
||||
<div class="t-cat">Containers</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="t-foot">
|
||||
<div class="up">98.1% <small>24h</small></div>
|
||||
<span class="tag warn">Slow</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="t-top">
|
||||
<div class="t-ico">🛡️</div>
|
||||
<div>
|
||||
<div class="t-name">Pi-hole</div>
|
||||
<div class="t-cat">DNS</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="t-foot">
|
||||
<div class="up">100% <small>24h</small></div>
|
||||
<span class="tag ok">Up</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="t-top">
|
||||
<div class="t-ico">📋</div>
|
||||
<div>
|
||||
<div class="t-name">Planka</div>
|
||||
<div class="t-cat">Kanban</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="t-foot">
|
||||
<div class="up">99.5% <small>24h</small></div>
|
||||
<span class="tag ok">Up</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile">
|
||||
<div class="t-top">
|
||||
<div class="t-ico">⬇️</div>
|
||||
<div>
|
||||
<div class="t-name">Deluge</div>
|
||||
<div class="t-cat">Downloads</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="t-foot">
|
||||
<div class="up" style="color: var(--bad)">—</div>
|
||||
<span class="tag bad">Down</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="colophon">
|
||||
<span>Editorial — Bricolage Grotesque + Instrument Serif</span
|
||||
><span>warm paper · ink rules · hard shadows · asymmetric grid</span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,723 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Web App Launcher — Cozy Home</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>
|
||||
:root {
|
||||
--bg: #fdf8f2; /* warm cream */
|
||||
--bg-2: #f6efe4;
|
||||
--card: #fffdfa;
|
||||
--ink: #3a322b;
|
||||
--ink-2: #857a6d;
|
||||
--ink-faint: #b3a899;
|
||||
--line: #ece2d3;
|
||||
--peach: #ff9a76;
|
||||
--terra: #e8754f;
|
||||
--sage: #7fb069;
|
||||
--sky: #6ca9d6;
|
||||
--butter: #f3c969;
|
||||
--lav: #b09fd6;
|
||||
--ok: #5fa86c;
|
||||
--warn: #d99a2b;
|
||||
--bad: #e0685f;
|
||||
--radius: 22px;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: 'Figtree', system-ui, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background:
|
||||
radial-gradient(50% 40% at 12% 0%, rgba(255, 154, 118, 0.16), transparent 70%),
|
||||
radial-gradient(45% 40% at 95% 8%, rgba(108, 169, 214, 0.14), transparent 70%),
|
||||
radial-gradient(50% 45% at 85% 100%, rgba(127, 176, 105, 0.12), transparent 70%);
|
||||
}
|
||||
.shell {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 236px 1fr;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* sidebar */
|
||||
.side {
|
||||
padding: 24px 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
padding: 6px 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.brand .m {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(135deg, var(--peach), var(--terra));
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: #fff;
|
||||
box-shadow: 0 10px 22px -8px var(--terra);
|
||||
}
|
||||
.brand .m svg {
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
}
|
||||
.brand .t {
|
||||
font-family: 'Fraunces';
|
||||
font-weight: 600;
|
||||
font-size: 19px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.brand .t span {
|
||||
display: block;
|
||||
font-family: 'Figtree';
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
.nlabel {
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
font-size: 10.5px;
|
||||
color: var(--ink-faint);
|
||||
margin: 16px 10px 8px;
|
||||
}
|
||||
.ni {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 11px 13px;
|
||||
border-radius: 14px;
|
||||
color: var(--ink-2);
|
||||
font-weight: 600;
|
||||
font-size: 14.5px;
|
||||
cursor: pointer;
|
||||
transition: 0.16s;
|
||||
}
|
||||
.ni svg {
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
}
|
||||
.ni:hover {
|
||||
background: var(--bg-2);
|
||||
color: var(--ink);
|
||||
}
|
||||
.ni.on {
|
||||
background: var(--card);
|
||||
color: var(--terra);
|
||||
box-shadow:
|
||||
0 6px 16px -8px rgba(0, 0, 0, 0.18),
|
||||
inset 0 0 0 1px var(--line);
|
||||
}
|
||||
.ni .c {
|
||||
margin-left: auto;
|
||||
font-size: 11px;
|
||||
background: var(--bg-2);
|
||||
color: var(--ink-2);
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.side-card {
|
||||
margin-top: auto;
|
||||
background: linear-gradient(135deg, rgba(127, 176, 105, 0.16), rgba(108, 169, 214, 0.14));
|
||||
border-radius: 18px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
.side-card p {
|
||||
font-size: 12.5px;
|
||||
color: var(--ink-2);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.side-card .who {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.side-card .av {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 11px;
|
||||
background: linear-gradient(135deg, var(--lav), var(--sky));
|
||||
color: #fff;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-weight: 700;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* main */
|
||||
.main {
|
||||
padding: 30px 36px 40px;
|
||||
min-width: 0;
|
||||
}
|
||||
.top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.top .search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 16px;
|
||||
padding: 12px 16px;
|
||||
color: var(--ink-faint);
|
||||
font-size: 14px;
|
||||
width: 320px;
|
||||
cursor: text;
|
||||
box-shadow: 0 4px 14px -10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.top .search svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.top .search .k {
|
||||
margin-left: auto;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
background: var(--bg-2);
|
||||
padding: 2px 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.rbtn {
|
||||
margin-left: auto;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 15px;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--line);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--ink-2);
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 14px -10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.rbtn + .rbtn {
|
||||
margin-left: 0;
|
||||
}
|
||||
.rbtn:hover {
|
||||
color: var(--terra);
|
||||
}
|
||||
.rbtn svg {
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
}
|
||||
|
||||
.greet {
|
||||
font-family: 'Fraunces';
|
||||
font-weight: 600;
|
||||
font-size: 34px;
|
||||
letter-spacing: -0.02em;
|
||||
margin: 18px 0 4px;
|
||||
}
|
||||
.greet .wave {
|
||||
display: inline-block;
|
||||
animation: wave 2.4s ease-in-out infinite;
|
||||
transform-origin: 70% 70%;
|
||||
}
|
||||
@keyframes wave {
|
||||
0%,
|
||||
60%,
|
||||
100% {
|
||||
transform: rotate(0);
|
||||
}
|
||||
10% {
|
||||
transform: rotate(16deg);
|
||||
}
|
||||
20% {
|
||||
transform: rotate(-8deg);
|
||||
}
|
||||
30% {
|
||||
transform: rotate(14deg);
|
||||
}
|
||||
40% {
|
||||
transform: rotate(-4deg);
|
||||
}
|
||||
}
|
||||
.gsub {
|
||||
color: var(--ink-2);
|
||||
font-size: 15px;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
.gsub b {
|
||||
color: var(--sage);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* summary chips */
|
||||
.chips {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 18px;
|
||||
padding: 14px 18px;
|
||||
box-shadow: 0 8px 20px -16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.chip .ic {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 12px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
.chip .ic svg {
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
color: #fff;
|
||||
}
|
||||
.chip .v {
|
||||
font-family: 'Fraunces';
|
||||
font-weight: 600;
|
||||
font-size: 21px;
|
||||
line-height: 1;
|
||||
}
|
||||
.chip .l {
|
||||
font-size: 12.5px;
|
||||
color: var(--ink-2);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.sec {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
margin: 6px 0 18px;
|
||||
}
|
||||
.sec h2 {
|
||||
font-family: 'Fraunces';
|
||||
font-weight: 600;
|
||||
font-size: 22px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.sec .more {
|
||||
margin-left: auto;
|
||||
font-size: 13.5px;
|
||||
color: var(--terra);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.apps {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 18px;
|
||||
}
|
||||
.card {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 0.2s cubic-bezier(0.2, 0.8, 0.2, 1),
|
||||
box-shadow 0.2s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 26px -20px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.card:hover {
|
||||
transform: translateY(-6px) rotate(-0.4deg);
|
||||
box-shadow: 0 22px 40px -22px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.blob {
|
||||
position: absolute;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
filter: blur(30px);
|
||||
opacity: 0.5;
|
||||
top: -50px;
|
||||
right: -40px;
|
||||
}
|
||||
.ico {
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border-radius: 18px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 26px;
|
||||
position: relative;
|
||||
}
|
||||
.nm {
|
||||
font-family: 'Fraunces';
|
||||
font-weight: 600;
|
||||
font-size: 17px;
|
||||
margin-top: 16px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.ct {
|
||||
font-size: 13px;
|
||||
color: var(--ink-2);
|
||||
margin-top: 1px;
|
||||
}
|
||||
.foot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.dot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.dot .b {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.dot.ok {
|
||||
color: var(--ok);
|
||||
}
|
||||
.dot.ok .b {
|
||||
background: var(--ok);
|
||||
box-shadow: 0 0 0 4px rgba(95, 168, 108, 0.18);
|
||||
}
|
||||
.dot.warn {
|
||||
color: var(--warn);
|
||||
}
|
||||
.dot.warn .b {
|
||||
background: var(--warn);
|
||||
box-shadow: 0 0 0 4px rgba(217, 154, 43, 0.18);
|
||||
}
|
||||
.dot.bad {
|
||||
color: var(--bad);
|
||||
}
|
||||
.dot.bad .b {
|
||||
background: var(--bad);
|
||||
box-shadow: 0 0 0 4px rgba(224, 104, 95, 0.18);
|
||||
}
|
||||
.up {
|
||||
font-size: 12px;
|
||||
color: var(--ink-faint);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@keyframes rise {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(16px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
.card,
|
||||
.chip {
|
||||
animation: rise 0.55s both;
|
||||
}
|
||||
.chip:nth-child(2) {
|
||||
animation-delay: 0.06s;
|
||||
}
|
||||
.chip:nth-child(3) {
|
||||
animation-delay: 0.12s;
|
||||
}
|
||||
.chip:nth-child(4) {
|
||||
animation-delay: 0.18s;
|
||||
}
|
||||
.apps .card:nth-child(1) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
.apps .card:nth-child(2) {
|
||||
animation-delay: 0.16s;
|
||||
}
|
||||
.apps .card:nth-child(3) {
|
||||
animation-delay: 0.22s;
|
||||
}
|
||||
.apps .card:nth-child(4) {
|
||||
animation-delay: 0.28s;
|
||||
}
|
||||
.apps .card:nth-child(5) {
|
||||
animation-delay: 0.34s;
|
||||
}
|
||||
.apps .card:nth-child(6) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
.apps .card:nth-child(7) {
|
||||
animation-delay: 0.46s;
|
||||
}
|
||||
.apps .card:nth-child(8) {
|
||||
animation-delay: 0.52s;
|
||||
}
|
||||
|
||||
.note {
|
||||
text-align: center;
|
||||
color: var(--ink-faint);
|
||||
font-family: 'Fraunces';
|
||||
font-style: italic;
|
||||
font-size: 14px;
|
||||
margin-top: 36px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="shell">
|
||||
<!-- Sidebar -->
|
||||
<aside class="side">
|
||||
<div class="brand">
|
||||
<div class="m">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="3" width="7" height="7" rx="2" />
|
||||
<rect x="14" y="3" width="7" height="7" rx="2" />
|
||||
<rect x="14" y="14" width="7" height="7" rx="2" />
|
||||
<rect x="3" y="14" width="7" height="7" rx="2" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="t">Launcher<span>our home cloud</span></div>
|
||||
</div>
|
||||
<div class="nlabel">Menu</div>
|
||||
<div class="ni on">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9">
|
||||
<path d="M3 11l9-8 9 8M5 10v10h14V10" />
|
||||
</svg>
|
||||
Home
|
||||
</div>
|
||||
<div class="ni">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9">
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M3 12h18M12 3a14 14 0 0 1 0 18 14 14 0 0 1 0-18" />
|
||||
</svg>
|
||||
All apps <span class="c">10</span>
|
||||
</div>
|
||||
<div class="ni">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9">
|
||||
<path d="M20 6 9 17l-5-5" />
|
||||
</svg>
|
||||
Status
|
||||
</div>
|
||||
<div class="nlabel">Rooms</div>
|
||||
<div class="ni">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9">
|
||||
<rect x="3" y="3" width="18" height="18" rx="4" />
|
||||
</svg>
|
||||
Movie night
|
||||
</div>
|
||||
<div class="ni">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9">
|
||||
<rect x="3" y="3" width="18" height="18" rx="4" />
|
||||
</svg>
|
||||
The basement rack
|
||||
</div>
|
||||
<div class="side-card">
|
||||
<p>“Everything’s running smoothly today. ☕ Two apps want a peek when you get a sec.”</p>
|
||||
<div class="who">
|
||||
<div class="av">AD</div>
|
||||
<div>
|
||||
<div style="font-weight: 700; font-size: 13px">Alexei</div>
|
||||
<div style="font-size: 11px; color: var(--ink-faint)">Admin</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main -->
|
||||
<main class="main">
|
||||
<div class="top">
|
||||
<div class="search">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="7" />
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
Search your apps… <span class="k">⌘K</span>
|
||||
</div>
|
||||
<div class="rbtn">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9">
|
||||
<path d="M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9" />
|
||||
<path d="M13.7 21a2 2 0 0 1-3.4 0" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="rbtn">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9">
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path
|
||||
d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="greet">Hi Alexei <span class="wave">👋</span></h1>
|
||||
<p class="gsub">It’s a calm evening — <b>8 of your 10 apps</b> are happy and online.</p>
|
||||
|
||||
<div class="chips">
|
||||
<div class="chip">
|
||||
<div class="ic" style="background: var(--sage)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||
<path d="M20 6 9 17l-5-5" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="v">8/10</div>
|
||||
<div class="l">Apps online</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chip">
|
||||
<div class="ic" style="background: var(--sky)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M12 7v5l3 2" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="v">142ms</div>
|
||||
<div class="l">Avg speed</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chip">
|
||||
<div class="ic" style="background: var(--butter)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||
<path d="M3 12h4l3 8 4-16 3 8h4" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="v">99.4%</div>
|
||||
<div class="l">Uptime · 30d</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chip">
|
||||
<div class="ic" style="background: var(--lav)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
|
||||
<path d="M13 2 3 14h7l-1 8 10-12h-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="v">38 min</div>
|
||||
<div class="l">Battery left</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sec">
|
||||
<h2>Your favorites</h2>
|
||||
<a class="more" href="#">See all →</a>
|
||||
</div>
|
||||
<div class="apps">
|
||||
<div class="card">
|
||||
<div class="blob" style="background: var(--terra)"></div>
|
||||
<div class="ico" style="background: rgba(232, 117, 79, 0.16)">🎬</div>
|
||||
<div class="nm">Jellyfin</div>
|
||||
<div class="ct">Movies & shows</div>
|
||||
<div class="foot">
|
||||
<span class="dot ok"><span class="b"></span>Online</span><span class="up">99.9%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="blob" style="background: var(--sky)"></div>
|
||||
<div class="ico" style="background: rgba(108, 169, 214, 0.18)">📷</div>
|
||||
<div class="nm">Immich</div>
|
||||
<div class="ct">Photos</div>
|
||||
<div class="foot">
|
||||
<span class="dot ok"><span class="b"></span>Online</span><span class="up">100%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="blob" style="background: var(--sage)"></div>
|
||||
<div class="ico" style="background: rgba(127, 176, 105, 0.18)">🌿</div>
|
||||
<div class="nm">Gitea</div>
|
||||
<div class="ct">Code</div>
|
||||
<div class="foot">
|
||||
<span class="dot ok"><span class="b"></span>Online</span><span class="up">99.8%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="blob" style="background: var(--butter)"></div>
|
||||
<div class="ico" style="background: rgba(243, 201, 105, 0.22)">🐳</div>
|
||||
<div class="nm">Portainer</div>
|
||||
<div class="ct">Containers</div>
|
||||
<div class="foot">
|
||||
<span class="dot warn"><span class="b"></span>A bit slow</span
|
||||
><span class="up">98.1%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="blob" style="background: var(--lav)"></div>
|
||||
<div class="ico" style="background: rgba(176, 159, 214, 0.2)">🛡️</div>
|
||||
<div class="nm">Pi-hole</div>
|
||||
<div class="ct">Ad blocker</div>
|
||||
<div class="foot">
|
||||
<span class="dot ok"><span class="b"></span>Online</span><span class="up">100%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="blob" style="background: var(--peach)"></div>
|
||||
<div class="ico" style="background: rgba(255, 154, 118, 0.2)">📋</div>
|
||||
<div class="nm">Planka</div>
|
||||
<div class="ct">To-dos</div>
|
||||
<div class="foot">
|
||||
<span class="dot ok"><span class="b"></span>Online</span><span class="up">99.5%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="blob" style="background: var(--bad)"></div>
|
||||
<div class="ico" style="background: rgba(224, 104, 95, 0.16)">⬇️</div>
|
||||
<div class="nm">Deluge</div>
|
||||
<div class="ct">Downloads</div>
|
||||
<div class="foot">
|
||||
<span class="dot bad"><span class="b"></span>Asleep</span><span class="up">—</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="blob" style="background: var(--sky)"></div>
|
||||
<div class="ico" style="background: rgba(108, 169, 214, 0.18)">🔀</div>
|
||||
<div class="nm">Proxy</div>
|
||||
<div class="ct">Networking</div>
|
||||
<div class="foot">
|
||||
<span class="dot ok"><span class="b"></span>Online</span><span class="up">99.9%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="note">
|
||||
Cozy Home — Fraunces + Figtree · warm cream · soft pastel rooms · gentle motion
|
||||
</p>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,639 @@
|
||||
<!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>
|
||||
@@ -0,0 +1,160 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Launcher — Redesign Mockups</title>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600&family=Figtree:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body {
|
||||
font-family: 'Figtree', system-ui, sans-serif;
|
||||
background: #0e0e12;
|
||||
color: #eee;
|
||||
min-height: 100vh;
|
||||
padding: 48px 24px;
|
||||
}
|
||||
.wrap {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
h1 {
|
||||
font-family: 'Fraunces';
|
||||
font-weight: 600;
|
||||
font-size: 34px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
p.sub {
|
||||
color: #9a99a6;
|
||||
margin-top: 8px;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
max-width: 62ch;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 18px;
|
||||
margin-top: 34px;
|
||||
}
|
||||
a.card {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
border: 1px solid #24242e;
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
background: #15151c;
|
||||
transition: 0.2s;
|
||||
}
|
||||
a.card:hover {
|
||||
transform: translateY(-4px);
|
||||
border-color: #3a3a4a;
|
||||
background: #191920;
|
||||
}
|
||||
.badge {
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
padding: 4px 10px;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
.nm {
|
||||
font-family: 'Fraunces';
|
||||
font-weight: 600;
|
||||
font-size: 21px;
|
||||
margin: 14px 0 6px;
|
||||
}
|
||||
.ds {
|
||||
color: #9a99a6;
|
||||
font-size: 13.5px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.swatch {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
.swatch span {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<h1>Web App Launcher — Redesign Directions</h1>
|
||||
<p class="sub">
|
||||
Four aesthetic directions for the same launcher, built as theme presets of one modernized
|
||||
design system. Open each, resize, hover the cards, and try the live accent swatches in
|
||||
Aurora Glass. Pick the one that fits — or mix and match.
|
||||
</p>
|
||||
<div class="grid">
|
||||
<a class="card" href="01-command-deck.html">
|
||||
<span class="badge" style="background: #0d1f18; color: #36e0a4"
|
||||
>01 · Dark · Power-user</span
|
||||
>
|
||||
<div class="nm">Command Deck</div>
|
||||
<div class="ds">
|
||||
Mission-control / terminal. Dense, glanceable telemetry, LED status, monospace data.
|
||||
Saira + JetBrains Mono.
|
||||
</div>
|
||||
<div class="swatch">
|
||||
<span style="background: #070a0d"></span><span style="background: #36e0a4"></span
|
||||
><span style="background: #ffb020"></span><span style="background: #ff4d5e"></span>
|
||||
</div>
|
||||
</a>
|
||||
<a class="card" href="02-aurora-glass.html">
|
||||
<span class="badge" style="background: #1d1340; color: #b69cff">02 · Dark · Premium</span>
|
||||
<div class="nm">Aurora Glass</div>
|
||||
<div class="ds">
|
||||
Frosted glass over a living gradient mesh. Soft glows, generous space, fully retintable
|
||||
accent. Outfit + Manrope.
|
||||
</div>
|
||||
<div class="swatch">
|
||||
<span style="background: #0a0a14"></span
|
||||
><span style="background: hsl(265 90% 66%)"></span
|
||||
><span style="background: hsl(325 85% 65%)"></span
|
||||
><span style="background: #34e0a1"></span>
|
||||
</div>
|
||||
</a>
|
||||
<a class="card" href="03-editorial.html">
|
||||
<span class="badge" style="background: #2a1109; color: #ff5436">03 · Light · Bold</span>
|
||||
<div class="nm">Editorial</div>
|
||||
<div class="ds">
|
||||
Magazine masthead, big display type, ink rules, hard shadows, asymmetric grid. Bricolage
|
||||
Grotesque + Instrument Serif.
|
||||
</div>
|
||||
<div class="swatch">
|
||||
<span style="background: #f4f1ea"></span><span style="background: #191712"></span
|
||||
><span style="background: #ff5436"></span><span style="background: #1f4ae0"></span>
|
||||
</div>
|
||||
</a>
|
||||
<a class="card" href="04-cozy-home.html">
|
||||
<span class="badge" style="background: #2a1c12; color: #ff9a76"
|
||||
>04 · Light · Friendly</span
|
||||
>
|
||||
<div class="nm">Cozy Home</div>
|
||||
<div class="ds">
|
||||
Warm cream, soft rounded cards, pastel “rooms”, gentle motion. Friendly for the whole
|
||||
household. Fraunces + Figtree.
|
||||
</div>
|
||||
<div class="swatch">
|
||||
<span style="background: #fdf8f2"></span><span style="background: #e8754f"></span
|
||||
><span style="background: #7fb069"></span><span style="background: #6ca9d6"></span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user