From 881b6635f936eb855104ce5d405811fc7eb4e533 Mon Sep 17 00:00:00 2001 From: mwiegand Date: Wed, 6 May 2026 12:06:23 +0200 Subject: [PATCH] feat(l4d2-web): add neutral shell and theme tokens --- l4d2web/static/css/components.css | 41 +++++++++++++++------- l4d2web/static/css/layout.css | 21 ++++++++---- l4d2web/static/css/logs.css | 15 +++++---- l4d2web/static/css/tokens.css | 56 +++++++++++++++++++++++++------ l4d2web/templates/base.html | 19 ++++++++--- l4d2web/templates/dashboard.html | 22 ++---------- l4d2web/tests/test_pages.py | 46 +++++++++++++++++++++++-- 7 files changed, 158 insertions(+), 62 deletions(-) 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.

- - - - - - {% for server in servers %} - - - - - - - - {% else %} - - {% endfor %} - -
NamePortDesiredActual
{{ server.name }}{{ server.port }}{{ server.desired_state }}{{ server.actual_state }}View
No servers yet.
+

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: