투자관리 수정

This commit is contained in:
Macbook
2026-05-31 15:30:14 +09:00
parent d6eedf19bb
commit 1b7c39e11f
9 changed files with 553 additions and 328 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

+8 -12
View File
@@ -297,10 +297,10 @@ const PaperTradingPage: React.FC<Props> = ({
<div className="ptd-invest-metrics"> <div className="ptd-invest-metrics">
<PaperAssetTrendChart refreshKey={dataRefreshKey} /> <PaperAssetTrendChart refreshKey={dataRefreshKey} />
<div className="ptd-metrics-grid"> <div className="ptd-metrics-grid">
<MetricCard icon="📉" title="MDD" value={`${metrics.mddPct.toFixed(2)}%`} sub="최대 낙폭" tone="down" /> <MetricCard label="MDD" value={`${metrics.mddPct.toFixed(2)}%`} tone="down" />
<MetricCard icon="📊" title="Sharpe Ratio" value={metrics.sharpeRatio.toFixed(2)} sub="위험 대비 수익" tone="up" /> <MetricCard label="Sharpe Ratio" value={metrics.sharpeRatio.toFixed(2)} tone="up" />
<MetricCard icon="🏆" title="Win Rate" value={`${metrics.winRatePct.toFixed(0)}%`} sub="승률" tone="up" /> <MetricCard label="Win Rate" value={`${metrics.winRatePct.toFixed(0)}%`} tone="up" />
<MetricCard icon="🛡" title="Profit Factor" value={metrics.profitFactor.toFixed(2)} sub="총이익/총손실" tone="up" /> <MetricCard label="Profit Factor" value={metrics.profitFactor.toFixed(2)} tone="up" />
</div> </div>
</div> </div>
</div> </div>
@@ -375,17 +375,13 @@ const PaperTradingPage: React.FC<Props> = ({
); );
}; };
function MetricCard({ icon, title, value, sub, tone }: { function MetricCard({ label, value, tone }: {
icon: string; title: string; value: string; sub: string; tone: 'up' | 'down'; label: string; value: string; tone: 'up' | 'down';
}) { }) {
return ( return (
<div className={`ptd-metric ptd-metric--${tone}`}> <div className={`ptd-metric ptd-metric--${tone}`}>
<span className="ptd-metric-icon">{icon}</span> <span className="ptd-metric-label">{label}</span>
<div> <span className="ptd-metric-value">{value}</span>
<div className="ptd-metric-title">{title}</div>
<div className="ptd-metric-value">{value}</div>
<div className="ptd-metric-sub">{sub}</div>
</div>
</div> </div>
); );
} }
+7 -7
View File
@@ -1,9 +1,9 @@
/** /**
* 서비스 진입 스플래시 — 글래스모피즘 로그인 * 서비스 진입 스플래시 — 참조 컨셉 로그인
*/ */
import React, { useState } from 'react'; import React, { useState } from 'react';
import { loginUser, type LoginResponse } from '../utils/backendApi'; import { loginUser, type LoginResponse } from '../utils/backendApi';
import SplashGradientBackdrop from './splash/SplashGradientBackdrop'; import SplashConceptBackdrop from './splash/SplashConceptBackdrop';
import '../styles/splashScreen.css'; import '../styles/splashScreen.css';
const DEFAULT_USERNAME = 'admin'; const DEFAULT_USERNAME = 'admin';
@@ -37,10 +37,10 @@ const SplashScreen: React.FC<Props> = ({ onLoginSuccess, onGuest }) => {
return ( return (
<div className="splash"> <div className="splash">
<SplashGradientBackdrop /> <SplashConceptBackdrop />
<div className="splash-center"> <div className="splash-center">
<div className="splash-glass"> <div className="splash-card">
<h1 className="splash-brand">GoldenChart</h1> <h1 className="splash-brand">GoldenChart</h1>
<form className="splash-form" onSubmit={submitLogin}> <form className="splash-form" onSubmit={submitLogin}>
@@ -51,7 +51,7 @@ const SplashScreen: React.FC<Props> = ({ onLoginSuccess, onGuest }) => {
value={username} value={username}
onChange={e => setUsername(e.target.value)} onChange={e => setUsername(e.target.value)}
disabled={loading} disabled={loading}
placeholder="ID" aria-label="아이디"
/> />
<input <input
className="splash-input" className="splash-input"
@@ -60,7 +60,7 @@ const SplashScreen: React.FC<Props> = ({ onLoginSuccess, onGuest }) => {
value={password} value={password}
onChange={e => setPassword(e.target.value)} onChange={e => setPassword(e.target.value)}
disabled={loading} disabled={loading}
placeholder="••••••••" aria-label="비밀번호"
/> />
{error && <p className="splash-error">{error}</p>} {error && <p className="splash-error">{error}</p>}
<button type="submit" className="splash-login-btn" disabled={loading}> <button type="submit" className="splash-login-btn" disabled={loading}>
@@ -69,7 +69,7 @@ const SplashScreen: React.FC<Props> = ({ onLoginSuccess, onGuest }) => {
</form> </form>
<p className="splash-version"> <p className="splash-version">
: {APP_VERSION} | Core Engine: Ta4j &amp; Spring Boot ver. {APP_VERSION} | Core Engine: Ta4j &amp; Spring Boot
</p> </p>
</div> </div>
@@ -2,6 +2,8 @@ import React, { useEffect, useMemo, useState } from 'react';
import { loadPaperDailySnapshots, type PaperDailySnapshotDto } from '../../utils/backendApi'; import { loadPaperDailySnapshots, type PaperDailySnapshotDto } from '../../utils/backendApi';
import { fmtKrw } from '../TradeOrderPanel'; import { fmtKrw } from '../TradeOrderPanel';
const EMPTY_HINT = '일별 스냅샷이 쌓이면 자산 추이가 표시됩니다.';
interface Props { interface Props {
refreshKey?: number; refreshKey?: number;
} }
@@ -34,27 +36,32 @@ const PaperAssetTrendChart: React.FC<Props> = ({ refreshKey = 0 }) => {
return { path: `M ${pts.join(' L ')}`, min: mn, max: mx, last: vals[vals.length - 1] }; return { path: `M ${pts.join(' L ')}`, min: mn, max: mx, last: vals[vals.length - 1] };
}, [snapshots]); }, [snapshots]);
if (snapshots.length < 2) { const hasChart = snapshots.length >= 2;
return (
<div className="ptd-asset-trend ptd-asset-trend--empty">
<span className="ptd-muted"> .</span>
</div>
);
}
return ( return (
<div className="ptd-asset-trend"> <div className="ptd-asset-trend">
<div className="ptd-asset-trend-head"> <div className="ptd-asset-trend-titlebar">
<span> </span> <div className="ptd-asset-trend-titlegroup">
<span className="ptd-asset-trend-last">{fmtKrw(last)}</span> <span className="ptd-asset-trend-title"></span>
</div> {!hasChart && (
<svg viewBox="0 0 280 48" className="ptd-asset-trend-svg" preserveAspectRatio="none"> <span className="ptd-asset-trend-hint">{EMPTY_HINT}</span>
<path d={path} fill="none" stroke="var(--gc-trade-buy, #ef5350)" strokeWidth="1.5" /> )}
</svg> </div>
<div className="ptd-asset-trend-range"> {hasChart && (
<span>{fmtKrw(min)}</span> <span className="ptd-asset-trend-last">{fmtKrw(last)}</span>
<span>{fmtKrw(max)}</span> )}
</div> </div>
{hasChart && (
<div className="ptd-asset-trend-body">
<svg viewBox="0 0 280 48" className="ptd-asset-trend-svg" preserveAspectRatio="none">
<path d={path} fill="none" stroke="var(--gc-trade-buy, #ef5350)" strokeWidth="1.5" />
</svg>
<div className="ptd-asset-trend-range">
<span>{fmtKrw(min)}</span>
<span>{fmtKrw(max)}</span>
</div>
</div>
)}
</div> </div>
); );
}; };
@@ -0,0 +1,65 @@
/**
* 로그인 스플래시 — 참조 컨셉: 은은한 그라데이션 + 흐릿한 대각선 폰 목업
* (이미지 파일 미사용, CSS/SVG만)
*/
import React from 'react';
import SplashPhoneMockup, { type SplashPhoneVariant } from './SplashPhoneMockup';
interface PhonePlacement {
variant: SplashPhoneVariant;
left: string;
top: string;
rotateY: number;
rotateZ: number;
scale: number;
opacity: number;
}
/** 중앙에서 우상단·좌하단으로 퍼지는 목업 배치 */
const PHONES: PhonePlacement[] = [
{ variant: 'chart', left: '4%', top: '68%', rotateY: 22, rotateZ: -14, scale: 0.72, opacity: 0.14 },
{ variant: 'watchlist', left: '11%', top: '54%', rotateY: 18, rotateZ: -10, scale: 0.78, opacity: 0.16 },
{ variant: 'orderbook', left: '18%', top: '40%', rotateY: 14, rotateZ: -6, scale: 0.84, opacity: 0.18 },
{ variant: 'trade', left: '52%', top: '38%', rotateY: -8, rotateZ: 4, scale: 0.88, opacity: 0.2 },
{ variant: 'chart', left: '62%', top: '26%', rotateY: -14, rotateZ: 8, scale: 0.92, opacity: 0.22 },
{ variant: 'watchlist', left: '72%', top: '14%', rotateY: -18, rotateZ: 12, scale: 0.96, opacity: 0.2 },
{ variant: 'orderbook', left: '82%', top: '4%', rotateY: -22, rotateZ: 14, scale: 1, opacity: 0.17 },
{ variant: 'chart', left: '90%', top: '-2%', rotateY: -26, rotateZ: 16, scale: 0.88, opacity: 0.12 },
];
const SplashConceptBackdrop: React.FC = () => (
<div className="splash-backdrop" aria-hidden>
<div className="splash-backdrop__base" />
<div className="splash-backdrop__aurora">
<span className="splash-backdrop__aurora--teal" />
<span className="splash-backdrop__aurora--gold" />
</div>
<div className="splash-backdrop__mesh" />
<div className="splash-backdrop__phones">
<div className="splash-backdrop__phones-blur">
{PHONES.map((p, i) => (
<SplashPhoneMockup
key={`${p.variant}-${i}`}
variant={p.variant}
style={{
left: p.left,
top: p.top,
opacity: p.opacity,
transform: `perspective(900px) rotateY(${p.rotateY}deg) rotateZ(${p.rotateZ}deg) scale(${p.scale})`,
}}
/>
))}
</div>
</div>
<div className="splash-backdrop__flow-glow" />
<div className="splash-backdrop__grain" />
<div className="splash-backdrop__vignette" />
<div className="splash-backdrop__spotlight" />
</div>
);
export default SplashConceptBackdrop;
@@ -0,0 +1,142 @@
import React from 'react';
export type SplashPhoneVariant = 'chart' | 'orderbook' | 'watchlist' | 'trade';
interface Props {
variant: SplashPhoneVariant;
style?: React.CSSProperties;
className?: string;
}
const MiniChart: React.FC = () => (
<svg viewBox="0 0 120 200" className="splash-phone__svg" aria-hidden>
<rect width="120" height="200" fill="#0a0e14" />
{[28, 52, 76, 100, 124, 148, 172].map(y => (
<line key={y} x1="0" y1={y} x2="120" y2={y} stroke="rgba(255,255,255,0.04)" />
))}
{[
{ x: 12, h: 90, l: 118, up: true },
{ x: 22, h: 82, l: 112, up: true },
{ x: 32, h: 95, l: 125, up: false },
{ x: 42, h: 78, l: 108, up: true },
{ x: 52, h: 70, l: 100, up: true },
{ x: 62, h: 88, l: 118, up: false },
{ x: 72, h: 65, l: 95, up: true },
{ x: 82, h: 58, l: 88, up: true },
{ x: 92, h: 72, l: 102, up: true },
{ x: 102, h: 48, l: 78, up: true },
].map((c, i) => (
<g key={i}>
<line x1={c.x} y1={c.h} x2={c.x} y2={c.l} stroke="rgba(255,255,255,0.2)" />
<rect
x={c.x - 4}
y={Math.min(c.h, c.l)}
width={8}
height={Math.abs(c.l - c.h) || 6}
fill={c.up ? 'rgba(34,197,94,0.55)' : 'rgba(239,68,68,0.5)'}
rx="1"
/>
</g>
))}
<path
d="M8,130 Q40,110 70,95 T108,55"
fill="none"
stroke="rgba(250,204,21,0.35)"
strokeWidth="1.2"
/>
</svg>
);
const MiniOrderbook: React.FC = () => (
<svg viewBox="0 0 120 200" className="splash-phone__svg" aria-hidden>
<rect width="120" height="200" fill="#0a0e14" />
{Array.from({ length: 8 }, (_, i) => {
const y = 24 + i * 20;
const askW = 18 + (i % 4) * 8;
const bidW = 22 + ((i + 2) % 4) * 6;
return (
<g key={i}>
<rect x={60 - askW} y={y} width={askW} height={12} fill="rgba(239,68,68,0.35)" rx="2" />
<rect x={60} y={y} width={bidW} height={12} fill="rgba(34,197,94,0.32)" rx="2" />
</g>
);
})}
<rect x="20" y="88" width="80" height="22" fill="rgba(212,175,55,0.12)" rx="4" />
</svg>
);
const MiniWatchlist: React.FC = () => (
<svg viewBox="0 0 120 200" className="splash-phone__svg" aria-hidden>
<rect width="120" height="200" fill="#0a0e14" />
{[0, 1, 2, 3, 4, 5].map(i => (
<g key={i}>
<rect x="10" y={18 + i * 28} width="100" height="22" fill="rgba(255,255,255,0.04)" rx="4" />
<text x="16" y={33 + i * 28} fill="rgba(255,255,255,0.2)" fontSize="7" fontFamily="monospace">
005930
</text>
<text
x="104"
y={33 + i * 28}
fill={i % 2 === 0 ? 'rgba(34,197,94,0.45)' : 'rgba(239,68,68,0.4)'}
fontSize="7"
textAnchor="end"
fontFamily="monospace"
>
{i % 2 === 0 ? '+1.2%' : '-0.4%'}
</text>
</g>
))}
</svg>
);
const MiniTrade: React.FC = () => (
<svg viewBox="0 0 120 200" className="splash-phone__svg" aria-hidden>
<rect width="120" height="200" fill="#0a0e14" />
<path
d="M8,120 Q45,95 75,80 T108,50"
fill="none"
stroke="rgba(34,197,94,0.3)"
strokeWidth="1.2"
/>
{[
{ x: 20, h: 100, l: 118, up: true },
{ x: 40, h: 85, l: 105, up: true },
{ x: 60, h: 92, l: 112, up: false },
{ x: 80, h: 72, l: 95, up: true },
].map((c, i) => (
<rect
key={i}
x={c.x - 3}
y={Math.min(c.h, c.l)}
width={6}
height={Math.abs(c.l - c.h) || 5}
fill={c.up ? 'rgba(34,197,94,0.5)' : 'rgba(239,68,68,0.45)'}
/>
))}
<rect x="10" y="158" width="46" height="28" fill="rgba(34,197,94,0.4)" rx="6" />
<rect x="64" y="158" width="46" height="28" fill="rgba(239,68,68,0.35)" rx="6" />
</svg>
);
const SCREENS: Record<SplashPhoneVariant, React.FC> = {
chart: MiniChart,
orderbook: MiniOrderbook,
watchlist: MiniWatchlist,
trade: MiniTrade,
};
const SplashPhoneMockup: React.FC<Props> = ({ variant, style, className = '' }) => {
const Screen = SCREENS[variant];
return (
<div className={`splash-phone ${className}`.trim()} style={style}>
<div className="splash-phone__frame">
<div className="splash-phone__notch" />
<div className="splash-phone__screen">
<Screen />
</div>
</div>
</div>
);
};
export default SplashPhoneMockup;
+22 -8
View File
@@ -742,7 +742,7 @@
.bps-footer .ptd-metrics-grid { .bps-footer .ptd-metrics-grid {
display: grid; display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px; gap: 8px;
} }
@@ -764,23 +764,37 @@
.se-page .down { color: var(--se-down, #ef4444); } .se-page .down { color: var(--se-down, #ef4444); }
@media (max-width: 1200px) { @media (max-width: 1200px) {
.bps-footer .ptd-metrics-grid { grid-template-columns: repeat(2, 1fr); }
} }
/* legacy layout removed — shell handles page structure */ /* legacy layout removed — shell handles page structure */
.ptd-metric { .ptd-metric {
display: flex; display: flex;
align-items: flex-start; align-items: center;
gap: 8px; justify-content: space-between;
gap: 10px;
padding: 8px 10px; padding: 8px 10px;
border-radius: 10px; border-radius: 10px;
background: var(--bg2); background: var(--bg2);
border: 1px solid var(--border); border: 1px solid var(--border);
min-width: 0;
}
.ptd-metric-label {
flex-shrink: 0;
font-size: 10px;
font-weight: 600;
color: var(--text3);
white-space: nowrap;
}
.ptd-metric-value {
flex: 1;
min-width: 0;
font-size: 17px;
font-weight: 800;
line-height: 1.1;
text-align: right;
font-variant-numeric: tabular-nums;
color: var(--text);
} }
.ptd-metric-icon { font-size: 16px; line-height: 1; }
.ptd-metric-title { font-size: 10px; color: var(--text3); margin-bottom: 2px; }
.ptd-metric-value { font-size: 17px; font-weight: 800; line-height: 1.1; color: var(--text); }
.ptd-metric-sub { font-size: 9px; color: var(--text3); margin-top: 2px; }
.ptd-metric--up .ptd-metric-value { color: #22c55e; } .ptd-metric--up .ptd-metric-value { color: #22c55e; }
.ptd-metric--down .ptd-metric-value { color: #ef4444; } .ptd-metric--down .ptd-metric-value { color: #ef4444; }
+174 -276
View File
@@ -1,4 +1,4 @@
/* GoldenChart splash — 프리미엄 흐림 그라데이션 + 글래스 로그인 */ /* GoldenChart splash — 은은한 그라데이션 + 흐릿한 폰 목업 배경 */
.splash { .splash {
position: fixed; position: fixed;
@@ -7,13 +7,13 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: #05070c; background: #06080e;
color: #e8eaed; color: #e8eaed;
overflow: hidden; overflow: hidden;
font-family: var(--font, 'Noto Sans KR', 'Inter', sans-serif); font-family: var(--font, 'Noto Sans KR', 'Inter', sans-serif);
} }
/* ── 배경 레이어 ─────────────────────────────────────────────────────────── */ /* ── 배경 ─────────────────────────────────────────────────────────────────── */
.splash-backdrop { .splash-backdrop {
position: absolute; position: absolute;
@@ -26,237 +26,149 @@
.splash-backdrop__base { .splash-backdrop__base {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: background: linear-gradient(160deg, #05070c 0%, #0a0e16 42%, #070a12 100%);
linear-gradient(155deg, #06080f 0%, #0a0e18 38%, #070a12 68%, #05070c 100%); }
.splash-backdrop__aurora {
position: absolute;
inset: 0;
}
.splash-backdrop__aurora--teal,
.splash-backdrop__aurora--gold {
position: absolute;
border-radius: 50%;
filter: blur(80px);
}
.splash-backdrop__aurora--teal {
width: min(58vw, 520px);
height: min(58vw, 520px);
left: 8%;
top: 38%;
background: radial-gradient(circle, rgba(34, 197, 94, 0.14) 0%, rgba(45, 212, 191, 0.05) 45%, transparent 70%);
}
.splash-backdrop__aurora--gold {
width: min(50vw, 460px);
height: min(50vw, 460px);
right: 6%;
top: 8%;
background: radial-gradient(circle, rgba(249, 226, 156, 0.18) 0%, rgba(212, 175, 55, 0.08) 42%, transparent 72%);
} }
.splash-backdrop__mesh { .splash-backdrop__mesh {
position: absolute; position: absolute;
inset: -20%; inset: -15%;
opacity: 0.45; opacity: 0.35;
background: background: conic-gradient(
conic-gradient( from 42deg at 55% 40%,
from 38deg at 58% 42%, rgba(212, 175, 55, 0.1) 0deg,
rgba(212, 175, 55, 0.12) 0deg, transparent 55deg,
transparent 48deg, rgba(34, 197, 94, 0.06) 110deg,
rgba(34, 197, 94, 0.07) 95deg, transparent 200deg,
transparent 165deg, rgba(56, 189, 248, 0.04) 280deg,
rgba(56, 189, 248, 0.05) 240deg, transparent 360deg
transparent 360deg );
filter: blur(48px);
}
/* 대각선 폰 목업 — 중앙→우상단 그라데이션 마스크 */
.splash-backdrop__phones {
position: absolute;
inset: 0;
perspective: 1100px;
mask-image:
radial-gradient(ellipse 42% 38% at 50% 48%, transparent 0%, rgba(0, 0, 0, 0.15) 42%, #000 58%),
linear-gradient(
125deg,
rgba(0, 0, 0, 0.25) 0%,
#000 18%,
#000 42%,
rgba(0, 0, 0, 0.45) 58%,
transparent 78%
);
-webkit-mask-image:
radial-gradient(ellipse 42% 38% at 50% 48%, transparent 0%, rgba(0, 0, 0, 0.15) 42%, #000 58%),
linear-gradient(
125deg,
rgba(0, 0, 0, 0.25) 0%,
#000 18%,
#000 42%,
rgba(0, 0, 0, 0.45) 58%,
transparent 78%
); );
filter: blur(52px);
} }
.splash-backdrop__orbs { .splash-backdrop__phones-blur {
position: absolute; position: absolute;
inset: 0; inset: -5%;
filter: blur(10px) saturate(0.75) brightness(0.82);
} }
.splash-backdrop__orb { .splash-phone {
position: absolute; position: absolute;
border-radius: 50%; width: clamp(88px, 10vw, 148px);
filter: blur(72px); aspect-ratio: 9 / 19.5;
transform-origin: center center;
will-change: transform; will-change: transform;
animation: splash-orb-float 22s ease-in-out infinite;
} }
.splash-backdrop__orb--gold { .splash-phone__frame {
width: min(52vw, 480px); height: 100%;
height: min(52vw, 480px); padding: 5px;
top: -6%; border-radius: clamp(16px, 2.2vw, 24px);
right: 2%; border: 1px solid rgba(255, 255, 255, 0.1);
background: radial-gradient(circle, rgba(249, 226, 156, 0.32) 0%, rgba(212, 175, 55, 0.12) 45%, transparent 72%); background: rgba(14, 18, 28, 0.42);
animation-delay: 0s; box-shadow:
0 16px 48px rgba(0, 0, 0, 0.45),
0 0 0 1px rgba(255, 255, 255, 0.04) inset;
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
} }
.splash-backdrop__orb--teal { .splash-phone__notch {
width: min(44vw, 400px); width: 36%;
height: min(44vw, 400px); height: 5px;
top: 18%; margin: 2px auto 4px;
right: -6%; border-radius: 4px;
background: radial-gradient(circle, rgba(34, 197, 94, 0.16) 0%, rgba(45, 212, 191, 0.06) 50%, transparent 70%); background: rgba(0, 0, 0, 0.35);
animation-delay: -8s;
animation-duration: 24s;
} }
.splash-backdrop__orb--indigo { .splash-phone__screen {
width: min(38vw, 340px); height: calc(100% - 14px);
height: min(38vw, 340px); border-radius: clamp(12px, 1.6vw, 18px);
bottom: -10%;
left: -8%;
background: radial-gradient(circle, rgba(99, 102, 241, 0.14) 0%, transparent 68%);
animation-delay: -14s;
animation-duration: 26s;
}
@keyframes splash-orb-float {
0%, 100% {
transform: translate(0, 0) scale(1);
}
33% {
transform: translate(1.5%, -1%) scale(1.03);
}
66% {
transform: translate(-1%, 1.5%) scale(0.98);
}
}
/* ── 중앙 → 우측 상단: 캔들·호가 실루엣 ───────────────────────────────────── */
.splash-backdrop__market-flow {
position: absolute;
inset: 0;
overflow: hidden; overflow: hidden;
} }
.splash-phone__svg {
display: block;
width: 100%;
height: 100%;
}
.splash-backdrop__flow-glow { .splash-backdrop__flow-glow {
position: absolute; position: absolute;
left: 42%; left: 38%;
top: 38%; top: 32%;
width: min(95vw, 920px); width: min(88vw, 840px);
height: min(72vh, 640px); height: min(68vh, 580px);
transform: translate(-18%, -22%); transform: translate(-12%, -8%);
background: linear-gradient( background: linear-gradient(
128deg, 128deg,
rgba(34, 197, 94, 0.07) 0%, rgba(34, 197, 94, 0.06) 0%,
rgba(212, 175, 55, 0.1) 32%, rgba(212, 175, 55, 0.09) 35%,
rgba(56, 189, 248, 0.05) 58%, rgba(249, 226, 156, 0.05) 55%,
transparent 78% transparent 75%
); );
filter: blur(56px); filter: blur(64px);
opacity: 0.9;
pointer-events: none; pointer-events: none;
} }
.splash-backdrop__flow-inner {
position: absolute;
left: 50%;
top: 50%;
width: min(128vw, 1680px);
height: min(96vh, 920px);
transform: translate(-42%, -48%);
transform-origin: center center;
pointer-events: none;
/* 중앙(로그인 카드 부근)에서 우측 상단으로 갈수록 서서히 사라짐 */
mask-image: linear-gradient(
125deg,
#000 0%,
#000 14%,
rgba(0, 0, 0, 0.62) 32%,
rgba(0, 0, 0, 0.28) 48%,
rgba(0, 0, 0, 0.08) 58%,
transparent 72%
);
-webkit-mask-image: linear-gradient(
125deg,
#000 0%,
#000 14%,
rgba(0, 0, 0, 0.62) 32%,
rgba(0, 0, 0, 0.28) 48%,
rgba(0, 0, 0, 0.08) 58%,
transparent 72%
);
}
.splash-backdrop__chart-layer {
position: absolute;
inset: 0;
opacity: 0.38;
filter: blur(22px) saturate(0.72) brightness(0.78);
}
.splash-backdrop__orderbook-layer {
position: absolute;
right: 2%;
top: 6%;
width: min(36vw, 340px);
height: min(52vh, 480px);
opacity: 0.32;
filter: blur(14px) saturate(0.65);
}
.splash-chart-bg {
display: block;
width: 100%;
height: 100%;
}
.splash-orderbook-bg {
display: block;
width: 100%;
height: 100%;
}
.splash-ob-mid {
stroke: rgba(255, 255, 255, 0.08);
stroke-width: 1;
stroke-dasharray: 4 6;
}
.splash-ob-ask-bar {
fill: rgba(239, 68, 68, 0.35);
}
.splash-ob-bid-bar {
fill: rgba(34, 197, 94, 0.32);
}
.splash-ob-ask-txt,
.splash-ob-bid-txt {
font-size: 9px;
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
fill: rgba(255, 255, 255, 0.12);
}
.splash-ob-spread {
fill: rgba(212, 175, 55, 0.08);
stroke: rgba(212, 175, 55, 0.15);
stroke-width: 1;
}
.splash-chart-grid {
stroke: rgba(255, 255, 255, 0.04);
stroke-width: 1;
}
.splash-vol-bar {
fill: rgba(255, 255, 255, 0.06);
}
.splash-ichi--tenkan { stroke: rgba(250, 204, 21, 0.35); stroke-width: 1.2; fill: none; }
.splash-ichi--kijun { stroke: rgba(96, 165, 250, 0.3); stroke-width: 1.2; fill: none; }
.splash-ichi--senkou-a { stroke: rgba(34, 197, 94, 0.22); stroke-width: 1; fill: none; }
.splash-ichi--senkou-b { stroke: rgba(239, 68, 68, 0.2); stroke-width: 1; fill: none; }
.splash-candle-wick {
stroke: rgba(255, 255, 255, 0.2);
stroke-width: 1;
}
.splash-candle-up {
fill: rgba(34, 197, 94, 0.42);
}
.splash-candle-down {
fill: rgba(239, 68, 68, 0.38);
}
.splash-backdrop__grid {
position: absolute;
inset: 0;
opacity: 0.35;
background-image:
linear-gradient(rgba(255, 255, 255, 0.028) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.028) 1px, transparent 1px);
background-size: 56px 56px;
mask-image: radial-gradient(ellipse 90% 80% at 50% 50%, #000 20%, transparent 85%);
-webkit-mask-image: radial-gradient(ellipse 90% 80% at 50% 50%, #000 20%, transparent 85%);
}
.splash-backdrop__grain { .splash-backdrop__grain {
position: absolute; position: absolute;
inset: 0; inset: 0;
opacity: 0.045; opacity: 0.04;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
background-size: 180px 180px; background-size: 180px 180px;
} }
@@ -265,27 +177,20 @@
position: absolute; position: absolute;
inset: 0; inset: 0;
background: background:
radial-gradient(ellipse 68% 58% at 50% 50%, transparent 0%, rgba(5, 7, 12, 0.42) 62%, rgba(2, 3, 6, 0.9) 100%), radial-gradient(ellipse 70% 62% at 50% 50%, transparent 0%, rgba(5, 7, 12, 0.5) 65%, rgba(2, 3, 6, 0.92) 100%),
linear-gradient(128deg, transparent 0%, rgba(5, 7, 12, 0.15) 55%, rgba(5, 7, 12, 0.35) 100%), linear-gradient(128deg, transparent 0%, rgba(5, 7, 12, 0.2) 50%, rgba(5, 7, 12, 0.35) 100%);
linear-gradient(180deg, rgba(5, 7, 12, 0.45) 0%, transparent 30%, transparent 70%, rgba(5, 7, 12, 0.5) 100%);
} }
.splash-backdrop__spotlight { .splash-backdrop__spotlight {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: radial-gradient( background: radial-gradient(
ellipse 42% 38% at 50% 46%, ellipse 40% 36% at 50% 46%,
rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.05) 0%,
transparent 100% transparent 100%
); );
} }
@media (prefers-reduced-motion: reduce) {
.splash-backdrop__orb {
animation: none;
}
}
/* ── 로그인 카드 ───────────────────────────────────────────────────────────── */ /* ── 로그인 카드 ───────────────────────────────────────────────────────────── */
.splash-center { .splash-center {
@@ -294,37 +199,35 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 18px; gap: 16px;
width: min(92vw, 380px); width: min(92vw, 400px);
padding: 24px 16px; padding: 20px 16px;
} }
.splash-glass { .splash-card {
width: 100%; width: 100%;
padding: 36px 32px 28px; padding: 38px 36px 30px;
border-radius: 20px; border-radius: 18px;
border: 1px solid rgba(255, 255, 255, 0.12); border: 1px solid rgba(212, 175, 55, 0.55);
background: rgba(10, 12, 18, 0.52); background: rgba(11, 14, 22, 0.97);
backdrop-filter: blur(24px) saturate(1.15);
-webkit-backdrop-filter: blur(24px) saturate(1.15);
box-shadow: box-shadow:
0 0 0 1px rgba(212, 175, 55, 0.06) inset, 0 0 0 1px rgba(212, 175, 55, 0.08) inset,
0 28px 72px rgba(0, 0, 0, 0.55), 0 0 40px rgba(212, 175, 55, 0.1),
0 8px 24px rgba(0, 0, 0, 0.35); 0 24px 64px rgba(0, 0, 0, 0.65);
} }
.splash-brand { .splash-brand {
margin: 0 0 28px; margin: 0 0 30px;
text-align: center; text-align: center;
font-size: 2rem; font-size: 2.15rem;
font-weight: 800; font-weight: 800;
letter-spacing: -0.02em; letter-spacing: -0.02em;
line-height: 1.1; line-height: 1.1;
background: linear-gradient(180deg, #fce9a8 0%, #e8c547 38%, #c9a227 72%, #a67c00 100%); background: linear-gradient(180deg, #fce9a8 0%, #e8c547 40%, #c9a227 78%, #a67c00 100%);
-webkit-background-clip: text; -webkit-background-clip: text;
background-clip: text; background-clip: text;
color: transparent; color: transparent;
filter: drop-shadow(0 2px 16px rgba(212, 175, 55, 0.28)); filter: drop-shadow(0 2px 12px rgba(212, 175, 55, 0.35));
} }
.splash-form { .splash-form {
@@ -336,24 +239,19 @@
.splash-input { .splash-input {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
padding: 13px 14px; padding: 14px 16px;
border-radius: 10px; border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(0, 0, 0, 0.38); background: rgba(22, 26, 36, 0.95);
color: #f3f4f6; color: #f3f4f6;
font-size: 0.95rem; font-size: 0.95rem;
transition: border-color 0.15s, box-shadow 0.15s, background 0.15s; transition: border-color 0.15s, box-shadow 0.15s;
}
.splash-input::placeholder {
color: rgba(255, 255, 255, 0.32);
} }
.splash-input:focus { .splash-input:focus {
outline: none; outline: none;
border-color: rgba(212, 175, 55, 0.5); border-color: rgba(212, 175, 55, 0.45);
background: rgba(0, 0, 0, 0.48); box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.12);
box-shadow: 0 0 0 3px rgba(212, 175, 55, 0.14);
} }
.splash-input:disabled { .splash-input:disabled {
@@ -369,26 +267,23 @@
.splash-login-btn { .splash-login-btn {
width: 100%; width: 100%;
margin-top: 4px; margin-top: 6px;
padding: 13px 16px; padding: 14px 16px;
border: none; border: none;
border-radius: 10px; border-radius: 10px;
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 1.05rem;
font-weight: 800; font-weight: 800;
color: #1a1408; color: #141008;
background: linear-gradient(180deg, #fce9a8 0%, #e0c04a 42%, #c9a227 100%); background: linear-gradient(90deg, #f5e6a8 0%, #e8c84a 48%, #d4af37 100%);
box-shadow: box-shadow:
0 1px 0 rgba(255, 255, 255, 0.25) inset, 0 1px 0 rgba(255, 255, 255, 0.3) inset,
0 6px 22px rgba(201, 162, 39, 0.38); 0 6px 24px rgba(201, 162, 39, 0.4);
transition: transform 0.12s, filter 0.12s, box-shadow 0.12s; transition: transform 0.12s, filter 0.12s, box-shadow 0.12s;
} }
.splash-login-btn:hover:not(:disabled) { .splash-login-btn:hover:not(:disabled) {
filter: brightness(1.06); filter: brightness(1.05);
box-shadow:
0 1px 0 rgba(255, 255, 255, 0.3) inset,
0 8px 28px rgba(201, 162, 39, 0.48);
} }
.splash-login-btn:active:not(:disabled) { .splash-login-btn:active:not(:disabled) {
@@ -401,17 +296,16 @@
} }
.splash-version { .splash-version {
margin: 22px 0 0; margin: 24px 0 0;
text-align: center; text-align: center;
font-size: 0.68rem; font-size: 0.7rem;
color: rgba(255, 255, 255, 0.34); color: rgba(255, 255, 255, 0.38);
letter-spacing: 0.01em;
} }
.splash-guest-link { .splash-guest-link {
border: none; border: none;
background: transparent; background: transparent;
color: rgba(255, 255, 255, 0.4); color: rgba(255, 255, 255, 0.38);
font-size: 0.78rem; font-size: 0.78rem;
cursor: pointer; cursor: pointer;
padding: 4px 8px; padding: 4px 8px;
@@ -427,28 +321,32 @@
cursor: not-allowed; cursor: not-allowed;
} }
@media (max-width: 420px) { @media (max-width: 480px) {
.splash-glass { .splash-card {
padding: 28px 22px 22px; padding: 30px 24px 24px;
border-radius: 16px;
} }
.splash-brand { .splash-brand {
font-size: 1.65rem; font-size: 1.75rem;
margin-bottom: 22px; margin-bottom: 24px;
} }
.splash-backdrop__orb {
filter: blur(56px); .splash-phone {
width: clamp(72px, 18vw, 110px);
} }
.splash-backdrop__flow-inner {
mask-image: radial-gradient( .splash-backdrop__phones-blur {
ellipse 90% 85% at 24% 56%, filter: blur(12px) saturate(0.7) brightness(0.78);
#000 0%, }
transparent 75%
); .splash-backdrop__aurora--teal,
-webkit-mask-image: radial-gradient( .splash-backdrop__aurora--gold {
ellipse 90% 85% at 24% 56%, filter: blur(64px);
#000 0%, }
transparent 75% }
);
@media (prefers-reduced-motion: reduce) {
.splash-phone {
will-change: auto;
} }
} }
+111 -8
View File
@@ -2834,6 +2834,54 @@
padding-top: 8px; padding-top: 8px;
border-top: 1px solid var(--se-border); border-top: 1px solid var(--se-border);
} }
.ptd-invest-metrics .ptd-metrics-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
.ptd-invest-metrics .ptd-metric {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 10px 12px;
border-radius: 6px;
background: var(--gc-surface-2, rgba(255, 255, 255, 0.04));
border: 1px solid var(--se-border, var(--border));
min-width: 0;
}
.ptd-invest-metrics .ptd-metric-label {
flex-shrink: 0;
font-size: 11px;
font-weight: 600;
color: var(--text3);
white-space: nowrap;
}
.ptd-invest-metrics .ptd-metric-value {
flex: 1;
min-width: 0;
font-size: 15px;
font-weight: 800;
text-align: right;
font-variant-numeric: tabular-nums;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--text);
}
.ptd-invest-metrics .ptd-metric--up .ptd-metric-value {
color: #22c55e;
}
.ptd-invest-metrics .ptd-metric--down .ptd-metric-value {
color: #ef4444;
}
.bps-page--ptd .ptd-right-history { .bps-page--ptd .ptd-right-history {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
@@ -2920,24 +2968,79 @@
gap: 10px; gap: 10px;
} }
.ptd-asset-trend { .ptd-asset-trend {
padding: 8px 12px;
border-radius: 6px;
background: var(--gc-surface-2, rgba(255,255,255,0.04));
}
.ptd-asset-trend-head {
display: flex; display: flex;
justify-content: space-between; flex-direction: column;
font-size: 12px; border-radius: 6px;
margin-bottom: 4px; background: var(--gc-surface-2, rgba(255, 255, 255, 0.04));
border: 1px solid var(--se-border, var(--border));
overflow: hidden;
min-width: 0;
} }
.ptd-asset-trend-titlebar {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
min-height: 24px;
padding: 3px 10px;
border-bottom: 1px solid color-mix(in srgb, var(--se-border, var(--border)) 65%, transparent);
}
.ptd-asset-trend-titlegroup {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
min-width: 0;
flex: 1;
}
.ptd-asset-trend-title {
flex-shrink: 0;
font-size: 14px;
font-weight: 700;
color: #ff9800;
letter-spacing: 0.04em;
line-height: 1.2;
}
.ptd-asset-trend-hint {
font-size: 11px;
font-weight: 400;
color: var(--text3);
opacity: 0.78;
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ptd-asset-trend-body {
padding: 8px 12px 10px;
min-width: 0;
}
.ptd-asset-trend-last {
flex-shrink: 0;
font-size: 11px;
font-weight: 600;
color: var(--text2);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.ptd-asset-trend-svg { .ptd-asset-trend-svg {
width: 100%; width: 100%;
height: 48px; height: 48px;
display: block; display: block;
} }
.ptd-asset-trend-range { .ptd-asset-trend-range {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
font-size: 10px; font-size: 10px;
opacity: 0.6; opacity: 0.6;
margin-top: 2px;
} }