From 3a5d0fca86fe2883515826f3c850e3df8ba837ea Mon Sep 17 00:00:00 2001 From: Steffen Skui Date: Tue, 21 Jul 2026 22:17:22 +0200 Subject: [PATCH] dev: Grafana-style dark reskin of the app UI --- static/style.css | 234 ++++++++++++++++++++++++++++++----------------- 1 file changed, 150 insertions(+), 84 deletions(-) diff --git a/static/style.css b/static/style.css index 030082b..a93f955 100644 --- a/static/style.css +++ b/static/style.css @@ -1,159 +1,225 @@ -/* One hand-written stylesheet, no framework. Plain and legible, works the same - in Docker with the machine offline. */ +/* AHDX — Grafana-style skin. Dark by default (that's the Grafana look); the + header toggle flips to a light variant. One hand-written sheet, no framework, + no web fonts, so it still works offline. Class names match the templates. */ -/* Light by default. Dark follows the OS setting, and the toggle in the header - overrides it either way (stored in localStorage as data-theme on ). */ :root { - --bg: #f5f5f4; + /* Grafana-ish dark palette */ + --bg: #111217; /* app canvas */ + --bg-alt: #0b0c0e; /* deeper wells */ + --panel: #181b1f; /* panels / cards */ + --panel-2: #22252b; /* inputs, hover */ + --line: #2c3235; /* borders */ + --line-soft: #202226; + --ink: #d8d9da; /* primary text */ + --muted: #8e8e8e; /* secondary text */ + --accent: #3d71d9; /* Grafana blue (primary) */ + --accent-hover: #5083e6; + --brand: #ff8833; /* Grafana orange (logo) */ + --danger: #d10e5c; + --ok: #3eb15b; + --radius: 3px; +} + +:root[data-theme="light"] { + --bg: #f4f5f5; + --bg-alt: #ffffff; --panel: #ffffff; - --ink: #1c1c1e; - --muted: #6b7280; - --line: #e2e2e0; - --accent: #0a84ff; /* the Apple Health blue, roughly */ - --danger: #c0392b; - --ok: #2e7d32; -} - -@media (prefers-color-scheme: dark) { - :root:not([data-theme="light"]) { - --bg: #16171a; - --panel: #1e2024; - --ink: #e8e8ea; - --muted: #9aa0a8; - --line: #2c2f36; - --accent: #4a9eff; - --danger: #ff6b5a; - --ok: #5cba5c; - } -} - -:root[data-theme="dark"] { - --bg: #16171a; - --panel: #1e2024; - --ink: #e8e8ea; - --muted: #9aa0a8; - --line: #2c2f36; - --accent: #4a9eff; - --danger: #ff6b5a; - --ok: #5cba5c; + --panel-2: #f1f2f3; + --line: #d8dade; + --line-soft: #e4e6e8; + --ink: #24292e; + --muted: #6c7079; + --accent: #3871dc; + --accent-hover: #2f63c4; + --brand: #e8730a; + --danger: #cf0e5b; + --ok: #1a7f4b; } * { box-sizing: border-box; } body { margin: 0; - font: 15px/1.5 -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + font: 14px/1.5 "Inter", -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; color: var(--ink); background: var(--bg); + -webkit-font-smoothing: antialiased; } +/* ---------- top bar ---------- */ header { display: flex; align-items: center; - gap: 24px; - padding: 12px 20px; + gap: 22px; + height: 52px; + padding: 0 18px; background: var(--panel); border-bottom: 1px solid var(--line); flex-wrap: wrap; } -.brand { font-weight: 700; } -.brand span { display: block; font-weight: 400; font-size: 12px; color: var(--muted); } +.brand { font-weight: 600; font-size: 15px; line-height: 1.1; letter-spacing: 0.2px; } +.brand span { + display: block; + font-weight: 400; + font-size: 11px; + color: var(--muted); +} +/* orange accent bar on the logo, Grafana-style */ +.brand::before { + content: ""; + display: inline-block; + width: 4px; height: 20px; + background: var(--brand); + border-radius: 2px; + margin-right: 9px; + vertical-align: -4px; +} -nav { display: flex; gap: 16px; } -nav a { text-decoration: none; color: var(--ink); } -nav a:hover { color: var(--accent); } +nav { display: flex; gap: 4px; } +nav a { + text-decoration: none; + color: var(--muted); + padding: 6px 11px; + border-radius: var(--radius); + font-weight: 500; +} +nav a:hover { color: var(--ink); background: var(--panel-2); } +nav a.active { color: var(--ink); background: var(--panel-2); } .db-picker { margin-left: auto; color: var(--muted); font-size: 13px; } -.db-picker strong { color: var(--ink); } +.db-picker strong { color: var(--ink); font-weight: 600; } -main { max-width: 960px; margin: 24px auto; padding: 0 20px; } +main { max-width: 1080px; margin: 22px auto; padding: 0 18px; } -h1 { font-size: 22px; } -h2 { font-size: 17px; margin-top: 0; } +h1 { font-size: 21px; font-weight: 600; } +h2 { font-size: 15px; font-weight: 600; margin-top: 0; } +h3 { font-size: 14px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.4px; } .muted { color: var(--muted); } .err { color: var(--danger); } -code { background: rgba(128,128,128,0.18); padding: 1px 5px; border-radius: 4px; font-size: 13px; } +a { color: var(--accent); } +a:hover { color: var(--accent-hover); } -.theme-toggle { - background: transparent; +code { + background: var(--panel-2); + border: 1px solid var(--line-soft); + padding: 1px 5px; + border-radius: var(--radius); + font: 12px/1.4 "Roboto Mono", ui-monospace, Menlo, Consolas, monospace; color: var(--ink); - border: 1px solid var(--line); - padding: 4px 9px; - font-size: 15px; } +/* ---------- panels / cards ---------- */ .card { background: var(--panel); border: 1px solid var(--line); - border-radius: 10px; + border-radius: var(--radius); padding: 16px; - margin: 16px 0; + margin: 14px 0; } -.flash { padding: 10px 14px; border-radius: 8px; margin: 10px 0; } -.flash.success { background: #e6f4ea; color: var(--ok); } -.flash.error { background: #fdecea; color: var(--danger); } +.flash { + padding: 10px 14px; + border-radius: var(--radius); + margin: 10px 0; + border-left: 3px solid transparent; +} +.flash.success { background: rgba(62,177,91,0.12); border-color: var(--ok); color: var(--ink); } +.flash.error { background: rgba(209,14,92,0.12); border-color: var(--danger); color: var(--ink); } +/* ---------- buttons ---------- */ button, .btn { font: inherit; + font-weight: 500; padding: 7px 14px; - border: 1px solid var(--line); - border-radius: 8px; + border: 1px solid var(--accent); + border-radius: var(--radius); background: var(--accent); color: #fff; cursor: pointer; text-decoration: none; display: inline-block; + line-height: 1.4; } -button:hover, .btn:hover { opacity: 0.9; } +button:hover, .btn:hover { background: var(--accent-hover); border-color: var(--accent-hover); } button.danger, .btn.danger { background: var(--danger); border-color: var(--danger); } -.btn-sm { padding: 5px 11px; font-size: 13px; border-radius: 7px; } +button.danger:hover { filter: brightness(1.08); } +.btn-sm { padding: 5px 11px; font-size: 13px; } -/* Subtle button for secondary actions (Rename, Make active). */ button.secondary { + background: transparent; + color: var(--ink); + border: 1px solid var(--line); +} +button.secondary:hover { background: var(--panel-2); border-color: var(--muted); color: var(--ink); } + +.theme-toggle { background: transparent; color: var(--muted); border: 1px solid var(--line); + padding: 5px 10px; + font-size: 15px; } -button.secondary:hover { color: var(--accent); border-color: var(--accent); opacity: 1; } +.theme-toggle:hover { background: var(--panel-2); color: var(--ink); } -/* One tidy row of actions per table row. */ -.actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; } -.actions form { display: inline-flex; gap: 6px; align-items: center; margin: 0; } -.actions input[type=text] { width: 116px; margin: 0; padding: 5px 9px; } - -input[type=file], input[type=text], input[type=date], input[type=password], select { +/* ---------- inputs ---------- */ +input[type=file], input[type=text], input[type=date], input[type=password], select, textarea { font: inherit; padding: 7px 10px; border: 1px solid var(--line); - border-radius: 8px; - background: var(--panel); + border-radius: var(--radius); + background: var(--panel-2); color: var(--ink); margin-right: 8px; } +input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); } +textarea { width: 100%; min-height: 90px; resize: vertical; } +label { display: block; margin: 10px 0 4px; color: var(--muted); font-size: 13px; } .filters { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; } -.filters label { color: var(--muted); } +.filters label { display: inline; margin: 0; } -.tiles { display: flex; gap: 12px; flex-wrap: wrap; } +/* ---------- stat tiles ---------- */ +.tiles { display: flex; gap: 10px; flex-wrap: wrap; } .tile { + flex: 1; + min-width: 130px; background: var(--panel); border: 1px solid var(--line); - border-radius: 10px; - padding: 16px 20px; - min-width: 120px; + border-radius: var(--radius); + padding: 14px 16px; } -.tile .n { display: block; font-size: 26px; font-weight: 700; } -.tile .l { color: var(--muted); font-size: 13px; } +.tile .n { display: block; font-size: 26px; font-weight: 600; color: var(--ink); } +.tile .n small { font-size: 14px; color: var(--muted); font-weight: 400; } +.tile .l { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.4px; } -table { width: 100%; border-collapse: collapse; margin: 12px 0; background: var(--panel); } -th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); } -th { font-size: 13px; color: var(--muted); } +/* ---------- tables ---------- */ +table { + width: 100%; + border-collapse: collapse; + margin: 12px 0; + background: var(--panel); + border: 1px solid var(--line); + border-radius: var(--radius); + overflow: hidden; +} +th, td { text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--line-soft); } +thead th { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.4px; background: var(--bg-alt); } +tbody tr:hover { background: var(--panel-2); } +tbody tr:last-child td { border-bottom: 0; } td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; } .badge { - background: var(--ok); color: #fff; font-size: 11px; - padding: 2px 7px; border-radius: 20px; + background: rgba(61,113,217,0.18); + color: var(--accent); + border: 1px solid rgba(61,113,217,0.35); + font-size: 11px; + padding: 1px 8px; + border-radius: 20px; + font-weight: 500; } + +.actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; } +.actions form { display: inline-flex; gap: 6px; align-items: center; margin: 0; } +.actions input[type=text] { width: 116px; margin: 0; padding: 5px 9px; }