From 1fde428db148c2d353e91c41703380418b694653 Mon Sep 17 00:00:00 2001 From: DemonGatanjieu <1027066412@qq.com> Date: Thu, 3 Sep 2026 20:27:44 +0800 Subject: [PATCH] feat(recipe-studio): overhaul recipe detail to modern studio layout with plain language and zero-overhead performance --- web/modules/locales.js | 54 +++--- web/modules/ui_recipe_detail.js | 238 +++++++++++++++-------- web/styles.css | 332 +++++++++++++++++++++++++++++++- 3 files changed, 507 insertions(+), 117 deletions(-) diff --git a/web/modules/locales.js b/web/modules/locales.js index c82d756..82e64ff 100644 --- a/web/modules/locales.js +++ b/web/modules/locales.js @@ -500,10 +500,10 @@ export const i18n = { recipeOpenCanvas: '新画布打开', recipeOpenError: '无法在新画布中打开此配方。', recipeScope: '配方类型', - recipeScopePartial: '局部配方', - recipeScopeComplete: '整体配方', - recipeScopePartialSaveHint: '检测到未闭合的必需连接或缺少输出节点,将保存为局部配方;使用时默认追加到当前画布。', - recipeScopeCompleteSaveHint: '检测到可独立运行的闭合工作流,将保存为整体配方;使用时会在新画布中打开。', + recipeScopePartial: '功能模块', + recipeScopeComplete: '完整工作流', + recipeScopePartialSaveHint: '检测到未闭合的节点链,将保存为可追加到当前画布的功能模块。', + recipeScopeCompleteSaveHint: '检测到完整可独立运行的工作流,将保存为完整工作流;支持在新画布中独立打开。', recipeDelete: '删除', recipeDeleteConfirm: '确定删除这个工作流配方吗?', recipeDeleteError: '删除工作流配方失败。', @@ -550,7 +550,7 @@ export const i18n = { recipeUpdateError: '更新工作流配方失败。', recipeViewDetails: '查看详情', recipeDetailBack: '返回配方列表', - recipeDetailOverview: '概览', + recipeDetailOverview: '配方概览', recipeGallery: '图库', recipeGalleryLoading: '正在查找匹配的历史出图…', recipeGalleryEmpty: '未在最近 200 张含工作流数据的 PNG 中找到与此配方结构匹配的出图。', @@ -569,30 +569,30 @@ export const i18n = { recipeGalleryDifferenceHint: '以下是节点参数的差异;图库匹配本身只关注节点类型。', recipeGalleryRecipeValue: '配方', recipeGalleryImageValue: '图片', - recipeDetailModels: '模型与复现', - recipeDetailParameters: '参数笔记本', - recipeDetailParametersHint: '这里展示配方保存的完整参数;长文本可展开并复制。参数图库只匹配相同参数,不会因为本地路径不同而失效。', - recipeParameterSnapshots: '参数快照', - recipeParameterSnapshotsHint: '每次保存配方都会留下一个只读版本。', - recipeParameterShowing: '当前显示的参数笔记', - recipeParameterCurrentRecipe: '当前配方参数', - recipeParameterActive: '当前', - recipeParameterRefresh: '刷新快照', - recipeParameterLoading: '正在读取参数快照…', - recipeParameterLoadError: '无法读取参数快照。', - recipeParameterNoSnapshots: '这个配方还没有参数快照。', - recipeParameterUntitled: '未命名参数快照', - recipeParameterDelete: '删除参数笔记', - recipeParameterDeleteConfirm: '确定删除参数笔记“{name}”吗?此操作无法撤销。', - recipeParameterDeleteError: '无法删除参数笔记,请稍后重试。', - recipeParameterNew: '新建参数笔记', - recipeParameterReadCurrent: '读取当前并新建', - recipeParameterReadCurrentSaving: '正在读取并保存…', + recipeDetailModels: '模型装备清单', + recipeDetailParameters: '调参预设', + recipeDetailParametersHint: '这里展示此配方保存的全部生成参数。可直接一键应用到当前画布,或从当前画布保存新预设。', + recipeParameterSnapshots: '已存调参预设', + recipeParameterSnapshotsHint: '同一个工作流可保存多组不同的调参预设,方便随时切换。', + recipeParameterShowing: '当前选中的预设', + recipeParameterCurrentRecipe: '默认配方参数', + recipeParameterActive: '当前生效', + recipeParameterRefresh: '刷新预设', + recipeParameterLoading: '正在读取预设…', + recipeParameterLoadError: '无法读取预设列表。', + recipeParameterNoSnapshots: '当前配方还没有额外保存的调参预设。', + recipeParameterUntitled: '未命名调参预设', + recipeParameterDelete: '删除此预设', + recipeParameterDeleteConfirm: '确定删除调参预设“{name}”吗?此操作无法撤销。', + recipeParameterDeleteError: '无法删除预设,请稍后重试。', + recipeParameterNew: '新建预设', + recipeParameterReadCurrent: '从当前画布保存新预设', + recipeParameterReadCurrentSaving: '正在保存当前画布参数…', recipeParameterReadCurrentError: '无法读取当前画布参数。', - recipeParameterSkeletonMismatch: '当前画布缺少配方骨架节点,未执行任何修改。', - recipeParameterWidgetMismatch: '当前画布的节点控件与参数笔记不一致:', + recipeParameterSkeletonMismatch: '当前画布缺少配方必需的核心节点,无法直接应用参数。', + recipeParameterWidgetMismatch: '当前画布的节点输入项与预设不一致:', recipeParameterNodeUnavailable: '无法在当前画布中定位匹配节点:', - recipeParameterApply: '应用到当前工作流', + recipeParameterApply: '应用到当前画布', recipeParameterApplying: '正在检查骨架并应用…', recipeParameterApplied: '已应用 {count} 项参数', recipeParameterApplyError: '应用参数失败,当前画布未完成修改。', diff --git a/web/modules/ui_recipe_detail.js b/web/modules/ui_recipe_detail.js index 6be4aaf..a757ca5 100644 --- a/web/modules/ui_recipe_detail.js +++ b/web/modules/ui_recipe_detail.js @@ -948,90 +948,174 @@ function renderReadinessBanner(parent, owner, recipe, references, finish, onRere parent.appendChild(banner); } +function renderPromptOverviewSection(parent, recipe) { + const prompts = promptValues(recipe, recipe); + if (!prompts?.entries?.length) return; + + const wrap = document.createElement('div'); + wrap.style.display = 'grid'; + wrap.style.gap = '10px'; + wrap.style.marginBottom = '16px'; + + for (const entry of prompts.entries) { + if (!entry?.value || typeof entry.value !== 'string' || !entry.value.trim()) continue; + const isNegative = entry.role === 'negative'; + const box = document.createElement('div'); + box.className = `anomalous-recipe-prompt-box ${isNegative ? 'is-negative' : 'is-positive'}`; + + const header = document.createElement('div'); + header.style.display = 'flex'; + header.style.justifyContent = 'space-between'; + header.style.alignItems = 'center'; + header.style.marginBottom = '6px'; + + const label = appendText(header, 'strong', isNegative ? '🔴 负向提示词 (Negative Prompt)' : '🟢 正向提示词 (Positive Prompt)'); + label.style.fontSize = '0.78rem'; + + const copyBtn = button(header, '📋 复制', 'anomalous-recipe-prompt-copy-btn'); + copyBtn.onclick = () => { + navigator.clipboard.writeText(entry.value).then(() => { + copyBtn.textContent = '✅ 已复制'; + setTimeout(() => { copyBtn.textContent = '📋 复制'; }, 1500); + }); + }; + + box.appendChild(header); + const text = appendText(box, 'div', entry.value); + text.style.whiteSpace = 'pre-wrap'; + text.style.fontFamily = 'monospace'; + text.style.fontSize = '0.82rem'; + + wrap.appendChild(box); + } + + if (wrap.childElementCount) { + parent.appendChild(wrap); + } +} + function renderOverview(content, owner, recipe, references, finish) { const overview = document.createElement('div'); overview.className = 'anomalous-recipe-detail-overview'; const hero = document.createElement('div'); hero.className = 'anomalous-recipe-detail-hero'; - appendRecipeCover(hero, owner, recipe); + + // Left media wrap + const mediaWrap = document.createElement('div'); + mediaWrap.className = 'anomalous-recipe-detail-hero-media'; + appendRecipeCover(mediaWrap, owner, recipe); + hero.appendChild(mediaWrap); + + // Right control deck const copy = document.createElement('div'); copy.className = 'anomalous-recipe-detail-hero-copy'; - const title = document.createElement('div'); - title.className = 'anomalous-recipe-inline-title-row'; - renderInlineTitle(title, owner, recipe); - copy.appendChild(title); - const notes = document.createElement('div'); - notes.className = 'anomalous-recipe-detail-notes'; - renderInlineNotes(notes, owner, recipe); - copy.appendChild(notes); - const tags = document.createElement('div'); - tags.className = 'anomalous-recipe-tags anomalous-recipe-detail-tags'; - renderInlineTags(tags, owner, recipe); - copy.appendChild(tags); - appendText(copy, 'small', `${t('recipeDetailUpdated')}: ${dateText(recipe.updated_timestamp || recipe.timestamp)}`, 'anomalous-recipe-detail-muted'); - const overviewActions = document.createElement('div'); - overviewActions.className = 'anomalous-recipe-actions-primary'; - overviewActions.style.marginTop = '12px'; - const matchRecipe = button(overviewActions, '🔎 ' + t('recipeMatchRecipeModels'), 'anomalous-btn-ghost'); - matchRecipe.title = t('recipeMatchRecipeModelsDesc'); - const matchRecipeStatus = appendText(overviewActions, 'small', '', 'anomalous-recipe-header-status'); - matchRecipeStatus.setAttribute('aria-live', 'polite'); - matchRecipe.onclick = async () => { - matchRecipe.disabled = true; - matchRecipe.classList.add('is-busy'); - matchRecipe.textContent = t('recipeMatchingRecipeModels'); - try { - const result = await matchRecipeModels(owner, references, matchRecipeStatus, () => { - if (owner.recipeDetailActiveTab === 'overview') selectTab('overview'); - }); - matchRecipeStatus.textContent = result.total - ? t('recipeRecipeMatchSummary').replace('{found}', String(result.found)).replace('{total}', String(result.total)) - : t('recipeAllModelsMatched'); - } catch (error) { - console.error('Could not match recipe models locally:', error); - matchRecipeStatus.textContent = t('recipeLocalModelMatchError'); - } finally { - matchRecipe.disabled = false; - matchRecipe.classList.remove('is-busy'); - matchRecipe.textContent = '🔎 ' + t('recipeMatchRecipeModels'); - } - }; + // Title + Scope Capsule + const titleRow = document.createElement('div'); + titleRow.className = 'anomalous-recipe-inline-title-row'; + titleRow.style.display = 'flex'; + titleRow.style.alignItems = 'center'; + titleRow.style.gap = '8px'; + titleRow.style.flexWrap = 'wrap'; + renderInlineTitle(titleRow, owner, recipe); - const heroAppend = button( - overviewActions, - `${recipe?.workflow_scope === 'partial' ? '🧩' : '🚀'} ${recipeCanvasActionLabel(recipe)}`, - 'anomalous-recipe-btn-primary-action', - ); - heroAppend.onclick = () => { - void runRecipeAction(heroAppend, async () => { - if (await applyRecipeToCanvas(owner, recipe)) finish('canvas'); - }); - }; - - const secondaryActions = document.createElement('div'); - secondaryActions.className = 'anomalous-recipe-actions-secondary'; - const heroEdit = button(secondaryActions, '✏️', 'anomalous-btn-icon anomalous-btn-edit'); - heroEdit.title = t('recipeEdit'); - heroEdit.onclick = () => finish('edit'); - overviewActions.appendChild(secondaryActions); - copy.appendChild(overviewActions); - - hero.appendChild(copy); - overview.appendChild(hero); + const scopePill = document.createElement('span'); + scopePill.className = 'anomalous-recipe-scope-pill'; + scopePill.textContent = recipe.workflow_scope === 'partial' ? t('recipeScopePartial') : t('recipeScopeComplete'); + titleRow.appendChild(scopePill); + copy.appendChild(titleRow); - - // Intuitive environment readiness banner replacing technical identity stats - renderReadinessBanner(overview, owner, recipe, references, finish, () => { + // Readiness status banner inside Hero + renderReadinessBanner(copy, owner, recipe, references, finish, () => { if (owner.recipeDetailActiveTab === 'overview') { content.replaceChildren(); renderOverview(content, owner, recipe, references, finish); } }); + // Primary action bar + const overviewActions = document.createElement('div'); + overviewActions.className = 'anomalous-recipe-actions-primary'; + overviewActions.style.margin = '8px 0'; + overviewActions.style.display = 'flex'; + overviewActions.style.alignItems = 'center'; + overviewActions.style.gap = '8px'; + + const heroAppend = button( + overviewActions, + `${recipe?.workflow_scope === 'partial' ? '🧩' : '🚀'} ${recipeCanvasActionLabel(recipe)}`, + 'anomalous-recipe-btn-primary-action', + ); + heroAppend.style.padding = '8px 16px'; + heroAppend.style.fontSize = '0.88rem'; + heroAppend.onclick = () => { + void runRecipeAction(heroAppend, async () => { + if (await applyRecipeToCanvas(owner, recipe)) finish('canvas'); + }); + }; + + const heroEdit = button(overviewActions, '✏️ ' + t('recipeEdit'), 'anomalous-btn-ghost'); + heroEdit.title = t('recipeEdit'); + heroEdit.onclick = () => finish('edit'); + + const heroExport = button(overviewActions, '📥 ' + t('recipeExport'), 'anomalous-btn-ghost'); + heroExport.title = t('recipeExport'); + heroExport.onclick = async () => { + try { + await exportRecipePackage(owner.recipeDetailFilename, recipe); + } catch (error) { + console.error('Could not export recipe package:', error); + await anomalousAlert(t('recipeExportError')); + } + }; + + copy.appendChild(overviewActions); + + // Studio Metrics Grid + const params = recipe?.params || {}; + const resDisplay = formatRecipeResolution(params.resolution); + if (params.steps || params.cfg || params.sampler_name || resDisplay) { + const metrics = document.createElement('div'); + metrics.className = 'anomalous-recipe-studio-metrics'; + const addTile = (labelKey, val) => { + if (val === undefined || val === null || val === '') return; + const tile = document.createElement('div'); + tile.className = 'anomalous-recipe-studio-metric-tile'; + appendText(tile, 'span', t(labelKey), 'anomalous-recipe-studio-metric-label'); + appendText(tile, 'span', String(val), 'anomalous-recipe-studio-metric-val'); + metrics.appendChild(tile); + }; + addTile('recipeDetailSteps', params.steps); + addTile('recipeDetailCFG', params.cfg); + addTile('recipeDetailSampler', params.sampler_name || params.samplers); + addTile('recipeDetailResolution', resDisplay); + copy.appendChild(metrics); + } + + // Notes & Tags + const notes = document.createElement('div'); + notes.className = 'anomalous-recipe-detail-notes'; + renderInlineNotes(notes, owner, recipe); + copy.appendChild(notes); + + const tags = document.createElement('div'); + tags.className = 'anomalous-recipe-tags anomalous-recipe-detail-tags'; + renderInlineTags(tags, owner, recipe); + copy.appendChild(tags); + + appendText(copy, 'small', `${t('recipeDetailUpdated')}: ${dateText(recipe.updated_timestamp || recipe.timestamp)}`, 'anomalous-recipe-detail-muted'); + + hero.appendChild(copy); + overview.appendChild(hero); + + // Prompt Showcase Section + renderPromptOverviewSection(overview, recipe); + + // Technical details const advanced = document.createElement('details'); advanced.className = 'anomalous-recipe-advanced-info'; - appendText(advanced, 'summary', t('recipeAdvancedInfo')); + advanced.style.marginBottom = '14px'; + appendText(advanced, 'summary', `⚙️ ${t('recipeAdvancedInfo')}`); const fingerprint = document.createElement('div'); fingerprint.className = 'anomalous-recipe-advanced-row'; appendText(fingerprint, 'span', `${t('recipeDetailFingerprint')}:`); @@ -1040,29 +1124,16 @@ function renderOverview(content, owner, recipe, references, finish) { advanced.appendChild(fingerprint); overview.appendChild(advanced); + // Model Equipment Summary const summary = document.createElement('section'); summary.className = 'anomalous-recipe-detail-section'; appendText(summary, 'h4', t('recipeDetailSummary')); - const summaryGrid = document.createElement('div'); - summaryGrid.className = 'anomalous-recipe-detail-summary-grid'; - const params = recipe.params || {}; const modelComposition = document.createElement('div'); modelComposition.className = 'anomalous-recipe-model-composition'; summary.appendChild(modelComposition); renderModelComposition(modelComposition, owner, recipe, references, finish, params); overview.appendChild(summary); - const actions = document.createElement('div'); - actions.className = 'anomalous-recipe-actions anomalous-recipe-detail-actions'; - const edit = button(actions, t('recipeEdit'), 'anomalous-btn-success'); - edit.onclick = () => finish('edit'); - const append = button(actions, recipeCanvasActionLabel(recipe), 'anomalous-btn-primary'); - append.onclick = () => { - void runRecipeAction(append, async () => { - if (await applyRecipeToCanvas(owner, recipe)) finish('canvas'); - }); - }; - overview.appendChild(actions); content.appendChild(overview); } @@ -2641,7 +2712,10 @@ export function showRecipeDetail(owner, { recipe, filename, history = [] }) { owner.recipeDetailPayload = { recipe, filename, history }; owner.recipeDetailFilename = filename; owner.recipeListContainer.style.display = 'none'; - owner.recipeView.querySelector('.anomalous-recipe-actionbar').style.display = 'none'; + const actionbar = owner.recipeView?.querySelector('.anomalous-recipe-actionbar'); + if (actionbar) actionbar.style.display = 'none'; + const betaNotice = owner.recipeView?.querySelector('.anomalous-recipe-beta-notice'); + if (betaNotice) betaNotice.style.display = 'none'; if (owner.recipeDetailView) owner.recipeDetailView.remove(); const view = document.createElement('div'); @@ -2659,8 +2733,8 @@ export function showRecipeDetail(owner, { recipe, filename, history = [] }) { owner.recipeDetailView = null; if (!['canvas', 'append', 'model'].includes(mode)) { owner.recipeListContainer.style.display = ''; - const actionbar = owner.recipeView?.querySelector('.anomalous-recipe-actionbar'); if (actionbar) actionbar.style.display = ''; + if (betaNotice) betaNotice.style.display = ''; } if (owner.recipeDetailFinish === finish) owner.recipeDetailFinish = null; if (mode !== 'model') delete owner.recipeDetailPayload; diff --git a/web/styles.css b/web/styles.css index 355e8a2..f9123e9 100644 --- a/web/styles.css +++ b/web/styles.css @@ -3901,12 +3901,328 @@ dialog.anomalous-gallery-viewer::backdrop { background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); color: #cbd5e1; - font-size: 0.82rem; - cursor: pointer; - transition: all 0.2s; -} - -.anomalous-recipe-lazy-expand-btn:hover { - background: rgba(255, 255, 255, 0.1); - color: #fff; + gap: 4px; + padding: 2px 8px; + border-radius: 999px; + font-size: 0.7rem; + font-weight: 600; + white-space: nowrap; + border: 1px solid transparent; +} + +.anomalous-recipe-readiness-pill.is-ready { + background: rgba(16, 185, 129, 0.16); + border-color: rgba(16, 185, 129, 0.45); + color: #6ee7b7; +} + +.anomalous-recipe-readiness-pill.is-warning { + background: rgba(245, 158, 11, 0.16); + border-color: rgba(245, 158, 11, 0.45); + color: #fcd34d; +} + +.anomalous-recipe-readiness-pill.is-missing { + background: rgba(239, 68, 68, 0.16); + border-color: rgba(239, 68, 68, 0.45); + color: #fca5a5; +} + +.anomalous-recipe-card-footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-top: auto; + padding-top: 8px; + border-top: 1px solid rgba(255, 255, 255, 0.06); +} + +.anomalous-recipe-btn-primary-action { + flex: 1; + padding: 8px 12px; + border-radius: 8px; + background: linear-gradient(135deg, rgba(99, 102, 241, 0.85), rgba(79, 70, 229, 0.95)); + border: 1px solid rgba(129, 140, 248, 0.4); + color: #ffffff; + font-size: 0.82rem; + font-weight: 500; + cursor: pointer; + text-align: center; + box-shadow: 0 2px 8px rgba(99, 102, 241, 0.25); + transition: all 0.2s ease; +} + +.anomalous-recipe-btn-primary-action:hover { + filter: brightness(1.15); + box-shadow: 0 4px 14px rgba(99, 102, 241, 0.45); + transform: translateY(-1px); +} + +.anomalous-recipe-card-mini-actions { + display: flex; + align-items: center; + gap: 4px; +} + +.anomalous-recipe-card-mini-btn { + padding: 6px 8px; + border-radius: 6px; + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.08); + color: #94a3b8; + cursor: pointer; + font-size: 0.85rem; + line-height: 1; + transition: all 0.15s ease; +} + +.anomalous-recipe-card-mini-btn:hover { + background: rgba(255, 255, 255, 0.12); + color: #fff; + transform: translateY(-1px); +} + +/* Detail View: Readiness Banner */ +.anomalous-recipe-readiness-banner { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 12px 16px; + border-radius: 10px; + margin-bottom: 12px; + font-size: 0.88rem; + backdrop-filter: blur(10px); +} + +.anomalous-recipe-readiness-banner.is-ready { + background: linear-gradient(135deg, rgba(16, 185, 129, 0.15), rgba(5, 150, 105, 0.08)); + border: 1px solid rgba(16, 185, 129, 0.35); + color: #a7f3d0; +} + +.anomalous-recipe-readiness-banner.is-warning { + background: linear-gradient(135deg, rgba(245, 158, 11, 0.18), rgba(217, 119, 6, 0.08)); + border: 1px solid rgba(245, 158, 11, 0.4); + color: #fde68a; +} + +.anomalous-recipe-readiness-banner.is-missing { + background: linear-gradient(135deg, rgba(239, 68, 68, 0.18), rgba(185, 28, 28, 0.08)); + border: 1px solid rgba(239, 68, 68, 0.4); + color: #fecaca; +} + +.anomalous-recipe-banner-btn { + padding: 6px 14px; + border-radius: 7px; + font-size: 0.82rem; + font-weight: 600; + cursor: pointer; + border: none; + transition: all 0.2s ease; + white-space: nowrap; +} + +.anomalous-recipe-banner-btn.is-match-all { + background: #f59e0b; + color: #1a1a1a; + box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3); +} + +.anomalous-recipe-banner-btn.is-match-all:hover { + filter: brightness(1.1); + transform: translateY(-1px); +} + +/* Metric Cards for Sampling Parameters */ +.anomalous-recipe-metric-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); + gap: 10px; + margin-bottom: 14px; +} + +.anomalous-recipe-metric-card { + display: flex; + flex-direction: column; + gap: 4px; + padding: 10px 12px; + border-radius: 8px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.07); +} + +.anomalous-recipe-metric-label { + font-size: 0.74rem; + color: #94a3b8; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.anomalous-recipe-metric-value { + font-size: 1.1rem; + font-weight: 600; + color: #f8fafc; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Lazy Loaded Raw Nodes Container */ +.anomalous-recipe-raw-nodes-summary { + padding: 10px 14px; + border-radius: 8px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.08); + color: #93c5fd; + cursor: pointer; + user-select: none; + font-size: 0.85rem; + font-weight: 500; + transition: background 0.2s; +} + +.anomalous-recipe-raw-nodes-summary:hover { + background: rgba(255, 255, 255, 0.06); +} + +.anomalous-recipe-lazy-expand-btn { + width: 100%; + margin-top: 10px; + padding: 9px; + border-radius: 8px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + color: #cbd5e1; + font-size: 0.82rem; + cursor: pointer; + transition: all 0.2s; +} + +.anomalous-recipe-lazy-expand-btn:hover { + background: rgba(255, 255, 255, 0.1); + color: #fff; +} + +/* Studio Layout Overhaul */ +.anomalous-recipe-detail-overview .anomalous-recipe-detail-hero { + display: grid !important; + grid-template-columns: 260px minmax(0, 1fr) !important; + gap: 20px !important; + align-items: start !important; + padding: 16px !important; + border-radius: 14px !important; + background: linear-gradient(145deg, rgba(30, 32, 38, 0.7), rgba(18, 19, 23, 0.85)) !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + margin-bottom: 16px !important; + backdrop-filter: blur(12px); +} + +.anomalous-recipe-detail-hero-media { + position: relative; + width: 100%; + height: 220px; + border-radius: 10px; + overflow: hidden; + background: #0d0e11; + border: 1px solid rgba(255, 255, 255, 0.06); + display: flex; + align-items: center; + justify-content: center; +} + +.anomalous-recipe-detail-hero-media img, +.anomalous-recipe-detail-hero-media video { + width: 100% !important; + height: 100% !important; + max-height: none !important; + object-fit: cover !important; + border-radius: 9px; + cursor: pointer; + transition: transform 0.25s ease; +} + +.anomalous-recipe-detail-hero-media:hover img { + transform: scale(1.02); +} + +.anomalous-recipe-studio-metrics { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); + gap: 8px; + margin: 12px 0 8px 0; +} + +.anomalous-recipe-studio-metric-tile { + display: flex; + flex-direction: column; + gap: 2px; + padding: 8px 10px; + border-radius: 8px; + background: rgba(255, 255, 255, 0.035); + border: 1px solid rgba(255, 255, 255, 0.06); +} + +.anomalous-recipe-studio-metric-label { + font-size: 0.72rem; + color: #94a3b8; + text-transform: uppercase; + letter-spacing: 0.3px; +} + +.anomalous-recipe-studio-metric-val { + font-size: 0.96rem; + font-weight: 600; + color: #f1f5f9; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Prompt Showcase Boxes */ +.anomalous-recipe-prompt-box { + position: relative; + padding: 12px 14px; + border-radius: 10px; + margin-bottom: 10px; + font-size: 0.84rem; + line-height: 1.55; + word-break: break-word; +} + +.anomalous-recipe-prompt-box.is-positive { + background: rgba(16, 185, 129, 0.06); + border: 1px solid rgba(16, 185, 129, 0.22); + color: #ecfdf5; +} + +.anomalous-recipe-prompt-box.is-negative { + background: rgba(244, 63, 94, 0.06); + border: 1px solid rgba(244, 63, 94, 0.22); + color: #fff1f2; +} + +.anomalous-recipe-prompt-copy-btn { + position: absolute; + top: 8px; + right: 8px; + padding: 3px 8px; + border-radius: 6px; + background: rgba(255, 255, 255, 0.07); + border: 1px solid rgba(255, 255, 255, 0.12); + color: #cbd5e1; + font-size: 0.72rem; + cursor: pointer; + transition: all 0.15s; +} + +.anomalous-recipe-prompt-copy-btn:hover { + background: rgba(255, 255, 255, 0.18); + color: #fff; +} + +.anomalous-recipe-beta-notice[style*="display: none"] { + display: none !important; }