Improve mobile layout across landing and portfolio pages

This commit is contained in:
Thigazhezhilan J 2026-04-01 23:46:21 +05:30
parent d63202261a
commit e183f9ddce
12 changed files with 185 additions and 141 deletions

View File

@ -8,11 +8,11 @@ type ChatButtonProps = {
export default function ChatButton({ open, onToggle }: ChatButtonProps) {
return (
<div className="fixed bottom-6 right-6 z-[60] flex flex-col items-end gap-2">
<div className="fixed bottom-4 right-4 z-[60] flex flex-col items-end gap-2 sm:bottom-6 sm:right-6">
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="rounded-full bg-white/10 px-3 py-1 text-xs text-slate-100 shadow-lg backdrop-blur"
className="hidden rounded-full bg-white/10 px-3 py-1 text-xs text-slate-100 shadow-lg backdrop-blur sm:block"
>
Need help?
</motion.div>
@ -20,7 +20,7 @@ export default function ChatButton({ open, onToggle }: ChatButtonProps) {
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.96 }}
onClick={onToggle}
className="relative flex h-14 w-14 items-center justify-center rounded-full bg-gradient-to-br from-sky-500 to-cyan-400 text-white shadow-xl"
className="relative flex h-12 w-12 items-center justify-center rounded-full bg-gradient-to-br from-sky-500 to-cyan-400 text-white shadow-xl sm:h-14 sm:w-14"
aria-label={open ? "Close chat" : "Open chat"}
>
<motion.span
@ -28,7 +28,7 @@ export default function ChatButton({ open, onToggle }: ChatButtonProps) {
animate={{ scale: [1, 1.4, 1], opacity: [0.6, 0, 0.6] }}
transition={{ duration: 2.2, repeat: Infinity, ease: "easeInOut" }}
/>
<MessageCircle className="relative z-10 h-6 w-6" />
<MessageCircle className="relative z-10 h-5 w-5 sm:h-6 sm:w-6" />
</motion.button>
</div>
);

View File

@ -91,7 +91,7 @@ export default function ChatWidget() {
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 20, scale: 0.98 }}
transition={{ duration: 0.25, ease: "easeOut" }}
className="fixed bottom-6 right-4 top-20 z-[60] w-[372px] max-w-[calc(100vw-2rem)]"
className="fixed bottom-20 left-3 right-3 z-[60] h-[min(68vh,32rem)] sm:bottom-6 sm:left-auto sm:right-4 sm:top-20 sm:h-auto sm:w-[372px] sm:max-w-[calc(100vw-2rem)]"
>
<div className="flex h-full flex-col overflow-hidden rounded-3xl border border-white/10 bg-slate-900/70 shadow-2xl backdrop-blur-xl">
<div className="flex items-center justify-between border-b border-white/10 px-5 py-4">

View File

@ -22,6 +22,7 @@ type BrokerConnectDialogProps = {
layout?: "desktop" | "mobile";
open?: boolean;
onOpenChange?: (open: boolean) => void;
triggerClassName?: string;
};
type SessionUser = Pick<User, "id" | "username">;
@ -48,6 +49,7 @@ export default function BrokerConnectDialog({
layout = "desktop",
open,
onOpenChange,
triggerClassName: triggerClassNameProp,
}: BrokerConnectDialogProps) {
const [connectOpenInternal, setConnectOpenInternal] = useState(false);
const isControlled = open !== undefined;
@ -243,7 +245,7 @@ export default function BrokerConnectDialog({
<Dialog open={connectOpen} onOpenChange={setConnectOpen}>
<Button
variant={connected ? "secondary" : "secondary"}
className={triggerClassName}
className={[triggerClassName, triggerClassNameProp].filter(Boolean).join(" ")}
disabled={connected}
onClick={handleConnectClick}
>

View File

@ -40,12 +40,12 @@ export default function FinalCTA({ onExploreStrategies }: FinalCTAProps) {
return (
<section
ref={sectionRef}
className="py-32 px-6 relative overflow-hidden"
className="relative overflow-hidden px-4 py-20 sm:px-6 sm:py-24 md:py-32"
data-testid="section-final-cta"
onMouseMove={handleMouseMove}
>
<div
className={`absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] h-[500px] transition-all duration-1000 ${
className={`absolute top-1/2 left-1/2 h-[280px] w-[280px] -translate-x-1/2 -translate-y-1/2 transition-all duration-1000 sm:h-[360px] sm:w-[360px] md:h-[500px] md:w-[500px] ${
isVisible ? "opacity-100 scale-100" : "opacity-0 scale-50"
}`}
style={{
@ -79,7 +79,7 @@ export default function FinalCTA({ onExploreStrategies }: FinalCTAProps) {
}`}
>
<h2
className="text-4xl md:text-5xl font-bold mb-6 leading-tight"
className="mb-6 text-3xl font-bold leading-tight sm:text-4xl md:text-5xl"
data-testid="text-final-cta-headline"
>
<span
@ -99,7 +99,7 @@ export default function FinalCTA({ onExploreStrategies }: FinalCTAProps) {
</span>
</h2>
<p
className={`text-xl text-muted-foreground mb-12 max-w-xl mx-auto transition-all duration-700 delay-400 ${
className={`mx-auto mb-10 max-w-xl text-base text-muted-foreground transition-all duration-700 delay-400 sm:mb-12 sm:text-lg md:text-xl ${
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"
}`}
>
@ -113,7 +113,7 @@ export default function FinalCTA({ onExploreStrategies }: FinalCTAProps) {
>
<Button
size="lg"
className="rounded-xl px-10 py-6 text-lg group relative overflow-hidden"
className="group relative mx-auto w-full max-w-[20rem] overflow-hidden rounded-xl px-6 py-5 text-base sm:max-w-none sm:px-10 sm:py-6 sm:text-lg"
data-testid="button-final-cta"
onClick={onExploreStrategies}
>

View File

@ -14,12 +14,27 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
const [isLoaded, setIsLoaded] = useState(false);
const [scrollY, setScrollY] = useState(0);
const [isCompactViewport, setIsCompactViewport] = useState(false);
const [, navigate] = useLocation();
useEffect(() => {
setIsLoaded(true);
}, []);
useEffect(() => {
const mediaQuery = window.matchMedia("(max-width: 639px)");
const updateViewportMode = () => setIsCompactViewport(mediaQuery.matches);
updateViewportMode();
if (mediaQuery.addEventListener) {
mediaQuery.addEventListener("change", updateViewportMode);
return () => mediaQuery.removeEventListener("change", updateViewportMode);
}
mediaQuery.addListener(updateViewportMode);
return () => mediaQuery.removeListener(updateViewportMode);
}, []);
useEffect(() => {
const handleScroll = () => {
setScrollY(window.scrollY);
@ -29,6 +44,10 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
}, []);
useEffect(() => {
if (isCompactViewport) {
setMousePosition({ x: 0, y: 0 });
return;
}
const handleMouseMove = (e: MouseEvent) => {
if (!containerRef.current) return;
const rect = containerRef.current.getBoundingClientRect();
@ -40,16 +59,17 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
const container = containerRef.current;
container?.addEventListener("mousemove", handleMouseMove);
return () => container?.removeEventListener("mousemove", handleMouseMove);
}, []);
}, [isCompactViewport]);
const parallaxY = scrollY * 0.5;
const videoScale = 1 + scrollY * 0.0003;
const videoOpacity = Math.max(0.4 - scrollY * 0.0005, 0.15);
const parallaxY = isCompactViewport ? scrollY * 0.18 : scrollY * 0.5;
const videoScale = 1 + scrollY * (isCompactViewport ? 0.00012 : 0.0003);
const videoOpacity = Math.max((isCompactViewport ? 0.3 : 0.4) - scrollY * 0.0005, 0.15);
const particleCount = isCompactViewport ? 10 : 25;
return (
<section
ref={containerRef}
className="relative min-h-screen flex items-center justify-center overflow-hidden"
className="relative flex min-h-[100svh] items-center justify-center overflow-hidden px-4 sm:px-0"
data-testid="section-hero"
style={{ perspective: "1000px" }}
>
@ -86,7 +106,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
}}
>
<div
className={`absolute top-1/4 left-1/4 w-96 h-96 bg-primary/20 rounded-full blur-3xl transition-all duration-1000 ${
className={`absolute left-[12%] top-[18%] h-56 w-56 rounded-full bg-primary/20 blur-3xl transition-all duration-1000 sm:top-1/4 sm:left-1/4 sm:h-96 sm:w-96 ${
isLoaded ? "opacity-100 scale-100" : "opacity-0 scale-50"
}`}
style={{
@ -94,7 +114,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
}}
/>
<div
className={`absolute bottom-1/3 right-1/4 w-80 h-80 bg-chart-2/20 rounded-full blur-3xl transition-all duration-1000 delay-200 ${
className={`absolute bottom-[18%] right-[8%] h-48 w-48 rounded-full bg-chart-2/20 blur-3xl transition-all duration-1000 delay-200 sm:bottom-1/3 sm:right-1/4 sm:h-80 sm:w-80 ${
isLoaded ? "opacity-100 scale-100" : "opacity-0 scale-50"
}`}
style={{
@ -102,7 +122,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
}}
/>
<div
className={`absolute top-1/2 right-1/3 w-64 h-64 bg-chart-3/15 rounded-full blur-3xl transition-all duration-1000 delay-500 ${
className={`absolute right-[10%] top-[46%] h-40 w-40 rounded-full bg-chart-3/15 blur-3xl transition-all duration-1000 delay-500 sm:top-1/2 sm:right-1/3 sm:h-64 sm:w-64 ${
isLoaded ? "opacity-100 scale-100" : "opacity-0 scale-50"
}`}
style={{
@ -112,7 +132,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
</div>
<div className="absolute inset-0 overflow-hidden z-10">
{[...Array(25)].map((_, i) => (
{[...Array(particleCount)].map((_, i) => (
<div
key={i}
className={`absolute rounded-full transition-all duration-700 ${
@ -144,7 +164,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
}}
>
<div
className={`absolute top-20 left-[15%] w-20 h-20 transition-all duration-1000 ${
className={`absolute left-[8%] top-24 h-12 w-12 transition-all duration-1000 sm:top-20 sm:left-[15%] sm:h-20 sm:w-20 ${
isLoaded ? "opacity-100 rotate-12" : "opacity-0 rotate-0"
}`}
style={{
@ -156,7 +176,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
<DollarSign className="w-full h-full text-primary/25 drop-shadow-sm" strokeWidth={1.25} />
</div>
<div
className={`absolute bottom-32 right-[20%] w-16 h-16 transition-all duration-1000 delay-300 ${
className={`absolute bottom-28 right-[12%] h-10 w-10 transition-all duration-1000 delay-300 sm:bottom-32 sm:right-[20%] sm:h-16 sm:w-16 ${
isLoaded ? "opacity-100" : "opacity-0"
}`}
style={{
@ -168,7 +188,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
<IndianRupee className="w-full h-full text-chart-2/25 drop-shadow-sm" strokeWidth={1.25} />
</div>
<div
className={`absolute top-1/3 right-[10%] w-12 h-12 transition-all duration-1000 delay-500 ${
className={`absolute right-[10%] top-[28%] h-8 w-8 transition-all duration-1000 delay-500 sm:top-1/3 sm:h-12 sm:w-12 ${
isLoaded ? "opacity-100 rotate-45" : "opacity-0 rotate-0"
}`}
style={{
@ -182,7 +202,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
</div>
<div
className="relative z-20 max-w-4xl mx-auto px-6 text-center"
className="relative z-20 mx-auto max-w-4xl px-4 text-center sm:px-6"
style={{
transform: `translate3d(${mousePosition.x * 0.15}px, ${mousePosition.y * 0.15}px, 100px)`,
transformStyle: "preserve-3d",
@ -194,7 +214,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
}`}
>
<h1
className="text-5xl md:text-7xl font-bold tracking-tight mb-6 leading-tight"
className="mb-6 text-4xl font-bold leading-tight tracking-tight sm:text-5xl md:text-7xl"
data-testid="text-hero-headline"
style={{ transform: "translateZ(20px)" }}
>
@ -215,7 +235,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
</div>
<p
className={`text-xl md:text-2xl text-muted-foreground max-w-2xl mx-auto mb-12 leading-relaxed transition-all duration-1000 delay-700 ${
className={`mx-auto mb-10 max-w-2xl text-base leading-relaxed text-muted-foreground transition-all duration-1000 delay-700 sm:mb-12 sm:text-lg md:text-2xl ${
isLoaded ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8"
}`}
data-testid="text-hero-subheadline"
@ -226,14 +246,14 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
</p>
<div
className={`flex flex-col sm:flex-row items-center justify-center gap-4 transition-all duration-1000 delay-1000 ${
className={`flex flex-col items-stretch justify-center gap-4 transition-all duration-1000 delay-1000 sm:flex-row sm:items-center ${
isLoaded ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8"
}`}
style={{ transform: "translateZ(30px)" }}
>
<Button
size="lg"
className="rounded-xl px-8 py-6 text-lg group relative overflow-hidden shadow-lg shadow-primary/20"
className="group relative mx-auto w-full max-w-[20rem] overflow-hidden rounded-xl px-6 py-5 text-base shadow-lg shadow-primary/20 sm:max-w-none sm:px-8 sm:py-6 sm:text-lg"
data-testid="button-explore-strategies"
onClick={onExploreStrategies}
>
@ -246,7 +266,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
<Button
variant="ghost"
size="lg"
className="rounded-xl px-8 py-6 text-lg text-muted-foreground backdrop-blur-sm"
className="mx-auto w-full max-w-[20rem] rounded-xl px-6 py-5 text-base text-muted-foreground backdrop-blur-sm sm:max-w-none sm:px-8 sm:py-6 sm:text-lg"
data-testid="button-learn-more"
onClick={() => navigate("/learn-more")}
>
@ -256,7 +276,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) {
</div>
<div
className={`absolute bottom-8 left-1/2 -translate-x-1/2 z-20 transition-all duration-1000 delay-1500 ${
className={`absolute bottom-8 left-1/2 z-20 hidden -translate-x-1/2 transition-all duration-1000 delay-1500 sm:block ${
isLoaded ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"
}`}
>

View File

@ -66,10 +66,10 @@ export default function HowItWorks() {
<section
ref={sectionRef}
id="how-it-works"
className="py-32 px-6 relative overflow-hidden"
className="relative overflow-hidden px-4 py-20 sm:px-6 sm:py-24 md:py-32"
data-testid="section-how-it-works"
>
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[800px] opacity-30">
<div className="absolute left-1/2 top-1/2 h-[420px] w-[420px] -translate-x-1/2 -translate-y-1/2 opacity-20 sm:h-[560px] sm:w-[560px] md:h-[800px] md:w-[800px] md:opacity-30">
<div
className="absolute inset-0 rounded-full border border-primary/20"
style={{ animation: "pulse-ring 4s ease-out infinite" }}
@ -89,20 +89,20 @@ export default function HowItWorks() {
<p className="text-sm uppercase tracking-widest text-muted-foreground mb-4">
How It Works
</p>
<h2 className="text-4xl md:text-5xl font-bold mb-4" data-testid="text-how-it-works-headline">
<h2 className="mb-4 text-3xl font-bold sm:text-4xl md:text-5xl" data-testid="text-how-it-works-headline">
Four Simple Steps
</h2>
<p className="text-xl text-muted-foreground">
<p className="text-base text-muted-foreground sm:text-lg md:text-xl">
Start your investment journey in minutes.
</p>
</div>
<div className="grid md:grid-cols-2 gap-8">
<div className="grid gap-6 md:grid-cols-2 md:gap-8">
{steps.map((step, index) => (
<div
key={step.title}
data-step={index}
className={`flex gap-5 transition-all duration-700 ease-out group cursor-pointer ${
className={`group flex items-start gap-4 transition-all duration-700 ease-out sm:gap-5 ${
visibleSteps.includes(index)
? "opacity-100 translate-y-0"
: "opacity-0 translate-y-8"
@ -111,7 +111,7 @@ export default function HowItWorks() {
onMouseEnter={() => setActiveStep(index)}
>
<div
className={`flex-shrink-0 w-14 h-14 rounded-xl flex items-center justify-center transition-all duration-500 ${
className={`flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-xl transition-all duration-500 sm:h-14 sm:w-14 ${
activeStep === index
? "bg-primary text-primary-foreground scale-110 shadow-lg shadow-primary/25"
: "bg-primary/10 text-primary"
@ -131,7 +131,7 @@ export default function HowItWorks() {
0{index + 1}
</span>
<h3
className={`text-lg font-semibold transition-colors duration-300 ${
className={`text-base font-semibold transition-colors duration-300 sm:text-lg ${
activeStep === index ? "text-foreground" : "text-foreground/80"
}`}
data-testid={`text-step-title-${index}`}

View File

@ -68,6 +68,7 @@ export default function Navigation() {
);
const handleNavClick = (href: string) => {
setIsMobileMenuOpen(false);
const hash = href.startsWith("#") ? href.slice(1) : "";
if (href.startsWith("/") && !href.startsWith("#")) {
navigate(href);
@ -130,12 +131,18 @@ export default function Navigation() {
return location === href || location.startsWith(`${href}/`);
};
const LinkItem = ({ link }: { link: { label: string; href: string; newTab?: boolean } }) => (
const LinkItem = ({
link,
mobile = false,
}: {
link: { label: string; href: string; newTab?: boolean };
mobile?: boolean;
}) => (
<button
onClick={() => handleNavClick(link.href)}
className={`group relative whitespace-nowrap text-sm font-medium transition-colors duration-300 ${
className={`group relative text-sm font-medium transition-colors duration-300 ${
isActiveLink(link.href) ? "text-foreground" : "text-muted-foreground hover:text-foreground"
}`}
} ${mobile ? "w-full py-1.5 text-left text-base" : "whitespace-nowrap"}`}
data-testid={`link-nav-${link.label.toLowerCase().replace(/\s/g, "-")}`}
>
{link.label}
@ -154,11 +161,11 @@ export default function Navigation() {
isScrolled ? "bg-background/80 backdrop-blur-xl border-b border-border" : "bg-transparent"
}`}
>
<div className="max-w-6xl mx-auto px-6 py-4">
<div className="mx-auto max-w-6xl px-4 py-3 sm:px-6 sm:py-4">
<div className="flex items-center justify-between gap-4">
<button
onClick={() => handleNavClick("#")}
className="text-xl font-bold tracking-tight text-foreground"
className="text-lg font-bold tracking-tight text-foreground sm:text-xl"
data-testid="link-logo"
>
QuantFortune
@ -173,9 +180,9 @@ export default function Navigation() {
<div className="hidden md:flex items-center gap-3">
{sessionUser ? (
<>
<div className="flex items-center gap-2 rounded-full border border-primary/40 bg-primary/10 px-3 py-1 text-sm font-medium text-primary">
<div className="flex max-w-[11rem] items-center gap-2 rounded-full border border-primary/40 bg-primary/10 px-3 py-1 text-sm font-medium text-primary lg:max-w-[16rem]">
<span className="h-2 w-2 rounded-full bg-primary" />
{sessionUser.username}
<span className="truncate">{sessionUser.username}</span>
</div>
<Button
variant="outline"
@ -195,27 +202,28 @@ export default function Navigation() {
<Button
variant="ghost"
size="icon"
className="md:hidden"
className="shrink-0 text-foreground md:hidden"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
data-testid="button-mobile-menu"
aria-label={isMobileMenuOpen ? "Close navigation menu" : "Open navigation menu"}
>
{isMobileMenuOpen ? <X className="h-5 w-5" /> : <Menu className="h-5 w-5" />}
</Button>
</div>
{isMobileMenuOpen && (
<div className="md:hidden mt-4 pb-4 border-t border-border pt-4">
<div className="mt-3 rounded-2xl border border-border bg-background/95 px-3 pb-4 pt-4 shadow-xl backdrop-blur-xl md:hidden">
<div className="flex flex-col gap-4">
{navLinks.map((link) => (
<LinkItem key={link.label} link={link} />
<LinkItem key={link.label} link={link} mobile />
))}
<div className="pt-2 border-t border-border">
<div className="border-t border-border pt-2">
<div className="flex flex-col gap-3">
{sessionUser ? (
<>
<div className="flex items-center gap-2 rounded-xl border border-primary/40 bg-primary/10 px-3 py-2 text-sm font-medium text-primary">
<span className="h-2 w-2 rounded-full bg-primary" />
{sessionUser.username}
<span className="truncate">{sessionUser.username}</span>
</div>
<Button
variant="outline"

View File

@ -84,7 +84,7 @@ export default function PerformanceChart() {
return (
<section
ref={sectionRef}
className="py-32 px-6 bg-card/30 relative overflow-hidden"
className="relative overflow-hidden bg-card/30 px-4 py-20 sm:px-6 sm:py-24 md:py-32"
data-testid="section-performance"
>
<div
@ -103,7 +103,7 @@ export default function PerformanceChart() {
The Power of Compounding
</p>
<h2
className={`text-4xl md:text-5xl font-bold mb-4 transition-all duration-700 delay-100 ${
className={`mb-4 text-3xl font-bold transition-all duration-700 delay-100 sm:text-4xl md:text-5xl ${
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"
}`}
data-testid="text-performance-headline"
@ -111,7 +111,7 @@ export default function PerformanceChart() {
Watch Your Wealth Grow
</h2>
<p
className={`text-xl text-muted-foreground transition-all duration-700 delay-200 ${
className={`text-base text-muted-foreground transition-all duration-700 delay-200 sm:text-lg md:text-xl ${
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"
}`}
>
@ -120,15 +120,17 @@ export default function PerformanceChart() {
</div>
<div
className={`relative bg-card rounded-2xl p-8 border border-card-border transition-all duration-700 delay-300 ${
className={`relative rounded-2xl border border-card-border bg-card p-4 transition-all duration-700 delay-300 sm:p-6 md:p-8 ${
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-8"
}`}
>
<svg
viewBox={`0 0 ${chartWidth} ${chartHeight}`}
className="w-full h-auto"
preserveAspectRatio="xMidYMid meet"
>
<div className="overflow-x-auto pb-2">
<div className="min-w-[560px]">
<svg
viewBox={`0 0 ${chartWidth} ${chartHeight}`}
className="h-auto w-full"
preserveAspectRatio="xMidYMid meet"
>
<defs>
<linearGradient id="investedGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="hsl(var(--muted-foreground))" stopOpacity="0.4" />
@ -340,9 +342,11 @@ export default function PerformanceChart() {
</text>
</g>
)}
</svg>
</svg>
</div>
</div>
<div className="flex items-center justify-center gap-8 mt-8 flex-wrap">
<div className="mt-8 flex flex-wrap items-center justify-center gap-4 sm:gap-8">
<div
className={`flex items-center gap-3 transition-all duration-500 ${
animationPhase >= 1 ? "opacity-100 translate-x-0" : "opacity-0 -translate-x-4"

View File

@ -1012,7 +1012,7 @@ export default function PortfolioSection() {
<section
ref={sectionRef}
id="portfolio"
className={`relative overflow-hidden py-32 px-6 pb-32 bg-gradient-to-b from-background to-background/80 ${revealTransition} ${sectionRevealClass}`}
className={`relative overflow-hidden bg-gradient-to-b from-background to-background/80 px-4 py-20 pb-20 sm:px-6 sm:py-24 sm:pb-24 md:py-32 md:pb-32 ${revealTransition} ${sectionRevealClass}`}
>
<LoginRequiredDialog
open={loginPromptOpen}
@ -1044,12 +1044,12 @@ export default function PortfolioSection() {
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div className="space-y-2">
<p className="text-sm uppercase tracking-[0.25em] text-muted-foreground">Portfolio</p>
<h3 className="text-3xl md:text-4xl font-bold">Your holdings & live positions</h3>
<h3 className="text-2xl font-bold sm:text-3xl md:text-4xl">Your holdings & live positions</h3>
<p className="text-muted-foreground max-w-2xl">
Connect your broker to sync holdings. When disconnected, values stay at zero and you will see a prompt to connect.
</p>
{isConnected && (brokerStatus?.userName || brokerStatus?.broker) ? (
<div className="inline-flex items-center gap-2 rounded-full border border-primary/50 bg-primary/10 px-3 py-1 text-sm font-medium text-primary">
<div className="inline-flex max-w-full flex-wrap items-center gap-2 rounded-full border border-primary/50 bg-primary/10 px-3 py-1 text-sm font-medium text-primary sm:flex-nowrap">
<Wallet className="h-4 w-4" />
{brokerStatus?.userName ? (
<>
@ -1063,9 +1063,13 @@ export default function PortfolioSection() {
</div>
) : null}
</div>
<div className="flex flex-wrap gap-2">
<BrokerConnectDialog open={brokerDialogOpen} onOpenChange={setBrokerDialogOpen} />
<Button variant="secondary" asChild>
<div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:flex-wrap">
<BrokerConnectDialog
open={brokerDialogOpen}
onOpenChange={setBrokerDialogOpen}
triggerClassName="w-full sm:w-auto"
/>
<Button variant="secondary" asChild className="w-full sm:w-auto">
<a href="/portfolio/paper" target="_blank" rel="noreferrer">
Paper Trading Portfolio
</a>
@ -1075,6 +1079,7 @@ export default function PortfolioSection() {
variant="outline"
onClick={handleRefreshBrokerData}
disabled={holdingsQuery.isFetching || equityCurveQuery.isFetching}
className="w-full sm:w-auto"
>
<RefreshCcw className="h-4 w-4" />
{holdingsQuery.isFetching || equityCurveQuery.isFetching
@ -1086,6 +1091,7 @@ export default function PortfolioSection() {
<Button
variant="outline"
onClick={handleReconnectClick}
className="w-full sm:w-auto"
>
<PlugZap className="h-4 w-4" />
Reconnect broker
@ -1096,6 +1102,7 @@ export default function PortfolioSection() {
variant="outline"
onClick={handleDisconnectBroker}
disabled={disconnectBrokerMutation.isPending}
className="w-full sm:w-auto"
>
{disconnectBrokerMutation.isPending ? "Disconnecting..." : "Disconnect broker"}
</Button>
@ -1128,7 +1135,7 @@ export default function PortfolioSection() {
className={`rounded-2xl border border-border/60 bg-card/70 shadow-xl overflow-hidden ${hoverLift} ${revealTransition} ${cardRevealClass}`}
style={prefersReducedMotion ? undefined : { transitionDelay: "500ms" }}
>
<div className="flex items-center justify-between px-6 py-4 border-b border-border/50">
<div className="flex flex-col gap-3 border-b border-border/50 px-4 py-4 sm:flex-row sm:items-center sm:justify-between sm:px-6">
<div className="space-y-1">
<p className="text-sm font-semibold">Holdings</p>
<p className="text-xs text-muted-foreground">
@ -1161,9 +1168,9 @@ export default function PortfolioSection() {
) : (
<div className="overflow-x-auto">
{showSessionExpired ? (
<div className="flex flex-wrap items-center justify-between gap-2 px-6 py-3 text-xs text-amber-200 bg-amber-500/10 border-b border-amber-400/20">
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-amber-400/20 bg-amber-500/10 px-4 py-3 text-xs text-amber-200 sm:px-6">
<span>Session expired. Showing the last known holdings. Reconnect to refresh.</span>
<Button size="sm" variant="secondary" onClick={handleReconnectClick}>
<Button size="sm" variant="secondary" onClick={handleReconnectClick} className="w-full sm:w-auto">
Reconnect broker
</Button>
</div>
@ -1171,11 +1178,11 @@ export default function PortfolioSection() {
<table className="min-w-full text-sm">
<thead className="bg-muted/40 text-muted-foreground">
<tr>
<th className="px-6 py-3 text-left font-medium">Symbol</th>
<th className="px-6 py-3 text-left font-medium">Qty</th>
<th className="px-6 py-3 text-left font-medium">Avg price</th>
<th className="px-6 py-3 text-left font-medium">LTP</th>
<th className="px-6 py-3 text-left font-medium">P&L</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">Symbol</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">Qty</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">Avg price</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">LTP</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">P&L</th>
</tr>
</thead>
<tbody className="divide-y divide-border/60">
@ -1188,7 +1195,7 @@ export default function PortfolioSection() {
const pnl = getDisplayPnl(item);
return (
<tr key={`${item.tradingsymbol || item.instrument_token || idx}`}>
<td className="px-6 py-3">
<td className="px-4 py-3 sm:px-6">
<div className="flex items-center gap-2">
<span className="font-semibold">
{item.tradingsymbol || item.symbol || "Instrument"}
@ -1196,7 +1203,7 @@ export default function PortfolioSection() {
<Badge variant="outline">{item.exchange || item.exchange_type || "N/A"}</Badge>
</div>
</td>
<td className="px-6 py-3">
<td className="px-4 py-3 sm:px-6">
<div className="flex flex-col">
<span>{qty}</span>
{t1Qty > 0 && settledQty <= 0 ? (
@ -1204,9 +1211,9 @@ export default function PortfolioSection() {
) : null}
</div>
</td>
<td className="px-6 py-3">{formatCurrency(avg, { decimals: 2 })}</td>
<td className="px-6 py-3">{ltp ? formatCurrency(ltp, { decimals: 2 }) : "-"}</td>
<td className="px-6 py-3">
<td className="px-4 py-3 sm:px-6">{formatCurrency(avg, { decimals: 2 })}</td>
<td className="px-4 py-3 sm:px-6">{ltp ? formatCurrency(ltp, { decimals: 2 }) : "-"}</td>
<td className="px-4 py-3 sm:px-6">
<span className={pnl >= 0 ? "text-emerald-500" : "text-red-500"}>
{formatCurrency(pnl, { decimals: 2 })}
</span>
@ -1225,14 +1232,14 @@ export default function PortfolioSection() {
className={`rounded-2xl border border-border/60 bg-card/70 shadow-xl overflow-hidden ${hoverLift} ${revealTransition} ${cardRevealClass}`}
style={prefersReducedMotion ? undefined : { transitionDelay: "600ms" }}
>
<div className="flex items-center justify-between px-6 py-4 border-b border-border/50">
<div className="flex flex-col gap-3 border-b border-border/50 px-4 py-4 sm:flex-row sm:items-center sm:justify-between sm:px-6">
<div className="space-y-1">
<p className="text-sm font-semibold">Strategy control</p>
<p className="text-xs text-muted-foreground">
Start or stop the Golden Nifty SIP engine from the dashboard.
</p>
</div>
<div className="flex items-center gap-2">
<div className="flex flex-wrap items-center gap-2">
<Badge variant="outline" className={livenessBadgeClass}>
{livenessBadgeLabel}
</Badge>
@ -1241,7 +1248,7 @@ export default function PortfolioSection() {
</Badge>
</div>
</div>
<div className="p-6 space-y-4">
<div className="space-y-4 p-4 sm:p-6">
<div className="rounded-lg border border-border/60 bg-background/40 px-4 py-3">
<div className="text-xs uppercase tracking-[0.18em] text-muted-foreground">
Next eligible SIP
@ -1311,13 +1318,13 @@ export default function PortfolioSection() {
Resume uses the previously saved SIP configuration. Choose restart to begin a fresh cycle.
</div>
) : null}
<div className="flex flex-wrap gap-2">
<div className="flex flex-col gap-2 sm:flex-row sm:flex-wrap">
{showResumeStrategy ? (
<MotionButton
{...ctaMotionProps}
onClick={handleResume}
disabled={isResuming || !canArmStrategy}
className="shimmer"
className="w-full shimmer sm:w-auto"
>
{isResuming ? "Resuming..." : "Resume Strategy"}
</MotionButton>
@ -1327,18 +1334,18 @@ export default function PortfolioSection() {
{...ctaMotionProps}
onClick={handleStart}
disabled={isStarting || !canArmStrategy || isStrategyActive}
className="shimmer"
className="w-full shimmer sm:w-auto"
>
{isStarting ? "Starting..." : "Start Strategy"}
</MotionButton>
) : null}
{showRestartStrategy ? (
<Button variant="outline" onClick={() => setFreshStartRequested(true)}>
<Button variant="outline" onClick={() => setFreshStartRequested(true)} className="w-full sm:w-auto">
Restart Strategy
</Button>
) : null}
{isStrategyActive ? (
<Button variant="outline" onClick={handleStop} disabled={isStopping}>
<Button variant="outline" onClick={handleStop} disabled={isStopping} className="w-full sm:w-auto">
{isStopping ? "Stopping..." : "Stop Strategy"}
</Button>
) : null}
@ -1351,14 +1358,14 @@ export default function PortfolioSection() {
className={`rounded-2xl border border-border/60 bg-card/70 shadow-xl p-6 space-y-4 ${hoverLift} ${revealTransition} ${cardRevealClass}`}
style={prefersReducedMotion ? undefined : { transitionDelay: "700ms" }}
>
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-3">
<div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div>
<p className="text-sm font-semibold">Equity curve</p>
<p className="text-xs text-muted-foreground">
Track exact stored daily broker snapshots from the day recording began.
</p>
</div>
<div className="flex items-center gap-2">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center">
<Label htmlFor="equity-start" className="text-xs text-muted-foreground">
From
</Label>
@ -1368,7 +1375,7 @@ export default function PortfolioSection() {
value={startDate}
max={formatDateInput(new Date())}
onChange={(e) => setStartDate(e.target.value)}
className="w-[180px]"
className="w-full sm:w-[180px]"
disabled={!isConnected}
/>
</div>
@ -1383,7 +1390,7 @@ export default function PortfolioSection() {
) : equityCurvePoints.length === 0 ? (
<ZeroState message="No exact equity snapshots yet. The curve starts from the first recorded daily snapshot." />
) : (
<div className="h-80">
<div className="h-72 sm:h-80">
<ChartContainer
config={{
equity: { label: "Equity", color: "hsl(var(--chart-1))" },

View File

@ -70,11 +70,11 @@ export default function StrategiesSection() {
<section
ref={sectionRef}
id="strategies"
className="py-32 px-6 relative overflow-hidden"
className="relative overflow-hidden px-4 py-20 sm:px-6 sm:py-24 md:py-32"
data-testid="section-strategies"
>
<div
className={`absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] bg-primary/5 rounded-full blur-3xl transition-all duration-1000 ${
className={`absolute top-1/2 left-1/2 h-[360px] w-[360px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary/5 blur-3xl transition-all duration-1000 sm:h-[460px] sm:w-[460px] md:h-[600px] md:w-[600px] ${
isVisible ? "opacity-100 scale-100" : "opacity-0 scale-50"
}`}
/>
@ -89,7 +89,7 @@ export default function StrategiesSection() {
Strategies
</p>
<h2
className={`text-4xl md:text-5xl font-bold mb-4 transition-all duration-700 delay-100 ${
className={`mb-4 text-3xl font-bold transition-all duration-700 delay-100 sm:text-4xl md:text-5xl ${
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"
}`}
data-testid="text-strategies-headline"
@ -97,7 +97,7 @@ export default function StrategiesSection() {
Choose Your Path
</h2>
<p
className={`text-xl text-muted-foreground max-w-2xl mx-auto transition-all duration-700 delay-200 ${
className={`mx-auto max-w-2xl text-base text-muted-foreground transition-all duration-700 delay-200 sm:text-lg md:text-xl ${
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"
}`}
>
@ -106,7 +106,7 @@ export default function StrategiesSection() {
</div>
<motion.div
className="grid md:grid-cols-3 gap-6"
className="grid gap-6 md:grid-cols-3"
variants={cardContainer}
initial="hidden"
animate={isVisible ? "show" : "hidden"}

View File

@ -67,7 +67,7 @@ export default function StrategySelectorModal({ open, onClose }: StrategySelecto
<AnimatePresence>
{open && (
<motion.div
className="fixed inset-0 z-[90] flex items-center justify-center px-6 py-10"
className="fixed inset-0 z-[90] flex items-end justify-center px-3 py-4 sm:items-center sm:px-6 sm:py-10"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
@ -88,14 +88,14 @@ export default function StrategySelectorModal({ open, onClose }: StrategySelecto
aria-modal="true"
aria-labelledby="strategy-modal-title"
aria-describedby="strategy-modal-subtitle"
className="relative z-[91] w-full max-w-4xl rounded-3xl border border-white/10 bg-gradient-to-br from-white/10 via-background/80 to-background/90 p-8 shadow-2xl"
className="relative z-[91] max-h-[90vh] w-full max-w-4xl overflow-y-auto rounded-[1.75rem] border border-white/10 bg-gradient-to-br from-white/10 via-background/80 to-background/90 p-4 shadow-2xl sm:rounded-3xl sm:p-8"
initial={{ opacity: 0, scale: 0.95, y: 16 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 16 }}
transition={{ duration: 0.3, ease: "easeOut" }}
onClick={(event) => event.stopPropagation()}
>
<div className="flex items-start justify-between gap-6">
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between sm:gap-6">
<div>
<h2 id="strategy-modal-title" className="text-2xl font-semibold text-foreground">
Choose Your Strategy
@ -107,13 +107,13 @@ export default function StrategySelectorModal({ open, onClose }: StrategySelecto
<button
type="button"
onClick={onClose}
className="rounded-full border border-white/10 bg-white/10 px-3 py-1 text-xs text-muted-foreground hover:text-foreground"
className="rounded-full border border-white/10 bg-white/10 px-3 py-1 text-xs text-muted-foreground hover:text-foreground sm:self-start"
>
Close
</button>
</div>
<div className="mt-8 grid gap-4 md:grid-cols-3">
<div className="mt-6 grid gap-4 md:mt-8 md:grid-cols-3">
<StrategyCard
title="Golden Nifty"
description="Balanced Nifty + Gold allocation for stability."

View File

@ -736,13 +736,13 @@ function PaperTradingPortfolio() {
<Navigation />
<PageEnter>
<main className="pt-24 pb-40">
<section className="max-w-6xl mx-auto px-6 py-10 space-y-8">
<section className="mx-auto max-w-6xl space-y-8 px-4 py-8 sm:px-6 sm:py-10">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div className="space-y-2">
<p className="text-sm uppercase tracking-[0.25em] text-muted-foreground">
Paper Trading Portfolio
</p>
<h2 className="text-3xl md:text-4xl font-bold">Paper trading (simulated)</h2>
<h2 className="text-2xl font-bold sm:text-3xl md:text-4xl">Paper trading (simulated)</h2>
<p className="text-muted-foreground max-w-2xl">
This dashboard mirrors live execution flow with simulated orders and balances.
</p>
@ -768,14 +768,14 @@ function PaperTradingPortfolio() {
</div>
<div className="rounded-2xl border border-border/60 bg-card/70 shadow-xl overflow-hidden">
<div className="flex items-center justify-between px-6 py-4 border-b border-border/50">
<div className="flex flex-col gap-3 border-b border-border/50 px-4 py-4 sm:flex-row sm:items-center sm:justify-between sm:px-6">
<div className="space-y-1">
<p className="text-sm font-semibold">Strategy control</p>
<p className="text-xs text-muted-foreground">
Start or stop the Golden Nifty SIP engine from the dashboard.
</p>
</div>
<div className="flex items-center gap-2">
<div className="flex flex-wrap items-center gap-2">
<Badge variant="outline" className={livenessBadgeClass}>
{livenessBadgeLabel}
</Badge>
@ -789,7 +789,7 @@ function PaperTradingPortfolio() {
) : null}
</div>
</div>
<div className="p-6 space-y-4">
<div className="space-y-4 p-4 sm:p-6">
<div className="rounded-lg border border-border/60 bg-background/40 px-4 py-3">
<div className="text-xs uppercase tracking-[0.18em] text-muted-foreground">
Next eligible SIP
@ -926,13 +926,13 @@ function PaperTradingPortfolio() {
Resume uses the previously saved paper SIP configuration. Choose restart to begin a fresh cycle.
</div>
) : null}
<div className="flex flex-wrap gap-2">
<div className="flex flex-col gap-2 sm:flex-row sm:flex-wrap">
{showResumeStrategy ? (
<MotionButton
{...ctaMotionProps}
onClick={handleResume}
disabled={isResuming || !canArmStrategy}
className="shimmer"
className="w-full shimmer sm:w-auto"
>
{isResuming ? "Resuming..." : "Resume Strategy"}
</MotionButton>
@ -942,7 +942,7 @@ function PaperTradingPortfolio() {
{...ctaMotionProps}
onClick={handleStart}
disabled={!canStartFresh || isStarting || isResuming || isStopping || isResetting || !canArmStrategy || isStrategyActive}
className="shimmer"
className="w-full shimmer sm:w-auto"
>
{isStarting ? "Starting..." : "Start Strategy"}
</MotionButton>
@ -952,6 +952,7 @@ function PaperTradingPortfolio() {
variant="outline"
onClick={() => setFreshStartRequested(true)}
disabled={isStarting || isResuming || isStopping || isResetting}
className="w-full sm:w-auto"
>
Restart Strategy
</Button>
@ -961,6 +962,7 @@ function PaperTradingPortfolio() {
variant="outline"
onClick={handleStop}
disabled={!canStop || isStarting || isResuming || isStopping || isResetting}
className="w-full sm:w-auto"
>
{isStopping ? "Stopping..." : "Stop Strategy"}
</Button>
@ -969,6 +971,7 @@ function PaperTradingPortfolio() {
variant="destructive"
onClick={handleReset}
disabled={isStarting || isResuming || isStopping || isResetting}
className="w-full sm:w-auto"
>
{isResetting ? "Resetting..." : "Reset Paper Account"}
</Button>
@ -1013,14 +1016,14 @@ function PaperTradingPortfolio() {
<p className="text-xs text-muted-foreground">Delivery SIP · No leverage</p>
<div className="rounded-2xl border border-border/60 bg-card/70 shadow-xl overflow-hidden">
<div className="flex items-center justify-between px-6 py-4 border-b border-border/50">
<div className="flex flex-col gap-3 border-b border-border/50 px-4 py-4 sm:flex-row sm:items-center sm:justify-between sm:px-6">
<div className="space-y-1">
<p className="text-sm font-semibold">Positions</p>
<p className="text-xs text-muted-foreground">
Current paper positions and unrealized P&L.
</p>
</div>
<div className="flex items-center gap-3">
<div className="flex flex-wrap items-center gap-3">
<span className="text-xs text-muted-foreground">
Live prices · 5s refresh
</span>
@ -1028,9 +1031,9 @@ function PaperTradingPortfolio() {
</div>
</div>
{positionsQuery.isLoading && displayPositions.length === 0 ? (
<div className="px-6 py-6 text-sm text-muted-foreground">Loading positions...</div>
<div className="px-4 py-6 text-sm text-muted-foreground sm:px-6">Loading positions...</div>
) : displayPositions.length === 0 ? (
<div className="px-6 py-6 text-sm text-muted-foreground">
<div className="px-4 py-6 text-sm text-muted-foreground sm:px-6">
No paper positions yet.
</div>
) : (
@ -1038,21 +1041,21 @@ function PaperTradingPortfolio() {
<table className="min-w-full text-sm">
<thead className="bg-muted/40 text-muted-foreground">
<tr>
<th className="px-6 py-3 text-left font-medium">Symbol</th>
<th className="px-6 py-3 text-left font-medium">Qty</th>
<th className="px-6 py-3 text-left font-medium">Avg price</th>
<th className="px-6 py-3 text-left font-medium">LTP</th>
<th className="px-6 py-3 text-left font-medium">P&amp;L</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">Symbol</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">Qty</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">Avg price</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">LTP</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">P&amp;L</th>
</tr>
</thead>
<tbody className="divide-y divide-border/60">
{displayPositions.map((pos) => (
<tr key={pos.symbol}>
<td className="px-6 py-3 font-semibold">{pos.symbol}</td>
<td className="px-6 py-3">{pos.qty}</td>
<td className="px-6 py-3">{formatCurrency(pos.avg_price, 2)}</td>
<td className="px-6 py-3">{formatCurrency(pos.last_price, 2)}</td>
<td className="px-6 py-3">
<td className="px-4 py-3 font-semibold sm:px-6">{pos.symbol}</td>
<td className="px-4 py-3 sm:px-6">{pos.qty}</td>
<td className="px-4 py-3 sm:px-6">{formatCurrency(pos.avg_price, 2)}</td>
<td className="px-4 py-3 sm:px-6">{formatCurrency(pos.last_price, 2)}</td>
<td className="px-4 py-3 sm:px-6">
<span className={pos.pnl >= 0 ? "text-emerald-400" : "text-red-400"}>
{formatCurrency(pos.pnl, 2)} ({pos.pnl_pct.toFixed(2)}%)
</span>
@ -1066,7 +1069,7 @@ function PaperTradingPortfolio() {
</div>
<div className="rounded-2xl border border-border/60 bg-card/70 shadow-xl overflow-hidden">
<div className="flex items-center justify-between px-6 py-4 border-b border-border/50">
<div className="flex flex-col gap-3 border-b border-border/50 px-4 py-4 sm:flex-row sm:items-center sm:justify-between sm:px-6">
<div className="space-y-1">
<p className="text-sm font-semibold">Orders</p>
<p className="text-xs text-muted-foreground">Paper order history.</p>
@ -1074,9 +1077,9 @@ function PaperTradingPortfolio() {
<Badge variant="outline">{orders.length} orders</Badge>
</div>
{ordersQuery.isLoading ? (
<div className="px-6 py-6 text-sm text-muted-foreground">Loading orders...</div>
<div className="px-4 py-6 text-sm text-muted-foreground sm:px-6">Loading orders...</div>
) : orders.length === 0 ? (
<div className="px-6 py-6 text-sm text-muted-foreground">
<div className="px-4 py-6 text-sm text-muted-foreground sm:px-6">
No paper orders yet.
</div>
) : (
@ -1084,27 +1087,27 @@ function PaperTradingPortfolio() {
<table className="min-w-full text-sm">
<thead className="bg-muted/40 text-muted-foreground">
<tr>
<th className="px-6 py-3 text-left font-medium">Order ID</th>
<th className="px-6 py-3 text-left font-medium">Time</th>
<th className="px-6 py-3 text-left font-medium">Symbol</th>
<th className="px-6 py-3 text-left font-medium">Side</th>
<th className="px-6 py-3 text-left font-medium">Qty</th>
<th className="px-6 py-3 text-left font-medium">Price</th>
<th className="px-6 py-3 text-left font-medium">Status</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">Order ID</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">Time</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">Symbol</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">Side</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">Qty</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">Price</th>
<th className="px-4 py-3 text-left font-medium sm:px-6">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-border/60">
{orders.map((order) => (
<tr key={order.id}>
<td className="px-6 py-3 font-mono text-xs">{order.id}</td>
<td className="px-6 py-3 text-xs text-muted-foreground">
<td className="px-4 py-3 font-mono text-xs sm:px-6">{order.id}</td>
<td className="px-4 py-3 text-xs text-muted-foreground sm:px-6">
{formatTimestamp(order.timestamp)}
</td>
<td className="px-6 py-3">{order.symbol}</td>
<td className="px-6 py-3">{order.side}</td>
<td className="px-6 py-3">{order.qty}</td>
<td className="px-6 py-3">{formatCurrency(order.price, 2)}</td>
<td className="px-6 py-3">
<td className="px-4 py-3 sm:px-6">{order.symbol}</td>
<td className="px-4 py-3 sm:px-6">{order.side}</td>
<td className="px-4 py-3 sm:px-6">{order.qty}</td>
<td className="px-4 py-3 sm:px-6">{formatCurrency(order.price, 2)}</td>
<td className="px-4 py-3 sm:px-6">
<Badge variant="outline">{order.status}</Badge>
</td>
</tr>