feat: add branding assets and favicon support across admin-web and desktop-client

Add NexaVPN logo images (full logo and mark-only variants) to admin-web and desktop-client public directories. Add favicon.ico and favicon.png to admin-web, and icon.png to desktop-client. Update index.html files to reference favicon assets. Add icon.png and icon.ico to desktop-client Tauri icons directory and configure bundle.icon in tauri.conf.json. Update Layout component to display logo in sidebar brand-block with
This commit is contained in:
2026-03-17 19:37:58 +01:00
parent d4e8fc28c7
commit 61d2b4b25c
14 changed files with 128 additions and 9 deletions

View File

@@ -3,6 +3,8 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/png" href="/NexaVPN_Logo_Only.png" />
<link rel="shortcut icon" href="/favicon.ico" />
<title>NexaVPN Admin</title> <title>NexaVPN Admin</title>
</head> </head>
<body> <body>

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

@@ -14,9 +14,13 @@ export function Layout() {
return ( return (
<div className="shell"> <div className="shell">
<aside className="sidebar"> <aside className="sidebar">
<div> <div className="brand-block">
<p className="eyebrow">NexaVPN</p> <img className="brand-logo brand-logo-full" src="/NexaVPN_Logo.png" alt="NexaVPN" />
<h1>Control Plane</h1> <div className="brand-copy">
<p className="eyebrow">NexaVPN</p>
<h1>Control Plane</h1>
<p className="brand-tagline">Remote access orchestration for your private WireGuard edge.</p>
</div>
</div> </div>
<nav className="nav"> <nav className="nav">
{items.map(([label, path]) => ( {items.map(([label, path]) => (
@@ -32,9 +36,12 @@ export function Layout() {
</aside> </aside>
<main className="content"> <main className="content">
<header className="topbar"> <header className="topbar">
<div> <div className="topbar-brand">
<p className="eyebrow">Enterprise WireGuard</p> <img className="brand-logo brand-logo-mark" src="/NexaVPN_Logo_Only.png" alt="NexaVPN mark" />
<h2>Self-hosted VPN management</h2> <div>
<p className="eyebrow">Enterprise WireGuard</p>
<h2>Self-hosted VPN management</h2>
</div>
</div> </div>
<div className="pill">Secure by design</div> <div className="pill">Secure by design</div>
</header> </header>

View File

@@ -58,8 +58,13 @@ export function LoginPage({ onAuthenticated }: LoginPageProps) {
return ( return (
<div className="auth-shell"> <div className="auth-shell">
<form className="auth-card" onSubmit={onSubmit}> <form className="auth-card" onSubmit={onSubmit}>
<p className="eyebrow">NexaVPN Admin</p> <div className="auth-brand">
<h2>{mode === "login" ? "Sign in" : "Create initial admin"}</h2> <img className="brand-logo brand-logo-full" src="/NexaVPN_Logo.png" alt="NexaVPN" />
<div>
<p className="eyebrow">NexaVPN Admin</p>
<h2>{mode === "login" ? "Sign in" : "Create initial admin"}</h2>
</div>
</div>
<p className="auth-copy"> <p className="auth-copy">
{mode === "login" {mode === "login"
? "Use your NexaVPN admin credentials." ? "Use your NexaVPN admin credentials."

View File

@@ -61,6 +61,45 @@ button {
backdrop-filter: blur(12px); backdrop-filter: blur(12px);
} }
.auth-brand,
.brand-block,
.topbar-brand {
display: flex;
align-items: center;
gap: 16px;
}
.brand-block {
align-items: flex-start;
}
.brand-copy {
display: grid;
gap: 6px;
}
.brand-tagline {
margin: 0;
color: var(--muted);
max-width: 240px;
line-height: 1.5;
}
.brand-logo {
display: block;
height: auto;
}
.brand-logo-full {
width: min(100%, 220px);
}
.brand-logo-mark {
width: 48px;
border-radius: 14px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
}
.auth-card label { .auth-card label {
display: grid; display: grid;
gap: 8px; gap: 8px;
@@ -258,6 +297,18 @@ button {
border-bottom: 1px solid var(--line); border-bottom: 1px solid var(--line);
} }
.auth-brand,
.brand-block,
.topbar-brand,
.page-header {
align-items: flex-start;
flex-direction: column;
}
.brand-logo-full {
width: min(100%, 200px);
}
.grid.two, .grid.two,
.grid.three { .grid.three {
grid-template-columns: 1fr; grid-template-columns: 1fr;

View File

@@ -3,6 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/png" href="/icon.png" />
<title>NexaVPN</title> <title>NexaVPN</title>
</head> </head>
<body> <body>

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

@@ -25,7 +25,10 @@
"bundle": { "bundle": {
"active": true, "active": true,
"targets": "all", "targets": "all",
"icon": [], "icon": [
"icons/icon.png",
"icons/icon.ico"
],
"resources": [ "resources": [
"bundled/**/*" "bundled/**/*"
] ]

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python3
from __future__ import annotations
import struct
import sys
from pathlib import Path
PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
def main() -> int:
if len(sys.argv) != 3:
print("usage: generate_ico_from_png.py <input.png> <output.ico>", file=sys.stderr)
return 1
source = Path(sys.argv[1])
target = Path(sys.argv[2])
png = source.read_bytes()
if not png.startswith(PNG_SIGNATURE):
print(f"{source} is not a PNG file", file=sys.stderr)
return 1
width = int.from_bytes(png[16:20], "big")
height = int.from_bytes(png[20:24], "big")
width_byte = 0 if width >= 256 else width
height_byte = 0 if height >= 256 else height
header = struct.pack("<HHH", 0, 1, 1)
directory = struct.pack(
"<BBBBHHII",
width_byte,
height_byte,
0,
0,
1,
32,
len(png),
len(header) + 16,
)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(header + directory + png)
print(f"wrote {target}")
return 0
if __name__ == "__main__":
raise SystemExit(main())