style(dashboard): redesign moderno con palette Modern Fintech Dark
Sostituita palette Inter FC con design language fintech moderno tipo Linear/Tremor/Plane: Palette: - BG #0A0E1A deep blue-black con radial gradient sottile - Surface #131829 / surface-2 #1A2138 per hover/active - Primary cyan #06B6D4 (highlight performance) - Secondary purple #A78BFA (median curves) - Accent amber #F59E0B (warnings, p90) - Success #10B981, danger #EF4444 Typography: - Inter da Google Fonts (peso 400-700) - JetBrains Mono per code/numeri tabulari - letter-spacing negativo + tnum font-feature Component refinement: - Card: border sottile rgba + hover cyan glow, no gradient pesante - Header: backdrop-blur 12px + glass effect - Nav link: underline animato bottom su active - Brand: dot pulsante cyan con glow - Progress bar: 8px rounded, track surface-2 - Metric card: layout left-aligned, label uppercase tracked, value 26px con accent class (cyan/purple/amber/green) per coding semantico - Tabella: header uppercase tracked, row hover cyan-tinted - Code block: surface scuro + border + JetBrains Mono Plot Plotly: - paper_bgcolor → surface (no più sfondo full-bleed) - gridcolor neutral rgba(slate, 0.08) - Legend bg surface 0.95 opacity Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,11 +5,15 @@ Default port 8080. Streamlit resta su 8501 durante la migrazione.
|
||||
|
||||
Riusa ``dashboard.data`` (Repository helpers) senza modifiche al backend.
|
||||
|
||||
Palette Inter FC:
|
||||
- Nero: #000000 (sfondo)
|
||||
- Dark navy: #010E80 (heritage Inter, surface)
|
||||
- Blu primary: #1E5BC6
|
||||
- Blu secondary: #0068A8
|
||||
Palette "Modern Fintech Dark":
|
||||
- BG: #0A0E1A (deep blue-black)
|
||||
- Surface: #131829 (card base)
|
||||
- Surface elevata: #1A2138 (hover/active)
|
||||
- Primary cyan: #06B6D4 (performance highlights)
|
||||
- Secondary purple: #A78BFA (median, secondary curves)
|
||||
- Accent amber: #F59E0B (warnings, p90)
|
||||
- Success green: #10B981, Danger red: #EF4444
|
||||
- Text: #F1F5F9 (primary), #94A3B8 (muted)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -34,57 +38,178 @@ from multi_swarm.dashboard.data import (
|
||||
DB_PATH = os.environ.get("DB_PATH", "./runs.db")
|
||||
REFRESH_INTERVAL_S = 3.0
|
||||
|
||||
# --- Inter FC palette ---
|
||||
COLOR_BG = "#000000"
|
||||
COLOR_SURFACE = "#010E80"
|
||||
COLOR_PRIMARY = "#1E5BC6"
|
||||
COLOR_SECONDARY = "#0068A8"
|
||||
COLOR_ACCENT = "#FFD700"
|
||||
COLOR_TEXT = "#E8EEFC"
|
||||
COLOR_TEXT_MUTED = "#8FA3D4"
|
||||
# --- Modern Fintech Dark palette ---
|
||||
COLOR_BG = "#0A0E1A"
|
||||
COLOR_SURFACE = "#131829"
|
||||
COLOR_SURFACE_2 = "#1A2138"
|
||||
COLOR_BORDER = "rgba(148, 163, 184, 0.12)"
|
||||
COLOR_PRIMARY = "#06B6D4"
|
||||
COLOR_SECONDARY = "#A78BFA"
|
||||
COLOR_ACCENT = "#F59E0B"
|
||||
COLOR_SUCCESS = "#10B981"
|
||||
COLOR_DANGER = "#EF4444"
|
||||
COLOR_TEXT = "#F1F5F9"
|
||||
COLOR_TEXT_MUTED = "#94A3B8"
|
||||
|
||||
_STATUS_BADGE: dict[str, tuple[str, str]] = {
|
||||
"running": ("🟢 running", "positive"),
|
||||
"completed": ("✅ completed", "positive"),
|
||||
"failed": ("❌ failed", "negative"),
|
||||
"running": ("● running", "positive"),
|
||||
"completed": ("✓ completed", "positive"),
|
||||
"failed": ("✕ failed", "negative"),
|
||||
}
|
||||
|
||||
_CUSTOM_CSS = f"""
|
||||
<style>
|
||||
body {{ background: {COLOR_BG} !important; color: {COLOR_TEXT}; }}
|
||||
.q-page {{ background: {COLOR_BG} !important; }}
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
|
||||
|
||||
* {{ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; letter-spacing: -0.01em; }}
|
||||
code, pre, .q-code {{ font-family: 'JetBrains Mono', 'Fira Code', monospace !important; font-size: 12.5px !important; }}
|
||||
|
||||
body, .q-page-container, .q-page {{
|
||||
background: {COLOR_BG} !important;
|
||||
color: {COLOR_TEXT};
|
||||
background-image: radial-gradient(ellipse at top, rgba(6, 182, 212, 0.04) 0%, transparent 50%);
|
||||
}}
|
||||
|
||||
.q-card {{
|
||||
background: linear-gradient(135deg, {COLOR_SURFACE} 0%, #000936 100%) !important;
|
||||
background: {COLOR_SURFACE} !important;
|
||||
color: {COLOR_TEXT} !important;
|
||||
border: 1px solid {COLOR_PRIMARY}33;
|
||||
box-shadow: 0 2px 8px {COLOR_PRIMARY}22;
|
||||
border: 1px solid {COLOR_BORDER};
|
||||
border-radius: 12px !important;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.3), 0 4px 12px rgba(0,0,0,0.15);
|
||||
transition: border-color 0.2s, transform 0.2s;
|
||||
}}
|
||||
.q-card:hover {{ border-color: rgba(6, 182, 212, 0.35); }}
|
||||
|
||||
.metric-card {{
|
||||
padding: 16px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
padding: 20px 16px;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-width: 140px;
|
||||
}}
|
||||
.metric-card .text-h4 {{ color: {COLOR_PRIMARY} !important; font-weight: 600; }}
|
||||
.metric-card .text-caption {{ color: {COLOR_TEXT_MUTED} !important; }}
|
||||
.q-header {{
|
||||
background: linear-gradient(90deg, {COLOR_BG} 0%, {COLOR_SURFACE} 100%) !important;
|
||||
border-bottom: 2px solid {COLOR_PRIMARY};
|
||||
.metric-card .text-caption {{
|
||||
color: {COLOR_TEXT_MUTED} !important;
|
||||
font-size: 11px !important;
|
||||
font-weight: 500 !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}}
|
||||
.q-tab--active {{ color: {COLOR_PRIMARY} !important; }}
|
||||
.q-tab__indicator {{ background: {COLOR_PRIMARY} !important; }}
|
||||
.nav-link {{
|
||||
.metric-card .text-h4 {{
|
||||
color: {COLOR_TEXT} !important;
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
transition: background 0.2s;
|
||||
font-weight: 600 !important;
|
||||
font-size: 26px !important;
|
||||
line-height: 1.2 !important;
|
||||
font-feature-settings: 'tnum';
|
||||
}}
|
||||
.nav-link:hover {{ background: {COLOR_PRIMARY}33; }}
|
||||
.nav-link.active {{ background: {COLOR_PRIMARY}; color: #fff !important; }}
|
||||
.q-linear-progress__track {{ background: {COLOR_BG} !important; }}
|
||||
.q-separator {{ background: {COLOR_PRIMARY}44 !important; }}
|
||||
.q-field--outlined .q-field__control {{ color: {COLOR_TEXT} !important; }}
|
||||
code, pre {{ background: #000936 !important; color: {COLOR_TEXT} !important; }}
|
||||
.metric-card.accent-cyan .text-h4 {{ color: {COLOR_PRIMARY} !important; }}
|
||||
.metric-card.accent-purple .text-h4 {{ color: {COLOR_SECONDARY} !important; }}
|
||||
.metric-card.accent-amber .text-h4 {{ color: {COLOR_ACCENT} !important; }}
|
||||
.metric-card.accent-green .text-h4 {{ color: {COLOR_SUCCESS} !important; }}
|
||||
|
||||
.q-header {{
|
||||
background: rgba(19, 24, 41, 0.85) !important;
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid {COLOR_BORDER} !important;
|
||||
box-shadow: none !important;
|
||||
}}
|
||||
|
||||
.nav-link {{
|
||||
color: {COLOR_TEXT_MUTED} !important;
|
||||
padding: 8px 14px;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-size: 13.5px;
|
||||
font-weight: 500;
|
||||
transition: all 0.15s ease;
|
||||
position: relative;
|
||||
}}
|
||||
.nav-link:hover {{
|
||||
color: {COLOR_TEXT} !important;
|
||||
background: {COLOR_SURFACE_2};
|
||||
}}
|
||||
.nav-link.active {{
|
||||
color: {COLOR_PRIMARY} !important;
|
||||
background: rgba(6, 182, 212, 0.08);
|
||||
}}
|
||||
.nav-link.active::after {{
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -16px;
|
||||
left: 14px;
|
||||
right: 14px;
|
||||
height: 2px;
|
||||
background: {COLOR_PRIMARY};
|
||||
border-radius: 2px 2px 0 0;
|
||||
}}
|
||||
|
||||
.brand {{
|
||||
color: {COLOR_TEXT};
|
||||
font-weight: 700;
|
||||
font-size: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}}
|
||||
.brand-dot {{
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: {COLOR_PRIMARY};
|
||||
box-shadow: 0 0 12px {COLOR_PRIMARY};
|
||||
}}
|
||||
|
||||
.q-linear-progress {{ height: 8px !important; border-radius: 6px !important; }}
|
||||
.q-linear-progress__track {{ background: {COLOR_SURFACE_2} !important; }}
|
||||
.q-linear-progress__model {{ border-radius: 6px !important; }}
|
||||
|
||||
.q-separator {{ background: {COLOR_BORDER} !important; }}
|
||||
|
||||
.q-field--outlined .q-field__control {{
|
||||
background: {COLOR_SURFACE} !important;
|
||||
border-radius: 8px !important;
|
||||
}}
|
||||
.q-field--outlined .q-field__control:before {{ border-color: {COLOR_BORDER} !important; }}
|
||||
.q-field--outlined.q-field--focused .q-field__control:after {{ border-color: {COLOR_PRIMARY} !important; }}
|
||||
.q-field__label {{ color: {COLOR_TEXT_MUTED} !important; }}
|
||||
.q-field__native, .q-field__input {{ color: {COLOR_TEXT} !important; }}
|
||||
|
||||
.q-btn {{ border-radius: 8px !important; font-weight: 500 !important; text-transform: none !important; letter-spacing: 0 !important; }}
|
||||
|
||||
.q-table {{ background: transparent !important; color: {COLOR_TEXT} !important; }}
|
||||
.q-table thead tr {{ background: {COLOR_SURFACE_2} !important; }}
|
||||
.q-table th {{
|
||||
color: {COLOR_TEXT_MUTED} !important;
|
||||
font-size: 11px !important;
|
||||
font-weight: 600 !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}}
|
||||
.q-table tbody tr {{ transition: background 0.15s; }}
|
||||
.q-table tbody tr:hover {{ background: rgba(6, 182, 212, 0.05) !important; }}
|
||||
.q-table tbody tr.selected {{ background: rgba(6, 182, 212, 0.12) !important; }}
|
||||
.q-table td {{ border-bottom: 1px solid {COLOR_BORDER} !important; font-feature-settings: 'tnum'; }}
|
||||
|
||||
.text-h6 {{ font-weight: 600 !important; letter-spacing: -0.015em !important; }}
|
||||
.text-subtitle1 {{
|
||||
color: {COLOR_TEXT_MUTED} !important;
|
||||
font-size: 11px !important;
|
||||
font-weight: 600 !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 0.08em !important;
|
||||
margin-bottom: 8px !important;
|
||||
}}
|
||||
|
||||
code, pre {{
|
||||
background: #0D1220 !important;
|
||||
color: {COLOR_TEXT} !important;
|
||||
border: 1px solid {COLOR_BORDER};
|
||||
border-radius: 8px !important;
|
||||
padding: 12px !important;
|
||||
}}
|
||||
|
||||
.q-badge {{ border-radius: 6px !important; font-weight: 500 !important; padding: 4px 10px !important; font-size: 12px !important; }}
|
||||
</style>
|
||||
"""
|
||||
|
||||
@@ -98,29 +223,32 @@ def _apply_theme() -> None:
|
||||
accent=COLOR_ACCENT,
|
||||
dark=COLOR_BG,
|
||||
dark_page=COLOR_BG,
|
||||
positive="#00C853",
|
||||
negative="#D32F2F",
|
||||
info=COLOR_SECONDARY,
|
||||
warning="#FFB300",
|
||||
positive=COLOR_SUCCESS,
|
||||
negative=COLOR_DANGER,
|
||||
info=COLOR_PRIMARY,
|
||||
warning=COLOR_ACCENT,
|
||||
)
|
||||
|
||||
|
||||
def _build_header(active: str) -> None:
|
||||
with ui.header().classes("items-center justify-between q-px-md"):
|
||||
with ui.row().classes("items-center gap-6"):
|
||||
ui.label("⚫🔵 Multi-Swarm Coevolutivo").classes("text-h6").style(
|
||||
f"color: {COLOR_TEXT}; font-weight: 700;"
|
||||
)
|
||||
with ui.header().classes("items-center justify-between q-px-lg q-py-md"):
|
||||
with ui.row().classes("items-center gap-8"):
|
||||
with ui.row().classes("items-center gap-2").classes("brand"):
|
||||
ui.html('<span class="brand-dot"></span>')
|
||||
ui.html('<span class="brand">Multi-Swarm <span style="color:'
|
||||
+ COLOR_TEXT_MUTED + ';font-weight:400;">/ Coevolutivo</span></span>')
|
||||
with ui.row().classes("items-center gap-1"):
|
||||
for path, label in (
|
||||
("/", "Overview"),
|
||||
("/convergence", "GA Convergence"),
|
||||
("/convergence", "Convergence"),
|
||||
("/genomes", "Genomes"),
|
||||
):
|
||||
cls = "nav-link active" if active == path else "nav-link"
|
||||
ui.link(label, path).classes(cls)
|
||||
ui.label(f"DB: {Path(DB_PATH).resolve().name}").classes("text-caption").style(
|
||||
f"color: {COLOR_TEXT_MUTED};"
|
||||
)
|
||||
with ui.row().classes("items-center gap-3"):
|
||||
ui.html(f'<span style="color:{COLOR_TEXT_MUTED};font-size:12px;'
|
||||
f'font-family:JetBrains Mono,monospace;">'
|
||||
f'⛁ {Path(DB_PATH).resolve().name}</span>')
|
||||
|
||||
|
||||
def _runs_options() -> dict[str, str]:
|
||||
@@ -206,13 +334,13 @@ def _convergence_figure(gens_df: Any) -> go.Figure:
|
||||
)
|
||||
fig.update_layout(
|
||||
template="plotly_dark",
|
||||
paper_bgcolor=COLOR_BG,
|
||||
plot_bgcolor=COLOR_BG,
|
||||
paper_bgcolor=COLOR_SURFACE,
|
||||
plot_bgcolor=COLOR_SURFACE,
|
||||
font={"color": COLOR_TEXT},
|
||||
xaxis={"title": "generation", "gridcolor": "rgba(30, 91, 198, 0.2)", "dtick": 1},
|
||||
yaxis={"title": "fitness", "gridcolor": "rgba(30, 91, 198, 0.2)"},
|
||||
xaxis={"title": "generation", "gridcolor": "rgba(148, 163, 184, 0.08)", "dtick": 1},
|
||||
yaxis={"title": "fitness", "gridcolor": "rgba(148, 163, 184, 0.08)"},
|
||||
title={"text": "Fitness convergence", "font": {"color": COLOR_TEXT, "size": 18}},
|
||||
legend={"bgcolor": "rgba(1, 14, 128, 0.6)", "bordercolor": COLOR_PRIMARY, "borderwidth": 1},
|
||||
legend={"bgcolor": "rgba(19, 24, 41, 0.95)", "bordercolor": COLOR_PRIMARY, "borderwidth": 1},
|
||||
margin={"l": 50, "r": 30, "t": 50, "b": 50},
|
||||
)
|
||||
return fig
|
||||
@@ -237,11 +365,11 @@ def _entropy_figure(gens_df: Any) -> go.Figure:
|
||||
)
|
||||
fig.update_layout(
|
||||
template="plotly_dark",
|
||||
paper_bgcolor=COLOR_BG,
|
||||
plot_bgcolor=COLOR_BG,
|
||||
paper_bgcolor=COLOR_SURFACE,
|
||||
plot_bgcolor=COLOR_SURFACE,
|
||||
font={"color": COLOR_TEXT},
|
||||
xaxis={"title": "generation", "gridcolor": "rgba(30, 91, 198, 0.2)", "dtick": 1},
|
||||
yaxis={"title": "entropy", "gridcolor": "rgba(30, 91, 198, 0.2)"},
|
||||
xaxis={"title": "generation", "gridcolor": "rgba(148, 163, 184, 0.08)", "dtick": 1},
|
||||
yaxis={"title": "entropy", "gridcolor": "rgba(148, 163, 184, 0.08)"},
|
||||
title={"text": "Diversity (fitness entropy)", "font": {"color": COLOR_TEXT, "size": 18}},
|
||||
margin={"l": 50, "r": 30, "t": 50, "b": 50},
|
||||
)
|
||||
@@ -275,16 +403,16 @@ def index() -> None:
|
||||
eval_bar = ui.linear_progress(0.0, show_value=False).props("size=20px color=accent")
|
||||
|
||||
with ui.row().classes("w-full gap-4"):
|
||||
with ui.card().classes("flex-grow metric-card"):
|
||||
with ui.card().classes("flex-grow metric-card accent-cyan"):
|
||||
ui.label("Top fitness").classes("text-caption")
|
||||
top_lbl = ui.label("—").classes("text-h4")
|
||||
with ui.card().classes("flex-grow metric-card"):
|
||||
with ui.card().classes("flex-grow metric-card accent-purple"):
|
||||
ui.label("Median fitness").classes("text-caption")
|
||||
median_lbl = ui.label("—").classes("text-h4")
|
||||
with ui.card().classes("flex-grow metric-card"):
|
||||
with ui.card().classes("flex-grow metric-card accent-amber"):
|
||||
ui.label("Parse success").classes("text-caption")
|
||||
parse_lbl = ui.label("—").classes("text-h4")
|
||||
with ui.card().classes("flex-grow metric-card"):
|
||||
with ui.card().classes("flex-grow metric-card accent-green"):
|
||||
ui.label("Cost (USD)").classes("text-caption")
|
||||
cost_lbl = ui.label("—").classes("text-h4")
|
||||
|
||||
@@ -475,19 +603,19 @@ def genomes() -> None:
|
||||
).style(f"color: {COLOR_TEXT_MUTED};")
|
||||
|
||||
with ui.row().classes("w-full gap-4 q-mt-sm"):
|
||||
with ui.card().classes("flex-grow metric-card"):
|
||||
with ui.card().classes("flex-grow metric-card accent-cyan"):
|
||||
ui.label("fitness").classes("text-caption")
|
||||
fit_lbl = ui.label("—").classes("text-h4")
|
||||
with ui.card().classes("flex-grow metric-card"):
|
||||
with ui.card().classes("flex-grow metric-card accent-purple"):
|
||||
ui.label("DSR").classes("text-caption")
|
||||
dsr_lbl = ui.label("—").classes("text-h4")
|
||||
with ui.card().classes("flex-grow metric-card"):
|
||||
with ui.card().classes("flex-grow metric-card accent-amber"):
|
||||
ui.label("Sharpe").classes("text-caption")
|
||||
sharpe_lbl = ui.label("—").classes("text-h4")
|
||||
with ui.card().classes("flex-grow metric-card"):
|
||||
ui.label("max DD").classes("text-caption")
|
||||
dd_lbl = ui.label("—").classes("text-h4")
|
||||
with ui.card().classes("flex-grow metric-card"):
|
||||
with ui.card().classes("flex-grow metric-card accent-green"):
|
||||
ui.label("trades").classes("text-caption")
|
||||
trades_lbl = ui.label("—").classes("text-h4")
|
||||
with ui.card().classes("flex-grow metric-card"):
|
||||
|
||||
Reference in New Issue
Block a user