diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index a913de3..8152243 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -92,8 +92,8 @@ DOM or live LiteGraph state. browser class and extracted-method wiring; `browser_entry.js` owns the single browser instance plus floating/topbar/menu entry behavior; `entry_controls.js` owns entry mode, trigger sizing/styling normalization, viewport-safe boundary - clamping (`clampFloatingTriggerPosition`), and legacy top-left auto-healing - (`isValidSavedTriggerPosition`); and `interface_settings.js` owns language and theme preferences. + clamping (`clampFloatingTriggerPosition`), and toolbar occlusion auto-healing + (`isValidSavedTriggerPosition`, `normalizeSavedTriggerPosition`); and `interface_settings.js` owns language and theme preferences. - `ui_domain_switcher.js` owns the dual-domain mode toggle (Visual Studio 🎨 vs Audio & Voice Studio 🎙️) embedded in the brand header. - `ui_audio_studio.js` owns the Audio & Voice Studio workspace, presenting character voice cards, emotion slice playback, prompt syntax copying, real-time search, animated equalizers, and canvas drag-and-drop. - `ui_audio_sidebar.js` owns the Audio Sidebar navigation, rendering trees for All Voices, Character Presets (e.g. Arona) with count badges, and Output History Vault. diff --git a/docs/architecture/frontend.md b/docs/architecture/frontend.md index 9832ab2..ed4ca28 100644 --- a/docs/architecture/frontend.md +++ b/docs/architecture/frontend.md @@ -23,12 +23,14 @@ the real ComfyUI runtime creates the configured entry. Floating trigger styling the ordered stylesheet bundle rooted at `web/styles.css` uses dynamic `1em` SVG scaling and flex centering to guarantee consistent visual presentation across all configured trigger sizes. By default, the -floating trigger button is positioned via pure responsive CSS (`bottom: 30px; right: 30px;`) +floating trigger button is positioned via pure responsive CSS (`top: 60px; left: 60px;`), +leaving comfortable 12px margins from both the ComfyUI left sidebar (~48px) and topbar (~48px) without inline left/top overrides. `browser_entry.js` strictly distinguishes clicks from drags (threshold: 5px) to prevent accidental persistence, while `entry_controls.js` provides robust boundary clamping (`clampFloatingTriggerPosition`) with safe viewport -fallbacks (1024x768) and automatic healing of legacy collapsed top-left coordinates -(`isValidSavedTriggerPosition`). Resetting trigger position cleanly restores default CSS positioning. +fallbacks (1024x768) and automatic healing of legacy or accidental toolbar/sidebar +occlusion coordinates (`isValidSavedTriggerPosition`, `normalizeSavedTriggerPosition`). +Resetting trigger position cleanly restores default CSS positioning. Major UI panels live in `web/modules/ui_*.js`. Shared browser state remains on the `AnomalousBrowser` instance. Pure parsing, normalization, comparison, and diff --git a/web/modules/browser_entry.js b/web/modules/browser_entry.js index 76b7efd..3d3b3b3 100644 --- a/web/modules/browser_entry.js +++ b/web/modules/browser_entry.js @@ -2,10 +2,12 @@ import { app } from '../../../scripts/app.js'; import { AnomalousBrowser } from './browser.js'; import { clampFloatingTriggerPosition, + DEFAULT_TRIGGER_POSITION, isValidSavedTriggerPosition, normalizeEntryMode, normalizeFloatingTriggerSize, - normalizeFloatingTriggerStyle + normalizeFloatingTriggerStyle, + normalizeSavedTriggerPosition } from './entry_controls.js'; import { createShortcutSettingControl, @@ -304,8 +306,13 @@ export function createBrowserEntry({ translate, getCurrentLanguage }) { isDragging = false; btn.style.transition = 'transform 0.15s, box-shadow 0.15s'; if (hasMoved) { - localStorage.setItem('anomalous_btn_x', btn.style.left); - localStorage.setItem('anomalous_btn_y', btn.style.top); + const norm = normalizeSavedTriggerPosition(btn.style.left, btn.style.top); + if (norm) { + btn.style.left = norm.x + 'px'; + btn.style.top = norm.y + 'px'; + localStorage.setItem('anomalous_btn_x', btn.style.left); + localStorage.setItem('anomalous_btn_y', btn.style.top); + } } else { open(); } @@ -314,7 +321,8 @@ export function createBrowserEntry({ translate, getCurrentLanguage }) { const updateBtnBounds = () => { const savedX = localStorage.getItem('anomalous_btn_x'); const savedY = localStorage.getItem('anomalous_btn_y'); - if (!isValidSavedTriggerPosition(savedX, savedY)) { + const norm = normalizeSavedTriggerPosition(savedX, savedY); + if (!norm) { btn.style.left = ''; btn.style.top = ''; btn.style.right = ''; @@ -322,8 +330,8 @@ export function createBrowserEntry({ translate, getCurrentLanguage }) { return; } const position = clampFloatingTriggerPosition({ - x: btn.style.left || savedX, - y: btn.style.top || savedY, + x: btn.style.left || (norm.x + 'px'), + y: btn.style.top || (norm.y + 'px'), width: btn.offsetWidth, height: btn.offsetHeight, viewportWidth: window.innerWidth, @@ -338,11 +346,14 @@ export function createBrowserEntry({ translate, getCurrentLanguage }) { const initialSavedX = localStorage.getItem('anomalous_btn_x'); const initialSavedY = localStorage.getItem('anomalous_btn_y'); - if (isValidSavedTriggerPosition(initialSavedX, initialSavedY)) { + const initialNorm = normalizeSavedTriggerPosition(initialSavedX, initialSavedY); + if (initialNorm) { btn.style.right = 'auto'; btn.style.bottom = 'auto'; - btn.style.left = initialSavedX; - btn.style.top = initialSavedY; + btn.style.left = initialNorm.x + 'px'; + btn.style.top = initialNorm.y + 'px'; + localStorage.setItem('anomalous_btn_x', btn.style.left); + localStorage.setItem('anomalous_btn_y', btn.style.top); setTimeout(updateBtnBounds, 200); } else { localStorage.removeItem('anomalous_btn_x'); @@ -355,7 +366,7 @@ export function createBrowserEntry({ translate, getCurrentLanguage }) { window.addEventListener('resize', () => { const curX = localStorage.getItem('anomalous_btn_x'); const curY = localStorage.getItem('anomalous_btn_y'); - if (isValidSavedTriggerPosition(curX, curY)) { + if (normalizeSavedTriggerPosition(curX, curY)) { updateBtnBounds(); } syncVisibility(); diff --git a/web/modules/entry_controls.js b/web/modules/entry_controls.js index 37e801a..e9200a5 100644 --- a/web/modules/entry_controls.js +++ b/web/modules/entry_controls.js @@ -14,6 +14,8 @@ export function normalizeEntryMode(value) { return ENTRY_MODES.has(value) ? value : 'floating'; } +export const DEFAULT_TRIGGER_POSITION = Object.freeze({ x: 60, y: 60 }); + export function isValidSavedTriggerPosition(x, y) { if (x == null || y == null) return false; const px = Number.parseFloat(x); @@ -24,6 +26,23 @@ export function isValidSavedTriggerPosition(x, y) { return true; } +export function normalizeSavedTriggerPosition(x, y) { + if (x == null || y == null) return null; + let px = Number.parseFloat(x); + let py = Number.parseFloat(y); + if (!Number.isFinite(px) || !Number.isFinite(py)) return null; + if (px < 10 && py < 10) return null; + // Auto-adjust away from left sidebar occlusion zone (sidebar width is ~48px) + if (px < 56 && py < 300) { + px = DEFAULT_TRIGGER_POSITION.x; + } + // Auto-adjust away from topbar occlusion zone (topbar height is ~48px) + if (py < 52 && px < 400) { + py = DEFAULT_TRIGGER_POSITION.y; + } + return { x: px, y: py }; +} + export function clampFloatingTriggerPosition({ x, y, @@ -39,8 +58,8 @@ export function clampFloatingTriggerPosition({ const safeVh = Math.max(safeHeight + margin, Number(viewportHeight) || (typeof window !== 'undefined' ? window.innerHeight : 768) || 768); const maxX = Math.max(0, safeVw - safeWidth); const maxY = Math.max(0, safeVh - safeHeight); - const fallbackX = Math.max(0, maxX - margin); - const fallbackY = Math.max(0, maxY - margin); + const fallbackX = DEFAULT_TRIGGER_POSITION.x; + const fallbackY = DEFAULT_TRIGGER_POSITION.y; const parsedX = Number.parseFloat(x); const parsedY = Number.parseFloat(y); diff --git a/web/styles/00-foundation-models.css b/web/styles/00-foundation-models.css index 9bf6dfd..7ccf1bc 100644 --- a/web/styles/00-foundation-models.css +++ b/web/styles/00-foundation-models.css @@ -77,8 +77,8 @@ html.theme-abyssal-scarlet, --anomalous-trigger-font-size: 28px; --anomalous-trigger-pill-padding: 18px; position: fixed; - bottom: 30px; - right: 30px; + top: 60px; + left: 60px; width: var(--anomalous-trigger-size); height: var(--anomalous-trigger-size); border-radius: 50%;