feat(recipe-studio): overhaul recipe detail to modern studio layout with plain language and zero-overhead performance

This commit is contained in:
DemonGatanjieu
2026-09-03 20:27:44 +08:00
parent 81463e70a8
commit 1fde428db1
3 changed files with 507 additions and 117 deletions
+27 -27
View File
@@ -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: '应用参数失败,当前画布未完成修改。',
+156 -82
View File
@@ -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;
+324 -8
View File
@@ -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;
}