diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ba6acab..d1bc766 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -150,22 +150,22 @@ DOM or live LiteGraph state. the library and Node Assistant use `ui_material_application.js` for the same receipt. - `ui_prompt_composer.js` owns the Prompt Studio Dual-Column Workbench (提示词工坊左右双分栏工作台); provides a clean, professional studio workspace (`.anomalous-prompt-workbench`) stripped of gaudy neon effects, cyberpunk backgrounds, and pulsing glow animations; + features compact vertical spacing (tightened paddings, 28px single-line metadata inputs, and 6px grid gaps) maximizing functional workspace; features responsive docked / sidebar adaptation with dedicated single-column collapse, generous vertical scrolling without arbitrary height clipping, and an inline responsive view switcher (`.anomalous-prompt-view-switcher`: `[🗃️ 词卡库 | 🎛️ 拼装台 | 📑 全部]`) for seamless navigation when docked as a sidebar (`#anomalous-container.anomalous-docked`) or on narrow screens; the Left Column hosts the Ready-to-use Prompt Cards Library (成型提示词库), featuring restrained dark slate card backgrounds (`#24262E`) with category-specific left accent borders (`border-left: 3.5px solid ...`, Base/Style/Subject/Trigger) eliminating color-palette clutter, minimalist empty state container (`.anomalous-source-empty`) with subtle dashed borders, - non-wrapping title layout, a dedicated sub-action bar with one-click canvas node prompt extraction (`🎯 从节点提取`, - reading selected ComfyUI text nodes like `CLIPTextEncode` with downstream link connection traversal for accurate negative conditioning detection, auto-generating categorised cards) and one-click - Material Library batch sync (`📥 从素材库导入`, cancellable fetch scoped to `category=prompts`), plus on-demand persistent card creation - (`➕ 新建词卡`, with explicit positive/negative role radios and `/anomalous/save_prompt_plan` backend persistence; temporary cards + a streamlined col-header holding a unified action button group (`[🎯 提取]`, `[📥 导入]`, `[➕ 新建]`) that reclaims ~36px vertical height for the card list, + one-click canvas node prompt extraction (`🎯 提取`, reading selected ComfyUI text nodes like `CLIPTextEncode` with downstream link connection traversal for accurate negative conditioning detection, auto-generating categorised cards) and one-click + Material Library batch sync (`📥 导入`, cancellable fetch scoped to `category=prompts`), plus on-demand persistent card creation + (`➕ 新建`, with explicit positive/negative role radios and `/anomalous/save_prompt_plan` backend persistence; temporary cards show `[未保存]` badge with one-click `💾 存入库`), completely eliminating default empty textareas; cards support native HTML5 drag-and-drop; the Right Column hosts the expanded Assembler & Arranger Stage (顺序编排调音台, `minmax(460px, 1fr)`), featuring quick node reading - (`🎯 读取选中节点`), compact modular Lego block cards (~56px height, expandable on focus) allowing generous top-to-bottom sequence stacking, - a clean dropzone (`.anomalous-assembly-dropzone`) that ingests dragged cards into positive/negative tracks with strict role isolation, + (`🎯 读取选中节点`), quick final text toggle (`[📄 最终文本]`), compact modular Lego block cards (~56px height, expandable on focus) allowing generous top-to-bottom sequence stacking, + a large, high-clearance dropzone (`.anomalous-assembly-dropzone`, min-height 240px) that ingests dragged cards into positive/negative tracks with strict role isolation, supports bidirectional drag-and-drop reordering with ghost indicator lines, role-aware up/down swapping, instant A/B bypass toggles (greyscale dimming without deleting), interactive category pills, one-click Smart Sort (`🪄 按分类排序`, Base ➔ Style ➔ Subject ➔ Trigger), a target widget dropdown with sticky - selection cache (`targetWidgetIndexByNodeId`), and a sticky floating frosted-glass output deck (`.anomalous-mixer-deck-output`, `position: sticky; bottom: 0; backdrop-filter: blur(12px)`) - with real-time word/token counts, single-mount canvas dragging (`bindMaterialDrag` onto text nodes), direct target node injection toolbar, and seamless return to Material Library; + selection cache (`targetWidgetIndexByNodeId`), and an on-demand collapsible output deck (`.anomalous-mixer-deck-output`): completely hidden when 0 blocks, defaulting to a slim 32px pill summary bar (`✨ 最终正向文本 (X字符 · Y词组) [📋复制] [👁️查看最终文本 ▾]`) when cards exist to prevent premature clutter and screen squashing, and smoothly expandable into a full drawer containing the compiled textarea, whole-deck canvas drag dock (`bindMaterialDrag`), and target node direct write bar; `prompt_composition.js` provides bidirectional schema mapping (`planToWorkbenchDraft` and `workbenchDraftToSavedPlan`) ensuring 100% roundtrip data integrity across `anomalous-prompt-plan-v1` and `version: 2`, lossless legacy dual-role splitting and trailing text retention, `categorizePromptSnippet`, `smartSortPromptBlocks`, `assemblePromptBlocks`, as well as backward-compatible text joining; diff --git a/web/modules/ui_prompt_composer.js b/web/modules/ui_prompt_composer.js index 4b19f7e..ee096a4 100644 --- a/web/modules/ui_prompt_composer.js +++ b/web/modules/ui_prompt_composer.js @@ -267,6 +267,7 @@ function buildPromptComposer(owner, container, options = {}) { let sourceFilterCategory = 'all'; let sourceFilterKeyword = ''; let draggedBlockId = null; + let isOutputExpanded = false; // Local in-memory source prompt cards (merged starters + materials + user custom) let sourceCards = [...STARTER_SOURCE_PROMPTS]; @@ -387,26 +388,26 @@ function buildPromptComposer(owner, container, options = {}) { const leftPanel = text(workbenchGrid, 'section', '', 'anomalous-workbench-left-panel'); const leftHeader = text(leftPanel, 'div', '', 'anomalous-workbench-col-header'); const leftTitleWrap = text(leftHeader, 'div', '', 'anomalous-workbench-col-title'); - leftTitleWrap.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '词卡库' : 'Prompt Library'} ${window.anomalous_browser_lang === 'zh' ? '· 拖拽卡片至右侧' : '· Drag to mixer'}`; + leftTitleWrap.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '词卡库' : 'Prompt Library'} `; - // Button: Create New Custom Card (placed in header right, never wraps) - const newCardTriggerBtn = text(leftHeader, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm'); - newCardTriggerBtn.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '新建词卡' : 'New Card'}`; - newCardTriggerBtn.title = window.anomalous_browser_lang === 'zh' ? '新建并保存一张提示词卡片' : 'Create a new prompt card'; - - // Sub-action bar: Node extract and Material Library import (full-width, balanced) - const leftSubActions = text(leftPanel, 'div', '', 'anomalous-workbench-sub-actions'); + // Action button group in leftHeader + const leftHeaderActions = text(leftHeader, 'div', '', 'anomalous-workbench-header-actions'); // Button 1: Extract Prompts from Selected Canvas Node - const extractNodeBtn = text(leftSubActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm anomalous-btn-extract-node'); - extractNodeBtn.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '从节点提取' : 'From Node'}`; - extractNodeBtn.title = window.anomalous_browser_lang === 'zh' ? '读取 ComfyUI 画布当前选中节点的提示词文本并生成词卡' : 'Extract prompt text from selected canvas node into cards'; + const extractNodeBtn = text(leftHeaderActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm anomalous-btn-extract-node'); + extractNodeBtn.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '提取' : 'Extract'}`; + extractNodeBtn.title = window.anomalous_browser_lang === 'zh' ? '从画布当前选中节点提取提示词并生成词卡' : 'Extract prompt text from selected canvas node into cards'; // Button 2: One-click Sync / Import from Material Library - const importMaterialsBtn = text(leftSubActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm'); - importMaterialsBtn.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '从素材库导入' : 'Import from Library'}`; + const importMaterialsBtn = text(leftHeaderActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm'); + importMaterialsBtn.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '导入' : 'Import'}`; importMaterialsBtn.title = window.anomalous_browser_lang === 'zh' ? '读取素材库,一键把素材库里沉淀的提示词捞入当前工坊词库' : 'Read and import all prompts from Material Library into workbench'; + // Button 3: Create New Custom Card (placed in header right, never wraps) + const newCardTriggerBtn = text(leftHeaderActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm'); + newCardTriggerBtn.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '新建' : 'New'}`; + newCardTriggerBtn.title = window.anomalous_browser_lang === 'zh' ? '新建并保存一张提示词卡片' : 'Create a new prompt card'; + // Search and category filters bar const leftFilterBar = text(leftPanel, 'div', '', 'anomalous-workbench-filter-bar'); const leftSearch = text(leftFilterBar, 'input', '', 'anomalous-workbench-search-input'); @@ -452,6 +453,11 @@ function buildPromptComposer(owner, container, options = {}) { const rightActions = text(rightHeader, 'div', '', 'anomalous-mixer-actions'); + // Right quick view final assembled text + const viewFinalBtn = text(rightActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm anomalous-mixer-final-btn'); + viewFinalBtn.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '最终文本' : 'Final Text'}`; + viewFinalBtn.title = window.anomalous_browser_lang === 'zh' ? '查看/展开最终合成的提示词文本' : 'View / Expand final assembled prompt text'; + // Right quick extract: suck into right mixer directly const rightSuckNodeBtn = text(rightActions, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm'); rightSuckNodeBtn.innerHTML = `${t('promptReadSelectedNode')}`; @@ -468,28 +474,83 @@ function buildPromptComposer(owner, container, options = {}) { // Blocks Container & Dropzone const blocksContainer = text(rightPanel, 'div', '', 'anomalous-mixer-blocks-container anomalous-assembly-track'); - // Bottom Live Assembled Output Deck - const outputDeck = text(rightPanel, 'div', '', 'anomalous-mixer-deck-output'); - const outputHeader = text(outputDeck, 'div', '', 'anomalous-mixer-output-header'); - const outputTitle = text(outputHeader, 'div', '', 'anomalous-mixer-output-title'); - const outputStats = text(outputHeader, 'div', '', 'anomalous-mixer-output-stats'); + // Bottom Live Assembled Output Deck (Collapsible on-demand) + const outputDeck = text(rightPanel, 'div', '', 'anomalous-mixer-deck-output is-collapsed'); - const outputBody = text(outputDeck, 'div', '', 'anomalous-mixer-output-body'); + // 1. Slim Dock Summary Bar (32px high, always visible when blocks exist) + const outputSummary = text(outputDeck, 'div', '', 'anomalous-mixer-output-summary'); + const summaryLeft = text(outputSummary, 'div', '', 'anomalous-mixer-summary-left'); + const outputTitle = text(summaryLeft, 'div', '', 'anomalous-mixer-output-title'); + const outputStats = text(summaryLeft, 'div', '', 'anomalous-mixer-output-stats'); + + const summaryRight = text(outputSummary, 'div', '', 'anomalous-mixer-summary-right'); + const copyOutputBtn = text(summaryRight, 'button', `📋 ${t('copy')}`, 'anomalous-btn-ghost anomalous-btn-sm anomalous-btn-quick-copy'); + copyOutputBtn.title = window.anomalous_browser_lang === 'zh' ? '复制当前合成的提示词' : 'Copy assembled prompt text'; + + const expandOutputBtn = text(summaryRight, 'button', '', 'anomalous-btn-ghost anomalous-btn-sm anomalous-btn-toggle-output'); + expandOutputBtn.title = window.anomalous_browser_lang === 'zh' ? '展开/收起最终文本' : 'Expand/collapse full text'; + + // 2. Expandable Drawer (reveals full textarea, canvas drag dock, and target node write bar) + const outputDrawer = text(outputDeck, 'div', '', 'anomalous-mixer-output-drawer'); + outputDrawer.style.display = 'none'; + + const outputBody = text(outputDrawer, 'div', '', 'anomalous-mixer-output-body'); const outputTextarea = text(outputBody, 'textarea', '', 'anomalous-mixer-output-textarea'); outputTextarea.readOnly = true; outputTextarea.rows = 2; - const outputFooter = text(outputDeck, 'div', '', 'anomalous-mixer-output-footer'); + const outputFooter = text(outputDrawer, 'div', '', 'anomalous-mixer-output-footer'); const dragHint = text(outputFooter, 'div', '', 'anomalous-mixer-drag-hint'); dragHint.innerHTML = `🖐️ ${window.anomalous_browser_lang === 'zh' ? '按住此预览坞' : 'Drag this deck'} ${window.anomalous_browser_lang === 'zh' ? '直接丢入 ComfyUI 画布节点' : 'onto canvas node'}`; - const outputActions = text(outputFooter, 'div', '', 'anomalous-mixer-output-actions'); - const copyOutputBtn = text(outputActions, 'button', `📋 ${t('copy')}`, 'anomalous-btn-ghost'); - - // Target Node Direct Write Bar - const targetBar = text(rightPanel, 'div', '', 'anomalous-prompt-target-bar'); + // Target Node Direct Write Bar placed INSIDE outputDrawer so it collapses cleanly + const targetBar = text(outputDrawer, 'div', '', 'anomalous-prompt-target-bar'); const targetWidgetIndexByNodeId = {}; + function updateOutputDeckState() { + const currentRoleParts = draft?.plan?.parts ? draft.plan.parts.filter(p => (p.role || 'positive') === activeTab) : []; + const compiledText = (draft?.plan?.[activeTab] || '').trim(); + const hasContent = currentRoleParts.length > 0 && compiledText.length > 0; + + if (!hasContent) { + outputDeck.style.display = 'none'; + viewFinalBtn.style.display = 'none'; + return; + } + + outputDeck.style.display = 'flex'; + viewFinalBtn.style.display = 'inline-flex'; + + if (isOutputExpanded) { + outputDeck.classList.remove('is-collapsed'); + outputDrawer.style.display = 'flex'; + expandOutputBtn.innerHTML = window.anomalous_browser_lang === 'zh' ? '收起 ▲' : 'Collapse ▲'; + viewFinalBtn.classList.add('is-active'); + } else { + outputDeck.classList.add('is-collapsed'); + outputDrawer.style.display = 'none'; + expandOutputBtn.innerHTML = window.anomalous_browser_lang === 'zh' ? '查看最终文本 ▾' : 'View Text ▾'; + viewFinalBtn.classList.remove('is-active'); + } + } + + expandOutputBtn.onclick = (e) => { + e.stopPropagation(); + isOutputExpanded = !isOutputExpanded; + updateOutputDeckState(); + }; + + outputSummary.onclick = (e) => { + if (e.target.closest('button')) return; + isOutputExpanded = !isOutputExpanded; + updateOutputDeckState(); + }; + + viewFinalBtn.onclick = () => { + isOutputExpanded = !isOutputExpanded; + updateOutputDeckState(); + }; + // Bind output deck drag once on mount bindMaterialDrag(outputDeck, owner, { payload: () => { @@ -676,7 +737,7 @@ function buildPromptComposer(owner, container, options = {}) { if (materialSyncController === controller) { materialSyncController = null; importMaterialsBtn.disabled = false; - importMaterialsBtn.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '从素材库导入' : 'Import from Library'}`; + importMaterialsBtn.innerHTML = `${window.anomalous_browser_lang === 'zh' ? '导入' : 'Import'}`; } } } @@ -1275,15 +1336,16 @@ function buildPromptComposer(owner, container, options = {}) { const compiledText = draft.plan[activeTab] || ''; outputTitle.innerHTML = activeTab === 'negative' - ? `🚫 ${window.anomalous_browser_lang === 'zh' ? '实时合成负向文本' : 'Assembled Negative'}` - : `✨ ${window.anomalous_browser_lang === 'zh' ? '实时合成正向文本' : 'Assembled Positive'}`; + ? `🚫 ${window.anomalous_browser_lang === 'zh' ? '最终负向文本' : 'Assembled Negative'}` + : `✨ ${window.anomalous_browser_lang === 'zh' ? '最终正向文本' : 'Assembled Positive'}`; const words = compiledText.trim() ? compiledText.split(/[\s,,\n\r]+/).filter(Boolean).length : 0; outputStats.textContent = window.anomalous_browser_lang === 'zh' - ? `${compiledText.length} 字符 · 约 ${words} 个词组` - : `${compiledText.length} chars · ~${words} tags`; + ? `(${compiledText.length} 字符 · 约 ${words} 词组)` + : `(${compiledText.length} chars · ~${words} tags)`; outputTextarea.value = compiledText; + updateOutputDeckState(); } // Smart Sort @@ -1317,8 +1379,9 @@ function buildPromptComposer(owner, container, options = {}) { }; // Copy preview text - copyOutputBtn.onclick = async () => { - const textToCopy = outputTextarea.value; + copyOutputBtn.onclick = async (e) => { + e?.stopPropagation?.(); + const textToCopy = (draft.plan[activeTab] || outputTextarea.value || '').trim(); if (!textToCopy) return; try { await navigator.clipboard.writeText(textToCopy); diff --git a/web/styles.css b/web/styles.css index 0bc93b7..3557030 100644 --- a/web/styles.css +++ b/web/styles.css @@ -9522,8 +9522,8 @@ dialog.anomalous-gallery-viewer::backdrop { .anomalous-prompt-composer { display: flex; flex-direction: column; - gap: 16px; - padding: 16px 20px 24px; + gap: 8px; + padding: 8px 12px 10px; overflow-y: auto; max-height: 100%; color: #e2e8f0; @@ -9534,20 +9534,20 @@ dialog.anomalous-gallery-viewer::backdrop { align-items: center; justify-content: space-between; flex-wrap: wrap; - gap: 12px; - padding-bottom: 12px; + gap: 8px; + padding-bottom: 6px; border-bottom: 1px solid rgba(255, 255, 255, 0.08); } .anomalous-prompt-topbar-left { display: flex; align-items: center; - gap: 12px; + gap: 8px; } .anomalous-prompt-topbar-left h3 { margin: 0; - font-size: 1.05rem; + font-size: 0.98rem; font-weight: 600; color: #f8fafc; letter-spacing: 0.02em; @@ -9556,16 +9556,16 @@ dialog.anomalous-gallery-viewer::backdrop { .anomalous-prompt-topbar-actions { display: flex; align-items: center; - gap: 8px; + gap: 6px; flex-wrap: wrap; } .anomalous-prompt-meta-strip { display: flex; align-items: center; - gap: 10px; - padding: 8px 12px; - margin: 8px 14px 0; + gap: 8px; + padding: 4px 8px; + margin: 2px 10px 0; border-radius: var(--amb-radius-control, 6px); background: var(--amb-bg-panel, #1C1E24); border: 1px solid var(--amb-border, rgba(255, 255, 255, 0.08)); @@ -9577,9 +9577,10 @@ dialog.anomalous-gallery-viewer::backdrop { background: var(--amb-bg-input, #181a22); border: 1px solid var(--amb-border, rgba(255, 255, 255, 0.12)); border-radius: var(--amb-radius-control, 6px); - padding: 6px 12px; + padding: 3px 8px; + height: 28px; color: var(--amb-text-main, #f1f5f9); - font-size: 0.82rem; + font-size: 0.78rem; outline: none; transition: border-color 0.15s ease; box-sizing: border-box; @@ -10299,7 +10300,7 @@ dialog.anomalous-gallery-viewer::backdrop { box-shadow: inset 0 0 0 1px rgba(220, 20, 60, 0.3); } -/* 4. Assembled Output Deck & Whole-Deck Canvas Drag (Sticky Floating Glass Deck) */ +/* 4. Assembled Output Deck & Whole-Deck Canvas Drag (Collapsible Glass Deck) */ .anomalous-mixer-deck-output { position: sticky; bottom: 0; @@ -10310,19 +10311,27 @@ dialog.anomalous-gallery-viewer::backdrop { -webkit-backdrop-filter: blur(12px); border: 1px solid var(--amb-border, rgba(255, 255, 255, 0.12)); box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.35); - padding: 8px 10px; + padding: 6px 10px; display: flex; flex-direction: column; - gap: 6px; + gap: 4px; cursor: grab; user-select: none; - transition: border-color 0.2s ease, box-shadow 0.2s ease; + transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1); flex-shrink: 0 !important; } +.anomalous-mixer-deck-output.is-collapsed { + padding: 3px 8px; + background: rgba(22, 24, 30, 0.92); + border-radius: 6px; + cursor: pointer; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + .anomalous-mixer-deck-output:hover { border-color: #38bdf8; - box-shadow: 0 0 12px rgba(56, 189, 248, 0.25); + box-shadow: 0 0 12px rgba(56, 189, 248, 0.2); } .theme-abyssal-scarlet .anomalous-mixer-deck-output { @@ -10338,24 +10347,53 @@ dialog.anomalous-gallery-viewer::backdrop { cursor: grabbing; } -.anomalous-mixer-output-header { +.anomalous-mixer-output-summary { display: flex; align-items: center; justify-content: space-between; gap: 8px; - border-bottom: 1px solid rgba(255, 255, 255, 0.08); - padding-bottom: 4px; + min-height: 26px; + cursor: pointer; +} + +.anomalous-mixer-summary-left { + display: flex; + align-items: center; + gap: 6px; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.anomalous-mixer-summary-right { + display: flex; + align-items: center; + gap: 5px; + flex-shrink: 0; } .anomalous-mixer-output-title { - font-size: 0.8rem; + font-size: 0.78rem; color: #e2e8f0; + white-space: nowrap; } .anomalous-mixer-output-stats { font-size: 0.7rem; color: #94a3b8; font-family: monospace; + white-space: nowrap; +} + +.anomalous-mixer-output-drawer { + display: flex; + flex-direction: column; + gap: 6px; + border-top: 1px solid rgba(255, 255, 255, 0.08); + padding-top: 6px; + margin-top: 2px; } .anomalous-mixer-output-body { @@ -10374,7 +10412,6 @@ dialog.anomalous-gallery-viewer::backdrop { padding: 4px 8px; outline: none; box-sizing: border-box; - pointer-events: none; /* allows deck drag seamlessly */ min-height: 34px; max-height: 52px; } @@ -10402,10 +10439,22 @@ dialog.anomalous-gallery-viewer::backdrop { color: #dc143c; } -.anomalous-mixer-output-actions { - display: flex; - align-items: center; - gap: 6px; +.anomalous-mixer-final-btn.is-active { + background: var(--amb-bg-card-hover, rgba(255, 255, 255, 0.15)) !important; + border-color: #38bdf8 !important; + color: #38bdf8 !important; +} + +.anomalous-btn-toggle-output { + font-size: 0.72rem !important; + padding: 2px 7px !important; + color: #38bdf8 !important; + border-color: rgba(56, 189, 248, 0.25) !important; +} + +.anomalous-btn-quick-copy { + font-size: 0.72rem !important; + padding: 2px 7px !important; } /* 5. Floating Toast Feedback */ @@ -10466,10 +10515,10 @@ dialog.anomalous-gallery-viewer::backdrop { .anomalous-prompt-workbench-grid { display: grid; grid-template-columns: minmax(280px, 320px) minmax(460px, 1fr); - gap: 14px; + gap: 10px; flex: 1; min-height: 0; - padding: 10px 14px 14px; + padding: 6px 10px 8px; overflow: hidden; box-sizing: border-box; } @@ -10599,9 +10648,9 @@ dialog.anomalous-gallery-viewer::backdrop { background: var(--amb-bg-panel, #1C1E24); border: 1px solid var(--amb-border, rgba(255, 255, 255, 0.08)); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); - padding: 14px 14px 12px; + padding: 10px 10px 8px; box-sizing: border-box; - gap: 10px; + gap: 8px; } .anomalous-workbench-right-panel { @@ -10613,8 +10662,8 @@ dialog.anomalous-gallery-viewer::backdrop { display: flex; align-items: center; justify-content: space-between; - gap: 8px; - padding-bottom: 8px; + gap: 6px; + padding-bottom: 6px; border-bottom: 1px solid rgba(255, 255, 255, 0.08); flex-shrink: 0; } @@ -11005,7 +11054,8 @@ dialog.anomalous-gallery-viewer::backdrop { -------------------------------------------------------------------------- */ .anomalous-assembly-track { flex: 1 1 0 !important; - min-height: 120px; + min-height: 200px; + height: 100%; max-height: none !important; overflow-y: auto; overflow-x: hidden; @@ -11026,7 +11076,8 @@ dialog.anomalous-gallery-viewer::backdrop { .anomalous-assembly-dropzone { flex: 1; - min-height: 160px; + min-height: 240px; + height: 100%; display: flex; flex-direction: column; align-items: center;