twskin 0.5.5 → 0.5.7

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/CHANGELOG.md CHANGED
@@ -5,6 +5,34 @@ All notable changes to this package are documented here. This project follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.5.7] - 2026-07-29
9
+
10
+ ### Fixed
11
+
12
+ - Prevented theme artwork from bleeding through right-side working content by
13
+ making layout-surface ownership a runtime contract. The main workspace now
14
+ retains its declared surface color, opacity, and blur beneath Markdown,
15
+ Task Summary, and Code/Design content.
16
+ - Updated `Solvay 1927 · Solarized Light` to theme version `1.0.4`, restoring
17
+ a readable warm-paper surface in the right workspace and Monaco editor.
18
+
19
+ ### Changed
20
+
21
+ - Added catalog validation that rejects official theme CSS which overrides a
22
+ runtime-owned layout surface directly; themes use surface-decoration
23
+ variables for optional texture instead.
24
+
25
+ ## [0.5.6] - 2026-07-27
26
+
27
+ ### Changed
28
+
29
+ - Reorganized Theme Manager controls by ownership: current-theme settings now
30
+ contain only per-theme appearance controls, while catalog updates, local theme
31
+ scanning, and restoring TRAE's native appearance live in Theme Library Settings.
32
+ - Moved Theme Library Settings to a compact, accessible gear button in the
33
+ manager header. Manual update checks and their status now live there, keeping
34
+ the theme gallery focused on browsing and switching themes.
35
+
8
36
  ## [0.5.5] - 2026-07-27
9
37
 
10
38
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twskin",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "description": "An unofficial community theme manager and CLI for TRAE Work on macOS.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,7 @@ import { spawnSync } from "node:child_process";
16
16
  import { fileURLToPath } from "node:url";
17
17
 
18
18
  const ROOT = path.dirname(fileURLToPath(import.meta.url));
19
- const VERSION = "0.5.5";
19
+ const VERSION = "0.5.7";
20
20
  const DATA_DIR = process.env.TWSKIN_DATA_DIR || path.join(os.homedir(), ".trae-work-skin");
21
21
 
22
22
  function parseArgs(argv) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "packageVersion": "0.5.5",
3
+ "packageVersion": "0.5.7",
4
4
  "themesBundled": false,
5
5
  "files": [
6
6
  {
@@ -11,7 +11,7 @@
11
11
  {
12
12
  "path": "injector.mjs",
13
13
  "size": 31308,
14
- "sha256": "ed68cba722846616215727222eb8588ac7dcb7fac1b4e7b8b04b27f6146b9db4"
14
+ "sha256": "b1ea0108ab8e4449bb509de458705e0019cad3d342fb1ef9fcd8ec98c890bf79"
15
15
  },
16
16
  {
17
17
  "path": "restore.sh",
@@ -20,8 +20,8 @@
20
20
  },
21
21
  {
22
22
  "path": "skin.js",
23
- "size": 56513,
24
- "sha256": "190e85b43a0c7d901a9b25cde65d19767bf1099901fb8e75f9fce7a40e2c3ea0"
23
+ "size": 61967,
24
+ "sha256": "c4b177a30b7d0681c28324d8042e0e75972df71924120342a1b852a8e0ff5cf0"
25
25
  },
26
26
  {
27
27
  "path": "start.sh",
@@ -30,13 +30,13 @@
30
30
  },
31
31
  {
32
32
  "path": "styles/base.css",
33
- "size": 32439,
34
- "sha256": "cfd57424d2b31732a44e0dd59410ab039d9842017112e6bd688a37db4831262a"
33
+ "size": 33535,
34
+ "sha256": "0356d33f4197461e325eb563471219064ded7cc75e835c28de6dbedb919c2a05"
35
35
  },
36
36
  {
37
37
  "path": "styles/manager.css",
38
- "size": 17342,
39
- "sha256": "b765d8ad89324af100794c653a23d64e1e647d78a7e9d815eb912189974d7dfc"
38
+ "size": 19040,
39
+ "sha256": "4a8acef7f2dddbcd61e5b1460a7639bd52478e9131099eb699874e9bffb0c87c"
40
40
  },
41
41
  {
42
42
  "path": "token-map.mjs",
package/runtime/skin.js CHANGED
@@ -665,24 +665,31 @@ ${selector} {
665
665
  const configToggle = document.createElement("button");
666
666
  configToggle.className = "ds-config-toggle";
667
667
  configToggle.type = "button";
668
- configToggle.textContent = "配置";
668
+ configToggle.textContent = "当前主题";
669
669
  configToggle.title = "查看当前主题配置";
670
+ const globalToggle = document.createElement("button");
671
+ globalToggle.className = "ds-global-toggle";
672
+ globalToggle.type = "button";
673
+ globalToggle.setAttribute("aria-label", "主题库设置");
674
+ globalToggle.setAttribute("aria-pressed", "false");
675
+ globalToggle.title = "主题库设置";
676
+ globalToggle.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 15.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Z"/><path d="m19.4 15 .1 1.7-2.1 2.1-1.7-.1-1.2 1-.4 1.6h-3l-.4-1.6-1.2-1-1.7.1-2.1-2.1.1-1.7-1-1.2L3 13.4v-3l1.6-.4 1-1.2-.1-1.7 2.1-2.1 1.7.1 1.2-1 .4-1.6h3l.4 1.6 1.2 1 1.7-.1 2.1 2.1-.1 1.7 1 1.2 1.6.4v3l-1.6.4-1 1.2Z"/></svg>';
670
677
  const syncButton = document.createElement("button");
671
678
  syncButton.className = "ds-sync";
672
679
  syncButton.type = "button";
673
- syncButton.textContent = "更新";
680
+ syncButton.textContent = "检查更新";
674
681
  syncButton.title = "检查官方主题更新";
675
682
  const reloadButton = document.createElement("button");
676
- reloadButton.className = "ds-reload";
683
+ reloadButton.className = "ds-local-reload";
677
684
  reloadButton.type = "button";
678
- reloadButton.innerHTML = '<span class="ds-reload-icon">↻</span>';
685
+ reloadButton.textContent = "重新扫描本地主题";
679
686
  reloadButton.title = "重新加载主题目录(新主题热加载)";
680
687
  const closeButton = document.createElement("button");
681
688
  closeButton.className = "ds-close";
682
689
  closeButton.type = "button";
683
690
  closeButton.textContent = "×";
684
691
  closeButton.title = "关闭 (Esc)";
685
- headerActions.append(syncButton, reloadButton, configToggle, closeButton);
692
+ headerActions.append(configToggle, globalToggle, closeButton);
686
693
  header.append(headerText, headerActions);
687
694
  const panelBody = document.createElement("div");
688
695
  panelBody.className = "ds-body";
@@ -717,6 +724,29 @@ ${selector} {
717
724
  blurSwitch.setAttribute("role", "switch");
718
725
  blurSwitch.setAttribute("aria-label", "面板模糊");
719
726
  blurRow.append(blurCopy, blurSwitch);
727
+ const globalView = document.createElement("div");
728
+ globalView.className = "ds-global-view";
729
+ const globalHead = document.createElement("div");
730
+ globalHead.className = "ds-global-head";
731
+ const globalTitle = document.createElement("div");
732
+ globalTitle.className = "ds-global-title";
733
+ globalTitle.textContent = "主题库设置";
734
+ const globalDesc = document.createElement("div");
735
+ globalDesc.className = "ds-global-desc";
736
+ globalDesc.textContent = "管理所有主题共用的更新与本地目录";
737
+ globalHead.append(globalTitle, globalDesc);
738
+ const checkRow = document.createElement("div");
739
+ checkRow.className = "ds-setting-row";
740
+ const checkCopy = document.createElement("div");
741
+ checkCopy.className = "ds-setting-copy";
742
+ const checkLabel = document.createElement("div");
743
+ checkLabel.className = "ds-setting-label";
744
+ checkLabel.textContent = "检查官方主题更新";
745
+ const checkDesc = document.createElement("div");
746
+ checkDesc.className = "ds-setting-desc";
747
+ checkDesc.setAttribute("aria-live", "polite");
748
+ checkCopy.append(checkLabel, checkDesc);
749
+ checkRow.append(checkCopy, syncButton);
720
750
  const updateRow = document.createElement("div");
721
751
  updateRow.className = "ds-setting-row";
722
752
  const updateCopy = document.createElement("div");
@@ -734,6 +764,18 @@ ${selector} {
734
764
  updateSwitch.setAttribute("role", "switch");
735
765
  updateSwitch.setAttribute("aria-label", "自动更新官方主题");
736
766
  updateRow.append(updateCopy, updateSwitch);
767
+ const localThemesRow = document.createElement("div");
768
+ localThemesRow.className = "ds-setting-row ds-local-themes-row";
769
+ const localThemesCopy = document.createElement("div");
770
+ localThemesCopy.className = "ds-setting-copy";
771
+ const localThemesLabel = document.createElement("div");
772
+ localThemesLabel.className = "ds-setting-label";
773
+ localThemesLabel.textContent = "本地主题";
774
+ const localThemesDesc = document.createElement("div");
775
+ localThemesDesc.className = "ds-setting-desc";
776
+ localThemesDesc.textContent = "新增或修改 themes/ 目录后重新读取";
777
+ localThemesCopy.append(localThemesLabel, localThemesDesc);
778
+ localThemesRow.append(localThemesCopy, reloadButton);
737
779
  const brightnessRow = document.createElement("div");
738
780
  brightnessRow.className = "ds-setting-row ds-setting-row-slider";
739
781
  const brightnessHead = document.createElement("div");
@@ -794,8 +836,21 @@ ${selector} {
794
836
  resetActions.className = "ds-reset-actions";
795
837
  resetActions.append(resetCancel, resetConfig);
796
838
  configHead.append(configName, copyConfig);
797
- configView.append(configHead, updateRow, brightnessRow, mainOverlayRow, blurRow, configCode);
798
- panelBody.append(syncBanner, galleryView, configView);
839
+ const restoreRow = document.createElement("div");
840
+ restoreRow.className = "ds-setting-row ds-restore-row";
841
+ const restoreCopy = document.createElement("div");
842
+ restoreCopy.className = "ds-setting-copy";
843
+ const restoreLabel = document.createElement("div");
844
+ restoreLabel.className = "ds-setting-label";
845
+ restoreLabel.textContent = "恢复原生外观";
846
+ const restoreDesc = document.createElement("div");
847
+ restoreDesc.className = "ds-setting-desc";
848
+ restoreDesc.textContent = "停止应用主题化,并恢复 TRAE 默认界面";
849
+ restoreCopy.append(restoreLabel, restoreDesc);
850
+ restoreRow.append(restoreCopy, resetActions);
851
+ configView.append(configHead, brightnessRow, mainOverlayRow, blurRow, configCode);
852
+ globalView.append(globalHead, syncBanner, checkRow, updateRow, localThemesRow, restoreRow);
853
+ panelBody.append(galleryView, configView, globalView);
799
854
 
800
855
  // 分类 Tab(固定在头部下,不随列表滚动;选择持久化)
801
856
  const CATEGORY_LS = `${LS_PREFIX}category`;
@@ -836,19 +891,25 @@ ${selector} {
836
891
  const footerText = document.createElement("span");
837
892
  footerText.className = "ds-footer-text";
838
893
  footerText.textContent = `TRAE Work Skin v${VERSION}`;
839
- footer.append(footerText, resetActions);
894
+ footer.append(footerText);
840
895
  panel.append(header, tabsBar, panelBody, footer);
841
896
 
842
- let configOpen = false;
843
- const showConfig = (open) => {
844
- configOpen = Boolean(open);
845
- // 配置视图只展示当前主题设置;返回画廊时再恢复分类栏和主题列表。
846
- tabsBar.style.display = configOpen ? "none" : "";
847
- galleryView.style.display = configOpen ? "none" : "";
848
- configView.style.display = configOpen ? "block" : "";
849
- configToggle.textContent = configOpen ? "主题" : "配置";
850
- configToggle.title = configOpen ? "返回主题列表" : "查看当前主题配置";
897
+ let activeView = "gallery";
898
+ const showView = (view) => {
899
+ activeView = view;
900
+ // 详情页只呈现对应所有权范围的设置,画廊则恢复主题库操作与分类列表。
901
+ tabsBar.style.display = view === "gallery" ? "" : "none";
902
+ galleryView.style.display = view === "gallery" ? "" : "none";
903
+ configView.style.display = view === "theme" ? "block" : "none";
904
+ globalView.style.display = view === "global" ? "block" : "none";
905
+ configToggle.textContent = view === "gallery" ? "当前主题" : "主题";
906
+ configToggle.title = view === "gallery" ? "查看当前主题配置" : "返回主题列表";
907
+ globalToggle.classList.toggle("ds-global-toggle-active", view === "global");
908
+ globalToggle.setAttribute("aria-pressed", String(view === "global"));
909
+ globalToggle.title = view === "global" ? "返回主题列表" : "主题库设置";
910
+ renderThemeSyncState();
851
911
  };
912
+ const showConfig = (open) => showView(open ? "theme" : "gallery");
852
913
  renderCurrentConfig = (theme = findTheme(
853
914
  (() => { try { return localStorage.getItem(LS_KEY); } catch { return null; } })(),
854
915
  )) => {
@@ -883,12 +944,21 @@ ${selector} {
883
944
  if (themeSyncState.phase === "error") return themeSyncState.message || "主题更新失败";
884
945
  return "";
885
946
  };
947
+ const SUCCESS_FEEDBACK_DURATION_MS = 4000;
948
+ let successFeedbackTimer = null;
949
+ const hasRecentSyncSuccess = () => {
950
+ if (themeSyncState.phase !== "success") return false;
951
+ const completedAt = Date.parse(themeSyncState.lastSuccessfulSyncAt || "");
952
+ return Number.isFinite(completedAt) && Date.now() - completedAt < SUCCESS_FEEDBACK_DURATION_MS;
953
+ };
886
954
  const renderThemeSyncState = () => {
887
955
  const phase = themeSyncState.phase || "idle";
888
956
  const summary = syncSummary();
957
+ const updateCount = Number(themeSyncState.updateCount || 0);
958
+ const showSuccessFeedback = hasRecentSyncSuccess();
889
959
  syncBanner.replaceChildren();
890
960
  syncBanner.className = `ds-sync-banner ds-sync-${phase}`;
891
- if (!summary || phase === "fresh" || phase === "idle") {
961
+ if (!summary || phase === "fresh" || phase === "idle" || (phase === "success" && !showSuccessFeedback) || activeView !== "global") {
892
962
  syncBanner.style.display = "none";
893
963
  } else {
894
964
  syncBanner.style.display = "flex";
@@ -918,13 +988,31 @@ ${selector} {
918
988
  syncBanner.append(retry);
919
989
  }
920
990
  }
921
- syncButton.classList.toggle("ds-sync-pending", Number(themeSyncState.updateCount || 0) > 0);
991
+ syncButton.classList.toggle("ds-sync-pending", updateCount > 0);
922
992
  syncButton.disabled = phase === "checking" || phase === "downloading" || phase === "installing";
923
993
  syncButton.title = summary || "检查官方主题更新";
994
+ globalToggle.classList.toggle("ds-global-pending", updateCount > 0);
995
+ let checkStatusCopy = "尚未检查";
996
+ if (phase === "checking") checkStatusCopy = "正在检查更新…";
997
+ else if (phase === "update-available") checkStatusCopy = `发现 ${updateCount} 项更新`;
998
+ else if (phase === "success" && showSuccessFeedback) checkStatusCopy = "已完成同步";
999
+ else if (phase === "error") checkStatusCopy = "检查失败,可重试";
1000
+ else if (themeSyncState.lastCheckedAt) checkStatusCopy = `上次检查:${new Date(themeSyncState.lastCheckedAt).toLocaleString()}`;
1001
+ checkDesc.textContent = checkStatusCopy;
1002
+ if (successFeedbackTimer) {
1003
+ clearTimeout(successFeedbackTimer);
1004
+ successFeedbackTimer = null;
1005
+ }
1006
+ if (showSuccessFeedback) {
1007
+ const remaining = SUCCESS_FEEDBACK_DURATION_MS - (Date.now() - Date.parse(themeSyncState.lastSuccessfulSyncAt));
1008
+ successFeedbackTimer = setTimeout(() => {
1009
+ successFeedbackTimer = null;
1010
+ renderThemeSyncState();
1011
+ }, Math.max(0, remaining));
1012
+ }
924
1013
  updateSwitch.setAttribute("aria-checked", String(Boolean(themeSyncState.autoUpdateThemes)));
925
1014
  updateSwitch.title = themeSyncState.autoUpdateThemes ? "关闭官方主题自动更新" : "开启官方主题自动更新";
926
- const checkedAt = themeSyncState.lastCheckedAt ? new Date(themeSyncState.lastCheckedAt).toLocaleString() : "尚未检查";
927
- footerText.textContent = `TRAE Work Skin v${VERSION} · 主题更新:${checkedAt}`;
1015
+ footerText.textContent = `TRAE Work Skin v${VERSION}`;
928
1016
  };
929
1017
  renderBrightnessControl = (theme = findTheme(
930
1018
  (() => { try { return localStorage.getItem(LS_KEY); } catch { return null; } })(),
@@ -952,8 +1040,16 @@ ${selector} {
952
1040
  };
953
1041
  configToggle.addEventListener("click", (event) => {
954
1042
  event.stopPropagation();
955
- renderCurrentConfig();
956
- showConfig(!configOpen);
1043
+ if (activeView === "gallery") {
1044
+ renderCurrentConfig();
1045
+ showView("theme");
1046
+ } else {
1047
+ showView("gallery");
1048
+ }
1049
+ });
1050
+ globalToggle.addEventListener("click", (event) => {
1051
+ event.stopPropagation();
1052
+ showView(activeView === "global" ? "gallery" : "global");
957
1053
  });
958
1054
  syncButton.addEventListener("click", (event) => {
959
1055
  event.stopPropagation();
@@ -1070,7 +1166,7 @@ ${selector} {
1070
1166
  card.dataset.category = theme.category || "其他";
1071
1167
  const badge = document.createElement("span");
1072
1168
  badge.className = "ds-badge";
1073
- badge.textContent = "✓ 使用中";
1169
+ badge.textContent = "当前主题";
1074
1170
  const preview = document.createElement("div");
1075
1171
  preview.className = "ds-preview";
1076
1172
  preview.style.backgroundImage = `url("${theme.art}")`;
@@ -1124,11 +1220,16 @@ ${selector} {
1124
1220
  });
1125
1221
  reloadButton.addEventListener("click", (e) => {
1126
1222
  e.stopPropagation();
1127
- if (reloadButton.classList.contains("ds-reload-loading")) return;
1128
- reloadButton.classList.add("ds-reload-loading");
1223
+ if (reloadButton.classList.contains("ds-local-reload-loading")) return;
1224
+ reloadButton.classList.add("ds-local-reload-loading");
1225
+ const original = reloadButton.textContent;
1226
+ reloadButton.textContent = "正在扫描…";
1129
1227
  // 守护进程轮询到这个标记后会重建主题目录、重新注入并重新打开面板
1130
1228
  window.__TRAE_DREAM_SKIN_RELOAD_REQUEST__ = Date.now();
1131
- setTimeout(() => reloadButton.classList.remove("ds-reload-loading"), 6000);
1229
+ setTimeout(() => {
1230
+ reloadButton.classList.remove("ds-local-reload-loading");
1231
+ reloadButton.textContent = original;
1232
+ }, 6000);
1132
1233
  });
1133
1234
  window.__TRAE_DREAM_SKIN_SYNC_LISTENER__ && window.removeEventListener("trae-dream-skin-sync-state", window.__TRAE_DREAM_SKIN_SYNC_LISTENER__);
1134
1235
  window.__TRAE_DREAM_SKIN_SYNC_LISTENER__ = () => {
@@ -73,10 +73,12 @@ body.trae-skin-v2 {
73
73
  }
74
74
  body.trae-skin-v2 .task-list-panel {
75
75
  background-color: var(--trae-skin-left-surface) !important;
76
- background-image: var(--trae-skin-left-art-layer, none) !important;
77
- background-position: center, var(--trae-skin-left-art-position, center) !important;
78
- background-size: cover, var(--trae-skin-left-art-size, cover) !important;
79
- background-repeat: no-repeat !important;
76
+ background-image:
77
+ var(--trae-skin-left-decoration, none),
78
+ var(--trae-skin-left-art-layer, none) !important;
79
+ background-position: var(--trae-skin-left-decoration-position, center), center, var(--trae-skin-left-art-position, center) !important;
80
+ background-size: var(--trae-skin-left-decoration-size, 100% 100%), 100% 100%, var(--trae-skin-left-art-size, cover) !important;
81
+ background-repeat: var(--trae-skin-left-decoration-repeat, no-repeat), no-repeat, no-repeat !important;
80
82
  border-right: 1px solid var(--trae-skin-divider) !important;
81
83
  backdrop-filter:
82
84
  blur(var(--trae-skin-left-blur))
@@ -86,7 +88,11 @@ body.trae-skin-v2 .task-list-panel {
86
88
  saturate(var(--trae-skin-left-saturation)) !important;
87
89
  }
88
90
  body.trae-skin-v2 .solo-lite-chat-panel-container {
89
- background: var(--trae-skin-chat-surface) !important;
91
+ background-color: var(--trae-skin-chat-surface) !important;
92
+ background-image: var(--trae-skin-chat-decoration, none) !important;
93
+ background-position: var(--trae-skin-chat-decoration-position, center) !important;
94
+ background-size: var(--trae-skin-chat-decoration-size, 100% 100%) !important;
95
+ background-repeat: var(--trae-skin-chat-decoration-repeat, no-repeat) !important;
90
96
  border-right: 1px solid var(--trae-skin-divider) !important;
91
97
  backdrop-filter:
92
98
  blur(var(--trae-skin-chat-blur))
@@ -97,10 +103,12 @@ body.trae-skin-v2 .solo-lite-chat-panel-container {
97
103
  }
98
104
  body.trae-skin-v2 .solo-lite-main-area {
99
105
  background-color: var(--trae-skin-main-surface) !important;
100
- background-image: var(--trae-skin-right-art-layer, none) !important;
101
- background-position: center, var(--trae-skin-right-art-position, right center) !important;
102
- background-size: cover, var(--trae-skin-right-art-size, auto 100%) !important;
103
- background-repeat: no-repeat !important;
106
+ background-image:
107
+ var(--trae-skin-main-decoration, none),
108
+ var(--trae-skin-right-art-layer, none) !important;
109
+ background-position: var(--trae-skin-main-decoration-position, center), center, var(--trae-skin-right-art-position, right center) !important;
110
+ background-size: var(--trae-skin-main-decoration-size, 100% 100%), 100% 100%, var(--trae-skin-right-art-size, auto 100%) !important;
111
+ background-repeat: var(--trae-skin-main-decoration-repeat, no-repeat), no-repeat, no-repeat !important;
104
112
  backdrop-filter:
105
113
  blur(var(--trae-skin-main-blur))
106
114
  saturate(var(--trae-skin-main-saturation)) !important;
@@ -109,7 +117,11 @@ body.trae-skin-v2 .solo-lite-main-area {
109
117
  saturate(var(--trae-skin-main-saturation)) !important;
110
118
  }
111
119
  body.trae-skin-v2 .panel-container > .panel-content {
112
- background: var(--trae-skin-landing-surface) !important;
120
+ background-color: var(--trae-skin-landing-surface) !important;
121
+ background-image: var(--trae-skin-landing-decoration, none) !important;
122
+ background-position: var(--trae-skin-landing-decoration-position, center) !important;
123
+ background-size: var(--trae-skin-landing-decoration-size, 100% 100%) !important;
124
+ background-repeat: var(--trae-skin-landing-decoration-repeat, no-repeat) !important;
113
125
  backdrop-filter:
114
126
  blur(var(--trae-skin-landing-blur))
115
127
  saturate(var(--trae-skin-landing-saturation)) !important;
@@ -59,37 +59,49 @@ body.trae-skin-v2 #trae-dream-skin-button {
59
59
  }
60
60
  #trae-dream-skin-panel .ds-header-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 5px; }
61
61
  #trae-dream-skin-panel .ds-config-toggle,
62
- #trae-dream-skin-panel .ds-sync,
63
62
  #trae-dream-skin-panel .ds-close,
64
- #trae-dream-skin-panel .ds-reload {
63
+ #trae-dream-skin-panel .ds-local-reload,
64
+ #trae-dream-skin-panel .ds-global-toggle {
65
65
  border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 6px;
66
66
  color: inherit; background: rgba(255, 255, 255, 0.08);
67
67
  font: inherit; cursor: pointer;
68
68
  }
69
69
  #trae-dream-skin-panel .ds-config-toggle { padding: 3px 7px; }
70
- #trae-dream-skin-panel .ds-sync { position: relative; padding: 3px 7px; }
70
+ #trae-dream-skin-panel .ds-sync {
71
+ position: relative; flex: 0 0 auto; padding: 4px 8px; border-radius: 6px;
72
+ border: 1px solid rgba(124, 156, 255, 0.52); color: inherit;
73
+ background: rgba(124, 156, 255, 0.13); font: inherit; cursor: pointer;
74
+ }
71
75
  #trae-dream-skin-panel .ds-sync.ds-sync-pending::after {
72
76
  content: ""; position: absolute; top: -3px; right: -3px;
73
77
  width: 6px; height: 6px; border-radius: 50%; background: #ff8d98;
74
78
  border: 1px solid rgba(20, 22, 34, 0.9);
75
79
  }
76
80
  #trae-dream-skin-panel .ds-close,
77
- #trae-dream-skin-panel .ds-reload {
81
+ #trae-dream-skin-panel .ds-local-reload,
82
+ #trae-dream-skin-panel .ds-global-toggle {
78
83
  box-sizing: border-box; width: 23px; height: 23px; padding: 0;
79
84
  display: flex; align-items: center; justify-content: center;
80
85
  font-size: 14px; line-height: 1;
81
86
  }
87
+ #trae-dream-skin-panel .ds-local-reload {
88
+ width: auto; height: auto; padding: 4px 7px; font-size: 10px; line-height: 1.2;
89
+ }
90
+ #trae-dream-skin-panel .ds-global-toggle { position: relative; padding: 0; }
91
+ #trae-dream-skin-panel .ds-global-toggle svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
92
+ #trae-dream-skin-panel .ds-global-toggle.ds-global-toggle-active { background: rgba(124, 156, 255, 0.18); border-color: rgba(124, 156, 255, 0.56); }
93
+ #trae-dream-skin-panel .ds-global-toggle.ds-global-toggle-active:hover { background: rgba(124, 156, 255, 0.24); }
94
+ #trae-dream-skin-panel .ds-global-toggle.ds-global-pending::after {
95
+ content: ""; position: absolute; top: -3px; right: -3px; width: 6px; height: 6px;
96
+ border-radius: 50%; background: #ff8d98; border: 1px solid rgba(20, 22, 34, 0.9);
97
+ }
82
98
  #trae-dream-skin-panel .ds-config-toggle:hover,
83
99
  #trae-dream-skin-panel .ds-sync:hover:not(:disabled),
84
100
  #trae-dream-skin-panel .ds-close:hover,
85
- #trae-dream-skin-panel .ds-reload:hover { background: rgba(255, 255, 255, 0.14); }
86
- @keyframes trae-skin-spin { to { transform: rotate(360deg); } }
87
- #trae-dream-skin-panel .ds-reload-icon { display: inline-block; line-height: 1; }
88
- #trae-dream-skin-panel .ds-reload.ds-reload-loading { pointer-events: none; }
89
- #trae-dream-skin-panel .ds-reload.ds-reload-loading .ds-reload-icon {
90
- animation: trae-skin-spin 0.8s linear infinite;
91
- }
101
+ #trae-dream-skin-panel .ds-local-reload:hover,
102
+ #trae-dream-skin-panel .ds-global-toggle:hover { background: rgba(255, 255, 255, 0.14); }
92
103
  #trae-dream-skin-panel .ds-sync:disabled { opacity: 0.55; cursor: wait; }
104
+ #trae-dream-skin-panel .ds-local-reload.ds-local-reload-loading { opacity: 0.55; cursor: wait; }
93
105
  #trae-dream-skin-panel .ds-body {
94
106
  flex: 1 1 auto; overflow-y: auto; padding: 12px;
95
107
  scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.22) transparent;
@@ -118,7 +130,8 @@ body.trae-skin-v2 #trae-dream-skin-button {
118
130
  }
119
131
  #trae-dream-skin-panel .ds-sync-action:hover { background: rgba(255, 255, 255, 0.18); }
120
132
  #trae-dream-skin-panel .ds-gallery-view,
121
- #trae-dream-skin-panel .ds-config-view { animation: trae-skin-fade-in 0.15s ease; }
133
+ #trae-dream-skin-panel .ds-config-view,
134
+ #trae-dream-skin-panel .ds-global-view { animation: trae-skin-fade-in 0.15s ease; }
122
135
  /* 分类 Tab */
123
136
  #trae-dream-skin-panel .ds-tabs {
124
137
  flex: 0 0 auto; display: flex; gap: 4px;
@@ -156,7 +169,8 @@ body.trae-skin-v2 #trae-dream-skin-panel .ds-tab.ds-tab-active {
156
169
  min-width: 0; opacity: 0.5;
157
170
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
158
171
  }
159
- #trae-dream-skin-panel .ds-config-view { display: none; }
172
+ #trae-dream-skin-panel .ds-config-view,
173
+ #trae-dream-skin-panel .ds-global-view { display: none; }
160
174
  #trae-dream-skin-panel .ds-config-head {
161
175
  display: flex; align-items: center; justify-content: space-between;
162
176
  gap: 8px; margin: 2px 2px 8px;
@@ -165,6 +179,9 @@ body.trae-skin-v2 #trae-dream-skin-panel .ds-tab.ds-tab-active {
165
179
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
166
180
  font-weight: 600;
167
181
  }
182
+ #trae-dream-skin-panel .ds-global-head { margin: 2px 2px 8px; }
183
+ #trae-dream-skin-panel .ds-global-title { font-weight: 600; }
184
+ #trae-dream-skin-panel .ds-global-desc { margin-top: 1px; font-size: 10px; opacity: 0.58; }
168
185
  #trae-dream-skin-panel .ds-setting-row {
169
186
  display: flex; align-items: center; justify-content: space-between;
170
187
  gap: 12px; margin: 0 2px 9px; padding: 8px 9px;
@@ -309,10 +326,12 @@ body.trae-skin-v2 #trae-dream-skin-panel .ds-tab.ds-tab-active {
309
326
  #trae-dream-skin-panel .ds-badge {
310
327
  flex: 0 0 auto;
311
328
  display: none; align-items: center; gap: 3px;
312
- padding: 2px 6px; border-radius: 999px;
313
- background: #7c9cff; color: #0a0a0a;
329
+ padding: 0; color: #7c9cff;
314
330
  font-size: 10px; font-weight: 600;
315
331
  }
332
+ #trae-dream-skin-panel .ds-badge::before {
333
+ content: ""; width: 5px; height: 5px; border-radius: 50%; background: currentColor;
334
+ }
316
335
  #trae-dream-skin-panel .ds-card.ds-active .ds-badge { display: flex; }
317
336
  body.trae-skin-v2 #trae-dream-skin-panel {
318
337
  color: var(--trae-skin-text-primary);
@@ -325,19 +344,16 @@ body.trae-skin-v2 #trae-dream-skin-panel .ds-card.ds-active {
325
344
  border-color: var(--trae-skin-accent);
326
345
  background: var(--trae-skin-accent-subtle);
327
346
  }
328
- body.trae-skin-v2 #trae-dream-skin-panel .ds-badge {
329
- background: var(--trae-skin-accent);
330
- color: var(--trae-skin-accent-on);
331
- }
347
+ body.trae-skin-v2 #trae-dream-skin-panel .ds-badge { color: var(--trae-skin-accent); }
332
348
  body.trae-skin-v2 #trae-dream-skin-panel .ds-header { border-bottom-color: var(--trae-skin-border-subtle); }
333
349
  body.trae-skin-v2 #trae-dream-skin-panel .ds-footer { border-top-color: var(--trae-skin-border-subtle); }
334
350
  body.trae-skin-v2 #trae-dream-skin-panel .ds-body::-webkit-scrollbar-thumb { background: var(--trae-skin-scrollbar-thumb); }
335
351
  body.trae-skin-v2 #trae-dream-skin-panel .ds-body::-webkit-scrollbar-thumb:hover { background: var(--trae-skin-scrollbar-hover); }
336
352
  body.trae-skin-effects-max #trae-dream-skin-panel .ds-header { padding-right: 16px; }
337
353
  body.trae-skin-v2 #trae-dream-skin-panel .ds-config-toggle,
338
- body.trae-skin-v2 #trae-dream-skin-panel .ds-sync,
339
354
  body.trae-skin-v2 #trae-dream-skin-panel .ds-close,
340
- body.trae-skin-v2 #trae-dream-skin-panel .ds-reload,
355
+ body.trae-skin-v2 #trae-dream-skin-panel .ds-local-reload,
356
+ body.trae-skin-v2 #trae-dream-skin-panel .ds-global-toggle,
341
357
  body.trae-skin-v2 #trae-dream-skin-panel .ds-copy-config,
342
358
  body.trae-skin-v2 #trae-dream-skin-panel .ds-reset-config,
343
359
  body.trae-skin-v2 #trae-dream-skin-panel .ds-reset-cancel {
@@ -348,13 +364,19 @@ body.trae-skin-v2 #trae-dream-skin-panel .ds-reset-cancel {
348
364
  body.trae-skin-v2 #trae-dream-skin-panel .ds-config-toggle:hover,
349
365
  body.trae-skin-v2 #trae-dream-skin-panel .ds-sync:hover:not(:disabled),
350
366
  body.trae-skin-v2 #trae-dream-skin-panel .ds-close:hover,
351
- body.trae-skin-v2 #trae-dream-skin-panel .ds-reload:hover,
367
+ body.trae-skin-v2 #trae-dream-skin-panel .ds-local-reload:hover,
368
+ body.trae-skin-v2 #trae-dream-skin-panel .ds-global-toggle:hover,
352
369
  body.trae-skin-v2 #trae-dream-skin-panel .ds-copy-config:hover,
353
370
  body.trae-skin-v2 #trae-dream-skin-panel .ds-reset-config:hover,
354
371
  body.trae-skin-v2 #trae-dream-skin-panel .ds-reset-cancel:hover {
355
372
  color: var(--trae-skin-accent);
356
373
  border-color: var(--trae-skin-accent);
357
374
  }
375
+ body.trae-skin-v2 #trae-dream-skin-panel .ds-global-toggle.ds-global-toggle-active {
376
+ color: var(--trae-skin-accent);
377
+ border-color: var(--trae-skin-accent);
378
+ background: var(--trae-skin-accent-subtle);
379
+ }
358
380
  body.trae-skin-v2 #trae-dream-skin-panel .ds-reset-config.ds-reset-armed {
359
381
  color: var(--trae-skin-error);
360
382
  border-color: var(--trae-skin-error);
@@ -371,6 +393,9 @@ body.trae-skin-v2 #trae-dream-skin-panel .ds-setting-row {
371
393
  body.trae-skin-v2 #trae-dream-skin-panel .ds-sync-banner {
372
394
  border-color: var(--trae-skin-border-default); background: var(--trae-skin-info-subtle);
373
395
  }
396
+ body.trae-skin-v2 #trae-dream-skin-panel .ds-sync {
397
+ border-color: var(--trae-skin-border-strong); background: var(--trae-skin-accent-subtle);
398
+ }
374
399
  body.trae-skin-v2 #trae-dream-skin-panel .ds-sync-banner.ds-sync-error {
375
400
  border-color: var(--trae-skin-error); background: color-mix(in srgb, var(--trae-skin-error) 12%, transparent);
376
401
  }