cicy-desktop 2.1.77 → 2.1.78
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/README.md +21 -18
- package/build/icon.icns +0 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/build/icon.svg +8 -18
- package/build/icons/icon-1024.png +0 -0
- package/build/icons/icon-128.png +0 -0
- package/build/icons/icon-16.png +0 -0
- package/build/icons/icon-24.png +0 -0
- package/build/icons/icon-256.png +0 -0
- package/build/icons/icon-32.png +0 -0
- package/build/icons/icon-48.png +0 -0
- package/build/icons/icon-512.png +0 -0
- package/build/icons/icon-64.png +0 -0
- package/build/icons/icon-96.png +0 -0
- package/build/icons/trayTemplate-16.png +0 -0
- package/build/icons/trayTemplate-16@2x.png +0 -0
- package/build/icons/trayTemplate-22.png +0 -0
- package/build/icons/trayTemplate-22@2x.png +0 -0
- package/build/icons/trayTemplate-32.png +0 -0
- package/build/icons/trayTemplate-32@2x.png +0 -0
- package/build/trayTemplate.png +0 -0
- package/build/trayTemplate.svg +11 -12
- package/build/trayTemplate@2x.png +0 -0
- package/package.json +6 -6
- package/src/backends/auth-loopback.js +17 -6
- package/src/backends/homepage-react/assets/index-DE9m6JTn.css +1 -0
- package/src/backends/homepage-react/assets/index-DLYMzgf5.js +365 -0
- package/src/backends/homepage-react/favicon-256.png +0 -0
- package/src/backends/homepage-react/favicon.svg +12 -0
- package/src/backends/homepage-react/index.html +4 -2
- package/src/backends/local-teams.js +53 -1
- package/src/backends/login-success.html +96 -0
- package/src/cloud/cloud-client.js +239 -0
- package/src/main.js +62 -1
- package/src/utils/brand-host-electron.js +134 -0
- package/src/utils/window-utils.js +62 -14
- package/workers/render/index.html +2 -0
- package/workers/render/public/favicon-256.png +0 -0
- package/workers/render/public/favicon.svg +12 -0
- package/workers/render/src/App.css +127 -31
- package/workers/render/src/App.jsx +170 -24
- package/src/backends/homepage-react/assets/index-CPH-S8uU.css +0 -1
- package/src/backends/homepage-react/assets/index-DuWX0iug.js +0 -365
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Rebrand the HOST (unpackaged) Electron bundle so cicy-desktop presents as
|
|
2
|
+
// "CiCy Desktop" at the OS level — menu-bar app name, Cmd-Tab app switcher,
|
|
3
|
+
// Finder, and dock — instead of the stock "Electron".
|
|
4
|
+
//
|
|
5
|
+
// Why this is needed: cicy-desktop runs UNPACKAGED (npx / npm i -g — the
|
|
6
|
+
// mac/win/linux default, 主人令). An unpackaged app borrows the shared
|
|
7
|
+
// node_modules/electron bundle for its OS identity, so the menu-bar name and
|
|
8
|
+
// the app-switcher icon come from that bundle's Info.plist + electron.icns,
|
|
9
|
+
// NOT from app code. `app.dock.setIcon()` (see tray.js) only repaints the dock
|
|
10
|
+
// at runtime; it cannot touch the menu-bar name or the Cmd-Tab icon. The only
|
|
11
|
+
// way to fully rebrand an unpackaged app is to patch the host bundle itself.
|
|
12
|
+
//
|
|
13
|
+
// We do it idempotently at startup: set CFBundleName/CFBundleDisplayName and
|
|
14
|
+
// swap Resources/electron.icns for our build/icon.icns (backing up the stock
|
|
15
|
+
// one once). macOS reads the bundle name at launch, so the first time we change
|
|
16
|
+
// the name we relaunch ONCE to apply it — that path is self-terminating because
|
|
17
|
+
// the next run sees the name already branded and does nothing.
|
|
18
|
+
//
|
|
19
|
+
// macOS-only for now. Windows (rcedit electron.exe icon + FileDescription) is a
|
|
20
|
+
// follow-up: the .exe is locked while running, so it needs a copy-swap or an
|
|
21
|
+
// install-time step rather than a startup patch.
|
|
22
|
+
|
|
23
|
+
const { app } = require("electron");
|
|
24
|
+
const path = require("path");
|
|
25
|
+
const fs = require("fs");
|
|
26
|
+
const cp = require("child_process");
|
|
27
|
+
const log = require("electron-log");
|
|
28
|
+
|
|
29
|
+
const BRAND = "CiCy Desktop";
|
|
30
|
+
|
|
31
|
+
// execPath = .../Electron.app/Contents/MacOS/Electron → return .../Contents
|
|
32
|
+
function macBundleContents() {
|
|
33
|
+
const macOSDir = path.dirname(process.execPath); // .../Contents/MacOS
|
|
34
|
+
const contents = path.dirname(macOSDir); // .../Contents
|
|
35
|
+
return path.basename(contents) === "Contents" ? contents : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function plistGet(plist, key) {
|
|
39
|
+
try {
|
|
40
|
+
return cp
|
|
41
|
+
.execFileSync("/usr/libexec/PlistBuddy", ["-c", `Print :${key}`, plist], { encoding: "utf8" })
|
|
42
|
+
.trim();
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function plistSet(plist, key, val) {
|
|
48
|
+
// PlistBuddy takes everything after the key as the value, spaces included,
|
|
49
|
+
// so "Set :CFBundleName CiCy Desktop" sets the value to "CiCy Desktop".
|
|
50
|
+
try {
|
|
51
|
+
cp.execFileSync("/usr/libexec/PlistBuddy", ["-c", `Set :${key} ${val}`, plist]);
|
|
52
|
+
return true;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function ourIcns() {
|
|
59
|
+
const p = path.join(__dirname, "..", "..", "build", "icon.icns");
|
|
60
|
+
return fs.existsSync(p) ? p : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Patch name + icon. Returns true if the bundle NAME changed (caller relaunches).
|
|
64
|
+
function brandMac() {
|
|
65
|
+
const contents = macBundleContents();
|
|
66
|
+
if (!contents) return false;
|
|
67
|
+
const plist = path.join(contents, "Info.plist");
|
|
68
|
+
if (!fs.existsSync(plist)) return false;
|
|
69
|
+
const icnsTarget = path.join(contents, "Resources", "electron.icns");
|
|
70
|
+
const src = ourIcns();
|
|
71
|
+
|
|
72
|
+
let nameChanged = false;
|
|
73
|
+
|
|
74
|
+
// 1) Menu-bar / switcher / Finder name. Read at launch, so a change needs a relaunch.
|
|
75
|
+
if (plistGet(plist, "CFBundleName") !== BRAND) {
|
|
76
|
+
plistSet(plist, "CFBundleName", BRAND);
|
|
77
|
+
plistSet(plist, "CFBundleDisplayName", BRAND);
|
|
78
|
+
nameChanged = true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 2) App-switcher / Finder / base dock icon. Compare by size as a cheap check;
|
|
82
|
+
// LaunchServices refresh below makes it visible without a relaunch.
|
|
83
|
+
if (src) {
|
|
84
|
+
let needIcon = true;
|
|
85
|
+
try {
|
|
86
|
+
needIcon =
|
|
87
|
+
!fs.existsSync(icnsTarget) || fs.statSync(icnsTarget).size !== fs.statSync(src).size;
|
|
88
|
+
} catch {}
|
|
89
|
+
if (needIcon) {
|
|
90
|
+
try {
|
|
91
|
+
const bak = icnsTarget + ".electron-orig";
|
|
92
|
+
if (fs.existsSync(icnsTarget) && !fs.existsSync(bak)) fs.copyFileSync(icnsTarget, bak);
|
|
93
|
+
fs.copyFileSync(src, icnsTarget);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
log.warn(`[Brand] icns swap failed: ${e.message}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Nudge LaunchServices / Finder / Dock to drop the cached icon + name.
|
|
101
|
+
try {
|
|
102
|
+
const bundle = path.dirname(contents); // .../Electron.app
|
|
103
|
+
cp.execFileSync("/usr/bin/touch", [bundle]);
|
|
104
|
+
cp.execFile(
|
|
105
|
+
"/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister",
|
|
106
|
+
["-f", bundle],
|
|
107
|
+
() => {}
|
|
108
|
+
);
|
|
109
|
+
} catch {}
|
|
110
|
+
|
|
111
|
+
log.info(`[Brand] host Electron bundle → ${BRAND} (nameChanged=${nameChanged})`);
|
|
112
|
+
return nameChanged;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let __done = false;
|
|
116
|
+
function brandHostElectron() {
|
|
117
|
+
if (__done) return;
|
|
118
|
+
__done = true;
|
|
119
|
+
try {
|
|
120
|
+
if (process.platform !== "darwin") return; // win32 rcedit path: TODO
|
|
121
|
+
const nameChanged = brandMac();
|
|
122
|
+
if (nameChanged) {
|
|
123
|
+
// The new menu-bar name only shows after a relaunch. Self-terminating:
|
|
124
|
+
// the relaunched run sees the name already branded → no second relaunch.
|
|
125
|
+
log.info("[Brand] relaunching once so the menu-bar name applies…");
|
|
126
|
+
app.relaunch({ args: process.argv.slice(1) });
|
|
127
|
+
app.exit(0);
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {
|
|
130
|
+
log.warn(`[Brand] failed: ${e.message}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = { brandHostElectron };
|
|
@@ -14,12 +14,33 @@ if (app) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function setupWindowHandlers(win) {
|
|
17
|
-
// Hook window.open
|
|
17
|
+
// Hook window.open. App/internal popups (about:blank or trusted/local hosts —
|
|
18
|
+
// e.g. a popped-out ttyd terminal from CiCy Code) must open as REAL Electron
|
|
19
|
+
// windows WITH webviewTag, or any <webview> inside them (terminal / artifact
|
|
20
|
+
// guest) collapses to 0x0. Returning action:"deny" (the old behavior) routed
|
|
21
|
+
// every popup through a dialog; an *allowed* window.open window inherits
|
|
22
|
+
// DEFAULT webPreferences (webviewTag=false) unless we override it here.
|
|
23
|
+
// External links (other websites) keep the open-in-browser/app dialog.
|
|
18
24
|
win.webContents.setWindowOpenHandler(({ url }) => {
|
|
19
25
|
log.info(`[WindowOpen] Intercepted: ${url}`);
|
|
20
|
-
if (url
|
|
21
|
-
|
|
26
|
+
if (!url || url === "about:blank" || isTrustedUrl(url)) {
|
|
27
|
+
return {
|
|
28
|
+
action: "allow",
|
|
29
|
+
overrideBrowserWindowOptions: {
|
|
30
|
+
autoHideMenuBar: true,
|
|
31
|
+
icon: require("./app-icon").appIconPath(),
|
|
32
|
+
webPreferences: {
|
|
33
|
+
webviewTag: true, // embedded <webview> (ttyd/artifact) must render
|
|
34
|
+
nodeIntegration: isTrustedUrl(url),
|
|
35
|
+
contextIsolation: !isTrustedUrl(url),
|
|
36
|
+
preload: path.join(__dirname, "../backends/webview-preload.js"),
|
|
37
|
+
webSecurity: false,
|
|
38
|
+
enableClipboard: true,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
22
42
|
}
|
|
43
|
+
showOpenLinkDialog(win, url);
|
|
23
44
|
return { action: "deny" };
|
|
24
45
|
});
|
|
25
46
|
if (!win.webContents.debugger.isAttached()) {
|
|
@@ -29,16 +50,13 @@ function setupWindowHandlers(win) {
|
|
|
29
50
|
// 初始化窗口监控(在 dom-ready 之前调用)
|
|
30
51
|
initWindowMonitoring(win);
|
|
31
52
|
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
event.preventDefault();
|
|
40
|
-
win.hide();
|
|
41
|
-
}
|
|
53
|
+
// Non-homepage windows (team / backend windows) close DIRECTLY — a close
|
|
54
|
+
// actually destroys the window, it does NOT hide it (主人令). Only the
|
|
55
|
+
// homepage is a persistent window; everything created here is disposable and
|
|
56
|
+
// re-openable from the homepage. (Previously these preventDefault()+hide()'d,
|
|
57
|
+
// so "closed" windows lingered hidden forever.)
|
|
58
|
+
win.on("close", () => {
|
|
59
|
+
log.info(`[Window ${win.id}] Close → destroy: ${win.getTitle()}`);
|
|
42
60
|
});
|
|
43
61
|
|
|
44
62
|
// 🔥 全局下载处理 - 自动保存到 ~/Downloads/electron/
|
|
@@ -291,7 +309,6 @@ function createWindow(options = {}, accountIdx = 0, forceNew = false) {
|
|
|
291
309
|
// the way for backend pages.
|
|
292
310
|
autoHideMenuBar: true,
|
|
293
311
|
webPreferences: {
|
|
294
|
-
webviewTag: true,
|
|
295
312
|
offscreen: false, // 确保不是离屏渲染
|
|
296
313
|
nodeIntegration: isTrustedUrl(url),
|
|
297
314
|
contextIsolation: !isTrustedUrl(url),
|
|
@@ -307,6 +324,10 @@ function createWindow(options = {}, accountIdx = 0, forceNew = false) {
|
|
|
307
324
|
// 允许 webview 访问剪贴板
|
|
308
325
|
webSecurity: false, // 在开发环境中可以考虑禁用,生产环境需要谨慎
|
|
309
326
|
...webPreferences,
|
|
327
|
+
// webviewTag MUST stay on for embedded <webview> (ttyd terminal, artifact
|
|
328
|
+
// guest) to render — a 0x0/blank <webview> is the classic symptom of it
|
|
329
|
+
// being off. Forced LAST so a caller's webPreferences can never disable it.
|
|
330
|
+
webviewTag: true,
|
|
310
331
|
},
|
|
311
332
|
});
|
|
312
333
|
|
|
@@ -412,6 +433,33 @@ if (app) {
|
|
|
412
433
|
app.on("browser-window-created", (event, win) => {
|
|
413
434
|
setupWindowHandlers(win);
|
|
414
435
|
});
|
|
436
|
+
|
|
437
|
+
// <webview> guests (Team Helper drawer, artifact, an embedded CiCy Code SPA)
|
|
438
|
+
// get their OWN webContents — setupWindowHandlers only runs for BrowserWindows,
|
|
439
|
+
// so a window.open from INSIDE a <webview> would otherwise create a window with
|
|
440
|
+
// default webPreferences (webviewTag=false) → any nested <webview> there is 0x0.
|
|
441
|
+
// Give every guest a handler that opens its popups WITH webviewTag.
|
|
442
|
+
app.on("web-contents-created", (_e, contents) => {
|
|
443
|
+
try {
|
|
444
|
+
if (contents.getType && contents.getType() === "webview") {
|
|
445
|
+
contents.setWindowOpenHandler(({ url }) => ({
|
|
446
|
+
action: "allow",
|
|
447
|
+
overrideBrowserWindowOptions: {
|
|
448
|
+
autoHideMenuBar: true,
|
|
449
|
+
webPreferences: {
|
|
450
|
+
webviewTag: true,
|
|
451
|
+
contextIsolation: !isTrustedUrl(url),
|
|
452
|
+
nodeIntegration: isTrustedUrl(url),
|
|
453
|
+
webSecurity: false,
|
|
454
|
+
enableClipboard: true,
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
}));
|
|
458
|
+
}
|
|
459
|
+
} catch (e) {
|
|
460
|
+
log.warn(`[web-contents-created] guest open-handler failed: ${e.message}`);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
415
463
|
}
|
|
416
464
|
|
|
417
465
|
function showOpenLinkDialog(parentWin, url) {
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
7
|
+
<link rel="icon" type="image/png" sizes="256x256" href="/favicon-256.png" />
|
|
6
8
|
<title>CiCy Desktop</title>
|
|
7
9
|
</head>
|
|
8
10
|
<body>
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" fill="none">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="cicyMark" x1="16" y1="12" x2="80" y2="84" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop stop-color="#60A5FA"/>
|
|
5
|
+
<stop offset="0.55" stop-color="#2563EB"/>
|
|
6
|
+
<stop offset="1" stop-color="#1E3A8A"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
9
|
+
<path d="M48 11L39.5 33.3L16 29.5L31 48L16 66.5L39.5 62.7L48 85L56.5 62.7L80 66.5L65 48L80 29.5L56.5 33.3Z"
|
|
10
|
+
fill="url(#cicyMark)" stroke="url(#cicyMark)" stroke-width="8"
|
|
11
|
+
stroke-linejoin="round" stroke-linecap="round"/>
|
|
12
|
+
</svg>
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
/* ── Palette (restrained: one accent + neutrals + sparse semantics) ──
|
|
2
|
+
accent = brand blue (primary actions, version/update/local accents)
|
|
3
|
+
brand gradient (blue→violet) = logo + primary CTA ONLY
|
|
4
|
+
ok/danger = sparse semantic (live dot / destructive); amber retired. */
|
|
5
|
+
:root {
|
|
6
|
+
--accent: #5b8df7;
|
|
7
|
+
--accent-soft: rgba(91,141,247,.16);
|
|
8
|
+
--accent-line: rgba(91,141,247,.32);
|
|
9
|
+
--accent-text: #a5c4ff;
|
|
10
|
+
--ok: #4ade80;
|
|
11
|
+
--danger: #f7a3a3;
|
|
12
|
+
--text: #e5e7eb;
|
|
13
|
+
--text-dim: #9da7b3;
|
|
14
|
+
--text-mute: #6b7280;
|
|
15
|
+
--line: rgba(255,255,255,.08);
|
|
16
|
+
}
|
|
1
17
|
* { box-sizing: border-box; }
|
|
2
18
|
html, body, #root { margin: 0; height: 100%; }
|
|
3
19
|
body {
|
|
@@ -132,10 +148,12 @@ body {
|
|
|
132
148
|
border-bottom: 1px solid rgba(255,255,255,.05);
|
|
133
149
|
backdrop-filter: blur(14px);
|
|
134
150
|
}
|
|
135
|
-
/*
|
|
136
|
-
(
|
|
137
|
-
|
|
138
|
-
|
|
151
|
+
/* macOS hiddenInset traffic-light buttons sit top-left. Instead of squeezing
|
|
152
|
+
the brand to their RIGHT (84px gutter), drop the whole header row BELOW them
|
|
153
|
+
— logo/title and the user chip live on their own line under the red/yellow/
|
|
154
|
+
green dots. Cleaner, and the brand isn't crammed beside the controls.
|
|
155
|
+
Removed in fullscreen where the buttons hide. */
|
|
156
|
+
[data-platform="darwin"][data-fullscreen="0"] .topbar { padding-top: 34px; }
|
|
139
157
|
.brand-mini { display: inline-flex; align-items: center; gap: 10px; }
|
|
140
158
|
.brand-mini .brand-name { font-size: 14px; }
|
|
141
159
|
|
|
@@ -144,10 +162,10 @@ body {
|
|
|
144
162
|
display: inline-flex; align-items: center; gap: 10px;
|
|
145
163
|
}
|
|
146
164
|
.welcome {
|
|
147
|
-
font-size: 12px; color:
|
|
165
|
+
font-size: 12px; color: var(--text-dim);
|
|
148
166
|
padding: 4px 10px; border-radius: 999px;
|
|
149
|
-
background: rgba(
|
|
150
|
-
border: 1px solid
|
|
167
|
+
background: rgba(255,255,255,.05);
|
|
168
|
+
border: 1px solid var(--line);
|
|
151
169
|
animation: fadein 200ms ease;
|
|
152
170
|
}
|
|
153
171
|
@keyframes fadein { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: none; } }
|
|
@@ -159,6 +177,82 @@ body {
|
|
|
159
177
|
}
|
|
160
178
|
.user-name { font-size: 13px; color: #c7cdd6; }
|
|
161
179
|
|
|
180
|
+
/* user-chip dropdown (我的钱包 / 我的订单 / HTTPS tip / 退出) */
|
|
181
|
+
.user-chip { position: relative; }
|
|
182
|
+
.user-chip__trigger {
|
|
183
|
+
-webkit-app-region: no-drag;
|
|
184
|
+
display: inline-flex; align-items: center; gap: 8px;
|
|
185
|
+
border: 1px solid transparent; background: transparent;
|
|
186
|
+
border-radius: 999px; padding: 3px 8px 3px 3px; cursor: pointer;
|
|
187
|
+
transition: .12s;
|
|
188
|
+
}
|
|
189
|
+
.user-chip__trigger:hover,
|
|
190
|
+
.user-chip__trigger.is-open {
|
|
191
|
+
background: rgba(255,255,255,.06); border-color: rgba(255,255,255,.12);
|
|
192
|
+
}
|
|
193
|
+
.user-chip__caret { font-size: 10px; color: #8a93a3; transition: transform .12s; }
|
|
194
|
+
.user-chip__trigger.is-open .user-chip__caret { transform: rotate(180deg); }
|
|
195
|
+
.user-chip__menu {
|
|
196
|
+
position: absolute; top: 38px; right: 0; z-index: 30;
|
|
197
|
+
min-width: 200px; padding: 6px;
|
|
198
|
+
background: #1b2027; border: 1px solid rgba(255,255,255,.12);
|
|
199
|
+
border-radius: 12px; box-shadow: 0 12px 34px rgba(0,0,0,.5);
|
|
200
|
+
display: flex; flex-direction: column; gap: 2px;
|
|
201
|
+
animation: fadein 140ms ease;
|
|
202
|
+
}
|
|
203
|
+
.user-chip__menu-item {
|
|
204
|
+
text-align: left; width: 100%;
|
|
205
|
+
border: none; background: transparent; color: #d1d5db;
|
|
206
|
+
border-radius: 8px; padding: 9px 11px; font-size: 13px;
|
|
207
|
+
cursor: pointer; transition: .12s;
|
|
208
|
+
}
|
|
209
|
+
.user-chip__menu-item:hover { background: rgba(255,255,255,.07); color: #fff; }
|
|
210
|
+
.user-chip__menu-item.is-danger { color: #f7a3a3; }
|
|
211
|
+
.user-chip__menu-item.is-danger:hover { background: rgba(239,68,68,.16); color: #fff; }
|
|
212
|
+
.user-chip__menu-sep { height: 1px; margin: 4px 2px; background: rgba(255,255,255,.08); }
|
|
213
|
+
/* HTTPS audit tip, rendered as a flat menu row with an on/off switch */
|
|
214
|
+
.user-chip__mitm-row {
|
|
215
|
+
display: flex; align-items: center; justify-content: space-between; gap: 10px;
|
|
216
|
+
cursor: default;
|
|
217
|
+
}
|
|
218
|
+
.user-chip__mitm-row:hover { background: transparent; }
|
|
219
|
+
.user-chip__mitm-label { font-size: 13px; color: var(--text); }
|
|
220
|
+
/* mini toggle switch (开/关) */
|
|
221
|
+
.mini-switch {
|
|
222
|
+
-webkit-app-region: no-drag;
|
|
223
|
+
position: relative; flex: 0 0 auto;
|
|
224
|
+
width: 36px; height: 20px; padding: 0;
|
|
225
|
+
border-radius: 999px; border: 1px solid var(--line);
|
|
226
|
+
background: rgba(255,255,255,.08); cursor: pointer;
|
|
227
|
+
transition: background .16s ease, border-color .16s ease;
|
|
228
|
+
}
|
|
229
|
+
.mini-switch.is-on { background: var(--accent); border-color: var(--accent); }
|
|
230
|
+
.mini-switch.is-busy { opacity: .6; cursor: default; }
|
|
231
|
+
.mini-switch:disabled { cursor: default; }
|
|
232
|
+
.mini-switch__knob {
|
|
233
|
+
position: absolute; top: 1px; left: 1px;
|
|
234
|
+
width: 16px; height: 16px; border-radius: 50%;
|
|
235
|
+
background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.45);
|
|
236
|
+
transition: transform .16s ease;
|
|
237
|
+
}
|
|
238
|
+
.mini-switch.is-on .mini-switch__knob { transform: translateX(16px); }
|
|
239
|
+
.mini-switch.is-busy .mini-switch__knob { animation: spin 1s linear infinite; }
|
|
240
|
+
.user-chip__mitm-note { margin: 0 4px; padding: 2px 4px 6px; font-size: 11px; color: var(--text-mute); }
|
|
241
|
+
.user-chip__mitm-err {
|
|
242
|
+
margin: 2px 4px 6px; padding: 5px 8px; font-size: 11px;
|
|
243
|
+
color: var(--danger); background: rgba(239,68,68,.1); border-radius: 6px; line-height: 1.4;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* cloud team card: 账单 entry (top-right, beside trial badge) */
|
|
247
|
+
.bcard__top-right { display: inline-flex; align-items: center; gap: 6px; }
|
|
248
|
+
.bcard__billing-btn {
|
|
249
|
+
-webkit-app-region: no-drag;
|
|
250
|
+
border: 1px solid var(--line); background: transparent; color: var(--text-dim);
|
|
251
|
+
border-radius: 7px; padding: 3px 9px; font-size: 11px; cursor: pointer;
|
|
252
|
+
transition: color .12s, border-color .12s, background .12s;
|
|
253
|
+
}
|
|
254
|
+
.bcard__billing-btn:hover { color: var(--accent-text); border-color: var(--accent-line); background: var(--accent-soft); }
|
|
255
|
+
|
|
162
256
|
.glow--app {
|
|
163
257
|
inset: -10% -10% auto -10%;
|
|
164
258
|
height: 50vh;
|
|
@@ -276,9 +370,9 @@ body {
|
|
|
276
370
|
border: 1px solid rgba(52,211,153,.3);
|
|
277
371
|
}
|
|
278
372
|
.bcard__badge--trial {
|
|
279
|
-
background: rgba(
|
|
280
|
-
color:
|
|
281
|
-
border: 1px solid
|
|
373
|
+
background: rgba(255,255,255,.06);
|
|
374
|
+
color: var(--text-dim);
|
|
375
|
+
border: 1px solid var(--line);
|
|
282
376
|
font-size: 9.5px;
|
|
283
377
|
letter-spacing: 0.3px;
|
|
284
378
|
padding: 2px 7px;
|
|
@@ -309,10 +403,10 @@ body {
|
|
|
309
403
|
opacity: .8;
|
|
310
404
|
}
|
|
311
405
|
.bcard__cta--helper {
|
|
312
|
-
background:
|
|
313
|
-
box-shadow:
|
|
406
|
+
background: rgba(255,255,255,.05);
|
|
407
|
+
box-shadow: none;
|
|
314
408
|
}
|
|
315
|
-
.bcard__cta--helper:hover {
|
|
409
|
+
.bcard__cta--helper:hover { background: var(--accent-soft); }
|
|
316
410
|
|
|
317
411
|
/* ── Helper drawer top bar (close button) ── */
|
|
318
412
|
.helper-aside__top {
|
|
@@ -563,8 +657,8 @@ body {
|
|
|
563
657
|
display: inline-flex; align-items: center;
|
|
564
658
|
padding: 2px 8px;
|
|
565
659
|
border-radius: 999px;
|
|
566
|
-
background:
|
|
567
|
-
color:
|
|
660
|
+
background: var(--accent-soft);
|
|
661
|
+
color: var(--accent-text);
|
|
568
662
|
font-size: 10px; font-weight: 700;
|
|
569
663
|
text-transform: uppercase; letter-spacing: 0.5px;
|
|
570
664
|
}
|
|
@@ -604,27 +698,29 @@ body {
|
|
|
604
698
|
border-radius: 999px;
|
|
605
699
|
}
|
|
606
700
|
|
|
701
|
+
/* One calm, uniform 打开 button for EVERY card type — no loud blue/amber/purple
|
|
702
|
+
fills or colored shadows (主人: 打开 别用不同颜色太醒目的 btn). Quiet neutral
|
|
703
|
+
surface that warms to the single accent on hover. */
|
|
607
704
|
.bcard__cta {
|
|
608
705
|
position: relative; z-index: 1;
|
|
609
706
|
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
|
|
610
707
|
width: 100%; height: 38px;
|
|
611
|
-
border:
|
|
612
|
-
background:
|
|
708
|
+
border: 1px solid var(--line); border-radius: 9px;
|
|
709
|
+
background: rgba(255,255,255,.05); color: var(--text);
|
|
613
710
|
font-size: 13px; font-weight: 600;
|
|
614
711
|
letter-spacing: 0.1px;
|
|
615
712
|
cursor: pointer;
|
|
616
|
-
transition: transform 80ms ease, background
|
|
617
|
-
box-shadow:
|
|
618
|
-
}
|
|
619
|
-
.bcard--cloud .bcard__cta {
|
|
620
|
-
background: var(--accent-cloud);
|
|
621
|
-
box-shadow: 0 4px 12px -2px rgba(245,158,11,.45);
|
|
713
|
+
transition: transform 80ms ease, background 140ms ease, border-color 140ms ease, color 140ms ease;
|
|
714
|
+
box-shadow: none;
|
|
622
715
|
}
|
|
716
|
+
/* card-type variants inherit the same quiet style (overrides removed) */
|
|
717
|
+
.bcard--cloud .bcard__cta,
|
|
623
718
|
.bcard--custom .bcard__cta {
|
|
624
|
-
background:
|
|
625
|
-
|
|
719
|
+
background: rgba(255,255,255,.05); box-shadow: none;
|
|
720
|
+
}
|
|
721
|
+
.bcard__cta:hover {
|
|
722
|
+
background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-text);
|
|
626
723
|
}
|
|
627
|
-
.bcard__cta:hover { filter: brightness(1.08); }
|
|
628
724
|
.bcard__cta:active { transform: translateY(1px); }
|
|
629
725
|
.bcard__cta:disabled {
|
|
630
726
|
background: rgba(255,255,255,.03);
|
|
@@ -676,8 +772,8 @@ body {
|
|
|
676
772
|
cursor: pointer; transition: .12s;
|
|
677
773
|
}
|
|
678
774
|
.bcard__menu-item:hover { background: rgba(255,255,255,.07); color: #fff; }
|
|
679
|
-
.bcard__menu-item.is-accent { color:
|
|
680
|
-
.bcard__menu-item.is-accent:hover { background:
|
|
775
|
+
.bcard__menu-item.is-accent { color: var(--accent-text); font-weight: 600; }
|
|
776
|
+
.bcard__menu-item.is-accent:hover { background: var(--accent-soft); color: #c7dbff; }
|
|
681
777
|
.bcard__menu-item.is-danger { color: #f7a3a3; }
|
|
682
778
|
.bcard__menu-item.is-danger:hover { background: rgba(239,68,68,.16); color: #fff; }
|
|
683
779
|
/* ---- Windows Docker 一键安装/启动卡 (DockerSetup) ---- */
|
|
@@ -723,9 +819,9 @@ body {
|
|
|
723
819
|
}
|
|
724
820
|
/* "新版 vX.Y.Z" chip on the card face */
|
|
725
821
|
.bcard__chip--new {
|
|
726
|
-
color:
|
|
727
|
-
background:
|
|
728
|
-
border-color:
|
|
822
|
+
color: var(--accent-text);
|
|
823
|
+
background: var(--accent-soft);
|
|
824
|
+
border-color: var(--accent-line);
|
|
729
825
|
}
|
|
730
826
|
.bcard__opmsg {
|
|
731
827
|
display: flex; align-items: center; gap: 6px;
|