Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import React from 'react';

interface GalleryPageHeaderProps {
title: string;
title: React.ReactNode;
subtitle?: React.ReactNode;
actions?: React.ReactNode;
extraContent?: React.ReactNode;
className?: string;
}

const GalleryPageHeader: React.FC<GalleryPageHeaderProps> = ({
title,
subtitle,
actions,
extraContent,
className,
}) => (
<div className="gallery-page-header">
<div className={['gallery-page-header', className].filter(Boolean).join(' ')}>
<div className="gallery-page-header__identity">
<h2 className="gallery-page-header__title">{title}</h2>
{subtitle ? <div className="gallery-page-header__subtitle">{subtitle}</div> : null}
Expand Down
16 changes: 8 additions & 8 deletions src/web-ui/src/app/components/NavPanel/MainNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ const MainNav: React.FC<MainNavProps> = ({
}, [resolvedAssistantWorkspace, setSelectedAssistantWorkspaceId]);

const handleOpenProModeSession = useCallback(async () => {
// 找到项目工作区(非 assistant 类型)
// Pick a project workspace (non-assistant)
const projectWorkspaces = openedWorkspacesList.filter(
w => w.workspaceKind !== WorkspaceKind.Assistant
);
Expand All @@ -481,7 +481,7 @@ const MainNav: React.FC<MainNavProps> = ({
? currentWorkspace
: projectWorkspaces[0] ?? null;

// 若当前激活的是 assistant workspace,先切回项目工作区
// If assistant workspace is active, switch to a project workspace first
if (targetWorkspace && currentWorkspace?.id !== targetWorkspace.id) {
await setActiveWorkspace(targetWorkspace.id).catch(() => {});
}
Expand Down Expand Up @@ -509,7 +509,7 @@ const MainNav: React.FC<MainNavProps> = ({
}
}

// 没有已有会话,显式传入 workspacePath 创建 Code 会话,避免被 assistant workspace 覆盖
// No session yet: pass workspacePath so Code session creation is not overridden by assistant workspace
openScene('session');
switchLeftPanelTab('sessions');
await flowChatManager.createChatSession({ workspacePath: workspacePath || undefined }, 'agentic');
Expand Down Expand Up @@ -548,20 +548,20 @@ const MainNav: React.FC<MainNavProps> = ({
}
}

// 没有已有会话,新建 Claw 会话
// No session yet: create a Claw session
await handleCreateSession('Claw');
}, [currentWorkspace, defaultAssistantWorkspace, handleCreateSession, isAssistantWorkspaceActive, openScene, setActiveWorkspace, switchLeftPanelTab]);

const handleToggleNavDisplayMode = useCallback(() => {
// 防止动画进行中重复触发
// Ignore repeat clicks while the transition runs
if (modeSwitchTimerRef.current !== null) return;

setIsModeSwitching(true);

// 点击时同步计算目标模式,避免 timeout 闭包中读取到过期值
// Resolve target mode synchronously on click so timeouts do not close over a stale value
const nextMode: NavDisplayMode = navDisplayMode === 'pro' ? 'assistant' : 'pro';

// 200msclip-path 收缩到最小圆点):只切换 nav 显示状态,不触发任何场景/会话操作
// 200ms (clip-path at smallest dot): only flip nav display; no scene/session changes yet
if (modeSwitchSwapTimerRef.current !== null) {
window.clearTimeout(modeSwitchSwapTimerRef.current);
}
Expand All @@ -571,7 +571,7 @@ const MainNav: React.FC<MainNavProps> = ({
modeSwitchSwapTimerRef.current = null;
}, 200);

// 480ms(动画完全结束):再切场景和会话,避免 tab 文字在动画期间闪动
// 480ms (animation finished): then switch scene/session so tab labels do not flicker mid-animation
modeSwitchTimerRef.current = window.setTimeout(() => {
setIsModeSwitching(false);
modeSwitchTimerRef.current = null;
Expand Down
23 changes: 14 additions & 9 deletions src/web-ui/src/app/components/NavPanel/NavPanel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ $_section-header-height: 24px;
}

&.is-switching {
// 整体 clip-path 涟漪收缩 → 展开,linear 因为各段 easing 写在关键帧内部
// Full clip-path ripple: shrink then expand; linear outer timing because easing is set on keyframes
animation: bitfun-nav-mode-switch 0.48s linear;

.bitfun-nav-panel__mode-switch-logo {
Expand Down Expand Up @@ -498,7 +498,7 @@ $_section-header-height: 24px;
border-radius: 2px;
}

// 与按钮涟漪同步:列表内容淡出后换组,再淡入
// Synced with button ripple: fade list out, swap group, fade in
&.is-mode-switching {
animation: bitfun-nav-sections-mode-switch 0.48s linear;
}
Expand Down Expand Up @@ -1180,9 +1180,9 @@ $_section-header-height: 24px;

}

// ── 形变涟漪:按钮 clip-path 收缩成圆点,再弹开回矩形 ──────────────────────
// 各关键帧内用 animation-timing-function 分段控制 easing:
// 收缩段用 ease-in(快速压缩);展开段用 spring curve(弹性回弹)
// ── Morph ripple: button clip-path shrinks to a dot, then springs back to a rectangle ──
// Split easing via animation-timing-function on each leg:
// shrink uses ease-in (fast squeeze); expand uses a springy cubic-bezier
@keyframes bitfun-nav-mode-switch {
0% {
clip-path: circle(120% at 50% 50%);
Expand All @@ -1192,7 +1192,7 @@ $_section-header-height: 24px;
clip-path: circle(26px at 50% 50%);
animation-timing-function: linear;
}
// 内容在此帧附近(200ms)悄然替换,圆点完全遮住跳变
// Content swaps around this hold (~200ms); the dot masks the jump
58% {
clip-path: circle(26px at 50% 50%);
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
Expand All @@ -1202,7 +1202,7 @@ $_section-header-height: 24px;
}
}

// Logo:缩小旋转消失,旋转反向放大出现
// Logo: shrink + rotate out, reverse-rotate in while scaling up
@keyframes bitfun-nav-mode-switch-logo {
0% {
transform: scale(1) rotate(0deg);
Expand All @@ -1225,7 +1225,7 @@ $_section-header-height: 24px;
}
}

// 文字区:向下淡出,从上淡入
// Copy block: fade out downward, fade in from above
@keyframes bitfun-nav-mode-switch-copy {
0% {
opacity: 1;
Expand Down Expand Up @@ -1256,7 +1256,7 @@ $_section-header-height: 24px;
// Footer: Notification + More-options menu
// ──────────────────────────────────────────────

// sections 与按钮涟漪联动:内容淡出后从对侧滑入
// Sections tied to button ripple: fade out then slide in from the opposite side
@keyframes bitfun-nav-sections-mode-switch {
0% {
opacity: 1;
Expand Down Expand Up @@ -1326,6 +1326,11 @@ $_section-header-height: 24px;
flex-shrink: 0;
}

// Footer notification: yellow only when there are unread messages (BellDot)
.bitfun-nav-panel__footer .bitfun-notification-btn .bitfun-notification-btn__icon--has-message {
color: #ca8a04;
}

.bitfun-nav-panel__footer-btn--icon {
display: flex;
align-items: center;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@
}
}

// ── 助理模式 item(独立样式,不与专业模式共享)──────────────────────────────
// ── Assistant-mode row item (standalone styles, not shared with pro mode) ────────────
.bitfun-nav-panel {
&__assistant-item {
position: relative;
Expand Down
54 changes: 14 additions & 40 deletions src/web-ui/src/app/components/SplashScreen/SplashScreen.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,14 @@
pointer-events: none;

// ── Exit state ─────────────────────────────────────────────────────────────
// Whole container fades together — logo, dots, and backdrop all in sync.
// Whole container fades together — logo and backdrop in sync.

&--exiting {
animation: splash-bg-exit 0.5s ease-in-out both;

.splash-screen__logo-wrap {
animation: none; // Stop idle pulse; let container opacity drive the fade
}

.splash-screen__dots {
animation: none;
}
}
}

Expand All @@ -43,49 +39,34 @@
display: flex;
flex-direction: column;
align-items: center;
gap: 28px;
}

// ── Logo ──────────────────────────────────────────────────────────────────────

.splash-screen__logo-wrap {
animation: splash-logo-idle 2.6s ease-in-out infinite;
animation: splash-logo-idle 3.2s ease-in-out infinite;
}

.splash-screen__logo {
display: block;
width: 56px;
height: 56px;
width: 112px;
height: 112px;
border-radius: $size-radius-lg;
user-select: none;
}

// ── Loading dots ──────────────────────────────────────────────────────────────

.splash-screen__dots {
display: flex;
align-items: center;
gap: 8px;
}

.splash-screen__dot {
display: block;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--color-text-muted);

&:nth-child(1) { animation: splash-dot 1.4s ease-in-out 0.0s infinite; }
&:nth-child(2) { animation: splash-dot 1.4s ease-in-out 0.2s infinite; }
&:nth-child(3) { animation: splash-dot 1.4s ease-in-out 0.4s infinite; }
}

// ── Keyframes ─────────────────────────────────────────────────────────────────

// Idle logo: gentle breathing pulse
// Idle logo: soft opacity pulse with a slight breathing scale
@keyframes splash-logo-idle {
0%, 100% { opacity: 0.78; transform: scale(1); }
50% { opacity: 1; transform: scale(1.04); }
0%, 100% {
opacity: 0.38;
transform: scale(0.98);
}
50% {
opacity: 0.98;
transform: scale(1);
}
}

// Exit: whole container fades to transparent
Expand All @@ -94,17 +75,10 @@
100% { opacity: 0; }
}

// Dot loading pulse without vertical movement
@keyframes splash-dot {
0%, 100% { opacity: 0.3; transform: scale(0.92); }
50% { opacity: 0.9; transform: scale(1.16); }
}

// ── Reduced motion ────────────────────────────────────────────────────────────

@media (prefers-reduced-motion: reduce) {
.splash-screen__logo-wrap,
.splash-screen__dot {
.splash-screen__logo-wrap {
animation: none;
}

Expand Down
9 changes: 1 addition & 8 deletions src/web-ui/src/app/components/SplashScreen/SplashScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* SplashScreen — full-screen loading overlay shown on app start.
*
* Idle: logo breathes softly; loading dots bounce.
* Idle: logo larger, soft fade in/out.
* Exiting: logo scales up and fades; backdrop dissolves.
*/

Expand Down Expand Up @@ -30,7 +30,6 @@ const SplashScreen: React.FC<SplashScreenProps> = ({ isExiting, onExited }) => {
className={`splash-screen${isExiting ? ' splash-screen--exiting' : ''}`}
aria-hidden="true"
>
{/* Center: logo + loading dots */}
<div className="splash-screen__center">
<div className="splash-screen__logo-wrap">
<img
Expand All @@ -40,12 +39,6 @@ const SplashScreen: React.FC<SplashScreenProps> = ({ isExiting, onExited }) => {
draggable={false}
/>
</div>

<div className="splash-screen__dots" aria-hidden="true">
<span className="splash-screen__dot" />
<span className="splash-screen__dot" />
<span className="splash-screen__dot" />
</div>
</div>
</div>
);
Expand Down
58 changes: 33 additions & 25 deletions src/web-ui/src/app/scenes/SceneViewport.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,49 @@
overflow: hidden;
min-height: 0;
border-radius: $size-radius-base;
border: 1px solid var(--border-subtle);
border: none;
background: var(--color-bg-scene);
transition:
border-color 240ms cubic-bezier(0.25, 1, 0.5, 1),
box-shadow 240ms cubic-bezier(0.25, 1, 0.5, 1);

&__clip {
position: absolute;
inset: 0;
overflow: hidden;
border-radius: inherit;
}

// Divider hover / resize: inner glow only (::after paints above scene content).
&__clip::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
z-index: $z-decoration;
border-radius: inherit;
opacity: 0;
transition: opacity 240ms cubic-bezier(0.25, 1, 0.5, 1);
box-shadow: inset 0 0 72px -18px rgba(96, 165, 250, 0.14);
}

body.bitfun-divider-hovered &__clip::after,
body.bitfun-is-resizing-nav &__clip::after {
opacity: 1;
}

body.bitfun-is-resizing-nav &__clip::after {
box-shadow: inset 0 0 64px -16px rgba(96, 165, 250, 0.16);
}

// ── Welcome overlay (app start) ──────────────────────

&--welcome {
&__clip--welcome {
display: flex;
align-items: center;
justify-content: center;
}

// ── Empty state (all tabs closed) ─────────────────────

&--empty {
&__clip--empty {
display: flex;
align-items: center;
justify-content: center;
Expand All @@ -53,26 +79,8 @@
}
}

// ── Drag handle hover / active glow effect ──────────────────────────────

body.bitfun-divider-hovered .bitfun-scene-viewport {
border-color: var(--border-accent-strong);
box-shadow:
inset 6px 0 32px -4px rgba(96, 165, 250, 0.22),
inset 0 0 60px -16px rgba(96, 165, 250, 0.10),
-3px 0 20px -4px rgba(96, 165, 250, 0.30);
}

body.bitfun-is-resizing-nav .bitfun-scene-viewport {
border-color: var(--border-accent-strong);
box-shadow:
inset 4px 0 24px -4px rgba(96, 165, 250, 0.20),
inset 0 0 48px -16px rgba(96, 165, 250, 0.08),
-2px 0 18px -4px rgba(96, 165, 250, 0.28);
}

@media (prefers-reduced-motion: reduce) {
.bitfun-scene-viewport {
.bitfun-scene-viewport__clip::after {
transition: none;
}
}
Loading
Loading