feat(canvas): 动效系统与响应式适配 - token落地+断点体系+无障碍 #99

基于 Wayfinder #99 决议,完成动效/响应式/无障碍三大维度落地:

1. 动效 token 迁移(CSS 动画)
   - 33 处 easing 替换为 var(--motion-ease-out/in-out/linear)
   - 7 处有限动画 duration 替换为 var(--motion-dur-*-calc)
     (含 motion-scale 降级,自动联动 prefers-reduced-motion)
   - canvas-node batch 动画内联值替换为 token

2. aceternityMotion 对齐 + motion/react 硬编码替换
   - 添加 JS↔CSS token 映射文档注释
   - 3 处 motion/react 硬编码 duration 替换为 aceternityMotion.* 引用

3. infinite 装饰动画 reduced-motion 降级
   - L1 (prefers-reduced-motion): 缩短至 0.01ms + 单次播放
   - L2 (.no-motion): animation: none 完全禁用
   - 覆盖 9 个 infinite 动画类(连线流光/边框流光/极光标题等)

4. 响应式断点体系
   - @theme 新增 3 断点 token: --breakpoint-tablet(768)/desktop(1024)/wide(1440)
   - 修复 canvas-toolbar 外观面板缺 max-w 导致移动端溢出

5. 无障碍
   - 画布页新增 skip-to-content 链接(键盘用户跳过侧栏直达画布)
   - 任务进度面板 aria-live="polite"(屏幕阅读器播报任务数变化)

Closes #99
This commit is contained in:
sikongyue
2026-08-03 22:22:24 +08:00
committed by ddCat
parent 9c4a114b38
commit d2741f39f3
8 changed files with 93 additions and 43 deletions

View File

@@ -62,7 +62,7 @@ export function CanvasActiveTaskPanel({ tasks }: { tasks: GenerationTask[] }) {
</span>
<span className="min-w-0">
<span className="block text-sm font-semibold leading-5">生成任务</span>
<span className="block truncate text-[var(--fs-label)]" style={{ color: theme.node.muted }}>当前画布 · {tasks.length} 个进行中</span>
<span className="block truncate text-[var(--fs-label)]" style={{ color: theme.node.muted }} aria-live="polite">当前画布 · {tasks.length} 个进行中</span>
</span>
</span>
<span className="flex shrink-0 items-center gap-2" style={{ color: theme.accent.primary }}>

View File

@@ -202,7 +202,7 @@ function EmotionHeadPreview({ preset }: { preset: CanvasEmotionPreset }) {
</Canvas>
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-10 bg-gradient-to-t from-black/55 to-transparent" />
<AnimatePresence mode="wait" initial={false}>
<motion.span key={preset.id} initial={{ opacity: 0, filter: "blur(6px)" }} animate={{ opacity: 1, filter: "blur(0px)" }} exit={{ opacity: 0, filter: "blur(5px)" }} transition={{ duration: 0.2 }} className="pointer-events-none absolute bottom-2 left-2.5 text-[var(--fs-tiny)] font-medium text-white/72">实时预览 · {preset.label}</motion.span>
<motion.span key={preset.id} initial={{ opacity: 0, filter: "blur(6px)" }} animate={{ opacity: 1, filter: "blur(0px)" }} exit={{ opacity: 0, filter: "blur(5px)" }} transition={{ duration: aceternityMotion.duration.state }} className="pointer-events-none absolute bottom-2 left-2.5 text-[var(--fs-tiny)] font-medium text-white/72">实时预览 · {preset.label}</motion.span>
</AnimatePresence>
</div>
);

View File

@@ -380,7 +380,7 @@ export const CanvasNode = React.memo(function CanvasNode({
"--batch-from-x": `${batchMotion?.x || 0}px`,
"--batch-from-y": `${batchMotion?.y || 0}px`,
"--batch-from-rotate": `${6 + (batchMotion?.index || 0) * 4}deg`,
animation: data.metadata?.batchRootId ? (batchClosing ? "canvas-batch-child-out 260ms cubic-bezier(.4,0,.2,1) both" : "canvas-batch-child-in 340ms cubic-bezier(.2,.85,.18,1) both") : undefined,
animation: data.metadata?.batchRootId ? (batchClosing ? `canvas-batch-child-out var(--motion-dur-base-calc) var(--motion-ease-in-out) both` : `canvas-batch-child-in var(--motion-dur-slow-calc) var(--motion-ease-out) both`) : undefined,
animationDelay: data.metadata?.batchRootId ? `${batchClosing ? 0 : 45 + (batchMotion?.index || 0) * 24}ms` : undefined,
} as React.CSSProperties
}

View File

@@ -142,7 +142,7 @@ export function CanvasToolbar({
<AnimatePresence>
{appearanceOpen ? (
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: aceternityMotion.duration.instant }} className="pointer-events-auto absolute bottom-[50px] z-30 w-[224px] -translate-x-1/2" style={{ left: panelX || "50%" }}>
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: aceternityMotion.duration.instant }} className="pointer-events-auto absolute bottom-[50px] z-30 w-[224px] max-w-[calc(100vw-24px)] -translate-x-1/2" style={{ left: panelX || "50%" }}>
<SpotlightSurface spotlightColor={theme.toolbar.itemHover} initial={{ y: 6, scale: 0.97 }} animate={{ y: 0, scale: 1 }} exit={{ y: 4, scale: 0.98 }} transition={{ duration: aceternityMotion.duration.instant, ease: aceternityMotion.easing.enter }} className="aceternity-floating-panel overflow-hidden rounded-[var(--panel-radius)] border p-2.5 backdrop-blur-2xl" style={{ background: theme.spatial.elevated, borderColor: theme.toolbar.border, color: theme.toolbar.item, boxShadow: `0 24px 64px ${theme.spatial.shadow}` }} onWheel={(event) => event.stopPropagation()}>
<PanelHeading icon={<Palette className="size-4" />} title="画布外观" subtitle="调整整个创作空间" theme={theme} />
<div className="mt-3 text-[var(--fs-micro)] font-semibold uppercase opacity-45">主题模式</div>

View File

@@ -1,3 +1,18 @@
/**
* 动效配置(对应 #99 动效系统)
*
* JS 侧 duration(秒)与 CSS token(毫秒)映射关系:
* instant 0.12s ≈ --motion-dur-fast (150ms)
* state 0.20s ≈ --motion-dur-fast (150ms)
* panel 0.32s ≈ --motion-dur-base (250ms)
*
* easing 与 CSS token 映射:
* enter [0.2, 0.85, 0.18, 1] ≈ --motion-ease-out
* exit [0.4, 0, 1, 1] ≈ --motion-ease-in
*
* prefers-reduced-motion 由组件层 useReducedMotion() 控制,
* CSS 层由 --motion-scale 联动(L0=1 / L1=0.3 / L2=0)。
*/
export const aceternityMotion = {
duration: {
instant: 0.12,

View File

@@ -34,7 +34,7 @@ export function CanvasUploadStatusToast({ status, theme }: { status: CanvasUploa
>
<div className="absolute inset-x-0 top-0 h-px" style={{ background: `linear-gradient(90deg, transparent, ${accent}, transparent)` }} />
<div className="flex items-start gap-3">
<motion.span animate={status.done || status.error ? { scale: [0.8, 1.08, 1] } : { y: [0, -2, 0] }} transition={status.done || status.error ? { duration: 0.3 } : { duration: 1.5, repeat: Number.POSITIVE_INFINITY }} className="mt-0.5 grid size-9 shrink-0 place-items-center rounded-[var(--dock-radius)] border" style={{ background: theme.spatial.surface, borderColor: `${accent}44`, color: accent }}>
<motion.span animate={status.done || status.error ? { scale: [0.8, 1.08, 1] } : { y: [0, -2, 0] }} transition={status.done || status.error ? { duration: aceternityMotion.duration.panel } : { duration: 1.5, repeat: Number.POSITIVE_INFINITY }} className="mt-0.5 grid size-9 shrink-0 place-items-center rounded-[var(--dock-radius)] border" style={{ background: theme.spatial.surface, borderColor: `${accent}44`, color: accent }}>
{status.error ? <TriangleAlert className="size-4" /> : status.done ? <CheckCircle2 className="size-4" /> : <CloudUpload className="size-4" />}
</motion.span>
<span className="min-w-0 flex-1">

View File

@@ -1350,7 +1350,9 @@ function InfiniteCanvasPage() {
if (!projectLoaded) return <CanvasRefreshShell />;
return (
<main className="flex h-full min-h-0 overflow-hidden" style={{ background: theme.canvas.background, color: theme.node.text }}>
<>
<a href="#canvas-main" className="sr-only focus:not-sr-only focus:absolute focus:left-4 focus:top-4 focus:z-[var(--z-toast)] focus:rounded-md focus:border focus:bg-background focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:shadow-lg">跳转到画布主内容</a>
<main id="canvas-main" tabIndex={-1} className="flex h-full min-h-0 overflow-hidden outline-none" style={{ background: theme.canvas.background, color: theme.node.text }}>
{currentProject?.projectId ? <CanvasProjectSidebar projectId={currentProject.projectId} detail={linkedProjectQuery.data} onAddChapter={handleProjectChapterInsert} onLocateStyle={locateProjectStyleNode} onOpenAssets={() => openProjectAssets()} /> : null}
<section className="relative min-w-0 flex-1 overflow-hidden">
<CanvasTopBar
@@ -1772,5 +1774,6 @@ function InfiniteCanvasPage() {
/>
) : null}
</main>
</>
);
}

View File

@@ -164,6 +164,33 @@
--motion-scale: 0;
}
/* infinite 装饰动画在 reduced-motion / no-motion 下降级(对应 #99 三级降级)*/
@media (prefers-reduced-motion: reduce) {
.canvas-connection-flow,
.canvas-connection-draft,
.canvas-generation-border-flow,
.canvas-task-progress-shimmer,
.ai-title-aurora,
.creation-intro-mark,
.creation-intro-word,
.creation-intro-signal,
.creation-intro-emphasis {
animation-duration: 0.01ms;
animation-iteration-count: 1;
}
}
.no-motion .canvas-connection-flow,
.no-motion .canvas-connection-draft,
.no-motion .canvas-generation-border-flow,
.no-motion .canvas-task-progress-shimmer,
.no-motion .ai-title-aurora,
.no-motion .creation-intro-mark,
.no-motion .creation-intro-word,
.no-motion .creation-intro-signal,
.no-motion .creation-intro-emphasis {
animation: none;
}
/* 动效计算函数(实际 duration = base * motion-scale)*/
:root {
--motion-dur-instant-calc: calc(var(--motion-dur-instant) * var(--motion-scale));
@@ -174,6 +201,11 @@
}
@theme inline {
/* 响应式断点(对应 #99 四断点体系:mobile/tablet/desktop/wide)*/
--breakpoint-tablet: 768px;
--breakpoint-desktop: 1024px;
--breakpoint-wide: 1440px;
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-border: var(--border);
@@ -259,7 +291,7 @@
--motion-instant: 120ms;
--motion-state: 180ms;
--motion-panel: 240ms;
--ease-aceternity: cubic-bezier(0.2, 0.85, 0.18, 1);
--ease-aceternity: var(--motion-ease-out);
--ease-product-enter: cubic-bezier(0.16, 1, 0.3, 1);
--workspace-canvas: #ffffff;
--workspace-surface: rgba(255, 255, 255, 0.82);
@@ -1209,7 +1241,7 @@
.creation-empty-composer {
width: 100%;
animation: creation-workbench-enter 420ms cubic-bezier(.16, 1, .3, 1) both;
animation: creation-workbench-enter var(--motion-dur-slow-calc) var(--motion-ease-out) both;
}
.creation-composer {
@@ -1275,7 +1307,7 @@
background: color-mix(in srgb, var(--creation-surface-muted) 80%, transparent);
color: var(--creation-muted);
transform: rotate(-7deg);
transition: border-color 160ms ease, background-color 160ms ease, color 160ms ease, transform 160ms cubic-bezier(.16, 1, .3, 1);
transition: border-color 160ms ease, background-color 160ms ease, color 160ms ease, transform 160ms var(--motion-ease-out);
}
.creation-reference-tile:hover:not(:disabled) {
@@ -1521,7 +1553,7 @@
width: 13px;
height: 13px;
color: var(--creation-muted);
transition: transform 160ms cubic-bezier(.16, 1, .3, 1);
transition: transform 160ms var(--motion-ease-out);
}
.creation-chip > svg:last-child.is-open {
@@ -1634,7 +1666,7 @@
box-shadow: inset 0 1px 0 color-mix(in srgb, white 52%, transparent);
backdrop-filter: blur(18px) saturate(120%);
-webkit-backdrop-filter: blur(18px) saturate(120%);
transition: border-color 160ms ease, background-color 160ms ease, transform 160ms cubic-bezier(.16, 1, .3, 1);
transition: border-color 160ms ease, background-color 160ms ease, transform 160ms var(--motion-ease-out);
}
.creation-inspiration-strip button:hover {
@@ -1680,7 +1712,7 @@
.creation-results {
width: 100%;
animation: creation-results-enter 280ms cubic-bezier(.16, 1, .3, 1) both;
animation: creation-results-enter var(--motion-dur-base-calc) var(--motion-ease-out) both;
}
.creation-thread-composer {
@@ -1728,7 +1760,7 @@
padding: 0 !important;
backdrop-filter: blur(28px) saturate(140%);
-webkit-backdrop-filter: blur(28px) saturate(140%);
animation: creation-popover-enter 180ms cubic-bezier(.16, 1, .3, 1) both;
animation: creation-popover-enter var(--motion-dur-fast-calc) var(--motion-ease-out) both;
}
.dark .creation-control-popover-surface,
@@ -2174,7 +2206,7 @@
border-radius: 26px;
background: var(--creation-surface);
box-shadow: 0 28px 70px rgba(0, 0, 0, .18);
animation: creation-workbench-enter 360ms cubic-bezier(.16, 1, .3, 1) both;
animation: creation-workbench-enter var(--motion-dur-slow-calc) var(--motion-ease-out) both;
}
html:not(.dark) .creation-studio-composer {
@@ -2208,7 +2240,7 @@
background: #1c1f25;
color: #c8cdd5;
transform: rotate(-7deg);
transition: transform 180ms cubic-bezier(.16, 1, .3, 1), border-color 180ms ease, background-color 180ms ease;
transition: transform 180ms var(--motion-ease-out), border-color 180ms ease, background-color 180ms ease;
}
html:not(.dark) .creation-studio-reference {
@@ -2316,7 +2348,7 @@
font-weight: 600;
white-space: nowrap;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .055);
transition: transform 150ms cubic-bezier(.16, 1, .3, 1), border-color 150ms ease, background-color 150ms ease;
transition: transform 150ms var(--motion-ease-out), border-color 150ms ease, background-color 150ms ease;
}
html:not(.dark) .creation-studio-control,
@@ -2344,7 +2376,7 @@
.creation-studio-control:active { transform: scale(.98); }
.creation-studio-control.is-mode { border-color: rgba(6, 182, 212, .36) !important; background: rgba(4, 73, 89, .28) !important; color: #18c2e1 !important; }
.creation-studio-control > svg { width: 19px; height: 19px; flex: 0 0 auto; }
.creation-studio-control > svg:last-child { width: 17px; height: 17px; color: var(--creation-muted); transition: transform 160ms cubic-bezier(.16, 1, .3, 1); }
.creation-studio-control > svg:last-child { width: 17px; height: 17px; color: var(--creation-muted); transition: transform 160ms var(--motion-ease-out); }
.creation-studio-control > svg:last-child.is-open { transform: rotate(180deg); }
.creation-model-picker.canvas-composer-model-picker {
@@ -2365,7 +2397,7 @@
background: #383d47;
color: #e5e8ec;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .13);
transition: transform 150ms cubic-bezier(.16, 1, .3, 1), background-color 150ms ease, color 150ms ease;
transition: transform 150ms var(--motion-ease-out), background-color 150ms ease, color 150ms ease;
}
.creation-studio-submit:hover:not(:disabled) { background: #f4f5f6; color: #111317; transform: translateY(-1px); }
.creation-studio-submit:active:not(:disabled) { transform: scale(.96); }
@@ -2396,7 +2428,7 @@
border-radius: 10px;
background: rgba(255, 255, 255, .055);
color: var(--creation-accent);
animation: creation-intro-mark 2.8s cubic-bezier(.16, 1, .3, 1) infinite;
animation: creation-intro-mark 2.8s var(--motion-ease-out) infinite;
}
.creation-chat-intro-mark svg { width: 16px; height: 16px; }
.creation-chat-headline {
@@ -2407,7 +2439,7 @@
line-height: 1.25;
text-align: center;
}
.creation-chat-headline span { display: inline-block; margin-left: 4px; color: var(--creation-accent); animation: creation-intro-word 2.8s cubic-bezier(.16, 1, .3, 1) infinite; }
.creation-chat-headline span { display: inline-block; margin-left: 4px; color: var(--creation-accent); animation: creation-intro-word 2.8s var(--motion-ease-out) infinite; }
.creation-chat-intro p { margin: 8px 0 0; color: var(--creation-muted); font-size: 12px; }
.creation-chat-composer {
@@ -2444,7 +2476,7 @@
background: #242730;
color: #eef1f4;
box-shadow: none;
transition: transform 140ms cubic-bezier(.16, 1, .3, 1), background-color 140ms ease, border-color 140ms ease;
transition: transform 140ms var(--motion-ease-out), background-color 140ms ease, border-color 140ms ease;
}
.creation-chat-reference:hover:not(:disabled) {
@@ -2527,7 +2559,7 @@
font-weight: 600;
white-space: nowrap;
box-shadow: none;
transition: border-color 140ms ease, background-color 140ms ease, transform 140ms cubic-bezier(.16, 1, .3, 1);
transition: border-color 140ms ease, background-color 140ms ease, transform 140ms var(--motion-ease-out);
}
.creation-chat-control:hover,
.creation-chat-control[aria-expanded="true"],
@@ -2536,7 +2568,7 @@
.creation-chat-control:active { transform: scale(.98); }
.creation-chat-control.is-mode { border-color: rgba(6, 182, 212, .36) !important; background: rgba(5, 80, 98, .24) !important; color: #17c4e2 !important; }
.creation-chat-control > svg { width: 15px; height: 15px; flex: 0 0 auto; }
.creation-chat-control > svg:last-child { width: 14px; height: 14px; color: var(--creation-muted); transition: transform 160ms cubic-bezier(.16, 1, .3, 1); }
.creation-chat-control > svg:last-child { width: 14px; height: 14px; color: var(--creation-muted); transition: transform 160ms var(--motion-ease-out); }
.creation-chat-control > svg:last-child.is-open { transform: rotate(180deg); }
.creation-chat-composer .creation-model-picker.canvas-composer-model-picker { width: min(196px, 26vw) !important; justify-content: flex-start; }
.creation-chat-composer .creation-model-picker .canvas-model-picker-trigger-icon { width: 19px; height: 19px; border-radius: 5px; }
@@ -2556,7 +2588,7 @@
border-radius: 50%;
background: #303540;
color: #f1f3f5;
transition: background-color 140ms ease, color 140ms ease, transform 140ms cubic-bezier(.16, 1, .3, 1);
transition: background-color 140ms ease, color 140ms ease, transform 140ms var(--motion-ease-out);
}
.creation-chat-submit:hover:not(:disabled) { background: #f3f4f6; color: #121417; transform: translateY(-1px); }
.creation-chat-submit:active:not(:disabled) { transform: scale(.96); }
@@ -3590,7 +3622,7 @@
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: ai-title-aurora 7s ease-in-out infinite;
animation: ai-title-aurora 7s var(--motion-ease-in-out) infinite;
}
.dark .ai-title-aurora {
@@ -4219,7 +4251,7 @@
.canvas-floating-dock,
.canvas-floating-panel,
.canvas-node-toolbar {
animation: canvas-panel-in 180ms cubic-bezier(0.2, 0.85, 0.18, 1) both;
animation: canvas-panel-in var(--motion-dur-fast-calc) var(--motion-ease-out) both;
}
/* 覆盖工具栏与节点之间的视觉留白,避免鼠标途中被相邻节点抢走悬浮状态。 */
@@ -4271,7 +4303,7 @@
color 140ms ease,
opacity 140ms ease,
box-shadow 140ms ease,
transform 160ms cubic-bezier(0.2, 0.85, 0.18, 1);
transform 160ms var(--motion-ease-out);
}
.canvas-connection-handle {
@@ -4307,7 +4339,7 @@
.canvas-menu-item-icon,
.canvas-node-tool-icon {
transition: transform 160ms cubic-bezier(0.2, 0.85, 0.18, 1);
transition: transform 160ms var(--motion-ease-out);
}
.canvas-node-shell {
@@ -4389,7 +4421,7 @@
content: "";
filter: drop-shadow(0 0 3px rgba(96, 165, 250, 0.2));
pointer-events: none;
animation: canvas-generation-border-flow 2.4s linear infinite;
animation: canvas-generation-border-flow 2.4s var(--motion-ease-linear) infinite;
-webkit-mask:
linear-gradient(#000 0 0) content-box,
linear-gradient(#000 0 0);
@@ -4402,11 +4434,11 @@
}
.canvas-connection-flow {
animation: canvas-connection-flow 1.25s linear infinite;
animation: canvas-connection-flow 1.25s var(--motion-ease-linear) infinite;
}
.canvas-connection-draft {
animation: canvas-connection-flow 0.9s linear infinite;
animation: canvas-connection-flow 0.9s var(--motion-ease-linear) infinite;
}
@keyframes canvas-panel-in {
@@ -4635,7 +4667,7 @@ html:not(.dark) .creation-home {
.creation-empty-composer {
width: 100%;
margin: 0;
animation: creation-composer-arrive 360ms cubic-bezier(.16, 1, .3, 1) both;
animation: creation-composer-arrive var(--motion-dur-slow-calc) var(--motion-ease-out) both;
}
.creation-chat-composer {
@@ -4675,7 +4707,7 @@ html:not(.dark) .creation-chat-composer {
color: var(--creation-muted);
box-shadow: none;
transform: none;
transition: color 140ms ease, background-color 140ms ease, transform 140ms cubic-bezier(.16, 1, .3, 1);
transition: color 140ms ease, background-color 140ms ease, transform 140ms var(--motion-ease-out);
}
.creation-chat-reference svg,
@@ -4751,7 +4783,7 @@ html:not(.dark) .creation-chat-reference:hover:not(:disabled) { background: rgba
font-size: 11px;
font-weight: 500;
box-shadow: none !important;
transition: color 140ms ease, background-color 140ms ease, transform 140ms cubic-bezier(.16, 1, .3, 1);
transition: color 140ms ease, background-color 140ms ease, transform 140ms var(--motion-ease-out);
}
.creation-chat-control:hover,
@@ -4790,7 +4822,7 @@ html:not(.dark) .creation-chat-composer .creation-model-picker.canvas-composer-m
border-radius: 9px;
background: #f3f4f5;
color: #1b1d21;
transition: transform 140ms cubic-bezier(.16, 1, .3, 1), background-color 140ms ease;
transition: transform 140ms var(--motion-ease-out), background-color 140ms ease;
}
.creation-chat-submit:hover:not(:disabled) { background: #fff; transform: translateY(-1px); }
@@ -4950,7 +4982,7 @@ html:not(.dark) .app-workspace-shell.is-creation-workspace .app-workspace-stage
align-items: center;
margin: 0 0 24px;
text-align: center;
animation: creation-intro-arrive 420ms cubic-bezier(.16, 1, .3, 1) both;
animation: creation-intro-arrive var(--motion-dur-slow-calc) var(--motion-ease-out) both;
}
.creation-chat-intro p {
@@ -4977,7 +5009,7 @@ html:not(.dark) .app-workspace-shell.is-creation-workspace .app-workspace-stage
width: 16px;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(227, 231, 235, .88), transparent);
animation: creation-intro-signal 2.8s cubic-bezier(.16, 1, .3, 1) infinite;
animation: creation-intro-signal 2.8s var(--motion-ease-out) infinite;
}
.creation-chat-reference.is-paper {
@@ -5121,7 +5153,7 @@ html:not(.dark) .creation-home .creation-chat-composer .creation-model-picker.ca
font-size: 1.25em;
font-weight: 760;
text-shadow: 0 0 24px rgba(89, 202, 232, .24);
animation: creation-intro-emphasis 2.6s cubic-bezier(.16, 1, .3, 1) infinite;
animation: creation-intro-emphasis 2.6s var(--motion-ease-out) infinite;
}
.creation-thread-stage,
@@ -5187,7 +5219,7 @@ html:not(.dark) .creation-user-message-reference { background: rgba(28, 32, 37,
.creation-media-result { max-width: 860px; padding: 14px 0 0 34px; }
.creation-image-result-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.creation-image-result { position: relative; aspect-ratio: 1; overflow: hidden; border: 0; border-radius: 9px; background: #181a1e; }
.creation-image-result img { width: 100%; height: 100%; object-fit: cover; transition: transform 220ms cubic-bezier(.16, 1, .3, 1); }
.creation-image-result img { width: 100%; height: 100%; object-fit: cover; transition: transform 220ms var(--motion-ease-out); }
.creation-image-result span, .creation-video-result > span { position: absolute; right: 8px; bottom: 8px; display: grid; width: 27px; height: 27px; place-items: center; border-radius: 7px; background: rgba(13, 14, 16, .62); color: #fff; opacity: 0; transition: opacity 160ms ease; backdrop-filter: blur(8px); }
.creation-image-result span svg, .creation-video-result span svg { width: 14px; height: 14px; }
.creation-image-result:hover img { transform: scale(1.025); }
@@ -5450,7 +5482,7 @@ html:not(.dark) .creation-message-content code { background: rgba(28, 32, 37, .0
color: var(--creation-muted) !important;
font-size: 11px !important;
font-weight: 500;
transition: background-color 160ms cubic-bezier(.16, 1, .3, 1), color 160ms cubic-bezier(.16, 1, .3, 1), transform 160ms cubic-bezier(.16, 1, .3, 1) !important;
transition: background-color 160ms var(--motion-ease-out), color 160ms var(--motion-ease-out), transform 160ms var(--motion-ease-out) !important;
}
.creation-choice-grid button:hover {
@@ -5490,7 +5522,7 @@ html:not(.dark) .creation-message-content code { background: rgba(28, 32, 37, .0
padding: 0 2px !important;
color: var(--creation-muted) !important;
font-size: 10px !important;
transition: color 160ms cubic-bezier(.16, 1, .3, 1), background-color 160ms cubic-bezier(.16, 1, .3, 1) !important;
transition: color 160ms var(--motion-ease-out), background-color 160ms var(--motion-ease-out) !important;
}
.creation-custom-trigger svg { width: 12px; height: 12px; }
@@ -5566,7 +5598,7 @@ html:not(.dark) .creation-message-content code { background: rgba(28, 32, 37, .0
color: var(--creation-muted) !important;
font-size: 11px !important;
font-weight: 500;
transition: background-color 160ms cubic-bezier(.16, 1, .3, 1), color 160ms cubic-bezier(.16, 1, .3, 1) !important;
transition: background-color 160ms var(--motion-ease-out), color 160ms var(--motion-ease-out) !important;
}
.creation-duration-choices button:hover { background: rgba(255, 255, 255, .055) !important; color: var(--creation-text) !important; }
@@ -5600,7 +5632,7 @@ html:not(.dark) .creation-custom-value { background: rgba(28, 32, 37, .055) !imp
border-radius: 8px;
background: rgba(255, 255, 255, .06);
color: var(--creation-muted);
transition: background-color 150ms ease, color 150ms ease, transform 150ms cubic-bezier(.16, 1, .3, 1);
transition: background-color 150ms ease, color 150ms ease, transform 150ms var(--motion-ease-out);
}
.creation-top-action svg { width: 16px; height: 16px; }