diff --git a/src/components/chat/ChatButton.tsx b/src/components/chat/ChatButton.tsx index b0595272..f238230b 100644 --- a/src/components/chat/ChatButton.tsx +++ b/src/components/chat/ChatButton.tsx @@ -8,11 +8,11 @@ type ChatButtonProps = { export default function ChatButton({ open, onToggle }: ChatButtonProps) { return ( -
@@ -113,7 +113,7 @@ export default function FinalCTA({ onExploreStrategies }: FinalCTAProps) { > diff --git a/src/components/landing/HeroSection.tsx b/src/components/landing/HeroSection.tsx index 9a904f23..9031268d 100644 --- a/src/components/landing/HeroSection.tsx +++ b/src/components/landing/HeroSection.tsx @@ -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 ( @@ -86,7 +106,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) { }} > - {[...Array(25)].map((_, i) => ( + {[...Array(particleCount)].map((_, i) => ( @@ -215,7 +235,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) { @@ -246,7 +266,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) { navigate("/learn-more")} > @@ -256,7 +276,7 @@ export default function HeroSection({ onExploreStrategies }: HeroSectionProps) { diff --git a/src/components/landing/HowItWorks.tsx b/src/components/landing/HowItWorks.tsx index 7a9799f9..19bebec8 100644 --- a/src/components/landing/HowItWorks.tsx +++ b/src/components/landing/HowItWorks.tsx @@ -66,10 +66,10 @@ export default function HowItWorks() { - + How It Works - + Four Simple Steps - + Start your investment journey in minutes. - + {steps.map((step, index) => ( setActiveStep(index)} > { + 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; + }) => ( 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" }`} > - + 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() { {sessionUser ? ( <> - + - {sessionUser.username} + {sessionUser.username} setIsMobileMenuOpen(!isMobileMenuOpen)} data-testid="button-mobile-menu" + aria-label={isMobileMenuOpen ? "Close navigation menu" : "Open navigation menu"} > {isMobileMenuOpen ? : } {isMobileMenuOpen && ( - + {navLinks.map((link) => ( - + ))} - + {sessionUser ? ( <> - {sessionUser.username} + {sessionUser.username} @@ -120,15 +120,17 @@ export default function PerformanceChart() { - + + + @@ -340,9 +342,11 @@ export default function PerformanceChart() { )} - + + + - + = 1 ? "opacity-100 translate-x-0" : "opacity-0 -translate-x-4" diff --git a/src/components/landing/PortfolioSection.tsx b/src/components/landing/PortfolioSection.tsx index 44ce1773..42d0021e 100644 --- a/src/components/landing/PortfolioSection.tsx +++ b/src/components/landing/PortfolioSection.tsx @@ -1012,7 +1012,7 @@ export default function PortfolioSection() { Portfolio - Your holdings & live positions + Your holdings & live positions Connect your broker to sync holdings. When disconnected, values stay at zero and you will see a prompt to connect. {isConnected && (brokerStatus?.userName || brokerStatus?.broker) ? ( - + {brokerStatus?.userName ? ( <> @@ -1063,9 +1063,13 @@ export default function PortfolioSection() { ) : null} - - - + + + Paper Trading Portfolio @@ -1075,6 +1079,7 @@ export default function PortfolioSection() { variant="outline" onClick={handleRefreshBrokerData} disabled={holdingsQuery.isFetching || equityCurveQuery.isFetching} + className="w-full sm:w-auto" > {holdingsQuery.isFetching || equityCurveQuery.isFetching @@ -1086,6 +1091,7 @@ export default function PortfolioSection() { 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"} @@ -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" }} > - + Holdings @@ -1161,9 +1168,9 @@ export default function PortfolioSection() { ) : ( {showSessionExpired ? ( - + Session expired. Showing the last known holdings. Reconnect to refresh. - + Reconnect broker @@ -1171,11 +1178,11 @@ export default function PortfolioSection() { - Symbol - Qty - Avg price - LTP - P&L + Symbol + Qty + Avg price + LTP + P&L @@ -1188,7 +1195,7 @@ export default function PortfolioSection() { const pnl = getDisplayPnl(item); return ( - + {item.tradingsymbol || item.symbol || "Instrument"} @@ -1196,7 +1203,7 @@ export default function PortfolioSection() { {item.exchange || item.exchange_type || "N/A"} - + {qty} {t1Qty > 0 && settledQty <= 0 ? ( @@ -1204,9 +1211,9 @@ export default function PortfolioSection() { ) : null} - {formatCurrency(avg, { decimals: 2 })} - {ltp ? formatCurrency(ltp, { decimals: 2 }) : "-"} - + {formatCurrency(avg, { decimals: 2 })} + {ltp ? formatCurrency(ltp, { decimals: 2 }) : "-"} + = 0 ? "text-emerald-500" : "text-red-500"}> {formatCurrency(pnl, { decimals: 2 })} @@ -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" }} > - + Strategy control Start or stop the Golden Nifty SIP engine from the dashboard. - + {livenessBadgeLabel} @@ -1241,7 +1248,7 @@ export default function PortfolioSection() { - + 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. ) : null} - + {showResumeStrategy ? ( {isResuming ? "Resuming..." : "Resume Strategy"} @@ -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"} ) : null} {showRestartStrategy ? ( - setFreshStartRequested(true)}> + setFreshStartRequested(true)} className="w-full sm:w-auto"> Restart Strategy ) : null} {isStrategyActive ? ( - + {isStopping ? "Stopping..." : "Stop Strategy"} ) : 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" }} > - + Equity curve Track exact stored daily broker snapshots from the day recording began. - + From @@ -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} /> @@ -1383,7 +1390,7 @@ export default function PortfolioSection() { ) : equityCurvePoints.length === 0 ? ( ) : ( - + @@ -89,7 +89,7 @@ export default function StrategiesSection() { Strategies @@ -106,7 +106,7 @@ export default function StrategiesSection() { {open && ( event.stopPropagation()} > - + Choose Your Strategy @@ -107,13 +107,13 @@ export default function StrategySelectorModal({ open, onClose }: StrategySelecto Close - + - + Paper Trading Portfolio - Paper trading (simulated) + Paper trading (simulated) This dashboard mirrors live execution flow with simulated orders and balances. @@ -768,14 +768,14 @@ function PaperTradingPortfolio() { - + Strategy control Start or stop the Golden Nifty SIP engine from the dashboard. - + {livenessBadgeLabel} @@ -789,7 +789,7 @@ function PaperTradingPortfolio() { ) : null} - + Next eligible SIP @@ -926,13 +926,13 @@ function PaperTradingPortfolio() { Resume uses the previously saved paper SIP configuration. Choose restart to begin a fresh cycle. ) : null} - + {showResumeStrategy ? ( {isResuming ? "Resuming..." : "Resume Strategy"} @@ -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"} @@ -952,6 +952,7 @@ function PaperTradingPortfolio() { variant="outline" onClick={() => setFreshStartRequested(true)} disabled={isStarting || isResuming || isStopping || isResetting} + className="w-full sm:w-auto" > Restart Strategy @@ -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"} @@ -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"} @@ -1013,14 +1016,14 @@ function PaperTradingPortfolio() { Delivery SIP · No leverage - + Positions Current paper positions and unrealized P&L. - + Live prices · 5s refresh @@ -1028,9 +1031,9 @@ function PaperTradingPortfolio() { {positionsQuery.isLoading && displayPositions.length === 0 ? ( - Loading positions... + Loading positions... ) : displayPositions.length === 0 ? ( - + No paper positions yet. ) : ( @@ -1038,21 +1041,21 @@ function PaperTradingPortfolio() { - Symbol - Qty - Avg price - LTP - P&L + Symbol + Qty + Avg price + LTP + P&L {displayPositions.map((pos) => ( - {pos.symbol} - {pos.qty} - {formatCurrency(pos.avg_price, 2)} - {formatCurrency(pos.last_price, 2)} - + {pos.symbol} + {pos.qty} + {formatCurrency(pos.avg_price, 2)} + {formatCurrency(pos.last_price, 2)} + = 0 ? "text-emerald-400" : "text-red-400"}> {formatCurrency(pos.pnl, 2)} ({pos.pnl_pct.toFixed(2)}%) @@ -1066,7 +1069,7 @@ function PaperTradingPortfolio() { - + Orders Paper order history. @@ -1074,9 +1077,9 @@ function PaperTradingPortfolio() { {orders.length} orders {ordersQuery.isLoading ? ( - Loading orders... + Loading orders... ) : orders.length === 0 ? ( - + No paper orders yet. ) : ( @@ -1084,27 +1087,27 @@ function PaperTradingPortfolio() { - Order ID - Time - Symbol - Side - Qty - Price - Status + Order ID + Time + Symbol + Side + Qty + Price + Status {orders.map((order) => ( - {order.id} - + {order.id} + {formatTimestamp(order.timestamp)} - {order.symbol} - {order.side} - {order.qty} - {formatCurrency(order.price, 2)} - + {order.symbol} + {order.side} + {order.qty} + {formatCurrency(order.price, 2)} + {order.status}
+
Start your investment journey in minutes.
@@ -120,15 +120,17 @@ export default function PerformanceChart() {
Portfolio
Connect your broker to sync holdings. When disconnected, values stay at zero and you will see a prompt to connect.
Holdings
@@ -1161,9 +1168,9 @@ export default function PortfolioSection() { ) : (
Strategy control
Start or stop the Golden Nifty SIP engine from the dashboard.
Equity curve
Track exact stored daily broker snapshots from the day recording began.
@@ -106,7 +106,7 @@ export default function StrategiesSection() {
Paper Trading Portfolio
This dashboard mirrors live execution flow with simulated orders and balances.
Delivery SIP · No leverage
Positions
Current paper positions and unrealized P&L.
Orders
Paper order history.