@co0ontty/wand 1.33.0 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server.js +95 -10
- package/dist/web-ui/content/scripts.js +184 -75
- package/dist/web-ui/content/styles.css +36 -63
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -86,11 +86,33 @@ function compareSemver(a, b) {
|
|
|
86
86
|
return -1;
|
|
87
87
|
if (!pa.pre && !pb.pre)
|
|
88
88
|
return 0;
|
|
89
|
-
// Both have prerelease:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
// Both have prerelease: 按 . 分段比较 (数字段数值比, 非数字段字典序), 贴近标准 semver,
|
|
90
|
+
// 避免跨月/跨年的 debug.MMDDHHMM 后缀因纯字典序而排反。
|
|
91
|
+
const segA = pa.pre.split(".");
|
|
92
|
+
const segB = pb.pre.split(".");
|
|
93
|
+
const segLen = Math.max(segA.length, segB.length);
|
|
94
|
+
for (let i = 0; i < segLen; i++) {
|
|
95
|
+
const sa = segA[i];
|
|
96
|
+
const sb = segB[i];
|
|
97
|
+
if (sa === undefined)
|
|
98
|
+
return -1; // 段少者更小
|
|
99
|
+
if (sb === undefined)
|
|
100
|
+
return 1;
|
|
101
|
+
const na = Number(sa);
|
|
102
|
+
const nb = Number(sb);
|
|
103
|
+
const aIsNum = sa !== "" && !Number.isNaN(na);
|
|
104
|
+
const bIsNum = sb !== "" && !Number.isNaN(nb);
|
|
105
|
+
if (aIsNum && bIsNum) {
|
|
106
|
+
if (na !== nb)
|
|
107
|
+
return na < nb ? -1 : 1;
|
|
108
|
+
}
|
|
109
|
+
else if (aIsNum !== bIsNum) {
|
|
110
|
+
return aIsNum ? -1 : 1; // 数字段 < 非数字段
|
|
111
|
+
}
|
|
112
|
+
else if (sa !== sb) {
|
|
113
|
+
return sa < sb ? -1 : 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
94
116
|
return 0;
|
|
95
117
|
}
|
|
96
118
|
let cachedGitHubApk = null;
|
|
@@ -119,6 +141,7 @@ async function fetchGitHubLatestApk(forceRefresh = false) {
|
|
|
119
141
|
downloadUrl: apkAsset.browser_download_url,
|
|
120
142
|
fileName: apkAsset.name,
|
|
121
143
|
size: apkAsset.size,
|
|
144
|
+
releaseNotes: release.body ? release.body.trim().slice(0, 500) : undefined,
|
|
122
145
|
};
|
|
123
146
|
gitHubApkCacheTs = now;
|
|
124
147
|
return cachedGitHubApk;
|
|
@@ -148,6 +171,7 @@ async function resolveLatestApkVersion(configDir, config) {
|
|
|
148
171
|
fileName: ghApk.fileName,
|
|
149
172
|
size: ghApk.size,
|
|
150
173
|
source: "github",
|
|
174
|
+
releaseNotes: ghApk.releaseNotes,
|
|
151
175
|
};
|
|
152
176
|
}
|
|
153
177
|
return null;
|
|
@@ -461,7 +485,24 @@ async function resolveAndroidApkAsset(configDir, config) {
|
|
|
461
485
|
fileStat,
|
|
462
486
|
};
|
|
463
487
|
}));
|
|
464
|
-
|
|
488
|
+
// 按语义版本选"最新", 而非修改时间 —— cp/rsync/解压/checkout 都可能让低版本号文件的
|
|
489
|
+
// mtime 更新, 用 mtime 会把旧版本号当成 latest 上报。版本相同或都无版本号时退回 mtime。
|
|
490
|
+
candidates.sort((a, b) => {
|
|
491
|
+
const va = extractAndroidApkVersion(a.entry.name);
|
|
492
|
+
const vb = extractAndroidApkVersion(b.entry.name);
|
|
493
|
+
if (va && vb) {
|
|
494
|
+
const cmp = compareSemver(vb, va);
|
|
495
|
+
if (cmp !== 0)
|
|
496
|
+
return cmp;
|
|
497
|
+
}
|
|
498
|
+
else if (va && !vb) {
|
|
499
|
+
return -1;
|
|
500
|
+
}
|
|
501
|
+
else if (!va && vb) {
|
|
502
|
+
return 1;
|
|
503
|
+
}
|
|
504
|
+
return b.fileStat.mtimeMs - a.fileStat.mtimeMs;
|
|
505
|
+
});
|
|
465
506
|
const selected = candidates[0];
|
|
466
507
|
return {
|
|
467
508
|
fileName: selected.entry.name,
|
|
@@ -936,22 +977,66 @@ export async function startServer(config, configPath) {
|
|
|
936
977
|
fileName: updateAvailable ? latest.fileName : null,
|
|
937
978
|
size: updateAvailable ? latest.size : null,
|
|
938
979
|
source: latest.source,
|
|
980
|
+
releaseNotes: updateAvailable ? (latest.releaseNotes ?? null) : null,
|
|
939
981
|
});
|
|
940
982
|
});
|
|
941
|
-
app.get("/android/download", async (
|
|
942
|
-
const androidApk = await resolveAndroidApkAsset(configDir, config);
|
|
983
|
+
app.get("/android/download", async (req, res) => {
|
|
943
984
|
if (config.android?.enabled !== true) {
|
|
944
985
|
res.status(404).json({ error: "Android APK 下载未启用。" });
|
|
945
986
|
return;
|
|
946
987
|
}
|
|
988
|
+
const androidApk = await resolveAndroidApkAsset(configDir, config);
|
|
947
989
|
if (!androidApk) {
|
|
948
990
|
res.status(404).json({ error: "当前没有可下载的 APK 文件。" });
|
|
949
991
|
return;
|
|
950
992
|
}
|
|
993
|
+
const total = androidApk.size;
|
|
951
994
|
res.setHeader("Content-Type", "application/vnd.android.package-archive");
|
|
952
|
-
res.setHeader("Content-Length", String(androidApk.size));
|
|
953
995
|
res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(androidApk.fileName)}"`);
|
|
954
|
-
|
|
996
|
+
// 声明支持断点续传 — 弱网/移动网络下中断后客户端可带 Range 续传, 而非从头重下。
|
|
997
|
+
res.setHeader("Accept-Ranges", "bytes");
|
|
998
|
+
// 解析 Range: bytes=start-end (含后缀范围 bytes=-N)。命中则返回 206 + 局部内容。
|
|
999
|
+
let start = 0;
|
|
1000
|
+
let end = total - 1;
|
|
1001
|
+
let isPartial = false;
|
|
1002
|
+
const rangeHeader = req.headers.range;
|
|
1003
|
+
if (rangeHeader) {
|
|
1004
|
+
const match = /^bytes=(\d*)-(\d*)$/.exec(rangeHeader.trim());
|
|
1005
|
+
if (match && (match[1] !== "" || match[2] !== "")) {
|
|
1006
|
+
if (match[1] === "") {
|
|
1007
|
+
// bytes=-N: 最后 N 字节
|
|
1008
|
+
start = Math.max(0, total - Number(match[2]));
|
|
1009
|
+
end = total - 1;
|
|
1010
|
+
}
|
|
1011
|
+
else {
|
|
1012
|
+
start = Number(match[1]);
|
|
1013
|
+
end = match[2] === "" ? total - 1 : Math.min(Number(match[2]), total - 1);
|
|
1014
|
+
}
|
|
1015
|
+
isPartial = true;
|
|
1016
|
+
if (start > end || start < 0 || start >= total) {
|
|
1017
|
+
res.status(416);
|
|
1018
|
+
res.setHeader("Content-Range", `bytes */${total}`);
|
|
1019
|
+
res.end();
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (isPartial) {
|
|
1025
|
+
res.status(206);
|
|
1026
|
+
res.setHeader("Content-Range", `bytes ${start}-${end}/${total}`);
|
|
1027
|
+
}
|
|
1028
|
+
res.setHeader("Content-Length", String(end - start + 1));
|
|
1029
|
+
const stream = createReadStream(androidApk.filePath, { start, end });
|
|
1030
|
+
stream.on("error", (err) => {
|
|
1031
|
+
// 文件在 stat 之后被删/读错误时, 让客户端尽快感知断流, 而不是等满读超时。
|
|
1032
|
+
if (!res.headersSent) {
|
|
1033
|
+
res.status(500).json({ error: getErrorMessage(err, "读取 APK 文件失败。") });
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
res.destroy();
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
stream.pipe(res);
|
|
955
1040
|
});
|
|
956
1041
|
// ── macOS DMG update & download (no auth required) ──
|
|
957
1042
|
app.get("/api/macos-dmg-update", async (req, res) => {
|
|
@@ -1390,7 +1390,7 @@
|
|
|
1390
1390
|
checkDmgAutoUpdate();
|
|
1391
1391
|
}
|
|
1392
1392
|
if (state.claudeHistoryExpanded && !state.claudeHistoryLoaded) {
|
|
1393
|
-
|
|
1393
|
+
ensureClaudeHistoryLoaded();
|
|
1394
1394
|
}
|
|
1395
1395
|
});
|
|
1396
1396
|
})
|
|
@@ -1462,8 +1462,11 @@
|
|
|
1462
1462
|
// Suppress CSS transitions during initial DOM build
|
|
1463
1463
|
document.documentElement.classList.add("no-transition");
|
|
1464
1464
|
|
|
1465
|
-
// Apply persisted pin state before rendering
|
|
1466
|
-
|
|
1465
|
+
// Apply persisted pin state before rendering.
|
|
1466
|
+
// 窄条(collapsed)形态不靠 .open 显示,靠 .pinned.collapsed 的 width:56px
|
|
1467
|
+
// 常驻;此时强制 sessionsDrawerOpen=true 会与 toggleSidebarCollapsed 里设的
|
|
1468
|
+
// false 打架,并在手机端误触发背景遮罩。窄条态下不强制 open。
|
|
1469
|
+
if (state.sidebarPinned && !state.sidebarCollapsed && !isMobileLayout()) {
|
|
1467
1470
|
state.sessionsDrawerOpen = true;
|
|
1468
1471
|
}
|
|
1469
1472
|
app.innerHTML = isLoggedIn ? renderAppShell() : renderLogin();
|
|
@@ -2912,7 +2915,7 @@
|
|
|
2912
2915
|
'<div id="android-auto-update-row" class="settings-toggle-row hidden">' +
|
|
2913
2916
|
'<div class="settings-toggle-text">' +
|
|
2914
2917
|
'<span class="settings-toggle-title">自动更新</span>' +
|
|
2915
|
-
'<span class="settings-toggle-desc" id="android-auto-update-hint">检测到新版 APK
|
|
2918
|
+
'<span class="settings-toggle-desc" id="android-auto-update-hint">检测到新版 APK 时自动拉起下载,安装仍需在系统中确认。</span>' +
|
|
2916
2919
|
'</div>' +
|
|
2917
2920
|
'<label class="settings-switch">' +
|
|
2918
2921
|
'<input type="checkbox" id="auto-update-apk-toggle" class="switch-toggle">' +
|
|
@@ -3558,6 +3561,11 @@
|
|
|
3558
3561
|
|
|
3559
3562
|
function toggleManageMode(force) {
|
|
3560
3563
|
state.sessionsManageMode = typeof force === "boolean" ? force : !state.sessionsManageMode;
|
|
3564
|
+
if (state.sessionsManageMode && !state.claudeHistoryLoaded) {
|
|
3565
|
+
// 进入管理模式即后台补齐 Claude 历史,让「已选 N」「全选」计数从一开始
|
|
3566
|
+
// 就覆盖全部历史,而不是只统计已加载的那部分。
|
|
3567
|
+
ensureClaudeHistoryLoaded().then(updateSessionsList);
|
|
3568
|
+
}
|
|
3561
3569
|
if (!state.sessionsManageMode) {
|
|
3562
3570
|
clearManageSelections();
|
|
3563
3571
|
closeSwipedItem();
|
|
@@ -3574,6 +3582,16 @@
|
|
|
3574
3582
|
}
|
|
3575
3583
|
|
|
3576
3584
|
function selectAllVisibleItems() {
|
|
3585
|
+
// 全选语义 = 选中所有可管理项(会话 + 全部 Claude 历史)。历史在登录后
|
|
3586
|
+
// 异步扫描,若用户在扫描完成前点「全选」,state.claudeHistory 仍为空会漏选,
|
|
3587
|
+
// 删除时表现为"只删了已加载的,跨目录/未扫完的历史还在"。这里先确保历史
|
|
3588
|
+
// 加载完成再全选。
|
|
3589
|
+
if (!state.claudeHistoryLoaded) {
|
|
3590
|
+
ensureClaudeHistoryLoaded().then(selectAllVisibleItems);
|
|
3591
|
+
return;
|
|
3592
|
+
}
|
|
3593
|
+
// 展开 Claude 历史分组,让用户能直观看到这些历史项也被选中了。
|
|
3594
|
+
state.claudeHistoryExpanded = true;
|
|
3577
3595
|
var nextSessionIds = {};
|
|
3578
3596
|
getSelectableSessions().forEach(function(session) {
|
|
3579
3597
|
nextSessionIds[session.id] = true;
|
|
@@ -3782,6 +3800,21 @@
|
|
|
3782
3800
|
});
|
|
3783
3801
|
}
|
|
3784
3802
|
|
|
3803
|
+
// 去重包装:登录后历史会异步扫描,多个入口(管理模式、全选、展开分组)
|
|
3804
|
+
// 可能同时想确保历史就绪。共享同一个 in-flight Promise,避免重复 fetch,
|
|
3805
|
+
// 且在已加载时立即 resolve。
|
|
3806
|
+
var _claudeHistoryLoadingPromise = null;
|
|
3807
|
+
function ensureClaudeHistoryLoaded() {
|
|
3808
|
+
if (state.claudeHistoryLoaded) return Promise.resolve();
|
|
3809
|
+
if (_claudeHistoryLoadingPromise) return _claudeHistoryLoadingPromise;
|
|
3810
|
+
_claudeHistoryLoadingPromise = loadClaudeHistory().then(function() {
|
|
3811
|
+
_claudeHistoryLoadingPromise = null;
|
|
3812
|
+
}, function() {
|
|
3813
|
+
_claudeHistoryLoadingPromise = null;
|
|
3814
|
+
});
|
|
3815
|
+
return _claudeHistoryLoadingPromise;
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3785
3818
|
function isMobileLayout() {
|
|
3786
3819
|
return window.innerWidth <= 768;
|
|
3787
3820
|
}
|
|
@@ -5892,11 +5925,17 @@
|
|
|
5892
5925
|
if (sidebarMoreBtn && sidebarOverflow) {
|
|
5893
5926
|
sidebarMoreBtn.addEventListener("click", function(e) {
|
|
5894
5927
|
e.stopPropagation();
|
|
5895
|
-
sidebarOverflow.classList.
|
|
5928
|
+
var willOpen = !sidebarOverflow.classList.contains("open");
|
|
5929
|
+
sidebarOverflow.classList.toggle("open", willOpen);
|
|
5930
|
+
if (willOpen) positionSidebarOverflowMenu(sidebarOverflow);
|
|
5896
5931
|
});
|
|
5897
5932
|
document.addEventListener("click", function() {
|
|
5898
5933
|
sidebarOverflow.classList.remove("open");
|
|
5899
5934
|
});
|
|
5935
|
+
// 视口尺寸变化时关闭,避免 clamp 后的定位与新尺寸不符。
|
|
5936
|
+
window.addEventListener("resize", function() {
|
|
5937
|
+
sidebarOverflow.classList.remove("open");
|
|
5938
|
+
});
|
|
5900
5939
|
}
|
|
5901
5940
|
var homeBtn = document.getElementById("sidebar-home-btn");
|
|
5902
5941
|
if (homeBtn) homeBtn.addEventListener("click", function() {
|
|
@@ -6964,7 +7003,7 @@
|
|
|
6964
7003
|
event.stopPropagation();
|
|
6965
7004
|
state.claudeHistoryExpanded = !state.claudeHistoryExpanded;
|
|
6966
7005
|
if (state.claudeHistoryExpanded && !state.claudeHistoryLoaded) {
|
|
6967
|
-
|
|
7006
|
+
ensureClaudeHistoryLoaded();
|
|
6968
7007
|
}
|
|
6969
7008
|
updateSessionsList();
|
|
6970
7009
|
return;
|
|
@@ -9588,10 +9627,55 @@
|
|
|
9588
9627
|
// 桌面端展开窄条 → 300px 全栏常驻。
|
|
9589
9628
|
state.sessionsDrawerOpen = true;
|
|
9590
9629
|
}
|
|
9591
|
-
render()
|
|
9630
|
+
// 轻量更新而非全量 render():render() 会 teardown 并重建整个终端 DOM,
|
|
9631
|
+
// 导致收窄/展开时终端闪烁、丢失滚动与渲染状态。这里只切布局 class
|
|
9632
|
+
// (宽度 56↔300 走 CSS width transition)、重渲侧栏列表内容、刷新
|
|
9633
|
+
// 收窄按钮自身的图标/文案,终端区保持不动。
|
|
9634
|
+
updateLayoutState();
|
|
9635
|
+
updateSessionsList();
|
|
9636
|
+
updateSidebarCollapseButton();
|
|
9637
|
+
hideCollapsedTileBubble();
|
|
9592
9638
|
scheduleTerminalRefitAfterPaddingTransition();
|
|
9593
9639
|
}
|
|
9594
9640
|
|
|
9641
|
+
// 收窄按钮的图标/title/状态随 collapsed 切换。抽出来给轻量更新路径用,
|
|
9642
|
+
// 避免为了换一个箭头方向就走全量 render()。
|
|
9643
|
+
function updateSidebarCollapseButton() {
|
|
9644
|
+
var btn = document.getElementById("sidebar-collapse-btn");
|
|
9645
|
+
if (!btn) return;
|
|
9646
|
+
var isCollapsed = !!state.sidebarPinned && !!state.sidebarCollapsed;
|
|
9647
|
+
btn.classList.toggle("collapsed", isCollapsed);
|
|
9648
|
+
btn.title = isCollapsed ? "展开侧栏" : "收起为窄条";
|
|
9649
|
+
btn.setAttribute("aria-label", isCollapsed ? "展开侧栏" : "收起为窄条");
|
|
9650
|
+
btn.innerHTML = isCollapsed
|
|
9651
|
+
? '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="10 6 16 12 10 18"/><line x1="20" y1="5" x2="20" y2="19"/></svg>'
|
|
9652
|
+
: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="14 6 8 12 14 18"/><line x1="4" y1="5" x2="4" y2="19"/></svg>';
|
|
9653
|
+
}
|
|
9654
|
+
|
|
9655
|
+
// 「更多操作」下拉默认 right:0 贴 more 按钮右沿向左展开。手机窄屏下这条会把
|
|
9656
|
+
// 菜单左缘顶出屏幕外。打开时按视口边界 clamp:先保持 CSS 默认右对齐,仅当
|
|
9657
|
+
// 真的越界才改写 left/right 把菜单拉回视口内(留 8px 边距)。
|
|
9658
|
+
function positionSidebarOverflowMenu(menu) {
|
|
9659
|
+
if (!menu) return;
|
|
9660
|
+
menu.style.left = "";
|
|
9661
|
+
menu.style.right = "";
|
|
9662
|
+
var parent = menu.offsetParent || menu.parentElement;
|
|
9663
|
+
if (!parent) return;
|
|
9664
|
+
var margin = 8;
|
|
9665
|
+
var parentRect = parent.getBoundingClientRect();
|
|
9666
|
+
var rect = menu.getBoundingClientRect();
|
|
9667
|
+
var vw = window.innerWidth;
|
|
9668
|
+
if (rect.left < margin) {
|
|
9669
|
+
// 左缘越界:改用 left 定位,把左缘顶到视口左 margin。
|
|
9670
|
+
menu.style.right = "auto";
|
|
9671
|
+
menu.style.left = (margin - parentRect.left) + "px";
|
|
9672
|
+
} else if (rect.right > vw - margin) {
|
|
9673
|
+
// 右缘越界:拉回视口右 margin(桌面右对齐时几乎不会触发)。
|
|
9674
|
+
menu.style.left = "auto";
|
|
9675
|
+
menu.style.right = (parentRect.right - (vw - margin)) + "px";
|
|
9676
|
+
}
|
|
9677
|
+
}
|
|
9678
|
+
|
|
9595
9679
|
|
|
9596
9680
|
// Store last focused element for focus trap
|
|
9597
9681
|
var lastFocusedElement = null;
|
|
@@ -10217,9 +10301,81 @@
|
|
|
10217
10301
|
var autoUpdateWebToggle = document.getElementById("auto-update-web-toggle");
|
|
10218
10302
|
if (autoUpdateWebToggle) autoUpdateWebToggle.checked = !!autoUpdate.web;
|
|
10219
10303
|
var autoUpdateApkToggle = document.getElementById("auto-update-apk-toggle");
|
|
10220
|
-
|
|
10304
|
+
// 自动更新开关只对 APK 壳生效, 浏览器里不绑定状态(该行也保持隐藏), 避免静默写一个看不见的控件。
|
|
10305
|
+
if (autoUpdateApkToggle) autoUpdateApkToggle.checked = !!_apkVersion && !!autoUpdate.apk;
|
|
10221
10306
|
var autoUpdateDmgToggle = document.getElementById("auto-update-dmg-toggle");
|
|
10222
|
-
if (autoUpdateDmgToggle) autoUpdateDmgToggle.checked = !!autoUpdate.dmg;
|
|
10307
|
+
if (autoUpdateDmgToggle) autoUpdateDmgToggle.checked = !!_macAppVersion && !!autoUpdate.dmg;
|
|
10308
|
+
|
|
10309
|
+
// ── 原生包下载 helper(APK / DMG 共用)──
|
|
10310
|
+
function safeNotify(msg, type) {
|
|
10311
|
+
if (typeof window.wandAlert === "function") {
|
|
10312
|
+
window.wandAlert(msg, { type: type === "error" ? "danger" : "info" });
|
|
10313
|
+
} else if (typeof showToast === "function") {
|
|
10314
|
+
showToast(msg, type === "error" ? "error" : "info");
|
|
10315
|
+
} else if (type === "error") {
|
|
10316
|
+
alert(msg);
|
|
10317
|
+
}
|
|
10318
|
+
}
|
|
10319
|
+
// 同源本地下载:先 HEAD 探测状态码, 避免 window.open("_self") 把整页导航成裸 JSON;
|
|
10320
|
+
// 命中则用隐藏 <a download> 触发下载, 并给出明确反馈。
|
|
10321
|
+
function triggerLocalDownload(url, fileName, btn) {
|
|
10322
|
+
var original = btn ? btn.textContent : "";
|
|
10323
|
+
if (btn) { btn.disabled = true; btn.textContent = "下载中…"; }
|
|
10324
|
+
function restore() { if (btn) { btn.disabled = false; btn.textContent = original; } }
|
|
10325
|
+
fetch(url, { method: "HEAD", credentials: "same-origin" })
|
|
10326
|
+
.then(function(resp) {
|
|
10327
|
+
if (!resp.ok) {
|
|
10328
|
+
safeNotify(resp.status === 404 ? "下载未启用或文件已移除" : ("下载失败 (HTTP " + resp.status + ")"), "error");
|
|
10329
|
+
restore();
|
|
10330
|
+
return;
|
|
10331
|
+
}
|
|
10332
|
+
var a = document.createElement("a");
|
|
10333
|
+
a.href = url;
|
|
10334
|
+
a.download = fileName || "";
|
|
10335
|
+
document.body.appendChild(a);
|
|
10336
|
+
a.click();
|
|
10337
|
+
document.body.removeChild(a);
|
|
10338
|
+
safeNotify("已开始下载,请在通知栏/下载管理中查看", "info");
|
|
10339
|
+
restore();
|
|
10340
|
+
})
|
|
10341
|
+
.catch(function(err) {
|
|
10342
|
+
safeNotify("下载失败: " + (err && err.message ? err.message : err), "error");
|
|
10343
|
+
restore();
|
|
10344
|
+
});
|
|
10345
|
+
}
|
|
10346
|
+
|
|
10347
|
+
// 设置页版本比较 (仅主版本三段, 预发布忽略) — 判断该升级 / 已最新 / 更旧。
|
|
10348
|
+
function compareVer(a, b) {
|
|
10349
|
+
function parse(v) {
|
|
10350
|
+
return String(v || "").replace(/^v/, "").split("-")[0].split(".").map(function(n) { return parseInt(n, 10) || 0; });
|
|
10351
|
+
}
|
|
10352
|
+
var pa = parse(a), pb = parse(b);
|
|
10353
|
+
for (var i = 0; i < 3; i++) {
|
|
10354
|
+
var d = (pa[i] || 0) - (pb[i] || 0);
|
|
10355
|
+
if (d !== 0) return d > 0 ? 1 : -1;
|
|
10356
|
+
}
|
|
10357
|
+
return 0;
|
|
10358
|
+
}
|
|
10359
|
+
// 壳内按钮: 据版本比较结果决定文案/可点性, 避免线上比已装旧时仍诱导"下载安装"。
|
|
10360
|
+
function applyApkButton(btn, cmp, url, fileName, source) {
|
|
10361
|
+
btn.classList.remove("hidden");
|
|
10362
|
+
btn.disabled = false;
|
|
10363
|
+
if (cmp > 0) {
|
|
10364
|
+
btn.textContent = "升级";
|
|
10365
|
+
} else if (cmp === 0) {
|
|
10366
|
+
btn.textContent = "已是最新";
|
|
10367
|
+
btn.disabled = true;
|
|
10368
|
+
} else {
|
|
10369
|
+
btn.textContent = "重新安装";
|
|
10370
|
+
}
|
|
10371
|
+
btn.onclick = btn.disabled ? null : function() {
|
|
10372
|
+
try {
|
|
10373
|
+
WandNative.downloadUpdate(url, fileName, source);
|
|
10374
|
+
} catch (e) {
|
|
10375
|
+
safeNotify("调用下载失败: " + (e && e.message ? e.message : e), "error");
|
|
10376
|
+
}
|
|
10377
|
+
};
|
|
10378
|
+
}
|
|
10223
10379
|
|
|
10224
10380
|
// ── Android APK version display ──
|
|
10225
10381
|
var apkSection = document.getElementById("android-apk-section");
|
|
@@ -10234,7 +10390,9 @@
|
|
|
10234
10390
|
var apkMessageEl = document.getElementById("android-apk-message");
|
|
10235
10391
|
var androidApk = data.androidApk || {};
|
|
10236
10392
|
var isInApk = !!_apkVersion;
|
|
10237
|
-
|
|
10393
|
+
// 浏览器模式下若管理员未启用 Android 分发(enabled===false), 整段隐藏; 壳内保留以便自升级。
|
|
10394
|
+
var apkEnabled = androidApk.enabled !== false;
|
|
10395
|
+
var hasApkInfo = isInApk || (apkEnabled && (!!androidApk.github || !!androidApk.local));
|
|
10238
10396
|
if (apkSection) {
|
|
10239
10397
|
if (hasApkInfo) apkSection.classList.remove("hidden");
|
|
10240
10398
|
else apkSection.classList.add("hidden");
|
|
@@ -10253,21 +10411,8 @@
|
|
|
10253
10411
|
apkGithubEl.textContent = ghLabel;
|
|
10254
10412
|
apkGithubRow.classList.remove("hidden");
|
|
10255
10413
|
if (apkGithubBtn) {
|
|
10256
|
-
|
|
10257
|
-
apkGithubBtn.
|
|
10258
|
-
apkGithubBtn.onclick = function() {
|
|
10259
|
-
try {
|
|
10260
|
-
WandNative.downloadUpdate(androidApk.github.downloadUrl, androidApk.github.fileName || "wand-update.apk", "github");
|
|
10261
|
-
} catch (e) {
|
|
10262
|
-
if (typeof window.wandAlert === "function") {
|
|
10263
|
-
window.wandAlert("调用下载失败: " + e.message, { type: "danger", title: "下载失败" });
|
|
10264
|
-
} else if (typeof showToast === "function") {
|
|
10265
|
-
showToast("调用下载失败: " + e.message, "error");
|
|
10266
|
-
} else {
|
|
10267
|
-
alert("调用下载失败: " + e.message);
|
|
10268
|
-
}
|
|
10269
|
-
}
|
|
10270
|
-
};
|
|
10414
|
+
var ghCmp = androidApk.github.version ? compareVer(androidApk.github.version, _apkVersion) : 1;
|
|
10415
|
+
applyApkButton(apkGithubBtn, ghCmp, androidApk.github.downloadUrl, androidApk.github.fileName || "wand-update.apk", "github");
|
|
10271
10416
|
}
|
|
10272
10417
|
}
|
|
10273
10418
|
// 本地版本
|
|
@@ -10277,21 +10422,8 @@
|
|
|
10277
10422
|
apkLocalEl.textContent = lcLabel;
|
|
10278
10423
|
apkLocalRow.classList.remove("hidden");
|
|
10279
10424
|
if (apkLocalBtn) {
|
|
10280
|
-
|
|
10281
|
-
apkLocalBtn.
|
|
10282
|
-
apkLocalBtn.onclick = function() {
|
|
10283
|
-
try {
|
|
10284
|
-
WandNative.downloadUpdate(androidApk.local.downloadUrl, androidApk.local.fileName || "wand-update.apk", "local");
|
|
10285
|
-
} catch (e) {
|
|
10286
|
-
if (typeof window.wandAlert === "function") {
|
|
10287
|
-
window.wandAlert("调用下载失败: " + e.message, { type: "danger", title: "下载失败" });
|
|
10288
|
-
} else if (typeof showToast === "function") {
|
|
10289
|
-
showToast("调用下载失败: " + e.message, "error");
|
|
10290
|
-
} else {
|
|
10291
|
-
alert("调用下载失败: " + e.message);
|
|
10292
|
-
}
|
|
10293
|
-
}
|
|
10294
|
-
};
|
|
10425
|
+
var lcCmp = androidApk.local.version ? compareVer(androidApk.local.version, _apkVersion) : 1;
|
|
10426
|
+
applyApkButton(apkLocalBtn, lcCmp, androidApk.local.downloadUrl, androidApk.local.fileName || "wand-update.apk", "local");
|
|
10295
10427
|
}
|
|
10296
10428
|
}
|
|
10297
10429
|
// 都没有时
|
|
@@ -10316,6 +10448,7 @@
|
|
|
10316
10448
|
apkGithubBtn.classList.remove("hidden");
|
|
10317
10449
|
apkGithubBtn.onclick = function() {
|
|
10318
10450
|
window.open(androidApk.github.downloadUrl, "_blank");
|
|
10451
|
+
safeNotify("正在打开下载页…", "info");
|
|
10319
10452
|
};
|
|
10320
10453
|
}
|
|
10321
10454
|
}
|
|
@@ -10329,12 +10462,12 @@
|
|
|
10329
10462
|
apkLocalBtn.textContent = "下载";
|
|
10330
10463
|
apkLocalBtn.classList.remove("hidden");
|
|
10331
10464
|
apkLocalBtn.onclick = function() {
|
|
10332
|
-
|
|
10465
|
+
triggerLocalDownload(androidApk.local.downloadUrl, androidApk.local.fileName || "wand-update.apk", apkLocalBtn);
|
|
10333
10466
|
};
|
|
10334
10467
|
}
|
|
10335
10468
|
}
|
|
10336
10469
|
if (!androidApk.github && !androidApk.local && apkMessageEl) {
|
|
10337
|
-
apkMessageEl.textContent = "
|
|
10470
|
+
apkMessageEl.textContent = apkEnabled ? "在线版本暂时获取失败,可稍后重试" : "Android 下载未在服务端启用";
|
|
10338
10471
|
apkMessageEl.classList.remove("hidden");
|
|
10339
10472
|
}
|
|
10340
10473
|
}
|
|
@@ -10352,7 +10485,8 @@
|
|
|
10352
10485
|
var dmgMessageEl = document.getElementById("macos-dmg-message");
|
|
10353
10486
|
var macosDmg = data.macosDmg || {};
|
|
10354
10487
|
var isInMacApp = !!_macAppVersion;
|
|
10355
|
-
var
|
|
10488
|
+
var dmgEnabled = macosDmg.enabled !== false;
|
|
10489
|
+
var hasDmgInfo = isInMacApp || (dmgEnabled && (!!macosDmg.github || !!macosDmg.local));
|
|
10356
10490
|
if (dmgSection) {
|
|
10357
10491
|
if (hasDmgInfo) dmgSection.classList.remove("hidden");
|
|
10358
10492
|
else dmgSection.classList.add("hidden");
|
|
@@ -10370,21 +10504,8 @@
|
|
|
10370
10504
|
dmgGithubEl.textContent = dghLabel;
|
|
10371
10505
|
dmgGithubRow.classList.remove("hidden");
|
|
10372
10506
|
if (dmgGithubBtn) {
|
|
10373
|
-
|
|
10374
|
-
dmgGithubBtn.
|
|
10375
|
-
dmgGithubBtn.onclick = function() {
|
|
10376
|
-
try {
|
|
10377
|
-
WandNative.downloadUpdate(macosDmg.github.downloadUrl, macosDmg.github.fileName || "wand-update.dmg", "github");
|
|
10378
|
-
} catch (e) {
|
|
10379
|
-
if (typeof window.wandAlert === "function") {
|
|
10380
|
-
window.wandAlert("调用下载失败: " + e.message, { type: "danger", title: "下载失败" });
|
|
10381
|
-
} else if (typeof showToast === "function") {
|
|
10382
|
-
showToast("调用下载失败: " + e.message, "error");
|
|
10383
|
-
} else {
|
|
10384
|
-
alert("调用下载失败: " + e.message);
|
|
10385
|
-
}
|
|
10386
|
-
}
|
|
10387
|
-
};
|
|
10507
|
+
var dghCmp = macosDmg.github.version ? compareVer(macosDmg.github.version, _macAppVersion) : 1;
|
|
10508
|
+
applyApkButton(dmgGithubBtn, dghCmp, macosDmg.github.downloadUrl, macosDmg.github.fileName || "wand-update.dmg", "github");
|
|
10388
10509
|
}
|
|
10389
10510
|
}
|
|
10390
10511
|
if (macosDmg.local && dmgLocalRow && dmgLocalEl) {
|
|
@@ -10393,21 +10514,8 @@
|
|
|
10393
10514
|
dmgLocalEl.textContent = dlcLabel;
|
|
10394
10515
|
dmgLocalRow.classList.remove("hidden");
|
|
10395
10516
|
if (dmgLocalBtn) {
|
|
10396
|
-
|
|
10397
|
-
dmgLocalBtn.
|
|
10398
|
-
dmgLocalBtn.onclick = function() {
|
|
10399
|
-
try {
|
|
10400
|
-
WandNative.downloadUpdate(macosDmg.local.downloadUrl, macosDmg.local.fileName || "wand-update.dmg", "local");
|
|
10401
|
-
} catch (e) {
|
|
10402
|
-
if (typeof window.wandAlert === "function") {
|
|
10403
|
-
window.wandAlert("调用下载失败: " + e.message, { type: "danger", title: "下载失败" });
|
|
10404
|
-
} else if (typeof showToast === "function") {
|
|
10405
|
-
showToast("调用下载失败: " + e.message, "error");
|
|
10406
|
-
} else {
|
|
10407
|
-
alert("调用下载失败: " + e.message);
|
|
10408
|
-
}
|
|
10409
|
-
}
|
|
10410
|
-
};
|
|
10517
|
+
var dlcCmp = macosDmg.local.version ? compareVer(macosDmg.local.version, _macAppVersion) : 1;
|
|
10518
|
+
applyApkButton(dmgLocalBtn, dlcCmp, macosDmg.local.downloadUrl, macosDmg.local.fileName || "wand-update.dmg", "local");
|
|
10411
10519
|
}
|
|
10412
10520
|
}
|
|
10413
10521
|
if (!macosDmg.github && !macosDmg.local && dmgMessageEl) {
|
|
@@ -10430,6 +10538,7 @@
|
|
|
10430
10538
|
dmgGithubBtn.classList.remove("hidden");
|
|
10431
10539
|
dmgGithubBtn.onclick = function() {
|
|
10432
10540
|
window.open(macosDmg.github.downloadUrl, "_blank");
|
|
10541
|
+
safeNotify("正在打开下载页…", "info");
|
|
10433
10542
|
};
|
|
10434
10543
|
}
|
|
10435
10544
|
}
|
|
@@ -10442,12 +10551,12 @@
|
|
|
10442
10551
|
dmgLocalBtn.textContent = "下载";
|
|
10443
10552
|
dmgLocalBtn.classList.remove("hidden");
|
|
10444
10553
|
dmgLocalBtn.onclick = function() {
|
|
10445
|
-
|
|
10554
|
+
triggerLocalDownload(macosDmg.local.downloadUrl, macosDmg.local.fileName || "wand-update.dmg", dmgLocalBtn);
|
|
10446
10555
|
};
|
|
10447
10556
|
}
|
|
10448
10557
|
}
|
|
10449
10558
|
if (!macosDmg.github && !macosDmg.local && dmgMessageEl) {
|
|
10450
|
-
dmgMessageEl.textContent = "
|
|
10559
|
+
dmgMessageEl.textContent = dmgEnabled ? "在线版本暂时获取失败,可稍后重试" : "macOS 下载未在服务端启用";
|
|
10451
10560
|
dmgMessageEl.classList.remove("hidden");
|
|
10452
10561
|
}
|
|
10453
10562
|
}
|
|
@@ -574,9 +574,8 @@
|
|
|
574
574
|
justify-content: center;
|
|
575
575
|
padding: 0;
|
|
576
576
|
box-shadow:
|
|
577
|
-
0 1px
|
|
578
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.28)
|
|
579
|
-
inset 0 -1px 0 rgba(0, 0, 0, 0.08);
|
|
577
|
+
0 1px 2px rgba(184, 92, 55, 0.22),
|
|
578
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.28);
|
|
580
579
|
transition:
|
|
581
580
|
filter var(--transition-fast),
|
|
582
581
|
box-shadow 0.22s ease,
|
|
@@ -586,40 +585,31 @@
|
|
|
586
585
|
filter: brightness(1.06);
|
|
587
586
|
transform: translateY(-1px);
|
|
588
587
|
box-shadow:
|
|
589
|
-
0 4px
|
|
590
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.
|
|
591
|
-
inset 0 -1px 0 rgba(0, 0, 0, 0.08);
|
|
588
|
+
0 4px 10px -3px rgba(184, 92, 55, 0.36),
|
|
589
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.32);
|
|
592
590
|
}
|
|
593
591
|
/* History tile — softer cream tone to distinguish from active sessions */
|
|
594
592
|
.sidebar-collapsed-tile.history {
|
|
595
593
|
background: linear-gradient(145deg, #e8d3c0 0%, #d3b69d 50%, #b89478 100%);
|
|
596
594
|
color: rgba(89, 58, 32, 0.88);
|
|
597
595
|
box-shadow:
|
|
598
|
-
0 1px
|
|
599
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.
|
|
600
|
-
inset 0 -1px 0 rgba(0, 0, 0, 0.06);
|
|
596
|
+
0 1px 2px rgba(120, 88, 56, 0.18),
|
|
597
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.42);
|
|
601
598
|
}
|
|
602
599
|
.sidebar-collapsed-tile.history:hover {
|
|
603
600
|
box-shadow:
|
|
604
|
-
0 4px
|
|
605
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.
|
|
606
|
-
inset 0 -1px 0 rgba(0, 0, 0, 0.06);
|
|
601
|
+
0 4px 10px -3px rgba(120, 88, 56, 0.3),
|
|
602
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.48);
|
|
607
603
|
}
|
|
608
|
-
/* Active —
|
|
604
|
+
/* Active — accent ring + 一层柔和暖投影 + 顶高光。原先 7 层叠加在 36px 小块上
|
|
605
|
+
糊成一团,精简到 3 层即可表达「升起 + 选中」。 */
|
|
609
606
|
.sidebar-collapsed-tile.active {
|
|
610
607
|
background: linear-gradient(145deg, #d27358 0%, #b35434 50%, #934128 100%);
|
|
611
608
|
transform: translateY(-1px);
|
|
612
609
|
box-shadow:
|
|
613
|
-
|
|
614
|
-
0
|
|
615
|
-
|
|
616
|
-
0 8px 22px -4px rgba(160, 74, 46, 0.55),
|
|
617
|
-
0 3px 8px -2px rgba(160, 74, 46, 0.32),
|
|
618
|
-
/* top sheen */
|
|
619
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.45),
|
|
620
|
-
inset 1px 0 0 rgba(255, 255, 255, 0.18),
|
|
621
|
-
/* bottom depth */
|
|
622
|
-
inset 0 -1px 0 rgba(0, 0, 0, 0.16);
|
|
610
|
+
0 0 0 2px rgba(197, 101, 61, 0.3),
|
|
611
|
+
0 5px 14px -4px rgba(160, 74, 46, 0.45),
|
|
612
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
|
623
613
|
}
|
|
624
614
|
.sidebar-collapsed-tile:active {
|
|
625
615
|
transform: translateY(0) scale(0.94);
|
|
@@ -9034,7 +9024,6 @@
|
|
|
9034
9024
|
background: rgba(178, 79, 69, 0.12);
|
|
9035
9025
|
color: var(--danger);
|
|
9036
9026
|
transform: rotate(90deg);
|
|
9037
|
-
box-shadow: 0 2px 8px rgba(178, 79, 69, 0.16);
|
|
9038
9027
|
}
|
|
9039
9028
|
.drawer-close-btn:active {
|
|
9040
9029
|
background: rgba(178, 79, 69, 0.2);
|
|
@@ -9184,17 +9173,17 @@
|
|
|
9184
9173
|
opacity: 1;
|
|
9185
9174
|
}
|
|
9186
9175
|
|
|
9187
|
-
/* ── Session item — clean card on glass ──
|
|
9176
|
+
/* ── Session item — clean card on glass ──
|
|
9177
|
+
克制原则:基础态用 hairline 描边 + 极轻顶部高光让卡片「贴」在侧栏上,
|
|
9178
|
+
不给每张卡都加漂浮投影;hover/active 才升起一层柔和投影。避免多层
|
|
9179
|
+
box-shadow 在半透明侧栏里互相叠加显脏。 */
|
|
9188
9180
|
.session-item {
|
|
9189
|
-
background: linear-gradient(180deg, rgba(255, 255, 255, 0.
|
|
9190
|
-
border:
|
|
9181
|
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0.55) 0%, rgba(255, 252, 247, 0.38) 100%);
|
|
9182
|
+
border: 1px solid rgba(125, 91, 57, 0.08);
|
|
9191
9183
|
border-radius: 13px;
|
|
9192
9184
|
padding: 11px 14px;
|
|
9193
9185
|
margin-bottom: 6px;
|
|
9194
|
-
box-shadow:
|
|
9195
|
-
0 0 0 0.5px rgba(125, 91, 57, 0.05),
|
|
9196
|
-
0 1px 2px rgba(125, 91, 57, 0.03),
|
|
9197
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
|
9186
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
|
|
9198
9187
|
transition:
|
|
9199
9188
|
background 0.2s ease,
|
|
9200
9189
|
border-color 0.2s ease,
|
|
@@ -9211,53 +9200,39 @@
|
|
|
9211
9200
|
}
|
|
9212
9201
|
.session-item:hover {
|
|
9213
9202
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.85) 0%, rgba(255, 253, 248, 0.62) 100%);
|
|
9214
|
-
border-color: rgba(
|
|
9203
|
+
border-color: rgba(125, 91, 57, 0.14);
|
|
9215
9204
|
transform: translateY(-1px);
|
|
9216
|
-
box-shadow:
|
|
9217
|
-
0 0 0 0.5px rgba(125, 91, 57, 0.08),
|
|
9218
|
-
0 6px 16px -6px rgba(125, 91, 57, 0.18),
|
|
9219
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
|
9205
|
+
box-shadow: 0 4px 12px -4px rgba(125, 91, 57, 0.16);
|
|
9220
9206
|
}
|
|
9221
9207
|
.session-item:hover::before {
|
|
9222
9208
|
opacity: 0.5;
|
|
9223
9209
|
transform: scaleY(1);
|
|
9224
9210
|
}
|
|
9225
|
-
/* Selected (active) —
|
|
9211
|
+
/* Selected (active) — accent ring 用边框表达,再叠一层柔和暖投影 + 顶高光。
|
|
9212
|
+
去掉原先的 6 层叠加(外环/光晕/底影/三向 inset),层数过多在玻璃上显脏。 */
|
|
9226
9213
|
.session-item.active {
|
|
9227
9214
|
background:
|
|
9228
9215
|
linear-gradient(180deg, rgba(255, 252, 247, 0.92) 0%, rgba(255, 244, 232, 0.78) 100%);
|
|
9229
|
-
border:
|
|
9216
|
+
border: 1px solid rgba(197, 101, 61, 0.32);
|
|
9230
9217
|
transform: translateY(-1px);
|
|
9231
9218
|
backdrop-filter: blur(20px) saturate(180%);
|
|
9232
9219
|
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
|
9233
9220
|
box-shadow:
|
|
9234
|
-
|
|
9235
|
-
|
|
9236
|
-
/* warm glow halo */
|
|
9237
|
-
0 8px 28px -6px rgba(197, 101, 61, 0.32),
|
|
9238
|
-
/* base elevation */
|
|
9239
|
-
0 2px 6px -1px rgba(125, 91, 57, 0.1),
|
|
9240
|
-
/* top inner highlight — light from above */
|
|
9241
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.95),
|
|
9242
|
-
/* bottom inner shadow — depth at base */
|
|
9243
|
-
inset 0 -1px 0 rgba(197, 101, 61, 0.08),
|
|
9244
|
-
/* left inner glint */
|
|
9245
|
-
inset 1px 0 0 rgba(255, 255, 255, 0.4);
|
|
9221
|
+
0 4px 14px -4px rgba(197, 101, 61, 0.24),
|
|
9222
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.7);
|
|
9246
9223
|
}
|
|
9224
|
+
/* ::before 的左侧高亮条受父级 overflow:hidden 裁切,原来的 box-shadow 光晕
|
|
9225
|
+
根本显示不出来,移除以省一次绘制。 */
|
|
9247
9226
|
.session-item.active::before {
|
|
9248
9227
|
opacity: 1;
|
|
9249
9228
|
transform: scaleY(1);
|
|
9250
|
-
box-shadow: 0 0 8px rgba(197, 101, 61, 0.5);
|
|
9251
9229
|
}
|
|
9252
9230
|
/* Multi-select state — quieter glass tint, no accent halo */
|
|
9253
9231
|
.session-item.selected {
|
|
9254
9232
|
background:
|
|
9255
9233
|
linear-gradient(180deg, rgba(255, 250, 244, 0.85) 0%, rgba(255, 246, 236, 0.65) 100%);
|
|
9256
|
-
border:
|
|
9257
|
-
box-shadow:
|
|
9258
|
-
0 0 0 1px rgba(197, 101, 61, 0.18),
|
|
9259
|
-
0 4px 14px -4px rgba(125, 91, 57, 0.14),
|
|
9260
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.85);
|
|
9234
|
+
border: 1px solid rgba(197, 101, 61, 0.2);
|
|
9235
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
|
9261
9236
|
}
|
|
9262
9237
|
.session-item.selected::before {
|
|
9263
9238
|
opacity: 0.7;
|
|
@@ -9402,21 +9377,19 @@
|
|
|
9402
9377
|
transform: scale(0.96);
|
|
9403
9378
|
transition-duration: 0.06s;
|
|
9404
9379
|
}
|
|
9405
|
-
/* Footer tab active — same liquid glass language as .session-item.active
|
|
9380
|
+
/* Footer tab active — same liquid glass language as .session-item.active
|
|
9381
|
+
(同步精简为 accent 边框 + 1 层柔和投影 + 顶高光) */
|
|
9406
9382
|
.sidebar-footer-actions .btn.active {
|
|
9407
9383
|
background:
|
|
9408
9384
|
linear-gradient(180deg, rgba(255, 252, 247, 0.95) 0%, rgba(255, 242, 228, 0.78) 100%);
|
|
9409
|
-
border:
|
|
9385
|
+
border: 1px solid rgba(197, 101, 61, 0.28);
|
|
9410
9386
|
color: var(--accent);
|
|
9411
9387
|
transform: translateY(-1px);
|
|
9412
9388
|
backdrop-filter: blur(16px) saturate(180%);
|
|
9413
9389
|
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
|
9414
9390
|
box-shadow:
|
|
9415
|
-
0
|
|
9416
|
-
0
|
|
9417
|
-
0 2px 4px -1px rgba(125, 91, 57, 0.08),
|
|
9418
|
-
inset 0 1px 0 rgba(255, 255, 255, 0.95),
|
|
9419
|
-
inset 0 -1px 0 rgba(197, 101, 61, 0.08);
|
|
9391
|
+
0 3px 10px -3px rgba(197, 101, 61, 0.22),
|
|
9392
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.7);
|
|
9420
9393
|
}
|
|
9421
9394
|
.sidebar-footer-actions .btn.sidebar-logout:hover {
|
|
9422
9395
|
background: rgba(178, 79, 69, 0.08);
|