twskin 0.5.4 → 0.5.6

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/runtime/skin.js CHANGED
@@ -7,6 +7,7 @@
7
7
  const TOKEN_MAP = self.DREAM_SKIN_TOKEN_MAP;
8
8
  const COMPONENT_MAP = self.DREAM_SKIN_COMPONENT_MAP;
9
9
  const CATALOG = __CATALOG__;
10
+ let themeSyncState = __THEME_SYNC_STATE__;
10
11
  const DEFAULT_THEME = __DEFAULT_THEME__;
11
12
  const VERSION = __VERSION__;
12
13
  const BASE_CSS = __BASE_CSS__;
@@ -664,25 +665,39 @@ ${selector} {
664
665
  const configToggle = document.createElement("button");
665
666
  configToggle.className = "ds-config-toggle";
666
667
  configToggle.type = "button";
667
- configToggle.textContent = "配置";
668
+ configToggle.textContent = "当前主题";
668
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>';
677
+ const syncButton = document.createElement("button");
678
+ syncButton.className = "ds-sync";
679
+ syncButton.type = "button";
680
+ syncButton.textContent = "检查更新";
681
+ syncButton.title = "检查官方主题更新";
669
682
  const reloadButton = document.createElement("button");
670
- reloadButton.className = "ds-reload";
683
+ reloadButton.className = "ds-local-reload";
671
684
  reloadButton.type = "button";
672
- reloadButton.innerHTML = '<span class="ds-reload-icon">↻</span>';
685
+ reloadButton.textContent = "重新扫描本地主题";
673
686
  reloadButton.title = "重新加载主题目录(新主题热加载)";
674
687
  const closeButton = document.createElement("button");
675
688
  closeButton.className = "ds-close";
676
689
  closeButton.type = "button";
677
690
  closeButton.textContent = "×";
678
691
  closeButton.title = "关闭 (Esc)";
679
- headerActions.append(reloadButton, configToggle, closeButton);
692
+ headerActions.append(configToggle, globalToggle, closeButton);
680
693
  header.append(headerText, headerActions);
681
694
  const panelBody = document.createElement("div");
682
695
  panelBody.className = "ds-body";
683
696
 
684
697
  const galleryView = document.createElement("div");
685
698
  galleryView.className = "ds-gallery-view";
699
+ const syncBanner = document.createElement("div");
700
+ syncBanner.className = "ds-sync-banner";
686
701
  const configView = document.createElement("div");
687
702
  configView.className = "ds-config-view";
688
703
  const configHead = document.createElement("div");
@@ -709,6 +724,58 @@ ${selector} {
709
724
  blurSwitch.setAttribute("role", "switch");
710
725
  blurSwitch.setAttribute("aria-label", "面板模糊");
711
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);
750
+ const updateRow = document.createElement("div");
751
+ updateRow.className = "ds-setting-row";
752
+ const updateCopy = document.createElement("div");
753
+ updateCopy.className = "ds-setting-copy";
754
+ const updateLabel = document.createElement("div");
755
+ updateLabel.className = "ds-setting-label";
756
+ updateLabel.textContent = "自动更新官方主题";
757
+ const updateDesc = document.createElement("div");
758
+ updateDesc.className = "ds-setting-desc";
759
+ updateDesc.textContent = "发现兼容的新主题或更新时自动下载安装";
760
+ updateCopy.append(updateLabel, updateDesc);
761
+ const updateSwitch = document.createElement("button");
762
+ updateSwitch.className = "ds-switch";
763
+ updateSwitch.type = "button";
764
+ updateSwitch.setAttribute("role", "switch");
765
+ updateSwitch.setAttribute("aria-label", "自动更新官方主题");
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);
712
779
  const brightnessRow = document.createElement("div");
713
780
  brightnessRow.className = "ds-setting-row ds-setting-row-slider";
714
781
  const brightnessHead = document.createElement("div");
@@ -769,8 +836,21 @@ ${selector} {
769
836
  resetActions.className = "ds-reset-actions";
770
837
  resetActions.append(resetCancel, resetConfig);
771
838
  configHead.append(configName, copyConfig);
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);
772
851
  configView.append(configHead, brightnessRow, mainOverlayRow, blurRow, configCode);
773
- panelBody.append(galleryView, configView);
852
+ globalView.append(globalHead, syncBanner, checkRow, updateRow, localThemesRow, restoreRow);
853
+ panelBody.append(galleryView, configView, globalView);
774
854
 
775
855
  // 分类 Tab(固定在头部下,不随列表滚动;选择持久化)
776
856
  const CATEGORY_LS = `${LS_PREFIX}category`;
@@ -811,25 +891,31 @@ ${selector} {
811
891
  const footerText = document.createElement("span");
812
892
  footerText.className = "ds-footer-text";
813
893
  footerText.textContent = `TRAE Work Skin v${VERSION}`;
814
- footer.append(footerText, resetActions);
894
+ footer.append(footerText);
815
895
  panel.append(header, tabsBar, panelBody, footer);
816
896
 
817
- let configOpen = false;
818
- const showConfig = (open) => {
819
- configOpen = Boolean(open);
820
- // 配置视图只展示当前主题设置;返回画廊时再恢复分类栏和主题列表。
821
- tabsBar.style.display = configOpen ? "none" : "";
822
- galleryView.style.display = configOpen ? "none" : "";
823
- configView.style.display = configOpen ? "block" : "";
824
- configToggle.textContent = configOpen ? "主题" : "配置";
825
- 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();
826
911
  };
912
+ const showConfig = (open) => showView(open ? "theme" : "gallery");
827
913
  renderCurrentConfig = (theme = findTheme(
828
914
  (() => { try { return localStorage.getItem(LS_KEY); } catch { return null; } })(),
829
915
  )) => {
830
916
  if (!theme) return;
831
917
  const config = publicThemeConfig(theme);
832
- configName.textContent = `${theme.name} · ${theme.id}`;
918
+ configName.textContent = `${theme.name} · ${theme.id} · v${theme.version || "0.0.0"}`;
833
919
  currentLabel.textContent = `当前:${theme.name}`;
834
920
  configCode.textContent = JSON.stringify(config, null, 2);
835
921
  };
@@ -840,6 +926,94 @@ ${selector} {
840
926
  blurSwitch.setAttribute("aria-checked", String(Boolean(enabled)));
841
927
  blurSwitch.title = enabled ? "关闭毛玻璃效果" : "启用毛玻璃效果";
842
928
  };
929
+ const requestThemeSync = (action, autoUpdate) => {
930
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
931
+ window.__TRAE_DREAM_SKIN_SYNC_REQUEST__ = { id, action, ...(typeof autoUpdate === "boolean" ? { autoUpdate } : {}) };
932
+ themeSyncState = {
933
+ ...themeSyncState,
934
+ phase: action === "check" ? "checking" : action === "auto-update" ? "idle" : "downloading",
935
+ message: action === "check" ? "正在检查主题更新" : "正在同步官方主题",
936
+ };
937
+ renderThemeSyncState();
938
+ };
939
+ const syncSummary = () => {
940
+ if (themeSyncState.phase === "checking") return "正在检查主题更新…";
941
+ if (themeSyncState.phase === "downloading" || themeSyncState.phase === "installing") return themeSyncState.message || "正在同步官方主题…";
942
+ if (themeSyncState.phase === "update-available") return `发现 ${themeSyncState.newThemes || 0} 个新主题、${themeSyncState.updatedThemes || 0} 个更新`;
943
+ if (themeSyncState.phase === "success") return themeSyncState.message || "主题同步完成";
944
+ if (themeSyncState.phase === "error") return themeSyncState.message || "主题更新失败";
945
+ return "";
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
+ };
954
+ const renderThemeSyncState = () => {
955
+ const phase = themeSyncState.phase || "idle";
956
+ const summary = syncSummary();
957
+ const updateCount = Number(themeSyncState.updateCount || 0);
958
+ const showSuccessFeedback = hasRecentSyncSuccess();
959
+ syncBanner.replaceChildren();
960
+ syncBanner.className = `ds-sync-banner ds-sync-${phase}`;
961
+ if (!summary || phase === "fresh" || phase === "idle" || (phase === "success" && !showSuccessFeedback) || activeView !== "global") {
962
+ syncBanner.style.display = "none";
963
+ } else {
964
+ syncBanner.style.display = "flex";
965
+ const text = document.createElement("span");
966
+ text.className = "ds-sync-copy";
967
+ text.textContent = summary;
968
+ syncBanner.append(text);
969
+ if (phase === "update-available") {
970
+ const install = document.createElement("button");
971
+ install.type = "button";
972
+ install.className = "ds-sync-action";
973
+ install.textContent = "立即更新";
974
+ install.addEventListener("click", (event) => {
975
+ event.stopPropagation();
976
+ requestThemeSync("sync");
977
+ });
978
+ syncBanner.append(install);
979
+ } else if (phase === "error") {
980
+ const retry = document.createElement("button");
981
+ retry.type = "button";
982
+ retry.className = "ds-sync-action";
983
+ retry.textContent = "重试";
984
+ retry.addEventListener("click", (event) => {
985
+ event.stopPropagation();
986
+ requestThemeSync("check");
987
+ });
988
+ syncBanner.append(retry);
989
+ }
990
+ }
991
+ syncButton.classList.toggle("ds-sync-pending", updateCount > 0);
992
+ syncButton.disabled = phase === "checking" || phase === "downloading" || phase === "installing";
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
+ }
1013
+ updateSwitch.setAttribute("aria-checked", String(Boolean(themeSyncState.autoUpdateThemes)));
1014
+ updateSwitch.title = themeSyncState.autoUpdateThemes ? "关闭官方主题自动更新" : "开启官方主题自动更新";
1015
+ footerText.textContent = `TRAE Work Skin v${VERSION}`;
1016
+ };
843
1017
  renderBrightnessControl = (theme = findTheme(
844
1018
  (() => { try { return localStorage.getItem(LS_KEY); } catch { return null; } })(),
845
1019
  ), value = theme ? backgroundBrightness(theme) : 1) => {
@@ -866,8 +1040,24 @@ ${selector} {
866
1040
  };
867
1041
  configToggle.addEventListener("click", (event) => {
868
1042
  event.stopPropagation();
869
- renderCurrentConfig();
870
- 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");
1053
+ });
1054
+ syncButton.addEventListener("click", (event) => {
1055
+ event.stopPropagation();
1056
+ requestThemeSync("check");
1057
+ });
1058
+ updateSwitch.addEventListener("click", (event) => {
1059
+ event.stopPropagation();
1060
+ requestThemeSync("auto-update", !themeSyncState.autoUpdateThemes);
871
1061
  });
872
1062
  copyConfig.addEventListener("click", async (event) => {
873
1063
  event.stopPropagation();
@@ -976,7 +1166,7 @@ ${selector} {
976
1166
  card.dataset.category = theme.category || "其他";
977
1167
  const badge = document.createElement("span");
978
1168
  badge.className = "ds-badge";
979
- badge.textContent = "✓ 使用中";
1169
+ badge.textContent = "当前主题";
980
1170
  const preview = document.createElement("div");
981
1171
  preview.className = "ds-preview";
982
1172
  preview.style.backgroundImage = `url("${theme.art}")`;
@@ -986,6 +1176,10 @@ ${selector} {
986
1176
  name.className = "ds-name";
987
1177
  name.textContent = theme.name;
988
1178
  meta.appendChild(name);
1179
+ const version = document.createElement("span");
1180
+ version.className = "ds-version";
1181
+ version.textContent = `v${theme.version || "0.0.0"}`;
1182
+ meta.appendChild(version);
989
1183
  if (theme.desc) {
990
1184
  const desc = document.createElement("span");
991
1185
  desc.className = "ds-desc";
@@ -998,6 +1192,7 @@ ${selector} {
998
1192
  galleryView.appendChild(card);
999
1193
  }
1000
1194
  applyCategoryFilter();
1195
+ renderThemeSyncState();
1001
1196
 
1002
1197
  const button = document.createElement("button");
1003
1198
  button.id = BUTTON_ID;
@@ -1025,12 +1220,26 @@ ${selector} {
1025
1220
  });
1026
1221
  reloadButton.addEventListener("click", (e) => {
1027
1222
  e.stopPropagation();
1028
- if (reloadButton.classList.contains("ds-reload-loading")) return;
1029
- 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 = "正在扫描…";
1030
1227
  // 守护进程轮询到这个标记后会重建主题目录、重新注入并重新打开面板
1031
1228
  window.__TRAE_DREAM_SKIN_RELOAD_REQUEST__ = Date.now();
1032
- setTimeout(() => reloadButton.classList.remove("ds-reload-loading"), 6000);
1229
+ setTimeout(() => {
1230
+ reloadButton.classList.remove("ds-local-reload-loading");
1231
+ reloadButton.textContent = original;
1232
+ }, 6000);
1033
1233
  });
1234
+ window.__TRAE_DREAM_SKIN_SYNC_LISTENER__ && window.removeEventListener("trae-dream-skin-sync-state", window.__TRAE_DREAM_SKIN_SYNC_LISTENER__);
1235
+ window.__TRAE_DREAM_SKIN_SYNC_LISTENER__ = () => {
1236
+ const next = window.__TRAE_DREAM_SKIN_SYNC_STATE__;
1237
+ if (next && typeof next === "object") {
1238
+ themeSyncState = next;
1239
+ renderThemeSyncState();
1240
+ }
1241
+ };
1242
+ window.addEventListener("trae-dream-skin-sync-state", window.__TRAE_DREAM_SKIN_SYNC_LISTENER__);
1034
1243
  document.addEventListener("click", (e) => {
1035
1244
  if (isPanelOpen() && !panel.contains(e.target)) setPanelOpen(false);
1036
1245
  });
@@ -60,27 +60,48 @@ body.trae-skin-v2 #trae-dream-skin-button {
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
62
  #trae-dream-skin-panel .ds-close,
63
- #trae-dream-skin-panel .ds-reload {
63
+ #trae-dream-skin-panel .ds-local-reload,
64
+ #trae-dream-skin-panel .ds-global-toggle {
64
65
  border: 1px solid rgba(255, 255, 255, 0.18); border-radius: 6px;
65
66
  color: inherit; background: rgba(255, 255, 255, 0.08);
66
67
  font: inherit; cursor: pointer;
67
68
  }
68
69
  #trae-dream-skin-panel .ds-config-toggle { 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
+ }
75
+ #trae-dream-skin-panel .ds-sync.ds-sync-pending::after {
76
+ content: ""; position: absolute; top: -3px; right: -3px;
77
+ width: 6px; height: 6px; border-radius: 50%; background: #ff8d98;
78
+ border: 1px solid rgba(20, 22, 34, 0.9);
79
+ }
69
80
  #trae-dream-skin-panel .ds-close,
70
- #trae-dream-skin-panel .ds-reload {
81
+ #trae-dream-skin-panel .ds-local-reload,
82
+ #trae-dream-skin-panel .ds-global-toggle {
71
83
  box-sizing: border-box; width: 23px; height: 23px; padding: 0;
72
84
  display: flex; align-items: center; justify-content: center;
73
85
  font-size: 14px; line-height: 1;
74
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
+ }
75
98
  #trae-dream-skin-panel .ds-config-toggle:hover,
99
+ #trae-dream-skin-panel .ds-sync:hover:not(:disabled),
76
100
  #trae-dream-skin-panel .ds-close:hover,
77
- #trae-dream-skin-panel .ds-reload:hover { background: rgba(255, 255, 255, 0.14); }
78
- @keyframes trae-skin-spin { to { transform: rotate(360deg); } }
79
- #trae-dream-skin-panel .ds-reload-icon { display: inline-block; line-height: 1; }
80
- #trae-dream-skin-panel .ds-reload.ds-reload-loading { pointer-events: none; }
81
- #trae-dream-skin-panel .ds-reload.ds-reload-loading .ds-reload-icon {
82
- animation: trae-skin-spin 0.8s linear infinite;
83
- }
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); }
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; }
84
105
  #trae-dream-skin-panel .ds-body {
85
106
  flex: 1 1 auto; overflow-y: auto; padding: 12px;
86
107
  scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.22) transparent;
@@ -92,8 +113,25 @@ body.trae-skin-v2 #trae-dream-skin-button {
92
113
  #trae-dream-skin-panel .ds-gallery-view {
93
114
  display: grid; grid-template-columns: 1fr; gap: 4px;
94
115
  }
116
+ #trae-dream-skin-panel .ds-sync-banner {
117
+ display: none; align-items: center; justify-content: space-between; gap: 8px;
118
+ margin: 0 0 9px; padding: 7px 8px; border-radius: 8px;
119
+ border: 1px solid rgba(124, 156, 255, 0.38); background: rgba(124, 156, 255, 0.1);
120
+ font-size: 10px;
121
+ }
122
+ #trae-dream-skin-panel .ds-sync-banner.ds-sync-error {
123
+ border-color: rgba(255, 102, 117, 0.58); background: rgba(255, 82, 99, 0.1);
124
+ }
125
+ #trae-dream-skin-panel .ds-sync-copy { min-width: 0; flex: 1 1 auto; }
126
+ #trae-dream-skin-panel .ds-sync-action {
127
+ flex: 0 0 auto; padding: 3px 7px; border-radius: 5px;
128
+ border: 1px solid rgba(255, 255, 255, 0.2); color: inherit;
129
+ background: rgba(255, 255, 255, 0.1); font: inherit; cursor: pointer;
130
+ }
131
+ #trae-dream-skin-panel .ds-sync-action:hover { background: rgba(255, 255, 255, 0.18); }
95
132
  #trae-dream-skin-panel .ds-gallery-view,
96
- #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; }
97
135
  /* 分类 Tab */
98
136
  #trae-dream-skin-panel .ds-tabs {
99
137
  flex: 0 0 auto; display: flex; gap: 4px;
@@ -131,7 +169,8 @@ body.trae-skin-v2 #trae-dream-skin-panel .ds-tab.ds-tab-active {
131
169
  min-width: 0; opacity: 0.5;
132
170
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
133
171
  }
134
- #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; }
135
174
  #trae-dream-skin-panel .ds-config-head {
136
175
  display: flex; align-items: center; justify-content: space-between;
137
176
  gap: 8px; margin: 2px 2px 8px;
@@ -140,6 +179,9 @@ body.trae-skin-v2 #trae-dream-skin-panel .ds-tab.ds-tab-active {
140
179
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
141
180
  font-weight: 600;
142
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; }
143
185
  #trae-dream-skin-panel .ds-setting-row {
144
186
  display: flex; align-items: center; justify-content: space-between;
145
187
  gap: 12px; margin: 0 2px 9px; padding: 8px 9px;
@@ -273,6 +315,10 @@ body.trae-skin-v2 #trae-dream-skin-panel .ds-tab.ds-tab-active {
273
315
  display: block; font-size: 12px; font-weight: 600;
274
316
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
275
317
  }
318
+ #trae-dream-skin-panel .ds-version {
319
+ display: block; margin-top: 1px; font-size: 9px; opacity: 0.48;
320
+ font-variant-numeric: tabular-nums;
321
+ }
276
322
  #trae-dream-skin-panel .ds-desc {
277
323
  display: block; margin-top: 1px; font-size: 10px; opacity: 0.65;
278
324
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
@@ -280,10 +326,12 @@ body.trae-skin-v2 #trae-dream-skin-panel .ds-tab.ds-tab-active {
280
326
  #trae-dream-skin-panel .ds-badge {
281
327
  flex: 0 0 auto;
282
328
  display: none; align-items: center; gap: 3px;
283
- padding: 2px 6px; border-radius: 999px;
284
- background: #7c9cff; color: #0a0a0a;
329
+ padding: 0; color: #7c9cff;
285
330
  font-size: 10px; font-weight: 600;
286
331
  }
332
+ #trae-dream-skin-panel .ds-badge::before {
333
+ content: ""; width: 5px; height: 5px; border-radius: 50%; background: currentColor;
334
+ }
287
335
  #trae-dream-skin-panel .ds-card.ds-active .ds-badge { display: flex; }
288
336
  body.trae-skin-v2 #trae-dream-skin-panel {
289
337
  color: var(--trae-skin-text-primary);
@@ -296,10 +344,7 @@ body.trae-skin-v2 #trae-dream-skin-panel .ds-card.ds-active {
296
344
  border-color: var(--trae-skin-accent);
297
345
  background: var(--trae-skin-accent-subtle);
298
346
  }
299
- body.trae-skin-v2 #trae-dream-skin-panel .ds-badge {
300
- background: var(--trae-skin-accent);
301
- color: var(--trae-skin-accent-on);
302
- }
347
+ body.trae-skin-v2 #trae-dream-skin-panel .ds-badge { color: var(--trae-skin-accent); }
303
348
  body.trae-skin-v2 #trae-dream-skin-panel .ds-header { border-bottom-color: var(--trae-skin-border-subtle); }
304
349
  body.trae-skin-v2 #trae-dream-skin-panel .ds-footer { border-top-color: var(--trae-skin-border-subtle); }
305
350
  body.trae-skin-v2 #trae-dream-skin-panel .ds-body::-webkit-scrollbar-thumb { background: var(--trae-skin-scrollbar-thumb); }
@@ -307,7 +352,8 @@ body.trae-skin-v2 #trae-dream-skin-panel .ds-body::-webkit-scrollbar-thumb:hover
307
352
  body.trae-skin-effects-max #trae-dream-skin-panel .ds-header { padding-right: 16px; }
308
353
  body.trae-skin-v2 #trae-dream-skin-panel .ds-config-toggle,
309
354
  body.trae-skin-v2 #trae-dream-skin-panel .ds-close,
310
- 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,
311
357
  body.trae-skin-v2 #trae-dream-skin-panel .ds-copy-config,
312
358
  body.trae-skin-v2 #trae-dream-skin-panel .ds-reset-config,
313
359
  body.trae-skin-v2 #trae-dream-skin-panel .ds-reset-cancel {
@@ -316,14 +362,21 @@ body.trae-skin-v2 #trae-dream-skin-panel .ds-reset-cancel {
316
362
  border-color: var(--trae-skin-border-default);
317
363
  }
318
364
  body.trae-skin-v2 #trae-dream-skin-panel .ds-config-toggle:hover,
365
+ body.trae-skin-v2 #trae-dream-skin-panel .ds-sync:hover:not(:disabled),
319
366
  body.trae-skin-v2 #trae-dream-skin-panel .ds-close:hover,
320
- 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,
321
369
  body.trae-skin-v2 #trae-dream-skin-panel .ds-copy-config:hover,
322
370
  body.trae-skin-v2 #trae-dream-skin-panel .ds-reset-config:hover,
323
371
  body.trae-skin-v2 #trae-dream-skin-panel .ds-reset-cancel:hover {
324
372
  color: var(--trae-skin-accent);
325
373
  border-color: var(--trae-skin-accent);
326
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
+ }
327
380
  body.trae-skin-v2 #trae-dream-skin-panel .ds-reset-config.ds-reset-armed {
328
381
  color: var(--trae-skin-error);
329
382
  border-color: var(--trae-skin-error);
@@ -337,6 +390,15 @@ body.trae-skin-v2 #trae-dream-skin-panel .ds-setting-row {
337
390
  background: var(--trae-skin-info-subtle);
338
391
  border-color: var(--trae-skin-border-subtle);
339
392
  }
393
+ body.trae-skin-v2 #trae-dream-skin-panel .ds-sync-banner {
394
+ border-color: var(--trae-skin-border-default); background: var(--trae-skin-info-subtle);
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
+ }
399
+ body.trae-skin-v2 #trae-dream-skin-panel .ds-sync-banner.ds-sync-error {
400
+ border-color: var(--trae-skin-error); background: color-mix(in srgb, var(--trae-skin-error) 12%, transparent);
401
+ }
340
402
  body.trae-skin-v2 #trae-dream-skin-panel .ds-switch[aria-checked="true"] {
341
403
  background: var(--trae-skin-accent);
342
404
  border-color: var(--trae-skin-border-strong);