feat: add consistent top navigation bar to all pages
Some checks failed
Watch Mihomo Meta / watch (push) Has been cancelled
Sync Upstream Parser / sync (push) Has been cancelled
Build and Push Docker image / 🔧 Prepare Metadata (push) Has been cancelled
Build and Push Docker image / 🐳 Docker Build (${{ matrix.arch }}) (push) Has been cancelled
Build and Push Docker image / 🪟 Windows Build (amd64) (push) Has been cancelled
Build and Push Docker image / 🔗 Docker Manifest (push) Has been cancelled
Build and Push Docker image / 🚀 Release Artifacts (push) Has been cancelled

- Add topbar with brand icon, nav links (Version/Inspector/Dashboard),
  and language toggle to version page and inspect page
- Change both pages from centered-container layout to shell+topbar layout
- Update JavaScript lang toggle selector for consistency
- WebApp page: add Version/Inspect nav links and more quick-action buttons
This commit is contained in:
sakuradairong
2026-06-16 10:34:33 +08:00
parent 819c29b843
commit 169a60690d
3 changed files with 200 additions and 101 deletions

View File

@@ -144,14 +144,11 @@ std::string page(Request &request, Response &response) {
body {
font-family: 'Outfit', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", sans-serif;
margin: 0;
min-height: 100vh;
min-height: 100svh;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-gradient);
background-attachment: fixed;
padding: 24px;
color: var(--text-primary);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -182,38 +179,82 @@ std::string page(Request &request, Response &response) {
opacity: 0.82;
}
.lang-toggle {
position: fixed;
top: calc(18px + env(safe-area-inset-top, 0px));
right: calc(18px + env(safe-area-inset-right, 0px));
z-index: 10;
display: inline-flex;
.shell {
position: relative;
z-index: 1;
width: min(1180px, calc(100% - 32px));
margin: 0 auto;
padding: 28px 0 42px;
}
.topbar {
display: flex;
align-items: center;
gap: 8px;
justify-content: space-between;
gap: 16px;
margin-bottom: 24px;
}
.brand-row {
display: flex;
align-items: center;
gap: 14px;
min-width: 0;
}
.brand-row img {
width: 48px;
height: 48px;
flex: 0 0 auto;
filter: drop-shadow(0 12px 24px rgba(2, 132, 199, 0.16));
}
.brand-row h1 {
margin: 0;
font-size: 1.8rem;
line-height: 1.08;
letter-spacing: 0;
overflow-wrap: anywhere;
background: none;
-webkit-background-clip: unset;
background-clip: unset;
-webkit-text-fill-color: unset;
}
.brand-row .top-subtitle {
margin-top: 5px;
color: var(--text-secondary);
font-size: 0.94rem;
font-weight: 600;
}
.actions {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
justify-content: flex-end;
}
.lang-btn {
border: 1px solid var(--control-border);
border-radius: 999px;
background: var(--control-bg);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
box-shadow: var(--control-shadow);
color: var(--text-primary);
cursor: pointer;
font: inherit;
font-size: 0.86rem;
font-size: 0.88rem;
font-weight: 700;
line-height: 1;
min-height: 40px;
min-width: 76px;
padding: 9px 13px;
transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}
.lang-toggle:hover {
.lang-btn:hover {
background: var(--control-hover);
transform: translateY(-1px);
}
.lang-btn:focus-visible {
outline: 3px solid rgba(99, 179, 237, 0.35);
outline-offset: 2px;
}
.lang-toggle:focus-visible,
button:focus-visible,
textarea:focus-visible,
input:focus-visible {
@@ -221,17 +262,6 @@ std::string page(Request &request, Response &response) {
outline-offset: 2px;
}
.lang-toggle svg {
width: 17px;
height: 17px;
flex: 0 0 auto;
}
.lang-toggle-text {
min-width: 20px;
text-align: center;
}
.page-links {
display: inline-flex;
justify-content: center;
@@ -747,9 +777,14 @@ std::string page(Request &request, Response &response) {
@media (max-width: 780px) {
body {
align-items: stretch;
padding: 76px 14px 18px;
padding: 0;
}
.shell {
padding: 16px 0 24px;
width: min(100% - 20px, 1180px);
}
.topbar { flex-direction: column; align-items: flex-start; }
.brand-row h1 { font-size: 1.4rem; }
.container {
border-radius: 24px;
@@ -827,17 +862,22 @@ std::string page(Request &request, Response &response) {
</style>
</head>
<body>
<button class="lang-toggle" type="button" aria-label="Switch language">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M4 5h9M9 3v2m1.7 0c-.6 3.5-2.4 6.1-5.2 7.7m2.8-3.1c1.1 1.3 2.3 2.3 3.7 3" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 20l4-9 4 9m-6.7-3h5.4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="lang-toggle-text" data-lang="en">中</span>
<span class="lang-toggle-text" data-lang="zh">EN</span>
</button>
<main class="container">
<header>
<div class="shell">
<div class="topbar">
<div class="brand-row">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="/version/favicon-dark.svg">
<img src="/version/favicon-light.svg" alt="SubConverter-Extended" width="48" height="48" decoding="async">
</picture>
<div>
<h1>SubConverter-Extended</h1>
<div class="top-subtitle">
<span data-lang="en">Request Inspector</span>
<span data-lang="zh"></span>
</div>
</div>
</div>
<div class="actions">
<picture class="brand-mark">
<source media="(prefers-color-scheme: dark)" srcset="/version/favicon-dark.svg">
<img src="/version/favicon-light.svg" alt="SubConverter-Extended icon" width="88" height="88" decoding="async">
@@ -865,8 +905,13 @@ std::string page(Request &request, Response &response) {
<span data-lang="zh"></span>
</a>)html" +
dashboard_link + R"html(
</nav>
</header>
<button type="button" class="lang-btn" id="lang-toggle" aria-label="Switch language">
<span class="lang-toggle-text">中</span>
</button>
</div>
</div>
<main class="container">
<header>
<section class="section">
<div class="section-title">
@@ -1036,7 +1081,7 @@ std::string page(Request &request, Response &response) {
R"html( · <a href="/version">版本信息</a> · 源代码:<a href="https://github.com/Aethersailor/SubConverter-Extended" target="_blank" rel="noopener noreferrer">GitHub</a> · 许可证:<a href="https://www.gnu.org/licenses/gpl-3.0.html" target="_blank" rel="noopener noreferrer">GPL-3.0</a></span>
</footer>
</main>
</div>
<script>
(function () {
var input = document.getElementById("request-input");
@@ -1502,7 +1547,7 @@ std::string page(Request &request, Response &response) {
}
}
document.querySelector(".lang-toggle").addEventListener("click", function () {
document.getElementById("lang-toggle").addEventListener("click", function () {
document.documentElement.lang = isZh() ? "en" : "zh-CN";
if (lastReport) {
renderReport(lastReport);

View File

@@ -270,14 +270,11 @@ std::string page(Request &request, Response &response) {
body {
font-family: 'Outfit', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC", sans-serif;
margin: 0;
min-height: 100vh;
min-height: 100svh;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg-gradient);
background-attachment: fixed;
padding: 24px;
color: var(--text-primary);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -308,53 +305,82 @@ std::string page(Request &request, Response &response) {
opacity: 0.82;
}
.lang-toggle {
position: fixed;
top: calc(18px + env(safe-area-inset-top, 0px));
right: calc(18px + env(safe-area-inset-right, 0px));
z-index: 10;
display: inline-flex;
.shell {
position: relative;
z-index: 1;
width: min(1180px, calc(100% - 32px));
margin: 0 auto;
padding: 28px 0 42px;
}
.topbar {
display: flex;
align-items: center;
gap: 8px;
justify-content: space-between;
gap: 16px;
margin-bottom: 24px;
}
.brand-row {
display: flex;
align-items: center;
gap: 14px;
min-width: 0;
}
.brand-row img {
width: 48px;
height: 48px;
flex: 0 0 auto;
filter: drop-shadow(0 12px 24px rgba(2, 132, 199, 0.16));
}
.brand-row h1 {
margin: 0;
font-size: 1.8rem;
line-height: 1.08;
letter-spacing: 0;
overflow-wrap: anywhere;
background: none;
-webkit-background-clip: unset;
background-clip: unset;
-webkit-text-fill-color: unset;
}
.brand-row .top-subtitle {
margin-top: 5px;
color: var(--text-secondary);
font-size: 0.94rem;
font-weight: 600;
}
.actions {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
justify-content: flex-end;
}
.lang-btn {
border: 1px solid var(--control-border);
border-radius: 999px;
background: var(--control-bg);
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
box-shadow: var(--control-shadow);
color: var(--text-primary);
cursor: pointer;
font: inherit;
font-size: 0.86rem;
font-size: 0.88rem;
font-weight: 700;
line-height: 1;
min-height: 40px;
min-width: 76px;
padding: 9px 13px;
transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
backdrop-filter: blur(18px);
-webkit-backdrop-filter: blur(18px);
transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}
.lang-toggle:hover {
.lang-btn:hover {
background: var(--control-hover);
transform: translateY(-1px);
}
.lang-toggle:focus-visible {
.lang-btn:focus-visible {
outline: 3px solid rgba(99, 179, 237, 0.35);
outline-offset: 2px;
}
.lang-toggle svg {
width: 17px;
height: 17px;
flex: 0 0 auto;
}
.lang-toggle-text {
min-width: 20px;
text-align: center;
}
.page-links {
display: inline-flex;
justify-content: center;
@@ -740,12 +766,12 @@ std::string page(Request &request, Response &response) {
.container::after {
border-radius: 26px;
}
.lang-toggle {
top: calc(14px + env(safe-area-inset-top, 0px));
right: calc(14px + env(safe-area-inset-right, 0px));
min-height: 38px;
min-width: 70px;
.shell {
padding: 16px 0 24px;
width: min(100% - 20px, 1180px);
}
.topbar { flex-direction: column; align-items: flex-start; }
.brand-row h1 { font-size: 1.4rem; }
header { margin-bottom: 24px; }
h1 {
font-size: 2em;
@@ -796,17 +822,22 @@ std::string page(Request &request, Response &response) {
</style>
</head>
<body>
<button type="button" class="lang-toggle" id="lang-toggle">
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<path d="M2 12h20"></path>
<path d="M12 2a15.3 15.3 0 0 1 0 20"></path>
<path d="M12 2a15.3 15.3 0 0 0 0 20"></path>
</svg>
<span class="lang-toggle-text">中</span>
</button>
<div class="container">
<header>
<main class="shell">
<div class="topbar">
<div class="brand-row">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="/version/favicon-dark.svg">
<img src="/version/favicon-light.svg" alt="SubConverter-Extended" width="48" height="48" decoding="async">
</picture>
<div>
<h1>SubConverter-Extended</h1>
<div class="top-subtitle">
<span data-lang="en">A Modern Evolution of Subconverter</span>
<span data-lang="zh">Subconverter </span>
</div>
</div>
</div>
<div class="actions">
<picture class="brand-mark">
<source media="(prefers-color-scheme: dark)" srcset="/version/favicon-dark.svg">
<img src="/version/favicon-light.svg" alt="SubConverter-Extended icon" width="96" height="96" decoding="async">
@@ -833,8 +864,13 @@ std::string page(Request &request, Response &response) {
<span data-lang="zh"></span>
</a>)html" +
dashboard_link + R"html(
</nav>
</header>
<button type="button" class="lang-btn" id="lang-toggle" aria-label="Switch language">
<span class="lang-toggle-text">中</span>
</button>
</div>
</div>
<div class="container">
<header>
<div class="info-grid">
<div class="info-card">
@@ -913,6 +949,7 @@ std::string page(Request &request, Response &response) {
<span data-lang="zh"><a href="https://github.com/Aethersailor/SubConverter-Extended" target="_blank" rel="noopener noreferrer">GitHub</a> <a href="https://www.gnu.org/licenses/gpl-3.0.html" target="_blank" rel="noopener noreferrer">GPL-3.0</a></span>
</div>
</div>
</main>
<script>
(function () {
var toggle = document.getElementById("lang-toggle");

View File

@@ -412,6 +412,14 @@ std::string page(Request &, Response &response) {
</div>
</div>
<div class="topbar-actions">
<a class="nav-link" href="/version">
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M12 16v-4"></path><circle cx="12" cy="8" r="1"></circle></svg>
<span data-lang="en">Version</span><span data-lang="zh"></span>
</a>
<a class="nav-link" href="/inspect">
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"></circle><path d="M21 21l-4.35-4.35"></path></svg>
<span data-lang="en">Inspect</span><span data-lang="zh"></span>
</a>
)html" +
dashboard_link +
R"html(
@@ -421,11 +429,20 @@ std::string page(Request &, Response &response) {
<!-- Quick Actions -->
<div class="quick-actions">
<a class="quick-btn" href="/clash"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>Clash</a>
<a class="quick-btn" href="/surge"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>Surge</a>
<a class="quick-btn" href="/singbox"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>Sing-box</a>
<a class="quick-btn" href="/quanx"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>QuanX</a>
<a class="quick-btn" href="/v2ray"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>V2Ray</a>
<a class="quick-btn" href="/clash" title="Clash / Mihomo"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>Clash</a>
<a class="quick-btn" href="/clashr" title="ClashR"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>ClashR</a>
<a class="quick-btn" href="/surge" title="Surge"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>Surge</a>
<a class="quick-btn" href="/quanx" title="Quantumult X"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>QuanX</a>
<a class="quick-btn" href="/quan" title="Quantumult"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>Quan</a>
<a class="quick-btn" href="/loon" title="Loon"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>Loon</a>
<a class="quick-btn" href="/surfboard" title="Surfboard"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>Surfboard</a>
<a class="quick-btn" href="/mellow" title="Mellow"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>Mellow</a>
<a class="quick-btn" href="/singbox" title="Sing-box"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>Sing-box</a>
<a class="quick-btn" href="/ss" title="Shadowsocks"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>SS</a>
<a class="quick-btn" href="/ssr" title="ShadowsocksR"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>SSR</a>
<a class="quick-btn" href="/v2ray" title="V2Ray"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>V2Ray</a>
<a class="quick-btn" href="/trojan" title="Trojan"><svg viewBox="0 0 24 24"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>Trojan</a>
<a class="quick-btn" href="/mixed" title="Mixed"><svg viewBox="0 0 24 24"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>Mixed</a>
</div>
<!-- Main Panel -->