jashdoshi77 commited on
Commit
0366c3e
·
1 Parent(s): 702ea51

changed the sidebar animation and added holdings in the analysis section

Browse files
frontend/src/App.tsx CHANGED
@@ -1,4 +1,5 @@
1
  import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
 
2
  import Sidebar from './components/Sidebar';
3
  import Landing from './pages/Landing';
4
  import Login from './pages/Login';
@@ -23,10 +24,12 @@ function ProtectedRoute({ children }: { children: React.ReactNode }) {
23
  }
24
 
25
  function AppLayout({ children }: { children: React.ReactNode }) {
 
 
26
  return (
27
  <div className="app-layout">
28
- <Sidebar />
29
- <main className="app-main">
30
  {children}
31
  </main>
32
  </div>
 
1
  import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
2
+ import { useState } from 'react';
3
  import Sidebar from './components/Sidebar';
4
  import Landing from './pages/Landing';
5
  import Login from './pages/Login';
 
24
  }
25
 
26
  function AppLayout({ children }: { children: React.ReactNode }) {
27
+ const [sidebarExpanded, setSidebarExpanded] = useState(false);
28
+
29
  return (
30
  <div className="app-layout">
31
+ <Sidebar onExpandChange={setSidebarExpanded} />
32
+ <main className="app-main" style={{ marginLeft: sidebarExpanded ? 260 : 68 }}>
33
  {children}
34
  </main>
35
  </div>
frontend/src/components/Sidebar.tsx CHANGED
@@ -1,8 +1,27 @@
1
- import { NavLink, useNavigate } from 'react-router-dom';
 
2
 
3
- export default function Sidebar() {
 
 
 
 
4
  const navigate = useNavigate();
 
5
  const user = JSON.parse(localStorage.getItem('qh_user') || 'null');
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  const handleLogout = () => {
8
  localStorage.removeItem('qh_token');
@@ -10,7 +29,7 @@ export default function Sidebar() {
10
  navigate('/login');
11
  };
12
 
13
- const links = [
14
  {
15
  section: 'Overview',
16
  items: [
@@ -44,10 +63,21 @@ export default function Sidebar() {
44
  },
45
  ];
46
 
47
- return (
48
- <aside className="sidebar">
49
- {/* Brand */}
50
- <div className="sidebar-brand">
 
 
 
 
 
 
 
 
 
 
 
51
  <NavLink to="/dashboard" className="sidebar-logo">
52
  <svg width="32" height="32" viewBox="0 0 32 32" fill="none">
53
  <rect width="32" height="32" rx="8" fill="#005241"/>
@@ -59,9 +89,8 @@ export default function Sidebar() {
59
  </NavLink>
60
  </div>
61
 
62
- {/* Navigation */}
63
  <nav className="sidebar-nav">
64
- {links.map((group) => (
65
  <div key={group.section} className="sidebar-section">
66
  <div className="sidebar-section-label">{group.section}</div>
67
  {group.items.map((link) => (
@@ -78,7 +107,6 @@ export default function Sidebar() {
78
  ))}
79
  </nav>
80
 
81
- {/* Bottom user section */}
82
  <div className="sidebar-footer">
83
  {user && (
84
  <div className="sidebar-user">
@@ -100,4 +128,86 @@ export default function Sidebar() {
100
  </div>
101
  </aside>
102
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  }
 
1
+ import { NavLink, useNavigate, useLocation } from 'react-router-dom';
2
+ import { useState, useEffect } from 'react';
3
 
4
+ interface SidebarProps {
5
+ onExpandChange?: (expanded: boolean) => void;
6
+ }
7
+
8
+ export default function Sidebar({ onExpandChange }: SidebarProps) {
9
  const navigate = useNavigate();
10
+ const location = useLocation();
11
  const user = JSON.parse(localStorage.getItem('qh_user') || 'null');
12
+ const [drawerOpen, setDrawerOpen] = useState(false);
13
+ const [isMobile, setIsMobile] = useState(false);
14
+ const [isExpanded, setIsExpanded] = useState(false);
15
+
16
+ useEffect(() => {
17
+ const check = () => setIsMobile(window.innerWidth <= 768);
18
+ check();
19
+ window.addEventListener('resize', check);
20
+ return () => window.removeEventListener('resize', check);
21
+ }, []);
22
+
23
+ // Close drawer on route change
24
+ useEffect(() => { setDrawerOpen(false); }, [location.pathname]);
25
 
26
  const handleLogout = () => {
27
  localStorage.removeItem('qh_token');
 
29
  navigate('/login');
30
  };
31
 
32
+ const allLinks = [
33
  {
34
  section: 'Overview',
35
  items: [
 
63
  },
64
  ];
65
 
66
+ // Primary nav items for mobile bottom bar (5 key items)
67
+ const mobileNavItems = [
68
+ allLinks[0].items[0], // Dashboard
69
+ allLinks[1].items[0], // Markets
70
+ allLinks[0].items[1], // Holdings
71
+ allLinks[1].items[1], // Factor Analysis
72
+ ];
73
+
74
+ // ── Desktop Sidebar ──────────────────────────────────────────────
75
+ const desktopSidebar = (
76
+ <aside
77
+ className={`sidebar${isExpanded ? ' sidebar-expanded' : ''}`}
78
+ onMouseEnter={() => { setIsExpanded(true); onExpandChange?.(true); }}
79
+ onMouseLeave={() => { setIsExpanded(false); onExpandChange?.(false); }}
80
+ > <div className="sidebar-brand">
81
  <NavLink to="/dashboard" className="sidebar-logo">
82
  <svg width="32" height="32" viewBox="0 0 32 32" fill="none">
83
  <rect width="32" height="32" rx="8" fill="#005241"/>
 
89
  </NavLink>
90
  </div>
91
 
 
92
  <nav className="sidebar-nav">
93
+ {allLinks.map((group) => (
94
  <div key={group.section} className="sidebar-section">
95
  <div className="sidebar-section-label">{group.section}</div>
96
  {group.items.map((link) => (
 
107
  ))}
108
  </nav>
109
 
 
110
  <div className="sidebar-footer">
111
  {user && (
112
  <div className="sidebar-user">
 
128
  </div>
129
  </aside>
130
  );
131
+
132
+ // ── Mobile Bottom Tab Bar + Drawer ───────────────────────────────
133
+ const mobileNav = (
134
+ <>
135
+ <nav className="mobile-nav">
136
+ <div className="mobile-nav-items">
137
+ {mobileNavItems.map((item) => (
138
+ <NavLink
139
+ key={item.to}
140
+ to={item.to}
141
+ className={({ isActive }) => `mobile-nav-item ${isActive ? 'active' : ''}`}
142
+ >
143
+ {item.icon}
144
+ <span>{item.label}</span>
145
+ </NavLink>
146
+ ))}
147
+ {/* More button */}
148
+ <button
149
+ className={`mobile-nav-item ${drawerOpen ? 'active' : ''}`}
150
+ onClick={() => setDrawerOpen(!drawerOpen)}
151
+ >
152
+ <svg viewBox="0 0 20 20" fill="currentColor" width="20" height="20">
153
+ <path fillRule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clipRule="evenodd"/>
154
+ </svg>
155
+ <span>More</span>
156
+ </button>
157
+ </div>
158
+ </nav>
159
+
160
+ {/* Slide-up drawer with all nav items */}
161
+ {drawerOpen && (
162
+ <>
163
+ <div className="mobile-drawer-overlay" onClick={() => setDrawerOpen(false)} />
164
+ <div className="mobile-drawer">
165
+ <div className="mobile-drawer-handle" />
166
+ {allLinks.map((group) => (
167
+ <div key={group.section} className="sidebar-section">
168
+ <div className="sidebar-section-label">{group.section}</div>
169
+ {group.items.map((link) => (
170
+ <NavLink
171
+ key={link.to}
172
+ to={link.to}
173
+ className={({ isActive }) => `sidebar-link ${isActive ? 'active' : ''}`}
174
+ onClick={() => setDrawerOpen(false)}
175
+ >
176
+ <span className="sidebar-icon">{link.icon}</span>
177
+ <span className="sidebar-label">{link.label}</span>
178
+ </NavLink>
179
+ ))}
180
+ </div>
181
+ ))}
182
+ {/* User + Logout in drawer */}
183
+ <div style={{ borderTop: '1px solid var(--border-subtle)', marginTop: '0.5rem', paddingTop: '0.75rem' }}>
184
+ {user && (
185
+ <div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', padding: '0.5rem 1rem', marginBottom: '0.5rem' }}>
186
+ <div className="sidebar-avatar">
187
+ {(user.username || 'U').charAt(0).toUpperCase()}
188
+ </div>
189
+ <div>
190
+ <div style={{ fontSize: '0.85rem', fontWeight: 600 }}>{user.full_name || user.username}</div>
191
+ <div style={{ fontSize: '0.7rem', color: 'var(--text-muted)' }}>{user.email}</div>
192
+ </div>
193
+ </div>
194
+ )}
195
+ <button className="sidebar-link sidebar-logout" onClick={handleLogout} style={{ width: '100%' }}>
196
+ <span className="sidebar-icon">
197
+ <svg viewBox="0 0 20 20" fill="currentColor"><path fillRule="evenodd" d="M3 3a1 1 0 00-1 1v12a1 1 0 102 0V4a1 1 0 00-1-1zm10.293 9.293a1 1 0 001.414 1.414l3-3a1 1 0 000-1.414l-3-3a1 1 0 10-1.414 1.414L14.586 9H7a1 1 0 100 2h7.586l-1.293 1.293z" clipRule="evenodd"/></svg>
198
+ </span>
199
+ <span className="sidebar-label">Log Out</span>
200
+ </button>
201
+ </div>
202
+ </div>
203
+ </>
204
+ )}
205
+ </>
206
+ );
207
+
208
+ return (
209
+ <>
210
+ {isMobile ? mobileNav : desktopSidebar}
211
+ </>
212
+ );
213
  }
frontend/src/index.css CHANGED
@@ -138,9 +138,8 @@ h3 { font-size: 1rem; font-family: var(--font-sans); font-weight: 600; }
138
  transition: width 0.25s cubic-bezier(0.4, 0, 0.2, 1);
139
  z-index: 200;
140
  }
141
- .sidebar:hover {
142
  width: 260px;
143
- box-shadow: var(--shadow-lg);
144
  }
145
 
146
  /* Brand */
@@ -172,7 +171,7 @@ h3 { font-size: 1rem; font-family: var(--font-sans); font-weight: 600; }
172
  transform: translateX(-8px);
173
  transition: opacity 0.2s ease 0.05s, transform 0.2s ease 0.05s;
174
  }
175
- .sidebar:hover .sidebar-brand-text {
176
  opacity: 1;
177
  transform: translateX(0);
178
  }
@@ -204,7 +203,7 @@ h3 { font-size: 1rem; font-family: var(--font-sans); font-weight: 600; }
204
  opacity: 0;
205
  transition: opacity 0.2s ease 0.05s;
206
  }
207
- .sidebar:hover .sidebar-section-label {
208
  opacity: 1;
209
  }
210
 
@@ -256,7 +255,7 @@ h3 { font-size: 1rem; font-family: var(--font-sans); font-weight: 600; }
256
  transform: translateX(-8px);
257
  transition: opacity 0.2s ease 0.05s, transform 0.2s ease 0.05s;
258
  }
259
- .sidebar:hover .sidebar-label {
260
  opacity: 1;
261
  transform: translateX(0);
262
  }
@@ -293,7 +292,7 @@ h3 { font-size: 1rem; font-family: var(--font-sans); font-weight: 600; }
293
  opacity: 0;
294
  transition: opacity 0.2s ease 0.05s;
295
  }
296
- .sidebar:hover .sidebar-user-info {
297
  opacity: 1;
298
  }
299
  .sidebar-user-name {
@@ -566,12 +565,393 @@ tr:hover td { background: var(--bg-hover); }
566
  }
567
  .icon-box svg { width: 22px; height: 22px; }
568
 
569
- /* ── Responsive ──────────────────────────────────────────────────────── */
 
 
 
 
570
  @media (max-width: 768px) {
571
- .app-main { margin-left: 0; }
572
- .sidebar { display: none; }
573
- .page { padding: 1.5rem 1rem; }
574
- .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
575
  }
576
 
577
  /* ── Scrollbar ───────────────────────────────────────────────────────── */
 
138
  transition: width 0.25s cubic-bezier(0.4, 0, 0.2, 1);
139
  z-index: 200;
140
  }
141
+ .sidebar.sidebar-expanded {
142
  width: 260px;
 
143
  }
144
 
145
  /* Brand */
 
171
  transform: translateX(-8px);
172
  transition: opacity 0.2s ease 0.05s, transform 0.2s ease 0.05s;
173
  }
174
+ .sidebar.sidebar-expanded .sidebar-brand-text {
175
  opacity: 1;
176
  transform: translateX(0);
177
  }
 
203
  opacity: 0;
204
  transition: opacity 0.2s ease 0.05s;
205
  }
206
+ .sidebar.sidebar-expanded .sidebar-section-label {
207
  opacity: 1;
208
  }
209
 
 
255
  transform: translateX(-8px);
256
  transition: opacity 0.2s ease 0.05s, transform 0.2s ease 0.05s;
257
  }
258
+ .sidebar.sidebar-expanded .sidebar-label {
259
  opacity: 1;
260
  transform: translateX(0);
261
  }
 
292
  opacity: 0;
293
  transition: opacity 0.2s ease 0.05s;
294
  }
295
+ .sidebar.sidebar-expanded .sidebar-user-info {
296
  opacity: 1;
297
  }
298
  .sidebar-user-name {
 
565
  }
566
  .icon-box svg { width: 22px; height: 22px; }
567
 
568
+ /* ═══════════════════════════════════════════════════════════════════════
569
+ MOBILE RESPONSIVE — max-width: 768px
570
+ All rules below ONLY apply on mobile. Desktop is untouched.
571
+ ═══════════════════════════════════════════════════════════════════════ */
572
+
573
  @media (max-width: 768px) {
574
+
575
+ /* ── App Shell ─────────────────────────────────────────────────────── */
576
+ .app-main {
577
+ margin-left: 0 !important;
578
+ padding-bottom: 72px; /* space for bottom tab bar */
579
+ }
580
+
581
+ /* ── Desktop Sidebar hidden on mobile ──────────────────────────────── */
582
+ .sidebar:not(.mobile-nav) {
583
+ display: none !important;
584
+ }
585
+
586
+ /* ── Mobile Bottom Tab Bar ─────────────────────────────────────────── */
587
+ .mobile-nav {
588
+ display: flex !important;
589
+ position: fixed;
590
+ bottom: 0;
591
+ left: 0;
592
+ right: 0;
593
+ height: 64px;
594
+ background: var(--bg-primary);
595
+ border-top: 1px solid var(--border-color);
596
+ z-index: 300;
597
+ padding: 0 0.25rem;
598
+ box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.06);
599
+ }
600
+ .mobile-nav-items {
601
+ display: flex;
602
+ justify-content: space-around;
603
+ align-items: center;
604
+ width: 100%;
605
+ height: 100%;
606
+ }
607
+ .mobile-nav-item {
608
+ display: flex;
609
+ flex-direction: column;
610
+ align-items: center;
611
+ justify-content: center;
612
+ gap: 0.2rem;
613
+ padding: 0.35rem 0.5rem;
614
+ border-radius: var(--radius-md);
615
+ color: var(--text-muted);
616
+ text-decoration: none;
617
+ font-size: 0.6rem;
618
+ font-weight: 600;
619
+ letter-spacing: 0.02em;
620
+ transition: color var(--transition-fast);
621
+ background: none;
622
+ border: none;
623
+ cursor: pointer;
624
+ font-family: var(--font-sans);
625
+ }
626
+ .mobile-nav-item svg {
627
+ width: 20px;
628
+ height: 20px;
629
+ }
630
+ .mobile-nav-item.active,
631
+ .mobile-nav-item:hover {
632
+ color: var(--accent);
633
+ }
634
+
635
+ /* ── Mobile drawer overlay ─────────────────────────────────────────── */
636
+ .mobile-drawer-overlay {
637
+ position: fixed;
638
+ inset: 0;
639
+ background: rgba(0, 0, 0, 0.4);
640
+ z-index: 400;
641
+ animation: fadeIn 0.2s ease;
642
+ }
643
+ .mobile-drawer {
644
+ position: fixed;
645
+ bottom: 0;
646
+ left: 0;
647
+ right: 0;
648
+ background: var(--bg-primary);
649
+ border-radius: 20px 20px 0 0;
650
+ padding: 1rem 0.75rem 2rem;
651
+ z-index: 500;
652
+ max-height: 70vh;
653
+ overflow-y: auto;
654
+ animation: slideUp 0.25s ease;
655
+ }
656
+ @keyframes slideUp {
657
+ from { transform: translateY(100%); }
658
+ to { transform: translateY(0); }
659
+ }
660
+ .mobile-drawer-handle {
661
+ width: 36px;
662
+ height: 4px;
663
+ background: var(--border-color);
664
+ border-radius: 2px;
665
+ margin: 0 auto 1rem;
666
+ }
667
+ .mobile-drawer .sidebar-link {
668
+ width: 100%;
669
+ padding: 0.75rem 1rem;
670
+ margin: 2px 0;
671
+ font-size: 0.88rem;
672
+ border-radius: var(--radius-md);
673
+ opacity: 1;
674
+ }
675
+ .mobile-drawer .sidebar-link .sidebar-label {
676
+ opacity: 1;
677
+ transform: none;
678
+ }
679
+ .mobile-drawer .sidebar-link .sidebar-icon {
680
+ width: 22px;
681
+ height: 22px;
682
+ }
683
+ .mobile-drawer .sidebar-section-label {
684
+ opacity: 1;
685
+ padding: 0.5rem 1rem 0.25rem;
686
+ font-size: 0.65rem;
687
+ }
688
+
689
+ /* ── Page layout ───────────────────────────────────────────────────── */
690
+ .page {
691
+ padding: 1.25rem 1rem !important;
692
+ max-width: 100%;
693
+ }
694
+ .page-header {
695
+ margin-bottom: 1.25rem;
696
+ padding-bottom: 1rem;
697
+ }
698
+ .page-header h1 {
699
+ font-size: 1.5rem !important;
700
+ }
701
+ .page-header p {
702
+ font-size: 0.85rem;
703
+ }
704
+
705
+ /* ── Grids collapse ────────────────────────────────────────────────── */
706
+ .grid-2,
707
+ .grid-3,
708
+ .grid-4 {
709
+ grid-template-columns: 1fr !important;
710
+ gap: 1rem !important;
711
+ }
712
+
713
+ /* ── Cards ─────────────────────────────────────────────────────────── */
714
+ .card {
715
+ padding: 1.125rem !important;
716
+ border-radius: var(--radius-md);
717
+ }
718
+ .card-header {
719
+ margin-bottom: 1rem;
720
+ padding-bottom: 0.75rem;
721
+ flex-wrap: wrap;
722
+ gap: 0.5rem;
723
+ }
724
+ .card-header h3 {
725
+ font-size: 0.88rem;
726
+ }
727
+
728
+ /* ── Tables ────────────────────────────────────────────────────────── */
729
+ .table-container {
730
+ margin: 0 -1.125rem;
731
+ border-radius: 0;
732
+ border-left: none;
733
+ border-right: none;
734
+ -webkit-overflow-scrolling: touch;
735
+ }
736
+ th, td {
737
+ padding: 0.5rem 0.625rem;
738
+ font-size: 0.72rem;
739
+ white-space: nowrap;
740
+ }
741
+
742
+ /* ── Charts ────────────────────────────────────────────────────────── */
743
+ .chart-container {
744
+ height: 240px !important;
745
+ padding: 0.375rem;
746
+ }
747
+
748
+ /* ── Metrics ───────────────────────────────────────────────────────── */
749
+ .metric { padding: 0.875rem 0.75rem; }
750
+ .metric-value { font-size: 1.375rem; }
751
+ .metric-label { font-size: 0.6rem; }
752
+
753
+ /* ── Tabs ───────────────────────────────────────────────────────────── */
754
+ .tabs {
755
+ overflow-x: auto;
756
+ -webkit-overflow-scrolling: touch;
757
+ flex-wrap: nowrap;
758
+ gap: 0;
759
+ margin-bottom: 1.25rem;
760
+ scrollbar-width: none;
761
+ }
762
+ .tabs::-webkit-scrollbar { display: none; }
763
+ .tab {
764
+ padding: 0.625rem 0.875rem;
765
+ font-size: 0.7rem;
766
+ flex-shrink: 0;
767
+ }
768
+
769
+ /* ── Buttons ───────────────────────────────────────────────────────── */
770
+ .btn-lg {
771
+ padding: 0.75rem 1.5rem;
772
+ font-size: 0.8rem;
773
+ }
774
+
775
+ /* ── Forms ─────────────────────────────────────────────────────────── */
776
+ .input, select, textarea {
777
+ font-size: 16px; /* prevents iOS zoom on focus */
778
+ }
779
+ .form-group label {
780
+ font-size: 0.7rem;
781
+ }
782
+
783
+ /* ── Flex Utils ────────────────────────────────────────────────────── */
784
+ .flex-between {
785
+ flex-wrap: wrap;
786
+ gap: 0.5rem;
787
+ }
788
+
789
+ /* ── Empty state ───────────────────────────────────────────────────── */
790
+ .empty-state {
791
+ padding: 2.5rem 1.5rem;
792
+ }
793
+
794
+ /* ── Loading ───────────────────────────────────────────────────────── */
795
+ .loading-overlay {
796
+ padding: 3rem 1.5rem;
797
+ }
798
+
799
+ /* ── Badge ─────────────────────────────────────────────────────────── */
800
+ .badge {
801
+ font-size: 0.6rem;
802
+ padding: 0.15rem 0.5rem;
803
+ }
804
+
805
+ /* ── Icon box ──────────────────────────────────────────────────────── */
806
+ .icon-box {
807
+ width: 40px;
808
+ height: 40px;
809
+ }
810
+ .icon-box svg { width: 18px; height: 18px; }
811
+
812
+ /* ═════════════════════════════════════════════════════════════════════
813
+ PAGE-SPECIFIC MOBILE OVERRIDES
814
+ ═════════════════════════════════════════════════════════════════════ */
815
+
816
+ /* ── Landing Page ──────────────────────────────────────────────────── */
817
+ .landing-header {
818
+ padding: 0.875rem 1rem !important;
819
+ }
820
+ .landing-header .brand-text {
821
+ font-size: 1.1rem;
822
+ }
823
+ .landing-hero {
824
+ min-height: 80vh !important;
825
+ padding: 1rem !important;
826
+ }
827
+ .landing-hero h1 {
828
+ font-size: clamp(1.75rem, 7vw, 2.5rem) !important;
829
+ line-height: 1.2 !important;
830
+ }
831
+ .landing-hero p {
832
+ font-size: 0.9rem !important;
833
+ }
834
+ .landing-hero .flex-gap {
835
+ flex-direction: column;
836
+ width: 100%;
837
+ }
838
+ .landing-hero .flex-gap .btn {
839
+ width: 100%;
840
+ }
841
+ .landing-section {
842
+ padding: 3rem 1.25rem !important;
843
+ }
844
+ .landing-footer {
845
+ flex-direction: column !important;
846
+ gap: 0.5rem;
847
+ text-align: center;
848
+ padding: 1.5rem 1rem !important;
849
+ }
850
+
851
+ /* ── Login / Register ──────────────────────────────────────────────── */
852
+ .auth-container {
853
+ flex-direction: column !important;
854
+ }
855
+ .auth-card {
856
+ padding: 2rem 1.25rem !important;
857
+ min-height: 100vh;
858
+ }
859
+ .auth-image {
860
+ display: none !important;
861
+ }
862
+
863
+ /* ── Dashboard ─────────────────────────────────────────────────────── */
864
+ .dashboard-ticker-strip {
865
+ gap: 0.375rem !important;
866
+ }
867
+ .dashboard-ticker-strip button {
868
+ min-width: 120px !important;
869
+ padding: 0.5rem 0.625rem !important;
870
+ }
871
+
872
+ /* ── Market Explorer ───────────────────────────────────────────────── */
873
+ .market-search-bar {
874
+ flex-direction: column !important;
875
+ gap: 0.75rem !important;
876
+ }
877
+ .market-search-bar .input {
878
+ width: 100% !important;
879
+ }
880
+ .market-search-bar select {
881
+ width: 100% !important;
882
+ }
883
+ .market-tabs {
884
+ overflow-x: auto !important;
885
+ flex-wrap: nowrap !important;
886
+ }
887
+ .ticker-chips {
888
+ flex-wrap: wrap !important;
889
+ gap: 0.375rem !important;
890
+ }
891
+ .company-info-grid {
892
+ grid-template-columns: 1fr 1fr !important;
893
+ gap: 0.625rem !important;
894
+ }
895
+
896
+ /* ── Factor Analysis ───────────────────────────────────────────────── */
897
+ .factor-input-bar {
898
+ flex-direction: column !important;
899
+ gap: 0.75rem !important;
900
+ }
901
+ .factor-input-bar .input {
902
+ width: 100% !important;
903
+ }
904
+
905
+ /* ── Sentiment ─────────────────────────────────────────────────────── */
906
+ .sentiment-gauge-row {
907
+ grid-template-columns: 1fr !important;
908
+ }
909
+
910
+ /* ── Holdings ──────────────────────────────────────────────────────── */
911
+ .holdings-actions {
912
+ flex-direction: column !important;
913
+ gap: 0.5rem !important;
914
+ }
915
+ .holdings-actions .btn {
916
+ width: 100%;
917
+ }
918
+
919
+ /* ── Strategy Builder ──────────────────────────────────────────────── */
920
+ .strategy-form-row {
921
+ flex-direction: column !important;
922
+ gap: 0.75rem !important;
923
+ }
924
+
925
+ /* ── Portfolio Analysis ────────────────────────────────────────────── */
926
+ .portfolio-stats-grid {
927
+ grid-template-columns: 1fr 1fr !important;
928
+ }
929
+
930
+ /* ── Recharts responsive ───────────────────────────────────────────── */
931
+ .recharts-wrapper {
932
+ font-size: 0.7rem;
933
+ }
934
+ .recharts-cartesian-axis-tick-value {
935
+ font-size: 0.65rem;
936
+ }
937
+
938
+ /* ── Generic inline-style overrides using attribute selectors ───── */
939
+ /* Override inline grid/flex styles that use fixed column widths */
940
+ [style*="grid-template-columns: repeat"] {
941
+ grid-template-columns: 1fr !important;
942
+ }
943
+ [style*="gridTemplateColumns"] {
944
+ grid-template-columns: 1fr !important;
945
+ }
946
+ [style*="padding: 6rem 3rem"],
947
+ [style*="padding:'6rem 3rem'"] {
948
+ padding: 3rem 1.25rem !important;
949
+ }
950
+
951
+ /* Fix inline flex layouts that don't wrap */
952
+ [style*="display: flex"][style*="gap:"] {
953
+ flex-wrap: wrap;
954
+ }
955
  }
956
 
957
  /* ── Scrollbar ───────────────────────────────────────────────────────── */
frontend/src/pages/Dashboard.tsx CHANGED
@@ -105,7 +105,7 @@ export default function Dashboard() {
105
  </div>
106
 
107
  {/* Global Market Ticker Strip */}
108
- <div style={{display:'flex',gap:'0.5rem',marginBottom:'1.5rem',overflowX:'auto',paddingBottom:'0.25rem'}}>
109
  {GLOBAL_INDICES.map(idx => {
110
  const data = globalPrices[idx.ticker];
111
  const change = data?.change || 0;
 
105
  </div>
106
 
107
  {/* Global Market Ticker Strip */}
108
+ <div className="dashboard-ticker-strip" style={{display:'flex',gap:'0.5rem',marginBottom:'1.5rem',overflowX:'auto',paddingBottom:'0.25rem'}}>
109
  {GLOBAL_INDICES.map(idx => {
110
  const data = globalPrices[idx.ticker];
111
  const change = data?.change || 0;
frontend/src/pages/FactorAnalysis.tsx CHANGED
@@ -78,7 +78,7 @@ export default function FactorAnalysis() {
78
 
79
  {/* Input */}
80
  <div className="card" style={{marginBottom:'1.5rem'}}>
81
- <div style={{display:'flex',gap:'0.75rem',alignItems:'flex-end',flexWrap:'wrap'}}>
82
  <div style={{flex:1,minWidth:300}}>
83
  <label style={{fontSize:'0.75rem',fontWeight:600,color:'var(--text-muted)',textTransform:'uppercase',letterSpacing:'0.05em',display:'block',marginBottom:'0.375rem'}}>Ticker Universe (comma-separated)</label>
84
  <input className="input" value={tickers} onChange={e => setTickers(e.target.value)} placeholder="AAPL, MSFT, GOOGL..." />
 
78
 
79
  {/* Input */}
80
  <div className="card" style={{marginBottom:'1.5rem'}}>
81
+ <div className="factor-input-bar" style={{display:'flex',gap:'0.75rem',alignItems:'flex-end',flexWrap:'wrap'}}>
82
  <div style={{flex:1,minWidth:300}}>
83
  <label style={{fontSize:'0.75rem',fontWeight:600,color:'var(--text-muted)',textTransform:'uppercase',letterSpacing:'0.05em',display:'block',marginBottom:'0.375rem'}}>Ticker Universe (comma-separated)</label>
84
  <input className="input" value={tickers} onChange={e => setTickers(e.target.value)} placeholder="AAPL, MSFT, GOOGL..." />
frontend/src/pages/Landing.tsx CHANGED
@@ -36,7 +36,7 @@ export default function Landing() {
36
  return (
37
  <div style={{minHeight:'100vh',background:'var(--bg-primary)'}}>
38
  {/* ── Top Nav ────────────────────────────────────────────── */}
39
- <header style={{display:'flex',justifyContent:'space-between',alignItems:'center',padding:'1.25rem 3rem',position:'absolute',top:0,left:0,right:0,zIndex:10}}>
40
  <div style={{fontFamily:'var(--font-serif)',fontSize:'1.3rem',fontWeight:600,color:'#fff',display:'flex',alignItems:'center',gap:'0.5rem'}}>
41
  <svg width="28" height="28" viewBox="0 0 32 32" fill="none">
42
  <rect width="32" height="32" rx="6" fill="rgba(255,255,255,0.15)"/>
@@ -51,7 +51,7 @@ export default function Landing() {
51
  </header>
52
 
53
  {/* ── Hero ──────────────────────────────────────────────── */}
54
- <section style={{
55
  position:'relative',
56
  minHeight:'92vh',
57
  display:'flex',
@@ -86,7 +86,7 @@ export default function Landing() {
86
  </section>
87
 
88
  {/* ── Capabilities ──────────────────────────────────────── */}
89
- <section style={{padding:'6rem 3rem',maxWidth:'1200px',margin:'0 auto'}}>
90
  <div style={{textAlign:'center',marginBottom:'4rem'}}>
91
  <p style={{fontSize:'0.75rem',fontWeight:600,letterSpacing:'0.2em',textTransform:'uppercase',color:'var(--accent)',marginBottom:'0.75rem'}}>Capabilities</p>
92
  <h2>What Powers Our Platform</h2>
@@ -106,7 +106,7 @@ export default function Landing() {
106
  </section>
107
 
108
  {/* ── Global Coverage ───────────────────────────────────── */}
109
- <section style={{
110
  padding:'6rem 3rem',
111
  backgroundImage:'url(/images/pattern-bg.png)',
112
  backgroundSize:'cover',
@@ -134,7 +134,7 @@ export default function Landing() {
134
  </section>
135
 
136
  {/* ── CTA ───────────────────────────────────────────────── */}
137
- <section style={{
138
  position:'relative',
139
  padding:'6rem 3rem',
140
  backgroundImage:'url(/images/buildings-bg.png)',
@@ -156,7 +156,7 @@ export default function Landing() {
156
  </section>
157
 
158
  {/* ── Footer ────────────────────────────────────────────── */}
159
- <footer style={{padding:'2rem 3rem',borderTop:'1px solid var(--border-color)',display:'flex',justifyContent:'space-between',alignItems:'center'}}>
160
  <p style={{color:'var(--text-muted)',fontSize:'0.75rem'}}>
161
  © 2026 QuantHedge. All rights reserved. For research and educational purposes only.
162
  </p>
 
36
  return (
37
  <div style={{minHeight:'100vh',background:'var(--bg-primary)'}}>
38
  {/* ── Top Nav ────────────────────────────────────────────── */}
39
+ <header className="landing-header" style={{display:'flex',justifyContent:'space-between',alignItems:'center',padding:'1.25rem 3rem',position:'absolute',top:0,left:0,right:0,zIndex:10}}>
40
  <div style={{fontFamily:'var(--font-serif)',fontSize:'1.3rem',fontWeight:600,color:'#fff',display:'flex',alignItems:'center',gap:'0.5rem'}}>
41
  <svg width="28" height="28" viewBox="0 0 32 32" fill="none">
42
  <rect width="32" height="32" rx="6" fill="rgba(255,255,255,0.15)"/>
 
51
  </header>
52
 
53
  {/* ── Hero ──────────────────────────────────────────────── */}
54
+ <section className="landing-hero" style={{
55
  position:'relative',
56
  minHeight:'92vh',
57
  display:'flex',
 
86
  </section>
87
 
88
  {/* ── Capabilities ──────────────────────────────────────── */}
89
+ <section className="landing-section" style={{padding:'6rem 3rem',maxWidth:'1200px',margin:'0 auto'}}>
90
  <div style={{textAlign:'center',marginBottom:'4rem'}}>
91
  <p style={{fontSize:'0.75rem',fontWeight:600,letterSpacing:'0.2em',textTransform:'uppercase',color:'var(--accent)',marginBottom:'0.75rem'}}>Capabilities</p>
92
  <h2>What Powers Our Platform</h2>
 
106
  </section>
107
 
108
  {/* ── Global Coverage ───────────────────────────────────── */}
109
+ <section className="landing-section" style={{
110
  padding:'6rem 3rem',
111
  backgroundImage:'url(/images/pattern-bg.png)',
112
  backgroundSize:'cover',
 
134
  </section>
135
 
136
  {/* ── CTA ───────────────────────────────────────────────── */}
137
+ <section className="landing-section" style={{
138
  position:'relative',
139
  padding:'6rem 3rem',
140
  backgroundImage:'url(/images/buildings-bg.png)',
 
156
  </section>
157
 
158
  {/* ── Footer ────────────────────────────────────────────── */}
159
+ <footer className="landing-footer" style={{padding:'2rem 3rem',borderTop:'1px solid var(--border-color)',display:'flex',justifyContent:'space-between',alignItems:'center'}}>
160
  <p style={{color:'var(--text-muted)',fontSize:'0.75rem'}}>
161
  © 2026 QuantHedge. All rights reserved. For research and educational purposes only.
162
  </p>
frontend/src/pages/Login.tsx CHANGED
@@ -25,9 +25,9 @@ export default function Login() {
25
  };
26
 
27
  return (
28
- <div style={{minHeight:'100vh',display:'flex'}}>
29
  {/* Left — form */}
30
- <div style={{flex:1,display:'flex',alignItems:'center',justifyContent:'center',padding:'3rem'}}>
31
  <div style={{width:'100%',maxWidth:'400px'}}>
32
  <div style={{marginBottom:'2.5rem'}}>
33
  <div style={{display:'flex',alignItems:'center',gap:'0.5rem',marginBottom:'2rem'}}>
@@ -70,7 +70,7 @@ export default function Login() {
70
  </div>
71
 
72
  {/* Right — image */}
73
- <div style={{
74
  flex:1,
75
  backgroundImage:'url(/images/buildings-bg.png)',
76
  backgroundSize:'cover',
 
25
  };
26
 
27
  return (
28
+ <div className="auth-container" style={{minHeight:'100vh',display:'flex'}}>
29
  {/* Left — form */}
30
+ <div className="auth-card" style={{flex:1,display:'flex',alignItems:'center',justifyContent:'center',padding:'3rem'}}>
31
  <div style={{width:'100%',maxWidth:'400px'}}>
32
  <div style={{marginBottom:'2.5rem'}}>
33
  <div style={{display:'flex',alignItems:'center',gap:'0.5rem',marginBottom:'2rem'}}>
 
70
  </div>
71
 
72
  {/* Right — image */}
73
+ <div className="auth-image" style={{
74
  flex:1,
75
  backgroundImage:'url(/images/buildings-bg.png)',
76
  backgroundSize:'cover',
frontend/src/pages/MarketExplorer.tsx CHANGED
@@ -50,7 +50,7 @@ export default function MarketExplorer() {
50
 
51
  {/* Search */}
52
  <div className="card" style={{marginBottom:'1.5rem'}}>
53
- <div style={{display:'flex',gap:'0.75rem',alignItems:'flex-end',flexWrap:'wrap'}}>
54
  <div style={{flex:1,minWidth:200}}>
55
  <label style={{fontSize:'0.75rem',fontWeight:600,color:'var(--text-muted)',textTransform:'uppercase',letterSpacing:'0.05em',display:'block',marginBottom:'0.375rem'}}>Ticker Symbol</label>
56
  <TickerSearch value={ticker} onChange={v => setTicker(v.toUpperCase())} onSelect={t => setTicker(t.symbol)} placeholder="Search any ticker..." />
@@ -73,7 +73,7 @@ export default function MarketExplorer() {
73
  <button key={m} className={`tab ${activeMarket === m ? 'active' : ''}`} onClick={() => loadPopularTickers(m)}>{m}</button>
74
  ))}
75
  </div>
76
- <div style={{display:'flex',gap:'0.5rem',flexWrap:'wrap'}}>
77
  {popularTickers.slice(0, 15).map(t => (
78
  <button key={t} className="btn btn-secondary btn-sm" style={{fontFamily:'var(--font-mono)',fontSize:'0.75rem'}}
79
  onClick={() => search(t)}>{t}</button>
@@ -113,7 +113,7 @@ export default function MarketExplorer() {
113
  {companyInfo && !companyInfo.error && (
114
  <div className="card">
115
  <div className="card-header"><h3>Company Info</h3></div>
116
- <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:'0.75rem'}}>
117
  {[
118
  ['Sector', companyInfo.sector],
119
  ['Industry', companyInfo.industry],
 
50
 
51
  {/* Search */}
52
  <div className="card" style={{marginBottom:'1.5rem'}}>
53
+ <div className="market-search-bar" style={{display:'flex',gap:'0.75rem',alignItems:'flex-end',flexWrap:'wrap'}}>
54
  <div style={{flex:1,minWidth:200}}>
55
  <label style={{fontSize:'0.75rem',fontWeight:600,color:'var(--text-muted)',textTransform:'uppercase',letterSpacing:'0.05em',display:'block',marginBottom:'0.375rem'}}>Ticker Symbol</label>
56
  <TickerSearch value={ticker} onChange={v => setTicker(v.toUpperCase())} onSelect={t => setTicker(t.symbol)} placeholder="Search any ticker..." />
 
73
  <button key={m} className={`tab ${activeMarket === m ? 'active' : ''}`} onClick={() => loadPopularTickers(m)}>{m}</button>
74
  ))}
75
  </div>
76
+ <div className="ticker-chips" style={{display:'flex',gap:'0.5rem',flexWrap:'wrap'}}>
77
  {popularTickers.slice(0, 15).map(t => (
78
  <button key={t} className="btn btn-secondary btn-sm" style={{fontFamily:'var(--font-mono)',fontSize:'0.75rem'}}
79
  onClick={() => search(t)}>{t}</button>
 
113
  {companyInfo && !companyInfo.error && (
114
  <div className="card">
115
  <div className="card-header"><h3>Company Info</h3></div>
116
+ <div className="company-info-grid" style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:'0.75rem'}}>
117
  {[
118
  ['Sector', companyInfo.sector],
119
  ['Industry', companyInfo.industry],
frontend/src/pages/PortfolioAnalysis.tsx CHANGED
@@ -13,6 +13,7 @@ export default function PortfolioAnalysis() {
13
  const [activeTab, setActiveTab] = useState('optimize');
14
  const [corrData, setCorrData] = useState<any>(null);
15
  const [corrLoading, setCorrLoading] = useState(false);
 
16
 
17
  const optimize = async () => {
18
  setLoading(true);
@@ -27,6 +28,25 @@ export default function PortfolioAnalysis() {
27
  } catch (e) { console.error(e); } finally { setLoading(false); }
28
  };
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  const pieData = weights?.weights ? Object.entries(weights.weights).filter(([,v]: any) => v > 0.001).map(([ticker, weight]: any) => ({
31
  name: ticker, value: parseFloat((weight * 100).toFixed(1)),
32
  })) : [];
@@ -42,7 +62,26 @@ export default function PortfolioAnalysis() {
42
  <div className="card" style={{marginBottom:'1.5rem'}}>
43
  <div style={{display:'flex',gap:'0.75rem',alignItems:'flex-end',flexWrap:'wrap'}}>
44
  <div style={{flex:1,minWidth:300}}>
45
- <label style={{fontSize:'0.75rem',fontWeight:600,color:'var(--text-muted)',textTransform:'uppercase',letterSpacing:'0.05em',display:'block',marginBottom:'0.375rem'}}>Portfolio Assets</label>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  <input className="input" value={tickers} onChange={e => setTickers(e.target.value)} />
47
  </div>
48
  <div>
 
13
  const [activeTab, setActiveTab] = useState('optimize');
14
  const [corrData, setCorrData] = useState<any>(null);
15
  const [corrLoading, setCorrLoading] = useState(false);
16
+ const [holdingsLoading, setHoldingsLoading] = useState(false);
17
 
18
  const optimize = async () => {
19
  setLoading(true);
 
28
  } catch (e) { console.error(e); } finally { setLoading(false); }
29
  };
30
 
31
+ const useMyHoldings = async () => {
32
+ setHoldingsLoading(true);
33
+ try {
34
+ const res = await holdingsAPI.summary();
35
+ const holdings = res.data.holdings || [];
36
+ if (holdings.length === 0) {
37
+ alert('No holdings found. Add positions in the Holdings section first.');
38
+ return;
39
+ }
40
+ const holdingTickers = holdings.map((h: any) => h.ticker).join(', ');
41
+ setTickers(holdingTickers);
42
+ } catch (e) {
43
+ console.error(e);
44
+ alert('Failed to fetch holdings. Please try again.');
45
+ } finally {
46
+ setHoldingsLoading(false);
47
+ }
48
+ };
49
+
50
  const pieData = weights?.weights ? Object.entries(weights.weights).filter(([,v]: any) => v > 0.001).map(([ticker, weight]: any) => ({
51
  name: ticker, value: parseFloat((weight * 100).toFixed(1)),
52
  })) : [];
 
62
  <div className="card" style={{marginBottom:'1.5rem'}}>
63
  <div style={{display:'flex',gap:'0.75rem',alignItems:'flex-end',flexWrap:'wrap'}}>
64
  <div style={{flex:1,minWidth:300}}>
65
+ <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:'0.375rem'}}>
66
+ <label style={{fontSize:'0.75rem',fontWeight:600,color:'var(--text-muted)',textTransform:'uppercase',letterSpacing:'0.05em'}}>Portfolio Assets</label>
67
+ <button
68
+ onClick={useMyHoldings}
69
+ disabled={holdingsLoading}
70
+ style={{
71
+ background:'none',border:'1px solid var(--border-color)',borderRadius:'var(--radius-sm)',
72
+ padding:'0.2rem 0.6rem',fontSize:'0.7rem',fontWeight:600,color:'var(--accent)',
73
+ cursor:'pointer',display:'flex',alignItems:'center',gap:'0.3rem',
74
+ transition:'all 150ms ease',fontFamily:'var(--font-sans)',
75
+ }}
76
+ onMouseEnter={e => { e.currentTarget.style.background = 'var(--accent-lighter)'; e.currentTarget.style.borderColor = 'var(--accent)'; }}
77
+ onMouseLeave={e => { e.currentTarget.style.background = 'none'; e.currentTarget.style.borderColor = 'var(--border-color)'; }}
78
+ >
79
+ {holdingsLoading ? <div className="spinner" style={{width:10,height:10,borderWidth:1.5}} /> : (
80
+ <svg width="12" height="12" viewBox="0 0 20 20" fill="currentColor"><path fillRule="evenodd" d="M4 4a2 2 0 00-2 2v4a2 2 0 002 2V6h10a2 2 0 00-2-2H4zm2 6a2 2 0 012-2h8a2 2 0 012 2v4a2 2 0 01-2 2H8a2 2 0 01-2-2v-4zm6 4a2 2 0 100-4 2 2 0 000 4z" clipRule="evenodd"/></svg>
81
+ )}
82
+ Use My Holdings
83
+ </button>
84
+ </div>
85
  <input className="input" value={tickers} onChange={e => setTickers(e.target.value)} />
86
  </div>
87
  <div>
frontend/src/pages/Register.tsx CHANGED
@@ -25,9 +25,9 @@ export default function Register() {
25
  };
26
 
27
  return (
28
- <div style={{minHeight:'100vh',display:'flex'}}>
29
  {/* Left — image */}
30
- <div style={{
31
  flex:1,
32
  backgroundImage:'url(/images/hero-bg.png)',
33
  backgroundSize:'cover',
@@ -47,7 +47,7 @@ export default function Register() {
47
  </div>
48
 
49
  {/* Right — form */}
50
- <div style={{flex:1,display:'flex',alignItems:'center',justifyContent:'center',padding:'3rem'}}>
51
  <div style={{width:'100%',maxWidth:'400px'}}>
52
  <div style={{marginBottom:'2.5rem'}}>
53
  <div style={{display:'flex',alignItems:'center',gap:'0.5rem',marginBottom:'2rem'}}>
 
25
  };
26
 
27
  return (
28
+ <div className="auth-container" style={{minHeight:'100vh',display:'flex'}}>
29
  {/* Left — image */}
30
+ <div className="auth-image" style={{
31
  flex:1,
32
  backgroundImage:'url(/images/hero-bg.png)',
33
  backgroundSize:'cover',
 
47
  </div>
48
 
49
  {/* Right — form */}
50
+ <div className="auth-card" style={{flex:1,display:'flex',alignItems:'center',justifyContent:'center',padding:'3rem'}}>
51
  <div style={{width:'100%',maxWidth:'400px'}}>
52
  <div style={{marginBottom:'2.5rem'}}>
53
  <div style={{display:'flex',alignItems:'center',gap:'0.5rem',marginBottom:'2rem'}}>