diff --git a/Domains/Frontend/MiniProjects/Stock-Exchange/app.js b/Domains/Frontend/MiniProjects/Stock-Exchange/app.js new file mode 100644 index 00000000..b14eec65 --- /dev/null +++ b/Domains/Frontend/MiniProjects/Stock-Exchange/app.js @@ -0,0 +1,520 @@ +/** + * FinFlow - Advanced Stock Analysis App + * JavaScript for Interactivity and Data Visualization + */ + +// --- 1. MOCK DATA SETUP --- +const MOCK_DATA = { + marketStatus: [ + { name: "Nifty 50", value: "22,501.80", change: "+125.40", percent: "0.56%", isPositive: true }, + { name: "Sensex", value: "74,085.99", change: "-88.20", percent: "0.12%", isPositive: false }, + { name: "Bank Nifty", value: "48,510.15", change: "+410.30", percent: "0.85%", isPositive: true }, + { name: "USD/INR", value: "83.45", change: "+0.02", percent: "0.02%", isPositive: false }, + ], + watchlist: [ + { symbol: "TCS", price: 3412.00, change: 0.70, chartData: [3400, 3405, 3412, 3410, 3415, 3420, 3412] }, + { symbol: "RELIANCE", price: 2950.50, change: -1.25, chartData: [2980, 2975, 2960, 2955, 2950, 2948, 2950], isNegative: true }, + { symbol: "HDFCBANK", price: 1530.20, change: 2.10, chartData: [1500, 1515, 1520, 1530, 1525, 1535, 1530] }, + { symbol: "INFY", price: 1475.10, change: -0.55, chartData: [1480, 1478, 1475, 1470, 1472, 1476, 1475], isNegative: true }, + ], + holdings: [ + { symbol: "TCS", name: "Tata Consultancy Services", qty: 10, avgPrice: 3200.00, currentPrice: 3412.00, sector: "IT" }, + { symbol: "SBIN", name: "State Bank of India", qty: 25, avgPrice: 700.00, currentPrice: 755.50, sector: "Finance" }, + { symbol: "ASIANPAINT", name: "Asian Paints Ltd.", qty: 5, avgPrice: 3000.00, currentPrice: 2850.10, sector: "Chemicals" }, + ], + movers: [ + { symbol: "BAJFINANCE", change: 5.10, isPositive: true }, + { symbol: "HCLTECH", change: 4.85, isPositive: true }, + { symbol: "SUNPHARMA", change: 4.12, isPositive: true }, + { symbol: "ULTRACEMCO", change: -3.55, isPositive: false }, + { symbol: "ADANIPORTS", change: -3.20, isPositive: false }, + { symbol: "TITAN", change: -2.90, isPositive: false }, + ], + news: [ + "RBI holds interest rates steady; forecasts strong economic growth.", + "Tata Motors stock surges 4% after robust Q4 earnings report.", + "Global chip shortage concerns ease, boosting IT sector outlook.", + "Crude oil prices drop, providing relief for airline and paint stocks.", + ], + tradeData: { + 'TCS': { + symbol: 'TCS', price: 3412.00, change: 0.70, open: 3400, high: 3445, low: 3380, volume: '2.4M', + candlestick: { + x: ['2025-01-01', '2025-01-02', '2025-01-03', '2025-01-06', '2025-01-07'], + open: [3400, 3420, 3410, 3435, 3450], high: [3425, 3440, 3430, 3460, 3470], low: [3380, 3400, 3400, 3430, 3440], close: [3420, 3410, 3435, 3450, 3465] + }, + indicators: { x: ['2025-01-01', '2025-01-02', '2025-01-03', '2025-01-06', '2025-01-07'], rsi: [65.2, 60.5, 68.9, 72.1, 75.0] } + }, + 'RELIANCE': { + symbol: 'RELIANCE', price: 2950.50, change: -1.25, open: 2980, high: 2990, low: 2945, volume: '1.8M', + candlestick: { + x: ['2025-01-01', '2025-01-02', '2025-01-03', '2025-01-06', '2025-01-07'], + open: [2980, 2960, 2970, 2955, 2940], high: [2985, 2970, 2980, 2965, 2950], low: [2950, 2950, 2960, 2940, 2930], close: [2960, 2970, 2955, 2940, 2935] + }, + indicators: { x: ['2025-01-01', '2025-01-02', '2025-01-03', '2025-01-06', '2025-01-07'], rsi: [45.2, 40.5, 38.9, 32.1, 28.0] } + } + } +}; + +// --- 2. GLOBAL STATE --- +let currentState = { + activeScreen: 'dashboard', + selectedStock: 'TCS' +}; + +// --- 3. CORE FUNCTIONS --- + +/** Utility to format currency */ +const formatCurrency = (value) => { + return `₹${value.toLocaleString('en-IN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; +}; + +/** Switches the active screen and updates the bottom navigation. */ +function goTo(screenId, symbol = null) { + document.querySelectorAll('main > section').forEach(section => { + section.style.display = 'none'; + }); + + const targetScreen = document.getElementById(`screen-${screenId}`); + if (targetScreen) { + targetScreen.style.display = 'block'; + currentState.activeScreen = screenId; + } + + document.querySelectorAll('.nav-item').forEach(item => { + item.classList.remove('active'); + // Use a more robust check for the correct nav item + const onclickAttr = item.getAttribute('onclick'); + if (onclickAttr && onclickAttr.includes(`goTo('${screenId}')`)) { + item.classList.add('active'); + } + }); + + if (screenId === 'trade' && symbol) { + loadTradeScreen(symbol); + } else if (screenId === 'trade' && !symbol) { + loadTradeScreen(currentState.selectedStock); + } else if (screenId === 'portfolio') { + renderPortfolioCharts(); + } else if (screenId === 'analytics') { + renderAnalyticsCharts(); + } +} + +// --- 4. RENDER FUNCTIONS (Dashboard) --- + +/** Renders the market status bar. */ +function renderMarketStatus() { + const container = document.getElementById('marketStatus'); + container.innerHTML = MOCK_DATA.marketStatus.map(item => ` +
+ ${item.name} + ${item.value} + + ${item.isPositive ? '▲' : '▼'} ${item.percent} + +
+ `).join(''); +} + +/** Renders the watchlist carousel. */ +function renderWatchlist() { + const container = document.getElementById('watchlist'); + container.innerHTML = MOCK_DATA.watchlist.map(stock => { + const changeClass = stock.change > 0 ? 'positive' : 'negative'; + const changeText = `${stock.change > 0 ? '+' : ''}${stock.change.toFixed(2)}%`; + const cardClass = stock.change > 0 ? '' : 'negative'; + + // Set a timeout to ensure the DOM elements are ready before plotting charts + setTimeout(() => plotMiniChart(stock.symbol, stock.chartData, stock.change > 0 ? 'var(--success)' : 'var(--danger)'), 0); + + return ` +
+
${stock.symbol}
+
${formatCurrency(stock.price)}
+
${changeText}
+
+
+ `; + }).join(''); +} + +/** Plots a simple line mini-chart for a stock. */ +function plotMiniChart(symbol, data, color) { + const chartDiv = document.getElementById(`mini-chart-${symbol}`); + if (!chartDiv) return; + + const trace = { + y: data, + mode: 'lines', + line: { color: color, width: 2 }, + fill: 'tozeroy', + // Convert CSS variable to RGBA for Plotly fill color + fillcolor: color.replace(')', ', 0.2)').replace('var(', 'rgba('), + }; + + const layout = { + margin: { l: 0, r: 0, t: 0, b: 0 }, height: 30, width: 140, + xaxis: { showgrid: false, zeroline: false, showticklabels: false }, + yaxis: { showgrid: false, zeroline: false, showticklabels: false }, + paper_bgcolor: 'rgba(0,0,0,0)', plot_bgcolor: 'rgba(0,0,0,0)', hovermode: false + }; + + Plotly.newPlot(chartDiv, [trace], layout, { displayModeBar: false }); +} + +/** Renders top movers. */ +function renderMovers() { + const container = document.getElementById('movers'); + container.innerHTML = MOCK_DATA.movers.slice(0, 3).map(mover => { + const changeClass = mover.isPositive ? 'positive' : 'negative'; + const changeText = `${mover.isPositive ? '+' : ''}${mover.change.toFixed(2)}%`; + return ` +
+
${mover.symbol}
+
${changeText}
+
+ `; + }).join(''); +} + +/** Renders the market news list. */ +function renderNews() { + const container = document.getElementById('news'); + container.innerHTML = MOCK_DATA.news.map(item => ` +
+ ${item} +
+ `).join(''); +} + +/** Renders the dashboard performance chart (Line Chart). */ +function renderPerformanceChart() { + const chartDiv = document.getElementById('performanceChart'); + const dates = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + const portfolioValue = [100000, 105000, 112000, 110500, 118000, 124832]; + const benchmark = [100000, 102000, 108000, 109000, 115000, 120500]; + + const trace1 = { x: dates, y: portfolioValue, mode: 'lines', name: 'My Portfolio', line: { color: 'var(--primary)', width: 3 } }; + const trace2 = { x: dates, y: benchmark, mode: 'lines', name: 'Benchmark (Nifty)', line: { color: 'var(--text-light)', dash: 'dash', width: 2 } }; + + const layout = { + margin: { l: 40, r: 10, t: 10, b: 40 }, xaxis: { showgrid: false, zeroline: false }, yaxis: { showgrid: true, zeroline: false }, + showlegend: true, legend: { x: 0, y: 1.15, orientation: 'h' }, + paper_bgcolor: 'var(--card)', plot_bgcolor: 'var(--card)', font: { family: 'Inter', color: 'var(--text)' } + }; + + Plotly.newPlot(chartDiv, [trace1, trace2], layout, { displayModeBar: false }); +} + +// --- 5. RENDER FUNCTIONS (Trade) --- + +/** Loads and renders the trade screen data for a specific stock. */ +function loadTradeScreen(symbol) { + currentState.selectedStock = symbol; + const stockData = MOCK_DATA.tradeData[symbol] || MOCK_DATA.tradeData['TCS']; + const isPositive = stockData.change > 0; + const changeClass = isPositive ? 'text-success' : 'text-danger'; + const changeText = `${isPositive ? '+' : ''}${stockData.change.toFixed(2)}%`; + + document.getElementById('tradeSymbol').textContent = stockData.symbol; + document.getElementById('tradePrice').innerHTML = `${formatCurrency(stockData.price)} ${changeText}`; + + // Update stock details + document.querySelector('#screen-trade .stats-grid .stat-card:nth-child(1) .stat-value').textContent = formatCurrency(stockData.open); + document.querySelector('#screen-trade .stats-grid .stat-card:nth-child(2) .stat-value').textContent = formatCurrency(stockData.high); + document.querySelector('#screen-trade .stats-grid .stat-card:nth-child(3) .stat-value').textContent = formatCurrency(stockData.low); + document.querySelector('#screen-trade .stats-grid .stat-card:nth-child(4) .stat-value').textContent = stockData.volume; + + changeTimeframe('1D'); // Render the default '1D' chart +} + +/** Renders the Candlestick and Indicator charts. */ +function plotTradeCharts(data) { + const chartDiv = document.getElementById('chart1'); + const indicatorsDiv = document.getElementById('indicatorsChart'); + + // 1. Candlestick Chart + const traceCandle = { + x: data.candlestick.x, open: data.candlestick.open, high: data.candlestick.high, + low: data.candlestick.low, close: data.candlestick.close, type: 'candlestick', + name: 'Price', + increasing: { line: { color: 'var(--success)' }, fillcolor: 'var(--success)' }, + decreasing: { line: { color: 'var(--danger)' }, fillcolor: 'var(--danger)' } + }; + + const layoutCandle = { + margin: { l: 40, r: 10, t: 10, b: 20 }, height: 320, + xaxis: { showgrid: false, zeroline: false, rangeslider: { visible: false } }, yaxis: { showgrid: true, zeroline: false }, + paper_bgcolor: 'var(--card)', plot_bgcolor: 'var(--card)', font: { family: 'Inter', color: 'var(--text)' }, hovermode: 'x unified', + }; + + Plotly.newPlot(chartDiv, [traceCandle], layoutCandle, { displayModeBar: false }); + + // 2. RSI Indicator Chart + const traceRSI = { + x: data.indicators.x, y: data.indicators.rsi, mode: 'lines', name: 'RSI (14)', + line: { color: 'var(--primary)', width: 2 }, + }; + + const layoutRSI = { + margin: { l: 40, r: 10, t: 10, b: 40 }, height: 200, + xaxis: { showgrid: false, zeroline: false }, yaxis: { showgrid: true, zeroline: false, range: [20, 80], title: 'RSI' }, + shapes: [ + // Overbought line (70) + { type: 'line', xref: 'paper', yref: 'y', x0: 0, y0: 70, x1: 1, y1: 70, line: { color: 'var(--danger)', width: 1, dash: 'dot' } }, + // Oversold line (30) + { type: 'line', xref: 'paper', yref: 'y', x0: 0, y0: 30, x1: 1, y1: 30, line: { color: 'var(--success)', width: 1, dash: 'dot' } } + ], + paper_bgcolor: 'var(--card)', plot_bgcolor: 'var(--card)', font: { family: 'Inter', color: 'var(--text)' } + }; + + Plotly.newPlot(indicatorsDiv, [traceRSI], layoutRSI, { displayModeBar: false }); +} + + +/** Changes the active timeframe for the trade chart. */ +function changeTimeframe(timeframe) { + document.querySelectorAll('.chart-tab').forEach(tab => { + tab.classList.remove('active'); + }); + // Find the correct button using its text content or attribute + const activeTab = Array.from(document.querySelectorAll('.chart-tab')).find(tab => tab.textContent.trim() === timeframe); + if (activeTab) { + activeTab.classList.add('active'); + } + + const stockData = MOCK_DATA.tradeData[currentState.selectedStock] || MOCK_DATA.tradeData['TCS']; + plotTradeCharts(stockData); // Chart plotting uses the same mock data regardless of timeframe for this example +} + +// --- 6. RENDER FUNCTIONS (Portfolio) --- + +/** Renders the list of user holdings. */ +function renderHoldings() { + const container = document.getElementById('holdings'); + container.innerHTML = MOCK_DATA.holdings.map(holding => { + const value = holding.currentPrice * holding.qty; + const invested = holding.avgPrice * holding.qty; + const pnl = value - invested; + const pnlPercent = (pnl / invested) * 100; + const pnlClass = pnl > 0 ? 'text-success' : 'text-danger'; + const pnlText = `${pnl > 0 ? '+' : ''}${formatCurrency(pnl)} (${pnlPercent.toFixed(2)}%)`; + + return ` +
+
+
${holding.symbol} - ${holding.name}
+
${holding.qty} Shares @ ${formatCurrency(holding.avgPrice)}
+
+
+
${formatCurrency(value)}
+
${pnlText}
+
+
+ `; + }).join(''); +} + +/** Renders the Portfolio and Sector Allocation charts. */ +function renderPortfolioCharts() { + let totalPortfolioValue = 0; + const allocationData = MOCK_DATA.holdings.map(h => { + const value = h.currentPrice * h.qty; + totalPortfolioValue += value; + return { symbol: h.symbol, value, sector: h.sector }; + }); + + // 1. Asset Allocation (Donut/Pie Chart) + const portfolioChartDiv = document.getElementById('portfolio-chart'); + const assetLabels = allocationData.map(d => d.symbol); + const assetValues = allocationData.map(d => d.value); + + const traceAsset = { + labels: assetLabels, values: assetValues, type: 'pie', hole: .4, + marker: { colors: ['#5367FF', '#00C48C', '#FF4B55', '#FFC107', '#9C27B0'] } + }; + + Plotly.newPlot(portfolioChartDiv, [traceAsset], { + margin: { t: 20, b: 20, l: 20, r: 20 }, showlegend: true, paper_bgcolor: 'var(--card)', font: { family: 'Inter', color: 'var(--text)' } + }, { displayModeBar: false }); + + // 2. Sector Distribution (Bar Chart) + const sectorChartDiv = document.getElementById('sector-chart'); + const sectorMap = allocationData.reduce((acc, curr) => { + acc[curr.sector] = (acc[curr.sector] || 0) + curr.value; + return acc; + }, {}); + + const sectorLabels = Object.keys(sectorMap); + const sectorValues = Object.values(sectorMap); + + const traceSector = { x: sectorLabels, y: sectorValues, type: 'bar', marker: { color: 'var(--primary)' } }; + + Plotly.newPlot(sectorChartDiv, [traceSector], { + margin: { t: 20, b: 40, l: 40, r: 10 }, xaxis: { automargin: true }, yaxis: { title: 'Value (₹)' }, + paper_bgcolor: 'var(--card)', plot_bgcolor: 'var(--card)', font: { family: 'Inter', color: 'var(--text)' } + }, { displayModeBar: false }); +} + +// --- 7. RENDER FUNCTIONS (Analytics) --- + +/** Renders the Analytics screen charts. */ +function renderAnalyticsCharts() { + // 1. Portfolio Growth (Area Chart) + const growthChartDiv = document.getElementById('growthChart'); + const dates = ['2024-01', '2024-03', '2024-05', '2024-07', '2024-09', '2024-11']; + const growthData = [95000, 105000, 115000, 110000, 120000, 124832]; + + const traceGrowth = { + x: dates, y: growthData, mode: 'lines', fill: 'tozeroy', name: 'Portfolio Value', + line: { color: 'var(--primary)', width: 2 }, fillcolor: 'rgba(83,103,255,0.2)', + hovertemplate: '%{x}
Value: %{y:$,.0f}' + }; + + Plotly.newPlot(growthChartDiv, [traceGrowth], { + margin: { l: 40, r: 10, t: 10, b: 40 }, height: 260, + xaxis: { showgrid: false, zeroline: false }, yaxis: { showgrid: true, zeroline: false, tickprefix: '₹' }, + paper_bgcolor: 'var(--card)', plot_bgcolor: 'var(--card)', font: { family: 'Inter', color: 'var(--text)' } + }, { displayModeBar: false }); + + // 2. Monthly Returns (Bar Chart) + const returnsChartDiv = document.getElementById('returnsChart'); + const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; + const returns = [2.5, 4.1, -1.5, 0.8, 3.2, 1.1]; + + const traceReturns = { + x: months, y: returns, type: 'bar', + marker: { color: returns.map(r => r > 0 ? 'var(--success)' : 'var(--danger)') }, + hovertemplate: '%{x}
Return: %{y:.2f}%' + }; + + Plotly.newPlot(returnsChartDiv, [traceReturns], { + margin: { l: 40, r: 10, t: 10, b: 40 }, height: 240, + xaxis: { automargin: true }, yaxis: { title: 'Return (%)' }, + paper_bgcolor: 'var(--card)', plot_bgcolor: 'var(--card)', font: { family: 'Inter', color: 'var(--text)' } + }, { displayModeBar: false }); + + // 3. Risk Analysis (Bar Chart) + const riskChartDiv = document.getElementById('riskChart'); + const riskMetrics = ['Volatility', 'Sharpe Ratio', 'Beta', 'Max Drawdown']; + const metricValues = [15.2, 1.25, 1.1, -8.5]; + + const traceRisk = { + x: riskMetrics, y: metricValues, type: 'bar', name: 'Risk Metrics', + marker: { color: ['var(--danger)', 'var(--success)', 'var(--danger)', 'var(--danger)'] } + }; + + Plotly.newPlot(riskChartDiv, [traceRisk], { + margin: { l: 40, r: 10, t: 10, b: 40 }, height: 240, + xaxis: { automargin: true }, yaxis: { title: 'Value' }, + paper_bgcolor: 'var(--card)', plot_bgcolor: 'var(--card)', font: { family: 'Inter', color: 'var(--text)' } + }, { displayModeBar: false }); +} + +// --- 8. INTERACTIVE LOGIC (Modals & Actions) --- + +/** Shows the generic modal. */ +function showModal() { + document.getElementById('searchModal').classList.add('active'); + document.getElementById('searchInput').focus(); +} + +/** Hides the generic modal. */ +function hideModal() { + document.getElementById('searchModal').classList.remove('active'); + document.getElementById('searchInput').value = ''; + document.getElementById('searchResults').innerHTML = ''; +} + +/** Simple stock search function. */ +function searchStocks() { + const input = document.getElementById('searchInput').value.toUpperCase(); + const resultsDiv = document.getElementById('searchResults'); + resultsDiv.innerHTML = ''; + + if (input.length < 2) return; + + // Aggregate all unique symbols from mock data + const allSymbols = [ + ...MOCK_DATA.watchlist, ...MOCK_DATA.holdings, + ...MOCK_DATA.movers.map(m => ({ symbol: m.symbol })), + ...Object.values(MOCK_DATA.tradeData) + ].filter((v, i, a) => a.findIndex(t => t.symbol === v.symbol) === i); + + const filtered = allSymbols.filter(stock => stock.symbol.includes(input)); + + if (filtered.length === 0) { + resultsDiv.innerHTML = '
No results found.
'; + return; + } + + resultsDiv.innerHTML = filtered.map(stock => ` +
+
+
${stock.symbol}
+
${stock.name || 'Equity'}
+
+
+
View Chart
+
+
+ `).join(''); +} + +/** Shows the Top Movers modal. */ +function showMoversModal() { + document.getElementById('moversModal').classList.add('active'); + const container = document.getElementById('allMovers'); + container.innerHTML = MOCK_DATA.movers.map(mover => { + const changeClass = mover.isPositive ? 'positive' : 'negative'; + const changeText = `${mover.isPositive ? '+' : ''}${mover.change.toFixed(2)}%`; + return ` +
+
${mover.symbol}
+
${changeText}
+
+ `; + }).join(''); +} + +/** Hides the Top Movers modal. */ +function hideMoversModal() { + document.getElementById('moversModal').classList.remove('active'); +} + +/** Simulates placing a buy/sell order. */ +function place(type) { + const qtyInput = document.getElementById('qty'); + const qty = qtyInput.value; + const symbol = currentState.selectedStock; + + if (qty <= 0) { + alert('Please enter a valid quantity.'); + return; + } + + if (confirm(`Confirm ${type.toUpperCase()} ${qty} shares of ${symbol}?`)) { + alert(`${type.toUpperCase()} order placed successfully for ${qty} shares of ${symbol}!`); + qtyInput.value = 1; + } +} + + +// --- 9. INITIALIZATION --- + +/** Runs on page load to initialize the app. */ +function init() { + renderMarketStatus(); + renderWatchlist(); + renderPerformanceChart(); + renderMovers(); + renderNews(); + renderHoldings(); + loadTradeScreen('TCS'); // Load trade data initially for TCS + // Go to dashboard to ensure correct display, nav, and chart visibility + goTo('dashboard'); +} + +// Initial application load +window.onload = init; \ No newline at end of file diff --git a/Domains/Frontend/MiniProjects/Stock-Exchange/index.html b/Domains/Frontend/MiniProjects/Stock-Exchange/index.html new file mode 100644 index 00000000..c5ae5725 --- /dev/null +++ b/Domains/Frontend/MiniProjects/Stock-Exchange/index.html @@ -0,0 +1,256 @@ + + + + + +FinFlow — Advanced Stock Analysis + + + + + +
+
+
+
+ +
+
FinFlow
+
Pro Trading Platform
+
+
+
+ + +
+
+
+
+ +
+
+
+
Total Portfolio Value
+
₹1,24,832
+
+₹4,312 (3.5%) Today
+
+ +
+
+
Watchlist
+
Edit
+
+
+
+ +
+
+
Performance Analytics
+
+
+
+
+
+ +
+
+
Top Gainers
+
View All
+
+
+
+
+
+ +
+
+
Quick Actions
+
+
+ + + + +
+
+ +
+
+
Market News
+
+
+
+
+
+
+ + + + + + +
+ +
+ + +
+
+
+
+ + + + +   + \ No newline at end of file diff --git a/Domains/Frontend/MiniProjects/Stock-Exchange/style.css b/Domains/Frontend/MiniProjects/Stock-Exchange/style.css new file mode 100644 index 00000000..d7e37107 --- /dev/null +++ b/Domains/Frontend/MiniProjects/Stock-Exchange/style.css @@ -0,0 +1,842 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap'); + +:root{ + /* Fixed minor syntax issue: removed extra spaces after colons */ + --primary: #5367FF; + --success: #00C48C; + --danger: #FF4B55; + --bg: #F7F9FC; + --card: #FFFFFF; + --text: #1F2937; + --text-light: #6B7280; + --border: #E5E7EB; + font-family: 'Inter', system-ui, sans-serif; +} + +* { box-sizing: border-box; margin: 0; padding: 0; } + +html, body { + height: 100%; + background: var(--bg); + color: var(--text); +} + +.app { + max-width: 428px; + margin: 0 auto; + min-height: 100vh; + display: flex; + flex-direction: column; + background: var(--bg); +} + +header { + padding: 16px; + background: var(--card); + border-bottom: 1px solid var(--border); + position: sticky; + top: 0; + z-index: 100; +} + +.header-top { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} + +.brand { + display: flex; + align-items: center; + gap: 10px; +} + +.logo { + width: 40px; + height: 40px; + border-radius: 10px; + background: linear-gradient(135deg, var(--primary), #7C3AED); + display: flex; + align-items: center; + justify-content: center; + font-weight: 800; + color: #fff; + font-size: 16px; +} + +.brand-text .title { + font-size: 18px; + font-weight: 700; + color: var(--text); +} + +.brand-text .subtitle { + font-size: 11px; + color: var(--text-light); +} + +.header-actions { + display: flex; + gap: 12px; +} + +.icon-btn { + width: 36px; + height: 36px; + border-radius: 8px; + background: var(--bg); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s; + font-size: 18px; + border: none; +} + +.icon-btn:hover { + background: var(--border); + transform: scale(1.1); +} + +.market-status { + display: flex; + gap: 12px; + overflow-x: auto; + padding: 8px 0; +} + +.market-status::-webkit-scrollbar { + display: none; +} + +.index-item { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + background: var(--bg); + border-radius: 8px; + min-width: fit-content; + font-size: 13px; + cursor: pointer; + transition: all 0.2s; +} + +.index-item:hover { + background: var(--card); + box-shadow: 0 2px 8px rgba(0,0,0,0.08); +} + +.index-name { + color: var(--text-light); + font-weight: 600; +} + +.index-value { + font-weight: 700; + color: var(--text); +} + +.index-change { + font-size: 12px; + font-weight: 600; + padding: 2px 6px; + border-radius: 4px; +} + +.index-change.positive { + color: var(--success); + background: #D1FAE5; +} + +.index-change.negative { + color: var(--danger); + background: #FEE2E2; +} + +main { + flex: 1; + padding: 16px 16px 80px; + overflow-y: auto; +} + +.section { + margin-bottom: 20px; +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +.section-title { + font-size: 16px; + font-weight: 700; + color: var(--text); +} + +.section-action { + font-size: 13px; + color: var(--primary); + font-weight: 600; + cursor: pointer; + transition: all 0.2s; +} + +.section-action:hover { + color: #7C3AED; +} + +.card { + background: var(--card); + border-radius: 12px; + padding: 16px; + box-shadow: 0 1px 3px rgba(0,0,0,0.05); + border: 1px solid var(--border); + transition: all 0.3s; +} + +.card:hover { + box-shadow: 0 4px 12px rgba(0,0,0,0.08); +} + +.portfolio-card { + background: linear-gradient(135deg, var(--primary), #7C3AED); + color: white; + padding: 20px; + margin-bottom: 16px; + cursor: pointer; + position: relative; + overflow: hidden; +} + +.portfolio-card::before { + content: ''; + position: absolute; + top: -50%; + right: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%); + animation: pulse 3s infinite; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1); opacity: 0.5; } + 50% { transform: scale(1.1); opacity: 0.8; } +} + +.portfolio-value { + font-size: 11px; + opacity: 0.9; + margin-bottom: 4px; +} + +.portfolio-amount { + font-size: 32px; + font-weight: 800; + margin-bottom: 8px; +} + +.portfolio-change { + display: inline-block; + font-size: 14px; + font-weight: 600; + padding: 6px 12px; + background: rgba(255,255,255,0.2); + border-radius: 6px; +} + +.watchlist { + display: flex; + gap: 12px; + overflow-x: auto; + padding: 4px 0; +} + +.watchlist::-webkit-scrollbar { + display: none; +} + +.stock-card { + min-width: 140px; + padding: 14px; + background: var(--card); + border: 1px solid var(--border); + border-radius: 10px; + cursor: pointer; + transition: all 0.3s; + position: relative; +} + +.stock-card::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 3px; + background: var(--success); + transform: scaleX(0); + transition: transform 0.3s; +} + +.stock-card.negative::after { + background: var(--danger); +} + +.stock-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 16px rgba(0,0,0,0.1); + border-color: var(--primary); +} + +.stock-card:hover::after { + transform: scaleX(1); +} + +.stock-symbol { + font-size: 14px; + font-weight: 700; + color: var(--text); + margin-bottom: 6px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.stock-price { + font-size: 16px; + font-weight: 700; + color: var(--text); + margin-bottom: 4px; +} + +.stock-change { + font-size: 12px; + font-weight: 600; +} + +.stock-change.positive { + color: var(--success); +} + +.stock-change.negative { + color: var(--danger); +} + +.mini-chart { + height: 30px; + margin-top: 8px; + opacity: 0.6; +} + +.holdings-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.holding-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 14px; + background: var(--bg); + border-radius: 10px; + cursor: pointer; + transition: all 0.2s; + border: 2px solid transparent; +} + +.holding-item:hover { + background: var(--card); + box-shadow: 0 4px 12px rgba(0,0,0,0.08); + border-color: var(--primary); + transform: translateX(4px); +} + +.holding-info { + flex: 1; +} + +.holding-name { + font-size: 14px; + font-weight: 600; + color: var(--text); + margin-bottom: 4px; +} + +.holding-qty { + font-size: 12px; + color: var(--text-light); +} + +.holding-values { + text-align: right; +} + +.holding-value { + font-size: 14px; + font-weight: 700; + color: var(--text); + margin-bottom: 4px; +} + +.holding-pnl { + font-size: 12px; + font-weight: 600; +} + +.movers-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; +} + +.mover-card { + padding: 12px; + background: var(--bg); + border-radius: 8px; + text-align: center; + cursor: pointer; + transition: all 0.3s; + border: 2px solid transparent; +} + +.mover-card:hover { + background: var(--card); + box-shadow: 0 4px 12px rgba(0,0,0,0.08); + transform: translateY(-2px) scale(1.05); + border-color: var(--primary); +} + +.mover-symbol { + font-size: 12px; + font-weight: 700; + color: var(--text); + margin-bottom: 6px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.mover-change { + font-size: 16px; + font-weight: 700; +} + +.chart-container { + background: var(--card); + border: 1px solid var(--border); + border-radius: 12px; + padding: 16px; + margin-bottom: 16px; +} + +.chart-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +.chart-symbol { + font-size: 18px; + font-weight: 700; +} + +.chart-price { + font-size: 24px; + font-weight: 800; + margin: 8px 0; +} + +.chart-tabs { + display: flex; + gap: 8px; + margin-bottom: 12px; + overflow-x: auto; +} + +.chart-tabs::-webkit-scrollbar { + display: none; +} + +.chart-tab { + padding: 6px 14px; + border-radius: 6px; + background: var(--bg); + font-size: 12px; + font-weight: 600; + color: var(--text-light); + cursor: pointer; + white-space: nowrap; + border: none; + transition: all 0.2s; +} + +.chart-tab:hover { + background: var(--border); +} + +.chart-tab.active { + background: var(--primary); + color: white; +} + +.chart-wrap { + height: 320px; + border-radius: 8px; + overflow: hidden; +} + +.order-section { + margin-top: 16px; +} + +.qty-input { + width: 100%; + padding: 14px; + border: 2px solid var(--border); + border-radius: 8px; + font-size: 15px; + font-weight: 600; + margin-bottom: 12px; + background: var(--bg); + color: var(--text); + transition: all 0.2s; +} + +.qty-input:focus { + outline: none; + border-color: var(--primary); + background: var(--card); + box-shadow: 0 0 0 4px rgba(83,103,255,0.1); +} + +.order-buttons { + display: flex; + gap: 12px; +} + +.order-btn { + flex: 1; + padding: 16px; + border: none; + border-radius: 8px; + font-size: 15px; + font-weight: 700; + cursor: pointer; + transition: all 0.3s; + text-transform: uppercase; + letter-spacing: 0.5px; + position: relative; + overflow: hidden; +} + +.order-btn::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + background: rgba(255,255,255,0.3); + border-radius: 50%; + transform: translate(-50%, -50%); + transition: width 0.6s, height 0.6s; +} + +.order-btn:active::before { + width: 300px; + height: 300px; +} + +.order-btn.buy { + background: var(--success); + color: white; +} + +.order-btn.buy:hover { + background: #00B37E; + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(0,196,140,0.3); +} + +.order-btn.sell { + background: var(--danger); + color: white; +} + +.order-btn.sell:hover { + background: #E63946; + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(255,75,85,0.3); +} + +.news-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.news-item { + padding: 12px; + background: var(--bg); + border-radius: 8px; + font-size: 13px; + line-height: 1.5; + color: var(--text); + cursor: pointer; + transition: all 0.3s; + border: 1px solid transparent; +} + +.news-item:hover { + background: var(--card); + border-color: var(--primary); + box-shadow: 0 4px 12px rgba(0,0,0,0.08); + transform: translateX(4px); +} + +.bottom-nav { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: var(--card); + border-top: 1px solid var(--border); + padding: 8px 16px 12px; + z-index: 100; + max-width: 428px; + margin: 0 auto; +} + +.nav-items { + display: flex; + justify-content: space-around; +} + +.nav-item { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 8px; + cursor: pointer; + border-radius: 8px; + transition: all 0.3s; + position: relative; +} + +.nav-item::before { + content: ''; + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 40%; + height: 3px; + background: var(--primary); + border-radius: 0 0 3px 3px; + opacity: 0; + transition: all 0.3s; +} + +.nav-item.active::before { + opacity: 1; +} + +.nav-item:hover { + background: var(--bg); +} + +.nav-icon { + font-size: 22px; + transition: transform 0.3s; +} + +.nav-item:hover .nav-icon { + transform: scale(1.2); +} + +.nav-label { + font-size: 11px; + font-weight: 600; + color: var(--text-light); + transition: color 0.3s; +} + +.nav-item.active .nav-label { + color: var(--primary); +} + +.quick-actions { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12px; +} + +.action-btn { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 14px 8px; + background: var(--bg); + border-radius: 10px; + cursor: pointer; + transition: all 0.3s; + border: none; + color: var(--text); +} + +.action-btn:hover { + background: var(--card); + box-shadow: 0 4px 12px rgba(0,0,0,0.08); + transform: translateY(-2px); +} + +.action-icon { + font-size: 24px; +} + +.action-label { + font-size: 11px; + font-weight: 600; + text-align: center; +} + +.fab { + position: fixed; + right: 20px; + bottom: 80px; + width: 56px; + height: 56px; + border-radius: 16px; + background: var(--primary); + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 24px; + font-weight: 700; + cursor: pointer; + box-shadow: 0 4px 16px rgba(83,103,255,0.4); + z-index: 99; + transition: all 0.3s; +} + +.fab:hover { + transform: scale(1.1) rotate(90deg); + box-shadow: 0 8px 24px rgba(83,103,255,0.6); +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; + margin-top: 12px; +} + +.stat-card { + padding: 12px; + background: var(--bg); + border-radius: 8px; + text-align: center; + transition: all 0.3s; +} + +.stat-card:hover { + background: var(--card); + box-shadow: 0 4px 12px rgba(0,0,0,0.08); + transform: scale(1.05); +} + +.stat-label { + font-size: 11px; + color: var(--text-light); + margin-bottom: 4px; +} + +.stat-value { + font-size: 18px; + font-weight: 700; + color: var(--text); +} + +.text-success { color: var(--success); } +.text-danger { color: var(--danger); } +.text-muted { color: var(--text-light); } + +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: var(--bg); +} + +::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-light); +} + +.modal { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.5); + z-index: 1000; + align-items: center; + justify-content: center; +} + +.modal.active { + display: flex; +} + +.modal-content { + background: var(--card); + border-radius: 16px; + padding: 24px; + max-width: 360px; + width: 90%; + animation: slideUp 0.3s ease-out; +} + +@keyframes slideUp { + from { transform: translateY(50px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +} + +.modal-header { + font-size: 18px; + font-weight: 700; + margin-bottom: 16px; +} + +.modal-close { + float: right; + font-size: 24px; + cursor: pointer; + color: var(--text-light); +} + +.modal-close:hover { + color: var(--text); +} \ No newline at end of file