Release 4.2.7+1: adaptive HiDPI window sizing (work-area DIPs + min size) and responsive CSS so the UI opens usable without stretch. Windows 4.2.7.1.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Navis
2026-08-02 12:00:10 +03:00
co-authored by Cursor
parent a1756715ef
commit fa3e11216e
15 changed files with 354 additions and 60 deletions
+99 -26
View File
@@ -149,7 +149,9 @@
[hidden] { display: none !important; }
html, body {
margin: 0;
width: 100%;
height: 100%;
height: 100dvh;
font-family: Manrope, sans-serif;
color: var(--ink);
background: var(--page-bg);
@@ -158,8 +160,12 @@
.app {
display: grid;
grid-template-columns: 216px 1fr;
grid-template-columns: 216px minmax(0, 1fr);
width: 100%;
height: 100%;
height: 100vh;
height: 100dvh;
min-height: 0;
}
/* ==== Sidebar ==== */
@@ -172,6 +178,8 @@
backdrop-filter: blur(18px) saturate(1.15);
border-right: 1px solid var(--line);
min-width: 0;
min-height: 0;
overflow: hidden;
}
.side .brand-row {
display: flex;
@@ -329,15 +337,26 @@
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
overflow: hidden;
height: 100%;
}
.pages {
flex: 1;
flex: 1 1 auto;
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
padding: 18px 20px 8px;
padding: 18px clamp(12px, 2.2vw, 20px) 8px;
-webkit-overflow-scrolling: touch;
}
/* Inactive pages must not paint (WebView2 overflow ghosts from hidden siblings). */
.page { display: none !important; max-width: 640px; margin: 0 auto; }
.page {
display: none !important;
width: 100%;
max-width: min(720px, 100%);
margin: 0 auto;
box-sizing: border-box;
}
.page.active { display: block !important; animation: rise .3s var(--ease) both; }
@keyframes rise {
from { opacity: 0; transform: translateY(10px); }
@@ -381,12 +400,13 @@
border-radius: var(--radius);
border: 1px solid var(--line);
background: var(--hero-bg);
padding: 26px 16px 20px;
padding: clamp(14px, 2.6vw, 26px) clamp(10px, 2vw, 16px) clamp(12px, 2vw, 20px);
margin-bottom: 14px;
text-align: center;
transition: border-color .25s, box-shadow .25s, background .25s;
min-height: 220px;
min-height: clamp(160px, 28vh, 220px);
box-sizing: border-box;
width: 100%;
}
.hero.on {
border-color: rgba(13,138,102,.35);
@@ -394,10 +414,11 @@
background: var(--hero-bg-on);
}
.power-btn {
width: 148px;
height: 148px;
min-width: 148px;
min-height: 148px;
--power-size: clamp(104px, 22vw, 148px);
width: var(--power-size);
height: var(--power-size);
min-width: var(--power-size);
min-height: var(--power-size);
border-radius: 50%;
border: 0;
cursor: pointer;
@@ -407,7 +428,7 @@
flex-direction: column;
align-items: center;
justify-content: center;
gap: 7px;
gap: clamp(4px, 1vw, 7px);
margin: 4px auto 14px;
transition: background .25s, color .25s, transform .12s, box-shadow .25s;
box-shadow: 0 14px 34px rgba(8, 145, 178, .3);
@@ -417,10 +438,13 @@
.power-btn:hover { transform: translateY(-2px); }
.power-btn:active { transform: none; }
.power-btn:disabled { opacity: .6; cursor: not-allowed; transform: none; }
.power-btn svg { width: 40px; height: 40px; }
.power-btn svg {
width: clamp(28px, 6vw, 40px);
height: clamp(28px, 6vw, 40px);
}
.power-btn .power-label {
font-family: Outfit, sans-serif;
font-size: .64rem;
font-size: clamp(.52rem, 1.5vw, .64rem);
font-weight: 800;
letter-spacing: .09em;
line-height: 1;
@@ -526,11 +550,12 @@
/* Current server card (home, bottom) */
.cur-server {
display: grid;
grid-template-columns: auto 1fr auto;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: 12px;
gap: clamp(8px, 1.5vw, 12px);
width: 100%;
padding: 12px 14px;
max-width: 100%;
padding: clamp(10px, 1.8vw, 12px) clamp(10px, 1.8vw, 14px);
border-radius: var(--radius);
border: 1px solid var(--line);
background: var(--surface-2);
@@ -540,6 +565,7 @@
text-align: left;
margin-bottom: 12px;
transition: border-color .15s, background .15s;
box-sizing: border-box;
}
.cur-server:hover { border-color: rgba(8,145,178,.35); }
.cur-server .flag {
@@ -1217,10 +1243,13 @@
border: 1px solid var(--line);
border-radius: var(--radius);
background: var(--row-bg);
padding: 12px 14px;
padding: clamp(10px, 1.8vw, 12px) clamp(10px, 1.8vw, 14px);
margin-bottom: 12px;
display: grid;
gap: 8px;
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
.traffic-row {
display: flex;
@@ -1256,7 +1285,10 @@
border: 1px solid var(--line);
border-radius: var(--radius);
background: var(--surface-2);
padding: 14px;
padding: clamp(10px, 1.8vw, 14px);
width: 100%;
max-width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 8px;
@@ -1371,12 +1403,53 @@
.modal h2 { font-family: Outfit, sans-serif; margin: 0 0 12px; font-size: 1.2rem; letter-spacing: -.02em; }
.modal .actions { margin-top: 12px; grid-template-columns: 1fr 1fr; }
/* Medium: keep labels, slightly narrower sidebar */
@media (max-width: 900px) and (min-width: 701px) {
.app { grid-template-columns: 176px minmax(0, 1fr); }
.side { padding: 14px 8px 10px; }
.nav-btn { gap: 10px; padding: 10px 12px; font-size: .84rem; }
.brand { font-size: 1.2rem; }
}
/* Narrow: icon sidebar — main pane stays usable (fluid power/cards) */
@media (max-width: 700px) {
.app { grid-template-columns: 64px 1fr; }
.side .brand-row { justify-content: center; padding-left: 0; padding-right: 0; }
.app { grid-template-columns: 64px minmax(0, 1fr); }
.side {
padding: 12px 6px 10px;
align-items: stretch;
}
.side .brand-row { justify-content: center; padding-left: 0; padding-right: 0; padding-bottom: 10px; }
.side .brand, .side .badge-ver, .nav-btn .nav-label, .side-foot .ver { display: none; }
.nav-btn { justify-content: center; padding: 12px 0; }
.side-foot { justify-content: center; }
.side-foot {
flex-direction: column;
justify-content: center;
align-items: center;
gap: 8px;
padding: 8px 0 0;
}
.side-foot-controls {
flex-direction: column;
align-items: center;
gap: 8px;
width: 100%;
}
.lang-seg {
flex-direction: column;
height: auto;
width: 40px;
}
.lang-seg button {
min-width: 0;
width: 100%;
padding: 7px 0;
}
.theme-toggle { width: 40px; height: 40px; }
.pages { padding: 12px 10px 8px; }
.page { max-width: 100%; }
.hero { min-height: 0; }
.power-btn { --power-size: clamp(96px, 28vw, 132px); }
.update-banner { margin-left: 10px; margin-right: 10px; }
details.panel .grid2 { grid-template-columns: 1fr; }
.tools { grid-template-columns: 1fr; }
}
@@ -1392,23 +1465,23 @@
<span class="badge-ver" id="badgeVer" hidden></span>
</div>
<button class="nav-btn active" type="button" data-page="guard">
<button class="nav-btn active" type="button" data-page="guard" title="Защита" data-i18n-title="nav_guard">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l7.5 3v5.2c0 4.6-3.1 8-7.5 9.8-4.4-1.8-7.5-5.2-7.5-9.8V6z"/><path d="M9 12l2.2 2.2L15.4 10"/></svg>
<span class="nav-label" data-i18n="nav_guard">Защита</span>
</button>
<button class="nav-btn" type="button" data-page="profiles">
<button class="nav-btn" type="button" data-page="profiles" title="Мои серверы" data-i18n-title="nav_profiles">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="4" width="17" height="6.5" rx="2"/><rect x="3.5" y="13.5" width="17" height="6.5" rx="2"/><path d="M7 7.2h.01M7 16.7h.01"/></svg>
<span class="nav-label" data-i18n="nav_profiles">Мои серверы</span>
</button>
<button class="nav-btn" type="button" data-page="settings">
<button class="nav-btn" type="button" data-page="settings" title="Настройки" data-i18n-title="nav_settings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3.2"/><path d="M19.4 13.5a7.6 7.6 0 0 0 0-3l2-1.5-2-3.4-2.3 1a7.7 7.7 0 0 0-2.6-1.5L14.2 2h-4l-.3 2.6a7.7 7.7 0 0 0-2.6 1.5l-2.3-1-2 3.4 2 1.5a7.6 7.6 0 0 0 0 3l-2 1.5 2 3.4 2.3-1a7.7 7.7 0 0 0 2.6 1.5l.3 2.6h4l.3-2.6a7.7 7.7 0 0 0 2.6-1.5l2.3 1 2-3.4z"/></svg>
<span class="nav-label" data-i18n="nav_settings">Настройки</span>
</button>
<button class="nav-btn" type="button" data-page="logs">
<button class="nav-btn" type="button" data-page="logs" title="Статистика" data-i18n-title="nav_stats">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M5 20V10M12 20V4M19 20v-7"/></svg>
<span class="nav-label" data-i18n="nav_stats">Статистика</span>
</button>
<button class="nav-btn" type="button" data-page="about">
<button class="nav-btn" type="button" data-page="about" title="Помощь" data-i18n-title="nav_help">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M9.6 9.2a2.5 2.5 0 1 1 3.5 2.3c-.8.4-1.1 1-1.1 1.8"/><path d="M12 16.6h.01"/></svg>
<span class="nav-label" data-i18n="nav_help">Помощь</span>
</button>
+9 -9
View File
@@ -17,8 +17,8 @@ import (
)
// CurrentVersion is the product/semver used for update eligibility (feed "version").
// Keep major.minor.patch only no build suffix here.
const CurrentVersion = "4.2.6"
// Keep major.minor.patch only — no build suffix here.
const CurrentVersion = "4.2.7"
// BuildNumber is the monotonic build within CurrentVersion (Windows FileVersion 4th part,
// macOS CFBundleVersion suffix, Android versionCode low digits). Bump on every release build.
@@ -75,7 +75,7 @@ type Status struct {
Checking bool `json:"checking,omitempty"`
Error string `json:"error,omitempty"`
ManifestURL string `json:"manifest_url"`
StoreManaged bool `json:"store_managed,omitempty"` // MSIX / Microsoft Store no in-app updates
StoreManaged bool `json:"store_managed,omitempty"` // MSIX / Microsoft Store — no in-app updates
}
// PlatformKey is "GOOS-GOARCH", e.g. windows-amd64, darwin-arm64.
@@ -86,7 +86,7 @@ func PlatformKey() string {
// AssetFor returns download URL + checksum for the given platform key.
//
// Rules (critical):
// - Only the asset for this OS/arch is considered never another OS.
// - Only the asset for this OS/arch is considered — never another OS.
// - If the feed has a newer version but no matching platform asset, ok=false
// (caller must treat Available as false).
// - Darwin may fall back to "darwin-universal" when the exact arch key is absent.
@@ -196,7 +196,7 @@ func Check(ctx context.Context, manifestURL string) (Status, error) {
if IsStorePackaged() {
st.StoreManaged = true
st.Available = false
st.Notes = "Обновления через Microsoft Store"
st.Notes = "Обновления через Microsoft Store"
return st, nil
}
@@ -212,7 +212,7 @@ func Check(ctx context.Context, manifestURL string) (Status, error) {
url, sha, ok := m.AssetFor(key)
if !ok {
// Higher feed version for another OS must NOT count as available here.
st.Error = fmt.Sprintf("нет сборки для %s", key)
st.Error = fmt.Sprintf("нет сборки для %s", key)
st.Available = false
return st, nil
}
@@ -254,7 +254,7 @@ func productParts(v string) [3]int {
func splitVer(v string) []int {
v = strings.TrimPrefix(strings.TrimSpace(v), "v")
// Strip build metadata: 2.7.2+1 / 2.7.2-beta compare product only.
// Strip build metadata: 2.7.2+1 / 2.7.2-beta в†’ compare product only.
if i := strings.IndexAny(v, "+-"); i >= 0 {
v = v[:i]
}
@@ -353,10 +353,10 @@ func prepareDownload(ctx context.Context, manifestURL string) (latest, url, sha,
if st.Error != "" {
return "", "", "", "", "", fmt.Errorf("%s", st.Error)
}
return "", "", "", "", "", fmt.Errorf("уже актуальная версия %s (канал %s)", DisplayVersion(), st.Latest)
return "", "", "", "", "", fmt.Errorf("уже актуальная версия %s (канал %s)", DisplayVersion(), st.Latest)
}
if !allowedDownloadURL(st.URL) {
return "", "", "", "", "", fmt.Errorf("URL обновления должен быть на git.de4ima.uk, git.evilfox.cc или files.de4ima.uk")
return "", "", "", "", "", fmt.Errorf("URL обновления должен быть на git.de4ima.uk, git.evilfox.cc или files.de4ima.uk")
}
exe, err := os.Executable()
if err != nil {