ui: restore a 50px top header with centered brand; rename to CoRE.Vizion+

Re-add the top header as a 50px bar injected by ensureHeader() on every
layout page (skipped on login), with the brand centered. The layout height
now subtracts both header and footer. Rebrand the project CoRE.Vision →
CoRE.Vizion+ everywhere: header + footer brand, all page <title>s, the login
card, and the FastAPI OpenAPI title (applies on next backend restart).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-01 22:03:04 +05:00
parent 02efe2ab4b
commit ab817e0d31
10 changed files with 26 additions and 17 deletions
+1 -1
View File
@@ -1 +1 @@
0.0.141
0.0.142
+1 -1
View File
@@ -70,7 +70,7 @@ async def lifespan(app: FastAPI):
await runtime.db.close()
app = FastAPI(title="CoRE.Vision", version="1.0", lifespan=lifespan)
app = FastAPI(title="CoRE.Vizion+", version="1.0", lifespan=lifespan)
# публичные пути (без авторизации)
PUBLIC_PATHS = {"/login", "/api/login", "/api/health"}
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CoRE.Vision — Архив</title>
<title>CoRE.Vizion+ — Архив</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body data-page="archive">
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CoRE.Vision — Камеры</title>
<title>CoRE.Vizion+ — Камеры</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body data-page="cams">
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CoRE.Vision — Live view</title>
<title>CoRE.Vizion+ — Live view</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body data-page="live">
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CoRE.Vision — Раскладка</title>
<title>CoRE.Vizion+ — Раскладка</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body data-page="layout">
+2 -2
View File
@@ -3,12 +3,12 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CoRE.Vision — Вход</title>
<title>CoRE.Vizion+ — Вход</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body class="login-body">
<form class="login-card" id="login-form">
<div class="brand login-brand">CoRE<span>.Vision</span></div>
<div class="brand login-brand">CoRE<span>.Vizion+</span></div>
<input type="text" id="username" placeholder="Логин" autocomplete="username" autofocus>
<input type="password" id="password" placeholder="Пароль" autocomplete="current-password">
<button type="submit">Войти</button>
+1 -1
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CoRE.Vision — Настройки</title>
<title>CoRE.Vizion+ — Настройки</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body data-page="settings">
+10 -1
View File
@@ -144,13 +144,21 @@ function ensureFooter() {
if (document.querySelector("footer.app-footer") || !document.querySelector(".layout")) return;
const f = document.createElement("footer");
f.className = "app-footer";
f.innerHTML = `<span class="brand">CoRE<span>.Vision</span></span>` +
f.innerHTML = `<span class="brand">CoRE<span>.Vizion+</span></span>` +
`<span class="spacer"></span>` +
`<span class="stats" id="sys-stats"></span>` +
`<span class="clock" id="clock"></span>` +
`<span id="footer-version"></span>`;
document.body.appendChild(f);
}
function ensureHeader() {
// шапка 50px сверху с брендом по центру — на страницах с раскладкой (не на login)
if (document.querySelector("header.app-header") || !document.querySelector(".layout")) return;
const h = document.createElement("header");
h.className = "app-header";
h.innerHTML = `<span class="brand">CoRE<span>.Vizion+</span></span>`;
document.body.insertBefore(h, document.body.firstChild);
}
async function refreshStats() {
const el = document.getElementById("sys-stats");
try {
@@ -2112,6 +2120,7 @@ function initTabs() {
// ── загрузка страницы ───────────────────────────────────────────
document.addEventListener("DOMContentLoaded", async () => {
ensureVersionEl();
ensureHeader(); // шапка 50px с брендом по центру
ensureFooter(); // создаёт футер с #sys-stats/#clock до их заполнения
startClock();
bindLogout();
+7 -7
View File
@@ -13,7 +13,7 @@
--ok: #22c55e;
--warn: #f59e0b;
--err: #ef4444;
--header-h: 48px;
--header-h: 50px;
--footer-h: 22px;
--sidebar-w: 72px;
}
@@ -27,12 +27,12 @@ html, body {
overflow: hidden;
}
/* ── Шапка ── */
header {
height: var(--header-h); display: flex; align-items: center;
/* ── Шапка (50px, бренд по центру) ── */
header.app-header {
height: var(--header-h); flex: none; display: flex; align-items: center; justify-content: center;
padding: 0 14px; background: var(--panel); border-bottom: 1px solid var(--border);
gap: 18px;
}
header.app-header .brand { font-size: 18px; }
.brand { font-weight: 700; letter-spacing: .5px; color: #fff; }
.brand span { color: #c0392b; }
nav { display: flex; gap: 4px; }
@@ -47,8 +47,8 @@ nav a.active { background: var(--accent); color: #fff; }
.stats b { color: var(--text); font-weight: 600; }
.clock { font-variant-numeric: tabular-nums; color: var(--text); }
/* ── Раскладка (хедер убран — контент во всю высоту минус футер) ── */
.layout { display: flex; height: calc(100% - var(--footer-h)); }
/* ── Раскладка (между шапкой и футером) ── */
.layout { display: flex; height: calc(100% - var(--header-h) - var(--footer-h)); }
/* ── Футер (бренд + CPU/RAM/диск + часы + версия) ── */
footer.app-footer {