UI: Refactor design system to align with dense mockup

This commit is contained in:
Hitonabi
2026-06-20 22:53:48 +02:00
parent a51f6ee88a
commit c76bcc7293
9 changed files with 940 additions and 68 deletions
+27 -23
View File
@@ -20,15 +20,19 @@
/* Schrift */
--mono:ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
--sans:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
/* Typografie Skala */
--text-xs: 11.5px; --text-sm: 13px; --text-base: 14px; --text-lg: 16px; --text-xl: 20px; --text-2xl: 24px;
/* Spacing Skala */
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 20px; --sp-6: 24px; --sp-8: 32px; --sp-10: 40px;
/* Maße */
--side:62px; --radius:14px;
--side:62px; --radius:6px;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;background:var(--bg);color:var(--tx);
font-family:var(--sans);font-size:14.5px;line-height:1.5;
font-family:var(--sans);font-size:var(--text-base);line-height:1.5;
-webkit-font-smoothing:antialiased;
}
a{color:var(--act);text-decoration:none}
@@ -41,7 +45,7 @@ a{color:var(--act);text-decoration:none}
width:var(--side);flex:0 0 var(--side);
background:var(--bg2);border-right:1px solid var(--line);
display:flex;flex-direction:column;align-items:center;
padding:14px 0;gap:6px;position:sticky;top:0;height:100vh;
padding:var(--sp-4) 0;gap:var(--sp-2);position:sticky;top:0;height:100vh;
}
.side-logo{
width:34px;height:34px;border-radius:9px;margin-bottom:10px;
@@ -51,7 +55,7 @@ a{color:var(--act);text-decoration:none}
.side-nav{display:flex;flex-direction:column;gap:4px;flex:1}
.side-foot{margin-top:auto}
.nav-item{
width:40px;height:40px;border-radius:10px;display:grid;place-items:center;
width:40px;height:40px;border-radius:var(--radius);display:grid;place-items:center;
color:var(--mut);cursor:pointer;border:1px solid transparent;transition:.15s;
}
.nav-item:hover{color:var(--tx);background:var(--panel)}
@@ -67,50 +71,50 @@ a{color:var(--act);text-decoration:none}
.topbar{
position:sticky;top:0;z-index:20;
display:flex;align-items:center;gap:14px;flex-wrap:wrap;
padding:12px 26px;background:rgba(10,13,18,.86);backdrop-filter:blur(8px);
display:flex;align-items:center;gap:var(--sp-4);flex-wrap:wrap;
padding:var(--sp-3) var(--sp-6);background:rgba(10,13,18,.86);backdrop-filter:blur(8px);
border-bottom:1px solid var(--line);
}
.spacer{flex:1}
.status-pill{
display:inline-flex;align-items:center;gap:8px;font-family:var(--mono);font-size:12.5px;
padding:6px 12px;border:1px solid var(--line);border-radius:999px;color:var(--mut);background:var(--panel);
display:inline-flex;align-items:center;gap:var(--sp-2);font-family:var(--mono);font-size:var(--text-xs);
padding:var(--sp-1) var(--sp-3);border:1px solid var(--line);border-radius:999px;color:var(--mut);background:var(--panel);
}
.dot{width:8px;height:8px;border-radius:50%;background:var(--mut);flex:0 0 auto}
.dot.on{background:var(--on);box-shadow:0 0 0 0 rgba(63,185,80,.5);animation:pulse 2.2s infinite}
.dot.off{background:var(--err)}
@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(63,185,80,.45)}70%{box-shadow:0 0 0 7px rgba(63,185,80,0)}100%{box-shadow:0 0 0 0 rgba(63,185,80,0)}}
.top-stat{font-size:12.5px;color:var(--mut)}
.top-stat{font-size:var(--text-sm);color:var(--mut)}
.top-stat b{color:var(--tx);font-family:var(--mono);font-weight:600;margin-left:4px}
.top-clock{font-family:var(--mono);font-size:12.5px;color:var(--act)}
.top-clock{font-family:var(--mono);font-size:var(--text-sm);color:var(--act)}
.tokin{
font-family:var(--mono);font-size:12.5px;background:var(--panel);border:1px solid var(--line);
color:var(--tx);border-radius:8px;padding:7px 10px;width:128px;
font-family:var(--mono);font-size:var(--text-sm);background:var(--panel);border:1px solid var(--line);
color:var(--tx);border-radius:var(--radius);padding:var(--sp-2) var(--sp-3);width:128px;
}
.tokin:focus{outline:none;border-color:var(--act)}
/* ---- Content-Bereich ---- */
.content{padding:22px 26px 64px;max-width:1300px;margin:0 auto;width:100%}
.content{padding:var(--sp-5) var(--sp-6) var(--sp-10);max-width:1300px;margin:0 auto;width:100%}
.view[hidden]{display:none}
.view{display:flex;flex-direction:column;gap:18px}
.view{display:flex;flex-direction:column;gap:var(--sp-4)}
/* Raster-Helfer */
.grid{display:grid;gap:18px}
.grid-3{grid-template-columns:1fr 1fr 1fr}
.grid-2{grid-template-columns:1fr 1fr}
.grid{display:grid;gap:var(--sp-4)}
.grid-3{grid-template-columns:repeat(auto-fit, minmax(300px, 1fr))}
.grid-2{grid-template-columns:repeat(auto-fit, minmax(400px, 1fr))}
.kpis{grid-template-columns:repeat(5,1fr)}
@media(max-width:1180px){.grid-3{grid-template-columns:1fr 1fr}.kpis{grid-template-columns:repeat(2,1fr)}}
@media(max-width:760px){.grid-3,.grid-2,.kpis{grid-template-columns:1fr}}
/* Alert-Banner */
.alert{
margin:14px 26px 0;padding:14px 18px;border-radius:12px;
background:linear-gradient(90deg,rgba(229,83,75,.16),rgba(229,83,75,.04));
border:1px solid rgba(229,83,75,.32);color:#ffcdc8;
display:flex;align-items:center;gap:12px;font-size:13.5px;
margin:var(--sp-4) var(--sp-6) 0;padding:var(--sp-3) var(--sp-4);border-radius:var(--radius);
background:rgba(229,83,75,.08);
border:1px solid rgba(229,83,75,.2);color:#ffcdc8;
display:flex;align-items:center;gap:var(--sp-3);font-size:var(--text-sm);
}
.alert .a-dot{width:8px;height:8px;border-radius:50%;background:var(--err);flex:0 0 auto}
.alert b{color:#ffe2de}
.alert.warn{background:linear-gradient(90deg,rgba(224,163,46,.15),rgba(224,163,46,.03));
border-color:rgba(224,163,46,.32);color:#f3dca6}
.alert.warn{background:rgba(224,163,46,.08);
border-color:rgba(224,163,46,.2);color:#f3dca6}
.alert.warn .a-dot{background:var(--warn)}
+48 -17
View File
@@ -5,7 +5,7 @@
/* ---- Karte (Grundbaustein) ---- */
.card{
background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);
padding:18px 20px;
padding:var(--sp-3) var(--sp-4);
}
.card-h{display:flex;align-items:center;gap:10px;margin:0 0 14px}
.card-h h3{font-size:15px;font-weight:600;margin:0;flex:1;color:var(--tx)}
@@ -14,11 +14,9 @@
/* ---- Hero (Overview-Kopf) ---- */
.hero{
background:
radial-gradient(120% 140% at 100% 0%,rgba(68,147,224,.10),transparent 60%),
var(--panel);
background:var(--panel);
border:1px solid var(--line);border-radius:var(--radius);
padding:26px 28px;display:flex;justify-content:space-between;gap:24px;flex-wrap:wrap;
padding:var(--sp-4) var(--sp-5);display:flex;justify-content:space-between;gap:var(--sp-6);flex-wrap:wrap;
}
.hero .eyebrow{font-size:11.5px;letter-spacing:.22em;text-transform:uppercase;color:var(--mut)}
.hero h1{font-size:26px;font-weight:650;margin:8px 0 6px}
@@ -34,24 +32,24 @@
/* ---- KPI-Kacheln ---- */
.kpi{
position:relative;border-radius:var(--radius);padding:18px 20px;
position:relative;border-radius:var(--radius);padding:var(--sp-3) var(--sp-4);
border:1px solid var(--line);background:var(--panel);overflow:hidden;
}
.kpi .k-h{display:flex;align-items:flex-start;justify-content:space-between;gap:8px}
.kpi .k-t{font-size:13.5px;color:var(--mut)}
.kpi .k-h{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--sp-2)}
.kpi .k-t{font-size:var(--text-sm);color:var(--mut)}
.kpi .k-ic{color:var(--mut);opacity:.85}
.kpi .k-ic svg{width:20px;height:20px}
.kpi .k-v{font-family:var(--mono);font-size:32px;font-weight:600;line-height:1.1;margin:14px 0 4px;color:var(--tx)}
.kpi .k-v small{font-size:15px;color:var(--mut);font-weight:400}
.kpi .k-s{font-size:12px;color:var(--mut)}
/* Farb-Varianten */
.kpi.green {background:linear-gradient(160deg,var(--t-green),transparent 70%);border-color:var(--b-green)}
.kpi .k-ic svg{width:18px;height:18px}
.kpi .k-v{font-family:var(--mono);font-size:var(--text-2xl);font-weight:600;line-height:1.1;margin:var(--sp-3) 0 var(--sp-1);color:var(--tx)}
.kpi .k-v small{font-size:var(--text-base);color:var(--mut);font-weight:400}
.kpi .k-s{font-size:var(--text-xs);color:var(--mut)}
/* Farb-Varianten (flach) */
.kpi.green {border-top:2px solid var(--on)}
.kpi.green .k-v,.kpi.green .k-ic{color:var(--on)}
.kpi.blue {background:linear-gradient(160deg,var(--t-blue),transparent 70%);border-color:var(--b-blue)}
.kpi.blue {border-top:2px solid var(--act)}
.kpi.blue .k-v,.kpi.blue .k-ic{color:var(--act)}
.kpi.purple{background:linear-gradient(160deg,var(--t-purple),transparent 70%);border-color:var(--b-purple)}
.kpi.purple{border-top:2px solid var(--purple)}
.kpi.purple .k-v,.kpi.purple .k-ic{color:var(--purple)}
.kpi.red {background:linear-gradient(160deg,var(--t-red),transparent 70%);border-color:var(--b-red)}
.kpi.red {border-top:2px solid var(--err)}
.kpi.red .k-v,.kpi.red .k-ic{color:var(--err)}
.kpi.muted .k-v{color:var(--dim)}
@@ -157,3 +155,36 @@ button:disabled{opacity:.5;cursor:not-allowed}
.guide-acc summary::after { content: "▼"; font-size: 10px; color: var(--mut); transition: transform 0.2s; }
.guide-acc[open] summary::after { transform: rotate(180deg); }
.guide-acc .acc-body { padding: 0 20px 20px 20px; }
/* ---- Utilities & Layout Helpers ---- */
.flex { display: flex; }
.flex-col { display: flex; flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-2 { gap: var(--sp-2); }
.gap-3 { gap: var(--sp-3); }
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.mt-6 { margin-top: var(--sp-6); }
.mb-2 { margin-bottom: var(--sp-2); }
.mb-4 { margin-bottom: var(--sp-4); }
.text-sm { font-size: var(--text-sm); }
.text-xs { font-size: var(--text-xs); }
.text-mut { color: var(--mut); }
.text-act { color: var(--act); }
/* ---- Interaktive Karten (A11y) ---- */
.card-btn {
display: block; width: 100%; text-align: left;
background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
padding: var(--sp-3) var(--sp-4); cursor: pointer; transition: border-color 0.15s, background 0.15s;
color: var(--tx); font-family: var(--sans);
}
.card-btn:hover, .card-btn:focus {
border-color: var(--act);
background: var(--bg2);
outline: none;
}
.card-btn h3 { margin: 0; font-size: var(--text-lg); font-weight: 600; }
.card-btn p { margin: var(--sp-2) 0 0; font-size: var(--text-sm); color: var(--mut); }
+4 -4
View File
@@ -43,9 +43,9 @@
<section class="view" data-view="overview">
<div id="hero"></div>
<div class="grid grid-3" id="ov-quick" style="margin-top: 24px"></div>
<div class="grid grid-3 mt-6" id="ov-quick"></div>
<div class="grid grid-2" style="margin-top: 24px; align-items: start;">
<div class="grid grid-2 mt-6" style="align-items: start;">
<div class="card" id="ov-models"></div>
<div class="card" id="ov-recent-jobs"></div>
</div>
@@ -86,10 +86,10 @@
<button id="sm-close" class="ghost" style="position:absolute; top:12px; right:12px;">Schließen</button>
<h2 style="margin-top:0">Einstellungen</h2>
<div style="margin-top:24px">
<div class="mt-6">
<label>API Token (Authentifizierung)</label>
<input id="token" class="tokin" placeholder="Optionales API Token..." autocomplete="off">
<div class="meta" style="font-size:12px; margin-top:4px;">Wird für die WebSockets und API Calls genutzt, falls der Server geschützt ist.</div>
<div class="meta text-xs mt-2">Wird für die WebSockets und API Calls genutzt, falls der Server geschützt ist.</div>
</div>
</div>
</div>
+4 -4
View File
@@ -20,7 +20,7 @@ function mount() {
$("#m-table").innerHTML = `
<div class="card-h"><h3>Modelle &amp; Ports</h3><span class="meta" id="m-count"></span></div>
<div class="hint" style="margin-bottom: 16px;">
<div class="hint mb-4">
💡 <b>Modelle werden automatisch geladen</b>, sobald eine Chat-Anfrage an sie gestellt wird. Du musst sie nicht manuell starten.
</div>
<table>
@@ -36,13 +36,13 @@ function mount() {
<h2 style="margin-top:0">Modell konfigurieren</h2>
<p class="meta" id="cfg-model-name"></p>
<div style="margin-top:24px">
<div class="mt-6">
<label>Context-Size (Tokens)</label>
<input id="cfg-ctx" type="number" class="tokin" value="8192">
<div class="meta" style="font-size:12px; margin-top:4px;">Höhere Werte erlauben längere Dokumente, brauchen aber mehr VRAM.</div>
<div class="meta text-xs mt-2">Höhere Werte erlauben längere Dokumente, brauchen aber mehr VRAM.</div>
</div>
<button class="primary" id="cfg-save" style="width:100%; margin-top:24px; padding:12px">Speichern</button>
<button class="primary mt-6" id="cfg-save" style="width:100%; padding:var(--sp-3)">Speichern</button>
</div>
</div>
`;
+20 -20
View File
@@ -29,29 +29,29 @@ function renderHero() {
function renderQuickActions() {
// 3 Kacheln (Cookbook, Server-Status, Aktivität/Guides)
$("#ov-quick").innerHTML = `
<div class="card" style="cursor:pointer; display:flex; flex-direction:column; gap:12px; transition:border-color 0.2s" onclick="document.querySelector('.nav-item[data-view=\\'cookbook\\']').click()" onmouseover="this.style.borderColor='var(--act)'" onmouseout="this.style.borderColor='var(--line)'">
<div style="display:flex; justify-content:space-between; align-items:center;">
<h3 style="margin:0; font-size:16px;">Modell finden</h3>
<span style="color:var(--act)">${icon("book")}</span>
<button class="card-btn" onclick="document.querySelector('.nav-item[data-view=\\'cookbook\\']').click()">
<div class="flex justify-between items-center">
<h3>Modell finden</h3>
<span class="text-act">${icon("book")}</span>
</div>
<p style="margin:0; font-size:13px; color:var(--mut);">Durchsuche HuggingFace nach neuen Modellen im Cookbook.</p>
</div>
<p>Durchsuche HuggingFace nach neuen Modellen im Cookbook.</p>
</button>
<div class="card" style="cursor:pointer; display:flex; flex-direction:column; gap:12px; transition:border-color 0.2s" onclick="document.querySelector('.nav-item[data-view=\\'activity\\']').click()" onmouseover="this.style.borderColor='var(--act)'" onmouseout="this.style.borderColor='var(--line)'">
<div style="display:flex; justify-content:space-between; align-items:center;">
<h3 style="margin:0; font-size:16px;">Live Metriken</h3>
<span style="color:var(--act)">${icon("pulse")}</span>
<button class="card-btn" onclick="document.querySelector('.nav-item[data-view=\\'activity\\']').click()">
<div class="flex justify-between items-center">
<h3>Live Metriken</h3>
<span class="text-act">${icon("pulse")}</span>
</div>
<p style="margin:0; font-size:13px; color:var(--mut);">${SYS ? `System läuft (RAM: ${SYS.ram.percent.toFixed(0)}%, CPU: ${SYS.cpu.percent.toFixed(0)}%)` : 'Lade Metriken...'}</p>
</div>
<p>${SYS ? `System läuft (RAM: ${SYS.ram.percent.toFixed(0)}%, CPU: ${SYS.cpu.percent.toFixed(0)}%)` : 'Lade Metriken...'}</p>
</button>
<div class="card" style="cursor:pointer; display:flex; flex-direction:column; gap:12px; transition:border-color 0.2s" onclick="document.querySelector('.nav-item[data-view=\\'server\\']').click()" onmouseover="this.style.borderColor='var(--act)'" onmouseout="this.style.borderColor='var(--line)'">
<div style="display:flex; justify-content:space-between; align-items:center;">
<h3 style="margin:0; font-size:16px;">Wartung</h3>
<span style="color:var(--act)">${icon("server")}</span>
<button class="card-btn" onclick="document.querySelector('.nav-item[data-view=\\'server\\']').click()">
<div class="flex justify-between items-center">
<h3>Wartung</h3>
<span class="text-act">${icon("server")}</span>
</div>
<p style="margin:0; font-size:13px; color:var(--mut);">Server neustarten, VRAM leeren oder Engine aktualisieren.</p>
</div>
<p>Server neustarten, VRAM leeren oder Engine aktualisieren.</p>
</button>
`;
}
@@ -107,9 +107,9 @@ function renderRecentJobs() {
${latest.length
? `<div class="list">
${latest.map(j => `
<div class="li" style="padding:10px 4px">
<div class="li">
<div class="li-main">
<div class="li-id" style="font-size:12.5px">${esc(j.label)}</div>
<div class="li-id text-sm">${esc(j.label)}</div>
</div>
<div class="li-right">${statusBadge(j.state)}</div>
</div>