feat(ui): 优化模型选择器交互、插件页分类留白、更新日志与暗黑模式适配

This commit is contained in:
✨await
2026-09-17 10:53:31 +08:00
parent d7388fa9a9
commit a208e0b89c
9 changed files with 408 additions and 135 deletions

View File

@@ -8,6 +8,23 @@
z-index: calc(var(--z-modal-overlay) + 1000) !important;
}
.agent-model-picker-popover .canvas-model-picker-menu {
width: min(340px, calc(100vw - 24px)) !important;
max-width: min(340px, calc(100vw - 24px)) !important;
}
.agent-model-picker-popover .canvas-model-picker-two-pane {
grid-template-columns: minmax(0, 1fr) !important;
}
.agent-model-picker-popover .canvas-model-picker-brand-rail {
display: none !important;
}
.agent-model-picker-popover .canvas-model-picker-model-pane {
min-width: 0 !important;
}
/* Flex 子页必须允许收缩,长模型名和 Skill 文案不能决定窗口宽度。 */
.canvas-agent-panel > div,
.canvas-agent-settings,

View File

@@ -11,7 +11,6 @@ export function AppChangelogDialog({ open, onClose }: { open: boolean; onClose:
return (
<AppModal
flush
rootClassName="app-spatial-modal app-changelog-modal"
title={
<div className="app-changelog-heading">
@@ -30,6 +29,7 @@ export function AppChangelogDialog({ open, onClose }: { open: boolean; onClose:
footer={null}
centered
onCancel={onClose}
styles={{ body: { padding: 0 } }}
modalRender={(node) => (
<motion.div initial={reducedMotion ? false : { opacity: 0, y: 14, scale: 0.975 }} animate={{ opacity: 1, y: 0, scale: 1 }} transition={{ duration: aceternityMotion.duration.panel, ease: aceternityMotion.easing.enter }}>
{node}

View File

@@ -258,7 +258,7 @@ export function ModelPicker({
disabledReason={disabledReason}
showDescription={selected || previewedModel === displayModel}
/>
{selected ? <Check className="canvas-model-picker-option-check" style={{ color: theme.node.activeStroke }} /> : null}
{selected ? <Check className="canvas-model-picker-option-check ml-1 shrink-0" style={{ color: theme.node.activeStroke }} /> : null}
</button>
);
})}
@@ -353,13 +353,17 @@ function ModelLabel({
<span className="grid size-6 shrink-0 place-items-center rounded-md" style={{ background: theme.toolbar.itemHover }}>
<ModelIcon config={config} model={model} />
</span>
<span className="min-w-0 flex-1 overflow-hidden">
<span className="min-w-44 flex-1 overflow-hidden">
<span className="block min-w-0 truncate text-[var(--fs-label)] font-medium leading-none">{pickerModelDisplayName(config, model, showConfiguredModelName)}</span>
<span className={cn("canvas-model-picker-description mt-1 block truncate text-[var(--fs-tiny)]", showDescription && "is-visible")} style={{ color: theme.node.muted }} title={capabilitySummary}>
{capabilitySummary}
</span>
</span>
{showPrice ? <ModelPrice price={modelMenuPrice(config, model, capability, true)} /> : null}
{showPrice ? (
<span className="ml-auto shrink-0 pl-2">
<ModelPrice price={modelMenuPrice(config, model, capability, true)} />
</span>
) : null}
{!creationVariant && meta.time ? (
<span className="shrink-0 rounded-full px-1.5 py-0.5 text-[var(--fs-tiny)] tabular-nums" style={{ background: theme.toolbar.itemHover, color: theme.node.muted }}>
{meta.time}

View File

@@ -354,6 +354,20 @@ export function getAntThemeConfig(dark: boolean, skinID: unknown = "classic"): T
headerFontSize: 15,
bodyPadding: 18,
},
Message: {
contentBg: elevatedBackground,
contentPadding: "10px 18px",
},
Notification: {
colorBgElevated: elevatedBackground,
},
Tooltip: {
colorBgSpotlight: elevatedBackground,
colorTextLightSolid: color.selectedFg,
},
Popover: {
colorBgElevated: elevatedBackground,
},
},
};
}
@@ -385,6 +399,9 @@ export function getWorkspaceAntThemeConfig(): ThemeConfig {
Dropdown: { borderRadiusLG: 16, controlItemBgHover: "var(--user-surface-muted)", paddingBlock: 6 },
Modal: { borderRadiusLG: 20 },
Popconfirm: { borderRadiusLG: 16 },
Message: { borderRadiusLG: 14, contentPadding: "10px 18px" },
Notification: { borderRadiusLG: 18 },
Tooltip: { borderRadius: 8 },
},
};
}

View File

@@ -87,7 +87,6 @@ export default function PluginsPage() {
const [detailsRestoreFocus, setDetailsRestoreFocus] = useState(false);
const [search, setSearch] = useState("");
const [categoryFilter, setCategoryFilter] = useState("all");
const [scrollTarget, setScrollTarget] = useState<string | null>(null);
const [statusFilter, setStatusFilter] = useState<"all" | "enabled" | "disabled">("all");
const [trustFilter, setTrustFilter] = useState<"all" | "trusted">("all");
const [eagleBaseUrl, setEagleBaseUrl] = useState("http://localhost:41595");
@@ -96,7 +95,6 @@ export default function PluginsPage() {
const [eagleFolders, setEagleFolders] = useState<EagleFolder[]>([]);
const [eagleFoldersLoading, setEagleFoldersLoading] = useState(false);
const [eagleFoldersError, setEagleFoldersError] = useState("");
const sectionRefs = useRef<Record<string, HTMLElement | null>>({});
useEffect(() => {
for (const plugin of builtinPlugins) ensurePlugin(plugin.manifest);
@@ -168,24 +166,15 @@ export default function PluginsPage() {
[categoryFilter, filteredPlugins],
);
const scrollContainerRef = useRef<HTMLDivElement>(null);
const selectCategory = (key: string) => {
setCategoryFilter(key);
setScrollTarget(key === "all" ? null : key);
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollTop = 0;
}
};
useEffect(() => {
if (!scrollTarget) return;
const section = sectionRefs.current[scrollTarget];
if (!section) return;
const frame = window.requestAnimationFrame(() => {
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
section.scrollIntoView({ behavior: reducedMotion ? "auto" : "smooth", block: "start" });
setScrollTarget(null);
});
return () => window.cancelAnimationFrame(frame);
}, [pluginSections, scrollTarget]);
const categoryCounts = useMemo(() => {
const visiblePlugins = registeredPlugins.filter((plugin) => user?.role === "admin" || features.systemPluginsVisibleToUsers || isOfficialApplicationPluginId(plugin.manifest.id));
const counts: Record<string, number> = { all: visiblePlugins.length, text: 0, image: 0, video: 0, audio: 0, payment: 0, other: 0 };
@@ -248,7 +237,7 @@ export default function PluginsPage() {
return (
<main className="app-workspace-page plugins-page flex h-full min-h-0 flex-col text-foreground">
<div className="app-workspace-scroll min-h-0 flex-1 overflow-y-auto">
<div ref={scrollContainerRef} className="app-workspace-scroll min-h-0 flex-1 overflow-y-auto">
<div className="plugins-page-layout">
<aside className="plugins-sidebar" aria-label="插件分类">
<div className="plugins-sidebar-heading">
@@ -334,15 +323,11 @@ export default function PluginsPage() {
{filteredPlugins.length ? (
<div className="plugins-sections">
{pluginSections.map((section) => {
if (!section.plugins.length) return null;
const SectionIcon = section.icon;
return (
<section
key={section.key}
id={`plugin-section-${section.key}`}
ref={(element) => {
sectionRefs.current[section.key] = element;
}}
className="plugin-section"
data-category={section.key === "image" ? "creative" : section.key === "video" ? "drama" : section.key === "audio" ? "social" : section.key === "payment" ? "ecommerce" : undefined}
>
@@ -356,114 +341,120 @@ export default function PluginsPage() {
</div>
<span className="plugin-section-count">{section.plugins.length}</span>
</header>
<div className="plugins-grid">
{section.plugins.map((plugin) => {
const installation = installations.find((item) => item.manifest.id === plugin.manifest.id);
const remote = backendPluginById.get(plugin.manifest.id);
const enabled = isPluginEnabled(plugin, installation);
const trusted = Boolean(plugin.manifest.trusted);
const state = pluginStates[plugin.manifest.id];
const sourceLabel = pluginSourceLabel(plugin, state);
const canConfigure = canConfigurePlugin(plugin);
return (
<section key={plugin.manifest.id} className={`product-collection-card plugin-card library-card-surface${trusted ? " is-trusted" : ""}`}>
<button
type="button"
className="plugin-card-main"
aria-label={`查看${plugin.manifest.name}文档`}
onClick={(event) => {
const openedByKeyboard = event.detail === 0;
setDetailsRestoreFocus(openedByKeyboard);
setDetailsPluginId(plugin.manifest.id);
if (!openedByKeyboard) event.currentTarget.blur();
}}
>
<div className="plugin-card-heading">
<span className={`plugin-icon-tile${trusted ? " is-trusted" : ""}`} aria-hidden="true">
<SectionIcon className="size-5" />
</span>
<div className="min-w-0 flex-1">
<div className="plugin-card-title-row">
<h3>{plugin.manifest.name}</h3>
<span className="plugin-version">v{plugin.manifest.version}</span>
</div>
<div className="plugin-card-labels">
<span className={`plugin-source-label${sourceLabel === "系统插件" ? " is-system" : ""}`}>{sourceLabel}</span>
{trusted ? (
<span className="plugin-trust-label">
<ShieldCheck className="size-3.5" />
可信插件
{section.plugins.length ? (
<div className="plugins-grid">
{section.plugins.map((plugin) => {
const installation = installations.find((item) => item.manifest.id === plugin.manifest.id);
const remote = backendPluginById.get(plugin.manifest.id);
const enabled = isPluginEnabled(plugin, installation);
const trusted = Boolean(plugin.manifest.trusted);
const state = pluginStates[plugin.manifest.id];
const sourceLabel = pluginSourceLabel(plugin, state);
const canConfigure = canConfigurePlugin(plugin);
return (
<section key={plugin.manifest.id} className={`product-collection-card plugin-card library-card-surface${trusted ? " is-trusted" : ""}`}>
<button
type="button"
className="plugin-card-main"
aria-label={`查看${plugin.manifest.name}文档`}
onClick={(event) => {
const openedByKeyboard = event.detail === 0;
setDetailsRestoreFocus(openedByKeyboard);
setDetailsPluginId(plugin.manifest.id);
if (!openedByKeyboard) event.currentTarget.blur();
}}
>
<div className="plugin-card-heading">
<span className={`plugin-icon-tile${trusted ? " is-trusted" : ""}`} aria-hidden="true">
<SectionIcon className="size-5" />
</span>
<div className="min-w-0 flex-1">
<div className="plugin-card-title-row">
<h3>{plugin.manifest.name}</h3>
<span className="plugin-version">v{plugin.manifest.version}</span>
</div>
<div className="plugin-card-labels">
<span className={`plugin-source-label${sourceLabel === "系统插件" ? " is-system" : ""}`}>{sourceLabel}</span>
{trusted ? (
<span className="plugin-trust-label">
<ShieldCheck className="size-3.5" />
可信插件
</span>
) : null}
<span className="plugin-category-label">
{contributionKindsFor(plugin.manifest)
.map((kind) => categoryLabels[kind] ?? kind)
.join(" · ")}
</span>
) : null}
<span className="plugin-category-label">
{contributionKindsFor(plugin.manifest)
.map((kind) => categoryLabels[kind] ?? kind)
.join(" · ")}
</span>
</div>
</div>
</div>
</div>
<p className="plugin-card-description">{plugin.manifest.description}</p>
<p className="plugin-card-description">{plugin.manifest.description}</p>
<div className="plugin-card-meta">
<span>
<CalendarDays className="size-3.5" />
发布 {formatPluginDate(plugin.manifest.publishedAt)}
</span>
<span>
<Clock3 className="size-3.5" />
更新 {formatPluginDate(plugin.manifest.updatedAt)}
<div className="plugin-card-meta">
<span>
<CalendarDays className="size-3.5" />
发布 {formatPluginDate(plugin.manifest.publishedAt)}
</span>
<span>
<Clock3 className="size-3.5" />
更新 {formatPluginDate(plugin.manifest.updatedAt)}
</span>
</div>
<div className="plugin-card-tags">
{(plugin.manifest.surfaces || []).map((surface) => (
<span key={surface}>{surfaceLabels[surface] ?? surface}</span>
))}
{providerCapabilitiesFor(plugin.manifest).map((capability) => (
<span key={capability}>{capabilityLabel(capability)}</span>
))}
{plugin.manifest.contributes.providers?.some((provider) => provider.poll) ? <span>异步轮询</span> : null}
<span>{plugin.manifest.permissions.length} 项能力</span>
</div>
</button>
<div className="plugin-card-actions">
<span role="status" className={`settings-channel-status ${enabled ? "is-ready" : ""}`}>
<i aria-hidden="true" />
{!state?.platformAvailable && state?.blockedReason ? state.blockedReason : enabled ? "已启用" : "已停用"}
</span>
<Switch
className="plugin-state-switch"
disabled={!state?.canToggle}
checked={enabled}
aria-label={`${plugin.manifest.name},当前${enabled ? "已启用,点击停用" : "已停用,点击启用"}`}
title={state?.blockedReason}
onChange={(checked) => void togglePlugin(plugin, checked)}
/>
{canConfigure ? (
<Button
className="plugin-settings-button"
icon={<Settings2 className="size-4" />}
aria-expanded={settingsPluginId === plugin.manifest.id}
aria-haspopup="dialog"
onClick={() => setSettingsPluginId(plugin.manifest.id)}
>
设置
</Button>
) : null}
</div>
<div className="plugin-card-tags">
{(plugin.manifest.surfaces || []).map((surface) => (
<span key={surface}>{surfaceLabels[surface] ?? surface}</span>
))}
{providerCapabilitiesFor(plugin.manifest).map((capability) => (
<span key={capability}>{capabilityLabel(capability)}</span>
))}
{plugin.manifest.contributes.providers?.some((provider) => provider.poll) ? <span>异步轮询</span> : null}
<span>{plugin.manifest.permissions.length} 项能力</span>
</div>
</button>
<div className="plugin-card-actions">
<span role="status" className={`settings-channel-status ${enabled ? "is-ready" : ""}`}>
<i aria-hidden="true" />
{!state?.platformAvailable && state?.blockedReason ? state.blockedReason : enabled ? "已启用" : "已停用"}
</span>
<Switch
className="plugin-state-switch"
disabled={!state?.canToggle}
checked={enabled}
aria-label={`${plugin.manifest.name},当前${enabled ? "已启用,点击停用" : "已停用,点击启用"}`}
title={state?.blockedReason}
onChange={(checked) => void togglePlugin(plugin, checked)}
/>
{canConfigure ? (
<Button
className="plugin-settings-button"
icon={<Settings2 className="size-4" />}
aria-expanded={settingsPluginId === plugin.manifest.id}
aria-haspopup="dialog"
onClick={() => setSettingsPluginId(plugin.manifest.id)}
>
设置
</Button>
{installation?.lastError || remote?.error ? (
<Typography.Text type="danger" className="plugin-error" role="alert">
{installation?.lastError || remote?.error}
</Typography.Text>
) : null}
</div>
{installation?.lastError || remote?.error ? (
<Typography.Text type="danger" className="plugin-error" role="alert">
{installation?.lastError || remote?.error}
</Typography.Text>
) : null}
</section>
);
})}
</div>
</section>
);
})}
</div>
) : (
<div className="plugin-section-empty-hint">
<span>暂无该类型的插件</span>
</div>
)}
</section>
);
})}

View File

@@ -18,10 +18,13 @@
padding: 0 0 var(--space-8);
}
.plugins-page-content .collection-toolbar {
margin-top: var(--space-2);
}
.plugins-page-layout {
display: grid;
width: min(var(--product-content-max), calc(100% - 64px));
min-height: 100%;
margin: 24px auto 0;
grid-template-columns: minmax(0, 1fr);
align-items: start;
@@ -31,7 +34,7 @@
position: static;
min-width: 0;
min-height: 0;
padding: 0 0 var(--space-4);
padding: 0;
}
.plugins-sidebar-heading {
@@ -250,6 +253,19 @@
margin-top: var(--space-3);
}
.plugin-section-empty-hint {
display: flex;
align-items: center;
justify-content: center;
min-height: 140px;
margin-top: var(--space-3);
border-radius: var(--plugins-card-radius);
border: 1px dashed var(--user-border);
background: color-mix(in srgb, var(--foreground) 2%, transparent);
color: color-mix(in srgb, var(--foreground) 45%, transparent);
font-size: var(--fs-label);
}
.plugin-card {
display: flex;
min-width: 0;
@@ -841,7 +857,7 @@
position: static;
min-height: 0;
border-right: 0;
padding: 0 0 var(--space-4);
padding: 0;
}
.plugins-sidebar-heading {

View File

@@ -222,6 +222,8 @@ export default function ProjectsPage() {
variant="creation"
placeholder="选择文本模型"
showSelectedPrice={false}
showOptionPrices
popoverClassName="agent-model-picker-popover"
/>
<Button type="default" icon={<Sparkles className="size-3.5" />} disabled={!storyDraft.trim() || generating} loading={generating} onClick={() => void generateStory()}>AI 生成章节</Button>
<Button type="primary" icon={<Plus className="size-3.5" />} onClick={() => openCreate(createSource)}>开始创作</Button>

View File

@@ -1305,6 +1305,41 @@
--workspace-overlay-shadow: 0 30px 84px rgba(0, 0, 0, 0.62), 0 8px 24px rgba(0, 0, 0, 0.34);
}
/* 全局轻量通知与消息提示框 */
.ant-message-notice .ant-message-notice-content {
color: var(--foreground) !important;
background: var(--color-surface, var(--workspace-overlay-bg-strong, #ffffff)) !important;
border: 1px solid var(--color-border, var(--workspace-overlay-border, rgba(0, 0, 0, 0.12))) !important;
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.18) !important;
}
.dark .ant-message-notice .ant-message-notice-content,
.dark body .ant-message-notice-content {
color: #f4f4f5 !important;
background: #1e1e20 !important;
border-color: rgba(255, 255, 255, 0.16) !important;
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.55) !important;
}
.ant-notification-notice {
color: var(--foreground) !important;
background: var(--color-surface, var(--workspace-overlay-bg-strong, #ffffff)) !important;
border: 1px solid var(--color-border, var(--workspace-overlay-border, rgba(0, 0, 0, 0.12))) !important;
}
.dark .ant-notification-notice,
.dark body .ant-notification-notice {
color: #f4f4f5 !important;
background: #1e1e20 !important;
border-color: rgba(255, 255, 255, 0.16) !important;
}
.dark .ant-tooltip .ant-tooltip-inner {
color: #f4f4f5 !important;
background: #1e1e20 !important;
border: 1px solid rgba(255, 255, 255, 0.14) !important;
}
body.app-spatial-overlays :where(
.ant-dropdown-menu,
.ant-select-dropdown,
@@ -7746,7 +7781,7 @@ html:not(.dark) .creation-studio-control:hover,
.canvas-model-picker-menu {
width: max-content !important;
min-width: var(--canvas-model-picker-trigger-width, 0px);
min-width: max(var(--canvas-model-picker-trigger-width, 0px), 260px);
max-width: calc(100vw - 24px);
max-height: min(420px, calc(100vh - 32px));
overflow-x: hidden;
@@ -7806,6 +7841,9 @@ html:not(.dark) .creation-studio-control:hover,
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
display: flex;
align-items: center;
width: 100%;
}
.canvas-model-picker-popover .canvas-model-picker-option > span:first-child > span:nth-child(2) {
@@ -15947,7 +15985,7 @@ html:not(.dark) .creation-history-drawer-root {
/* 空态:banner 剧照 + 三张欢迎卡 */
.assets-empty { display: flex; width: 100%; flex-direction: column; align-items: center; padding: 5vh 0 8vh; }
.assets-empty-banner { position: relative; width: min(720px, calc(100% - 24px)); height: 180px; }
.assets-empty-banner { position: relative; width: min(720px, calc(100% - 24px)); height: 180px; margin-bottom: 24px; }
.assets-empty-banner-frame { position: absolute; overflow: hidden; border: 1px solid color-mix(in srgb, var(--foreground) 14%, transparent); border-radius: var(--r-lg); box-shadow: var(--elevation-card); }
.assets-empty-banner-frame img { width: 100%; height: 100%; object-fit: cover; filter: grayscale(.45) contrast(.94) saturate(.7); }
.assets-empty-banner-frame.is-back { top: 16px; left: 2%; width: 24%; height: 132px; transform: rotate(-6deg); opacity: .45; }
@@ -15955,9 +15993,9 @@ html:not(.dark) .creation-history-drawer-root {
.assets-empty-banner-frame.is-front { top: 20px; right: 2%; width: 24%; height: 132px; transform: rotate(6deg); opacity: .5; }
.assets-empty-banner-frame.is-main::after { position: absolute; inset: 0; background: linear-gradient(180deg, transparent 40%, color-mix(in srgb, var(--foreground) 55%, transparent)); content: ""; }
.assets-empty-banner-frame span { position: absolute; right: 12px; bottom: 10px; z-index: 1; color: #fff; font-size: var(--fs-micro); letter-spacing: .08em; text-shadow: 0 1px 6px rgba(0, 0, 0, .5); }
.assets-empty-banner-caption { position: absolute; right: 19%; bottom: 2px; color: color-mix(in srgb, var(--foreground) 44%, transparent); font-size: var(--fs-micro); letter-spacing: .1em; }
.assets-empty-banner-caption { position: absolute; left: 0; right: 0; bottom: -22px; text-align: center; color: color-mix(in srgb, var(--foreground) 44%, transparent); font-size: var(--fs-micro); letter-spacing: .1em; z-index: 3; }
.assets-empty-banner-caption span { color: var(--workspace-accent); font-weight: 620; font-variant-numeric: tabular-nums; }
.assets-empty-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; width: min(680px, calc(100% - 24px)); margin-top: 34px; }
.assets-empty-cards { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; width: min(680px, calc(100% - 24px)); margin-top: 24px; }
.assets-empty-card {
display: flex;
flex-direction: column;
@@ -17402,13 +17440,32 @@ html:not(.dark) .creation-home .creation-user-message-copy-wrap { box-shadow: 0
flex: 0 0 34px;
place-items: center;
border-radius: 10px;
color: rgba(0, 0, 0, .46);
color: var(--user-ink-muted);
transition: background-color .18s ease, color .18s ease;
}
.app-user-workspace .app-workspace-sidebar-notification:hover {
background: rgba(72, 72, 73, .055);
color: #171717;
background: var(--user-surface-hover);
color: var(--user-ink);
}
.dark .app-user-workspace .app-workspace-sidebar-notification,
.dark .app-user-workspace .app-workspace-topbar-icon-button,
.dark .app-user-workspace .app-workspace-sidebar-rail-button {
color: rgba(255, 255, 255, 0.65) !important;
}
.dark .app-user-workspace .app-workspace-sidebar-notification:hover,
.dark .app-user-workspace .app-workspace-topbar-icon-button:hover,
.dark .app-user-workspace .app-workspace-sidebar-rail-button:hover {
background: rgba(255, 255, 255, 0.08) !important;
color: #ffffff !important;
}
.dark .app-user-workspace .app-workspace-topbar-icon-button svg,
.dark .app-user-workspace .app-workspace-sidebar-notification svg,
.dark .app-user-workspace .app-workspace-sidebar-rail-button svg {
color: currentColor !important;
}
.app-user-workspace .app-workspace-sidebar-notification svg {
@@ -17732,7 +17789,25 @@ html:not(.dark) .creation-home .creation-user-message-copy-wrap { box-shadow: 0
.dark .app-user-workspace .app-workspace-sidebar-profile-menu button:hover { background: rgba(108,94,236,.15); color: #dcd8ff; }
.dark .app-user-workspace .creation-home-heading h1 { color: #f5f5f5; }
.dark .app-user-workspace .creation-home-heading p,
.dark .app-user-workspace .app-workspace-topbar-breadcrumb { color: rgba(255, 255, 255, .48); }
.dark .app-user-workspace .app-workspace-topbar-breadcrumb > span:not(.truncate) {
color: rgba(255, 255, 255, 0.28) !important;
}
.dark .app-user-workspace .app-workspace-sidebar-collapse-button {
color: rgba(255, 255, 255, 0.55) !important;
opacity: 0.85;
}
.dark .app-user-workspace .app-workspace-sidebar-collapse-button:hover,
.dark .app-user-workspace .app-workspace-sidebar-collapse-button:focus-visible {
background: rgba(255, 255, 255, 0.08) !important;
color: #ffffff !important;
opacity: 1;
}
.dark .app-user-workspace .app-workspace-sidebar-collapse-button svg {
color: currentColor !important;
}
.dark .app-user-workspace .app-workspace-topbar { border-bottom-color: rgba(255, 255, 255, .08); }
.dark .app-user-workspace .creation-empty-workspace > .creation-mode-capsule { background: #232326; border-color: rgba(255,255,255,.08); }
.dark .app-user-workspace .creation-empty-workspace > .creation-mode-capsule .creation-chat-control[aria-selected="true"] { background: #303034; color: #ffffff; }

View File

@@ -665,6 +665,157 @@
.app-product-workspace .story-launcher-panel { padding: 16px; border-radius: 16px; }
}
/* Changelog Modal Styles */
.app-changelog-modal .ant-modal-container {
display: flex;
flex-direction: column;
overflow: hidden;
border: 1px solid var(--user-border);
border-radius: 20px;
background: var(--user-page-bg);
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.28);
max-height: min(84vh, 780px);
}
.app-changelog-modal .ant-modal-header {
margin-bottom: 0;
padding: 20px 24px 16px;
border-bottom: 1px solid var(--user-border);
background: var(--user-surface);
}
.app-changelog-heading {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
}
.app-changelog-heading-icon {
display: grid;
width: 38px;
height: 38px;
flex: 0 0 38px;
place-items: center;
border-radius: 11px;
background: color-mix(in srgb, var(--user-accent) 14%, var(--user-surface));
color: var(--user-accent);
}
.app-changelog-heading-copy {
display: grid;
min-width: 0;
flex: 1 1 auto;
gap: 2px;
}
.app-changelog-heading-title {
color: var(--user-ink);
font-size: 16px;
font-weight: 650;
line-height: 1.3;
}
.app-changelog-heading-description {
color: var(--user-ink-muted);
font-size: 12px;
font-weight: 400;
}
.app-changelog-current-version {
margin-right: 28px;
border-radius: 999px;
border: 1px solid color-mix(in srgb, var(--user-accent) 25%, transparent);
background: color-mix(in srgb, var(--user-accent) 10%, var(--user-surface));
padding: 3px 10px;
color: var(--user-accent);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 11px;
font-weight: 600;
white-space: nowrap;
}
.app-changelog-scroll {
max-height: min(66vh, 600px);
overflow-y: auto;
padding: 20px 28px 32px;
color: var(--user-ink);
}
.app-changelog-section-heading {
display: flex;
align-items: center;
gap: 8px;
margin: 24px 0 12px;
padding-bottom: 6px;
border-bottom: 1px solid var(--user-border);
color: var(--user-ink);
font-size: 15px;
font-weight: 650;
}
.app-changelog-section-heading:first-child {
margin-top: 0;
}
.app-changelog-section-marker {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--user-ink-muted);
}
.app-changelog-section-heading.is-latest .app-changelog-section-marker {
background: var(--user-accent);
}
.app-changelog-latest-badge {
border-radius: 999px;
background: var(--user-accent);
padding: 1px 7px;
color: var(--user-accent-foreground, #fff);
font-size: 10px;
font-weight: 600;
}
.app-changelog-list {
margin: 8px 0 16px 20px;
padding: 0;
list-style-type: disc;
display: flex;
flex-direction: column;
gap: 8px;
}
.app-changelog-list li {
color: color-mix(in srgb, var(--user-ink) 88%, transparent);
font-size: 13px;
line-height: 1.65;
}
.app-changelog-paragraph {
margin: 6px 0;
color: color-mix(in srgb, var(--user-ink) 78%, transparent);
font-size: 13px;
line-height: 1.6;
}
.app-changelog-code {
border-radius: 5px;
border: 1px solid var(--user-border);
background: var(--user-surface-muted);
padding: 1px 5px;
color: var(--user-accent);
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 11.5px;
}
@media (max-width: 620px) {
.app-changelog-heading-description { display: none; }
.app-changelog-current-version { display: none; }
.app-changelog-scroll { padding: 14px 16px 24px; }
}
@media (prefers-reduced-motion: reduce) {
.app-product-workspace *,
.app-product-overlays * { animation-duration: 1ms !important; animation-delay: 0ms !important; animation-iteration-count: 1 !important; transition-duration: 1ms !important; }