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