From a79008fa1a8a386f15054ab4e96778d3bab0178b Mon Sep 17 00:00:00 2001
From: 171696257 <1761696257@qq.com>
Date: Tue, 27 May 2025 15:08:55 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=95=8C=E9=9D=A2UI=E6=B7=BB?=
=?UTF-8?q?=E5=8A=A0=E4=B8=BB=E9=A2=98=E9=80=89=E9=A1=B9=EF=BC=8C=E4=BF=AE?=
=?UTF-8?q?=E5=A4=8DBUG?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README-EN.md | 7 +-
README.md | 12 +-
js/Intelligent Layout/LayoutCore.js | 767 +++++-----------
js/Intelligent Layout/LayoutExt.js | 166 +++-
js/Intelligent Layout/styles/UIStyles.js | 845 ++++++++++++++++++
js/Intelligent Layout/styles/UIUtils.js | 214 +++++
.../styles/古神之眼/AncientGod1.js | 468 ++++++++++
.../styles/古神之眼/AncientGod2.js | 535 +++++++++++
.../styles/古神之眼/AncientGodTheme.js | 842 +++++++++++++++++
.../styles/古神之眼/ChaosPanelStyle.js | 70 ++
.../styles/古神之眼/OrderPanelStyle.js | 262 ++++++
11 files changed, 3641 insertions(+), 547 deletions(-)
create mode 100644 js/Intelligent Layout/styles/UIStyles.js
create mode 100644 js/Intelligent Layout/styles/UIUtils.js
create mode 100644 js/Intelligent Layout/styles/古神之眼/AncientGod1.js
create mode 100644 js/Intelligent Layout/styles/古神之眼/AncientGod2.js
create mode 100644 js/Intelligent Layout/styles/古神之眼/AncientGodTheme.js
create mode 100644 js/Intelligent Layout/styles/古神之眼/ChaosPanelStyle.js
create mode 100644 js/Intelligent Layout/styles/古神之眼/OrderPanelStyle.js
diff --git a/README-EN.md b/README-EN.md
index 9e0f752..dbebe00 100644
--- a/README-EN.md
+++ b/README-EN.md
@@ -88,7 +88,9 @@ High-performance model loading optimizer with multiple optimization modes:
DD Interface Layout
-- Alpha version implemented, can randomly or specifically color node titles, and quickly organize messy workflows into modules
+- Beta version implemented, can randomly or specifically color node titles, and quickly organize messy workflows.
+- Beautified UI, added theme system, each theme has two different styles to switch between, supports custom themes.
+- Added modular presets, you can preset your own layouts and apply them with one click.
@@ -119,6 +121,9 @@ This extension now supports ComfyUI's native internationalization (i18n) system,
3. Currently only supports node translation, frontend functionality translation is not yet supported
## Version History
+- v2.3.1 (2025-05-27)
+ - Improved UI, added theme options, and fixed bugs.
+
- v2.3.0 (2025-05-14)
- Interface Layout Alpha 2.0 version released, with improved interface and basic tools
- Now you can synchronize the width, height, and size of selected nodes, and align all selected nodes
diff --git a/README.md b/README.md
index b846939..541c3d1 100644
--- a/README.md
+++ b/README.md
@@ -90,7 +90,9 @@
DD 界面布局
--已实现Alpha版本,可以随机或指定节点标题的颜色,可以对杂乱工作流做快速的模块化分类整理
+- 已实现Bate版本,可以随机或指定节点标题的颜色,可以对杂乱节点做快速整理
+- 美化UI,添加主题系统,每一种主题有两种不同风格可以切换,支持自定义主题
+- 添加模块化预设,可以预设自己的布局,一键应用
@@ -110,7 +112,10 @@
3. 重启 ComfyUI
方法2(推荐)
-使用Manager或启动器进行安装
+使用Manager或启动器通过git进行安装
+
+方法3(推荐)
+直接在Manager中搜索插件名称安装
## 语言切换
@@ -121,6 +126,9 @@
3. 暂时只支持节点翻译,暂不支持前端功能翻译切换
## 版本历史
+- v2.3.1 (2025-05-27)
+ -完善界面UI添加主题选项,修复BUG。
+
- v2.3.0 (2025-05-14)
- 界面布局Alpha2.0版本上线,完善界面和添加基本工具,现在可以同步选中节点的宽高与大小,对齐所有的选中节点。
- 依然是测试版本,如果有BUG请及时反馈。
diff --git a/js/Intelligent Layout/LayoutCore.js b/js/Intelligent Layout/LayoutCore.js
index a0992ac..dcf6f12 100644
--- a/js/Intelligent Layout/LayoutCore.js
+++ b/js/Intelligent Layout/LayoutCore.js
@@ -1,73 +1,40 @@
// 智能布局核心模块
-// 布局面板相关样式
-const layoutPanelStyles = `
- .layout-panel {
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
- }
-
- /* 按钮彩虹动画 */
- @keyframes rainbow {
- 0% { background-position: 0% 82% }
- 50% { background-position: 100% 19% }
- 100% { background-position: 0% 82% }
- }
- .layout-btn-rainbow:hover {
- background-image: linear-gradient(124deg, #ff2400, #e81d1d, #e8b71d, #e3e81d, #1de840, #1ddde8, #2b1de8, #dd00f3, #dd00f3) !important;
- background-size: 1800% 1800% !important;
- animation: rainbow 6s ease infinite !important;
- }
-
- /* 按钮水波纹效果 */
- .layout-btn::after {
- content: '';
- position: absolute;
- top: 50%;
- left: 50%;
- width: 5px;
- height: 5px;
- background: rgba(255, 255, 255, .5);
- opacity: 0;
- border-radius: 100%;
- transform: scale(1, 1) translate(-50%, -50%);
- transform-origin: 50% 50%;
- }
-
- .layout-btn:active::after {
- animation: ripple 0.6s ease-out;
- }
-
- @keyframes ripple {
- 0% {
- transform: scale(0, 0) translate(-50%, -50%);
- opacity: 0.5;
- }
- 100% {
- transform: scale(20, 20) translate(-50%, -50%);
- opacity: 0;
- }
- }
-`;
+// 导入统一样式模块
+import {
+ layoutPanelStyles,
+ injectStyles,
+ removeStyles,
+ createPanelElement,
+ createButtonsContainer,
+ createButton,
+ createModeContainer,
+ createSelectBox,
+ addOptionsToSelect,
+ setPanelBackgroundOpacity,
+ setButtonsOpacity,
+ showNotification,
+ adjustButtonsLayout,
+ registerTheme,
+ getRegisteredThemes,
+ getTheme,
+ applyTheme,
+ setDefaultTheme,
+ getDefaultTheme,
+ createCoinElement,
+ showAtMousePosition
+} from './styles/UIStyles.js';
-// 工具函数:向页面注入样式
-function injectStyles(styleId, styleContent) {
- // 如果已经存在相同ID的样式,则不重复注入
- if (document.getElementById(styleId)) return;
-
- const styleElement = document.createElement('style');
- styleElement.id = styleId;
- styleElement.textContent = styleContent;
- document.head.appendChild(styleElement);
-}
-
-// 工具函数:移除已注入的样式
-function removeStyles(styleId) {
- const styleElement = document.getElementById(styleId);
- if (styleElement && styleElement.parentNode) {
- styleElement.parentNode.removeChild(styleElement);
- }
-}
+// 导入通用工具函数 - 修改导入路径为styles文件夹
+import {
+ getComfyUIApp,
+ getSelectedNodes,
+ getSelectedGroups,
+ getRandomColor,
+ syncNodeSize,
+ syncNodeWidthHeight,
+ alignNodes
+} from './styles/UIUtils.js';
// 跟踪鼠标位置
let mousePosition = { x: 0, y: 0 };
@@ -75,12 +42,15 @@ document.addEventListener('mousemove', (e) => {
mousePosition = { x: e.clientX, y: e.clientY };
});
-export class LayoutPanel { constructor() {
+export class LayoutPanel {
+ constructor() {
this.shortcut = 'alt+x';
this.visible = false;
this.container = null;
this.buttonsContainer = null;
this.colorMode = '完全随机'; // 默认为完全随机模式
+ this.currentTheme = null;
+ this.coinElement = null;
}
setEnabled() {
@@ -96,6 +66,31 @@ export class LayoutPanel { constructor() {
}
}
+ // 设置当前主题
+ setTheme(themeId) {
+ if (!this.container) {
+ this._createPanel();
+ }
+
+ // 保存当前的透明度设置,以便应用主题后恢复
+ const bgOpacity = this.container.dataset.bgOpacity;
+ const btnOpacity = this.container.dataset.btnOpacity;
+
+ // 应用新主题
+ if (applyTheme(themeId, this.container, this.coinElement)) {
+ this.currentTheme = themeId;
+
+ // 主题应用后,恢复保存的透明度设置
+ setTimeout(() => {
+ if (bgOpacity) this.setOpacity(parseFloat(bgOpacity) * 100);
+ if (btnOpacity) this.setButtonOpacity(parseFloat(btnOpacity) * 100);
+ }, 50); // 短延迟确保主题样式完全应用
+
+ return true;
+ }
+ return false;
+ }
+
// 只有通过toggle方法才能控制面板显示/隐藏,确保只能通过快捷键激活
toggle() {
if (this.visible) {
@@ -110,35 +105,25 @@ export class LayoutPanel { constructor() {
this._createPanel();
}
- // 使用当前鼠标位置
- this._showAtMousePosition();
+ // 使用当前鼠标位置显示面板
+ showAtMousePosition(this.container, mousePosition);
this.container.style.display = 'flex';
this.container.style.pointerEvents = 'auto'; // 显示时允许事件
- // 使用动画效果显示
+ // 使用动画效果显示 - 修改:使用 opacity 动画,不再修改 transform
requestAnimationFrame(() => {
this.container.style.opacity = '1';
- this.container.style.transform = 'scale(1)';
});
this.visible = true;
}
- // 在鼠标位置显示面板
- _showAtMousePosition() {
- if (!this.container) return;
- // 调整为居中显示
- this.container.style.left = `${mousePosition.x}px`;
- this.container.style.top = `${mousePosition.y}px`;
- this.container.style.transform = 'translate(-50%, -50%)';
- }
-
hide() {
if (!this.container) return;
+ // 只淡出,不修改 transform
this.container.style.opacity = '0';
- this.container.style.transform = 'translate(-50%, -50%) scale(0.95)';
this.container.style.pointerEvents = 'none'; // 隐藏时禁止事件
setTimeout(() => {
@@ -153,105 +138,102 @@ export class LayoutPanel { constructor() {
setOpacity(opacity) {
// opacity: 0~100
if (!this.container) return;
- const percent = Math.max(0, Math.min(100, Number(opacity)));
- this.container.style.backgroundColor = `rgba(30, 30, 40, ${percent/100})`;
+ setPanelBackgroundOpacity(this.container, opacity);
}
-
+
+ // 添加新方法:设置按钮透明度
+ setButtonOpacity(opacity) {
+ // opacity: 0~100
+ if (!this.container) return;
+ setButtonsOpacity(this.container, opacity);
+ }
+
_createPanel() {
if (this.container) return;
- // 创建容器
- this.container = document.createElement('div');
- this.container.className = 'layout-panel';
- // 只设置一次 position
- this.container.style.position = 'fixed';
- this.container.style.left = `${mousePosition.x}px`;
- this.container.style.top = `${mousePosition.y}px`;
- this.container.style.transform = 'translate(-50%, -50%)';
- this.container.style.zIndex = 99999;
- this.container.style.display = 'none';
- this.container.style.pointerEvents = 'none'; // 默认禁用事件,确保只有在面板显示时才能交互
- this.container.style.transition = 'opacity 0.3s, transform 0.3s';
- this.container.style.opacity = '0';
- // 更现代的面板样式
- this.container.style.padding = '20px';
- this.container.style.borderRadius = '16px';
- this.container.style.backdropFilter = 'blur(12px)';
- this.container.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.1) inset';
- // 移除 backgroundColor 的硬编码,交由 setOpacity 控制
- this.container.style.border = '1px solid rgba(255, 255, 255, 0.1)';
- this.container.style.flexDirection = 'column';
- this.container.style.alignItems = 'center';
- this.container.style.gap = '16px';
+
+ // 使用外部样式模块创建面板
+ this.container = createPanelElement(mousePosition);
+
+ // 存储初始透明度值到容器数据属性中,便于后续恢复
+ this.container.dataset.initialSetup = 'pending';
+
// 初始化透明度为全局设置值
let opacity = 85;
+ let buttonOpacity = 90;
try {
if (window.app?.extensionManager?.setting) {
opacity = window.app.extensionManager.setting.get("LayoutPanel.opacity", 85);
+ buttonOpacity = window.app.extensionManager.setting.get("LayoutPanel.buttonOpacity", 90);
}
- } catch(e) {}
- this.setOpacity(opacity);
- // 监听设置变化(如果有API)
+ } catch(e) {
+ console.warn("获取透明度设置失败:", e);
+ }
+
+ // 将初始透明度值存储到数据属性
+ this.container.dataset.initialBgOpacity = opacity / 100;
+ this.container.dataset.initialBtnOpacity = buttonOpacity / 100;
+
+ // 预先保存透明度值到数据属性,但延迟应用
+ // 这样可以确保主题应用后再设置透明度
+ this.container.dataset.bgOpacity = opacity / 100;
+ this.container.dataset.btnOpacity = buttonOpacity / 100;
+
+ // 确保面板存在后才设置透明度
+ setTimeout(() => {
+ // 确保面板已经完全创建并添加到DOM中
+ this.setOpacity(opacity);
+ this.setButtonOpacity(buttonOpacity);
+ }, 50);
+
+ // 监听设置变化
if (window.app?.extensionManager?.setting?.onChange) {
window.app.extensionManager.setting.onChange("LayoutPanel.opacity", (val) => {
this.setOpacity(val);
});
+
+ window.app.extensionManager.setting.onChange("LayoutPanel.buttonOpacity", (val) => {
+ this.setButtonOpacity(val);
+ });
}
- // 创建按钮容器 - 居中对齐
- this.buttonsContainer = document.createElement('div');
- this.buttonsContainer.className = 'layout-buttons-container';
- this.buttonsContainer.style.display = 'flex';
- this.buttonsContainer.style.justifyContent = 'center'; // 确保按钮居中
- this.buttonsContainer.style.width = '100%';
- this.buttonsContainer.style.gap = '12px';
- this.buttonsContainer.style.marginTop = '4px';
+
+ // 创建硬币容器
+ this.coinElement = createCoinElement();
- // 创建随机颜色按钮
- const randomColorBtn = this._createButton('随机颜色', 'rainbow');
+ // 确保硬币元素添加到面板顶部
+ if (this.container.firstChild) {
+ this.container.insertBefore(this.coinElement, this.container.firstChild);
+ } else {
+ this.container.appendChild(this.coinElement);
+ }
+
+ // 创建按钮容器
+ this.buttonsContainer = createButtonsContainer();
+
+ // 所有按钮使用统一的创建和样式方法
+ const randomColorBtn = createButton('随机颜色', 'normal');
randomColorBtn.addEventListener('click', (e) => {
e.stopPropagation();
this._setRandomNodeColor();
});
// 创建自定义颜色按钮
- const customColorBtn = this._createButton('自定义颜色', 'pick');
+ const customColorBtn = createButton('自定义颜色', 'normal');
customColorBtn.addEventListener('click', (e) => {
e.stopPropagation();
this._openCustomColorPicker();
});
// 创建节点同步按钮
- const syncBtn = this._createButton('节点同步', 'sync', '#2db7f5');
+ const syncBtn = createButton('节点同步', 'normal');
+
// 节点同步模式选择器
- const syncModeContainer = document.createElement('div');
- syncModeContainer.style.display = 'flex';
- syncModeContainer.style.flexDirection = 'column';
- syncModeContainer.style.width = '100%';
- syncModeContainer.style.marginTop = '10px';
- syncModeContainer.style.gap = '8px';
- const syncModeLabel = document.createElement('div');
- syncModeLabel.textContent = '节点同步模式:';
- syncModeLabel.style.color = '#e1e1e1';
- syncModeLabel.style.fontSize = '14px';
- syncModeLabel.style.fontWeight = '500';
- const syncModeSelect = document.createElement('select');
- syncModeSelect.style.width = '100%';
- syncModeSelect.style.padding = '6px 10px';
- syncModeSelect.style.backgroundColor = 'rgba(45, 45, 55, 0.8)';
- syncModeSelect.style.color = '#e1e1e1';
- syncModeSelect.style.border = '1px solid rgba(255, 255, 255, 0.2)';
- syncModeSelect.style.borderRadius = '6px';
- syncModeSelect.style.outline = 'none';
- [
+ const { modeContainer: syncModeContainer, modeLabel: syncModeLabel } = createModeContainer('节点同步模式:');
+ const syncModeSelect = createSelectBox();
+ addOptionsToSelect(syncModeSelect, [
{ value: 'size', text: '同步大小' },
{ value: 'wh', text: '同步宽高' },
{ value: 'align', text: '同步对齐' }
- ].forEach(option => {
- const opt = document.createElement('option');
- opt.value = option.value;
- opt.textContent = option.text;
- syncModeSelect.appendChild(opt);
- });
- syncModeContainer.appendChild(syncModeLabel);
+ ]);
syncModeContainer.appendChild(syncModeSelect);
syncBtn.addEventListener('click', (e) => {
e.stopPropagation();
@@ -259,47 +241,24 @@ export class LayoutPanel { constructor() {
});
// 创建颜色模式选择器
- const modeContainer = document.createElement('div');
- modeContainer.style.display = 'flex';
- modeContainer.style.flexDirection = 'column';
- modeContainer.style.width = '100%';
- modeContainer.style.marginTop = '10px';
- modeContainer.style.gap = '8px';
-
- const modeLabel = document.createElement('div');
- modeLabel.textContent = '随机颜色模式:';
- modeLabel.style.color = '#e1e1e1';
- modeLabel.style.fontSize = '14px';
- modeLabel.style.fontWeight = '500';
-
- const modeSelect = document.createElement('select');
- modeSelect.style.width = '100%';
- modeSelect.style.padding = '6px 10px';
- modeSelect.style.backgroundColor = 'rgba(45, 45, 55, 0.8)';
- modeSelect.style.color = '#e1e1e1';
- modeSelect.style.border = '1px solid rgba(255, 255, 255, 0.2)';
- modeSelect.style.borderRadius = '6px';
- modeSelect.style.outline = 'none';
- // 添加选项
- const options = [
+ const { modeContainer, modeLabel } = createModeContainer('随机颜色模式:');
+ const modeSelect = createSelectBox();
+ addOptionsToSelect(modeSelect, [
{ value: '完全随机', text: '完全随机(每个节点不同颜色)' },
{ value: '统一随机', text: '统一随机(所有节点相同颜色)' }
- ];
+ ]);
- options.forEach(option => {
- const opt = document.createElement('option');
- opt.value = option.value;
- opt.textContent = option.text;
+ // 设置默认选中项
+ Array.from(modeSelect.options).forEach(option => {
if (option.value === this.colorMode) {
- opt.selected = true;
+ option.selected = true;
}
- modeSelect.appendChild(opt);
});
// 监听选择变化
modeSelect.addEventListener('change', (e) => {
this.colorMode = e.target.value;
- }); modeContainer.appendChild(modeLabel);
+ });
modeContainer.appendChild(modeSelect);
// 添加按钮到容器
@@ -312,6 +271,42 @@ export class LayoutPanel { constructor() {
this.container.appendChild(modeContainer);
this.container.appendChild(syncModeContainer);
+ // 添加面板调整大小时重新调整按钮文字大小的功能
+ const resizeObserver = new ResizeObserver(() => {
+ if (this.buttonsContainer) {
+ const buttons = this.buttonsContainer.querySelectorAll('.layout-btn');
+ buttons.forEach(button => {
+ // 检查是否有adjustTextFontSize函数
+ if (typeof adjustTextFontSize === 'function') {
+ adjustTextFontSize(button);
+ } else if (window.adjustTextFontSize) {
+ window.adjustTextFontSize(button);
+ }
+ });
+ }
+ });
+
+ // 观察容器大小变化
+ resizeObserver.observe(this.container);
+
+ // 调整按钮布局
+ adjustButtonsLayout(this.buttonsContainer);
+
+ // 强制触发一次字体调整,确保初始状态正确
+ setTimeout(() => {
+ const buttons = this.buttonsContainer.querySelectorAll('.layout-btn');
+ buttons.forEach(button => {
+ if (typeof adjustTextFontSize === 'function') {
+ adjustTextFontSize(button);
+ } else if (window.adjustTextFontSize) {
+ window.adjustTextFontSize(button);
+ } else {
+ // 如果没有找到调整函数,则确保基本字体大小
+ button.style.fontSize = '15px';
+ }
+ });
+ }, 200);
+
// 使用外部样式模块注入样式
injectStyles('layout-panel-styles', layoutPanelStyles);
@@ -320,152 +315,57 @@ export class LayoutPanel { constructor() {
e.stopPropagation();
});
- // 添加可拖动区域(左上角)
- const dragHandle = document.createElement('div');
- dragHandle.style.position = 'absolute';
- dragHandle.style.left = '0';
- dragHandle.style.top = '0';
- dragHandle.style.width = '32px';
- dragHandle.style.height = '32px';
- dragHandle.style.cursor = 'move';
- dragHandle.style.borderTopLeftRadius = '16px';
- dragHandle.style.background = 'linear-gradient(135deg, rgba(255,255,255,0.08) 60%, transparent 100%)';
- dragHandle.style.zIndex = '100001';
- dragHandle.title = '拖动面板';
- this.container.appendChild(dragHandle);
- this.container.style.userSelect = 'none';
- // 拖动逻辑
- let isDragging = false;
- let dragOffsetX = 0;
- let dragOffsetY = 0;
- dragHandle.addEventListener('mousedown', (e) => {
- e.stopPropagation();
- isDragging = true;
- // 记录鼠标与面板左上角的偏移
- const rect = this.container.getBoundingClientRect();
- dragOffsetX = e.clientX - rect.left;
- dragOffsetY = e.clientY - rect.top;
- document.body.style.cursor = 'move';
- });
- document.addEventListener('mousemove', (e) => {
- if (isDragging) {
- // 计算新位置
- const left = e.clientX - dragOffsetX;
- const top = e.clientY - dragOffsetY;
- this.container.style.left = `${left + 0.5}px`;
- this.container.style.top = `${top + 0.5}px`;
- this.container.style.transform = 'none';
- }
- });
- document.addEventListener('mouseup', () => {
- if (isDragging) {
- isDragging = false;
- document.body.style.cursor = '';
- }
- });
-
// 添加到文档
document.body.appendChild(this.container);
- }
- // 创建按钮辅助方法 - 更美观的按钮
- _createButton(text, type, bgColor) {
- const button = document.createElement('button');
- button.className = `layout-btn layout-btn-${type}`;
- button.textContent = text;
- button.title = text;
- // 添加更现代的按钮样式
- const isPrimary = type === 'pick';
- const isRainbow = type === 'rainbow';
-
- // 基础样式
- Object.assign(button.style, {
- backgroundColor: bgColor || (isRainbow ? 'rgba(138, 43, 226, 0.75)' : 'rgba(16, 130, 246, 0.75)'),
- border: 'none',
- borderRadius: '10px',
- padding: '10px 16px',
- color: '#ffffff',
- fontSize: '14px',
- fontWeight: '600',
- boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
- cursor: 'pointer',
- transition: 'all 0.2s ease',
- textShadow: '0 1px 2px rgba(0, 0, 0, 0.2)',
- minWidth: '120px',
- textAlign: 'center',
- position: 'relative',
- overflow: 'hidden',
- margin: '2px'
- });
-
- // 添加悬停效果
- button.addEventListener('mouseover', () => {
- const baseColor = bgColor || (isRainbow ? 'rgba(138, 43, 226, 0.75)' : 'rgba(16, 130, 246, 0.75)');
- // 提取RGB部分,增加不透明度
- const colorPattern = /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/;
- const match = baseColor.match(colorPattern);
- if (match) {
- const r = match[1];
- const g = match[2];
- const b = match[3];
- button.style.backgroundColor = `rgba(${r}, ${g}, ${b}, 0.9)`;
- } else {
- button.style.backgroundColor = baseColor;
+ // 应用当前主题或默认主题
+ setTimeout(() => {
+ let themeId = null;
+ try {
+ if (window.app?.extensionManager?.setting) {
+ themeId = window.app.extensionManager.setting.get("LayoutPanel.theme");
+ }
+ } catch(e) {
+ console.warn("获取主题设置失败:", e);
}
- button.style.boxShadow = '0 6px 16px rgba(0, 0, 0, 0.25)';
- button.style.transform = 'translateY(-2px)';
- });
-
- // 添加离开效果
- button.addEventListener('mouseout', () => {
- button.style.backgroundColor = bgColor || (isRainbow ? 'rgba(138, 43, 226, 0.75)' : 'rgba(16, 130, 246, 0.75)');
- button.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
- button.style.transform = 'translateY(0)';
- });
-
- // 添加点击效果
- button.addEventListener('mousedown', () => {
- button.style.transform = 'translateY(1px)';
- button.style.boxShadow = '0 2px 6px rgba(0, 0, 0, 0.15)';
- });
-
- button.addEventListener('mouseup', () => {
- button.style.transform = 'translateY(0)';
- button.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
- });
-
- return button;
+
+ if (!themeId) {
+ themeId = getDefaultTheme();
+ }
+
+ if (themeId) {
+ this.setTheme(themeId);
+
+ // 最后应用透明度,确保不被主题覆盖
+ setTimeout(() => {
+ this.container.dataset.initialSetup = 'completed';
+ this.setOpacity(opacity);
+ this.setButtonOpacity(buttonOpacity);
+ }, 150);
+ }
+ }, 100);
}
- // 此方法已被移除,使用_showAtMousePosition替代
- // 设置随机节点颜色
+
+ // 设置随机节点颜色
_setRandomNodeColor() {
try {
// 获取ComfyUI应用实例
- const app = this._getComfyUIApp();
+ const app = getComfyUIApp();
if (!app) {
- this._showNotification("无法获取ComfyUI应用实例");
+ showNotification("无法获取ComfyUI应用实例");
return;
}
// 获取选中的节点和组
- const selectedNodes = this._getSelectedNodes(app);
- const selectedGroups = this._getSelectedGroups(app);
+ const selectedNodes = getSelectedNodes(app);
+ const selectedGroups = getSelectedGroups(app);
if (selectedNodes.length === 0 && selectedGroups.length === 0) {
- this._showNotification("请先选择要应用颜色的节点或组");
+ showNotification("请先选择要应用颜色的节点或组");
return;
}
- // 生成随机颜色函数
- const getRandomColor = () => {
- const letters = '0123456789ABCDEF';
- let color = '#';
- for (let i = 0; i < 6; i++) {
- color += letters[Math.floor(Math.random() * 16)];
- }
- return color;
- };
- // 根据颜色模式应用颜色
+ // 根据颜色模式应用颜色
if (this.colorMode === '统一随机') {
// 统一随机模式 - 所有节点和组使用同一个随机颜色
const uniformColor = getRandomColor();
@@ -478,7 +378,7 @@ export class LayoutPanel { constructor() {
group.color = uniformColor;
});
- this._showNotification("已应用统一随机颜色", "info");
+ showNotification("已应用统一随机颜色", "info");
} else {
// 完全随机模式 - 每个节点和组使用不同的随机颜色
selectedNodes.forEach(node => {
@@ -490,13 +390,12 @@ export class LayoutPanel { constructor() {
group.color = getRandomColor();
});
- this._showNotification("已应用完全随机颜色", "info");
+ showNotification("已应用完全随机颜色", "info");
}
app.graph.setDirtyCanvas(true, true);
- // 不再自动关闭面板,面板只通过快捷键 toggle() 控制显示/隐藏
} catch (error) {
console.error("设置随机颜色失败:", error);
- this._showNotification(`设置随机颜色失败: ${error.message}`);
+ showNotification(`设置随机颜色失败: ${error.message}`);
}
}
@@ -504,22 +403,21 @@ export class LayoutPanel { constructor() {
_openCustomColorPicker() {
try {
// 获取ComfyUI应用实例
- const app = this._getComfyUIApp();
+ const app = getComfyUIApp();
if (!app) {
- this._showNotification("无法获取ComfyUI应用实例");
+ showNotification("无法获取ComfyUI应用实例");
return;
}
// 获取选中的节点和组
- const selectedNodes = this._getSelectedNodes(app);
- const selectedGroups = this._getSelectedGroups(app);
+ const selectedNodes = getSelectedNodes(app);
+ const selectedGroups = getSelectedGroups(app);
if (selectedNodes.length === 0 && selectedGroups.length === 0) {
- this._showNotification("请先选择要应用颜色的节点或组");
+ showNotification("请先选择要应用颜色的节点或组");
return;
}
- // 不再自动关闭面板,面板只通过快捷键 toggle() 控制显示/隐藏
// 创建颜色选择器
setTimeout(() => {
const defaultColor = selectedNodes.length > 0 ?
@@ -533,7 +431,7 @@ export class LayoutPanel { constructor() {
colorInput.style.visibility = 'hidden';
document.body.appendChild(colorInput);
- // 处理颜色变化 -
+ // 处理颜色变化
colorInput.addEventListener('input', (e) => {
const color = e.target.value;
@@ -547,234 +445,60 @@ export class LayoutPanel { constructor() {
});
app.graph.setDirtyCanvas(true, true);
- }); colorInput.addEventListener('change', (e) => { this._showNotification("已应用自定义颜色", "info");
+ });
+
+ colorInput.addEventListener('change', (e) => {
+ showNotification("已应用自定义颜色", "info");
document.body.removeChild(colorInput);
});
+
// 模拟点击打开颜色选择器
colorInput.click();
}, 100);
} catch (error) {
console.error("打开颜色选择器失败:", error);
- this._showNotification(`打开颜色选择器失败: ${error.message}`);
+ showNotification(`打开颜色选择器失败: ${error.message}`);
}
}
- // 获取ComfyUI应用实例
- _getComfyUIApp() {
- if (window.app?.canvas && window.app?.graph) {
- return window.app;
- }
-
- if (window.LiteGraph?.LGraphCanvas?.active_canvas) {
- const canvas = LiteGraph.LGraphCanvas.active_canvas;
- if (canvas?.graph) {
- return { canvas, graph: canvas.graph };
- }
- }
-
- const canvasElement = document.querySelector(".litegraph.litegraph-canvas");
- if (canvasElement?.lgraphcanvas) {
- const canvas = canvasElement.lgraphcanvas;
- if (canvas?.graph) {
- return { canvas, graph: canvas.graph };
- }
- }
-
- return null;
- }
-
- // 获取选中的节点
- _getSelectedNodes(app) {
- if (app.canvas.selected_nodes?.length) {
- return Array.from(app.canvas.selected_nodes);
- }
-
- const selectedNodes = [];
- if (app.graph?._nodes) {
- for (const node of app.graph._nodes) {
- if (node.is_selected) {
- selectedNodes.push(node);
- }
- }
- }
-
- return selectedNodes;
- }
-
- // 获取选中的组
- _getSelectedGroups(app) {
- const selectedGroups = [];
-
- if (app.canvas?.selected_groups?.length) {
- return Array.from(app.canvas.selected_groups);
- }
-
- if (app.graph?.groups) {
- for (const group of app.graph.groups) {
- if (group.selected) {
- selectedGroups.push(group);
- }
- }
- }
-
- return selectedGroups;
- }
-
- // 显示通知信息
- _showNotification(message, type = 'error') {
- const notification = document.createElement('div');
- notification.className = 'layout-notification';
- notification.textContent = message;
- // 通知基本样式
- const baseNotificationStyles = {
- position: 'fixed',
- top: '24px',
- left: '50%',
- transform: 'translateX(-50%) translateY(-20px)',
- padding: '12px 24px',
- borderRadius: '8px',
- fontSize: '14px',
- fontWeight: '500',
- zIndex: '10000',
- transition: 'all 0.3s ease',
- backdropFilter: 'blur(8px)',
- boxShadow: '0 8px 24px rgba(0, 0, 0, 0.2)',
- border: '1px solid rgba(255, 255, 255, 0.1)',
- width: 'auto',
- minWidth: '240px',
- maxWidth: '90%',
- textAlign: 'center',
- opacity: '0'
- };
-
- // 应用样式
- Object.assign(notification.style, baseNotificationStyles);
-
- if (type === 'info') {
- notification.style.backgroundColor = 'rgba(20, 120, 60, 0.85)';
- notification.style.color = 'white';
- } else if (type === 'warn') {
- notification.style.backgroundColor = 'rgba(230, 150, 10, 0.85)';
- notification.style.color = 'white';
- } else {
- notification.style.backgroundColor = 'rgba(200, 0, 0, 0.85)';
- notification.style.color = 'white';
- }
-
- document.body.appendChild(notification);
-
- // 显示动画 - 从上方移入
- setTimeout(() => {
- notification.style.opacity = '1';
- notification.style.transform = 'translateX(-50%) translateY(0)';
- }, 10);
-
- setTimeout(() => {
- notification.style.opacity = '0';
- notification.style.transform = 'translateX(-50%) translateY(-20px)'; // 向上方消失
- setTimeout(() => {
- if (notification.parentNode) {
- document.body.removeChild(notification);
- }
- }, 300);
- }, 3000);
- }
-
- // 辅助函数:判断节点排列主方向
- _getMainDirection(centers) {
- let dx = 0, dy = 0;
- for (let i = 1; i < centers.length; i++) {
- dx += Math.abs(centers[i].x - centers[0].x);
- dy += Math.abs(centers[i].y - centers[0].y);
- }
- return dx > dy ? 'horizontal' : 'vertical';
- }
-
// 节点同步逻辑
_syncSelectedNodes(mode) {
try {
- const app = this._getComfyUIApp();
+ const app = getComfyUIApp();
if (!app) {
- this._showNotification('无法获取ComfyUI应用实例');
+ showNotification('无法获取ComfyUI应用实例');
return;
}
- const selectedNodes = this._getSelectedNodes(app);
+
+ const selectedNodes = getSelectedNodes(app);
if (selectedNodes.length < 2) {
- this._showNotification('请至少选择两个节点进行同步');
+ showNotification('请至少选择两个节点进行同步');
return;
}
+
+ let result;
+
if (mode === 'size') {
// 大小同步:统一为最大宽高
- let maxW = 0, maxH = 0;
- selectedNodes.forEach(node => {
- if (node.size) {
- maxW = Math.max(maxW, node.size[0]);
- maxH = Math.max(maxH, node.size[1]);
- }
- });
- selectedNodes.forEach(node => {
- if (node.size) {
- node.size[0] = maxW;
- node.size[1] = maxH;
- }
- });
- this._showNotification('已同步为统一大小', 'info');
+ result = syncNodeSize(selectedNodes);
+ showNotification('已同步为统一大小', 'info');
} else if (mode === 'wh') {
// 宽高同步:判断排列方向
- const centers = selectedNodes.map(node => ({
- node,
- x: node.pos ? node.pos[0] + (node.size ? node.size[0] / 2 : 0) : 0,
- y: node.pos ? node.pos[1] + (node.size ? node.size[1] / 2 : 0) : 0
- }));
- const mainDir = this._getMainDirection(centers);
- if (mainDir === 'horizontal') {
- // 横向排列,同步高度
- let maxH = 0;
- selectedNodes.forEach(node => {
- if (node.size) maxH = Math.max(maxH, node.size[1]);
- });
- selectedNodes.forEach(node => {
- if (node.size) node.size[1] = maxH;
- });
- this._showNotification('已同步高度(横向排列)', 'info');
- } else {
- // 纵向排列,同步宽度
- let maxW = 0;
- selectedNodes.forEach(node => {
- if (node.size) maxW = Math.max(maxW, node.size[0]);
- });
- selectedNodes.forEach(node => {
- if (node.size) node.size[0] = maxW;
- });
- this._showNotification('已同步宽度(竖向排列)', 'info');
- }
+ result = syncNodeWidthHeight(selectedNodes);
+ const direction = result.direction === 'horizontal' ? '横向排列' : '纵向排列';
+ const dimension = result.direction === 'horizontal' ? '高度' : '宽度';
+ showNotification(`已同步${dimension}(${direction})`, 'info');
} else if (mode === 'align') {
// 同步对齐:自动排列节点
- const centers = selectedNodes.map(node => ({
- node,
- x: node.pos ? node.pos[0] + (node.size ? node.size[0] / 2 : 0) : 0,
- y: node.pos ? node.pos[1] + (node.size ? node.size[1] / 2 : 0) : 0
- }));
- const mainDir = this._getMainDirection(centers);
- if (mainDir === 'horizontal') {
- // 横向排列,对齐Y轴
- const avgY = centers.reduce((sum, c) => sum + c.y, 0) / centers.length;
- selectedNodes.forEach(node => {
- if (node.pos) node.pos[1] = avgY - (node.size ? node.size[1] / 2 : 0);
- });
- this._showNotification('已横向对齐(Y轴对齐)', 'info');
- } else {
- // 纵向排列,对齐X轴
- const avgX = centers.reduce((sum, c) => sum + c.x, 0) / centers.length;
- selectedNodes.forEach(node => {
- if (node.pos) node.pos[0] = avgX - (node.size ? node.size[0] / 2 : 0);
- });
- this._showNotification('已纵向对齐(X轴对齐)', 'info');
- }
+ result = alignNodes(selectedNodes);
+ const alignment = result.direction === 'horizontal' ? 'Y轴对齐' : 'X轴对齐';
+ const direction = result.direction === 'horizontal' ? '横向' : '纵向';
+ showNotification(`已${direction}对齐(${alignment})`, 'info');
}
+
app.graph.setDirtyCanvas(true, true);
} catch (e) {
- this._showNotification('节点同步失败: ' + e.message);
+ showNotification('节点同步失败: ' + e.message);
}
}
}
@@ -783,5 +507,6 @@ export const DEFAULT_CONFIG = {
enabled: true, // 默认启用
shortcut: 'alt+x',
colorMode: '完全随机', // 默认颜色模式
- continueIteration: false // 默认应用颜色后关闭面板
+ continueIteration: false, // 默认应用颜色后关闭面板
+ theme: '古神之眼' // 默认主题
};
\ No newline at end of file
diff --git a/js/Intelligent Layout/LayoutExt.js b/js/Intelligent Layout/LayoutExt.js
index 273ab08..1518a27 100644
--- a/js/Intelligent Layout/LayoutExt.js
+++ b/js/Intelligent Layout/LayoutExt.js
@@ -1,11 +1,24 @@
// 智能布局扩展
import { app } from "/scripts/app.js";
import { LayoutPanel, DEFAULT_CONFIG } from "./LayoutCore.js";
+import { registerTheme, getRegisteredThemes, setDefaultTheme, initDefaultThemes } from "./styles/UIStyles.js";
+
+// 初始化默认主题
+try {
+ // 调用主题初始化函数
+ if (initDefaultThemes()) {
+ console.log("主题系统已成功初始化");
+ } else {
+ console.warn("主题系统初始化返回失败");
+ }
+} catch (error) {
+ console.error("主题系统初始化失败:", error);
+}
// 确保全局唯一实例
function getOrCreateLayoutInstance() {
if (!app.canvas) {
- console.warn("智能布局:app.canvas未初始化");
+ console.warn("界面布局:app.canvas未初始化");
return null;
}
@@ -58,23 +71,64 @@ export const layoutExt = {
this.initLayoutPanel();
},
- initLayoutPanel() {
+ initLayoutPanel() {
// 初始化布局面板
const layoutPanel = getOrCreateLayoutInstance();
if (!layoutPanel) return;
- // 应用初始设置 - 功能始终启用
+ // 应用所有初始设置 - 确保所有参数都在初始化时应用
const applySettings = () => {
- // 默认始终启用,不再依赖设置
- layoutPanel.setEnabled(true);
- const shortcut = app.extensionManager.setting.get("LayoutPanel.shortcut", DEFAULT_CONFIG.shortcut);
- layoutPanel.setShortcut(shortcut);
+ try {
+ // 主要设置
+ const shortcut = app.extensionManager?.setting?.get("LayoutPanel.shortcut") ?? DEFAULT_CONFIG.shortcut;
+ const theme = app.extensionManager?.setting?.get("LayoutPanel.theme") ?? DEFAULT_CONFIG.theme;
+
+ // 透明度设置
+ const opacity = app.extensionManager?.setting?.get("LayoutPanel.opacity") ?? 85;
+ const buttonOpacity = app.extensionManager?.setting?.get("LayoutPanel.buttonOpacity") ?? 90;
+
+ // 启用面板(始终启用)
+ layoutPanel.setEnabled(true);
+
+ // 应用所有设置 - 顺序很重要
+ layoutPanel.setShortcut(shortcut);
+ layoutPanel.setTheme(theme);
+
+ // 确保透明度设置最后应用,这样不会被主题初始化覆盖
+ setTimeout(() => {
+ layoutPanel.setOpacity(opacity);
+ layoutPanel.setButtonOpacity(buttonOpacity);
+ }, 100);
+
+ console.log("布局面板初始设置已加载:", { shortcut, theme, opacity, buttonOpacity });
+ } catch (e) {
+ console.warn("应用设置时出错:", e);
+ // 应用默认设置作为回退
+ layoutPanel.setShortcut(DEFAULT_CONFIG.shortcut);
+ layoutPanel.setTheme(DEFAULT_CONFIG.theme);
+ layoutPanel.setOpacity(85);
+ layoutPanel.setButtonOpacity(90);
+ }
};
applySettings();
}
};
+// 获取所有注册主题用于设置选择
+function getThemeOptions() {
+ try {
+ const themes = getRegisteredThemes();
+ return themes.map(theme => ({
+ text: theme.name,
+ value: theme.id
+ }));
+ } catch (e) {
+ console.error("获取主题选项失败:", e);
+ return [{ text: '古神之眼', value: '古神之眼' }];
+ }
+}
+
// 注册设置面板
app.registerExtension({
name: "ComfyUI.LayoutPanel.Settings",
@@ -88,17 +142,45 @@ app.registerExtension({
// 初始化布局面板
const layoutPanel = getOrCreateLayoutInstance();
if (!layoutPanel) return;
- // 始终启用布局功能,不再依赖设置 layoutPanel.setEnabled(true);
- layoutPanel.setShortcut(app.extensionManager.setting.get("LayoutPanel.shortcut") ?? DEFAULT_CONFIG.shortcut);
- }, settings: [
+
+ // 确保显式加载和应用所有设置,与init方法保持一致
+ try {
+ // 先启用面板
+ layoutPanel.setEnabled(true);
+
+ // 然后应用所有保存的设置
+ const shortcut = app.extensionManager?.setting?.get("LayoutPanel.shortcut") ?? DEFAULT_CONFIG.shortcut;
+ const theme = app.extensionManager?.setting?.get("LayoutPanel.theme") ?? DEFAULT_CONFIG.theme;
+ const opacity = app.extensionManager?.setting?.get("LayoutPanel.opacity") ?? 85;
+ const buttonOpacity = app.extensionManager?.setting?.get("LayoutPanel.buttonOpacity") ?? 90;
+
+ // 应用设置 - 注意顺序
+ layoutPanel.setShortcut(shortcut);
+ layoutPanel.setTheme(theme);
+
+ // 确保透明度在主题后设置
+ setTimeout(() => {
+ layoutPanel.setOpacity(opacity);
+ layoutPanel.setButtonOpacity(buttonOpacity);
+ }, 100);
+ } catch (e) {
+ console.warn("应用设置时出错:", e);
+ // 应用默认设置
+ layoutPanel.setShortcut(DEFAULT_CONFIG.shortcut);
+ layoutPanel.setTheme(DEFAULT_CONFIG.theme);
+ layoutPanel.setOpacity(85);
+ layoutPanel.setButtonOpacity(90);
+ }
+ },
+ settings: [
// 移除启用/禁用设置选项
{
id: "LayoutPanel.shortcut",
- name: "快捷键",
+ name: "快捷呼出",
type: "text",
defaultValue: DEFAULT_CONFIG.shortcut,
- tooltip: "弹出节点颜色工具面板的快捷键(如alt+x)",
- category: ["🍺界面布局", "1·功能", "快捷键"],
+ tooltip: "弹出界面布局工具面板的快捷键(如alt+x)",
+ category: ["🍺界面布局", "1·功能", "快捷呼出"],
onChange(value) {
if (typeof value === 'string' && value.includes('+')) {
const layoutPanel = getOrCreateLayoutInstance();
@@ -110,31 +192,69 @@ app.registerExtension({
},
{
id: "LayoutPanel.opacity",
- name: "界面透明度",
+ name: "背景透明",
type: "slider",
defaultValue: 85,
attrs: { min: 0, max: 100, step: 1 },
- tooltip: "设置智能布局主面板的透明度(0~100%)",
- category: ["🍺界面布局", "2·外观", "界面透明度"],
+ tooltip: "设置界面布局主面板背景的透明度(0~100%)",
+ category: ["🍺界面布局", "2·外观", "背景透明"],
onChange(value) {
const layoutPanel = getOrCreateLayoutInstance();
if (layoutPanel && typeof layoutPanel.setOpacity === 'function') {
layoutPanel.setOpacity(value);
}
}
+ },
+ {
+ id: "LayoutPanel.buttonOpacity",
+ name: "按钮透明",
+ type: "slider",
+ defaultValue: 90,
+ attrs: { min: 0, max: 100, step: 1 },
+ tooltip: "设置界面布局面板中按钮的透明度(0~100%)",
+ category: ["🍺界面布局", "2·外观", "按钮透明"],
+ onChange(value) {
+ const layoutPanel = getOrCreateLayoutInstance();
+ if (layoutPanel && typeof layoutPanel.setButtonOpacity === 'function') {
+ layoutPanel.setButtonOpacity(value);
+ }
+ }
+ },
+ {
+ id: "LayoutPanel.theme",
+ name: "界面主题",
+ type: "combo",
+ defaultValue: DEFAULT_CONFIG.theme,
+ tooltip: "选择界面布局面板的视觉主题",
+ options: getThemeOptions,
+ category: ["🍺界面布局", "2·外观", "界面主题"],
+ onChange(value) {
+ const layoutPanel = getOrCreateLayoutInstance();
+ if (layoutPanel && typeof layoutPanel.setTheme === 'function') {
+ layoutPanel.setTheme(value);
+ }
+ }
}
]
});
+// 主扩展注册
app.registerExtension({
name: "ComfyUI.LayoutPanel",
init() {
- setTimeout(() => {
- if (app.canvas) {
- layoutExt.init();
- } else {
- console.error("智能布局:延迟初始化失败,app.canvas仍未准备好");
- }
- }, 500);
+ try {
+ console.log("正在初始化智能布局面板...");
+ // 延迟初始化,确保其他扩展已经加载
+ setTimeout(() => {
+ if (app.canvas) {
+ layoutExt.init();
+ console.log("智能布局面板初始化完成");
+ } else {
+ console.error("智能布局:延迟初始化失败,app.canvas仍未准备好");
+ }
+ }, 1000);
+ } catch (e) {
+ console.error("初始化智能布局面板时出错:", e);
+ }
}
});
\ No newline at end of file
diff --git a/js/Intelligent Layout/styles/UIStyles.js b/js/Intelligent Layout/styles/UIStyles.js
new file mode 100644
index 0000000..616c891
--- /dev/null
+++ b/js/Intelligent Layout/styles/UIStyles.js
@@ -0,0 +1,845 @@
+// 智能布局 UI 样式统一模块
+
+// ==========================================================
+// 主题管理相关功能
+// ==========================================================
+
+// 导入主题模块
+import AncientGodTheme from "./古神之眼/AncientGodTheme.js";
+
+// 存储所有已注册的主题
+const registeredThemes = {};
+let currentTheme = null;
+let defaultTheme = '古神之眼';
+
+// 自动注册默认主题
+export function initDefaultThemes() { try {
+ // 注册主题
+ registerTheme('古神之眼', AncientGodTheme);
+
+ setDefaultTheme('古神之眼');
+ console.log("默认主题已成功注册");
+ return true;
+ } catch (error) {
+ console.error("注册默认主题失败:", error);
+ return false;
+ }
+}
+
+// 注册主题 - 主题必须实现特定接口
+export function registerTheme(id, themeModule) {
+ if (!id || typeof id !== 'string') {
+ console.error('主题ID必须是有效字符串');
+ return false;
+ }
+
+ // 验证主题模块是否符合接口规范
+ if (!themeModule || typeof themeModule !== 'object') {
+ console.error(`主题 "${id}" 必须是有效对象`);
+ return false;
+ }
+
+ // 验证必要的方法
+ const requiredMethods = ['init', 'applyTheme', 'flipCoin', 'cleanup'];
+ for (const method of requiredMethods) {
+ if (typeof themeModule[method] !== 'function') {
+ console.error(`主题 "${id}" 缺少必要方法: ${method}`);
+ return false;
+ }
+ }
+
+ // 验证必要的属性
+ if (!themeModule.name) {
+ console.error(`主题 "${id}" 缺少名称属性`);
+ return false;
+ }
+
+ // 注册主题
+ registeredThemes[id] = themeModule;
+ console.log(`主题 "${id}" (${themeModule.name}) 已成功注册`);
+ return true;
+}
+
+// 获取已注册的所有主题
+export function getRegisteredThemes() {
+ const themes = [];
+ for (const id in registeredThemes) {
+ themes.push({
+ id: id,
+ name: registeredThemes[id].name || id
+ });
+ }
+ return themes;
+}
+
+// 获取主题模块
+export function getTheme(id) {
+ return registeredThemes[id] || null;
+}
+
+// 应用主题
+export function applyTheme(id, container, coinElement) {
+ // 清理当前主题(如果有)
+ if (currentTheme && registeredThemes[currentTheme]) {
+ registeredThemes[currentTheme].cleanup();
+ }
+
+ // 获取并应用新主题
+ const themeModule = registeredThemes[id];
+ if (!themeModule) {
+ console.error(`主题 "${id}" 未注册`);
+ return false;
+ }
+
+ // 初始化并应用新主题
+ // 确保硬币元素存在且为初始状态
+ if (coinElement) {
+ // 移除可能的 flipped 类,确保硬币始终从正面开始
+ if (coinElement.classList.contains('flipped')) {
+ coinElement.classList.remove('flipped');
+ }
+ }
+
+ // 强制重新创建背景和硬币
+ themeModule.init(container, coinElement);
+ themeModule.applyTheme();
+ currentTheme = id;
+
+ return true;
+}
+
+// 设置默认主题
+export function setDefaultTheme(id) {
+ if (registeredThemes[id]) {
+ defaultTheme = id;
+ return true;
+ }
+ return false;
+}
+
+// 获取默认主题
+export function getDefaultTheme() {
+ return defaultTheme;
+}
+
+// ==========================================================
+// 通用工具函数
+// ==========================================================
+
+// 工具函数:向页面注入样式
+export function injectStyles(styleId, styleContent) {
+ // 如果已经存在相同ID的样式,则不重复注入
+ if (document.getElementById(styleId)) return;
+
+ const styleElement = document.createElement('style');
+ styleElement.id = styleId;
+ styleElement.textContent = styleContent;
+ document.head.appendChild(styleElement);
+}
+
+// 工具函数:移除已注入的样式
+export function removeStyles(styleId) {
+ const styleElement = document.getElementById(styleId);
+ if (styleElement && styleElement.parentNode) {
+ styleElement.parentNode.removeChild(styleElement);
+ }
+}
+
+// ==========================================================
+// CSS 样式定义 - 简化为最基本的结构,其余移至主题实现
+// ==========================================================
+
+// 布局面板相关样式 - 只保留必要的基础结构
+export const layoutPanelStyles = `
+ .layout-panel {
+ position: fixed;
+ z-index: 99999;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 12px;
+ user-select: none;
+ transition: opacity 0.3s;
+ border-radius: 16px;
+ }
+`;
+
+// ==========================================================
+// 面板相关函数
+// ==========================================================
+
+// 创建面板UI元素
+export function createPanelElement(mousePosition) {
+ // 创建容器
+ const container = document.createElement('div');
+ container.className = 'layout-panel';
+ // 初始位置,后续会根据面板实际大小进行调整
+ container.style.left = `${mousePosition.x}px`;
+ container.style.top = `${mousePosition.y}px`;
+ container.style.transform = 'none';
+ container.style.display = 'none';
+ container.style.pointerEvents = 'none';
+ container.style.opacity = '0';
+ container.style.padding = '30px'; // 增加内边距适应大尺寸硬币
+ container.style.width = '360px'; // 设置基础宽度
+ container.style.boxSizing = 'border-box'; // 确保padding不增加总宽度
+
+ // 添加拖动区域 - 改为在面板边缘创建多个拖动区域
+ addDragHandles(container);
+
+ // 注入基础样式
+ injectStyles('layout-panel-styles', layoutPanelStyles);
+
+ return container;
+}
+
+// 添加多个拖动区域到面板边缘
+function addDragHandles(container) {
+ // 创建拖动状态对象 - 所有拖动句柄共享
+ const dragState = {
+ isDragging: false,
+ offsetX: 0,
+ offsetY: 0
+ };
+
+ // 拖动处理函数 - 所有拖动区域共享
+ const handleMouseMove = function(e) {
+ if (!dragState.isDragging) return;
+
+ // 计算新位置
+ const left = e.clientX - dragState.offsetX;
+ const top = e.clientY - dragState.offsetY;
+
+ // 使用 requestAnimationFrame 减少重绘次数
+ requestAnimationFrame(() => {
+ container.style.left = `${left}px`;
+ container.style.top = `${top}px`;
+ // 一旦开始拖动,就固定不使用 transform,避免布局重新计算
+ if (container.style.transform !== 'none') {
+ container.style.transform = 'none';
+ }
+ });
+ };
+
+ const handleMouseUp = function() {
+ if (!dragState.isDragging) return;
+
+ dragState.isDragging = false;
+ document.body.style.cursor = '';
+
+ // 重要:在拖动结束后移除临时添加的事件监听器
+ document.removeEventListener('mousemove', handleMouseMove);
+ document.removeEventListener('mouseup', handleMouseUp);
+ };
+
+ // 创建拖动开始函数
+ const createDragStart = (handle) => {
+ return (e) => {
+ e.stopPropagation();
+ e.preventDefault();
+
+ // 记录鼠标与面板左上角的偏移
+ const rect = container.getBoundingClientRect();
+ dragState.offsetX = e.clientX - rect.left;
+ dragState.offsetY = e.clientY - rect.top;
+
+ dragState.isDragging = true;
+ document.body.style.cursor = 'move';
+
+ // 只在开始拖动时添加事件监听器
+ document.addEventListener('mousemove', handleMouseMove);
+ document.addEventListener('mouseup', handleMouseUp);
+ };
+ };
+
+ // 定义边缘宽度
+ const edgeWidth = 15; // 拖动区域宽度,px
+
+ // 创建顶部拖动区域
+ const topHandle = document.createElement('div');
+ topHandle.className = 'layout-drag-handle layout-drag-handle-top';
+ Object.assign(topHandle.style, {
+ position: 'absolute',
+ top: '0',
+ left: edgeWidth + 'px',
+ right: edgeWidth + 'px',
+ height: edgeWidth + 'px',
+ cursor: 'move',
+ zIndex: '10001'
+ });
+ topHandle.addEventListener('mousedown', createDragStart(topHandle));
+ container.appendChild(topHandle);
+
+ // 创建底部拖动区域
+ const bottomHandle = document.createElement('div');
+ bottomHandle.className = 'layout-drag-handle layout-drag-handle-bottom';
+ Object.assign(bottomHandle.style, {
+ position: 'absolute',
+ bottom: '0',
+ left: edgeWidth + 'px',
+ right: edgeWidth + 'px',
+ height: edgeWidth + 'px',
+ cursor: 'move',
+ zIndex: '10001'
+ });
+ bottomHandle.addEventListener('mousedown', createDragStart(bottomHandle));
+ container.appendChild(bottomHandle);
+
+ // 创建左侧拖动区域
+ const leftHandle = document.createElement('div');
+ leftHandle.className = 'layout-drag-handle layout-drag-handle-left';
+ Object.assign(leftHandle.style, {
+ position: 'absolute',
+ top: edgeWidth + 'px',
+ bottom: edgeWidth + 'px',
+ left: '0',
+ width: edgeWidth + 'px',
+ cursor: 'move',
+ zIndex: '10001'
+ });
+ leftHandle.addEventListener('mousedown', createDragStart(leftHandle));
+ container.appendChild(leftHandle);
+
+ // 创建右侧拖动区域
+ const rightHandle = document.createElement('div');
+ rightHandle.className = 'layout-drag-handle layout-drag-handle-right';
+ Object.assign(rightHandle.style, {
+ position: 'absolute',
+ top: edgeWidth + 'px',
+ bottom: edgeWidth + 'px',
+ right: '0',
+ width: edgeWidth + 'px',
+ cursor: 'move',
+ zIndex: '10001'
+ });
+ rightHandle.addEventListener('mousedown', createDragStart(rightHandle));
+ container.appendChild(rightHandle);
+
+ // 创建四个角落的拖动区域
+ const topLeftHandle = document.createElement('div');
+ topLeftHandle.className = 'layout-drag-handle layout-drag-handle-top-left';
+ Object.assign(topLeftHandle.style, {
+ position: 'absolute',
+ top: '0',
+ left: '0',
+ width: edgeWidth + 'px',
+ height: edgeWidth + 'px',
+ cursor: 'move',
+ zIndex: '10001',
+ borderTopLeftRadius: '16px'
+ });
+ topLeftHandle.addEventListener('mousedown', createDragStart(topLeftHandle));
+ container.appendChild(topLeftHandle);
+
+ const topRightHandle = document.createElement('div');
+ topRightHandle.className = 'layout-drag-handle layout-drag-handle-top-right';
+ Object.assign(topRightHandle.style, {
+ position: 'absolute',
+ top: '0',
+ right: '0',
+ width: edgeWidth + 'px',
+ height: edgeWidth + 'px',
+ cursor: 'move',
+ zIndex: '10001',
+ borderTopRightRadius: '16px'
+ });
+ topRightHandle.addEventListener('mousedown', createDragStart(topRightHandle));
+ container.appendChild(topRightHandle);
+
+ const bottomLeftHandle = document.createElement('div');
+ bottomLeftHandle.className = 'layout-drag-handle layout-drag-handle-bottom-left';
+ Object.assign(bottomLeftHandle.style, {
+ position: 'absolute',
+ bottom: '0',
+ left: '0',
+ width: edgeWidth + 'px',
+ height: edgeWidth + 'px',
+ cursor: 'move',
+ zIndex: '10001',
+ borderBottomLeftRadius: '16px'
+ });
+ bottomLeftHandle.addEventListener('mousedown', createDragStart(bottomLeftHandle));
+ container.appendChild(bottomLeftHandle);
+
+ const bottomRightHandle = document.createElement('div');
+ bottomRightHandle.className = 'layout-drag-handle layout-drag-handle-bottom-right';
+ Object.assign(bottomRightHandle.style, {
+ position: 'absolute',
+ bottom: '0',
+ right: '0',
+ width: edgeWidth + 'px',
+ height: edgeWidth + 'px',
+ cursor: 'move',
+ zIndex: '10001',
+ borderBottomRightRadius: '16px'
+ });
+ bottomRightHandle.addEventListener('mousedown', createDragStart(bottomRightHandle));
+ container.appendChild(bottomRightHandle);
+
+ // 为调试可视化边框 - 通过注释控制是否添加
+ // injectStyles('drag-handles-debug', `
+ // .layout-drag-handle {
+ // border: 1px dashed rgba(255, 255, 255, 0.15);
+ // background-color: rgba(128, 128, 255, 0.05);
+ // }
+ // `);
+}
+
+// ==========================================================
+// UI 控件相关函数
+// ==========================================================
+
+// 创建按钮容器
+export function createButtonsContainer() {
+ const buttonsContainer = document.createElement('div');
+ buttonsContainer.className = 'layout-buttons-container';
+ buttonsContainer.style.display = 'flex';
+ buttonsContainer.style.flexWrap = 'wrap'; // 允许按钮在需要时换行
+ buttonsContainer.style.justifyContent = 'center'; // 居中排列
+ buttonsContainer.style.width = '100%';
+ buttonsContainer.style.gap = '8px'; // 按钮之间的间距
+
+ return buttonsContainer;
+}
+
+// 创建按钮
+export function createButton(text, type, bgColor) {
+ const button = document.createElement('button');
+ button.className = `layout-btn layout-btn-${type}`;
+ button.textContent = text;
+ button.title = text;
+
+ // 自适应布局的按钮样式
+ button.style.padding = '8px 12px';
+ button.style.flexGrow = '1'; // 按钮可以增长
+ button.style.flexBasis = '0'; // 所有按钮基础宽度相同
+ button.style.minWidth = '80px'; // 最小宽度
+ button.style.maxWidth = 'none'; // 移除最大宽度限制,让按钮自动适应
+ button.style.textAlign = 'center'; // 确保文本居中
+ button.style.justifyContent = 'center'; // Flex布局居中
+ button.style.alignItems = 'center'; // Flex布局居中
+ button.style.display = 'flex'; // 改为flex布局以确保内容居中
+ button.style.cursor = 'pointer';
+ button.style.borderRadius = '10px';
+ button.style.transition = 'all 0.2s ease';
+ button.style.margin = '1px';
+ button.style.whiteSpace = 'nowrap'; // 防止文字换行
+ button.style.overflow = 'hidden';
+
+ // 使用自定义属性存储原始文本和字体大小,便于调整显示
+ button.dataset.originalText = text;
+ button.dataset.originalFontSize = '15px'; // 调大默认字体大小为15px(原为14px)
+ button.style.fontSize = button.dataset.originalFontSize;
+
+ // 添加点击效果
+ button.addEventListener('mousedown', () => {
+ button.style.transform = 'translateY(1px)';
+ });
+
+ button.addEventListener('mouseup', () => {
+ button.style.transform = 'translateY(0)';
+ });
+
+ // 添加自适应字体大小功能
+ button.addEventListener('DOMNodeInserted', () => {
+ setTimeout(() => adjustTextFontSize(button), 0);
+ });
+
+ return button;
+}
+
+// 新增函数:调整文字字体大小以适应按钮宽度
+function adjustTextFontSize(button) {
+ if (!button || !button.dataset.originalText) return;
+
+ // 重置为原始字体大小以便测量
+ button.style.fontSize = button.dataset.originalFontSize || '15px';
+
+ // 计算按钮可用宽度(考虑内边距)
+ const buttonWidth = button.clientWidth;
+ const paddingLeft = parseInt(getComputedStyle(button).paddingLeft) || 0;
+ const paddingRight = parseInt(getComputedStyle(button).paddingRight) || 0;
+ const availableWidth = buttonWidth - paddingLeft - paddingRight - 4; // 额外留4px边距
+
+ // 创建临时元素测量文本宽度
+ const tempSpan = document.createElement('span');
+ tempSpan.style.visibility = 'hidden';
+ tempSpan.style.position = 'absolute';
+ tempSpan.style.whiteSpace = 'nowrap';
+ tempSpan.style.fontSize = button.dataset.originalFontSize || '15px';
+ tempSpan.textContent = button.dataset.originalText;
+ document.body.appendChild(tempSpan);
+
+ const textWidth = tempSpan.offsetWidth;
+ document.body.removeChild(tempSpan);
+
+ // 如果文本宽度大于可用宽度,则缩小字体
+ if (textWidth > availableWidth) {
+ const scaleFactor = availableWidth / textWidth;
+ const originalFontSize = parseInt(button.dataset.originalFontSize) || 15;
+ // 限制字体最小缩小到11px(原为9px),确保字体不会太小
+ const newFontSize = Math.max(11, Math.floor(originalFontSize * scaleFactor));
+
+ button.style.fontSize = `${newFontSize}px`;
+ } else {
+ // 恢复原始字体大小
+ button.style.fontSize = button.dataset.originalFontSize || '15px';
+ }
+
+ // 确保显示完整文本
+ button.textContent = button.dataset.originalText;
+}
+
+// 调整按钮布局 - 添加新函数用于根据按钮数量自动调整布局
+export function adjustButtonsLayout(buttonsContainer) {
+ if (!buttonsContainer) return;
+
+ const buttons = buttonsContainer.querySelectorAll('.layout-btn');
+ const count = buttons.length;
+ if (count === 0) return;
+
+ // 根据按钮数量决定每行显示多少个按钮
+ let buttonsPerRow;
+ if (count <= 3) {
+ // 1-3个按钮时,放在一行
+ buttonsPerRow = count;
+ } else if (count === 4) {
+ // 4个按钮时,每行2个
+ buttonsPerRow = 2;
+ } else {
+ // 5个及以上按钮时,每行3个
+ buttonsPerRow = 3;
+ }
+
+ // 计算每行宽度的百分比
+ const widthPercent = Math.floor(100 / buttonsPerRow);
+
+ // 应用样式到每个按钮
+ buttons.forEach((button, index) => {
+ // 计算当前按钮是当前行的第几个按钮
+ const positionInRow = index % buttonsPerRow;
+
+ // 设置按钮宽度
+ button.style.flexBasis = `calc(${widthPercent}% - 8px)`;
+
+ // 每行最后一个按钮需处理外边距
+ if (positionInRow === buttonsPerRow - 1) {
+ button.style.marginRight = '0';
+ }
+
+ // 调整文字大小以适应按钮
+ adjustTextFontSize(button);
+ });
+}
+
+// 创建模式选择器容器
+export function createModeContainer(labelText) {
+ const modeContainer = document.createElement('div');
+ modeContainer.className = 'layout-mode-container';
+ modeContainer.style.display = 'flex';
+ modeContainer.style.flexDirection = 'column';
+ modeContainer.style.width = '100%';
+ modeContainer.style.marginTop = '10px';
+ modeContainer.style.gap = '8px';
+
+ const modeLabel = document.createElement('div');
+ modeLabel.className = 'layout-mode-label';
+ modeLabel.textContent = labelText;
+ modeLabel.style.fontSize = '14px';
+
+ modeContainer.appendChild(modeLabel);
+
+ return { modeContainer, modeLabel };
+}
+
+// 创建下拉选择框
+export function createSelectBox() {
+ const selectBox = document.createElement('select');
+ selectBox.className = 'layout-select';
+ selectBox.style.width = '100%';
+ selectBox.style.padding = '6px 10px';
+ selectBox.style.borderRadius = '6px';
+ selectBox.style.outline = 'none';
+
+ return selectBox;
+}
+
+// ==========================================================
+// 添加选项到选择框
+// ==========================================================
+
+export function addOptionsToSelect(selectBox, options) {
+ options.forEach(option => {
+ const optionElement = document.createElement('option');
+ optionElement.value = option.value;
+ optionElement.textContent = option.text;
+ selectBox.appendChild(optionElement);
+ });
+
+ return selectBox;
+}
+
+// ==========================================================
+// 透明度管理系统 - 集中管理所有透明度相关功能
+// ==========================================================
+
+// 默认透明度值
+const DEFAULT_OPACITY = {
+ panel: 0.85, // 面板背景默认透明度 (85%)
+ buttons: 0.90 // 按钮默认透明度 (90%)
+};
+
+// 当前透明度设置
+let currentOpacity = {
+ panel: DEFAULT_OPACITY.panel,
+ buttons: DEFAULT_OPACITY.buttons
+};
+
+// 设置面板背景透明度
+export function setPanelBackgroundOpacity(container, opacity) {
+ if (!container) return;
+
+ // 将百分比值转换为0-1范围的小数
+ const opacityValue = Math.max(10, Math.min(100, Number(opacity))) / 100;
+ currentOpacity.panel = opacityValue;
+
+ // 存储透明度值到数据属性,方便主题系统读取
+ container.dataset.bgOpacity = String(opacityValue);
+
+ // 设置背景容器的透明度
+ const bgContainer = container.querySelector('.theme-svg-background');
+ if (bgContainer) {
+ bgContainer.style.opacity = String(opacityValue);
+ }
+
+ // 调整面板整体透明度 - 背景透明但内容清晰
+ _updatePanelStyles(container);
+
+ // 通知当前主题更新透明度设置
+ _notifyThemeOpacityChange(container, opacityValue);
+
+ return opacityValue;
+}
+
+// 设置按钮透明度
+export function setButtonsOpacity(container, opacity) {
+ if (!container) return;
+
+ // 将百分比值转换为0-1范围的小数
+ const opacityValue = Math.max(10, Math.min(100, Number(opacity))) / 100;
+ currentOpacity.buttons = opacityValue;
+
+ // 存储透明度值到数据属性
+ container.dataset.btnOpacity = String(opacityValue);
+
+ // 获取当前按钮背景颜色基础值(不含透明度)
+ const getCurrentButtonBaseColor = (button) => {
+ try {
+ const currentBg = window.getComputedStyle(button).backgroundColor;
+ // 从rgba中提取rgb部分
+ if (currentBg.includes('rgba')) {
+ return currentBg.replace(/rgba\((.+?), [\d\.]+\)/, 'rgba($1, ' + opacityValue + ')');
+ }
+ // 如果是rgb,转换为rgba
+ else if (currentBg.includes('rgb')) {
+ return currentBg.replace(/rgb\((.+?)\)/, 'rgba($1, ' + opacityValue + ')');
+ }
+ return currentBg;
+ } catch (e) {
+ console.warn('获取按钮颜色失败:', e);
+ // 根据当前主题提供默认颜色
+ if (currentTheme === '古神之眼') {
+ return 'rgba(106, 90, 205, ' + opacityValue + ')';
+ } else {
+ return 'rgba(0, 180, 220, ' + opacityValue + ')';
+ }
+ }
+ };
+
+ // 获取所有按钮并设置透明度
+ const buttons = container.querySelectorAll('.layout-btn');
+ if (buttons.length > 0) {
+ buttons.forEach(button => {
+ button.style.backgroundColor = getCurrentButtonBaseColor(button);
+ });
+ }
+
+ return opacityValue;
+}
+
+// 保留setPanelOpacity函数,但将其实现为只调用背景透明度函数
+// 这样可以保持与原来代码的兼容性
+export function setPanelOpacity(container, opacity) {
+ // 调用背景透明度设置函数,不再同时设置按钮透明度
+ return setPanelBackgroundOpacity(container, opacity);
+}
+
+// 获取当前面板透明度设置
+export function getPanelOpacity() {
+ return {
+ panel: currentOpacity.panel * 100, // 转换为百分比形式
+ buttons: currentOpacity.buttons * 100
+ };
+}
+
+// 更新面板整体样式
+function _updatePanelStyles(container) {
+ if (!container) return;
+
+ // 获取背景透明度
+ const bgOpacity = parseFloat(container.dataset.bgOpacity || currentOpacity.panel);
+
+ // 设置适当的背景模糊效果 - 透明度越高模糊越少
+ const blurAmount = Math.round(14 - bgOpacity * 8); // 根据透明度计算模糊值
+ container.style.backdropFilter = `blur(${blurAmount}px)`;
+ container.style.webkitBackdropFilter = `blur(${blurAmount}px)`;
+
+ // 设置适当的边框透明度 - 透明度低时边框更明显
+ const borderOpacity = Math.min(0.6, 0.8 - bgOpacity * 0.3);
+ const borderRGBA = container.classList.contains('theme-dark')
+ ? `rgba(0, 0, 0, ${borderOpacity})`
+ : `rgba(255, 255, 255, ${borderOpacity})`;
+
+ // 只设置边框颜色,不修改边框宽度和样式
+ if (container.style.border) {
+ container.style.border = container.style.border.replace(/rgba?\(.+?\)/, borderRGBA);
+ }
+}
+
+// 通知当前主题透明度变化
+function _notifyThemeOpacityChange(container, opacity) {
+ if (!container) return;
+
+ try {
+ // 获取当前主题ID
+ const themeId = currentTheme;
+ if (themeId) {
+ const theme = getTheme(themeId);
+ if (theme && typeof theme.setPanelOpacity === 'function') {
+ theme.setPanelOpacity(opacity);
+ }
+ }
+ } catch (e) {
+ console.warn('无法通过主题系统设置透明度:', e);
+ }
+}
+
+// ==========================================================
+// 硬币元素创建相关
+// ==========================================================
+
+// 创建硬币元素
+export function createCoinElement() {
+ const coinElement = document.createElement('div');
+ coinElement.className = 'layout-coin-container';
+ coinElement.style.width = '180px';
+ coinElement.style.height = '180px';
+ coinElement.style.margin = '5px auto 25px auto';
+ coinElement.style.position = 'relative';
+ coinElement.style.perspective = '1600px'; // 增强3D效果
+
+ return coinElement;
+}
+
+// 翻转硬币动画
+export function flipCoin(coinElement) {
+ if (!coinElement) return;
+
+ // 简单切换flipped类来触发动画
+ coinElement.classList.toggle('flipped');
+
+ // 如果当前主题有自定义的翻转功能,则调用
+ const currentTheme = getTheme(currentTheme);
+ if (currentTheme && typeof currentTheme.flipCoin === 'function') {
+ currentTheme.flipCoin(coinElement);
+ }
+}
+
+// ==========================================================
+// 面板位置计算函数
+// ==========================================================
+
+// 在鼠标位置显示面板
+export function showAtMousePosition(container, mousePosition) {
+ if (!container) return;
+
+ // 修改导入路径 - 从相对路径调整为同目录导入
+ try {
+ const { calculatePanelPosition } = require('./UIUtils.js');
+ const position = calculatePanelPosition(mousePosition, container);
+
+ container.style.left = `${position.left}px`;
+ container.style.top = `${position.top}px`;
+ container.style.transform = 'none'; // 不再使用 transform 进行定位
+ } catch (e) {
+ // 如果导入失败,使用简单的居中逻辑
+ const width = container.offsetWidth || 300;
+ const height = container.offsetHeight || 400;
+
+ container.style.left = `${mousePosition.x - width/2}px`;
+ container.style.top = `${mousePosition.y - height/2}px`;
+ container.style.transform = 'none';
+ }
+}
+
+// ==========================================================
+// 通知相关函数
+// ==========================================================
+
+// 显示通知信息
+export function showNotification(message, type = 'error') {
+ const notification = document.createElement('div');
+ notification.className = 'layout-notification';
+ notification.textContent = message;
+
+ // 通知基本样式 - 保留,因为通知应该有一致的UI体验
+ Object.assign(notification.style, {
+ position: 'fixed',
+ top: '24px',
+ left: '50%',
+ transform: 'translateX(-50%) translateY(-20px)',
+ padding: '12px 24px',
+ borderRadius: '8px',
+ fontSize: '14px',
+ fontWeight: '500',
+ zIndex: '10000',
+ transition: 'all 0.3s ease',
+ backdropFilter: 'blur(8px)',
+ boxShadow: '0 8px 24px rgba(0, 0, 0, 0.2)',
+ border: '1px solid rgba(255, 255, 255, 0.1)',
+ width: 'auto',
+ minWidth: '240px',
+ maxWidth: '90%',
+ textAlign: 'center',
+ opacity: '0'
+ });
+
+ if (type === 'info') {
+ notification.style.backgroundColor = 'rgba(20, 120, 60, 0.85)';
+ notification.style.color = 'white';
+ } else if (type === 'warn') {
+ notification.style.backgroundColor = 'rgba(230, 150, 10, 0.85)';
+ notification.style.color = 'white';
+ } else {
+ notification.style.backgroundColor = 'rgba(200, 0, 0, 0.85)';
+ notification.style.color = 'white';
+ }
+
+ document.body.appendChild(notification);
+
+ // 显示动画 - 从上方移入
+ setTimeout(() => {
+ notification.style.opacity = '1';
+ notification.style.transform = 'translateX(-50%) translateY(0)';
+ }, 10);
+
+ setTimeout(() => {
+ notification.style.opacity = '0';
+ notification.style.transform = 'translateX(-50%) translateY(-20px)'; // 向上方消失
+ setTimeout(() => {
+ if (notification.parentNode) {
+ document.body.removeChild(notification);
+ }
+ }, 300);
+ }, 3000);
+}
\ No newline at end of file
diff --git a/js/Intelligent Layout/styles/UIUtils.js b/js/Intelligent Layout/styles/UIUtils.js
new file mode 100644
index 0000000..6697142
--- /dev/null
+++ b/js/Intelligent Layout/styles/UIUtils.js
@@ -0,0 +1,214 @@
+// 智能布局 通用工具模块
+
+// ==========================================================
+// ComfyUI应用和节点相关函数
+// ==========================================================
+
+// 获取ComfyUI应用实例
+export function getComfyUIApp() {
+ if (window.app?.canvas && window.app?.graph) {
+ return window.app;
+ }
+
+ if (window.LiteGraph?.LGraphCanvas?.active_canvas) {
+ const canvas = LiteGraph.LGraphCanvas.active_canvas;
+ if (canvas?.graph) {
+ return { canvas, graph: canvas.graph };
+ }
+ }
+
+ const canvasElement = document.querySelector(".litegraph.litegraph-canvas");
+ if (canvasElement?.lgraphcanvas) {
+ const canvas = canvasElement.lgraphcanvas;
+ if (canvas?.graph) {
+ return { canvas, graph: canvas.graph };
+ }
+ }
+
+ return null;
+}
+
+// 获取选中的节点
+export function getSelectedNodes(app) {
+ if (app.canvas.selected_nodes?.length) {
+ return Array.from(app.canvas.selected_nodes);
+ }
+
+ const selectedNodes = [];
+ if (app.graph?._nodes) {
+ for (const node of app.graph._nodes) {
+ if (node.is_selected) {
+ selectedNodes.push(node);
+ }
+ }
+ }
+
+ return selectedNodes;
+}
+
+// 获取选中的组
+export function getSelectedGroups(app) {
+ const selectedGroups = [];
+
+ if (app.canvas?.selected_groups?.length) {
+ return Array.from(app.canvas.selected_groups);
+ }
+
+ if (app.graph?.groups) {
+ for (const group of app.graph.groups) {
+ if (group.selected) {
+ selectedGroups.push(group);
+ }
+ }
+ }
+
+ return selectedGroups;
+}
+
+// ==========================================================
+// 颜色生成和处理函数
+// ==========================================================
+
+// 生成随机颜色
+export function getRandomColor() {
+ const letters = '0123456789ABCDEF';
+ let color = '#';
+ for (let i = 0; i < 6; i++) {
+ color += letters[Math.floor(Math.random() * 16)];
+ }
+ return color;
+}
+
+// ==========================================================
+// 节点排列和同步相关功能
+// ==========================================================
+
+// 判断节点排列主方向
+export function getMainDirection(centers) {
+ let dx = 0, dy = 0;
+ for (let i = 1; i < centers.length; i++) {
+ dx += Math.abs(centers[i].x - centers[0].x);
+ dy += Math.abs(centers[i].y - centers[0].y);
+ }
+ return dx > dy ? 'horizontal' : 'vertical';
+}
+
+// 计算节点中心位置
+export function getNodeCenters(nodes) {
+ return nodes.map(node => ({
+ node,
+ x: node.pos ? node.pos[0] + (node.size ? node.size[0] / 2 : 0) : 0,
+ y: node.pos ? node.pos[1] + (node.size ? node.size[1] / 2 : 0) : 0
+ }));
+}
+
+// 同步节点大小
+export function syncNodeSize(nodes) {
+ let maxW = 0, maxH = 0;
+ nodes.forEach(node => {
+ if (node.size) {
+ maxW = Math.max(maxW, node.size[0]);
+ maxH = Math.max(maxH, node.size[1]);
+ }
+ });
+
+ nodes.forEach(node => {
+ if (node.size) {
+ node.size[0] = maxW;
+ node.size[1] = maxH;
+ }
+ });
+
+ return { width: maxW, height: maxH };
+}
+
+// 同步节点宽高(根据排列方向)
+export function syncNodeWidthHeight(nodes) {
+ const centers = getNodeCenters(nodes);
+ const mainDir = getMainDirection(centers);
+
+ if (mainDir === 'horizontal') {
+ // 横向排列,同步高度
+ let maxH = 0;
+ nodes.forEach(node => {
+ if (node.size) maxH = Math.max(maxH, node.size[1]);
+ });
+ nodes.forEach(node => {
+ if (node.size) node.size[1] = maxH;
+ });
+ return { direction: 'horizontal', height: maxH };
+ } else {
+ // 纵向排列,同步宽度
+ let maxW = 0;
+ nodes.forEach(node => {
+ if (node.size) maxW = Math.max(maxW, node.size[0]);
+ });
+ nodes.forEach(node => {
+ if (node.size) node.size[0] = maxW;
+ });
+ return { direction: 'vertical', width: maxW };
+ }
+}
+
+// 对齐节点(根据排列方向)
+export function alignNodes(nodes) {
+ const centers = getNodeCenters(nodes);
+ const mainDir = getMainDirection(centers);
+
+ if (mainDir === 'horizontal') {
+ // 横向排列,对齐Y轴
+ const avgY = centers.reduce((sum, c) => sum + c.y, 0) / centers.length;
+ nodes.forEach(node => {
+ if (node.pos) node.pos[1] = avgY - (node.size ? node.size[1] / 2 : 0);
+ });
+ return { direction: 'horizontal', y: avgY };
+ } else {
+ // 纵向排列,对齐X轴
+ const avgX = centers.reduce((sum, c) => sum + c.x, 0) / centers.length;
+ nodes.forEach(node => {
+ if (node.pos) node.pos[0] = avgX - (node.size ? node.size[0] / 2 : 0);
+ });
+ return { direction: 'vertical', x: avgX };
+ }
+}
+
+// ==========================================================
+// 面板位置和大小计算
+// ==========================================================
+
+// 计算面板在鼠标位置的居中定位
+export function calculatePanelPosition(mousePosition, container) {
+ if (!container) return { left: 0, top: 0 };
+
+ const width = container.offsetWidth || 300; // 假设默认宽度为300px
+ const height = container.offsetHeight || 400; // 假设默认高度为400px
+
+ // 确保面板完全在视口内
+ const viewportWidth = window.innerWidth;
+ const viewportHeight = window.innerHeight;
+
+ let left = mousePosition.x - width/2;
+ let top = mousePosition.y - height/2;
+
+ // 防止面板超出右边界
+ if (left + width > viewportWidth) {
+ left = viewportWidth - width - 10;
+ }
+
+ // 防止面板超出左边界
+ if (left < 10) {
+ left = 10;
+ }
+
+ // 防止面板超出下边界
+ if (top + height > viewportHeight) {
+ top = viewportHeight - height - 10;
+ }
+
+ // 防止面板超出上边界
+ if (top < 10) {
+ top = 10;
+ }
+
+ return { left, top };
+}
diff --git a/js/Intelligent Layout/styles/古神之眼/AncientGod1.js b/js/Intelligent Layout/styles/古神之眼/AncientGod1.js
new file mode 100644
index 0000000..06af5bd
--- /dev/null
+++ b/js/Intelligent Layout/styles/古神之眼/AncientGod1.js
@@ -0,0 +1,468 @@
+/**
+ * AncientGod1.js - 古神之眼 紫色版
+ * 这个文件将SVG内容转换为可在JavaScript中直接使用的格式
+ * 使用方法:调用createAncientGod1Eye函数,传入目标DOM元素ID和可选的配置参数
+ */
+
+(function(global) {
+ // SVG内容作为字符串存储
+ const svgContent = ``;
+
+// 导出函数:将SVG插入到指定DOM元素
+global.createAncientGod1Eye = function(targetId, options = {}) {
+ const container = document.getElementById(targetId);
+ if (!container) return;
+ container.innerHTML = svgContent;
+
+ // 获取SVG元素
+ const svg = container.querySelector('svg');
+ const eyeball = svg.querySelector('.eyeball');
+ const iris = svg.querySelector('g[transform="translate(50, 50)"] > circle[r="8"]'); // 虹膜
+ const sclera = svg.querySelector('g[transform="translate(50, 50)"] > circle[r="12.8"]'); // 眼白
+
+ // 存储原始瞳孔半径
+ const pupil = eyeball.querySelector('circle');
+ const originalPupilRadius = pupil ? pupil.getAttribute('r') : "3.5";
+
+ // 最大移动范围(像素)
+ const maxEyeMovement = 4;
+ const maxIrisMovement = 2.5; // 虹膜移动范围比瞳孔小一点
+
+ // 添加变量跟踪鼠标活动状态
+ let mouseActive = false;
+ let mouseTimer = null;
+
+ // 鼠标移动监听函数
+ function handleMouseMove(event) {
+ mouseActive = true;
+ clearTimeout(mouseTimer);
+
+ // 暂停原有动画
+ const animateTransform = eyeball.querySelector('animateTransform');
+ if (animateTransform) {
+ try {
+ // 完全暂停动画而非设置为indefinite
+ animateTransform.setAttribute('repeatCount', '0');
+ animateTransform.setAttribute('dur', '999999s');
+ } catch (e) {
+ console.error("无法暂停动画:", e);
+ }
+ }
+
+ // 获取SVG的位置和尺寸 - 无论鼠标在哪都要获取
+ const svgRect = svg.getBoundingClientRect();
+
+ // 计算鼠标相对于SVG中心的位置
+ const centerX = svgRect.width / 2 + svgRect.left;
+ const centerY = svgRect.height / 2 + svgRect.top;
+
+ // 计算偏移方向
+ const deltaX = event.clientX - centerX;
+ const deltaY = event.clientY - centerY;
+
+ // 计算目光注视方向
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ const normalizedX = distance > 0 ? deltaX / distance : 0;
+ const normalizedY = distance > 0 ? deltaY / distance : 0;
+
+ // 计算鼠标与中心的相对距离比例(用于瞳孔大小)
+ // 根据距离计算瞳孔大小 - 距离越近,瞳孔越大
+ const maxDistance = Math.max(svgRect.width, svgRect.height) / 2;
+ const distanceRatio = 1 - Math.min(distance / maxDistance, 1); // 0到1之间
+
+ // 瞳孔设置 - 根据距离调整大小
+ if (pupil) {
+ const minPupilSize = 3.0;
+ const maxPupilSize = 4.2;
+ const pupilSize = minPupilSize + distanceRatio * (maxPupilSize - minPupilSize);
+ pupil.setAttribute('r', pupilSize.toFixed(1));
+ }
+
+ // 设置眼球跟随,限制在合理范围内
+ const eyeballMoveX = normalizedX * maxEyeMovement;
+ const eyeballMoveY = normalizedY * maxEyeMovement;
+
+ // 设置虹膜跟随,幅度比瞳孔小一些
+ const irisMoveX = normalizedX * maxIrisMovement;
+ const irisMoveY = normalizedY * maxIrisMovement;
+
+ // 应用变换 - 眼瞳
+ eyeball.style.transform = `translate(${eyeballMoveX}px, ${eyeballMoveY}px)`;
+
+ // 应用变换 - 虹膜
+ if (iris) {
+ iris.style.transform = `translate(${irisMoveX}px, ${irisMoveY}px)`;
+ }
+
+ // 鼠标不活动定时器,3秒后恢复原动画
+ mouseTimer = setTimeout(() => {
+ mouseActive = false;
+ resetEyeAnimations();
+ }, 3000);
+ }
+
+ // 重置眼睛所有动画
+ function resetEyeAnimations() {
+ // 获取当前位置,用于平滑过渡
+ const currentTransform = eyeball.style.transform;
+ let currentX = 0;
+ let currentY = 0;
+
+ // 如果当前有transform,解析出当前位置
+ if (currentTransform && currentTransform.includes('translate')) {
+ const match = currentTransform.match(/translate\(([^,]+)px,\s*([^)]+)px\)/);
+ if (match) {
+ currentX = parseFloat(match[1]);
+ currentY = parseFloat(match[2]);
+ }
+ }
+
+ // 创建平滑返回中心的过渡动画
+ if (currentX !== 0 || currentY !== 0) {
+ // 清除之前可能存在的过渡
+ eyeball.style.transition = '';
+
+ // 先确保移除animateTransform的所有效果
+ const animateTransform = eyeball.querySelector('animateTransform');
+ if (animateTransform) {
+ // 完全禁用SVG动画,直到CSS过渡完成
+ animateTransform.setAttribute('repeatCount', '0');
+ animateTransform.setAttribute('dur', '999999s');
+ animateTransform.setAttribute('values', '0,0; 0,0; 0,0');
+ }
+
+ // 在下一帧添加过渡效果
+ requestAnimationFrame(() => {
+ eyeball.style.transition = 'transform 1s cubic-bezier(0.4, 0.0, 0.2, 1)';
+ eyeball.style.transform = 'translate(0px, 0px)';
+
+ // 平滑过渡到中心后再恢复原始动画
+ setTimeout(() => {
+ // 完全移除CSS transform和transition
+ eyeball.style.transition = '';
+ eyeball.style.transform = '';
+
+ // 等待DOM更新完成
+ setTimeout(() => {
+ // 确保没有残留的内联样式
+ eyeball.removeAttribute('style');
+
+ // 重新启用SVG动画
+ if (animateTransform) {
+ // 恢复原始动画内容
+ animateTransform.setAttribute('values', '0,0; 2,-1; -1,-2; -2,1; 3,2; 1,3; -3,-1; -1,2; 2,2; 0,0');
+ animateTransform.setAttribute('dur', '15s');
+ animateTransform.setAttribute('repeatCount', 'indefinite');
+ }
+ }, 50);
+
+ // 同样对虹膜进行过渡处理
+ if (iris) {
+ iris.style.transition = '';
+ iris.style.transform = '';
+ iris.removeAttribute('style');
+ }
+ }, 1000); // 与过渡时间相匹配
+ });
+ } else {
+ // 如果已经在中心位置,直接恢复动画
+ eyeball.style.removeProperty('transform');
+ eyeball.removeAttribute('style');
+
+ const animateTransform = eyeball.querySelector('animateTransform');
+ if (animateTransform) {
+ animateTransform.setAttribute('values', '0,0; 2,-1; -1,-2; -2,1; 3,2; 1,3; -3,-1; -1,2; 2,2; 0,0');
+ animateTransform.setAttribute('dur', '15s');
+ animateTransform.setAttribute('repeatCount', 'indefinite');
+ }
+ }
+
+ // 重置瞳孔大小为原始值,添加平滑过渡
+ if (pupil) {
+ // 获取当前瞳孔大小
+ const currentPupilSize = parseFloat(pupil.getAttribute('r')) || parseFloat(originalPupilRadius);
+ const targetPupilSize = parseFloat(originalPupilRadius);
+
+ // 如果尺寸差异显著,使用动画过渡
+ if (Math.abs(currentPupilSize - targetPupilSize) > 0.1) {
+ // 创建动画过渡
+ const duration = 800; // 持续时间800毫秒
+ const startTime = performance.now();
+ const animate = (time) => {
+ const elapsedTime = time - startTime;
+ const progress = Math.min(elapsedTime / duration, 1);
+ // 使用缓动函数让动画更自然
+ const easeProgress = 1 - Math.pow(1 - progress, 3); // 缓出效果
+
+ const currentSize = currentPupilSize + (targetPupilSize - currentPupilSize) * easeProgress;
+ pupil.setAttribute('r', currentSize.toFixed(2));
+
+ if (progress < 1) {
+ requestAnimationFrame(animate);
+ }
+ };
+
+ requestAnimationFrame(animate);
+ } else {
+ // 如果差异很小,直接设置
+ pupil.setAttribute('r', originalPupilRadius);
+ }
+ }
+
+ // 重置虹膜,也添加平滑过渡
+ if (iris) {
+ const currentIrisTransform = iris.style.transform;
+ let currentIrisX = 0;
+ let currentIrisY = 0;
+
+ if (currentIrisTransform && currentIrisTransform.includes('translate')) {
+ const match = currentIrisTransform.match(/translate\(([^,]+)px,\s*([^)]+)px\)/);
+ if (match) {
+ currentIrisX = parseFloat(match[1]);
+ currentIrisY = parseFloat(match[2]);
+ }
+ }
+
+ if (currentIrisX !== 0 || currentIrisY !== 0) {
+ iris.style.transition = '';
+ requestAnimationFrame(() => {
+ iris.style.transition = 'transform 1s cubic-bezier(0.4, 0.0, 0.2, 1)';
+ iris.style.transform = 'translate(0px, 0px)';
+
+ setTimeout(() => {
+ iris.style.removeProperty('transform');
+ }, 50);
+ });
+ } else {
+ iris.style.transform = '';
+ }
+ }
+ }
+
+ // 添加鼠标离开监听
+ function handleMouseLeave() {
+ clearTimeout(mouseTimer);
+ mouseTimer = setTimeout(() => {
+ mouseActive = false;
+ resetEyeAnimations();
+ }, 1000); // 鼠标离开后1秒恢复原动画
+ }
+
+ // 添加事件监听器 - 改为监听整个文档的鼠标移动
+ document.addEventListener('mousemove', handleMouseMove);
+
+ // 返回清理函数
+ return function cleanup() {
+ document.removeEventListener('mousemove', handleMouseMove);
+ };
+};
+
+})(typeof window !== "undefined" ? window : this);
\ No newline at end of file
diff --git a/js/Intelligent Layout/styles/古神之眼/AncientGod2.js b/js/Intelligent Layout/styles/古神之眼/AncientGod2.js
new file mode 100644
index 0000000..8dd9c24
--- /dev/null
+++ b/js/Intelligent Layout/styles/古神之眼/AncientGod2.js
@@ -0,0 +1,535 @@
+/**
+ * AncientGod2.js - 古神之眼 血色版
+ * 这个文件将SVG内容转换为可在JavaScript中直接使用的格式
+ * 使用方法:调用createAncientGod2Eye函数,传入目标DOM元素ID和可选的配置参数
+ */
+
+(function(global) {
+ // SVG内容作为字符串存储
+ const svgContent = ``;
+
+// 导出函数:将SVG插入到指定DOM元素
+global.createAncientGod2Eye = function(targetId, options = {}) {
+ const container = document.getElementById(targetId);
+ if (!container) return;
+ container.innerHTML = svgContent;
+
+ // 获取SVG元素
+ const svg = container.querySelector('svg');
+ const eyeball = svg.querySelector('.eyeball');
+ const iris = svg.querySelector('g[transform="translate(50, 50)"] > circle[r="8"]'); // 血色虹膜
+ const sclera = svg.querySelector('g[transform="translate(50, 50)"] > circle[r="12.8"]'); // 眼白(暗红色)
+
+ // 存储原始瞳孔半径
+ const pupil = eyeball.querySelector('circle');
+ const originalPupilRadius = pupil ? pupil.getAttribute('r') : "4";
+
+ // 最大移动范围(像素)- 血色版眼睛可以有更剧烈的反应
+ const maxEyeMovement = 5;
+ const maxIrisMovement = 3; // 虹膜移动范围比瞳孔小一点,但血色版幅度更大
+
+ // 添加变量跟踪鼠标活动状态
+ let mouseActive = false;
+ let mouseTimer = null;
+ let debugLog = false; // 调试模式
+
+ // 鼠标移动监听函数
+ function handleMouseMove(event) {
+ mouseActive = true;
+ clearTimeout(mouseTimer);
+
+ // 暂停原有动画
+ const animateTransform = eyeball.querySelector('animateTransform');
+ if (animateTransform) {
+ try {
+ // 完全暂停动画而非设置为indefinite
+ animateTransform.setAttribute('repeatCount', '0');
+ animateTransform.setAttribute('dur', '999999s');
+ } catch (e) {
+ console.error("无法暂停动画:", e);
+ }
+ }
+
+ // 获取SVG的位置和尺寸
+ const svgRect = svg.getBoundingClientRect();
+
+ // 计算鼠标相对于SVG中心的位置
+ const centerX = svgRect.width / 2 + svgRect.left;
+ const centerY = svgRect.height / 2 + svgRect.top;
+
+ // 计算偏移方向
+ const deltaX = event.clientX - centerX;
+ const deltaY = event.clientY - centerY;
+
+ // 计算眼睛注视方向
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ const normalizedX = distance > 0 ? deltaX / distance : 0;
+ const normalizedY = distance > 0 ? deltaY / distance : 0;
+
+ // 设置眼球跟随,限制在最大范围内
+ const eyeballMoveX = normalizedX * maxEyeMovement;
+ const eyeballMoveY = normalizedY * maxEyeMovement;
+
+ // 设置虹膜跟随,幅度比瞳孔小一些
+ const irisMoveX = normalizedX * maxIrisMovement;
+ const irisMoveY = normalizedY * maxIrisMovement;
+
+ // 应用变换 - 眼瞳
+ eyeball.style.transform = `translate(${eyeballMoveX}px, ${eyeballMoveY}px)`;
+
+ // 应用变换 - 虹膜
+ if (iris) {
+ iris.style.transform = `translate(${irisMoveX}px, ${irisMoveY}px)`;
+
+ // 血色版虹膜脉动效果 - 根据鼠标接近程度增强
+ // 临时暂停原有动画
+ const irisAnimateR = iris.querySelector('animate[attributeName="r"]');
+ if (irisAnimateR) {
+ irisAnimateR.setAttribute('dur', 'indefinite');
+ }
+
+ const irisAnimateFill = iris.querySelector('animate[attributeName="fill"]');
+ if (irisAnimateFill) {
+ irisAnimateFill.setAttribute('dur', 'indefinite');
+ }
+
+ // 根据距离调整虹膜大小和颜色 - 距离越近,虹膜越紧缩
+ const maxDistance = Math.max(svgRect.width, svgRect.height) / 2;
+ const distanceRatio = Math.min(distance / maxDistance, 1); // 0到1之间
+
+ const minIrisSize = 7.4; // 接近时紧缩
+ const maxIrisSize = 8.6; // 远离时放大
+ const irisSize = minIrisSize + distanceRatio * (maxIrisSize - minIrisSize);
+ iris.setAttribute('r', irisSize.toFixed(1));
+
+ // 距离越近,血色越深
+ const irisColor = distanceRatio < 0.3 ? '#6A0303' : // 非常近 - 深红色
+ distanceRatio < 0.6 ? '#7A0404' : // 中等距离
+ '#B80000'; // 远距离 - 鲜红色
+ iris.setAttribute('fill', irisColor);
+ }
+
+ // 添加血色瞳孔收缩效应 - 鼠标靠近时瞳孔扩大(血色版特性相反)
+ if (pupil) {
+ // 基于距离计算瞳孔大小
+ const maxDistance = Math.max(svgRect.width, svgRect.height) / 2;
+ const distanceRatio = Math.min(distance / maxDistance, 1); // 0到1之间
+
+ // 血色版特殊效果:越近瞳孔越大(掠食特性),相反于普通眼睛
+ // 显著增大变化范围,使效果更加明显
+ const minPupilSize = 2.8; // 远时最小
+ const maxPupilSize = 5.5; // 近时最大
+ const pupilSize = maxPupilSize - distanceRatio * (maxPupilSize - minPupilSize);
+
+ // 直接设置瞳孔大小,并确保应用生效
+ pupil.setAttribute('r', pupilSize.toFixed(1));
+
+ // 调试日志
+ if (debugLog) {
+ console.log(`距离比例: ${distanceRatio.toFixed(2)}, 瞳孔大小: ${pupilSize.toFixed(1)}`);
+ }
+ }
+
+ // 设置鼠标不活动定时器,2.5秒后恢复原动画(血色版反应更快)
+ mouseTimer = setTimeout(() => {
+ mouseActive = false;
+ resetEyeAnimations();
+ }, 2500);
+ }
+
+ // 重置眼睛所有动画
+ function resetEyeAnimations() {
+ // 获取当前位置,用于平滑过渡
+ const currentTransform = eyeball.style.transform;
+ let currentX = 0;
+ let currentY = 0;
+
+ // 如果当前有transform,解析出当前位置
+ if (currentTransform && currentTransform.includes('translate')) {
+ const match = currentTransform.match(/translate\(([^,]+)px,\s*([^)]+)px\)/);
+ if (match) {
+ currentX = parseFloat(match[1]);
+ currentY = parseFloat(match[2]);
+ }
+ }
+
+ // 创建平滑返回中心的过渡动画 - 血色版眼睛的过渡比秩序之眼更快速但仍然平滑
+ if (currentX !== 0 || currentY !== 0) {
+ // 清除之前可能存在的过渡
+ eyeball.style.transition = '';
+
+ // 先确保移除animateTransform的所有效果
+ const animateTransform = eyeball.querySelector('animateTransform');
+ if (animateTransform) {
+ // 完全禁用SVG动画,直到CSS过渡完成
+ animateTransform.setAttribute('repeatCount', '0');
+ animateTransform.setAttribute('dur', '999999s');
+ animateTransform.setAttribute('values', '0,0; 0,0; 0,0');
+ }
+
+ // 在下一帧添加过渡效果 - 血色版使用更短的过渡时间
+ requestAnimationFrame(() => {
+ eyeball.style.transition = 'transform 0.8s cubic-bezier(0.25, 0.1, 0.25, 1)';
+ eyeball.style.transform = 'translate(0px, 0px)';
+
+ // 过渡结束后平滑恢复原始动画
+ setTimeout(() => {
+ // 完全移除CSS transform和transition
+ eyeball.style.transition = '';
+ eyeball.style.transform = '';
+
+ // 等待DOM更新完成
+ setTimeout(() => {
+ // 确保没有残留的内联样式
+ eyeball.removeAttribute('style');
+
+ // 重新启用SVG动画
+ if (animateTransform) {
+ // 恢复原始动画内容
+ animateTransform.setAttribute('values', '0,0; 2,-1; -1,-2; -2,1; 3,2; 1,3; -3,-1; -1,2; 2,2; 0,0');
+ animateTransform.setAttribute('dur', '15s');
+ animateTransform.setAttribute('repeatCount', 'indefinite');
+ }
+ }, 50);
+
+ // 同样对虹膜进行平滑过渡
+ if (iris) {
+ iris.style.transition = '';
+ iris.style.transform = '';
+ iris.removeAttribute('style');
+ }
+ }, 800); // 与过渡时间相匹配
+ });
+ } else {
+ // 如果已经在中心位置,直接恢复动画
+ eyeball.style.removeProperty('transform');
+ eyeball.removeAttribute('style');
+
+ const animateTransform = eyeball.querySelector('animateTransform');
+ if (animateTransform) {
+ animateTransform.setAttribute('values', '0,0; 2,-1; -1,-2; -2,1; 3,2; 1,3; -3,-1; -1,2; 2,2; 0,0');
+ animateTransform.setAttribute('dur', '15s');
+ animateTransform.setAttribute('repeatCount', 'indefinite');
+ }
+ }
+
+ // 重置瞳孔大小为原始值,添加平滑过渡效果
+ if (pupil) {
+ // 获取当前瞳孔大小
+ const currentPupilSize = parseFloat(pupil.getAttribute('r')) || parseFloat(originalPupilRadius);
+ const targetPupilSize = parseFloat(originalPupilRadius);
+
+ // 如果尺寸差异显著,使用动画过渡
+ if (Math.abs(currentPupilSize - targetPupilSize) > 0.1) {
+ // 创建动画过渡 - 血色版动画更快
+ const duration = 600; // 持续时间600毫秒
+ const startTime = performance.now();
+ const animate = (time) => {
+ const elapsedTime = time - startTime;
+ const progress = Math.min(elapsedTime / duration, 1);
+ // 使用弹性缓动让血色版更有特点
+ const easeProgress = 1 - Math.pow(1 - progress, 2.5); // 略微弹性的缓出
+
+ const currentSize = currentPupilSize + (targetPupilSize - currentPupilSize) * easeProgress;
+ pupil.setAttribute('r', currentSize.toFixed(2));
+
+ if (progress < 1) {
+ requestAnimationFrame(animate);
+ }
+ };
+
+ requestAnimationFrame(animate);
+ } else {
+ // 如果差异很小,直接设置
+ pupil.setAttribute('r', originalPupilRadius);
+ }
+ }
+
+ // 重置虹膜,同样添加平滑过渡
+ if (iris) {
+ // 对虹膜大小和颜色也添加过渡效果
+ iris.style.transition = 'fill 0.8s ease';
+ iris.removeAttribute('r'); // 让原有动画接管大小变化
+ iris.removeAttribute('fill'); // 让原有动画接管颜色变化
+
+ // 处理虹膜的位置过渡
+ const currentIrisTransform = iris.style.transform;
+ let currentIrisX = 0;
+ let currentIrisY = 0;
+
+ if (currentIrisTransform && currentIrisTransform.includes('translate')) {
+ const match = currentIrisTransform.match(/translate\(([^,]+)px,\s*([^)]+)px\)/);
+ if (match) {
+ currentIrisX = parseFloat(match[1]);
+ currentIrisY = parseFloat(match[2]);
+ }
+ }
+
+ if (currentIrisX !== 0 || currentIrisY !== 0) {
+ iris.style.transition = iris.style.transition + ', transform 0.8s cubic-bezier(0.25, 0.1, 0.25, 1)';
+ requestAnimationFrame(() => {
+ iris.style.transform = 'translate(0px, 0px)';
+
+ setTimeout(() => {
+ iris.style.transition = '';
+ iris.style.transform = '';
+
+ // 恢复虹膜动画
+ const irisAnimateR = iris.querySelector('animate[attributeName="r"]');
+ if (irisAnimateR) {
+ irisAnimateR.setAttribute('dur', '3s');
+ }
+
+ const irisAnimateFill = iris.querySelector('animate[attributeName="fill"]');
+ if (irisAnimateFill) {
+ irisAnimateFill.setAttribute('dur', '5s');
+ }
+ }, 800);
+ });
+ } else {
+ iris.style.transform = '';
+
+ // 恢复虹膜动画
+ const irisAnimateR = iris.querySelector('animate[attributeName="r"]');
+ if (irisAnimateR) {
+ irisAnimateR.setAttribute('dur', '3s');
+ }
+
+ const irisAnimateFill = iris.querySelector('animate[attributeName="fill"]');
+ if (irisAnimateFill) {
+ irisAnimateFill.setAttribute('dur', '5s');
+ }
+ }
+ }
+ }
+
+ // 添加事件监听器 - 改为监听整个文档的鼠标移动
+ document.addEventListener('mousemove', handleMouseMove);
+
+ // 返回清理函数
+ return function cleanup() {
+ document.removeEventListener('mousemove', handleMouseMove);
+ // 不再需要移除mouseleave事件,因为它现在全局监视
+ };
+};
+
+})(typeof window !== "undefined" ? window : this);
diff --git a/js/Intelligent Layout/styles/古神之眼/AncientGodTheme.js b/js/Intelligent Layout/styles/古神之眼/AncientGodTheme.js
new file mode 100644
index 0000000..048d2d8
--- /dev/null
+++ b/js/Intelligent Layout/styles/古神之眼/AncientGodTheme.js
@@ -0,0 +1,842 @@
+/**
+ * AncientGodTheme.js - 古神之眼主题
+ * 整合秩序和混沌两种风格的面板和硬币效果
+ */
+
+import './AncientGod1.js'; // 秩序主题硬币
+import './AncientGod2.js'; // 混沌主题硬币
+import { injectStyles, removeStyles } from '../UIStyles.js';
+
+// 导入模块化的面板背景SVG
+import { OrderPanelSVG } from './OrderPanelStyle.js';
+import { ChaosPanelSVG } from './ChaosPanelStyle.js';
+
+// 秩序主题CSS
+const OrderPanelCSS = `
+ .layout-panel {
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+ border: 1px solid rgba(176, 141, 225, 0.3);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 0 8px rgba(138, 43, 226, 0.2);
+ }
+
+ .layout-panel::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 1px;
+ background: linear-gradient(90deg, transparent, rgba(176, 141, 225, 0.7), transparent);
+ }
+
+ .ancient-god-coin-container {
+ perspective: 1600px;
+ width: 180px;
+ height: 180px;
+ margin: 5px auto 25px auto;
+ position: relative;
+ }
+
+ .ancient-god-coin {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ transform-style: preserve-3d;
+ transition: transform 0.9s cubic-bezier(.4,2,.3,1);
+ cursor: pointer;
+ }
+
+ .ancient-god-coin.flipped {
+ transform: rotateY(180deg);
+ }
+
+ .ancient-god-coin-face {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ backface-visibility: hidden;
+ border-radius: 50%;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
+ }
+
+ .ancient-god-coin-face.back {
+ transform: rotateY(180deg);
+ }
+
+ .layout-btn {
+ background: rgba(106, 90, 205, 0.4);
+ border: 1px solid rgba(176, 141, 225, 0.5);
+ color: #e1e1e1;
+ }
+
+ .layout-btn:hover {
+ background: rgba(106, 90, 205, 0.6);
+ box-shadow: 0 0 15px rgba(138, 43, 226, 0.5);
+ border: 1px solid rgba(176, 141, 225, 0.8);
+ color: white;
+ }
+
+ /* 移除特殊按钮样式,统一所有按钮外观 */
+
+ select {
+ border: 1px solid rgba(176, 141, 225, 0.4);
+ background-color: rgba(42, 30, 74, 0.6);
+ }
+`;
+
+// 混沌主题CSS
+const ChaosPanelCSS = `
+ .layout-panel {
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+ border: 1px solid rgba(255, 0, 0, 0.3);
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 0 10px rgba(184, 0, 0, 0.2);
+ }
+
+ .layout-panel::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 1px;
+ background: linear-gradient(90deg, transparent, rgba(255, 0, 0, 0.7), transparent);
+ }
+
+ .ancient-god-coin-container {
+ perspective: 1600px;
+ width: 180px;
+ height: 180px;
+ margin: 5px auto 25px auto;
+ position: relative;
+ }
+
+ .ancient-god-coin {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ transform-style: preserve-3d;
+ transition: transform 0.9s cubic-bezier(.4,2,.3,1);
+ cursor: pointer;
+ }
+
+ .ancient-god-coin.flipped {
+ transform: rotateY(180deg);
+ }
+
+ .ancient-god-coin-face {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ backface-visibility: hidden;
+ border-radius: 50%;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
+ }
+
+ .ancient-god-coin-face.back {
+ transform: rotateY(180deg);
+ }
+
+ .layout-btn {
+ background: rgba(170, 0, 0, 0.4);
+ border: 1px solid rgba(255, 0, 0, 0.5);
+ color: #e1e1e1;
+ }
+
+ .layout-btn:hover {
+ background: rgba(170, 0, 0, 0.6);
+ box-shadow: 0 0 15px rgba(255, 0, 0, 0.5);
+ border: 1px solid rgba(255, 0, 0, 0.8);
+ color: white;
+ }
+
+ select {
+ border: 1px solid rgba(255, 0, 0, 0.4);
+ background-color: rgba(51, 0, 0, 0.6);
+ }
+`;
+
+// 添加主题切换过渡动画CSS
+const themeTransitionCSS = `
+ .ancient-god-ripple-container {
+ pointer-events: none;
+ z-index: 10000;
+ }
+
+ .theme-svg-background {
+ transition: opacity 0.6s ease;
+ }
+
+ .layout-panel {
+ transition: border-color 0.8s ease, box-shadow 0.8s ease, opacity 0.5s ease !important;
+ }
+
+ .layout-btn {
+ transition: background 0.5s ease, border-color 0.5s ease, box-shadow 0.5s ease;
+ }
+
+ select {
+ transition: border-color 0.5s ease, background-color 0.5s ease;
+ }
+
+ .theme-old-layer, .theme-new-layer {
+ backface-visibility: hidden;
+ transform: translateZ(0);
+ }
+
+ .theme-new-layer {
+ clip-path: circle(0% at 50% 50%);
+ }
+`;
+
+// 主题标识符
+const THEME_ID = {
+ PURPLE: 'purple', // 秩序主题
+ BLOOD: 'blood' // 混沌主题
+};
+
+let currentThemeId = THEME_ID.PURPLE;
+let panelContainer = null;
+let coinElement = null;
+let coinFlipped = false;
+let currentPanelOpacity = 0.8; // 默认透明度设置为80%
+
+// 主题SVG定义 - 使用导入的模块
+const themeSVGs = {
+ [THEME_ID.PURPLE]: OrderPanelSVG,
+ [THEME_ID.BLOOD]: ChaosPanelSVG
+};
+
+// 主题CSS样式 - 使用导入的模块
+const themeCSSTemplates = {
+ [THEME_ID.PURPLE]: OrderPanelCSS,
+ [THEME_ID.BLOOD]: ChaosPanelCSS
+};
+
+// 主题样式注入函数
+function injectThemeStyles() {
+ // 注入当前主题的CSS
+ removeStyles('ancient-god-theme-css');
+ injectStyles('ancient-god-theme-css', themeCSSTemplates[currentThemeId]);
+
+ // 注入过渡动画CSS
+ removeStyles('ancient-god-transition-css');
+ injectStyles('ancient-god-transition-css', themeTransitionCSS);
+}
+
+// 辅助函数:设置SVG元素样式,避免重复代码
+function applySVGStyles(svgElement) {
+ if (!svgElement) return;
+
+ svgElement.style.width = '100%';
+ svgElement.style.height = '100%';
+ svgElement.style.display = 'block';
+ svgElement.setAttribute('preserveAspectRatio', 'xMidYMid slice');
+}
+
+// 设置SVG背景
+function setSVGBackground(themeId = null) {
+ if (!panelContainer) return;
+
+ // 如果未指定主题ID,则使用当前主题
+ const targetThemeId = themeId || currentThemeId;
+
+ // 创建或获取SVG背景容器
+ let svgContainer = panelContainer.querySelector('.theme-svg-background');
+ if (!svgContainer) {
+ svgContainer = document.createElement('div');
+ svgContainer.className = 'theme-svg-background';
+ svgContainer.style.position = 'absolute';
+ svgContainer.style.top = '0';
+ svgContainer.style.left = '0';
+ svgContainer.style.width = '100%';
+ svgContainer.style.height = '100%';
+ svgContainer.style.zIndex = '-1';
+ svgContainer.style.borderRadius = 'inherit';
+ svgContainer.style.overflow = 'hidden';
+
+ // 确保SVG背景完全覆盖面板,包括边缘和内边距
+ svgContainer.style.boxSizing = 'border-box';
+ svgContainer.style.margin = '0';
+ svgContainer.style.padding = '0';
+
+ panelContainer.insertBefore(svgContainer, panelContainer.firstChild);
+ }
+
+ // 设置SVG内容
+ svgContainer.innerHTML = themeSVGs[targetThemeId];
+
+ // 确保SVG元素本身也完全填充容器
+ const svgElement = svgContainer.querySelector('svg');
+ if (svgElement) {
+ applySVGStyles(svgElement);
+ }
+}
+
+// 创建硬币元素
+function createCoinElement() {
+ // 创建容器
+ const container = document.createElement('div');
+ container.className = 'ancient-god-coin-container';
+
+ // 创建硬币元素
+ const coin = document.createElement('div');
+ coin.className = 'ancient-god-coin';
+
+ // 创建正面 (紫色)
+ const front = document.createElement('div');
+ front.className = 'ancient-god-coin-face front';
+ front.id = 'ancient-god-eye1';
+
+ // 创建反面 (血色)
+ const back = document.createElement('div');
+ back.className = 'ancient-god-coin-face back';
+ back.id = 'ancient-god-eye2';
+
+ // 组装硬币
+ coin.appendChild(front);
+ coin.appendChild(back);
+ container.appendChild(coin);
+
+ // 初始化硬币眼睛
+ setTimeout(() => {
+ if (typeof window.createAncientGod1Eye === 'function') {
+ window.createAncientGod1Eye('ancient-god-eye1');
+ } else {
+ console.warn('无法找到createAncientGod1Eye函数');
+ }
+ if (typeof window.createAncientGod2Eye === 'function') {
+ window.createAncientGod2Eye('ancient-god-eye2');
+ } else {
+ console.warn('无法找到createAncientGod2Eye函数');
+ }
+ }, 100);
+
+ return {container, coin};
+}
+
+// 设置面板透明度 - 修改为配合UIStyles.js的统一透明度控制
+export function setPanelOpacity(opacity) {
+ // 只保存当前透明度,实际的透明度设置由UIStyles.js处理
+ currentPanelOpacity = Math.max(0.1, Math.min(1.0, Number(opacity)));
+
+ // 更新数据属性,供UIStyles.js读取
+ if (panelContainer) {
+ panelContainer.dataset.opacity = String(currentPanelOpacity);
+ }
+}
+
+// 获取当前面板透明度
+export function getPanelOpacity() {
+ return currentPanelOpacity;
+}
+
+// 从DOM元素或默认值恢复透明度设置
+function restorePanelOpacity() {
+ if (!panelContainer) return;
+
+ const savedOpacity = panelContainer.dataset.opacity;
+ const opacity = savedOpacity ? Number(savedOpacity) : currentPanelOpacity;
+ setPanelOpacity(opacity);
+}
+
+// 翻转硬币并切换主题
+function flipCoin() {
+ if (!coinElement || !panelContainer) return;
+
+ // 保存当前的透明度设置,以便稍后恢复
+ const savedBgOpacity = panelContainer.dataset.bgOpacity || currentPanelOpacity;
+ const savedBtnOpacity = panelContainer.dataset.btnOpacity || DEFAULT_OPACITY.buttons;
+
+ // 根据目标状态确定硬币的翻转方向
+ const targetState = !coinFlipped;
+ coinFlipped = targetState; // 直接设置为目标状态
+
+ // 获取当前和目标主题
+ const fromTheme = !targetState ? THEME_ID.BLOOD : THEME_ID.PURPLE;
+ const toTheme = targetState ? THEME_ID.BLOOD : THEME_ID.PURPLE;
+
+ // 更新硬币翻转状态
+ if (coinFlipped) {
+ coinElement.classList.add('flipped');
+ currentThemeId = THEME_ID.BLOOD;
+ } else {
+ coinElement.classList.remove('flipped');
+ currentThemeId = THEME_ID.PURPLE;
+ }
+
+ // 等待硬币翻转动画完成后再开始扩散效果
+ setTimeout(() => {
+ // 准备新旧主题层
+ prepareThemeLayers(fromTheme, toTheme);
+
+ // 获取硬币元素在面板中的位置
+ const coinRect = coinElement.getBoundingClientRect();
+ const panelRect = panelContainer.getBoundingClientRect();
+
+ // 计算硬币中心相对于面板的位置
+ const coinCenterX = (coinRect.left + coinRect.width / 2) - panelRect.left;
+ const coinCenterY = (coinRect.top + coinRect.height / 2) - panelRect.top;
+
+ // 计算面板对角线长度 (作为最大扩散半径)
+ const maxRadius = Math.sqrt(Math.pow(panelRect.width, 2) + Math.pow(panelRect.height, 2));
+
+ // 创建扩散效果容器 - 仅用于视觉效果
+ const rippleContainer = document.createElement('div');
+ rippleContainer.className = 'ancient-god-ripple-container';
+ rippleContainer.style.position = 'absolute';
+ rippleContainer.style.top = '0';
+ rippleContainer.style.left = '0';
+ rippleContainer.style.width = '100%';
+ rippleContainer.style.height = '100%';
+ rippleContainer.style.pointerEvents = 'none';
+ rippleContainer.style.zIndex = '10000';
+ rippleContainer.style.borderRadius = 'inherit';
+ rippleContainer.style.overflow = 'hidden';
+
+ // 创建发光扩散效果元素 - 纯视觉效果
+ const glowRipple = document.createElement('div');
+ glowRipple.className = 'ancient-god-glow-ripple';
+ glowRipple.style.position = 'absolute';
+ glowRipple.style.top = coinCenterY + 'px';
+ glowRipple.style.left = coinCenterX + 'px';
+ glowRipple.style.width = '0';
+ glowRipple.style.height = '0';
+ glowRipple.style.borderRadius = '50%';
+ glowRipple.style.transform = 'translate(-50%, -50%)';
+ glowRipple.style.pointerEvents = 'none';
+ glowRipple.style.background = coinFlipped
+ ? 'radial-gradient(circle, rgba(255, 0, 0, 0.3) 0%, rgba(122, 0, 0, 0.1) 70%, transparent 100%)'
+ : 'radial-gradient(circle, rgba(176, 141, 225, 0.3) 0%, rgba(106, 90, 205, 0.1) 70%, transparent 100%)';
+ glowRipple.style.boxShadow = coinFlipped
+ ? '0 0 20px 10px rgba(255, 0, 0, 0.3)'
+ : '0 0 20px 10px rgba(138, 43, 226, 0.3)';
+
+ // 添加CSS动画样式
+ const rippleStyle = document.createElement('style');
+ rippleStyle.textContent = `
+ @keyframes ancient-god-glow-animation {
+ 0% {
+ width: 0;
+ height: 0;
+ opacity: 0.8;
+ }
+ 100% {
+ width: ${maxRadius * 2}px;
+ height: ${maxRadius * 2}px;
+ opacity: 0;
+ }
+ }
+
+ @keyframes ancient-god-reveal-animation {
+ 0% {
+ clip-path: circle(0% at ${coinCenterX}px ${coinCenterY}px);
+ }
+ 100% {
+ clip-path: circle(${maxRadius}px at ${coinCenterX}px ${coinCenterY}px);
+ }
+ }
+
+ .theme-transition-active {
+ animation: ancient-god-reveal-animation 2s ease-out forwards;
+ }
+
+ .ancient-god-glow-ripple {
+ animation: ancient-god-glow-animation 2s ease-out forwards;
+ }
+ `;
+ document.head.appendChild(rippleStyle);
+
+ // 添加扩散元素到容器
+ rippleContainer.appendChild(glowRipple);
+ panelContainer.appendChild(rippleContainer);
+ // 启动新主题的显示动画
+ const newThemeLayer = panelContainer.querySelector('.theme-new-layer');
+ if (newThemeLayer) {
+ // 设置新主题背景的初始clip-path位置到硬币中心
+ newThemeLayer.style.clipPath = `circle(0% at ${coinCenterX}px ${coinCenterY}px)`;
+ // 让CSS接管动画,而不是通过JS重设位置
+ newThemeLayer.classList.add('theme-transition-active');
+ // 保留新主题中的SVG动画,不要重新渲染
+ const svgElement = newThemeLayer.querySelector('svg');
+ if (svgElement) {
+ svgElement.setAttribute('data-preserve-animation', 'true');
+ applySVGStyles(svgElement);
+ }
+ }
+
+ // 注入当前主题的CSS以确保应用正确的样式
+ injectThemeStyles();
+
+ // 动画结束后清理并恢复透明度
+ setTimeout(() => {
+ // 移除临时元素
+ rippleContainer.remove();
+ rippleStyle.remove();
+
+ // 优化主题层,但保留动画状态
+ cleanupThemeLayers();
+
+ // 恢复之前保存的透明度设置
+ if (panelContainer) {
+ // 设置数据属性
+ panelContainer.dataset.bgOpacity = savedBgOpacity;
+ panelContainer.dataset.btnOpacity = savedBtnOpacity;
+
+ // 应用透明度
+ const bgContainer = panelContainer.querySelector('.theme-svg-background');
+ if (bgContainer) {
+ bgContainer.style.opacity = savedBgOpacity;
+ }
+
+ // 通知主题系统透明度已更改
+ _updatePanelStyles(panelContainer);
+ }
+ }, 2000);
+ }, 900); // 等待硬币翻转动画完成(0.9秒)
+}
+
+// 准备主题过渡层
+function prepareThemeLayers(fromTheme, toTheme) {
+ if (!panelContainer) return;
+
+ // 清除可能存在的旧层
+ cleanupThemeLayers();
+
+ // 创建旧主题层 (底层)
+ const oldLayer = document.createElement('div');
+ oldLayer.className = 'theme-old-layer';
+ oldLayer.style.position = 'absolute';
+ oldLayer.style.top = '0';
+ oldLayer.style.left = '0';
+ oldLayer.style.width = '100%';
+ oldLayer.style.height = '100%';
+ oldLayer.style.borderRadius = 'inherit';
+ oldLayer.style.zIndex = '-2';
+ oldLayer.style.overflow = 'hidden';
+
+ // 创建新主题层 (顶层)
+ const newLayer = document.createElement('div');
+ newLayer.className = 'theme-new-layer';
+ newLayer.style.position = 'absolute';
+ newLayer.style.top = '0';
+ newLayer.style.left = '0';
+ newLayer.style.width = '100%';
+ newLayer.style.height = '100%';
+ newLayer.style.borderRadius = 'inherit';
+ newLayer.style.zIndex = '-1';
+ newLayer.style.overflow = 'hidden';
+
+ // 初始状态下,新主题层是隐藏的(通过clip-path)
+ newLayer.style.clipPath = 'circle(0% at 50% 50%)';
+
+ // 为新旧层设置不同的背景和样式
+ // 旧主题背景
+ const oldBg = document.createElement('div');
+ oldBg.innerHTML = themeSVGs[fromTheme];
+ oldBg.style.width = '100%';
+ oldBg.style.height = '100%';
+ oldLayer.appendChild(oldBg);
+
+ // 确保SVG完全覆盖容器
+ const oldSvg = oldBg.querySelector('svg');
+ if (oldSvg) {
+ applySVGStyles(oldSvg);
+ }
+
+ // 新主题背景
+ const newBg = document.createElement('div');
+ newBg.innerHTML = themeSVGs[toTheme];
+ newBg.style.width = '100%';
+ newBg.style.height = '100%';
+ newLayer.appendChild(newBg);
+
+ // 确保SVG完全覆盖容器
+ const newSvg = newBg.querySelector('svg');
+ if (newSvg) {
+ applySVGStyles(newSvg);
+ }
+
+ // 添加主题样式
+ applyThemeToLayer(oldLayer, fromTheme);
+ applyThemeToLayer(newLayer, toTheme);
+
+ // 添加层到面板
+ panelContainer.insertBefore(oldLayer, panelContainer.firstChild);
+ panelContainer.insertBefore(newLayer, panelContainer.firstChild);
+
+ // 移除原来的SVG背景容器
+ const oldSvgContainer = panelContainer.querySelector('.theme-svg-background');
+ if (oldSvgContainer) {
+ oldSvgContainer.remove();
+ }
+}
+
+// 应用主题样式到层
+function applyThemeToLayer(layer, themeId) {
+ // 添加边框样式
+ if (themeId === THEME_ID.PURPLE) {
+ layer.style.border = '1px solid rgba(176, 141, 225, 0.3)';
+ layer.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.3), 0 0 8px rgba(138, 43, 226, 0.2)';
+ } else {
+ layer.style.border = '1px solid rgba(255, 0, 0, 0.3)';
+ layer.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.3), 0 0 10px rgba(184, 0, 0, 0.2)';
+ }
+
+ // 添加背景模糊效果
+ layer.style.backdropFilter = 'blur(12px)';
+ layer.style.webkitBackdropFilter = 'blur(12px)';
+}
+
+// 清理主题层
+function cleanupThemeLayers() {
+ if (!panelContainer) return;
+
+ // 保存当前透明度设置
+ const savedBgOpacity = panelContainer.dataset.bgOpacity || currentPanelOpacity;
+
+ // 移除旧层,但保留新层作为当前背景
+ const oldLayer = panelContainer.querySelector('.theme-old-layer');
+ if (oldLayer) oldLayer.remove();
+
+ // 将新层转换为标准背景层
+ const newLayer = panelContainer.querySelector('.theme-new-layer');
+ if (newLayer) {
+ // 移除动画类并重设clip-path,保持全覆盖状态
+ newLayer.classList.remove('theme-transition-active');
+ newLayer.style.clipPath = 'none';
+ newLayer.className = 'theme-svg-background';
+ newLayer.style.zIndex = '-1';
+
+ // 保留SVG中的动画状态,不要重新设置内容
+ const existingSvg = newLayer.querySelector('svg');
+ if (existingSvg && existingSvg.getAttribute('data-preserve-animation') === 'true') {
+ applySVGStyles(existingSvg);
+ }
+
+ // 恢复透明度设置
+ newLayer.style.opacity = savedBgOpacity;
+ } else {
+ // 如果没有新层(异常情况),则创建标准背景
+ setSVGBackground();
+
+ // 恢复透明度
+ const bgContainer = panelContainer.querySelector('.theme-svg-background');
+ if (bgContainer) {
+ bgContainer.style.opacity = savedBgOpacity;
+ }
+ }
+
+ // 添加以下代码来更新按钮颜色
+ setTimeout(() => {
+ if (panelContainer) {
+ const buttons = panelContainer.querySelectorAll('.layout-btn');
+ const btnOpacity = parseFloat(panelContainer.dataset.btnOpacity || DEFAULT_OPACITY.buttons);
+
+ buttons.forEach(button => {
+ // 根据当前主题设置按钮背景颜色
+ const bgColor = currentThemeId === THEME_ID.PURPLE
+ ? `rgba(106, 90, 205, ${btnOpacity})` // 秩序主题 - 紫色
+ : `rgba(170, 0, 0, ${btnOpacity})`; // 混沌主题 - 红色
+
+ button.style.backgroundColor = bgColor;
+ });
+ }
+ }, 50);
+}
+
+// 主题模块接口
+const AncientGodTheme = {
+ name: '古神之眼',
+ description: '一款融合秩序与混沌的主题,通过硬币翻转切换两种截然不同的视觉风格',
+ // 初始化主题
+ init(container, existingCoinEl) {
+ panelContainer = container;
+
+ // 确保每次初始化时硬币都处于正面状态
+ coinFlipped = false;
+ currentThemeId = THEME_ID.PURPLE;
+
+ // 创建或使用硬币元素
+ if (existingCoinEl) {
+ // 完全清空现有容器并重建硬币
+ existingCoinEl.innerHTML = '';
+
+ // 创建硬币元素
+ const coin = document.createElement('div');
+ coin.className = 'ancient-god-coin';
+ // 确保移除任何可能存在的flipped类
+ coin.classList.remove('flipped');
+
+ // 创建正面 (紫色)
+ const front = document.createElement('div');
+ front.className = 'ancient-god-coin-face front';
+ front.id = 'ancient-god-eye1';
+ front.style.width = '100%';
+ front.style.height = '100%';
+ front.style.position = 'absolute';
+ front.style.backfaceVisibility = 'hidden';
+ front.style.borderRadius = '50%';
+
+ // 创建反面 (血色)
+ const back = document.createElement('div');
+ back.className = 'ancient-god-coin-face back';
+ back.id = 'ancient-god-eye2';
+ back.style.width = '100%';
+ back.style.height = '100%';
+ back.style.position = 'absolute';
+ back.style.backfaceVisibility = 'hidden';
+ back.style.borderRadius = '50%';
+ back.style.transform = 'rotateY(180deg)';
+
+ // 组装硬币
+ coin.appendChild(front);
+ coin.appendChild(back);
+ existingCoinEl.appendChild(coin);
+ coinElement = coin;
+
+ // 初始化硬币眼睛
+ setTimeout(() => {
+ if (typeof window.createAncientGod1Eye === 'function') {
+ window.createAncientGod1Eye('ancient-god-eye1');
+ }
+ if (typeof window.createAncientGod2Eye === 'function') {
+ window.createAncientGod2Eye('ancient-god-eye2');
+ }
+ }, 100);
+ } else {
+ // 如果没有提供容器,则创建完整的硬币容器
+ const coinContainer = document.createElement('div');
+ coinContainer.className = 'ancient-god-coin-container';
+
+ // 创建硬币元素
+ const coin = document.createElement('div');
+ coin.className = 'ancient-god-coin';
+
+ // 创建正面 (紫色)
+ const front = document.createElement('div');
+ front.className = 'ancient-god-coin-face front';
+ front.id = 'ancient-god-eye1';
+
+ // 创建反面 (血色)
+ const back = document.createElement('div');
+ back.className = 'ancient-god-coin-face back';
+ back.id = 'ancient-god-eye2';
+
+ // 组装硬币
+ coin.appendChild(front);
+ coin.appendChild(back);
+ coinContainer.appendChild(coin);
+ coinElement = coin;
+
+ // 初始化硬币眼睛
+ setTimeout(() => {
+ if (typeof window.createAncientGod1Eye === 'function') {
+ window.createAncientGod1Eye('ancient-god-eye1');
+ }
+ if (typeof window.createAncientGod2Eye === 'function') {
+ window.createAncientGod2Eye('ancient-god-eye2');
+ }
+ }, 100);
+
+ // 添加到面板
+ if (panelContainer.firstChild) {
+ panelContainer.insertBefore(coinContainer, panelContainer.firstChild);
+ } else {
+ panelContainer.appendChild(coinContainer);
+ }
+ }
+
+ // 添加硬币点击事件
+ coinElement.addEventListener('click', (e) => {
+ e.stopPropagation();
+ this.flipCoin();
+ });
+
+ return true;
+ },
+ // 应用主题
+ applyTheme() {
+ injectThemeStyles();
+
+ // 总是重新初始化背景,保证背景与硬币状态同步
+ // 移除任何现有的背景层
+ const existingBgs = panelContainer?.querySelectorAll('.theme-svg-background, .theme-new-layer, .theme-old-layer');
+ if (existingBgs) {
+ existingBgs.forEach(bg => bg.remove());
+ }
+
+ // 设置初始状态的背景(与硬币的正面状态对应)
+ setSVGBackground();
+
+ // 添加更新按钮样式的过程
+ setTimeout(() => {
+ // 根据当前主题ID更新按钮样式
+ if (panelContainer) {
+ const buttons = panelContainer.querySelectorAll('.layout-btn');
+ const btnOpacity = parseFloat(panelContainer.dataset.btnOpacity || DEFAULT_OPACITY.buttons);
+
+ buttons.forEach(button => {
+ // 根据当前主题设置按钮背景颜色
+ const bgColor = currentThemeId === THEME_ID.PURPLE
+ ? `rgba(106, 90, 205, ${btnOpacity})` // 秩序主题 - 紫色
+ : `rgba(170, 0, 0, ${btnOpacity})`; // 混沌主题 - 红色
+
+ button.style.backgroundColor = bgColor;
+ });
+ }
+ }, 100);
+
+ return true;
+ },
+
+ // 翻转硬币
+ flipCoin() {
+ flipCoin();
+ return true;
+ },
+
+ // 设置面板透明度
+ setPanelOpacity(opacity) {
+ setPanelOpacity(opacity);
+ return true;
+ },
+
+ // 获取面板透明度
+ getPanelOpacity() {
+ return currentPanelOpacity;
+ },
+
+ // 获取当前主题样式ID
+ getCurrentThemeId() {
+ return currentThemeId;
+ },
+ // 清理主题资源
+ cleanup() {
+ removeStyles('ancient-god-theme-css');
+ removeStyles('ancient-god-transition-css');
+ if (panelContainer) {
+ // 移除所有类型的背景层
+ const backgrounds = panelContainer.querySelectorAll('.theme-svg-background, .theme-old-layer, .theme-new-layer');
+ backgrounds.forEach(bg => bg.remove());
+
+ // 移除可能存在的扩散效果容器
+ const rippleContainer = panelContainer.querySelector('.ancient-god-ripple-container');
+ if (rippleContainer) {
+ rippleContainer.remove();
+ }
+ }
+ return true;
+ }
+};
+
+export default AncientGodTheme;
diff --git a/js/Intelligent Layout/styles/古神之眼/ChaosPanelStyle.js b/js/Intelligent Layout/styles/古神之眼/ChaosPanelStyle.js
new file mode 100644
index 0000000..0cca3d5
--- /dev/null
+++ b/js/Intelligent Layout/styles/古神之眼/ChaosPanelStyle.js
@@ -0,0 +1,70 @@
+/**
+ * ChaosPanelStyle.js - 古神-混沌-面板
+ * 混沌风格面板的SVG背景和CSS样式 - 带有怪奇网络动画效果
+ */
+
+// 混沌主题面板背景SVG - 带丝线连接的电脑屏幕和神秘中心
+export const ChaosPanelSVG = `
+
+`;
\ No newline at end of file
diff --git a/js/Intelligent Layout/styles/古神之眼/OrderPanelStyle.js b/js/Intelligent Layout/styles/古神之眼/OrderPanelStyle.js
new file mode 100644
index 0000000..ea73397
--- /dev/null
+++ b/js/Intelligent Layout/styles/古神之眼/OrderPanelStyle.js
@@ -0,0 +1,262 @@
+/**
+ * OrderPanelStyle.js - 古神-秩序-面板
+ * 秩序风格面板的SVG背景和CSS样式
+ */
+
+// 秩序主题面板背景SVG - 星球有序旋转
+export const OrderPanelSVG = `
+
+`;
\ No newline at end of file