Add Store MSIX packaging and dark/light theme for 2.9.0.

EOF

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Navis
2026-08-01 14:12:30 +03:00
co-authored by Cursor
parent 93017e2076
commit d42202a1cf
17 changed files with 706 additions and 63 deletions
+216 -41
View File
@@ -4,12 +4,28 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Navis</title>
<script>
(function () {
try {
var k = "navis.theme";
var t = localStorage.getItem(k);
if (t !== "light" && t !== "dark") {
t = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
document.documentElement.setAttribute("data-theme", t);
} catch (_) {
document.documentElement.setAttribute("data-theme", "light");
}
})();
</script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Sora:wght@500;600;700;800&family=Figtree:wght@400;500;600;700&display=swap" rel="stylesheet" />
<style>
:root {
color-scheme: light;
--ink: #0b1c18;
--text: var(--ink);
--muted: #5a736b;
--line: rgba(11, 28, 24, 0.10);
--accent: #0d8a66;
@@ -17,11 +33,96 @@
--accent-soft: #d8f3e9;
--danger: #c0362c;
--ok: #0d8a66;
--ms-good: #0a7a3e;
--ms-mid: #b8860b;
--surface: rgba(255, 255, 255, 0.82);
--surface-2: rgba(255, 255, 255, 0.62);
--input-bg: rgba(255, 255, 255, 0.94);
--row-bg: rgba(255, 255, 255, 0.6);
--hero-bg: linear-gradient(160deg, rgba(255,255,255,.92), rgba(232,247,240,.88));
--hero-bg-on: linear-gradient(160deg, rgba(216,243,233,.95), rgba(255,255,255,.9));
--chip-bg: rgba(255, 255, 255, 0.75);
--server-list-bg: rgba(255, 255, 255, 0.4);
--server-row-bg: rgba(255, 255, 255, 0.72);
--server-row-hover: #fff;
--server-row-active: linear-gradient(135deg, rgba(216,243,233,.95), #fff);
--meta-bg: rgba(255, 255, 255, 0.35);
--meta-ok-bg: rgba(216, 243, 233, 0.65);
--meta-err-bg: rgba(255, 236, 234, 0.75);
--shop-bg: linear-gradient(135deg, rgba(13,138,102,.09), rgba(255,255,255,.55));
--shop-border: rgba(13, 138, 102, 0.16);
--modal-bg: #f5fbf8;
--modal-scrim: rgba(8, 28, 24, 0.42);
--update-bg: linear-gradient(135deg, rgba(13,138,102,.14), rgba(255,255,255,.75));
--update-border: rgba(13, 138, 102, 0.28);
--icon-btn-bg: linear-gradient(180deg, #d2efe4, #bfe6d6);
--icon-btn-border: rgba(8, 90, 68, 0.22);
--icon-btn-hover: linear-gradient(180deg, #c5e9db, #aedfcb);
--mini-bg: linear-gradient(180deg, #d2efe4, #bfe6d6);
--mini-accent-bg: linear-gradient(180deg, #2f6f5f, #25584b);
--mini-accent-fg: #f2fff9;
--switch-off: #c7d5cf;
--switch-knob: #fff;
--dot-off: #9aaba4;
--shadow: 0 18px 50px rgba(8, 40, 32, 0.12);
--radius: 22px;
--ease: cubic-bezier(.22,.8,.24,1);
--page-bg:
radial-gradient(780px 420px at 8% -10%, #9fe0c8 0%, transparent 55%),
radial-gradient(640px 380px at 100% 0%, #b8d4e8 0%, transparent 48%),
radial-gradient(500px 320px at 50% 110%, #cfe8dc 0%, transparent 45%),
linear-gradient(165deg, #e7f6f0 0%, #f4faf7 48%, #eef4f8 100%);
--theme-toggle-bg: rgba(255, 255, 255, 0.7);
}
html[data-theme="dark"] {
color-scheme: dark;
--ink: #e4f2eb;
--text: var(--ink);
--muted: #8fa89c;
--line: rgba(180, 220, 200, 0.12);
--accent: #2ec995;
--accent-deep: #1aa877;
--accent-soft: rgba(46, 201, 149, 0.16);
--danger: #e07068;
--ok: #2ec995;
--ms-good: #3dd68c;
--ms-mid: #d4a017;
--surface: rgba(18, 32, 28, 0.88);
--surface-2: rgba(24, 40, 35, 0.72);
--input-bg: rgba(12, 24, 20, 0.92);
--row-bg: rgba(22, 36, 31, 0.75);
--hero-bg: linear-gradient(160deg, rgba(22,38,33,.95), rgba(16,28,24,.9));
--hero-bg-on: linear-gradient(160deg, rgba(18,48,40,.96), rgba(22,38,33,.92));
--chip-bg: rgba(28, 44, 38, 0.85);
--server-list-bg: rgba(10, 20, 17, 0.55);
--server-row-bg: rgba(22, 36, 31, 0.85);
--server-row-hover: rgba(30, 48, 41, 0.95);
--server-row-active: linear-gradient(135deg, rgba(18,52,42,.95), rgba(24,40,35,.95));
--meta-bg: rgba(14, 26, 22, 0.55);
--meta-ok-bg: rgba(18, 52, 42, 0.65);
--meta-err-bg: rgba(56, 28, 26, 0.7);
--shop-bg: linear-gradient(135deg, rgba(46,201,149,.1), rgba(18,32,28,.7));
--shop-border: rgba(46, 201, 149, 0.2);
--modal-bg: #15251f;
--modal-scrim: rgba(4, 12, 10, 0.62);
--update-bg: linear-gradient(135deg, rgba(46,201,149,.16), rgba(18,32,28,.85));
--update-border: rgba(46, 201, 149, 0.28);
--icon-btn-bg: linear-gradient(180deg, #1e3d34, #17332b);
--icon-btn-border: rgba(46, 201, 149, 0.28);
--icon-btn-hover: linear-gradient(180deg, #255045, #1c3c33);
--mini-bg: linear-gradient(180deg, #1e3d34, #17332b);
--mini-accent-bg: linear-gradient(180deg, #1aa877, #0d8a66);
--mini-accent-fg: #f2fff9;
--switch-off: #3a4f47;
--switch-knob: #e8f5ef;
--dot-off: #5a736b;
--shadow: 0 18px 50px rgba(0, 0, 0, 0.45);
--page-bg:
radial-gradient(720px 400px at 6% -8%, rgba(26, 90, 70, 0.45) 0%, transparent 55%),
radial-gradient(560px 340px at 100% 0%, rgba(28, 56, 72, 0.4) 0%, transparent 48%),
radial-gradient(480px 300px at 50% 110%, rgba(18, 64, 48, 0.35) 0%, transparent 45%),
linear-gradient(165deg, #0c1613 0%, #121f1a 48%, #101a1c 100%);
--theme-toggle-bg: rgba(28, 44, 38, 0.85);
}
* { box-sizing: border-box; }
html, body {
@@ -29,11 +130,7 @@
min-height: 100%;
font-family: Figtree, sans-serif;
color: var(--ink);
background:
radial-gradient(780px 420px at 8% -10%, #9fe0c8 0%, transparent 55%),
radial-gradient(640px 380px at 100% 0%, #b8d4e8 0%, transparent 48%),
radial-gradient(500px 320px at 50% 110%, #cfe8dc 0%, transparent 45%),
linear-gradient(165deg, #e7f6f0 0%, #f4faf7 48%, #eef4f8 100%);
background: var(--page-bg);
}
body {
display: grid;
@@ -60,8 +157,8 @@
margin-bottom: 12px;
padding: 12px 14px;
border-radius: 16px;
border: 1px solid rgba(13, 138, 102, 0.28);
background: linear-gradient(135deg, rgba(13,138,102,.14), rgba(255,255,255,.75));
border: 1px solid var(--update-border);
background: var(--update-bg);
animation: rise .4s var(--ease) both;
}
.update-banner.show { display: block; }
@@ -136,14 +233,35 @@
font-size: .84rem;
line-height: 1.35;
}
.theme-toggle {
flex: 0 0 auto;
width: 42px;
height: 42px;
border-radius: 14px;
border: 1px solid var(--line);
background: var(--theme-toggle-bg);
color: var(--ink);
cursor: pointer;
display: grid;
place-items: center;
transition: background .15s, border-color .15s, transform .12s;
padding: 0;
}
.theme-toggle:hover {
border-color: rgba(13, 138, 102, 0.35);
transform: translateY(-1px);
}
.theme-toggle:active { transform: none; }
.theme-toggle svg { width: 20px; height: 20px; display: none; }
html[data-theme="light"] .theme-toggle .icon-moon { display: block; }
html[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
.hero {
position: relative;
overflow: hidden;
border-radius: var(--radius);
border: 1px solid var(--line);
background:
linear-gradient(160deg, rgba(255,255,255,.92), rgba(232,247,240,.88));
background: var(--hero-bg);
padding: 16px 16px 14px;
margin-bottom: 14px;
transition: border-color .25s, box-shadow .25s, background .25s;
@@ -151,8 +269,7 @@
.hero.on {
border-color: rgba(13,138,102,.35);
box-shadow: 0 14px 36px rgba(13, 138, 102, 0.14);
background:
linear-gradient(160deg, rgba(216,243,233,.95), rgba(255,255,255,.9));
background: var(--hero-bg-on);
}
.hero::after {
content: "";
@@ -184,7 +301,7 @@
}
.dot {
width: 11px; height: 11px; border-radius: 50%;
background: #9aaba4;
background: var(--dot-off);
transition: background .2s;
}
.dot.on {
@@ -202,7 +319,7 @@
letter-spacing: .03em;
text-transform: uppercase;
color: var(--muted);
background: rgba(255,255,255,.75);
background: var(--chip-bg);
border: 1px solid var(--line);
border-radius: 999px;
padding: 5px 10px;
@@ -265,7 +382,7 @@
select, input[type="text"], textarea {
width: 100%;
border: 1px solid var(--line);
background: rgba(255,255,255,.94);
background: var(--input-bg);
border-radius: 14px;
padding: 11px 13px;
font: inherit;
@@ -290,8 +407,8 @@
display: grid;
place-items: center;
border-radius: 14px;
border: 1px solid rgba(8, 90, 68, 0.22);
background: linear-gradient(180deg, #d2efe4, #bfe6d6);
border: 1px solid var(--icon-btn-border);
background: var(--icon-btn-bg);
color: var(--accent-deep);
font-size: 1.15rem;
font-weight: 700;
@@ -300,9 +417,9 @@
box-shadow: 0 4px 12px rgba(8, 90, 68, 0.08);
}
.icon-btn:hover {
background: linear-gradient(180deg, #c5e9db, #aedfcb);
background: var(--icon-btn-hover);
border-color: rgba(8, 90, 68, 0.38);
color: #05553f;
color: var(--accent-deep);
box-shadow: 0 6px 14px rgba(8, 90, 68, 0.14);
}
.icon-btn.danger {
@@ -374,7 +491,7 @@
padding: 11px 13px;
border: 1px solid var(--line);
border-radius: 14px;
background: rgba(255,255,255,.6);
background: var(--row-bg);
margin-bottom: 12px;
font-weight: 600;
font-size: .9rem;
@@ -382,7 +499,7 @@
.switch { position: relative; width: 44px; height: 26px; flex: 0 0 auto; }
.switch input { opacity: 0; width: 0; height: 0; }
.slider {
position: absolute; inset: 0; background: #c7d5cf;
position: absolute; inset: 0; background: var(--switch-off);
border-radius: 999px; cursor: pointer; transition: background .15s;
}
.slider::before {
@@ -390,7 +507,7 @@
position: absolute;
width: 20px; height: 20px;
left: 3px; top: 3px;
background: #fff;
background: var(--switch-knob);
border-radius: 50%;
transition: transform .15s;
box-shadow: 0 2px 6px rgba(0,0,0,.12);
@@ -471,8 +588,8 @@
}
.server-actions .mini {
appearance: none;
border: 1px solid rgba(8, 90, 68, 0.22);
background: linear-gradient(180deg, #d2efe4, #bfe6d6);
border: 1px solid var(--icon-btn-border);
background: var(--mini-bg);
color: var(--accent-deep);
border-radius: 999px;
padding: 6px 10px;
@@ -484,8 +601,8 @@
}
.server-actions .mini:hover { filter: brightness(.97); }
.server-actions .mini.accent {
background: linear-gradient(180deg, #2f6f5f, #25584b);
color: #f2fff9;
background: var(--mini-accent-bg);
color: var(--mini-accent-fg);
border-color: rgba(12, 48, 40, 0.35);
}
.auto-best {
@@ -506,7 +623,7 @@
padding: 4px;
border-radius: 14px;
border: 1px solid var(--line);
background: rgba(255,255,255,.4);
background: var(--server-list-bg);
}
.server-row {
display: grid;
@@ -516,7 +633,7 @@
padding: 6px 10px;
border-radius: 10px;
border: 1px solid transparent;
background: rgba(255,255,255,.72);
background: var(--server-row-bg);
cursor: pointer;
transition: background .12s, border-color .12s, box-shadow .12s;
text-align: left;
@@ -526,11 +643,11 @@
}
.server-row:hover {
border-color: rgba(13,138,102,.25);
background: #fff;
background: var(--server-row-hover);
}
.server-row.active {
border-color: rgba(13,138,102,.4);
background: linear-gradient(135deg, rgba(216,243,233,.95), #fff);
background: var(--server-row-active);
box-shadow: 0 4px 14px rgba(13,138,102,.1);
}
.server-row .left { min-width: 0; }
@@ -573,8 +690,8 @@
color: var(--muted);
}
.server-row .ms.ok { color: var(--ok); }
.server-row .ms.good { color: #0a7a3e; }
.server-row .ms.mid { color: #b8860b; }
.server-row .ms.good { color: var(--ms-good); }
.server-row .ms.mid { color: var(--ms-mid); }
.server-row .ms.bad { color: var(--danger); }
.server-empty {
padding: 14px 10px;
@@ -600,18 +717,18 @@
min-height: 2.4em;
padding: 8px 10px;
border-radius: 12px;
background: rgba(255,255,255,.35);
background: var(--meta-bg);
border: 1px solid transparent;
transition: color .15s, border-color .15s, background .15s;
}
.meta.ok {
color: var(--ok);
background: rgba(216,243,233,.65);
background: var(--meta-ok-bg);
border-color: rgba(13,138,102,.18);
}
.meta.err {
color: var(--danger);
background: rgba(255,236,234,.75);
background: var(--meta-err-bg);
border-color: rgba(192,54,44,.16);
}
@@ -627,7 +744,7 @@
padding: 8px 12px;
border-radius: 12px;
border: 1px solid var(--line);
background: rgba(255,255,255,.6);
background: var(--row-bg);
font-size: .82rem;
}
.ping-item .ms { font-weight: 700; }
@@ -638,8 +755,8 @@
margin-top: 12px;
padding: 13px 14px 12px;
border-radius: 18px;
border: 1px solid rgba(13, 138, 102, 0.16);
background: linear-gradient(135deg, rgba(13,138,102,.09), rgba(255,255,255,.55));
border: 1px solid var(--shop-border);
background: var(--shop-bg);
}
.shop h3 {
font-family: Sora, sans-serif;
@@ -680,7 +797,7 @@
.modal-backdrop {
position: fixed; inset: 0;
background: rgba(8, 28, 24, .42);
background: var(--modal-scrim);
backdrop-filter: blur(4px);
display: none;
place-items: center;
@@ -690,7 +807,7 @@
.modal-backdrop.open { display: grid; }
.modal {
width: min(400px, 100%);
background: #f5fbf8;
background: var(--modal-bg);
border-radius: 22px;
border: 1px solid var(--line);
box-shadow: var(--shadow);
@@ -737,10 +854,19 @@
<div class="brand-wrap">
<div class="brand-row">
<h1 class="brand">Navis</h1>
<span class="badge-ver">2.7.1</span>
<span class="badge-ver" id="badgeVer">2.9.0</span>
</div>
<p class="tagline">Быстрый клиент · Naive · Hy2 · AWG · Xray</p>
</div>
<button type="button" class="theme-toggle" id="themeToggle" title="Тема" aria-label="Переключить тему">
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M20.5 14.2A8.2 8.2 0 0 1 9.8 3.5 8.5 8.5 0 1 0 20.5 14.2z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
</svg>
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<circle cx="12" cy="12" r="4.2" stroke="currentColor" stroke-width="1.8"/>
<path d="M12 2.8v2.2M12 19v2.2M2.8 12h2.2M19 12h2.2M5.1 5.1l1.6 1.6M17.3 17.3l1.6 1.6M17.3 6.7l1.6-1.6M5.1 18.9l1.6-1.6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
</svg>
</button>
</header>
<section class="hero" id="hero">
@@ -867,6 +993,7 @@
<button class="action secondary" id="updCheckBtn" type="button">Проверить обновление</button>
<button class="action secondary" id="coreBtn" type="button">Установить cores</button>
</div>
<p class="meta" id="storeUpdateNote" hidden style="margin-top:8px;min-height:0">Обновления этой сборки — через Microsoft Store.</p>
</details>
<p class="meta" id="meta">Загрузка…</p>
@@ -952,7 +1079,41 @@
const updateBtn = $("updateBtn");
const skipUpdateBtn = $("skipUpdateBtn");
const verLabel = $("verLabel");
const badgeVer = $("badgeVer");
const storeUpdateNote = $("storeUpdateNote");
const themeToggle = $("themeToggle");
const THEME_KEY = "navis.theme";
const SKIP_UPDATE_KEY = "navis.skipUpdateVersion";
let storePackaged = false;
function readTheme() {
try {
const t = localStorage.getItem(THEME_KEY);
if (t === "light" || t === "dark") return t;
} catch (_) {}
try {
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
} catch (_) {
return "light";
}
}
function applyTheme(t) {
const theme = t === "dark" ? "dark" : "light";
document.documentElement.setAttribute("data-theme", theme);
try { localStorage.setItem(THEME_KEY, theme); } catch (_) {}
if (themeToggle) {
themeToggle.title = theme === "dark" ? "Светлая тема" : "Тёмная тема";
themeToggle.setAttribute("aria-label", themeToggle.title);
}
}
applyTheme(document.documentElement.getAttribute("data-theme") || readTheme());
if (themeToggle) {
themeToggle.addEventListener("click", () => {
const cur = document.documentElement.getAttribute("data-theme") === "dark" ? "dark" : "light";
applyTheme(cur === "dark" ? "light" : "dark");
});
}
function skippedVersion() {
try { return localStorage.getItem(SKIP_UPDATE_KEY) || ""; } catch (_) { return ""; }
}
@@ -1191,7 +1352,20 @@
}
function renderUpdate(u, version) {
verLabel.textContent = "Navis v" + (version || "?");
const label = "Navis v" + (version || "?");
verLabel.textContent = label;
if (badgeVer && version) {
const short = String(version).split("+")[0];
badgeVer.textContent = short || version;
}
if (storePackaged || (u && u.store_managed)) {
updateBanner.classList.remove("show");
if (updCheckBtn) updCheckBtn.hidden = true;
if (storeUpdateNote) storeUpdateNote.hidden = false;
return;
}
if (updCheckBtn) updCheckBtn.hidden = false;
if (storeUpdateNote) storeUpdateNote.hidden = true;
if (!u) {
updateBanner.classList.remove("show");
return;
@@ -1214,6 +1388,7 @@
function paint(state, opts) {
const syncForm = opts && opts.syncForm;
storePackaged = !!(state && state.store_packaged);
connected = !!state.connected;
dot.classList.toggle("on", connected);
hero.classList.toggle("on", connected);