Remove animated background (grid / orbs)

This commit is contained in:
Justin Oros
2026-01-28 16:22:59 -07:00
parent 7943440e54
commit 0dff4b0e5b
+46 -257
View File
@@ -20,13 +20,11 @@
--text-muted:#6e7681;
--border:#30363d;
}
*{
margin:0;
padding:0;
box-sizing:border-box;
}
body{
font-family:'Outfit',sans-serif;
background:var(--bg-primary);
@@ -34,62 +32,6 @@
min-height:100vh;
overflow-x:hidden;
}
/* Animated background grid */
.bg-grid {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(var(--border) 1px, transparent 1px),
linear-gradient(90deg, var(--border) 1px, transparent 1px);
background-size: 50px 50px;
opacity: 0.3;
animation: gridMove 20s linear infinite;
pointer-events: none;
z-index: 0;
}
@keyframes gridMove {
0% { transform: translate(0, 0); }
100% { transform: translate(50px, 50px); }
}
/* Gradient orb effects */
.orb {
position: fixed;
border-radius: 50%;
filter: blur(80px);
opacity: 0.15;
pointer-events: none;
z-index: 0;
}
.orb-1 {
width: 300px;
height: 300px;
background: var(--accent-green);
top: -100px;
right: -100px;
animation: float 15s ease-in-out infinite;
}
.orb-2 {
width: 400px;
height: 400px;
background: var(--accent-blue);
bottom: -150px;
left: -150px;
animation: float 20s ease-in-out infinite reverse;
}
@keyframes float {
0%, 100% { transform: translate(0, 0) scale(1); }
50% { transform: translate(30px, -30px) scale(1.1); }
}
.container{
position:relative;
z-index:1;
@@ -97,32 +39,26 @@
margin:0 auto;
padding:20px;
}
/* Header */
header{
text-align:center;
padding:40px 0 30px;
animation: slideDown 0.6s ease-out;
animation:slideDown .6s ease-out;
}
@keyframes slideDown{
from{opacity:0;transform:translateY(-20px);}
to{opacity:1;transform:translateY(0);}
}
.logo-container{
display:flex;
flex-direction:column;
align-items:center;
gap:16px;
}
.logo-image{
width:clamp(180px,30vw,200px);
height:auto;
filter: drop-shadow(0 4px 12px rgba(0, 255, 136, 0.3));
filter:drop-shadow(0 4px 12px rgba(0,255,136,.3));
}
.logo{
font-family:'JetBrains Mono',monospace;
font-size:clamp(28px,6vw,42px);
@@ -133,14 +69,11 @@
background-clip:text;
letter-spacing:-1px;
}
.tagline{
font-size:clamp(14px,3vw,16px);
color:var(--text-secondary);
font-weight:300;
}
/* Login Card */
.login-card{
background:var(--bg-card);
border:1px solid var(--border);
@@ -148,37 +81,30 @@
padding:clamp(24px,5vw,40px);
margin:20px auto;
max-width:500px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
animation: fadeInUp 0.6s ease-out 0.2s backwards;
box-shadow:0 8px 32px rgba(0,0,0,.4);
animation:fadeInUp .6s ease-out .2s backwards;
}
@keyframes fadeInUp{
from{opacity:0;transform:translateY(20px);}
to{opacity:1;transform:translateY(0);}
}
/* Trading Mode Toggle */
.trading-mode-group{
margin-bottom:24px;
}
.mode-options{
display:grid;
grid-template-columns:1fr 1fr;
gap:12px;
}
.mode-option{
position:relative;
}
.mode-option input[type="radio"]{
position:absolute;
opacity:0;
width:0;
height:0;
}
.mode-option-label{
display:flex;
flex-direction:column;
@@ -189,19 +115,16 @@
border:2px solid var(--border);
border-radius:8px;
cursor:pointer;
transition: all 0.3s ease;
transition:all .3s ease;
}
.mode-option input[type="radio"]:checked+.mode-option-label{
background: rgba(0, 255, 136, 0.1);
background:rgba(0,255,136,.1);
border-color:var(--accent-green);
box-shadow: 0 0 0 3px rgba(0, 255, 136, 0.1);
box-shadow:0 0 0 3px rgba(0,255,136,.1);
}
.mode-option-label:hover{
border-color:var(--accent-green);
}
.mode-title{
font-family:'JetBrains Mono',monospace;
font-size:14px;
@@ -209,17 +132,14 @@
color:var(--text-primary);
margin-bottom:4px;
}
.mode-description{
font-size:11px;
color:var(--text-muted);
text-align:center;
}
.input-group{
margin-bottom:20px;
}
label{
display:block;
font-size:13px;
@@ -227,11 +147,9 @@
color:var(--text-secondary);
margin-bottom:8px;
text-transform:uppercase;
letter-spacing: 0.5px;
letter-spacing:.5px;
}
input[type="password"],
input[type="text"] {
input[type="password"],input[type="text"]{
width:100%;
padding:14px 16px;
background:var(--bg-secondary);
@@ -240,22 +158,17 @@
color:var(--text-primary);
font-family:'JetBrains Mono',monospace;
font-size:14px;
transition: all 0.3s ease;
transition:all .3s ease;
}
input[type="password"]:focus,
input[type="text"]:focus {
input[type="password"]:focus,input[type="text"]:focus{
outline:none;
border-color:var(--accent-green);
box-shadow: 0 0 0 3px rgba(0, 255, 136, 0.1);
box-shadow:0 0 0 3px rgba(0,255,136,.1);
}
/* Make text input look like password field */
input[type="text"]#apiKey{
-webkit-text-security:disc;
text-security:disc;
}
.btn-go{
width:100%;
padding:16px;
@@ -269,48 +182,39 @@
cursor:pointer;
text-transform:uppercase;
letter-spacing:1px;
transition: all 0.3s ease;
box-shadow: 0 4px 16px rgba(0, 255, 136, 0.3);
transition:all .3s ease;
box-shadow:0 4px 16px rgba(0,255,136,.3);
}
.btn-go:hover{
transform:translateY(-2px);
box-shadow: 0 6px 24px rgba(0, 255, 136, 0.4);
box-shadow:0 6px 24px rgba(0,255,136,.4);
}
.btn-go:active{
transform:translateY(0);
}
.btn-go:disabled{
opacity: 0.5;
opacity:.5;
cursor:not-allowed;
}
.api-key-info{
text-align:center;
margin-top:12px;
font-size:11px;
color:var(--text-muted);
}
.api-key-info a{
color:var(--accent-green);
text-decoration:none;
transition: color 0.3s ease;
transition:color .3s ease;
}
.api-key-info a:hover{
color:var(--accent-blue);
text-decoration:underline;
}
/* Dashboard */
#dashboard{
display:none;
animation: fadeInUp 0.6s ease-out;
animation:fadeInUp .6s ease-out;
}
.dashboard-header{
display:flex;
justify-content:space-between;
@@ -319,49 +223,42 @@
flex-wrap:wrap;
gap:12px;
}
.account-info{
display:flex;
align-items:center;
gap:12px;
}
.status-badge{
padding:6px 12px;
border-radius:20px;
font-size:12px;
font-weight:600;
text-transform:uppercase;
letter-spacing: 0.5px;
letter-spacing:.5px;
}
.status-active{
background: rgba(0, 255, 136, 0.15);
background:rgba(0,255,136,.15);
color:var(--accent-green);
border:1px solid var(--accent-green);
}
.account-number{
font-family:'JetBrains Mono',monospace;
color:var(--text-secondary);
font-size:14px;
cursor:pointer;
transition: all 0.3s ease;
transition:all .3s ease;
user-select:none;
position:relative;
padding:4px 8px;
border-radius:4px;
}
.account-number:hover{
color:var(--accent-green);
background: rgba(0, 255, 136, 0.1);
background:rgba(0,255,136,.1);
}
.account-number:active{
transform: scale(0.95);
transform:scale(.95);
}
.account-number.copied::after{
content:'Copied!';
position:absolute;
@@ -376,13 +273,11 @@
white-space:nowrap;
animation:fadeOut 2s ease-out forwards;
}
@keyframes fadeOut{
0%{opacity:1;}
70%{opacity:1;}
100%{opacity:0;}
}
.btn-logout{
padding:8px 16px;
background:transparent;
@@ -391,32 +286,27 @@
color:var(--text-secondary);
font-size:14px;
cursor:pointer;
transition: all 0.3s ease;
transition:all .3s ease;
}
.btn-logout:hover{
border-color:var(--accent-red);
color:var(--accent-red);
}
/* Stats Grid */
.stats-grid{
display:grid;
grid-template-columns:repeat(auto-fit,minmax(250px,1fr));
gap:16px;
margin-bottom:24px;
}
.stat-card{
background:var(--bg-card);
border:1px solid var(--border);
border-radius:12px;
padding:20px;
transition: all 0.3s ease;
transition:all .3s ease;
position:relative;
overflow:hidden;
}
.stat-card::before{
content:'';
position:absolute;
@@ -427,48 +317,39 @@
background:linear-gradient(90deg,var(--accent-green),var(--accent-blue));
transform:scaleX(0);
transform-origin:left;
transition: transform 0.3s ease;
transition:transform .3s ease;
}
.stat-card:hover::before{
transform:scaleX(1);
}
.stat-card:hover{
transform:translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
box-shadow:0 8px 24px rgba(0,0,0,.3);
}
.stat-label{
font-size:12px;
color:var(--text-muted);
text-transform:uppercase;
letter-spacing: 0.5px;
letter-spacing:.5px;
margin-bottom:8px;
}
.stat-value{
font-family:'JetBrains Mono',monospace;
font-size:clamp(20px,4vw,28px);
font-weight:700;
color:var(--text-primary);
}
.stat-value.positive{
color:var(--accent-green);
}
.stat-value.negative{
color:var(--accent-red);
}
.stat-change{
font-size:13px;
margin-top:4px;
font-weight:600;
}
/* Positions Table */
.section-title{
font-size:18px;
font-weight:700;
@@ -477,7 +358,6 @@
align-items:center;
gap:8px;
}
.section-title::before{
content:'';
width:4px;
@@ -485,7 +365,6 @@
background:var(--accent-green);
border-radius:2px;
}
.positions-container{
background:var(--bg-card);
border:1px solid var(--border);
@@ -493,20 +372,16 @@
overflow:hidden;
margin-bottom:24px;
}
.positions-scroll{
overflow-x:auto;
}
table{
width:100%;
border-collapse:collapse;
}
thead{
background:var(--bg-secondary);
}
th{
padding:16px;
text-align:left;
@@ -514,34 +389,28 @@
font-weight:600;
color:var(--text-secondary);
text-transform:uppercase;
letter-spacing: 0.5px;
letter-spacing:.5px;
white-space:nowrap;
}
td{
padding:16px;
border-top:1px solid var(--border);
font-size:14px;
white-space:nowrap;
}
.symbol{
font-family:'JetBrains Mono',monospace;
font-weight:700;
color:var(--text-primary);
}
.mono{
font-family:'JetBrains Mono',monospace;
}
/* Loading */
.loading{
text-align:center;
padding:40px;
color:var(--text-secondary);
}
.spinner{
border:3px solid var(--border);
border-top:3px solid var(--accent-green);
@@ -551,15 +420,12 @@
animation:spin 1s linear infinite;
margin:0 auto 16px;
}
@keyframes spin{
0%{transform:rotate(0deg);}
100%{transform:rotate(360deg);}
}
/* Error */
.error{
background: rgba(255, 68, 102, 0.1);
background:rgba(255,68,102,.1);
border:1px solid var(--accent-red);
color:var(--accent-red);
padding:16px;
@@ -567,60 +433,45 @@
margin-top:16px;
font-size:14px;
}
.empty-state{
text-align:center;
padding:60px 20px;
color:var(--text-muted);
}
.empty-state-icon{
font-size:48px;
margin-bottom:16px;
opacity: 0.5;
opacity:.5;
}
/* Responsive */
@media (max-width:768px){
.container{
padding:12px;
}
header{
padding:20px 0 20px;
}
.stats-grid{
grid-template-columns:1fr;
}
th,td{
padding:12px 8px;
font-size:12px;
}
.dashboard-header{
flex-direction:column;
align-items:flex-start;
}
}
/* Refresh animation */
@keyframes pulse{
0%,100%{opacity:1;}
50% { opacity: 0.5; }
50%{opacity:.5;}
}
.refreshing{
animation:pulse 1.5s ease-in-out infinite;
}
</style>
</head>
<body>
<div class="bg-grid"></div>
<div class="orb orb-1"></div>
<div class="orb orb-2"></div>
<div class="container">
<header>
<div class="logo-container">
@@ -630,11 +481,9 @@
<div class="tagline">Real-time portfolio monitoring</div>
</header>
<!-- Login Form -->
<div id="loginForm">
<div class="login-card">
<form id="loginFormElement" onsubmit="login(event)">
<!-- Trading Mode Selection -->
<div class="trading-mode-group">
<div class="mode-options">
<div class="mode-option">
@@ -654,26 +503,13 @@
</div>
</div>
<!-- API Credentials -->
<div class="input-group">
<label for="apiKey">API Key</label>
<input
type="text"
id="apiKey"
name="username"
placeholder="PK••••••••••••••••"
autocomplete="username"
>
<input type="text" id="apiKey" name="username" placeholder="PK••••••••••••••••" autocomplete="username">
</div>
<div class="input-group">
<label for="apiSecret">API Secret</label>
<input
type="password"
id="apiSecret"
name="password"
placeholder="••••••••••••••••"
autocomplete="current-password"
>
<input type="password" id="apiSecret" name="password" placeholder="••••••••••••••••" autocomplete="current-password">
</div>
<button type="submit" class="btn-go">GO!</button>
<div class="api-key-info">
@@ -684,23 +520,16 @@
</div>
</div>
<!-- Dashboard -->
<div id="dashboard">
<div class="dashboard-header">
<div class="account-info">
<span class="status-badge status-active">● LIVE</span>
<span id="accountNumber" class="account-number"
onmouseenter="revealAccountNumber()"
onmouseleave="hideAccountNumber()"
onclick="copyAccountNumber(event)"
oncontextmenu="copyAccountNumber(event)">#PA••••••••</span>
<span id="accountNumber" class="account-number" onmouseenter="revealAccountNumber()" onmouseleave="hideAccountNumber()" onclick="copyAccountNumber(event)" oncontextmenu="copyAccountNumber(event)">#PA••••••••</span>
</div>
<button class="btn-logout" onclick="logout()">Logout</button>
</div>
<div class="stats-grid" id="statsGrid">
<!-- Stats will be inserted here -->
</div>
<div class="stats-grid" id="statsGrid"></div>
<h2 class="section-title">Positions</h2>
<div class="positions-container">
@@ -713,13 +542,11 @@
<th>Avg Cost</th>
<th>Current Price</th>
<th>Market Value</th>
<th>P&L</th>
<th>P&L %</th>
<th>P&amp;L</th>
<th>P&amp;L %</th>
</tr>
</thead>
<tbody id="positionsBody">
<!-- Positions will be inserted here -->
</tbody>
<tbody id="positionsBody"></tbody>
</table>
</div>
</div>
@@ -729,14 +556,13 @@
<script>
let API_KEY = '';
let API_SECRET = '';
let BASE_URL = 'https://paper-api.alpaca.markets'; // Default to paper trading
let BASE_URL = 'https://paper-api.alpaca.markets';
let ACCOUNT_NUMBER = '';
let hideTimeout = null;
let isRevealed = false;
async function login(event) {
event.preventDefault(); // Prevent form submission
event.preventDefault();
const apiKey = document.getElementById('apiKey').value.trim();
const apiSecret = document.getElementById('apiSecret').value.trim();
const errorDiv = document.getElementById('loginError');
@@ -749,8 +575,6 @@
API_KEY = apiKey;
API_SECRET = apiSecret;
// Set BASE_URL based on selected trading mode
BASE_URL = tradingMode === 'paper'
? 'https://paper-api.alpaca.markets'
: 'https://api.alpaca.markets';
@@ -767,7 +591,6 @@
document.getElementById('loginForm').style.display = 'none';
document.getElementById('dashboard').style.display = 'block';
await loadDashboard();
// Auto-refresh every 30 seconds
setInterval(loadDashboard, 30000);
return;
}
@@ -809,7 +632,6 @@
function displayAccount(account) {
ACCOUNT_NUMBER = account.account_number;
// Set initial display with first 3 characters visible
updateAccountDisplay();
const equity = parseFloat(account.equity);
@@ -819,27 +641,10 @@
const plTodayPercent = (plToday / parseFloat(account.last_equity)) * 100;
const stats = [
{
label: 'Portfolio Value',
value: formatCurrency(equity),
class: ''
},
{
label: 'Cash',
value: formatCurrency(cash),
class: ''
},
{
label: 'Buying Power',
value: formatCurrency(buyingPower),
class: ''
},
{
label: 'Today\'s P&L',
value: formatCurrency(plToday),
change: `${plTodayPercent >= 0 ? '+' : ''}${plTodayPercent.toFixed(2)}%`,
class: plToday >= 0 ? 'positive' : 'negative'
}
{ label: 'Portfolio Value', value: formatCurrency(equity), class: '' },
{ label: 'Cash', value: formatCurrency(cash), class: '' },
{ label: 'Buying Power', value: formatCurrency(buyingPower), class: '' },
{ label: "Today's P&L", value: formatCurrency(plToday), change: `${plToday >= 0 ? '+' : ''}${plTodayPercent.toFixed(2)}%`, class: plToday >= 0 ? 'positive' : 'negative' }
];
const statsGrid = document.getElementById('statsGrid');
@@ -853,8 +658,7 @@
}
function getPartialAccountNumber() {
// Show first 3 characters after # and mask the rest
const prefix = ACCOUNT_NUMBER.substring(0, 2); // PA or similar
const prefix = ACCOUNT_NUMBER.substring(0, 2);
const dots = '•'.repeat(ACCOUNT_NUMBER.length - 2);
return `#${prefix}${dots}`;
}
@@ -869,18 +673,15 @@
}
function revealAccountNumber() {
// Clear any pending hide timeout
if (hideTimeout) {
clearTimeout(hideTimeout);
hideTimeout = null;
}
isRevealed = true;
updateAccountDisplay();
}
function hideAccountNumber() {
// Only hide if not clicked (click sets a 5 second timer)
if (!hideTimeout) {
isRevealed = false;
updateAccountDisplay();
@@ -890,30 +691,19 @@
function copyAccountNumber(event) {
event.preventDefault();
const accountElement = document.getElementById('accountNumber');
// Reveal the account number
isRevealed = true;
updateAccountDisplay();
// Clear any existing timeout
if (hideTimeout) {
clearTimeout(hideTimeout);
}
// Copy to clipboard
navigator.clipboard.writeText(`#${ACCOUNT_NUMBER}`).then(() => {
// Add copied class for visual feedback
accountElement.classList.add('copied');
// Remove the copied class after animation completes
setTimeout(() => {
accountElement.classList.remove('copied');
}, 2000);
}).catch(err => {
console.error('Failed to copy:', err);
});
// Set a timeout to hide after 5 seconds
hideTimeout = setTimeout(() => {
isRevealed = false;
updateAccountDisplay();
@@ -942,7 +732,6 @@
const pl = parseFloat(pos.unrealized_pl);
const plPercent = parseFloat(pos.unrealized_plpc) * 100;
const plClass = pl >= 0 ? 'positive' : 'negative';
return `
<tr>
<td class="symbol">${pos.symbol}</td>