SudhanvaD commited on
Commit
5400cf3
·
verified ·
1 Parent(s): e842c32

Upload 31 files

Browse files
backend/__pycache__/gemini.cpython-311.pyc ADDED
Binary file (4.36 kB). View file
 
backend/__pycache__/server.cpython-311.pyc ADDED
Binary file (1.59 kB). View file
 
backend/gemini.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import backtrader as bt
2
+ import yfinance as yf
3
+ import re
4
+ import google.generativeai as genai
5
+ import matplotlib.pyplot as plt
6
+ import pandas as pd
7
+ import numpy as np
8
+ from datetime import datetime
9
+
10
+
11
+ # Configure the Generative AI model
12
+ MODEL_ID = "models/gemini-2.0-flash"
13
+ API_KEY = "ENTER API_KEY HERE"
14
+ genai.configure(api_key=API_KEY)
15
+ model = genai.GenerativeModel(MODEL_ID)
16
+
17
+
18
+
19
+
20
+ # ====== 1. Get Strategy Code from Gemini ======
21
+ def get_strategy_code_from_gemini(user_input: str):
22
+ prompt = f"""
23
+ You are a financial assistant that writes Backtrader strategies.
24
+
25
+
26
+ Take this user request: "{user_input}"
27
+ Generate a complete Backtrader strategy (Python) using bt.Strategy with these requirements:
28
+ 1. Initial capital is $100
29
+ 2. Only whole share orders (no fractional shares)
30
+ 3. Must include both buy and sell logic
31
+ 4. Strategy should have at least one indicator (like SMA, RSI, etc.)
32
+ 5. Include proper order management (check for pending orders)
33
+ 6. Include logging of trades
34
+ 7. Output only valid Python code (no explanation or markdown formatting)
35
+ """
36
+ response = model.generate_content(prompt)
37
+ strategy_code = response.text.strip()
38
+ strategy_code = strategy_code.replace("```python", "").replace("```", "")
39
+ return strategy_code
40
+
41
+
42
+ # ====== 2. Convert Gemini Output into a Strategy Class ======
43
+ def create_strategy_from_code(code_string: str):
44
+ local_scope = {}
45
+ try:
46
+ exec(code_string, globals(), local_scope)
47
+ for obj in local_scope.values():
48
+ if isinstance(obj, type) and issubclass(obj, bt.Strategy):
49
+ return obj
50
+ raise ValueError("No valid strategy class found in Gemini output.")
51
+ except Exception as e:
52
+ raise ValueError(f"Error creating strategy from code: {str(e)}")
53
+
54
+
55
+ # ====== 3. Remove extraneous main execution block ======
56
+ def extract_strategy_only(code_str: str) -> str:
57
+ main_block_start = code_str.find("if __name__ == '__main__':")
58
+ return code_str[:main_block_start].strip() if main_block_start != -1 else code_str.strip()
59
+
60
+
61
+ # ====== 4. Main Workflow ======
62
+ def full_workflow(user_input: str):
63
+ # Extract ticker and dates
64
+ ticker_match = re.search(r'\b([A-Z]{2,5})\b', user_input)
65
+ ticker = ticker_match.group(1) if ticker_match else "AAPL"
66
+
67
+
68
+ dates = re.findall(r'(\d{4}-\d{2}-\d{2})', user_input)
69
+ start_date = dates[0] if len(dates) > 0 else '2022-01-01'
70
+ end_date = dates[1] if len(dates) > 1 else '2023-01-01'
71
+
72
+
73
+ print("\n🤖 Generating strategy...")
74
+ strategy_code = get_strategy_code_from_gemini(user_input)
75
+
76
+
77
+ print(extract_strategy_only(strategy_code))
78
+
79
+
80
+ # ====== 7. Example Execution ======
81
+ if __name__ == "__main__":
82
+ user_input = "Create RSI strategy for MSFT, buy below 30 sell above 70, from 2021-01-01 to 2022-12-31"
83
+ full_workflow(user_input)
84
+
85
+ generate_answer = get_strategy_code_from_gemini
backend/requirements.txt ADDED
Binary file (194 Bytes). View file
 
backend/server.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from pydantic import BaseModel
4
+ from gemini import generate_answer
5
+
6
+ app = FastAPI()
7
+
8
+ # ✅ Allow requests from your frontend URL
9
+ app.add_middleware(
10
+ CORSMiddleware,
11
+ allow_origins=[
12
+ "https://fintrack-flax.vercel.app", # Vercel deployment URL
13
+ "http://localhost:5173", # Vite default port
14
+ "http://127.0.0.1:5173", # Some systems resolve as 127.0.0.1 instead
15
+ ],
16
+ allow_credentials=True,
17
+ allow_methods=["*"],
18
+ allow_headers=["*"],
19
+ )
20
+
21
+ class StrategyRequest(BaseModel):
22
+ user_input: str
23
+
24
+ @app.post("/generate-strategy")
25
+ async def generate_strategy(request: StrategyRequest):
26
+ try:
27
+ response = generate_answer(request.user_input)
28
+ return {"strategy_code": response}
29
+ except Exception as e:
30
+ return {"error": str(e)}
public/Cropped_Image.png ADDED
public/image.png ADDED
public/newchat.png ADDED
public/opensidebar.png ADDED
public/vite.svg ADDED
src/.DS_Store ADDED
Binary file (6.15 kB). View file
 
src/App.tsx ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useRef } from 'react'
2
+ import { useAuth0 } from '@auth0/auth0-react'
3
+ import { useNavigate } from 'react-router-dom'
4
+ import ChatInput from './components/chatinput'
5
+ import MessageBubble from './components/MessageBubble'
6
+ import './styles/App.css'
7
+
8
+ type Message = { role: 'user' | 'bot'; text: string }
9
+ type Session = { id: number; title: string; messages: Message[] }
10
+
11
+ function App() {
12
+ const { loginWithRedirect, logout, isAuthenticated } = useAuth0()
13
+ const navigate = useNavigate()
14
+
15
+ const [sidebarOpen, setSidebarOpen] = useState(true)
16
+ const [showPrompt, setShowPrompt] = useState(true)
17
+ const [sessions, setSessions] = useState<Session[]>([
18
+ { id: 1, title: 'Session 1', messages: [] }
19
+ ])
20
+ const [activeSessionId, setActiveSessionId] = useState(1)
21
+ const activeSession = sessions.find(s => s.id === activeSessionId) || sessions[0]
22
+ const videoRef = useRef<HTMLVideoElement>(null)
23
+
24
+ const handleSend = async (text: string) => {
25
+ const userMessage: Message = { role: 'user', text }
26
+ setSessions(prev =>
27
+ prev.map(session =>
28
+ session.id === activeSessionId
29
+ ? { ...session, messages: [...session.messages, userMessage] }
30
+ : session
31
+ )
32
+ )
33
+ setShowPrompt(false)
34
+
35
+ try {
36
+ const res = await fetch('https://fintrack-backend-8dji.onrender.com/generate-strategy', {
37
+ method: 'POST',
38
+ headers: { 'Content-Type': 'application/json' },
39
+ body: JSON.stringify({ user_input: text })
40
+ });
41
+
42
+ const data = await res.json()
43
+ const botText = data.strategy_code || data.error || 'Error: Empty response.'
44
+
45
+ const botMessage: Message = { role: 'bot', text: botText }
46
+ setSessions(prev =>
47
+ prev.map(session =>
48
+ session.id === activeSessionId
49
+ ? { ...session, messages: [...session.messages, botMessage] }
50
+ : session
51
+ )
52
+ )
53
+ } catch (err) {
54
+ const errorMessage: Message = { role: 'bot', text: '⚠️ Error: Failed to connect to server.' }
55
+ setSessions(prev =>
56
+ prev.map(session =>
57
+ session.id === activeSessionId
58
+ ? { ...session, messages: [...session.messages, errorMessage] }
59
+ : session
60
+ )
61
+ )
62
+ }
63
+ }
64
+
65
+ const handleNewChat = () => {
66
+ const newId = sessions.length + 1
67
+ const newSession: Session = { id: newId, title: `Session ${newId}`, messages: [] }
68
+ setSessions(prev => [newSession, ...prev])
69
+ setActiveSessionId(newId)
70
+ setShowPrompt(true)
71
+ }
72
+
73
+ const suggestions = [
74
+ 'Enter a stock trading strategy...',
75
+ 'Backtest a moving average crossover...',
76
+ 'Analyze S&P 500 signals...',
77
+ 'Try a momentum-based portfolio...',
78
+ 'Run a mean reversion strategy...',
79
+ 'Backtest Bollinger Band breakouts...'
80
+ ]
81
+ const [currentSuggestion, setCurrentSuggestion] = useState(suggestions[0])
82
+
83
+ useEffect(() => {
84
+ const interval = setInterval(() => {
85
+ setCurrentSuggestion(prev => {
86
+ let next = prev
87
+ while (next === prev) {
88
+ next = suggestions[Math.floor(Math.random() * suggestions.length)]
89
+ }
90
+ return next
91
+ })
92
+ }, 3000)
93
+ return () => clearInterval(interval)
94
+ }, [])
95
+
96
+ useEffect(() => {
97
+ const handleVisibility = () => {
98
+ if (videoRef.current) {
99
+ if (document.visibilityState === 'visible') {
100
+ videoRef.current.play().catch(() => {})
101
+ } else {
102
+ videoRef.current.pause()
103
+ }
104
+ }
105
+ }
106
+
107
+ document.addEventListener('visibilitychange', handleVisibility)
108
+ return () => {
109
+ document.removeEventListener('visibilitychange', handleVisibility)
110
+ }
111
+ }, [])
112
+
113
+ return (
114
+ <>
115
+ <video
116
+ ref={videoRef}
117
+ className="background-video"
118
+ autoPlay
119
+ loop
120
+ muted
121
+ playsInline
122
+ src="https://i.imgur.com/RCoLmZ9.mp4"
123
+ />
124
+
125
+ <div className="app-container">
126
+ {sidebarOpen && (
127
+ <aside className="sidebar">
128
+ <div className="sidebar-header">
129
+ <img
130
+ src="/image.png"
131
+ alt="Collapse Sidebar"
132
+ className="sidebar-icon"
133
+ onClick={() => setSidebarOpen(false)}
134
+ />
135
+ <h1 className="site-title">FinTrack</h1>
136
+ <img
137
+ src="/newchat.png"
138
+ alt="New Chat"
139
+ className="sidebar-icon"
140
+ onClick={handleNewChat}
141
+ />
142
+ </div>
143
+
144
+ <div className="history-list">
145
+ {sessions.map(session => (
146
+ <div
147
+ key={session.id}
148
+ className={`history-item ${session.id === activeSessionId ? 'active' : ''}`}
149
+ onClick={() => setActiveSessionId(session.id)}
150
+ >
151
+ {session.title}
152
+ </div>
153
+ ))}
154
+ </div>
155
+ </aside>
156
+ )}
157
+
158
+ {!sidebarOpen && (
159
+ <img
160
+ src="/opensidebar.png"
161
+ alt="Open Sidebar"
162
+ className="open-sidebar-button"
163
+ onClick={() => setSidebarOpen(true)}
164
+ />
165
+ )}
166
+
167
+ <main className="main-panel">
168
+ <div className="top-ui-wrapper">
169
+ <div className="top-navbar">
170
+ <img
171
+ src="/Cropped_Image.png"
172
+ className="nav-logo"
173
+ alt="Logo"
174
+ onClick={() => navigate('/')}
175
+ />
176
+ <span className="nav-link" onClick={() => navigate('/about')}>About</span>
177
+ <span className="nav-link" onClick={() => navigate('/about#founders')}>Founders</span>
178
+ </div>
179
+
180
+ <div className="auth-buttons">
181
+ {!isAuthenticated ? (
182
+ <>
183
+ <span className="auth-link" onClick={() => loginWithRedirect()}>Login</span>
184
+ <span
185
+ className="auth-link"
186
+ onClick={() =>
187
+ loginWithRedirect({ authorizationParams: { screen_hint: 'signup' } })
188
+ }
189
+ >
190
+ Sign Up
191
+ </span>
192
+ </>
193
+ ) : (
194
+ <span className="auth-link" onClick={() => logout({ logoutParams: { returnTo: window.location.origin } })}>
195
+ Log Out
196
+ </span>
197
+ )}
198
+ </div>
199
+ </div>
200
+
201
+ {showPrompt && (
202
+ <div className="prompt-banner">
203
+ <h2 className="prompt-text fade-in">How can I help you?</h2>
204
+ <p className="suggestion-text">{currentSuggestion}</p>
205
+ </div>
206
+ )}
207
+
208
+ <div className="chat-area">
209
+ {activeSession.messages.map((msg, i) => (
210
+ <MessageBubble key={i} role={msg.role} text={msg.text} />
211
+ ))}
212
+ </div>
213
+
214
+ <div className="chat-box-wrapper">
215
+ <ChatInput onSend={handleSend} />
216
+ </div>
217
+ </main>
218
+ </div>
219
+ </>
220
+ )
221
+ }
222
+
223
+ export default App
src/TextToCodeModel(1).py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datasets import load_dataset
2
+ from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments, TextStreamer, DataCollatorForLanguageModeling
3
+ from peft import LoraConfig, get_peft_model, TaskType
4
+ from trl import SFTTrainer
5
+ import torch
6
+
7
+
8
+ # Clear CUDA cache before loading models
9
+ torch.cuda.empty_cache()
10
+
11
+ device = torch.device("cuda")
12
+
13
+ dataset = load_dataset("koutch/stackoverflow_python")
14
+
15
+ dataset = dataset["train"]
16
+
17
+ def format_example(example):
18
+ return {
19
+ "text": f"### Question:\n{example['question_body'].strip()}\n\n### Answer:\n{example['answer_body'].strip()}"
20
+ }
21
+
22
+ dataset = dataset.map(format_example)
23
+ dataset = dataset.filter(lambda x: x["text"] is not None and len(x["text"]) > 0)
24
+ dataset = dataset.shuffle(seed=42).select(range(30_000))
25
+
26
+ tokenizer = AutoTokenizer.from_pretrained("deepseek-ai/DeepSeek-R1-Distill-Qwen-7B")
27
+
28
+ tokenizer.add_special_tokens({'additional_special_tokens': ["<|user|>", "<|assistant|>"]})
29
+
30
+ data_collator = DataCollatorForLanguageModeling(
31
+ tokenizer=tokenizer,
32
+ mlm=False # Causal LM, so no masked LM
33
+ )
34
+
35
+ model = AutoModelForCausalLM.from_pretrained("deepseek-ai/DeepSeek-R1-Distill-Qwen-7B",device_map="cuda",torch_dtype=torch.float16)
36
+
37
+ model.resize_token_embeddings(len(tokenizer))
38
+
39
+ lora_config = LoraConfig(
40
+ r=8,
41
+ lora_alpha=32,
42
+ target_modules=["q_proj", "v_proj"],
43
+ lora_dropout=0.1,
44
+ bias="none",
45
+ task_type=TaskType.CAUSAL_LM
46
+ )
47
+
48
+ model = get_peft_model(model, lora_config)
49
+
50
+ def tokenize_function(example):
51
+ result = tokenizer(
52
+ example["text"],
53
+ truncation=True,
54
+ padding="max_length",
55
+ max_length=1024,
56
+ )
57
+ result["text"] = example["text"] # Keep original text
58
+ return result
59
+
60
+ tokenized_dataset = dataset.map(tokenize_function, batched=True)
61
+
62
+ # Step 3: Training arguments
63
+ training_args = TrainingArguments(
64
+ output_dir="/home/deshpa70",
65
+ per_device_train_batch_size=2,
66
+ gradient_accumulation_steps=2,
67
+ num_train_epochs=1,
68
+ logging_dir="./logs",
69
+ save_total_limit=2,
70
+ logging_steps=250,
71
+ save_steps=500,
72
+ learning_rate=3e-4,
73
+ bf16=True,
74
+ optim="paged_adamw_8bit",
75
+ report_to="none"
76
+ )
77
+
78
+ trainer = SFTTrainer(
79
+ peft_config=lora_config,
80
+ model=model,
81
+ train_dataset=tokenized_dataset,
82
+ args=training_args,
83
+ data_collator=data_collator
84
+ )
85
+
86
+ trainer.train()
87
+ model.eval()
88
+
89
+ while True:
90
+ prompt = input("\n Enter your programming question (or type 'exit' to quit):\n> ")
91
+ if prompt.lower() == 'exit':
92
+ break
93
+
94
+ formatted_prompt = f"<|user|>\n{prompt}\n<|assistant|>\n"
95
+ inputs = tokenizer(formatted_prompt, return_tensors="pt").to(device)
96
+ streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
97
+
98
+
99
+ output = model.generate(
100
+ **inputs,
101
+ max_new_tokens=50000,
102
+ do_sample=True,
103
+ top_p=0.9,
104
+ temperature=0.8,
105
+ repetition_penalty=1.1,
106
+ streamer=streamer
107
+ )
108
+
109
+
110
+ # Clear CUDA cache before loading models
111
+ torch.cuda.empty_cache()
src/assets/react.svg ADDED
src/components/AboutPage.tsx ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useRef } from 'react'
2
+ import { useNavigate } from 'react-router-dom'
3
+ import '../styles/AboutPage.css'
4
+
5
+ function AboutPage() {
6
+ const videoRef = useRef<HTMLVideoElement>(null)
7
+ const navigate = useNavigate()
8
+
9
+ useEffect(() => {
10
+ const hash = window.location.hash
11
+ if (hash === '#founders') {
12
+ const el = document.getElementById('founders-section')
13
+ el?.scrollIntoView({ behavior: 'smooth' })
14
+ }
15
+
16
+ const handleVisibility = () => {
17
+ if (videoRef.current) {
18
+ if (document.visibilityState === 'visible') {
19
+ videoRef.current.play().catch(() => {})
20
+ } else {
21
+ videoRef.current.pause()
22
+ }
23
+ }
24
+ }
25
+
26
+ document.addEventListener('visibilitychange', handleVisibility)
27
+ return () => {
28
+ document.removeEventListener('visibilitychange', handleVisibility)
29
+ }
30
+ }, [])
31
+
32
+ return (
33
+ <>
34
+ <video
35
+ ref={videoRef}
36
+ className="about-video"
37
+ autoPlay
38
+ loop
39
+ muted
40
+ playsInline
41
+ src="https://i.imgur.com/RCoLmZ9.mp4"
42
+ />
43
+
44
+ {/* 🔼 Top navbar centered like on home screen */}
45
+ <div className="about-navbar-wrapper">
46
+ <div className="top-navbar">
47
+ <img
48
+ src="/Cropped_Image.png"
49
+ className="nav-logo"
50
+ alt="Logo"
51
+ onClick={() => navigate('/')}
52
+ />
53
+ <span className="nav-link" onClick={() => navigate('/about')}>About</span>
54
+ <span className="nav-link" onClick={() => navigate('/about#founders')}>Founders</span>
55
+ </div>
56
+ </div>
57
+
58
+ <div className="about-page-scroll">
59
+ <section className="about-section" id="about-section">
60
+ <h1>About <span className="highlight">FinTrack</span></h1>
61
+ <p>
62
+ FinTrack is an intuitive, natural-language–powered stock strategy builder.
63
+ Just describe your idea — whether it’s backtesting a moving average crossover,
64
+ analyzing tech stocks, or deploying a momentum strategy — and our AI handles the rest.
65
+ </p>
66
+ </section>
67
+
68
+ <section className="founders-section" id="founders-section">
69
+ <h2>Founders</h2>
70
+ <p>Meet the team behind FinTrack:</p>
71
+ <ul>
72
+ <li><a href="https://www.linkedin.com/in/vishruthg/" target="_blank" rel="noopener noreferrer"><strong>Vishruth</strong></a> – Frontend</li>
73
+ <li><a href="https://www.linkedin.com/in/ronilmitra/" target="_blank" rel="noopener noreferrer"><strong>Ronil</strong></a> – Frontend</li>
74
+ <li><a href="https://www.linkedin.com/in/kalyan-archakam/" target="_blank" rel="noopener noreferrer"><strong>Kalyan</strong></a> – Backend</li>
75
+ <li><a href="https://www.linkedin.com/in/sudhanva-deshpande-81a94a280/" target="_blank" rel="noopener noreferrer"><strong>Sudhanva</strong></a> – Backend</li>
76
+ <li><a href="https://www.linkedin.com/in/navan-dendukuri-64a03b360/" target="_blank" rel="noopener noreferrer"><strong>Navan</strong></a> – Backend</li>
77
+ </ul>
78
+ </section>
79
+ </div>
80
+ </>
81
+ )
82
+ }
83
+
84
+ export default AboutPage
src/components/History.tsx ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function History() {
2
+ return (
3
+ <aside className="history-panel">
4
+ <h2>History</h2>
5
+ <ul>
6
+ <li className="history-item">Session 1</li>
7
+ <li className="history-item">Session 2</li>
8
+ <li className="history-item">Session 3</li>
9
+ </ul>
10
+ </aside>
11
+ );
12
+ }
13
+
14
+ export default History;
src/components/MessageBubble.tsx ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import '../styles/MessageBubble.css'
2
+
3
+ function MessageBubble({ role, text }: { role: 'user' | 'bot'; text: string }) {
4
+ const isCode = text.includes('\n') && role === 'bot'
5
+
6
+ return (
7
+ <div className={`message-bubble ${role}`}>
8
+ {isCode ? (
9
+ <pre className="code-block">
10
+ <code>{text}</code>
11
+ </pre>
12
+ ) : (
13
+ <p>{text}</p>
14
+ )}
15
+ </div>
16
+ )
17
+ }
18
+
19
+ export default MessageBubble
src/components/Metrics.tsx ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react'
2
+ import '../styles/Metrics.css'
3
+
4
+ function Metrics() {
5
+ const [tab, setTab] = useState('code')
6
+ return (
7
+ <div className="metrics-panel">
8
+ <div className="tab-buttons">
9
+ <button className={tab === 'code' ? 'active' : ''} onClick={() => setTab('code')}>Code</button>
10
+ <button className={tab === 'chart' ? 'active' : ''} onClick={() => setTab('chart')}>Chart</button>
11
+ <button className={tab === 'metrics' ? 'active' : ''} onClick={() => setTab('metrics')}>Metrics</button>
12
+ </div>
13
+ <div className="tab-content">
14
+ {tab === 'code' && <pre>{`function backtest() {
15
+ // strategy here
16
+ }`}</pre>}
17
+ {tab === 'chart' && <p>[Chart output goes here]</p>}
18
+ {tab === 'metrics' && (
19
+ <ul>
20
+ <li>Return: 10%</li>
21
+ <li>Sharpe: 1.4</li>
22
+ <li>Max Drawdown: 5%</li>
23
+ </ul>
24
+ )}
25
+ </div>
26
+ </div>
27
+ )
28
+ }
29
+
30
+ export default Metrics
src/components/Spinner.tsx ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import '../styles/Spinner.css'
2
+
3
+ function Spinner() {
4
+ return <div className="spinner"></div>
5
+ }
6
+
7
+ export default Spinner
src/components/chatinput.tsx ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react'
2
+ import '../styles/chatinput.css' // Make sure file is named exactly like this
3
+
4
+ function ChatInput({ onSend }: { onSend: (msg: string) => void }) {
5
+ const [input, setInput] = useState('')
6
+
7
+ const handleKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
8
+ if (e.key === 'Enter' && input.trim()) {
9
+ onSend(input)
10
+ setInput('')
11
+ }
12
+ }
13
+
14
+ return (
15
+ <div className="chat-input-wrapper">
16
+ <input
17
+ className="chat-input"
18
+ type="text"
19
+ placeholder="Ask something..."
20
+ value={input}
21
+ onChange={(e) => setInput(e.target.value)}
22
+ onKeyDown={handleKey}
23
+ />
24
+ <button className="send-button" onClick={handleSend}>
25
+ Send
26
+ </button>
27
+ </div>
28
+ )
29
+
30
+ function handleSend() {
31
+ if (input.trim()) {
32
+ onSend(input)
33
+ setInput('')
34
+ }
35
+ }
36
+ }
37
+
38
+ export default ChatInput
src/main.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { BrowserRouter, Routes, Route } from 'react-router-dom'
4
+ import { Auth0Provider } from '@auth0/auth0-react'
5
+ import './styles/index.css'
6
+ import App from './App'
7
+ import AboutPage from './components/AboutPage'
8
+
9
+ createRoot(document.getElementById('root')!).render(
10
+ <StrictMode>
11
+ <Auth0Provider
12
+ domain="fintrack-dev.us.auth0.com"
13
+ clientId="tM4JJsEXpaSaYU84C7aXxlp0bWhXcwyM"
14
+ authorizationParams={{
15
+ redirect_uri: window.location.origin,
16
+ }}
17
+ >
18
+ <BrowserRouter>
19
+ <Routes>
20
+ <Route path="/" element={<App />} />
21
+ <Route path="/about" element={<AboutPage />} />
22
+ </Routes>
23
+ </BrowserRouter>
24
+ </Auth0Provider>
25
+ </StrictMode>
26
+ )
src/styles/AboutPage.css ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .about-video {
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100vw;
6
+ height: 100vh;
7
+ object-fit: cover;
8
+ z-index: 0;
9
+ opacity: 0.15;
10
+ filter: blur(5px) brightness(0.9);
11
+ pointer-events: none;
12
+ }
13
+
14
+ /* 🔼 Centered NAVBAR on AboutPage */
15
+ .about-navbar-wrapper {
16
+ position: fixed;
17
+ top: 1.5rem;
18
+ left: 50%;
19
+ transform: translateX(-50%);
20
+ z-index: 5;
21
+ }
22
+
23
+ .top-navbar {
24
+ display: flex;
25
+ align-items: center;
26
+ justify-content: center;
27
+ gap: 2rem;
28
+ background-color: rgba(0, 0, 0, 0.4);
29
+ padding: 0.75rem 1.5rem;
30
+ border-radius: 50px;
31
+ }
32
+
33
+ .nav-logo {
34
+ width: 32px;
35
+ height: 32px;
36
+ cursor: pointer;
37
+ }
38
+
39
+ .nav-link {
40
+ font-size: 1rem;
41
+ font-weight: 500;
42
+ cursor: pointer;
43
+ color: var(--color-text);
44
+ }
45
+
46
+ /* 🔁 Scroll layout */
47
+ .about-page-scroll {
48
+ position: relative;
49
+ z-index: 2;
50
+ height: 100vh;
51
+ overflow-y: scroll;
52
+ scroll-snap-type: y mandatory;
53
+ }
54
+
55
+ .about-section,
56
+ .founders-section {
57
+ scroll-snap-align: start;
58
+ min-height: 100vh;
59
+ padding: 5rem 2rem;
60
+ max-width: 900px;
61
+ margin: 0 auto;
62
+ color: var(--color-text);
63
+ font-family: 'Poppins', sans-serif;
64
+ display: flex;
65
+ flex-direction: column;
66
+ justify-content: center;
67
+ text-align: center;
68
+ }
69
+
70
+ .about-section h1,
71
+ .founders-section h2 {
72
+ font-size: 2.5rem;
73
+ margin-bottom: 1rem;
74
+ }
75
+
76
+ .highlight {
77
+ background: linear-gradient(to right, #38d9a9, #66ffe3);
78
+ -webkit-background-clip: text;
79
+ -webkit-text-fill-color: transparent;
80
+ font-weight: 800;
81
+ }
82
+
83
+ .about-section p,
84
+ .founders-section p,
85
+ .founders-section ul {
86
+ font-size: 1.1rem;
87
+ line-height: 1.6;
88
+ color: var(--color-muted);
89
+ max-width: 700px;
90
+ margin: 0 auto;
91
+ }
92
+
93
+ .founders-section ul {
94
+ list-style: none;
95
+ padding-left: 0;
96
+ margin-top: 1.5rem;
97
+ }
98
+
99
+ .founders-section li {
100
+ padding: 0.5rem 0;
101
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
102
+ }
src/styles/App.css ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;800&display=swap');
2
+
3
+ :root {
4
+ --color-bg: #0a192f;
5
+ --color-panel: rgba(0, 0, 0, 0.6);
6
+ --color-accent: #38d9a9;
7
+ --color-text: #ffffff;
8
+ --color-muted: #9ca3af;
9
+ --color-error: #ef4444;
10
+ font-family: 'Poppins', 'Segoe UI', sans-serif;
11
+ }
12
+
13
+ /* Reset */
14
+ body {
15
+ margin: 0;
16
+ padding: 0;
17
+ background-color: var(--color-bg);
18
+ color: var(--color-text);
19
+ font-size: 16px;
20
+ line-height: 1.5;
21
+ font-family: 'Poppins', 'Segoe UI', sans-serif;
22
+ overflow: hidden;
23
+ }
24
+
25
+ /* 🎥 Background video */
26
+ .background-video {
27
+ position: fixed;
28
+ top: 0;
29
+ left: 0;
30
+ width: 100vw;
31
+ height: 100vh;
32
+ object-fit: cover;
33
+ z-index: 0;
34
+ opacity: 0.15;
35
+ filter: blur(5px) brightness(0.9);
36
+ pointer-events: none;
37
+ }
38
+
39
+ /* Layout */
40
+ .app-container {
41
+ display: flex;
42
+ height: 100vh;
43
+ position: relative;
44
+ z-index: 1;
45
+ background-color: transparent;
46
+ color: var(--color-text);
47
+ }
48
+
49
+ /* Sidebar */
50
+ .sidebar {
51
+ width: 280px;
52
+ height: 100vh;
53
+ padding: 2rem 1.5rem;
54
+ background-color: rgba(0, 0, 0, 0.45);
55
+ backdrop-filter: blur(16px);
56
+ box-shadow: 2px 0 12px rgba(0, 0, 0, 0.3);
57
+ z-index: 2;
58
+ display: flex;
59
+ flex-direction: column;
60
+ justify-content: flex-start;
61
+ position: relative;
62
+ }
63
+
64
+ .sidebar-header {
65
+ display: flex;
66
+ justify-content: space-between;
67
+ align-items: center;
68
+ height: 40px;
69
+ margin-bottom: 2.5rem;
70
+ position: relative;
71
+ }
72
+
73
+ .sidebar-icon {
74
+ width: 28px;
75
+ height: 28px;
76
+ cursor: pointer;
77
+ opacity: 0.9;
78
+ transition: transform 0.2s ease;
79
+ }
80
+ .sidebar-icon:hover {
81
+ transform: scale(1.1);
82
+ opacity: 1;
83
+ }
84
+
85
+ .site-title {
86
+ position: absolute;
87
+ left: 50%;
88
+ transform: translateX(-50%);
89
+ font-size: 1.75rem;
90
+ font-weight: 800;
91
+ background: linear-gradient(to right, #38d9a9, #66ffe3);
92
+ -webkit-background-clip: text;
93
+ -webkit-text-fill-color: transparent;
94
+ margin: 0;
95
+ }
96
+
97
+ .open-sidebar-button {
98
+ position: absolute;
99
+ top: 1.5rem;
100
+ left: 1.5rem;
101
+ width: 28px;
102
+ height: 28px;
103
+ cursor: pointer;
104
+ z-index: 3;
105
+ transition: transform 0.2s ease;
106
+ }
107
+ .open-sidebar-button:hover {
108
+ transform: scale(1.1);
109
+ }
110
+
111
+ /* 🧠 Session History */
112
+ .history-list {
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: 0.5rem;
116
+ }
117
+ .history-item {
118
+ padding: 0.6rem 1rem;
119
+ border-radius: 999px; /* bubble style */
120
+ background-color: rgba(51, 65, 85, 0.8);
121
+ cursor: pointer;
122
+ font-size: 0.95rem;
123
+ transition: background-color 0.2s ease, color 0.2s ease;
124
+ text-align: center;
125
+ }
126
+ .history-item:hover {
127
+ background-color: #38d9a9;
128
+ color: #0f172a;
129
+ }
130
+ .history-item.active {
131
+ background-color: #38d9a9;
132
+ color: #0f172a;
133
+ font-weight: 600;
134
+ }
135
+
136
+ /* 🔼 Top navbar */
137
+ .top-ui-wrapper {
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: space-between;
141
+ margin-bottom: 2rem;
142
+ padding: 0 2rem;
143
+ }
144
+ .top-navbar {
145
+ display: flex;
146
+ align-items: center;
147
+ gap: 2rem;
148
+ background-color: rgba(0, 0, 0, 0.4);
149
+ padding: 0.75rem 1.5rem;
150
+ border-radius: 50px;
151
+ margin: 0 auto;
152
+ }
153
+ .nav-logo {
154
+ width: 32px;
155
+ height: 32px;
156
+ cursor: pointer;
157
+ }
158
+ .nav-link {
159
+ font-size: 1rem;
160
+ font-weight: 500;
161
+ cursor: pointer;
162
+ }
163
+
164
+ /* 🔐 Auth buttons */
165
+ .auth-buttons {
166
+ display: flex;
167
+ gap: 2rem;
168
+ background-color: rgba(0, 0, 0, 0.4);
169
+ padding: 0.75rem 1.5rem;
170
+ border-radius: 50px;
171
+ }
172
+ .auth-link {
173
+ font-size: 1rem;
174
+ font-weight: 500;
175
+ cursor: pointer;
176
+ }
177
+
178
+ /* 👨‍💻 Main */
179
+ .main-panel {
180
+ flex: 1;
181
+ display: flex;
182
+ flex-direction: column;
183
+ padding: 1rem 2rem 8rem;
184
+ overflow-y: auto;
185
+ background-color: transparent;
186
+ position: relative;
187
+ z-index: 2;
188
+ }
189
+
190
+
191
+ /* 💬 Prompt */
192
+ .prompt-banner {
193
+ display: flex;
194
+ flex-direction: column;
195
+ align-items: center;
196
+ justify-content: center;
197
+ height: 100%;
198
+ min-height: 300px;
199
+ }
200
+ .prompt-text {
201
+ font-size: 3rem;
202
+ font-weight: 900;
203
+ background: linear-gradient(to right, #38d9a9, #66ffe3);
204
+ -webkit-background-clip: text;
205
+ -webkit-text-fill-color: transparent;
206
+ text-align: center;
207
+ }
208
+ .suggestion-text {
209
+ font-size: 0.9rem;
210
+ color: var(--color-muted);
211
+ margin-top: 1rem;
212
+ }
213
+
214
+ /* 💬 Chat area */
215
+ .chat-area {
216
+ flex: 1;
217
+ display: flex;
218
+ flex-direction: column;
219
+ justify-content: flex-end;
220
+ padding-bottom: 2rem;
221
+ }
222
+
223
+ /* 📝 Chat box */
224
+ .chat-box-wrapper {
225
+ display: flex;
226
+ justify-content: center;
227
+ padding: 1.5rem;
228
+ position: fixed;
229
+ bottom: 0;
230
+ left: 280px; /* width of your sidebar */
231
+ right: 0;
232
+ z-index: 3;
233
+ background: linear-gradient(to top, rgba(10, 25, 47, 0.95), transparent); /* optional: smooth fade */
234
+ }
235
+
236
+ .chat-box-wrapper > .chat-input-wrapper {
237
+ width: 100%;
238
+ max-width: 900px;
239
+ padding: 1rem 1.25rem;
240
+ border-radius: 16px;
241
+ background-color: rgba(0, 0, 0, 0.5);
242
+ backdrop-filter: blur(10px);
243
+ display: flex;
244
+ gap: 1rem;
245
+ align-items: center;
246
+ border: 1px solid rgba(255, 255, 255, 0.05);
247
+ }
248
+
249
+ /* 🌐 Utility */
250
+ button {
251
+ cursor: pointer;
252
+ transition: all 0.2s ease-in-out;
253
+ }
254
+ a {
255
+ color: var(--color-accent);
256
+ text-decoration: none;
257
+ }
258
+ a:hover {
259
+ text-decoration: underline;
260
+ }
261
+
262
+ .about-page {
263
+ padding: 2rem;
264
+ background-color: var(--color-bg);
265
+ min-height: 100vh;
266
+ color: var(--color-text);
267
+ position: relative;
268
+ z-index: 1;
269
+ overflow-y: auto;
270
+ }
271
+
272
+ .about-content {
273
+ max-width: 900px;
274
+ margin: 0 auto;
275
+ padding-top: 3rem;
276
+ }
src/styles/History.css ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .chat-input-wrapper {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ gap: 1rem;
6
+ padding: 1rem 2rem;
7
+ margin-top: auto;
8
+ background-color: rgba(0, 0, 0, 0.6);
9
+ border-top: 1px solid #334155;
10
+ position: sticky;
11
+ bottom: 0;
12
+ z-index: 10;
13
+ }
14
+
15
+ .chat-input {
16
+ flex: 1;
17
+ max-width: 800px;
18
+ padding: 1rem 1.25rem;
19
+ background-color: #0f172a;
20
+ color: #ffffff;
21
+ border: 1px solid #334155;
22
+ border-radius: 12px;
23
+ font-size: 1rem;
24
+ }
25
+
26
+ .send-button {
27
+ background-color: var(--color-accent);
28
+ color: #0f172a;
29
+ font-weight: 600;
30
+ border: none;
31
+ padding: 1rem 1.5rem;
32
+ border-radius: 12px;
33
+ transition: background-color 0.2s;
34
+ }
35
+
36
+ .send-button:hover {
37
+ background-color: #2ec4a1;
38
+ }
src/styles/MessageBubble.css ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .message-bubble {
2
+ max-width: 70%;
3
+ padding: 0.75rem 1rem;
4
+ border-radius: 12px;
5
+ margin-bottom: 0.75rem;
6
+ font-size: 0.95rem;
7
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
8
+ line-height: 1.4;
9
+ font-family: 'Poppins', sans-serif;
10
+ }
11
+
12
+ .message-bubble.user {
13
+ background-color: #38d9a9;
14
+ color: #0f172a;
15
+ align-self: flex-end;
16
+ }
17
+
18
+ .message-bubble.bot {
19
+ background-color: #334155;
20
+ color: #ffffff;
21
+ align-self: flex-start;
22
+ }
23
+
24
+ /* 🧠 Code formatting for bot responses */
25
+ .code-block {
26
+ background-color: #1e293b;
27
+ font-family: 'Fira Code', 'Courier New', monospace;
28
+ font-size: 0.85rem;
29
+ padding: 1rem;
30
+ border-radius: 10px;
31
+ overflow-x: auto;
32
+ white-space: pre-wrap;
33
+ word-break: break-word;
34
+ line-height: 1.5;
35
+ margin: 0;
36
+ }
src/styles/Metrics.css ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .metrics-panel {
2
+ background-color: #1e293b;
3
+ color: #e2e8f0;
4
+ border-radius: 12px;
5
+ padding: 1.5rem;
6
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
7
+ margin-top: 1rem;
8
+ }
9
+
10
+ .tab-buttons button {
11
+ background-color: #334155;
12
+ color: #ffffff;
13
+ padding: 0.5rem 1rem;
14
+ margin-right: 0.5rem;
15
+ border: none;
16
+ border-radius: 6px;
17
+ font-weight: 500;
18
+ transition: background-color 0.2s ease;
19
+ }
20
+
21
+ .tab-buttons .active {
22
+ background-color: #38d9a9;
23
+ color: #0f172a;
24
+ }
25
+
26
+ .tab-content {
27
+ margin-top: 1rem;
28
+ background-color: #0f172a;
29
+ padding: 1rem;
30
+ border-radius: 8px;
31
+ font-family: monospace;
32
+ color: #e2e8f0;
33
+ }
src/styles/Spinner.css ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .spinner {
2
+ width: 32px;
3
+ height: 32px;
4
+ border: 4px solid #f3f3f3;
5
+ border-top: 4px solid #38d9a9;
6
+ border-radius: 50%;
7
+ animation: spin 1s linear infinite;
8
+ margin: 2rem auto;
9
+ }
10
+
11
+ @keyframes spin {
12
+ 0% { transform: rotate(0deg); }
13
+ 100% { transform: rotate(360deg); }
14
+ }
src/styles/chatinput.css ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .chat-input-wrapper {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ gap: 1rem;
6
+ padding: 1rem 2rem;
7
+ margin-top: auto;
8
+
9
+ background-color: rgba(0, 0, 0, 0.4); /* translucent black */
10
+ backdrop-filter: blur(12px); /* soft blur to match sidebar */
11
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
12
+
13
+ position: sticky;
14
+ bottom: 0;
15
+ z-index: 10;
16
+ }
17
+
18
+ .chat-input {
19
+ flex: 1;
20
+ max-width: 800px;
21
+ padding: 1rem 1.25rem;
22
+ background-color: rgba(15, 23, 42, 0.6);
23
+ color: #ffffff;
24
+ border: 1px solid #334155;
25
+ border-radius: 12px;
26
+ font-size: 1rem;
27
+ font-family: 'Poppins', sans-serif;
28
+ outline: none;
29
+ }
30
+
31
+ .send-button {
32
+ background-color: #38d9a9;
33
+ color: #0f172a;
34
+ font-weight: 600;
35
+ border: none;
36
+ padding: 1rem 1.5rem;
37
+ border-radius: 12px;
38
+ transition: background-color 0.2s ease;
39
+ font-family: 'Poppins', sans-serif;
40
+ }
41
+
42
+ .send-button:hover {
43
+ background-color: #2ec4a1;
44
+ }
src/styles/index.css ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;800&display=swap');
2
+
3
+ /* Root theme variables */
4
+ :root {
5
+ --color-bg: #0a192f;
6
+ --color-panel: rgba(0, 0, 0, 0.6);
7
+ --color-accent: #38d9a9;
8
+ --color-text: #ffffff;
9
+ --color-muted: #9ca3af;
10
+ --color-error: #ef4444;
11
+
12
+ font-family: 'Poppins', 'Segoe UI', sans-serif;
13
+ }
14
+
15
+ /* Global Reset */
16
+ * {
17
+ margin: 0;
18
+ padding: 0;
19
+ box-sizing: border-box;
20
+ }
21
+
22
+ html, body, #root {
23
+ height: 100%;
24
+ width: 100%;
25
+ font-family: 'Poppins', 'Segoe UI', sans-serif;
26
+ background-color: var(--color-bg);
27
+ color: var(--color-text);
28
+ overflow: hidden;
29
+ }
30
+
31
+ a {
32
+ color: var(--color-accent);
33
+ text-decoration: none;
34
+ }
35
+
36
+ a:hover {
37
+ text-decoration: underline;
38
+ }
39
+
40
+ button {
41
+ font-family: 'Poppins', sans-serif;
42
+ cursor: pointer;
43
+ transition: all 0.2s ease-in-out;
44
+ }
45
+
46
+ /* Make scrollbars prettier on webkit */
47
+ ::-webkit-scrollbar {
48
+ width: 6px;
49
+ }
50
+
51
+ ::-webkit-scrollbar-track {
52
+ background: transparent;
53
+ }
54
+
55
+ ::-webkit-scrollbar-thumb {
56
+ background-color: rgba(255, 255, 255, 0.2);
57
+ border-radius: 3px;
58
+ }
src/styles/styles.css ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .card {
2
+ background-color: var(--color-panel);
3
+ padding: 1.5rem;
4
+ border-radius: 12px;
5
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
6
+ margin-bottom: 1rem;
7
+ }
8
+
9
+ .section-title {
10
+ font-size: 1.25rem;
11
+ font-weight: 600;
12
+ margin-bottom: 1rem;
13
+ color: var(--color-text);
14
+ }
15
+
16
+ .accent-text {
17
+ color: var(--color-accent);
18
+ }
19
+
20
+ .muted-text {
21
+ color: var(--color-muted);
22
+ }
23
+
src/vite-env.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ /// <reference types="vite/client" />