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
33 changes: 29 additions & 4 deletions frontend/src/components/ChatArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,24 @@ export default function ChatArea({
const messagesEndRef = useRef<HTMLDivElement | null>(null);
const previousMessageCountRef = useRef(0);

const hasMessages = messages.length > 0;
const [welcomeRendered, setWelcomeRendered] = useState(!hasMessages);
const [welcomeVisible, setWelcomeVisible] = useState(!hasMessages);

useEffect(() => {
if (!hasMessages) {
setWelcomeRendered(true);
setWelcomeVisible(true);
return;
}

if (!welcomeVisible) return;

setWelcomeVisible(false);
const timer = window.setTimeout(() => setWelcomeRendered(false), 350);
return () => window.clearTimeout(timer);
}, [hasMessages, welcomeVisible]);

const handleSendMessage = async (query: string) => {
await sendQuery(query);
};
Expand Down Expand Up @@ -212,11 +230,18 @@ export default function ChatArea({
{/* Chat messages area */}
<div
ref={messageListRef}
className={`flex-1 p-4 ${messages.length > 0 ? 'overflow-y-auto' : 'overflow-hidden'}`}
className={`relative flex-1 p-4 ${hasMessages ? 'overflow-y-auto' : 'overflow-hidden'}`}
>
{messages.length === 0 ? (
<WelcomeScreen onQuestionSelect={handleQuestionSelect} />
) : (
{welcomeRendered && (
<div
className={`absolute inset-0 p-4 transition-opacity duration-300 ease-out ${
welcomeVisible ? 'opacity-100' : 'opacity-0 pointer-events-none'
}`}
>
<WelcomeScreen onQuestionSelect={handleQuestionSelect} />
</div>
)}
{hasMessages && (
<div className="mx-auto max-w-4xl space-y-6">
{messages.map((message, index) => {
// Find if this is the first user message
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/MessageBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export default function MessageBubble({
if (message.role === 'user') {
return (
<div className={`flex justify-end ${isFirstUserMessage ? 'mt-20' : ''}`}>
<div className="max-w-xs lg:max-w-md">
<div className="max-w-xs lg:max-w-md animate-user-bubble-enter">
<div className="message-bubble-user px-4 py-3 rounded-xl text-[0.9375rem] leading-relaxed tracking-[-0.01em]">
<p>{message.content}</p>
</div>
Expand Down
18 changes: 16 additions & 2 deletions frontend/src/components/WelcomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { useState } from 'react';
import { exampleQuestions } from '../data/exampleQuestions';
import { welcomeScreenContent } from '../data/welcomeScreenContent';

interface WelcomeScreenProps {
onQuestionSelect: (question: string) => void;
}

const LIFT_DURATION_MS = 180;

export default function WelcomeScreen({ onQuestionSelect }: WelcomeScreenProps) {
const [liftedIndex, setLiftedIndex] = useState<number | null>(null);

const featuredQuestions = welcomeScreenContent.featuredQuestionIds
.map((id) => exampleQuestions.find((question) => question.id === id)?.question)
.filter((question): question is string => Boolean(question));
Expand All @@ -15,6 +20,12 @@ export default function WelcomeScreen({ onQuestionSelect }: WelcomeScreenProps)
return delays[index] || '';
};

const handleQuestionClick = (question: string, index: number) => {
if (liftedIndex !== null) return;
setLiftedIndex(index);
window.setTimeout(() => onQuestionSelect(question), LIFT_DURATION_MS);
};

return (
<div className="flex h-full w-full items-center justify-center">
<div className="mx-auto w-full max-w-2xl space-y-8 lg:space-y-10 text-center px-4">
Expand All @@ -33,8 +44,11 @@ export default function WelcomeScreen({ onQuestionSelect }: WelcomeScreenProps)
{featuredQuestions.map((question, index) => (
<button
key={index}
onClick={() => onQuestionSelect(question)}
className={`glass-card rounded-xl p-4 text-left w-full ${getDelayClass(index)}`}
onClick={() => handleQuestionClick(question, index)}
disabled={liftedIndex !== null}
className={`glass-card rounded-xl p-4 text-left w-full ${getDelayClass(index)} ${
liftedIndex === index ? 'is-lifting' : ''
}`}
>
<span className="relative z-10 text-[0.8125rem] lg:text-[0.9375rem] font-medium leading-snug tracking-[-0.01em] text-[var(--color-text-primary)]">
{question}
Expand Down
31 changes: 31 additions & 0 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,22 @@
transform: scale(0.992);
}

.glass-card.is-lifting,
.glass-card.is-lifting:hover {
transform: translateY(-6px) scale(1.025);
box-shadow: var(--glass-inset), var(--shadow-lg);
border-color: rgba(0, 113, 227, 0.30);
transition:
transform 220ms cubic-bezier(0.34, 1.56, 0.64, 1),
box-shadow 220ms var(--ease-out),
border-color 200ms ease;
}

.dark .glass-card.is-lifting,
.dark .glass-card.is-lifting:hover {
border-color: rgba(10, 132, 255, 0.35);
}

.dark .glass-card {
background: rgba(255, 255, 255, 0.04);
border-color: rgba(255, 255, 255, 0.07);
Expand Down Expand Up @@ -415,6 +431,21 @@
animation: chatbot-bubble-enter 380ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

@keyframes user-bubble-enter {
0% {
opacity: 0;
transform: translateY(12px) scale(0.97);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}

.animate-user-bubble-enter {
animation: user-bubble-enter 420ms cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

@keyframes fadeIn {
0% { opacity: 0; }
100% { opacity: 1; }
Expand Down