diff --git a/README.en.md b/README.en.md index 02d82a1..96487ec 100644 --- a/README.en.md +++ b/README.en.md @@ -49,6 +49,7 @@ CLI password (export / import): prefer the `ZSW_PASSWORD` environment variable ( ```bash npm install npm run tauri dev # development (HMR) +`npm run dev # frontend-only preview (open preview.html / preview-settings.html in a browser, mocked data, no Tauri needed)` npm run tauri build # NSIS installer cd src-tauri && cargo test # unit tests (incl. node↔Rust cross-language crypto vectors) ``` diff --git a/README.md b/README.md index 59a45eb..659f16f 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ CLI 密码(export / import):优先环境变量 `ZSW_PASSWORD`(不出现 ```bash npm install npm run tauri dev # 开发(HMR) +`npm run dev # 仅前端预览(浏览器打开 preview.html / preview-settings.html,内置 mock 数据,无需 Tauri)` npm run tauri build # NSIS 安装包 cd src-tauri && cargo test # 单元测试(含 node↔Rust 跨语言加密向量) ``` diff --git a/captcha.html b/captcha.html index 9f3c622..ebdbeb2 100644 --- a/captcha.html +++ b/captcha.html @@ -5,12 +5,13 @@ Z·SWITCH
+
diff --git a/docs/screenshot.png b/docs/screenshot.png index c40810d..a4680aa 100644 Binary files a/docs/screenshot.png and b/docs/screenshot.png differ diff --git a/icon-src-1024.png b/icon-src-1024.png index dd14977..9b9fbe8 100644 Binary files a/icon-src-1024.png and b/icon-src-1024.png differ diff --git a/index.html b/index.html index 40734b7..451eb44 100644 --- a/index.html +++ b/index.html @@ -6,20 +6,20 @@ Z·SWITCH @@ -28,7 +28,9 @@
-
+
+ +
diff --git a/logo-src.png b/logo-src.png new file mode 100644 index 0000000..0736eb6 Binary files /dev/null and b/logo-src.png differ diff --git a/make_icon.py b/make_icon.py index 9dbab04..4446435 100644 --- a/make_icon.py +++ b/make_icon.py @@ -1,138 +1,149 @@ import zlib, struct, math, io, os -SS = 2 -N = 1024 * SS -R_CORNER = int(N * 0.19) - -BG_TOP = (44, 47, 56) -BG_BOT = (26, 28, 34) - -def lerp(a, b, t): - return tuple(int(a[i] + (b[i] - a[i]) * t) for i in range(3)) - -def clamp01(v): - return 0.0 if v < 0 else 1.0 if v > 1 else v - -def inside_rounded(x, y, n, r): - if x < 0 or y < 0 or x >= n or y >= n: - return False - cx = min(max(x, r), n - r) - cy = min(max(y, r), n - r) - dx, dy = x - cx, y - cy - return dx * dx + dy * dy <= r * r - -def poly_contains(pts, px, py): - inside = False - j = len(pts) - 1 - for i in range(len(pts)): - xi, yi = pts[i]; xj, yj = pts[j] - if (yi > py) != (yj > py) and px < (xj - xi) * (py - yi) / (yj - yi) + xi: - inside = not inside - j = i - return inside - -pixels = [[None] * N for _ in range(N)] - -for y in range(N): - t = y / N - base = lerp(BG_TOP, BG_BOT, t ** 1.15) - if t < 0.12: - base = lerp((58, 62, 74), base, t / 0.12) - for x in range(N): - if inside_rounded(x, y, N, R_CORNER): - pixels[y][x] = base - -u = N / 2048.0 - -def put_poly(pts, color_fn): - """color_fn(x, y) -> (r,g,b),支持笔画内渐变""" - xs = [p[0] for p in pts]; ys = [p[1] for p in pts] - x0, x1 = max(0, int(min(xs))), min(N, int(max(xs)) + 1) - y0, y1 = max(0, int(min(ys))), min(N, int(max(ys)) + 1) - for y in range(y0, y1): - for x in range(x0, x1): - if poly_contains(pts, x, y): - pixels[y][x] = color_fn(x, y) - -border = 58 * u -BORDER_C = (244, 192, 104) -def inner_rounded(x, y): - b = border - n2 = N - 2 * b - r2 = max(1, R_CORNER - b) - xx, yy = x - b, y - b - if xx < 0 or yy < 0 or xx >= n2 or yy >= n2: - return False - cx = min(max(xx, r2), n2 - r2) - cy = min(max(yy, r2), n2 - r2) - dx, dy = xx - cx, yy - cy - return dx * dx + dy * dy <= r2 * r2 -for y in range(N): - for x in range(N): - if pixels[y][x] is not None and not inner_rounded(x, y): - bg = pixels[y][x] - xx = min(x, N - x); yy = min(y, N - y) - edge_t = clamp01(1 - (min(xx, yy) / (border * 0.8))) - c = lerp(BORDER_C, (255, 219, 150), edge_t * 0.5) - pixels[y][x] = lerp(bg, c, 0.92) - -bar_h = 340 * u -y_c = 620 * u -x_l, x_r, tip_x = 400 * u, 1330 * u, 1690 * u -def top_color(x, y): - t = clamp01((x - x_l) / (tip_x - x_l)) - return lerp((228, 152, 54), (255, 216, 134), t) -put_poly([ - (x_l, y_c - bar_h/2), (x_r, y_c - bar_h/2), - (x_r, y_c - bar_h*1.32), (tip_x, y_c), - (x_r, y_c + bar_h*1.32), (x_r, y_c + bar_h/2), - (x_l, y_c + bar_h/2)], top_color) - -y_c2 = 1440 * u -x_l2, x_r2, tip_x2 = 700 * u, 1660 * u, 360 * u -def bot_color(x, y): - t = clamp01((x_r2 - x) / (x_r2 - tip_x2)) - return lerp((96, 201, 111), (162, 243, 174), t) -put_poly([ - (x_r2, y_c2 - bar_h/2), (x_l2, y_c2 - bar_h/2), - (x_l2, y_c2 - bar_h*1.32), (tip_x2, y_c2), - (x_l2, y_c2 + bar_h*1.32), (x_l2, y_c2 + bar_h/2), - (x_r2, y_c2 + bar_h/2)], bot_color) - -w = 250 * u -ax, ay = 1470 * u, 840 * u -bx, by = 590 * u, 1230 * u -L = math.hypot(bx - ax, by - ay) -nx, ny = -(by - ay) / L * w / 2, (bx - ax) / L * w / 2 -def diag_color(x, y): - t = clamp01(((x - ax) * (bx - ax) + (y - ay) * (by - ay)) / (L * L)) - return lerp((247, 197, 109), (150, 232, 163), t) -put_poly([ - (ax + nx, ay + ny), (bx + nx, by + ny), - (bx - nx, by - ny), (ax - nx, ay - ny)], diag_color) - -out_n = N -rows = [] -for y4 in range(out_n): - row = bytearray([0]) - for x4 in range(out_n): - r = g = b = cnt = 0 - for dyy in range(SS): - for dxx in range(SS): - c = pixels[y4 * SS + dyy][x4 * SS + dxx] - if c is not None: - r += c[0]; g += c[1]; b += c[2]; cnt += 1 - row += bytes([r - rows.append(bytes(row)) - -def chunk(tag, data): - c = struct.pack(">I", len(data)) + tag + data - return c + struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF) - -ihdr = struct.pack(">IIBBBBB", out_n, out_n, 8, 6, 0, 0, 0) -png = b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) + chunk(b"IDAT", zlib.compress(b"".join(rows), 9)) + chunk(b"IEND", b"") - -out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "icon-src-1024.png") -with io.open(out, "wb") as f: - f.write(png) -print("written", out, len(png), "bytes") +# Z·SWITCH 应用图标生成: +# 读取 logo-src.png(品牌 Z 字箭头 logo,1024 RGBA), +# 应用 iOS 风格圆角裁切(1024 坐标,半径 232,超采样抗锯齿), +# 输出 icon-src-1024.png 供 `npx tauri icon` 生成全套平台图标。 + +SRC = "logo-src.png" +OUT_NAME = "icon-src-1024.png" +OUT = 1024 +CORNER = 232.0 +SS = 4 # 圆角抗锯齿超采样 +AA = CORNER / SS # 覆盖率过渡宽度(输出像素) + +# ---------- PNG 解码(纯标准库) ---------- + +def read_png(path): + with io.open(path, "rb") as f: + data = f.read() + assert data[:8] == b"\x89PNG\r\n\x1a\n", "not a PNG" + pos = 8 + idat = bytearray() + meta = None + palette = None + while pos < len(data): + length = struct.unpack(">I", data[pos:pos + 4])[0] + tag = data[pos + 4:pos + 8] + body = data[pos + 8:pos + 8 + length] + if tag == b"IHDR": + w, h, bit, ctype, comp, filt, inter = struct.unpack(">IIBBBBB", body) + assert bit == 8 and inter == 0, "unsupported PNG variant" + meta = (w, h, ctype) + elif tag == b"PLTE": + palette = body + elif tag == b"IDAT": + idat += body + elif tag == b"IEND": + break + pos += 12 + length + w, h, ctype = meta + channels = {0: 1, 2: 3, 3: 1, 4: 2, 6: 4}[ctype] + raw = zlib.decompress(bytes(idat)) + stride = w * channels + out = bytearray(w * h * 4) + prev = bytearray(stride) + p = 0 + for y in range(h): + ftype = raw[p]; p += 1 + line = bytearray(raw[p:p + stride]); p += stride + if ftype == 1: # Sub + for i in range(channels, stride): + line[i] = (line[i] + line[i - channels]) & 0xFF + elif ftype == 2: # Up + for i in range(stride): + line[i] = (line[i] + prev[i]) & 0xFF + elif ftype == 3: # Average + for i in range(stride): + a = line[i - channels] if i >= channels else 0 + line[i] = (line[i] + ((a + prev[i]) >> 1)) & 0xFF + elif ftype == 4: # Paeth + for i in range(stride): + a = line[i - channels] if i >= channels else 0 + b = prev[i] + c = prev[i - channels] if i >= channels else 0 + pa = abs(b - c); pb = abs(a - c); pc = abs(a + b - 2 * c) + pr = a if (pa <= pb and pa <= pc) else (b if pb <= pc else c) + line[i] = (line[i] + pr) & 0xFF + prev = line + for x in range(w): + o = (y * w + x) * 4 + if ctype == 6: + out[o:o + 4] = line[x * 4:x * 4 + 4] + elif ctype == 2: + out[o:o + 3] = line[x * 3:x * 3 + 3]; out[o + 3] = 255 + elif ctype == 0: + g = line[x]; out[o] = out[o + 1] = out[o + 2] = g; out[o + 3] = 255 + elif ctype == 4: + out[o] = out[o + 1] = out[o + 2] = line[x * 2]; out[o + 3] = line[x * 2 + 1] + elif ctype == 3: + idx = line[x] + out[o:o + 3] = palette[idx * 3:idx * 3 + 3]; out[o + 3] = 255 + return w, h, out + +# ---------- 圆角遮罩 + 重编码 ---------- + +def inside_rounded_aa(x, y, n, r): + """4×4 超采样的圆角覆盖率""" + cnt = 0 + for sy in range(SS): + for sx in range(SS): + px = x + (sx + 0.5) / SS + py = y + (sy + 0.5) / SS + cx = min(max(px, r), n - r) + cy = min(max(py, r), n - r) + if (px - cx) ** 2 + (py - cy) ** 2 <= r * r: + cnt += 1 + return cnt / (SS * SS) + +def write_png(path, w, h, rgba): + def chunk(tag, payload): + c = struct.pack(">I", len(payload)) + tag + payload + return c + struct.pack(">I", zlib.crc32(tag + payload) & 0xFFFFFFFF) + stride = w * 4 + raw = bytearray() + for y in range(h): + raw.append(0) + raw += rgba[y * stride:(y + 1) * stride] + ihdr = struct.pack(">IIBBBBB", w, h, 8, 6, 0, 0, 0) + png = b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) + chunk(b"IDAT", zlib.compress(bytes(raw), 9)) + chunk(b"IEND", b"") + with io.open(path, "wb") as f: + f.write(png) + +w, h, px = read_png(SRC) +assert w == OUT and h == OUT, f"logo-src.png 应为 {OUT}x{OUT}" +r = CORNER +for y in range(OUT): + # 仅在可能受圆角影响的边缘区域计算遮罩,加速中心区域 + near_edge = y < r or y >= OUT - r + for x in range(OUT): + o = (y * OUT + x) * 4 + if near_edge or x < r or x >= OUT - r: + a = inside_rounded_aa(x, y, OUT, r) + if a < 1.0: + px[o + 3] = int(px[o + 3] * a) +here = os.path.dirname(os.path.abspath(__file__)) +out = os.path.join(here, OUT_NAME) +write_png(out, OUT, OUT, px) +print("written", out, os.path.getsize(out), "bytes") + +# 额外输出 UI 用小尺寸 logo(public/logo.png,供前端 引用) +UI = 128 +step = OUT // UI +os.makedirs(os.path.join(here, "public"), exist_ok=True) +ui = bytearray() +for y in range(UI): + for x in range(UI): + r = g = b = a = 0 + for sy in range(step): + for sx in range(step): + o = ((y * step + sy) * OUT + (x * step + sx)) * 4 + pa = px[o + 3] + r += px[o] * pa; g += px[o + 1] * pa; b += px[o + 2] * pa; a += pa + if a == 0: + ui += bytes(4) + else: + ui += bytes([r // a, g // a, b // a, a // (step * step)]) +ui_out = os.path.join(here, "public", "logo.png") +write_png(ui_out, UI, UI, ui) +print("written", ui_out, os.path.getsize(ui_out), "bytes") diff --git a/package-lock.json b/package-lock.json index 013977d..beaf94c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "zcode-switch", - "version": "1.3.2", + "version": "1.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "zcode-switch", - "version": "1.3.2", + "version": "1.6.0", "license": "MIT", "dependencies": { "@tauri-apps/api": "^2.1.1" diff --git a/package.json b/package.json index afa6ee4..665b2c7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zcode-switch", "private": true, - "version": "1.5.3", + "version": "1.6.0", "type": "module", "scripts": { "dev": "vite", diff --git a/preview-settings.html b/preview-settings.html new file mode 100644 index 0000000..56f50b3 --- /dev/null +++ b/preview-settings.html @@ -0,0 +1,14 @@ + + + + + + Z·SWITCH settings — preview + + + +
+ + + + diff --git a/preview.html b/preview.html new file mode 100644 index 0000000..cfd6141 --- /dev/null +++ b/preview.html @@ -0,0 +1,14 @@ + + + + + + Z·SWITCH — preview + + + +
+ + + + diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..443b86d Binary files /dev/null and b/public/logo.png differ diff --git a/settings.html b/settings.html index f3e575a..11574d1 100644 --- a/settings.html +++ b/settings.html @@ -6,20 +6,20 @@ Z·SWITCH @@ -28,7 +28,9 @@
-
+
+ +
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 9c40506..4a8a58e 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -5317,7 +5317,7 @@ dependencies = [ [[package]] name = "zcode-switch" -version = "1.5.3" +version = "1.6.0" dependencies = [ "aes-gcm", "base64 0.22.1", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 18829a4..1e8ecc3 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zcode-switch" -version = "1.5.3" +version = "1.6.0" description = "ZCode 账号切换器" license = "MIT" edition = "2021" diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png index 770b283..93fe763 100644 Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png index e1d16cb..303caad 100644 Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png index b3ef1f5..0b7ff10 100644 Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png index 9009d66..39c4e77 100644 Binary files a/src-tauri/icons/64x64.png and b/src-tauri/icons/64x64.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png index 8d5c417..f95c25b 100644 Binary files a/src-tauri/icons/Square107x107Logo.png and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png index b816a62..f0f0225 100644 Binary files a/src-tauri/icons/Square142x142Logo.png and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png index 94fa626..e0148dc 100644 Binary files a/src-tauri/icons/Square150x150Logo.png and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png index 5a85f68..c8900be 100644 Binary files a/src-tauri/icons/Square284x284Logo.png and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png index 992751b..b1ac34b 100644 Binary files a/src-tauri/icons/Square30x30Logo.png and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png index 8ddb18e..8189df6 100644 Binary files a/src-tauri/icons/Square310x310Logo.png and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png index f9e7c73..01b4252 100644 Binary files a/src-tauri/icons/Square44x44Logo.png and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png index bd93bba..ad541b3 100644 Binary files a/src-tauri/icons/Square71x71Logo.png and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png index 293512e..3f076d5 100644 Binary files a/src-tauri/icons/Square89x89Logo.png and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png index c8b95ad..1871af3 100644 Binary files a/src-tauri/icons/StoreLogo.png and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png index 8209e7f..02791c5 100644 Binary files a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png index 82a8ce3..cdc5a4a 100644 Binary files a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png index 4771ff9..50ad068 100644 Binary files a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png index 283539c..e62f75a 100644 Binary files a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png index d4fe8b0..ddeaf59 100644 Binary files a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png index 1351e3b..0e6d1a3 100644 Binary files a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png index fd56fd4..dc7da78 100644 Binary files a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png index ce8ce9b..082f888 100644 Binary files a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png index 62089a8..06e73f7 100644 Binary files a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png index da3390b..9b8de79 100644 Binary files a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png index 06992a1..db4d708 100644 Binary files a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png index e818fce..260c220 100644 Binary files a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png index dbceecf..77eadcc 100644 Binary files a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png index 2157c72..5a127a7 100644 Binary files a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png index 2305f61..bcc2ee4 100644 Binary files a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns index 639cb22..a4c75e9 100644 Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index 5ab5d02..5c225aa 100644 Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png index 7c96a25..7461eb7 100644 Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/src-tauri/icons/ios/AppIcon-20x20@1x.png index 1fafaa4..9eba749 100644 Binary files a/src-tauri/icons/ios/AppIcon-20x20@1x.png and b/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png index 465feec..cb8f262 100644 Binary files a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png and b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x.png b/src-tauri/icons/ios/AppIcon-20x20@2x.png index 465feec..cb8f262 100644 Binary files a/src-tauri/icons/ios/AppIcon-20x20@2x.png and b/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@3x.png b/src-tauri/icons/ios/AppIcon-20x20@3x.png index 4ed9428..b1f7885 100644 Binary files a/src-tauri/icons/ios/AppIcon-20x20@3x.png and b/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@1x.png b/src-tauri/icons/ios/AppIcon-29x29@1x.png index cc11dc6..b7f2553 100644 Binary files a/src-tauri/icons/ios/AppIcon-29x29@1x.png and b/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png index 2a967e8..a43f5e5 100644 Binary files a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png and b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x.png b/src-tauri/icons/ios/AppIcon-29x29@2x.png index 2a967e8..a43f5e5 100644 Binary files a/src-tauri/icons/ios/AppIcon-29x29@2x.png and b/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@3x.png b/src-tauri/icons/ios/AppIcon-29x29@3x.png index 0b6c4bf..05c00f3 100644 Binary files a/src-tauri/icons/ios/AppIcon-29x29@3x.png and b/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@1x.png b/src-tauri/icons/ios/AppIcon-40x40@1x.png index 465feec..cb8f262 100644 Binary files a/src-tauri/icons/ios/AppIcon-40x40@1x.png and b/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png index 31aee01..9a6f37c 100644 Binary files a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png and b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x.png b/src-tauri/icons/ios/AppIcon-40x40@2x.png index 31aee01..9a6f37c 100644 Binary files a/src-tauri/icons/ios/AppIcon-40x40@2x.png and b/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@3x.png b/src-tauri/icons/ios/AppIcon-40x40@3x.png index 0e9f073..99cea1e 100644 Binary files a/src-tauri/icons/ios/AppIcon-40x40@3x.png and b/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-512@2x.png b/src-tauri/icons/ios/AppIcon-512@2x.png index 1880512..f7dc6f0 100644 Binary files a/src-tauri/icons/ios/AppIcon-512@2x.png and b/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/src-tauri/icons/ios/AppIcon-60x60@2x.png index 0e9f073..99cea1e 100644 Binary files a/src-tauri/icons/ios/AppIcon-60x60@2x.png and b/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@3x.png b/src-tauri/icons/ios/AppIcon-60x60@3x.png index 454027a..3c207fc 100644 Binary files a/src-tauri/icons/ios/AppIcon-60x60@3x.png and b/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@1x.png b/src-tauri/icons/ios/AppIcon-76x76@1x.png index 78527eb..0d721ab 100644 Binary files a/src-tauri/icons/ios/AppIcon-76x76@1x.png and b/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/src-tauri/icons/ios/AppIcon-76x76@2x.png index f8b2f64..c19e543 100644 Binary files a/src-tauri/icons/ios/AppIcon-76x76@2x.png and b/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png index 68a1a00..3402fe9 100644 Binary files a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png and b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 2dd6c7e..eb38cb1 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "zcode-switch", - "version": "1.5.3", + "version": "1.6.0", "identifier": "com.zswitch.app", "build": { "beforeDevCommand": "npm run dev", @@ -20,7 +20,9 @@ "resizable": true, "center": true, "theme": "Dark", - "backgroundColor": "#0a0a0c", + "backgroundColor": "#101012", + "titleBarStyle": "Overlay", + "hiddenTitle": true, "additionalBrowserArgs": "--disable-features=msWebOOUI,msPdfOOUI,msSmartScreenProtection --no-proxy-server" } ], diff --git a/src/icons.js b/src/icons.js index 235d3e8..73a7488 100644 --- a/src/icons.js +++ b/src/icons.js @@ -2,46 +2,46 @@ const P = (d) => ``; const ICONS = { - gauge: P("M2.5 12.5h11") + P("M3.6 12.5a4.4 4.4 0 0 1 8.8 0") + P("M8 12.5 10.8 8.2"), + gauge: P("M2.2 12.6a6.3 6.3 0 1 1 11.6 0") + P("M8 12.4 10.6 8.6") + ``, - pen: P("M2.5 13.5l.9-3.3 7.6-7.6 2.4 2.4-7.6 7.6z") + P("M9.9 4.9l2.4 2.4"), + pen: P("M2.7 13.3l.8-3 7.3-7.3a1.7 1.7 0 0 1 2.4 2.4l-7.3 7.3z") + P("M9.6 4.2l2.2 2.2"), - export: P("M8 9V2") + P("M5.2 4.8 8 2l2.8 2.8") + P("M2.5 10.5v1.5a1.5 1.5 0 0 0 1.5 1.5h8a1.5 1.5 0 0 0 1.5-1.5v-1.5"), + export: P("M8 9.5V2.5") + P("M5.2 5 8 2.2 10.8 5") + P("M2.8 10.2v1.6a1.7 1.7 0 0 0 1.7 1.7h7a1.7 1.7 0 0 0 1.7-1.7v-1.6"), - import: P("M8 2v7") + P("M5.2 6.2 8 9l2.8-2.8") + P("M2.5 10.5v1.5a1.5 1.5 0 0 0 1.5 1.5h8a1.5 1.5 0 0 0 1.5-1.5v-1.5"), + x: P("M4 4l8 8") + P("M12 4l-8 8"), - exportAll: P("M2.5 13.5h11") + P("M4.5 11h7") + P("M8 2v6") + P("M5.5 5.5 8 8l2.5-2.5"), + swap: P("M2.6 5.4h9.6") + P("M9.8 3 12.2 5.4 9.8 7.8") + P("M13.4 10.6H3.8") + P("M6.2 8.2 3.8 10.6l2.4 2.4"), - x: P("M3.5 3.5l9 9") + P("M12.5 3.5l-9 9"), + power: P("M8 2.2v5.6") + P("M4.9 4.4a4.9 4.9 0 1 0 6.2 0"), - swap: P("M2.5 5.5h9.5") + P("M9.5 3 12 5.5 9.5 8") + P("M13.5 10.5H4") + P("M6.5 8 4 10.5 6.5 13"), + sliders: P("M2.2 4.8h5") + P("M10.6 4.8h3.2") + P("M2.2 11.2h3.2") + P("M8.8 11.2h5") + `` + ``, - capture: P("M3 2.5h10") + P("M8 5.2v5.8") + P("M5.5 8.5 8 11l2.5-2.5") + P("M3 13.5h10"), + lock: `` + P("M5.5 7V5.2a2.5 2.5 0 0 1 5 0V7") + P("M8 9.6v1.8"), - gift: P("M2.5 6.5h11") + `` + P("M8 6.5v7") + P("M8 6.5 6 4.6") + P("M8 6.5 10 4.6"), + lockOpen: `` + P("M5.5 7V5.2a2.5 2.5 0 0 1 4.9-.6") + P("M8 9.6v1.8"), - userPlus: P("M6 6.8a2.3 2.3 0 1 0 0-4.6 2.3 2.3 0 0 0 0 4.6") + P("M2 13.5v-.9a4 4 0 0 1 8 0v.9") + P("M12 5.5v4") + P("M10 7.5h4"), + check: P("M3 8.5l3.4 3.3L13 4.8"), - play: ``, + alert: P("M8 2.3 14.7 13.7H1.3z") + P("M8 6.6v3") + ``, - power: P("M8 2v5.5") + P("M4.6 4.3a5 5 0 1 0 6.8 0"), - - sliders: P("M2 4.5h12") + P("M2 11.5h12") + `` + ``, - - lock: `` + P("M5.5 7.2V5a2.5 2.5 0 0 1 5 0v2.2") + P("M8 9.7v1.8"), - - lockOpen: `` + P("M5.5 7.2V5a2.5 2.5 0 0 1 4.9-.6") + P("M8 9.7v1.8"), - - check: P("M3 8.6l3.3 3.2L13 4.6"), - alert: P("M8 2.2 14.8 13.8H1.2z") + P("M8 6.4v3.2") + P("M8 11.6v.2"), + chev: P("M6 3.5 10.5 8 6 12.5"), +}; - empty: P("M3 3h10") + P("M3 13h10") + ``, +/* 24 系图标(外部图标库,如 Lucide),stroke 视觉粗细与 16 系对齐 */ +const ICONS24 = { + bot: ``, }; export function ic(name, size = 16, cls = "") { + const body24 = ICONS24[name]; + if (body24) { + return ``; + } const body = ICONS[name]; if (!body) return ""; return ``; + stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${body}`; } diff --git a/src/locales/en.js b/src/locales/en.js index 96d2761..483c683 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -83,6 +83,8 @@ export const en = { "m.emptyBody": "Use Save current login below to store your first account, or import account files", "m.saveLoginDisabledTitle": "Current login is already saved as \"{name}\"", "m.accounts": "ACCOUNTS · Library", + "m.signedInOne": "{count} account signed in", + "m.signedInOther": "{count} accounts signed in", "m.count": "{count} saved", "btn.saveLogin": "Save current login", @@ -96,6 +98,7 @@ export const en = { "btn.switch": "Switch", "btn.current": "Current", "btn.inUse": "In use", + "btn.selected": "Selected", "btn.quota": "Quota", "btn.rename": "Rename", "btn.export": "Export", @@ -125,7 +128,7 @@ export const en = { "s.title": "Settings", "s.behaviorLabel": "Behavior", - "s.langLabel": "LANGUAGE · Language", + "s.langLabel": "Language", "s.langZh": "中文", "s.langEn": "English", "s.autostart": "Launch at Windows startup", @@ -136,14 +139,14 @@ export const en = { "s.closeTrayDesc": "Clicking × hides to the system tray instead of quitting (main window only)", "s.hotSwitch": "Hot switch (no ZCode restart)", "s.hotSwitchDesc": "Off by default: switching changes the device identity, and the client only accepts the new device code after a restart. On = replace the login while running; open sessions keep the old account, and the client may keep using the old device code until restarted (at your own risk)", - "s.authLabel": "AUTH · Sign-in", + "s.authLabel": "Sign-in", "s.proxyToggle": "Proxy for the sign-in window", "s.proxyToggleDesc": "The z.ai login page routes by exit IP: mainland IPs only get phone-number login; email login needs an overseas exit. When on, only the login window opened by \"Add account\" uses this proxy; quota / claim / switch traffic is unchanged", "s.proxyPh": "http://127.0.0.1:7890 or socks5://127.0.0.1:1080", - "s.libLabel": "LIBRARY · Accounts", + "s.libLabel": "Accounts", "s.importBtn": "Import account files", "s.exportAllBtn": "Export all (encrypted)", - "s.pathLabel": "ZCODE PATH · ZCode executable", + "s.pathLabel": "ZCode executable", "s.browse": "Browse", "s.githubLink": "GitHub repository · feedback & updates", "s.hint": "Switching replaces both credentials.json and config.json; any unsaved current login is auto-preserved before switching — accounts are never lost.", diff --git a/src/locales/zh.js b/src/locales/zh.js index 8bfbe0a..8c59e06 100644 --- a/src/locales/zh.js +++ b/src/locales/zh.js @@ -83,6 +83,8 @@ export const zh = { "m.emptyBody": "先用下方 保存当前登录 存入第一个账号,或导入账号文件", "m.saveLoginDisabledTitle": "当前登录已是存档「{name}」,无需重复保存", "m.accounts": "ACCOUNTS · 账号库", + "m.signedInOne": "已登录 {count} 个账号", + "m.signedInOther": "已登录 {count} 个账号", "m.count": "{count} 个存档", "btn.saveLogin": "保存当前登录", @@ -96,6 +98,7 @@ export const zh = { "btn.switch": "切换", "btn.current": "当前", "btn.inUse": "使用中", + "btn.selected": "已选", "btn.quota": "查额度", "btn.rename": "重命名", "btn.export": "导出", @@ -125,7 +128,7 @@ export const zh = { "s.title": "设置", "s.behaviorLabel": "行为", - "s.langLabel": "LANGUAGE · 语言", + "s.langLabel": "语言", "s.langZh": "中文", "s.langEn": "English", "s.autostart": "开机自启动", @@ -136,14 +139,14 @@ export const zh = { "s.closeTrayDesc": "点 × 不退出,最小化到系统托盘(仅主窗口)", "s.hotSwitch": "热切换(不重启 ZCode)", "s.hotSwitchDesc": "默认关:切换要更换设备身份,客户端重启后才认新设备码。开启 = 运行中直接替换登录态,已打开的会话仍用原账号,且客户端可能继续用旧设备码直至重启(自担风险)", - "s.authLabel": "AUTH · 授权登录", + "s.authLabel": "授权登录", "s.proxyToggle": "授权登录走代理", "s.proxyToggleDesc": "z.ai 登录页按出口 IP 分流:国内 IP 只显示手机号登录,邮箱登录需海外出口。开启后仅「添加账号」弹出的登录窗口走此代理;额度查询 / 领取 / 切换等流量不变", "s.proxyPh": "http://127.0.0.1:7890 或 socks5://127.0.0.1:1080", - "s.libLabel": "LIBRARY · 账号库", + "s.libLabel": "账号库", "s.importBtn": "导入账号文件", "s.exportAllBtn": "导出全部(加密)", - "s.pathLabel": "ZCODE PATH · ZCode 程序路径", + "s.pathLabel": "ZCode 程序路径", "s.browse": "浏览", "s.githubLink": "GitHub 仓库 · 问题反馈与更新", "s.hint": "切换会同时替换 credentials.json 与 config.json;未入库的当前登录切换前会自动保存,绝不丢号。", diff --git a/src/main.js b/src/main.js index 8407706..b707ce8 100644 --- a/src/main.js +++ b/src/main.js @@ -5,6 +5,8 @@ import { ic } from "./icons.js"; import { init, t, has, lang, localeTag, stripErr } from "./i18n.js"; const $app = document.getElementById("app"); +const isMac = /Macintosh|MacIntel/.test(navigator.userAgent); +if (isMac) document.body.classList.add("mac"); let state = null; let renaming = null; let busy = false; @@ -12,13 +14,6 @@ let acctQuota = {}; let claimable = {}; let claimAllRunning = false; -const NOTCH_COLORS = ["var(--notch-1)", "var(--notch-2)", "var(--notch-3)", "var(--notch-4)", "var(--notch-5)", "var(--notch-6)"]; -function notchColor(id) { - let h = 0; - for (const c of id) h = (h * 31 + c.charCodeAt(0)) >>> 0; - return NOTCH_COLORS[h % NOTCH_COLORS.length]; -} - function fmtNum(v) { if (v == null) return t("q.unknown"); const n = Number(v); @@ -309,13 +304,12 @@ const actions = { }, }; -function quotaBarHtml(pct) { +function quotaMeterHtml(pct) { const used = pct == null ? null : Math.min(100, Math.max(0, pct)); - const remaining = used == null ? null : 100 - used; - const danger = used != null && used >= 90 ? " danger" : used != null && used >= 70 ? " warn" : ""; - const txt = remaining == null ? "--" : remaining.toFixed(0) + "%"; - const txtCls = (remaining ?? 100) >= 58 ? " in-fill" : ""; - return `
${txt}
`; + const cls = used != null && used >= 90 ? " danger" : used != null && used >= 70 ? " warn" : ""; + const rem = used == null ? null : 100 - used; + const txt = rem == null ? "--" : rem.toFixed(0) + "%"; + return `
${txt}
`; } function itemKind(it) { @@ -343,8 +337,8 @@ function resetLabel(it) { function winRowHtml(it, cls = "") { return `
- ${esc(windowLabel(it))} - ${quotaBarHtml(it.percent_used)} + ${esc(windowLabel(it))} + ${quotaMeterHtml(it.percent_used)} ${it.reset || it.period_end ? esc(resetLabel(it)) : ""}
`; } @@ -368,7 +362,7 @@ function balRowHtml(it) { return `
${esc(label)} - ${quotaBarHtml(it.percent_used)} + ${quotaMeterHtml(it.percent_used)} ${esc(rem)}
`; } @@ -415,10 +409,9 @@ function claimStripHtml(id) { const label = plan.name || plan.plan_id; return `
- ${ic("gift", 15)} ${esc(label)} ${grants ? `${esc(grants)}` : ""} - +
`; } @@ -517,7 +510,7 @@ function restoreScroll(cap) { function render() { const scrollCap = captureScroll(); if (!state) { - $app.innerHTML = `
LOADING
`; + $app.innerHTML = `
${t("q.loading")}
`; return; } const s = state; @@ -535,43 +528,44 @@ function render() { const isActive = a.is_active; if (renaming === a.id) { return `
- -
- -
${t("btn.renameMeta")}
-
-
- - +
+
+ +
${t("btn.renameMeta")}
+
+
+ + +
`; } const ident = [a.identity?.username, a.identity?.email].filter(Boolean).join(" · "); const q = acctQuota[a.id]; - let meta = ""; - if (!a.has_config) meta += `${t("q.noCfg")}`; + const metaParts = []; + if (!a.has_config) metaParts.push(`${t("q.noCfg")}`); const exp = expireInfo(q?.data?.plan_expire); if (exp) { - meta += `${esc(t("q.validUntil", { date: exp.text }))}`; + metaParts.push(`${esc(t("q.validUntil", { date: exp.text }))}`); } - if (ident) meta += `${meta ? " · " : ""}${esc(ident)}`; + if (ident) metaParts.push(`${esc(ident)}`); + const meta = metaParts.join('·'); return `
-
-
${esc(a.name)}${tierBadgeFor(a.id)}${isActive ? `${t("btn.inUse")}` : ""}
+
${esc(a.name)}${tierBadgeFor(a.id)}
${meta}
- - - - - + + + + + ${isActive + ? `` + : ``}
${acctQuotaSlot(a.id)} @@ -580,7 +574,6 @@ function render() { const listHtml = s.accounts.length === 0 ? `
-
${ic("empty", 34)}
${t("m.emptyTitle")}
${t("m.emptyBody")}
` @@ -588,37 +581,35 @@ function render() { const claimableCount = s.accounts.filter((a) => (claimable[a.id]?.plans || []).length > 0).length; + const countText = s.accounts.length === 1 + ? t("m.signedInOne", { count: s.accounts.length }) + : t("m.signedInOther", { count: s.accounts.length }); $app.innerHTML = ` -
-
Z·SWITCH
-
- +
+
+ ${ic("bot", 15)} ${esc(statusText)}
+ ${esc(countText)}
${claimableCount > 0 ? `` + title="${t("btn.claimAllTitle")}">${t("btn.claimAll")}${claimableCount > 1 ? ` (${claimableCount})` : ""}` : ""} - + ${s.zcode_running - ? `` - : ``} + ? `` + : ``}
-
-

${t("m.accounts")}

- ${t("m.count", { count: s.accounts.length })} -
-
${listHtml}
`; restoreScroll(scrollCap); diff --git a/src/preview-mock.js b/src/preview-mock.js new file mode 100644 index 0000000..33dfde9 --- /dev/null +++ b/src/preview-mock.js @@ -0,0 +1,133 @@ +// 开发预览专用:在纯浏览器里模拟 Tauri IPC,便于 UI 开发与截图。 +// 仅 dev 使用;`vite build` 的 rollupOptions.input 不含 preview.html,不会进产物。 + +function pad(n) { return String(n).padStart(2, "0"); } +function fmt(d) { + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; +} +const NOW = Date.now(); +const at = (min) => fmt(new Date(NOW + min * 60000)); + +const ACCOUNTS = [ + { id: "acct-1", name: "大号", is_active: true, has_config: true, identity: { username: "zhang_dev", email: "zhang***@gmail.com" } }, + { id: "acct-2", name: "工作号", is_active: false, has_config: true, identity: { username: "li_work" } }, + { id: "acct-3", name: "体验号", is_active: false, has_config: true, identity: { email: "trial***@z.ai" } }, +]; + +const QUOTAS = { + "acct-1": { + plan_expire: "2027-07-28", + plans: [ + { + tier: "Max", tier_code: "max", name: "GLM Coding Max", expire: "2027-07-28", + items: [ + { kind: "prompt_count", name: "提示次数(每5小时)", window: "hours:5", percent_used: 3, reset: at(320) }, + { kind: "prompt_count", name: "提示次数(每周)", window: "week", percent_used: 42, reset: at(4200) }, + { kind: "prompt_count", name: "提示次数(每月)", window: "month", percent_used: 12, reset: at(9800) }, + ], + }, + { + tier: "体验", tier_code: "trial", name: "ZCode Weekend Build", expire: "2026-08-31", + items: [ + { name: "GLM-5.3-Flash", total: 300000000, remaining: 90000000, percent_used: 70 }, + ], + }, + ], + }, + "acct-2": { + plan_expire: "2026-12-01", + plans: [ + { + tier: "Pro", tier_code: "pro", name: "GLM Coding Pro", expire: "2026-12-01", + items: [ + { kind: "prompt_count", name: "提示次数(每5小时)", window: "hours:5", percent_used: 74, reset: at(140) }, + { kind: "prompt_count", name: "提示次数(每月)", window: "month", percent_used: 55, reset: at(12000) }, + { name: "GLM-5.3", total: 120000000, remaining: 54000000, percent_used: 55 }, + ], + }, + ], + }, + "acct-3": { + plan_expire: "2026-09-06", + plans: [ + { + tier: "体验", tier_code: "trial", name: "GLM Flash 体验", expire: "2026-09-06", + items: [ + { kind: "prompt_count", name: "提示次数(每5小时)", window: "hours:5", percent_used: 95, reset: at(48) }, + { name: "GLM-5.3-Flash", total: 5000000, remaining: 250000, percent_used: 95 }, + ], + }, + ], + }, +}; + +const CLAIMS = { + "acct-2": [{ + plan_id: "weekend-build", + name: "ZCode Weekend Build", + description: "周末限时体验包", + grant_items: [{ name: "GLM-5.3-Flash", units: 300000000, period: "one_time" }], + grants: [], + }], + "acct-3": [{ + plan_id: "weekend-build", + name: "ZCode Weekend Build", + description: "周末限时体验包", + grant_items: [{ name: "GLM-5.3-Flash", units: 300000000, period: "one_time" }], + grants: [], + }], +}; + +function mockInvoke(cmd, args = {}) { + switch (cmd) { + case "get_state": + return Promise.resolve({ + language: "zh", + zcode_running: true, + live_logged_in: true, + hot_switch: false, + launch_after_switch: true, + close_to_tray: true, + zcode_path_ok: true, + auth_proxy_on: false, + auth_proxy_url: "", + zcode_path: "C:\\Program Files\\ZCode\\ZCode.exe", + accounts: ACCOUNTS, + }); + case "get_account_quota": + return Promise.resolve(QUOTAS[args.id] || { plans: [], items: [] }); + case "claim_preview": + return Promise.resolve(CLAIMS[args.id] || []); + case "oauth_providers": + return Promise.resolve([ + { id: "bigmodel", display: "BigModel" }, + { id: "zai", display: "z.ai" }, + ]); + case "autostart_status": + return Promise.resolve(false); + case "reveal_main": + case "open_settings": + case "launch_zcode": + case "kill_zcode": + case "switch_to": + return Promise.resolve({ name: "工作号", hot: false, killed: false, preserved_as: null, launched: false, config_stale: false }); + default: + return Promise.resolve({}); + } +} + +let cbId = 1; +window.__TAURI_INTERNALS__ = { + invoke: (cmd, args) => mockInvoke(cmd, args), + transformCallback: (cb) => { + const id = cbId++; + window[`_${id}`] = cb; + return id; + }, + unregisterCallback: () => {}, + metadata: { + currentWindow: { label: "main" }, + currentWebview: { label: "main", windowLabel: "main" }, + }, + plugins: {}, +}; diff --git a/src/settings.js b/src/settings.js index bb7737b..3be4b88 100644 --- a/src/settings.js +++ b/src/settings.js @@ -5,6 +5,8 @@ import { ic } from "./icons.js"; import { init, t, lang, stripErr } from "./i18n.js"; const $app = document.getElementById("app"); +const isMac = /Macintosh|MacIntel/.test(navigator.userAgent); +if (isMac) document.body.classList.add("mac"); let state = null; let autostart = false; let busy = false; @@ -160,39 +162,45 @@ const langSeg = (cur) => ` function render() { if (!state) { - $app.innerHTML = `
LOADING
`; + $app.innerHTML = `
${t("q.loading")}
`; return; } const s = state; document.title = `Z·SWITCH ${t("s.title")}`; $app.innerHTML = ` -
-
Z·SWITCH / ${t("s.title")}
-
+
- ${langSeg(s.language || "zh")} - - ${toggle(autostart, "actions.toggleAutostart()", t("s.autostart"), t("s.autostartDesc"))} - ${toggle(s.launch_after_switch, "actions.toggleBehavior('launch')", t("s.launchAfter"), t("s.launchAfterDesc"))} - ${toggle(s.close_to_tray, "actions.toggleBehavior('tray')", t("s.closeTray"), t("s.closeTrayDesc"))} - ${toggle(s.hot_switch, "actions.toggleBehavior('hot')", t("s.hotSwitch"), t("s.hotSwitchDesc"))} - - ${toggle(s.auth_proxy_on, "actions.toggleAuthProxy()", t("s.proxyToggle"), t("s.proxyToggleDesc"))} -
- - +
${langSeg(s.language || "zh")}
+
${t("s.behaviorLabel")}
+
+ ${toggle(autostart, "actions.toggleAutostart()", t("s.autostart"), t("s.autostartDesc"))} + ${toggle(s.launch_after_switch, "actions.toggleBehavior('launch')", t("s.launchAfter"), t("s.launchAfterDesc"))} + ${toggle(s.close_to_tray, "actions.toggleBehavior('tray')", t("s.closeTray"), t("s.closeTrayDesc"))} + ${toggle(s.hot_switch, "actions.toggleBehavior('hot')", t("s.hotSwitch"), t("s.hotSwitchDesc"))}
- -
- - +
${t("s.authLabel")}
+
+ ${toggle(s.auth_proxy_on, "actions.toggleAuthProxy()", t("s.proxyToggle"), t("s.proxyToggleDesc"))} +
+ + +
- -
- - - +
${t("s.libLabel")}
+
+
+ + +
+
+
${t("s.pathLabel")}
+
+
+ + + +
${t("s.hint")}
diff --git a/src/styles.css b/src/styles.css index d1a4999..51661d4 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,40 +1,56 @@ +/* ============================================================ + Z·SWITCH — quiet professional + 中性灰度为主,品牌绿仅用于主操作与激活指示。 + 无渐变、无光晕、无毛玻璃;层级靠字号/字重/灰度, + 数字与时间用等宽字体,4/8px 间距栅格。 + ============================================================ */ + :root { - --bg: #16171b; - --bg-2: #1b1d22; - --panel: #1f2127; - --panel-2: #23252c; - --hairline: #2c2f37; - --hairline-soft: #26282f; - --ink: #e9e7e1; - --ink-dim: #b3b6bf; - --ink-mute: #838794; - --amber: #e8a33d; - --amber-deep: #c9862a; - --amber-ink: #241a09; - --mint: #62c370; - --mint-dim: #3d8a4a; - --red: #e05252; - --red-dim: #8f3434; - --mono: "Cascadia Code", "Consolas", "SF Mono", monospace; - --body: "Segoe UI", "Microsoft YaHei UI", "PingFang SC", sans-serif; - --notch-1: #e8a33d; - --notch-2: #6da8e8; - --notch-3: #b78ae0; - --notch-4: #e07b9a; - --notch-5: #6dc8b8; - --notch-6: #c9b458; + --bg: #101012; + --surface: #161618; + --surface-2: #1b1b1e; + --inset: #0c0c0e; + + --line: #27272b; + --line-soft: #1e1e21; + + --fg: #e9e9eb; + --fg-2: #9fa0a6; + --fg-3: #6c6d73; + + --accent: #ffffff; + --accent-hover: #ececee; + --accent-ink: #101013; + --accent-dim: #b6b6bc; + --accent-line: rgba(255, 255, 255, .4); + + --warn: #e08a4a; + --bad: #e0574b; + + /* 旧别名(少量内联样式引用) */ + --red: var(--bad); + --ok: var(--accent); + + --font-ui: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI Variable Text", + "Segoe UI", "PingFang SC", "Microsoft YaHei UI", "Helvetica Neue", system-ui, sans-serif; + --font-mono: "SF Mono", "Cascadia Code", "JetBrains Mono", ui-monospace, "Consolas", + "PingFang SC", monospace; + + --r: 6px; + --r-lg: 10px; + --shadow-pop: 0 16px 48px rgba(0, 0, 0, .5); } * { box-sizing: border-box; margin: 0; padding: 0; } html, body { height: 100%; - background: linear-gradient(180deg, var(--bg-2) 0%, var(--bg) 220px); - color: var(--ink); - font-family: var(--body); - font-size: 16.5px; - line-height: 1.6; + background: var(--bg); + color: var(--fg); + font-family: var(--font-ui); + font-size: 13px; + line-height: 1.5; -webkit-font-smoothing: antialiased; user-select: none; } @@ -48,852 +64,778 @@ html, body { } body.settings-win #app { overflow-y: auto; } +::selection { background: rgba(255, 255, 255, .2); } + +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-thumb { + background: #2a2a2e; + border-radius: 99px; + border: 3px solid transparent; + background-clip: content-box; +} +::-webkit-scrollbar-thumb:hover { background: #38383d; background-clip: content-box; } +::-webkit-scrollbar-track { background: transparent; } + +/* ---------- 控件基线 ---------- */ + button { font-family: inherit; - font-size: 15.5px; + font-size: 12.5px; border: 1px solid transparent; - border-radius: 6px; + border-radius: var(--r); cursor: pointer; - padding: 7px 14px; + padding: 6px 12px; background: none; - color: inherit; + color: var(--fg-2); transition: background .12s ease, border-color .12s ease, color .12s ease, opacity .12s ease; } -button:disabled { opacity: .45; cursor: not-allowed; } +button:disabled { opacity: .4; cursor: not-allowed; } -.ic { display: block; flex: none; } -button.has-ic { - display: inline-flex; - align-items: center; - gap: 7px; +button.has-ic { display: inline-flex; align-items: center; gap: 7px; } + +input[type="text"], input[type="password"] { + font-family: var(--font-mono); + font-size: 12.5px; + background: var(--inset); + border: 1px solid var(--line); + border-radius: var(--r); + color: var(--fg); + padding: 7px 10px; + transition: border-color .12s ease; + user-select: text; +} +input[type="text"]:focus, input[type="password"]:focus { + outline: none; + border-color: var(--accent-line); } -.icon-btn .ic { margin: 0 auto; } -button:focus-visible, -input:focus-visible, -.row-actions a:focus-visible { - outline: 2px solid var(--amber); +button:focus-visible, input:focus-visible, .row-actions a:focus-visible, +[role="switch"]:focus-visible, [role="radio"]:focus-visible, [role="radio"]:focus-within { + outline: 2px solid var(--accent-line); outline-offset: 1px; } +input:focus-visible { outline: none; } + +/* ---------- 顶栏 ---------- */ .topbar { - padding: 18px 18px 14px; + padding: 12px 20px; display: flex; - align-items: baseline; - justify-content: space-between; - border-bottom: 1px solid var(--hairline-soft); + align-items: center; + gap: 16px; + border-bottom: 1px solid var(--line-soft); + flex: none; } -.wordmark { - font-family: var(--mono); - font-size: 16.5px; - letter-spacing: .18em; - color: var(--ink); - font-weight: 600; +body.mac .topbar { + padding: 9px 20px 10px 84px; /* 左侧避让 macOS 红绿灯,与系统标题栏同一行 */ } -.wordmark .tick { color: var(--amber); } -.topbar .ver { - font-family: var(--mono); - font-size: 12.5px; - color: var(--ink-mute); - letter-spacing: .08em; -} - -.live-wrap { padding: 14px 18px 4px; } - -.live-card { - position: relative; - overflow: hidden; - border: 1px solid var(--hairline); - border-radius: 10px; - background: var(--panel); -} -.live-card.unsaved { border-color: #4a3d22; } - -.stripe { +.tb-drag { display: none; } +body.mac .tb-drag { + display: block; height: 26px; - background: - repeating-linear-gradient(115deg, - #101116 0px, #101116 10px, - #171921 10px, #171921 20px); - border-bottom: 1px solid var(--hairline-soft); - position: relative; + flex: none; } -.stripe::after { - content: ""; - position: absolute; - right: 12px; top: 8px; - width: 34px; height: 10px; - border-radius: 2px; - background: linear-gradient(90deg, #3a3d46, #555a66); - opacity: .9; +.acct-count { + font-size: 12px; + color: var(--fg-3); + white-space: nowrap; } -.live-body { - display: flex; +.top-status { + display: inline-flex; align-items: center; - gap: 14px; - padding: 15px 16px 16px; + gap: 7px; + font-size: 12px; + color: var(--fg-2); + white-space: nowrap; } +.top-status .ic { color: var(--fg-3); } +.top-status .status-text { overflow: hidden; text-overflow: ellipsis; } +.top-status.off, .top-status.off .ic { color: var(--fg-3); } +.top-status.unsaved, .top-status.unsaved .ic { color: var(--warn); } +@keyframes pulse { 50% { opacity: .35; } } -.live-main { flex: 1; min-width: 0; } -.live-label { - font-family: var(--mono); - font-size: 11.5px; - letter-spacing: .22em; - color: var(--ink-mute); - margin-bottom: 3px; +/* ---------- 工具栏 ---------- */ + +.toolbar { + margin: 12px 20px 0; + display: flex; + flex-wrap: wrap; /* 放不下时换行,绝不挤压按钮 */ + gap: 8px; + align-items: center; + flex: none; } -.live-name { - font-size: 22px; - font-weight: 600; - color: var(--ink); +.toolbar button { + height: 32px; + padding: 0 12px; + display: inline-flex; + align-items: center; + flex: none; white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; } -.live-sub { - font-family: var(--mono); - font-size: 13px; - color: var(--ink-dim); - margin-top: 3px; - letter-spacing: .04em; -} -.live-sub .warn { color: var(--amber); } +.tb-spacer { flex: 1; } +.toolbar .tb-gear { width: 32px; padding: 0; justify-content: center; } -.live-led { display: flex; flex-direction: column; align-items: center; gap: 5px; min-width: 64px; } -.led { - width: 10px; height: 10px; - border-radius: 50%; - background: var(--mint); - box-shadow: 0 0 8px rgba(98, 195, 112, .5); -} -.led.run { - background: var(--red); - box-shadow: 0 0 8px rgba(224, 82, 82, .55); - animation: pulse 1.6s ease-in-out infinite; +.btn-primary { + background: var(--accent); + color: var(--accent-ink); + font-weight: 600; } -.led.off { background: #4a4e57; box-shadow: none; } -@keyframes pulse { 50% { opacity: .45; } } -.led-text { - font-size: 13px; - color: var(--ink-dim); - text-align: center; - line-height: 1.3; +.btn-primary:hover:not(:disabled) { background: var(--accent-hover); } +.btn-primary.attention { animation: ring 2.4s ease-out infinite; } +@keyframes ring { + 0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, .3); } + 70% { box-shadow: 0 0 0 6px rgba(255, 255, 255, 0); } + 100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); } } -.led-text b { color: var(--ink); font-weight: 600; } -.section-head { - display: flex; - align-items: baseline; - justify-content: space-between; - padding: 16px 18px 8px; -} -.section-head h2 { - font-family: var(--mono); - font-size: 12.5px; - font-weight: 600; - letter-spacing: .22em; - color: var(--ink-mute); +.btn-ghost { + border: 1px solid var(--line); + color: var(--fg-2); } -.section-head .count { - font-family: var(--mono); - font-size: 12.5px; - color: var(--ink-mute); +.btn-ghost:hover:not(:disabled) { + color: var(--fg); + border-color: #3a3a40; + background: var(--surface-2); } +.toolbar .claim-all { color: var(--fg); border-color: #3a3a40; } +.toolbar .claim-all:hover:not(:disabled) { border-color: #4a4a51; } +.toolbar .claim-all:disabled { opacity: .5; cursor: default; } + +/* ---------- 分区标题 ---------- */ + +/* ---------- 账号列表 ---------- */ .list { flex: 1; min-height: 0; /* 允许 flex 子项收缩,滚动约束在本区内 */ overflow-y: auto; - padding: 0 18px 12px; - scrollbar-width: thin; - scrollbar-color: #34373f transparent; + padding: 14px 20px 20px; } .empty { - border: 1px dashed var(--hairline); - border-radius: 10px; - padding: 30px 20px; + border: 1px dashed var(--line); + border-radius: var(--r-lg); + padding: 36px 20px; text-align: center; - color: var(--ink-mute); - font-size: 16px; -} -.empty .glyph { - margin-bottom: 10px; - color: #3d4049; + color: var(--fg-3); + font-size: 12.5px; + line-height: 1.9; } -.empty .glyph .ic { margin: 0 auto; } -.empty b { color: var(--ink-dim); font-weight: 600; } .row { display: flex; flex-direction: column; - border: 1px solid var(--hairline-soft); - border-radius: 8px; - background: var(--panel); - padding: 12px 12px 11px 16px; - margin-bottom: 9px; + border: 1px solid var(--line-soft); + border-radius: var(--r-lg); + background: var(--surface); + padding: 11px 12px 11px 16px; + margin-bottom: 8px; position: relative; transition: border-color .12s ease, background .12s ease; } .row-top { display: flex; align-items: center; - gap: 10px; + gap: 12px; } -.row:hover, .row:focus-within { border-color: var(--hairline); background: var(--panel-2); } -.row.active { border-color: #3b4a3d; background: #1d241f; } - -.notch { - position: absolute; - left: 0; top: 8px; bottom: 8px; - width: 3px; - border-radius: 2px; +.row:hover, .row:focus-within { + border-color: var(--line); + background: var(--surface-2); } +.row.active { border-color: var(--line); background: var(--surface-2); } .row-main { flex: 1; min-width: 0; } .row-name { - font-size: 17px; + font-size: 13.5px; font-weight: 600; - color: var(--ink); + color: var(--fg); display: flex; align-items: center; - gap: 7px; + gap: 8px; white-space: nowrap; overflow: hidden; } -.tag-use { - font-family: var(--mono); - font-size: 11.5px; - letter-spacing: .1em; - color: var(--mint); - border: 1px solid var(--mint-dim); - border-radius: 3px; - padding: 0 4px; - flex: none; -} .row-meta { - font-family: var(--mono); - font-size: 12.5px; - color: var(--ink-mute); + font-size: 11.5px; + color: var(--fg-3); margin-top: 2px; - letter-spacing: .03em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + display: flex; + align-items: baseline; + gap: 7px; + min-width: 0; +} +.row-meta .tier-b { flex: none; } +.row-meta .meta-dot { flex: none; color: var(--fg-3); opacity: .7; } +.row-meta > span:not(.tier-b) { + overflow: hidden; + text-overflow: ellipsis; } .row-actions { display: flex; align-items: center; - gap: 6px; + gap: 2px; flex: none; } .icon-btn { - width: 32px; height: 32px; + width: 26px; height: 26px; padding: 0; display: inline-flex; align-items: center; justify-content: center; - border: 1px solid var(--hairline-soft); - border-radius: 5px; - color: var(--ink-mute); - font-size: 16px; - background: var(--bg-2); + border: 1px solid transparent; + border-radius: var(--r); + color: var(--fg-3); + background: transparent; } -.icon-btn:hover { color: var(--ink); border-color: var(--hairline); background: var(--panel-2); } -.icon-btn.danger:hover { color: var(--red); border-color: var(--red-dim); background: rgba(224, 82, 82, .08); } +.icon-btn:hover { + color: var(--fg); + border-color: var(--line); + background: var(--surface); +} +.icon-btn.danger:hover { color: var(--bad); } .btn-switch { - border: 1px solid var(--amber-deep); - color: var(--amber); - font-weight: 600; - height: 32px; - padding: 0 12px; - border-radius: 6px; - background: rgba(232, 163, 61, .06); + border: 1px solid var(--line); + color: var(--fg-2); + font-weight: 500; + height: 26px; + padding: 0 11px; + margin-left: 6px; + border-radius: var(--r); display: inline-flex; align-items: center; justify-content: center; + font-size: 12px; } .btn-switch:hover:not(:disabled) { - background: rgba(232, 163, 61, .16); - border-color: var(--amber); + color: var(--accent); + border-color: var(--accent-line); +} +.btn-switch:disabled { + color: var(--fg-3); + opacity: 1; + cursor: default; } .row .rename-input { - font-family: var(--body); - font-size: 17px; + font-family: inherit; + font-size: 13.5px; font-weight: 600; - color: var(--ink); - background: var(--bg-2); - border: 1px solid var(--amber-deep); - border-radius: 4px; - padding: 2px 6px; + color: var(--fg); + padding: 3px 8px; width: 100%; - max-width: 180px; + max-width: 200px; + border-radius: var(--r); } -.row .rename-input:focus { outline: none; border-color: var(--amber); } -.confirm-box { +/* ---------- 额度区 ---------- */ + +.row-quota-slot { + width: 100%; + min-height: 24px; + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid var(--line-soft); display: flex; + flex-direction: column; + justify-content: center; + gap: 6px; +} + +.q-win { + display: grid; + grid-template-columns: 56px 1fr 124px; align-items: center; - gap: 8px; - font-size: 15.5px; - color: var(--ink-dim); + gap: 10px; } -.confirm-box .yes { - border-color: var(--red-dim); - color: var(--red); - padding: 3px 10px; +.q-win-label { + font-size: 11.5px; + color: var(--fg-2); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; } -.confirm-box .yes:hover { background: rgba(224, 82, 82, .12); } -.confirm-box .no { padding: 3px 10px; color: var(--ink-mute); } -.confirm-box .no:hover { color: var(--ink); border-color: var(--hairline); } - -.toolbar { - margin: 10px 18px 0; - padding: 0; +.q-meter { display: flex; - flex-wrap: wrap; /* 放不下时换行,绝不挤压按钮 */ - gap: 6px; - align-items: center; + height: 14px; + border-radius: 3px; + overflow: hidden; + min-width: 0; } +.q-meter-pct { + width: 47px; + flex: none; + display: grid; + place-items: center; + background: #2b2b30; + font-family: var(--font-mono); + font-size: 10px; + color: var(--fg-2); +} +.q-win-reset { + font-family: var(--font-mono); + font-size: 10px; + color: var(--fg-3); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: right; +} +.qbar { + flex: 1; + min-width: 0; + height: 100%; + background: #1a1a1d; + overflow: hidden; +} +.qbar-fill { + height: 100%; + background-color: #93939a; + background-image: repeating-linear-gradient(-55deg, + rgba(16, 16, 18, .13) 0 4px, transparent 4px 9px); + transition: width .3s ease; +} +.qbar.warn .qbar-fill { background-color: #c47435; } +.qbar.danger .qbar-fill { background-color: #c94f44; } +.q-win.mini { + grid-template-columns: 56px 1fr 124px; + gap: 8px; +} +.q-win.mini .q-win-label { font-size: 11px; } +.q-win.mini .q-pct { font-size: 10.5px; } +.q-win.mini .q-win-reset { font-size: 9.5px; } -.btn-primary { - background: var(--amber); - color: var(--amber-ink); - font-weight: 600; - font-size: 14.5px; - padding: 6px 14px; - border-radius: 6px; - border: 1px solid var(--amber); +.aq-loading { color: var(--fg-3); font-size: 11.5px; display: inline-flex; align-items: center; gap: 8px; } +.aq-loading::before { + content: ""; + width: 10px; height: 10px; + border-radius: 50%; + border: 1.5px solid var(--line); + border-top-color: var(--fg-2); + animation: spin .8s linear infinite; } -.btn-primary:hover:not(:disabled) { background: #f0b055; } -.btn-primary.attention { box-shadow: 0 0 0 1px var(--bg-2), 0 0 14px rgba(232, 163, 61, .35); } +@keyframes spin { to { transform: rotate(360deg); } } +.aq-err { color: #c98d86; font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.btn-ghost { - border: 1px solid var(--hairline); - color: var(--ink-dim); - padding: 8px 13px; - border-radius: 7px; - background: var(--panel); -} -.btn-ghost:hover:not(:disabled) { color: var(--ink); border-color: #3a3e47; } -.toolbar .btn-primary, -.toolbar .btn-ghost { - height: 36px; - font-size: 14px; - padding: 0 13px; - display: inline-flex; +/* 套餐分组 */ +.plan-grp + .plan-grp { + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid var(--line-soft); +} +.pg-head { + display: flex; align-items: center; + gap: 7px; + margin-bottom: 5px; + min-width: 0; +} +.pg-name { + font-size: 11.5px; + font-weight: 550; + color: var(--fg-2); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +.pg-exp { + margin-left: auto; flex: none; + font-family: var(--font-mono); + font-size: 10px; + color: var(--fg-3); white-space: nowrap; } -.toolbar .tb-gear { padding: 0 11px; } -.tb-spacer { flex: 1; } -.lib-row { display: flex; gap: 8px; margin-top: 4px; } -.lib-row .btn-ghost { flex: 1; } -.settings { - padding: 6px 20px 20px; - flex: 1; +/* 套餐等级徽章:统一样式,仅文字区分 */ +.tier-b { + font-family: var(--font-mono); + font-size: 9.5px; + font-weight: 600; + letter-spacing: .04em; + color: var(--fg-3); + border: 1px solid var(--line); + background: var(--inset); + border-radius: 4px; + padding: 1px 5px; + line-height: 13px; + flex: none; } -.settings label { - display: block; - font-family: var(--mono); - font-size: 12px; - letter-spacing: .18em; - color: var(--ink-mute); - margin-bottom: 6px; +.tier-b.max { color: var(--fg-2); } + +/* 可领取行:安静的文本行 + 小按钮 */ +.claim-strip { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 6px 6px 10px; + margin-bottom: 2px; + border: 1px solid var(--line-soft); + background: var(--inset); + border-radius: var(--r); + font-size: 11.5px; + min-width: 0; } -.settings .path-line { display: flex; gap: 8px; } -.settings .path-line .btn-ghost { flex: none; } -.settings input { +.claim-name { + font-weight: 600; + color: var(--fg-2); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.claim-grants { + color: var(--fg-3); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; flex: 1; min-width: 0; - font-family: var(--mono); - font-size: 14.5px; - color: var(--ink); - background: var(--panel); - border: 1px solid var(--hairline); - border-radius: 6px; - padding: 7px 10px; } -.settings input:focus { outline: none; border-color: var(--amber-deep); } -.settings .hint { - font-size: 14px; - color: var(--ink-mute); - margin-top: 14px; - line-height: 1.7; - border-top: 1px solid var(--hairline-soft); - padding-top: 12px; +.btn-claim { + flex: none; + font-size: 11.5px; + font-weight: 600; + height: 24px; + padding: 0 10px; + border-radius: 5px; + cursor: pointer; + background: var(--accent); + border: none; + color: var(--accent-ink); + display: inline-flex; + align-items: center; } +.btn-claim:hover:not(:disabled) { background: var(--accent-hover); } +.btn-claim:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 1px; } -.gh-row { - margin-top: 10px; +.warn-line { color: var(--warn) !important; } +.no-cfg { color: var(--warn); opacity: .85; } + +/* ---------- 空态 / 加载 ---------- */ + +.loading { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + height: 60vh; + color: var(--fg-3); + font-size: 11.5px; + letter-spacing: .08em; } -.gh-link { - font-size: 14px; - color: var(--amber-deep); - text-decoration: none; - cursor: pointer; +.loading::before { + content: ""; + width: 18px; height: 18px; + border-radius: 50%; + border: 2px solid var(--line); + border-top-color: var(--fg-2); + animation: spin .8s linear infinite; } -.gh-link:hover { text-decoration: underline; } + +/* ---------- Toast ---------- */ .toast-zone { position: fixed; left: 50%; - bottom: 74px; + bottom: 36px; transform: translateX(-50%); display: flex; flex-direction: column; - gap: 6px; + gap: 8px; z-index: 50; align-items: center; pointer-events: none; } .toast { - background: #26282f; - border: 1px solid var(--hairline); - color: var(--ink); - border-radius: 7px; - padding: 10px 16px; - font-size: 15.5px; - box-shadow: 0 6px 22px rgba(0, 0, 0, .45); - max-width: 400px; + background: #1d1d20; + border: 1px solid var(--line); + color: var(--fg); + border-radius: 8px; + padding: 9px 14px 9px 11px; + font-size: 12.5px; + box-shadow: var(--shadow-pop); + max-width: 420px; display: flex; align-items: flex-start; gap: 9px; animation: toast-in .18s ease; } -.toast .t-ic { flex: none; margin-top: 4px; color: var(--ink-mute); } -.toast.ok .t-ic { color: var(--mint); } -.toast.err .t-ic { color: var(--red); } -.toast.warn .t-ic { color: var(--amber); } +.toast .t-ic { flex: none; margin-top: 1px; color: var(--fg-3); } +.toast.ok .t-ic { color: var(--fg-2); } +.toast.err .t-ic { color: var(--bad); } +.toast.warn .t-ic { color: var(--warn); } .toast .t-body { min-width: 0; } -.toast.ok { border-color: var(--mint-dim); } -.toast.err { border-color: var(--red-dim); color: #f0b9b9; } -.toast.warn { border-color: rgba(214, 158, 46, .45); } -.toast .detail { display: block; color: var(--ink-mute); font-size: 14.5px; margin-top: 2px; } -.toast.err .detail { color: #cf9d9d; } -@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } } +.toast .detail { display: block; color: var(--fg-3); font-size: 11.5px; margin-top: 2px; word-break: break-all; } +.toast.err .detail { color: #c98d86; } +@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } } -.loading { +/* ---------- 确认弹窗 ---------- */ + +.cm-mask { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, .55); display: flex; align-items: center; justify-content: center; - height: 60vh; - color: var(--ink-mute); - font-family: var(--mono); - font-size: 14.5px; - letter-spacing: .2em; -} - -@media (prefers-reduced-motion: reduce) { - * { animation: none !important; transition: none !important; } + z-index: 110; + animation: fade-in .12s ease; } - -.top-status { - display: flex; - align-items: center; - gap: 7px; - font-size: 14.5px; - color: var(--ink-dim); +@keyframes fade-in { from { opacity: 0; } } +.cm-panel { + width: 340px; + max-width: calc(100vw - 48px); + background: #17171a; + border: 1px solid var(--line); + border-radius: var(--r-lg); + padding: 20px; + box-shadow: var(--shadow-pop); + animation: pop-in .14s ease-out; +} +@keyframes pop-in { + from { transform: scale(.97) translateY(6px); opacity: 0; } + to { transform: none; opacity: 1; } } -.top-status.unsaved .status-text { color: var(--amber); } -.status-dot { - width: 8px; height: 8px; - border-radius: 50%; - background: var(--mint); - box-shadow: 0 0 7px rgba(98, 195, 112, .5); +.cm-strip { display: none; } +.cm-head { display: flex; align-items: center; gap: 11px; margin: 0 0 6px; } +.cm-ic { + width: 34px; height: 34px; + border-radius: 8px; + display: grid; + place-items: center; flex: none; + background: var(--inset); + border: 1px solid var(--line-soft); + color: var(--fg-2); +} +.cm-ic.danger { color: var(--bad); } +.cm-ic.warn { color: var(--warn); } +.cm-title { font-size: 14px; font-weight: 600; color: var(--fg); line-height: 1.4; } +.cm-desc { + font-size: 12px; + color: var(--fg-2); + line-height: 1.7; + margin: 6px 0 16px; + word-break: break-all; } -.status-dot.run { - background: var(--red); - box-shadow: 0 0 7px rgba(224, 82, 82, .55); - animation: pulse 1.6s ease-in-out infinite; +.cm-desc b { color: var(--fg); font-weight: 600; } +.cm-desc .warn-line { color: var(--warn); } +.cm-actions { display: flex; gap: 8px; justify-content: flex-end; } +.cm-actions .btn-ghost { padding: 6px 13px; } +.cm-yes { + font-size: 12.5px; + font-weight: 600; + padding: 6px 15px; + border-radius: var(--r); + cursor: pointer; + border: none; + color: #fff; } -.status-dot.off { background: #4a4e57; box-shadow: none; } +.cm-yes.danger { background: var(--bad); } +.cm-yes.danger:hover:not(:disabled) { background: #e96a5e; } +.cm-yes.warn { background: var(--warn); } +.cm-yes.warn:hover:not(:disabled) { background: #e69859; } +.cm-yes.plain { background: var(--accent); color: var(--accent-ink); } +.cm-yes.plain:hover:not(:disabled) { background: var(--accent-hover); } +.cm-yes:disabled { opacity: .5; cursor: default; } -.quota-panel { - border: 1px solid var(--hairline); - border-radius: 10px; - background: var(--panel); - padding: 12px 15px 13px; -} -.q-head { +/* ---------- 口令弹窗 ---------- */ + +.pw-mask { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, .55); display: flex; align-items: center; - justify-content: space-between; - margin-bottom: 8px; + justify-content: center; + z-index: 100; + animation: fade-in .12s ease; } -.q-eyebrow { - font-family: var(--mono); +.pw-panel { + width: 372px; + max-width: calc(100vw - 48px); + background: #17171a; + border: 1px solid var(--line); + border-radius: var(--r-lg); + padding: 20px; + box-shadow: var(--shadow-pop); + animation: pop-in .14s ease-out; +} +.pw-title { font-size: 14px; font-weight: 600; color: var(--fg); } +.pw-sub { font-size: 12px; - letter-spacing: .18em; - color: var(--ink-mute); + color: var(--fg-2); + line-height: 1.7; + margin: 6px 0 14px; + word-break: break-all; } -.q-who { color: var(--ink-dim); letter-spacing: .04em; margin-left: 8px; } - .q-tier-row { margin-bottom: 8px; min-height: 22px; } -.q-win { - display: grid; - grid-template-columns: 60px 1fr 136px; - align-items: center; - gap: 8px; - margin-bottom: 7px; -} -.q-win:last-of-type { margin-bottom: 0; } -.q-win .qbar { margin-top: 0; } -.q-win-label { font-size: 14.5px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; } -.q-win-pct { font-family: var(--mono); font-size: 14px; color: var(--ink-dim); text-align: right; } -.q-win-reset { font-family: var(--mono); font-size: 12px; color: var(--ink-mute); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; } -.q-month-detail { margin-top: 7px; border-top: 1px dashed var(--hairline-soft); padding-top: 9px; } -.q-month-detail .q-win { margin-bottom: 5px; } -.q-loading { color: var(--ink-mute); font-size: 15.5px; padding: 4px 0; } -.q-fetch { width: 100%; } -.q-err { color: #f0b9b9; font-size: 15px; line-height: 1.5; padding: 2px 0; } -.q-err-mini { color: #f0b9b9; } -.tier { - font-family: var(--mono); +.pw-sub b { color: var(--fg); font-weight: 600; } +.pw-input { + width: 100%; font-size: 13px; - font-weight: 700; - letter-spacing: .08em; - color: var(--amber); - border: 1px solid var(--amber-deep); - border-radius: 4px; - padding: 1px 7px; - display: inline-block; -} -.qbar { height: 19px; border-radius: 4px; background: #2b2e36; overflow: hidden; margin-top: 4px; position: relative; } -.qbar-pct { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-family: var(--mono); font-size: 11.5px; color: #97a1b4; letter-spacing: .02em; } -.qbar-pct.in-fill { color: #0e1116; font-weight: 600; } -.qbar-fill { height: 100%; background: var(--mint); border-radius: 3px; transition: width .3s ease; } -.qbar.warn .qbar-fill { background: var(--amber); } -.qbar.danger .qbar-fill { background: var(--red); } -.q-line { font-size: 14px; color: var(--ink-mute); margin-top: 5px; } -.q-expand { - margin-top: 8px; - font-size: 14.5px; - color: var(--ink-dim); - border: none; - padding: 2px 0; -} -.q-expand:hover { color: var(--ink); } -.q-items { margin-top: 7px; border-top: 1px dashed var(--hairline-soft); padding-top: 9px; } -.q-item { margin-bottom: 10px; } -.q-item:last-child { margin-bottom: 0; } -.q-item-name { - font-size: 15px; - color: var(--ink); - display: flex; - justify-content: space-between; - align-items: baseline; - gap: 8px; + padding: 9px 11px; + margin-bottom: 9px; } -.q-item-nums { font-family: var(--mono); font-size: 12.5px; color: var(--ink-mute); margin-top: 2px; } -.q-item-name .q-item-nums { margin-top: 0; font-size: 14px; color: var(--ink-dim); } -.btn-mini { - border: 1px solid var(--hairline); - border-radius: 4px; - background: var(--bg-2); - color: var(--ink-dim); +.pw-err { font-size: 12px; color: var(--bad); min-height: 17px; margin-bottom: 7px; } +.pw-actions { display: flex; gap: 8px; justify-content: flex-end; } +.pw-actions .btn-ghost { padding: 6px 13px; } +.pw-actions .pw-go { flex: none; padding: 6px 15px; background: var(--accent); color: var(--accent-ink); font-weight: 600; border: none; } +.pw-actions .pw-go:hover:not(:disabled) { background: var(--accent-hover); color: var(--accent-ink); } + +/* ---------- OAuth 提供方弹窗 ---------- */ + +.pv-mask { + position: fixed; inset: 0; z-index: 1200; + background: rgba(0, 0, 0, .55); + display: flex; align-items: center; justify-content: center; + animation: fade-in .12s ease; +} +.pv-panel { + width: min(372px, calc(100vw - 48px)); + background: #17171a; + border: 1px solid var(--line); + border-radius: var(--r-lg); + padding: 20px; + box-shadow: var(--shadow-pop); + animation: pop-in .14s ease-out; +} +.pv-title { font-size: 14px; font-weight: 600; color: var(--fg); } +.pv-sub { font-size: 12px; color: var(--fg-3); line-height: 1.7; margin: 5px 0 14px; } +.pv-sub b { color: var(--fg); font-weight: 600; } +.pv-list { display: flex; flex-direction: column; gap: 6px; } +.pv-item { + display: flex; align-items: center; justify-content: space-between; + padding: 11px 13px; + background: none; + border: 1px solid var(--line-soft); + border-radius: 8px; + cursor: pointer; font-size: 13px; - padding: 2px 9px; + font-weight: 550; + color: var(--fg); + text-align: left; + transition: border-color .12s ease, background .12s ease; } -.btn-mini:hover { color: var(--ink); border-color: #3a3e47; } +.pv-item:hover { border-color: var(--line); background: var(--surface-2); } +.pv-item:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 1px; } +.pv-item .pv-arrow { color: var(--fg-3); display: grid; place-items: center; } +.pv-actions { display: flex; justify-content: flex-end; margin-top: 14px; } +.pv-actions .btn-ghost { padding: 6px 13px; } -.row-quota-slot { - width: 100%; - min-height: 46px; - margin-top: 7px; - padding-top: 7px; - border-top: 1px dashed var(--hairline-soft); - display: flex; - flex-direction: column; - justify-content: center; - gap: 4px; +/* ---------- 设置窗 ---------- */ + +.settings { + padding: 4px 20px 24px; + flex: 1; +} +.set-label { + font-size: 11px; + font-weight: 600; + letter-spacing: .08em; + color: var(--fg-3); + margin: 18px 2px 8px; +} +.set-group { + background: var(--surface); + border: 1px solid var(--line-soft); + border-radius: var(--r-lg); + padding: 2px 14px; +} +.settings .path-line { display: flex; gap: 8px; padding: 9px 0; } +.settings .path-line .btn-ghost { flex: none; } +.settings input { + flex: 1; + min-width: 0; +} +.lib-row { display: flex; gap: 8px; padding: 9px 0; } +.lib-row .btn-ghost { flex: 1; } +.gh-row { margin-top: 14px; } +.gh-link { + font-size: 12px; + color: var(--fg-3); + text-decoration: none; +} +.gh-link:hover { color: var(--fg-2); text-decoration: underline; } + +.settings .hint { + font-size: 11.5px; + color: var(--fg-3); + margin-top: 16px; + line-height: 1.8; + border-top: 1px solid var(--line-soft); + padding-top: 13px; } -.q-win.mini { - grid-template-columns: 52px 1fr 128px; - gap: 6px; - margin-bottom: 0; -} -.q-win.mini .q-win-label { font-size: 13px; } -.q-win.mini .q-win-pct { font-size: 12.5px; } -.q-win.mini .q-win-reset { font-size: 11.5px; } -.q-win.mini .qbar { height: 17px; } -.q-win.mini .qbar-pct { font-size: 11px; } -.aq-loading { color: var(--ink-mute); font-size: 13px; } -.aq-err { color: #c98f8f; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.row.force { border-color: #4a3d22; background: #241f16; } -.warn-line { color: var(--amber) !important; } -.no-cfg { color: var(--amber); opacity: .85; } -.confirm-box .go { - border-color: var(--amber-deep); - color: var(--amber); - padding: 3px 10px; -} -.confirm-box .go:hover { background: rgba(232, 163, 61, .14); } -.rb-confirm { margin: 0; } -.rb-confirm span { font-size: 14.5px; } .tog-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; - padding: 7px 0; - border-bottom: 1px solid var(--hairline-soft); + padding: 10px 0; + border-bottom: 1px solid var(--line-soft); } .tog-row:last-of-type { border-bottom: none; } -.tog-label { font-size: 16px; color: var(--ink); font-weight: 600; } -.tog-desc { font-size: 14px; color: var(--ink-mute); margin-top: 1px; } +.tog-label { font-size: 12.5px; color: var(--fg); font-weight: 550; } +.tog-desc { font-size: 11.5px; color: var(--fg-3); margin-top: 1px; line-height: 1.5; } .toggle { position: relative; - width: 40px; - height: 22px; - border-radius: 10px; - border: 1px solid var(--hairline); - background: #2b2e36; + width: 36px; + height: 20px; + border-radius: 99px; + border: 1px solid var(--line); + background: var(--inset); padding: 0; flex: none; + transition: background .15s ease, border-color .15s ease; } .toggle .knob { position: absolute; left: 2px; top: 2px; - width: 16px; height: 16px; + width: 14px; height: 14px; border-radius: 50%; - background: #767a84; + background: #74757c; transition: transform .15s ease, background .15s ease; } -.toggle.on { background: rgba(232, 163, 61, .25); border-color: var(--amber-deep); } -.toggle.on .knob { transform: translateX(18px); background: var(--amber); } +.toggle.on { background: var(--accent); border-color: var(--accent); } +.toggle.on .knob { transform: translateX(16px); background: var(--accent-ink); } .lang-seg { display: flex; - gap: 4px; + gap: 2px; padding: 2px; - border: 1px solid var(--hairline); - border-radius: 11px; - background: #2b2e36; + border: 1px solid var(--line); + border-radius: 7px; + background: var(--inset); flex: none; } .lang-opt { border: none; background: transparent; - color: var(--ink-mute); - font-size: 13px; - font-weight: 600; + color: var(--fg-3); + font-size: 12px; + font-weight: 550; padding: 3px 12px; - border-radius: 8px; - cursor: pointer; - transition: background .15s ease, color .15s ease; -} -.lang-opt:hover { color: var(--ink); } -.lang-opt.on { - background: rgba(232, 163, 61, .25); - color: var(--amber); -} - -.tier-b { - font-family: var(--mono); - font-size: 11.5px; - font-weight: 700; - letter-spacing: .06em; - border-radius: 3px; - padding: 0 5px; - line-height: 18px; - flex: none; -} -.tier-b.max { color: #f0b055; border: 1px solid #a06a22; background: rgba(232,163,61,.10); } -.tier-b.pro { color: #7fb3e8; border: 1px solid #33567c; background: rgba(109,168,232,.10); } -.tier-b.lite { color: #9fd4a8; border: 1px solid #3d6c46; background: rgba(98,195,112,.10); } -.tier-b.trial { color: #c9a6e8; border: 1px solid #5c4179; background: rgba(183,138,224,.10); } -.tier-b.free, .tier-b.other { color: var(--ink-mute); border: 1px solid var(--hairline); background: var(--bg-2); } - -.plan-grp + .plan-grp { margin-top: 7px; padding-top: 6px; border-top: 1px dashed var(--hairline-soft); } -.pg-head { display: flex; align-items: center; gap: 7px; margin-bottom: 3px; min-width: 0; } -.pg-name { - font-size: 12.5px; - color: var(--ink-dim); - font-family: var(--mono); - letter-spacing: .02em; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -.pg-exp { margin-left: auto; flex: none; font-family: var(--mono); font-size: 11.5px; color: var(--ink-mute); white-space: nowrap; } - -.pw-mask { - position: fixed; - inset: 0; - background: rgba(10, 11, 14, .72); - display: flex; - align-items: center; - justify-content: center; - z-index: 100; -} -.pw-panel { - width: 380px; - max-width: calc(100vw - 48px); - background: var(--panel); - border: 1px solid var(--hairline); - border-radius: 12px; - padding: 20px 20px 18px; - box-shadow: 0 18px 50px rgba(0, 0, 0, .5); -} -.pw-title { font-size: 18.5px; font-weight: 700; color: var(--ink); } -.pw-sub { font-size: 14px; color: var(--ink-dim); line-height: 1.7; margin: 8px 0 14px; word-break: break-all; } -.pw-sub b { color: var(--amber); font-weight: 600; } -.pw-input { - width: 100%; - font-family: var(--body); - font-size: 16px; - color: var(--ink); - background: var(--bg-2); - border: 1px solid var(--hairline); - border-radius: 7px; - padding: 9px 12px; - margin-bottom: 9px; -} -.pw-input:focus { outline: none; border-color: var(--amber-deep); } -.pw-err { font-size: 14px; color: var(--red); min-height: 16px; margin-bottom: 6px; } -.pw-actions { display: flex; gap: 8px; justify-content: flex-end; } -.pw-actions .pw-go { flex: none; padding: 8px 18px; } - -.cm-mask { - position: fixed; - inset: 0; - background: rgba(10, 11, 14, .72); - display: flex; - align-items: center; - justify-content: center; - z-index: 110; -} -.cm-panel { - position: relative; - width: 340px; - max-width: calc(100vw - 48px); - background: var(--panel); - border: 1px solid var(--hairline); - border-radius: 12px; - padding: 18px 20px 16px; - box-shadow: 0 18px 50px rgba(0, 0, 0, .5); - overflow: hidden; - animation: cm-in .16s ease-out; -} -@keyframes cm-in { - from { transform: scale(.96) translateY(4px); opacity: 0; } - to { transform: none; opacity: 1; } -} -@media (prefers-reduced-motion: reduce) { - .cm-panel { animation: none; } -} -.cm-strip { - position: absolute; - top: 0; left: 0; right: 0; - height: 2px; -} -.cm-strip.danger { background: var(--red); } -.cm-strip.warn { background: var(--amber); } -.cm-strip.plain { background: var(--amber); } -.cm-head { - display: flex; - align-items: center; - gap: 10px; - margin: 2px 0 6px; -} -.cm-ic.danger { color: var(--red); } -.cm-ic.warn, .cm-ic.plain { color: var(--amber); } -.cm-title { font-size: 17px; font-weight: 700; color: var(--ink); line-height: 1.35; } -.cm-desc { font-size: 14px; color: var(--ink-dim); line-height: 1.7; margin: 0 0 14px; word-break: break-all; } -.cm-desc b { color: var(--amber); font-weight: 600; } -.cm-desc .warn-line { color: var(--amber); } -.cm-actions { display: flex; gap: 8px; justify-content: flex-end; } -.cm-actions .btn-ghost { padding: 7px 14px; } -.cm-yes { - font-family: var(--body); - font-size: 14.5px; - font-weight: 600; - padding: 7px 16px; - border-radius: 6px; - cursor: pointer; -} -.cm-yes.danger { - background: rgba(224, 82, 82, .14); - border: 1px solid var(--red-dim); - color: var(--red); -} -.cm-yes.danger:hover:not(:disabled) { background: rgba(224, 82, 82, .24); } -.cm-yes.warn, .cm-yes.plain { - background: var(--amber); - border: 1px solid var(--amber); - color: var(--amber-ink); -} -.cm-yes.warn:hover:not(:disabled), .cm-yes.plain:hover:not(:disabled) { background: #f0b055; } -.cm-yes:disabled { opacity: .55; cursor: default; } -.cm-yes:focus-visible, .cm-actions .btn-ghost:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; } - -.claim-strip { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 9px; - margin-bottom: 4px; - border: 1px solid rgba(232, 163, 61, .38); - background: rgba(232, 163, 61, .07); - border-radius: 6px; - color: var(--amber); - font-size: 12.5px; - min-width: 0; -} -.claim-strip .ic { flex: none; } -.claim-name { - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.claim-grants { - color: var(--ink-dim); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - flex: 1; - min-width: 0; -} -.btn-claim { - flex: none; - font-family: var(--body); - font-size: 13px; - font-weight: 600; - height: 26px; - padding: 0 13px; - border-radius: 6px; + border-radius: 5px; cursor: pointer; - background: var(--amber); - border: 1px solid var(--amber); - color: var(--amber-ink); - display: inline-flex; - align-items: center; - gap: 6px; + transition: background .12s ease, color .12s ease; } -.btn-claim:hover { background: #f0b055; } -.btn-claim:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; } +.lang-opt:hover { color: var(--fg); } +.lang-opt.on { background: var(--surface-2); color: var(--fg); } -.toolbar .claim-all { - border-color: var(--amber-deep); - color: var(--amber); -} -.toolbar .claim-all:hover:not(:disabled) { - border-color: var(--amber); - background: rgba(232, 163, 61, .1); -} -.toolbar .claim-all:disabled { opacity: .6; cursor: default; } +/* ---------- 验证码窗 ---------- */ .captcha-win { - background: #0a0a0c; - color: var(--ink); - font-family: var(--body); + background: var(--bg); + color: var(--fg); + font-family: var(--font-ui); user-select: none; } #cap-app { @@ -903,56 +845,55 @@ input:focus-visible, flex-direction: column; align-items: center; justify-content: center; - gap: 10px; - padding: 18px; + gap: 12px; + padding: 20px; } +.cap-mark { width: 44px; height: 44px; margin-bottom: 2px; } .cap-status { display: flex; align-items: center; - gap: 9px; - font-size: 14px; - color: var(--ink); + gap: 8px; + font-size: 13px; + color: var(--fg); } .cap-dot { - width: 8px; - height: 8px; + width: 6px; + height: 6px; border-radius: 50%; - background: var(--amber); - animation: cap-pulse 1.4s ease-in-out infinite; + background: var(--fg-2); + animation: pulse 1.4s ease-in-out infinite; } -.cap-dot.ok { background: var(--mint); animation: none; } -.cap-dot.err { background: var(--red); animation: none; } -@keyframes cap-pulse { 50% { opacity: .35; } } +.cap-dot.ok { background: var(--accent-dim); animation: none; } +.cap-dot.err { background: var(--bad); animation: none; } .cap-detail { - font-size: 12px; - color: var(--ink-mute); + font-size: 11.5px; + color: var(--fg-3); max-width: 320px; text-align: center; - line-height: 1.6; + line-height: 1.65; word-break: break-all; min-height: 19px; } #cap-btn { - font-family: var(--body); - font-size: 14px; + font-size: 12.5px; font-weight: 600; padding: 8px 22px; - border-radius: 6px; + border-radius: var(--r); cursor: pointer; - background: var(--amber); - border: 1px solid var(--amber); - color: var(--amber-ink); + background: var(--accent); + border: none; + color: var(--accent-ink); } -#cap-btn:hover { background: #f0b055; } -#cap-btn:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; } +#cap-btn:hover { background: var(--accent-hover); } +#cap-btn:focus-visible { outline: 2px solid var(--accent-line); outline-offset: 1px; } .cap-foot { position: absolute; - bottom: 12px; + bottom: 14px; left: 0; right: 0; text-align: center; - font-size: 11px; - color: var(--ink-mute); + font-size: 10.5px; + color: var(--fg-3); } #cap-holder { position: relative; @@ -960,38 +901,8 @@ input:focus-visible, min-height: 60px; } -.pv-mask { - position: fixed; inset: 0; z-index: 1200; - background: rgba(8, 9, 11, .62); - backdrop-filter: blur(3px); - display: flex; align-items: center; justify-content: center; - animation: pw-in .14s ease; -} -.pv-panel { - width: min(400px, calc(100vw - 48px)); - background: var(--panel); - border: 1px solid var(--hairline); - border-radius: 10px; - padding: 20px 20px 16px; - box-shadow: 0 18px 60px rgba(0, 0, 0, .5); -} -.pv-title { font-size: 16px; font-weight: 700; color: var(--ink); } -.pv-sub { font-size: 12.5px; color: var(--ink-mute); line-height: 1.7; margin: 8px 0 14px; } -.pv-sub b { color: var(--amber); font-weight: 600; } -.pv-list { display: flex; flex-direction: column; gap: 8px; } -.pv-item { - display: flex; align-items: center; justify-content: space-between; - padding: 12px 14px; - background: var(--panel-2); - border: 1px solid var(--hairline); - border-radius: 8px; - cursor: pointer; - font-family: var(--body); - font-size: 14px; - color: var(--ink); - text-align: left; +/* ---------- 动效偏好 ---------- */ + +@media (prefers-reduced-motion: reduce) { + * { animation: none !important; transition: none !important; } } -.pv-item:hover { border-color: var(--amber-deep); background: rgba(232, 163, 61, .07); } -.pv-item:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; } -.pv-item .pv-arrow { color: var(--ink-mute); font-family: var(--mono); } -.pv-actions { display: flex; justify-content: flex-end; margin-top: 14px; } diff --git a/src/ui.js b/src/ui.js index b7a7cd2..17b672f 100644 --- a/src/ui.js +++ b/src/ui.js @@ -206,7 +206,7 @@ export function openProviderModal(m) { ${(m.providers || []).map((p) => ` `).join("")}