perf(*): 创作工作台 - 优化首页按需加载与画布交互并修复历史迁移冲突

This commit is contained in:
ddCat
2026-09-05 13:24:10 +08:00
parent 165b4443ab
commit 58f01d1eb6
107 changed files with 3025 additions and 1089 deletions

View File

@@ -2,7 +2,6 @@
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@source "../../node_modules/streamdown/dist/*.js";
@source "../../node_modules/@streamdown/code/dist/*.js";
@custom-variant dark (&:is(.dark *));
@@ -2039,6 +2038,157 @@
.workspace-route-loader { transition: none; }
}
/* 启动页以品牌标记和创作流程为视觉中心,不模拟尚未出现的工作区骨架。 */
.full-screen-loader {
display: grid;
place-items: center;
background-color: var(--workspace-page);
background-image:
linear-gradient(color-mix(in srgb, var(--foreground) 3.5%, transparent) 1px, transparent 1px),
linear-gradient(90deg, color-mix(in srgb, var(--foreground) 3.5%, transparent) 1px, transparent 1px);
background-size: 32px 32px;
}
.full-screen-loader::before {
position: absolute;
inset: 0;
background: radial-gradient(circle at center, color-mix(in srgb, var(--workspace-accent) 8%, transparent), transparent 34%);
content: "";
pointer-events: none;
}
.full-screen-loader-scene {
position: relative;
width: min(300px, 72vw);
aspect-ratio: 1;
color: var(--foreground);
}
.full-screen-loader-logo {
position: absolute;
z-index: 3;
top: 50%;
left: 50%;
display: grid;
width: 72px;
height: 72px;
place-items: center;
border: 1px solid color-mix(in srgb, var(--foreground) 13%, transparent);
border-radius: var(--r-xl);
background: color-mix(in srgb, var(--workspace-navigation) 92%, transparent);
box-shadow: 0 20px 60px color-mix(in srgb, var(--workspace-accent) 12%, transparent);
transform: translate(-50%, -50%);
animation: loader-logo-breathe 2.8s ease-in-out infinite;
}
.full-screen-loader-logo-image { width: 38px; height: 38px; }
.full-screen-loader-logo-fallback { display: block; width: 36px; height: 36px; background: currentColor; mask: url('/logo.svg') center / contain no-repeat; -webkit-mask: url('/logo.svg') center / contain no-repeat; }
.workspace-route-loader-logo .full-screen-loader-logo-fallback { width: 16px; height: 16px; }
.full-screen-loader-orbit {
position: absolute;
z-index: 2;
inset: 65px;
border: 1px solid color-mix(in srgb, var(--workspace-accent) 30%, transparent);
border-right-color: transparent;
border-radius: 50%;
animation: loader-orbit-spin 5.5s linear infinite;
}
.full-screen-loader-orbit::before,
.full-screen-loader-orbit::after {
position: absolute;
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--workspace-accent);
box-shadow: 0 0 18px color-mix(in srgb, var(--workspace-accent) 72%, transparent);
content: "";
}
.full-screen-loader-orbit::before { top: 17px; right: 17px; }
.full-screen-loader-orbit::after { bottom: 17px; left: 17px; opacity: .45; }
.full-screen-loader-guide { position: absolute; z-index: 0; opacity: .45; }
.full-screen-loader-guide.is-horizontal { top: 50%; right: 0; left: 0; border-top: 1px dashed color-mix(in srgb, var(--foreground) 14%, transparent); }
.full-screen-loader-guide.is-vertical { top: 0; bottom: 0; left: 50%; border-left: 1px dashed color-mix(in srgb, var(--foreground) 14%, transparent); }
.full-screen-loader-frame,
.full-screen-loader-script,
.full-screen-loader-timeline {
position: absolute;
z-index: 1;
border: 1px solid color-mix(in srgb, var(--foreground) 11%, transparent);
background: color-mix(in srgb, var(--workspace-navigation) 78%, transparent);
box-shadow: 0 10px 28px color-mix(in srgb, #000 16%, transparent);
}
.full-screen-loader-frame { top: 38px; display: grid; width: 70px; height: 46px; grid-template-columns: repeat(3, 1fr); gap: 3px; padding: 5px; border-radius: var(--r-sm); }
.full-screen-loader-frame.is-left { left: 10px; transform: rotate(-7deg); animation: loader-frame-float 3.2s ease-in-out infinite; }
.full-screen-loader-frame.is-right { right: 10px; transform: rotate(7deg); animation: loader-frame-float 3.2s .8s ease-in-out infinite reverse; }
.full-screen-loader-frame i { border-radius: 2px; background: color-mix(in srgb, var(--workspace-accent) 16%, var(--workspace-content)); }
.full-screen-loader-script { bottom: 34px; left: 24px; width: 78px; height: 54px; border-radius: var(--r-sm); padding: 10px; transform: rotate(5deg); animation: loader-frame-float 3.6s .35s ease-in-out infinite; }
.full-screen-loader-script i { display: block; width: 78%; height: 2px; margin-bottom: 7px; background: color-mix(in srgb, var(--foreground) 25%, transparent); }
.full-screen-loader-script i:nth-child(2) { width: 100%; }
.full-screen-loader-script i:nth-child(3) { width: 56%; }
.full-screen-loader-script b { position: absolute; right: 9px; bottom: 8px; width: 1px; height: 12px; background: var(--workspace-accent); animation: loader-caret 900ms steps(1) infinite; }
.full-screen-loader-timeline { right: 18px; bottom: 38px; display: flex; width: 88px; height: 44px; align-items: flex-end; gap: 3px; border-radius: var(--r-sm); padding: 8px; transform: rotate(-4deg); animation: loader-frame-float 3.4s 1.1s ease-in-out infinite; }
.full-screen-loader-timeline i { width: 14px; background: color-mix(in srgb, var(--workspace-accent) 18%, var(--workspace-content)); }
.full-screen-loader-timeline i:nth-child(1) { height: 16px; }
.full-screen-loader-timeline i:nth-child(2) { height: 27px; }
.full-screen-loader-timeline i:nth-child(3) { height: 20px; }
.full-screen-loader-timeline i:nth-child(4) { height: 24px; }
.full-screen-loader-timeline b { position: absolute; right: 8px; bottom: 6px; left: 8px; height: 1px; background: var(--workspace-accent); transform-origin: left; animation: loader-timeline-progress 2.2s ease-in-out infinite; }
.full-screen-loader-copy { position: absolute; top: calc(50% + 176px); left: 50%; display: flex; min-width: 240px; flex-direction: column; align-items: center; gap: var(--space-1); color: var(--foreground); text-align: center; transform: translateX(-50%); }
.full-screen-loader-copy strong { font-size: var(--fs-body); font-weight: 620; letter-spacing: .01em; }
.full-screen-loader-copy > span:not(.loading-signal) { color: color-mix(in srgb, var(--foreground) 48%, transparent); font-size: var(--fs-label); }
.loading-signal {
display: flex;
width: auto;
height: 12px;
flex: 0 0 auto;
align-items: center;
gap: 4px;
margin-top: var(--space-2);
border: 0;
border-radius: 0;
animation: none;
}
.loading-signal i { width: 4px; height: 4px; border-radius: 50%; background: color-mix(in srgb, var(--workspace-accent) 76%, var(--foreground)); animation: loader-signal-pulse 1.25s ease-in-out infinite; }
.loading-signal i:nth-child(2) { animation-delay: 140ms; }
.loading-signal i:nth-child(3) { animation-delay: 280ms; }
.workspace-route-loader-content { flex-direction: column; gap: var(--space-2); }
.workspace-route-loader-content .loading-signal { margin-top: 0; }
.workspace-route-loader-mark { display: grid; width: 36px; height: 36px; place-items: center; border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); border-radius: var(--r-md); background: var(--workspace-navigation); }
.workspace-route-loader-logo { display: grid; place-items: center; color: var(--foreground); }
@keyframes loader-orbit-spin { to { transform: rotate(360deg); } }
@keyframes loader-logo-breathe { 0%, 100% { transform: translate(-50%, -50%) scale(.97); } 50% { transform: translate(-50%, -50%) scale(1.035); } }
@keyframes loader-frame-float { 0%, 100% { translate: 0 0; } 50% { translate: 0 -6px; } }
@keyframes loader-caret { 0%, 48% { opacity: 1; } 49%, 100% { opacity: 0; } }
@keyframes loader-timeline-progress { 0% { transform: scaleX(.15); opacity: .5; } 55% { transform: scaleX(1); opacity: 1; } 100% { transform: scaleX(1); opacity: 0; } }
@keyframes loader-signal-pulse { 0%, 100% { opacity: .25; transform: translateY(0); } 50% { opacity: 1; transform: translateY(-2px); } }
@media (max-width: 639px) {
.full-screen-loader-scene { width: min(260px, 76vw); }
.full-screen-loader-copy { top: calc(50% + 154px); }
}
@media (prefers-reduced-motion: reduce) {
.full-screen-loader-logo,
.full-screen-loader-orbit,
.full-screen-loader-frame,
.full-screen-loader-script,
.full-screen-loader-timeline,
.full-screen-loader-script b,
.full-screen-loader-timeline b,
.loading-signal i { animation: none; }
}
:where(.ant-btn) {
letter-spacing: 0;
transition:
@@ -5322,6 +5472,9 @@
.task-record-thumb { position: relative; display: grid; width: 84px; height: 60px; flex: 0 0 auto; place-items: center; overflow: hidden; border: 0; border-radius: var(--r-md); background: color-mix(in srgb, var(--foreground) 4%, transparent); padding: 0; color: color-mix(in srgb, var(--foreground) 28%, transparent); }
/* 媒体脱离 Grid 固有尺寸计算,避免竖图按原始宽高比把固定缩略图撑出任务行。 */
.task-record-thumb img, .task-record-thumb video { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: cover; }
.task-video-poster-placeholder { display: grid; width: 100%; height: 100%; place-content: center; justify-items: center; gap: 4px; background: color-mix(in srgb, var(--foreground) 3%, transparent); color: color-mix(in srgb, var(--foreground) 42%, transparent); }
.task-video-poster-placeholder small { font-size: var(--fs-micro); font-weight: 600; letter-spacing: .08em; }
.task-video-poster-placeholder > svg { width: 20px; height: 20px; }
/* 资源已被删除或失效时,由组件接管原生破图,相关页面仍保持可读。 */
.media-unavailable { display: flex; width: 100%; height: 100%; flex-direction: column; align-items: center; justify-content: center; gap: 5px; color: color-mix(in srgb, var(--foreground) 38%, transparent); font-size: var(--fs-tiny); line-height: 1.25; text-align: center; }
.media-unavailable > svg { width: 18px; height: 18px; flex: 0 0 auto; }
@@ -5410,17 +5563,53 @@
/* 项目概览:原本是「有边框的浅色盒子 + 一堆等重小字」,层级读不出来。
改成:主区留白撑开、标题字阶拉高、右栏用背景色而非边框分隔、流水线行给足行高。 */
.workflow-chapter-navigator { display: none; height: 32px; flex: 0 0 auto; align-items: center; overflow: hidden; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); }
.project-workspace-topbar { display: flex; width: 100%; min-width: 0; height: 44px; align-items: center; gap: 6px; transform: translateY(1px); }
.project-workspace-back, .project-workspace-create { display: grid; width: 30px; height: 30px; flex: 0 0 auto; place-items: center; border-radius: var(--r-md); color: color-mix(in srgb, var(--foreground) 44%, transparent); transition: background-color var(--motion-instant), color var(--motion-instant); }
.project-workspace-back:hover, .project-workspace-create:hover { background: var(--surface-hover); color: var(--foreground); }
.project-workspace-back:focus-visible, .project-workspace-create:focus-visible, .project-workspace-tab:focus-visible { outline: 2px solid var(--ring); outline-offset: -2px; }
.project-workspace-back svg, .project-workspace-create svg { width: 15px; height: 15px; }
.project-workspace-identity { display: flex; min-width: 84px; max-width: clamp(118px, 15vw, 220px); flex: 1 1 180px; align-items: center; gap: 7px; overflow: hidden; }
.project-workspace-identity h1 { min-width: 0; margin: 0; overflow: hidden; color: color-mix(in srgb, var(--foreground) 90%, transparent); font-size: var(--fs-caption); font-weight: 650; line-height: 1.2; text-overflow: ellipsis; white-space: nowrap; }
.project-workspace-status-dot { width: 6px; height: 6px; flex: 0 0 auto; border-radius: 999px; background: var(--workspace-accent); }
.project-workspace-status-dot.is-archived { background: color-mix(in srgb, var(--foreground) 30%, transparent); }
.project-workspace-status-label { flex: 0 0 auto; color: color-mix(in srgb, var(--foreground) 42%, transparent); font-size: var(--fs-tiny); white-space: nowrap; }
.project-workspace-tabs { display: flex; min-width: 0; height: 44px; flex: 1 1 auto; align-items: center; gap: 2px; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none; }
.project-workspace-tabs::-webkit-scrollbar { display: none; }
.project-workspace-tab { position: relative; display: inline-flex; height: 30px; flex: 0 0 auto; align-items: center; justify-content: center; gap: 5px; border-radius: var(--r-md); padding: 0 clamp(6px, .62vw, 9px); color: color-mix(in srgb, var(--foreground) 46%, transparent); font-size: clamp(11px, .76vw, 12px); font-weight: 560; line-height: 1; white-space: nowrap; transition: background-color var(--motion-instant), color var(--motion-instant); }
.project-workspace-tab:hover { background: var(--surface-hover); color: var(--foreground); }
.project-workspace-tab.is-active { background: var(--workspace-accent-soft); color: var(--workspace-accent); }
.project-workspace-tab.is-active::after { position: absolute; right: 9px; bottom: 0; left: 9px; height: 2px; border-radius: 999px; background: var(--workspace-accent); content: ""; }
.project-workspace-tab svg { width: 14px; height: 14px; flex: 0 0 auto; stroke-width: 1.8; }
@media (max-width: 1380px) {
.project-workspace-identity { max-width: 150px; flex-basis: 130px; }
.project-workspace-status-label { display: none; }
.project-workspace-tab { gap: 4px; padding-inline: 6px; font-size: 11px; }
}
@media (max-width: 900px) {
.project-workspace-identity { max-width: 112px; flex-basis: 96px; }
.project-workspace-status-dot { display: none; }
}
@media (max-width: 640px) {
.project-workspace-topbar { gap: 4px; }
.project-workspace-identity { min-width: 72px; max-width: 96px; flex-basis: 82px; }
}
.workflow-chapter-navigator { display: none; height: 30px; flex: 0 0 auto; align-items: center; overflow: hidden; border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); }
@media (min-width: 1280px) { .workflow-chapter-navigator { display: flex; } }
.workflow-chapter-navigator > button { display: grid; width: 30px; height: 30px; flex: 0 0 auto; place-items: center; color: color-mix(in srgb, var(--foreground) 48%, transparent); transition: background-color var(--motion-instant), color var(--motion-instant); }
.workflow-chapter-navigator > button { display: grid; width: 28px; height: 28px; flex: 0 0 auto; place-items: center; color: color-mix(in srgb, var(--foreground) 48%, transparent); transition: background-color var(--motion-instant), color var(--motion-instant); }
.workflow-chapter-navigator > button:hover:not(:disabled) { background: var(--surface-hover); color: var(--foreground); }
.workflow-chapter-navigator > button:disabled { cursor: not-allowed; opacity: .28; }
.workflow-chapter-navigator > button svg { width: 14px; height: 14px; }
.workflow-chapter-navigator .workflow-chapter-current { display: flex; width: clamp(190px, 19vw, 270px); padding: 0 8px; border-right: 1px solid var(--border); border-left: 1px solid var(--border); gap: 6px; text-align: left; }
.workflow-chapter-navigator .workflow-chapter-current { display: flex; width: clamp(124px, 11vw, 156px); padding: 0 7px; border-right: 1px solid var(--border); border-left: 1px solid var(--border); gap: 5px; text-align: left; }
.workflow-chapter-current > span { flex: 0 0 auto; color: color-mix(in srgb, var(--foreground) 44%, transparent); font-size: var(--fs-micro); font-variant-numeric: tabular-nums; }
.workflow-chapter-current > strong { min-width: 0; flex: 1 1 auto; overflow: hidden; color: var(--foreground); font-size: var(--fs-label); font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
.workflow-chapter-current > em { flex: 0 0 auto; color: color-mix(in srgb, var(--foreground) 35%, transparent); font-size: var(--fs-micro); font-style: normal; font-variant-numeric: tabular-nums; }
.workflow-chapter-current > svg { flex: 0 0 auto; }
@media (min-width: 1280px) and (max-width: 1439px) {
.workflow-chapter-navigator > button:first-child, .workflow-chapter-navigator > button:last-child { display: none; }
.workflow-chapter-navigator .workflow-chapter-current { width: 88px; justify-content: center; border: 0; padding-inline: 7px; }
.workflow-chapter-current > strong, .workflow-chapter-current > svg { display: none; }
}
.workspace-modal.workspace-modal-wide.workflow-chapter-modal { width: min(1360px, calc(100vw - 48px)) !important; }
.workflow-chapter-modal-shell { display: flex; height: min(820px, 88dvh); min-height: min(680px, 88dvh); flex-direction: column; color: var(--foreground); }
.workflow-chapter-modal-head { display: flex; flex: 0 0 auto; align-items: center; justify-content: space-between; gap: 24px; padding: 22px 64px 18px 24px; }
@@ -8951,9 +9140,34 @@
height: 28px;
align-items: center;
gap: var(--space-1);
border-radius: var(--r-md);
background: var(--canvas-composer-control-surface);
padding-inline: var(--space-2);
padding-inline: var(--space-1);
}
.canvas-node-composer-header-action {
border: 0;
background: transparent;
color: color-mix(in srgb, var(--cn-text) 72%, var(--canvas-composer-surface));
cursor: pointer;
white-space: nowrap;
transition: background-color var(--motion-instant) ease-out, color var(--motion-instant) ease-out;
}
.canvas-node-composer-header-action:hover,
.canvas-node-composer-header-action[aria-expanded="true"] {
background: var(--canvas-composer-control-hover) !important;
color: var(--cn-text);
}
.canvas-node-composer-header-action:focus-visible {
outline: 2px solid var(--cn-text);
outline-offset: 2px;
}
.canvas-node-composer-reference-heading {
flex-shrink: 0;
margin-right: var(--space-1);
font-size: var(--fs-micro);
color: color-mix(in srgb, var(--cn-text) 65%, var(--canvas-composer-surface));
}
.canvas-node-composer-icon-button:hover,
@@ -9527,6 +9741,33 @@
backdrop-filter: none !important;
}
/* LibTV 式默认选择光标:短促圆润的黑色箭头配细白描边,不使用发光或阴影。 */
.canvas-cursor-select {
cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='22' viewBox='0 0 20 22'%3E%3Cpath d='M4.05 2.75c-.78-.38-1.64.34-1.38 1.17l4.05 13.1c.26.84 1.38 1.01 1.88.28l2.35-3.38 4.14-.63c.88-.14 1.17-1.25.46-1.78L4.05 2.75Z' fill='%23070707' stroke='%23fff' stroke-width='1.5' stroke-linejoin='round'/%3E%3C/svg%3E") 3 3, default;
}
/* 框选移动期只标记发生变化的 DOM 节点,避免每帧提交 React 选择状态。 */
[data-node-id][data-canvas-selection-preview="include"] {
outline: calc(2px * var(--canvas-live-inverse-scale)) solid var(--workspace-accent);
outline-offset: calc(3px * var(--canvas-live-inverse-scale));
border-radius: var(--node-radius);
}
[data-node-id][data-canvas-selection-preview="remove"] {
outline: calc(2px * var(--canvas-live-inverse-scale)) dashed var(--destructive);
outline-offset: calc(3px * var(--canvas-live-inverse-scale));
border-radius: var(--node-radius);
opacity: 0.72;
}
[data-canvas-pan-state="grab"] [data-node-id] {
cursor: grab !important;
}
[data-canvas-pan-state="grabbing"] [data-node-id] {
cursor: grabbing !important;
}
/* 静止期用最终倍率重新排版 DOM,避免 Windows Chromium 复用交互期的低分辨率合成纹理。 */
.canvas-world-layer {
transform: translate(var(--canvas-live-x), var(--canvas-live-y)) scale(var(--canvas-live-scale-ratio));
@@ -9548,6 +9789,16 @@
animation: none !important;
}
[data-canvas-viewport-interacting="true"] :where(.canvas-node-toolbar, [data-canvas-node-panel]) {
will-change: transform;
}
[data-canvas-viewport-interacting="true"] :where(.canvas-node-toolbar, .canvas-node-toolbar > div, [data-canvas-node-panel], [data-canvas-node-panel] .canvas-floating-panel, [data-canvas-node-panel] .canvas-node-panel-placement-change) {
animation-play-state: paused !important;
filter: none !important;
backdrop-filter: none !important;
}
/* 节点状态徽章 success 淡出动画(对应 #97 决策2)*/
@keyframes canvas-status-success-fade {
0% { opacity: 1; transform: scale(1); }
@@ -16436,9 +16687,9 @@ html:not(.dark) .creation-home .storyboard-workbench-bar-action:hover { backgrou
width: min(680px, 86%);
max-width: none;
flex-direction: column;
align-items: flex-end;
align-items: flex-start;
gap: var(--space-2);
margin: 0 0 var(--space-10) auto;
margin: 0 0 var(--space-10);
border-radius: 0;
background: transparent;
padding: 0;
@@ -16493,7 +16744,7 @@ html:not(.dark) .creation-home .creation-user-message { background: transparent;
color: var(--creation-text);
font-size: var(--fs-body);
line-height: 1.75;
text-align: right;
text-align: left;
white-space: pre-wrap;
overflow-wrap: anywhere;
}
@@ -16515,6 +16766,23 @@ html:not(.dark) .creation-home .creation-user-message { background: transparent;
.creation-home .creation-message-heading strong { color: var(--creation-text); font-size: var(--fs-body); font-weight: 620; }
.creation-home .creation-message-progress-copy { color: var(--creation-muted); font-size: var(--fs-body); }
.creation-home .creation-message-variant-action {
display: inline-flex;
min-height: var(--space-7);
align-items: center;
gap: var(--space-1);
margin-left: auto;
border: 1px solid var(--creation-border);
border-radius: var(--r-sm);
background: var(--creation-surface);
color: var(--creation-muted);
padding: 0 var(--space-2);
font-size: var(--fs-label);
cursor: pointer;
}
.creation-home .creation-message-variant-action:hover { background: var(--creation-surface-hover); color: var(--creation-text); }
.creation-home .creation-message-variant-action:focus-visible { outline: 2px solid var(--creation-accent); outline-offset: 1px; }
.creation-home .creation-message-variant-action svg { width: var(--space-3); height: var(--space-3); }
.creation-home .creation-message-content {
max-width: 720px;