diff --git a/l4d2web/static/css/components.css b/l4d2web/static/css/components.css
index 79d925a..f001fc9 100644
--- a/l4d2web/static/css/components.css
+++ b/l4d2web/static/css/components.css
@@ -1,10 +1,10 @@
+.panel,
.card {
- background: var(--color-card);
- border: 1px solid var(--color-border);
- border-radius: var(--radius);
- padding: var(--space-4);
- margin-bottom: var(--space-4);
- box-shadow: 0 8px 20px #0F766E12;
+ background: var(--color-surface);
+ border: var(--line);
+ border-radius: var(--radius-m);
+ padding: var(--space-l);
+ margin-bottom: var(--space-l);
}
.table {
@@ -15,8 +15,8 @@
.table th,
.table td {
text-align: left;
- padding: var(--space-2) var(--space-3);
- border-bottom: 1px solid var(--color-border);
+ padding: var(--space-s) var(--space-m);
+ border-bottom: var(--line);
}
.muted {
@@ -25,7 +25,7 @@
.stack {
display: grid;
- gap: var(--space-3);
+ gap: var(--space-m);
}
input,
@@ -36,10 +36,27 @@ textarea {
}
button {
- background: var(--color-link);
+ background: var(--color-primary);
border: none;
- border-radius: 8px;
+ border-radius: var(--radius-s);
color: #fff;
- padding: var(--space-2) var(--space-4);
+ padding: var(--space-s) var(--space-l);
cursor: pointer;
}
+
+button.danger {
+ background: var(--color-danger);
+}
+
+.link-button {
+ background: none;
+ border: none;
+ color: var(--color-link);
+ padding: 0;
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+.inline-form {
+ display: inline;
+}
diff --git a/l4d2web/static/css/layout.css b/l4d2web/static/css/layout.css
index fc7b343..db176c2 100644
--- a/l4d2web/static/css/layout.css
+++ b/l4d2web/static/css/layout.css
@@ -4,35 +4,42 @@
body {
margin: 0;
- font-family: "Segoe UI", "Helvetica Neue", sans-serif;
- background: radial-gradient(circle at top right, #DDEFEA, #F3F7F6 45%);
+ font-family: system-ui, -apple-system, sans-serif;
+ background: var(--color-bg);
color: var(--color-text);
}
.site-header {
- background: #FFFFFFD9;
- border-bottom: 1px solid var(--color-border);
+ background: var(--color-surface);
+ border-bottom: var(--line);
position: sticky;
top: 0;
- backdrop-filter: blur(6px);
}
.site-header-inner {
max-width: 960px;
margin: 0 auto;
- padding: var(--space-4);
+ padding: var(--space-l);
display: flex;
justify-content: space-between;
align-items: center;
}
+.primary-nav,
+.account-nav {
+ display: flex;
+ gap: var(--space-l);
+ align-items: center;
+}
+
.brand {
font-weight: 700;
text-decoration: none;
+ margin-right: var(--space-l);
}
.container {
max-width: 960px;
margin: 0 auto;
- padding: var(--space-6) var(--space-4) var(--space-6);
+ padding: var(--space-2xl) var(--space-l);
}
diff --git a/l4d2web/static/css/logs.css b/l4d2web/static/css/logs.css
index 9fcfc59..c28274e 100644
--- a/l4d2web/static/css/logs.css
+++ b/l4d2web/static/css/logs.css
@@ -1,10 +1,13 @@
.log-stream {
min-height: 180px;
- max-height: 360px;
+ max-height: 480px;
overflow: auto;
- background: #0A1412;
- color: #CCE9E1;
- border-radius: 8px;
- padding: var(--space-3);
- font-family: "SFMono-Regular", Menlo, Consolas, monospace;
+ background: var(--color-log-bg);
+ color: var(--color-log-text);
+ border-radius: var(--radius-s);
+ padding: var(--space-m);
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+ font-size: 0.875rem;
+ line-height: 1.4;
+ white-space: pre-wrap;
}
diff --git a/l4d2web/static/css/tokens.css b/l4d2web/static/css/tokens.css
index f07d7d7..1d14232 100644
--- a/l4d2web/static/css/tokens.css
+++ b/l4d2web/static/css/tokens.css
@@ -1,15 +1,49 @@
:root {
- --color-link: #0F766E;
- --color-bg: #F3F7F6;
- --color-text: #11201D;
- --color-card: #FFFFFF;
- --color-border: #D4E4DF;
- --color-muted: #4A6A63;
- --radius: 10px;
- --space-2: 0.5rem;
- --space-3: 0.75rem;
- --space-4: 1rem;
- --space-6: 1.5rem;
+ --color-bg: #f4f4f5;
+ --color-surface: #ffffff;
+ --color-surface-muted: #f8fafc;
+ --color-text: #18181b;
+ --color-muted: #60646c;
+ --color-border: #d4d4d8;
+ --color-link: #1d4ed8;
+ --color-primary: #1d4ed8;
+ --color-danger: #b42318;
+ --color-warning: #a15c07;
+ --color-success: #067647;
+ --color-focus: #2563eb;
+ --color-log-bg: #111827;
+ --color-log-text: #e5e7eb;
+
+ --space-base: 0.25rem;
+ --space-xs: var(--space-base);
+ --space-s: calc(var(--space-base) * 2);
+ --space-m: calc(var(--space-base) * 3);
+ --space-l: calc(var(--space-base) * 4);
+ --space-xl: calc(var(--space-base) * 6);
+ --space-2xl: calc(var(--space-base) * 8);
+
+ --radius-base: 0.25rem;
+ --radius-s: var(--radius-base);
+ --radius-m: calc(var(--radius-base) * 2);
+
+ --line: 1px solid var(--color-border);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --color-bg: #18181b;
+ --color-surface: #27272a;
+ --color-surface-muted: #1f1f23;
+ --color-text: #f4f4f5;
+ --color-muted: #a1a1aa;
+ --color-border: #3f3f46;
+ --color-link: #93c5fd;
+ --color-primary: #93c5fd;
+ --color-danger: #fca5a5;
+ --color-warning: #fcd34d;
+ --color-success: #86efac;
+ --color-focus: #bfdbfe;
+ }
}
a {
diff --git a/l4d2web/templates/base.html b/l4d2web/templates/base.html
index 8ea892e..dd1a278 100644
--- a/l4d2web/templates/base.html
+++ b/l4d2web/templates/base.html
@@ -13,11 +13,22 @@
diff --git a/l4d2web/templates/dashboard.html b/l4d2web/templates/dashboard.html
index 9d857e9..f43ac03 100644
--- a/l4d2web/templates/dashboard.html
+++ b/l4d2web/templates/dashboard.html
@@ -3,26 +3,8 @@
{% block title %}Dashboard | left4me{% endblock %}
{% block content %}
-
+
Dashboard
- Status refresh every {{ refresh_seconds }}s.
-
-
- | Name | Port | Desired | Actual | |
-
-
- {% for server in servers %}
-
- | {{ server.name }} |
- {{ server.port }} |
- {{ server.desired_state }} |
- {{ server.actual_state }} |
- View |
-
- {% else %}
- | No servers yet. |
- {% endfor %}
-
-
+ Use the navigation to manage servers, blueprints, and overlays.
{% endblock %}
diff --git a/l4d2web/tests/test_pages.py b/l4d2web/tests/test_pages.py
index 006a060..b75a585 100644
--- a/l4d2web/tests/test_pages.py
+++ b/l4d2web/tests/test_pages.py
@@ -1,4 +1,5 @@
import pytest
+from pathlib import Path
from l4d2web.app import create_app
from l4d2web.auth import hash_password
@@ -59,11 +60,52 @@ def user_client_and_other_blueprint(tmp_path, monkeypatch):
return client, blueprint_id
-def test_dashboard_renders_server_and_status(auth_client_with_server) -> None:
+def test_dashboard_is_simple_landing_page(auth_client_with_server) -> None:
response = auth_client_with_server.get("/dashboard")
text = response.get_data(as_text=True)
+
assert response.status_code == 200
- assert "alpha" in text
+ assert "Dashboard" in text
+ assert "Use the navigation to manage servers, blueprints, and overlays." in text
+ assert "alpha" not in text
+
+
+def test_shell_nav_uses_main_sections(auth_client_with_server) -> None:
+ response = auth_client_with_server.get("/dashboard")
+ text = response.get_data(as_text=True)
+
+ assert 'href="/dashboard"' in text
+ assert 'href="/servers"' in text
+ assert 'href="/blueprints"' in text
+ assert 'href="/overlays"' in text
+ assert 'action="/logout"' in text
+ assert "csrf_token" in text
+
+
+def test_css_tokens_define_neutral_light_and_dark_theme() -> None:
+ css = Path("l4d2web/static/css/tokens.css").read_text()
+
+ for token in [
+ "--color-bg",
+ "--color-surface",
+ "--color-text",
+ "--color-muted",
+ "--color-border",
+ "--color-link",
+ "--space-base",
+ "--space-xs",
+ "--space-s",
+ "--space-m",
+ "--space-l",
+ "--space-xl",
+ "--space-2xl",
+ "--radius-s",
+ "--radius-m",
+ "--line",
+ ]:
+ assert token in css
+ assert "prefers-color-scheme: dark" in css
+ assert "radial-gradient" not in Path("l4d2web/static/css/layout.css").read_text()
def test_blueprint_page_private_visibility(user_client_and_other_blueprint) -> None: