알림팝업 전체닫기 오류 수정

This commit is contained in:
Macbook
2026-05-23 21:25:46 +09:00
parent 70ac67afe7
commit 4df8279a31
11 changed files with 290 additions and 170 deletions
+27 -24
View File
@@ -29,7 +29,8 @@
} }
/* ── Dark Theme: 도쿄 나이트 (THEME_SETTINGS.md §2.2) ───────────── */ /* ── Dark Theme: 도쿄 나이트 (THEME_SETTINGS.md §2.2) ───────────── */
.app.dark { .app.dark,
html.theme-dark {
--bg: #1a1b26; --bg: #1a1b26;
--bg1: #1a1b26; --bg1: #1a1b26;
--bg2: #24283b; --bg2: #24283b;
@@ -48,7 +49,8 @@
} }
/* ── Light Theme (THEME_SETTINGS.md §2.1) ──────────────────────── */ /* ── Light Theme (THEME_SETTINGS.md §2.1) ──────────────────────── */
.app.light { .app.light,
html.theme-light {
--bg: #f5f5f5; --bg: #f5f5f5;
--bg1: #f5f5f5; --bg1: #f5f5f5;
--bg2: #ffffff; --bg2: #ffffff;
@@ -67,7 +69,8 @@
} }
/* ── Blue Theme: 딥 네이비 (THEME_SETTINGS.md §2.3) ─────────────── */ /* ── Blue Theme: 딥 네이비 (THEME_SETTINGS.md §2.3) ─────────────── */
.app.blue { .app.blue,
html.theme-blue {
--bg: #0c1929; --bg: #0c1929;
--bg1: #0c1929; --bg1: #0c1929;
--bg2: #15273d; --bg2: #15273d;
@@ -9412,32 +9415,32 @@
} }
/* ── 라이트 테마 오버라이드 ── */ /* ── 라이트 테마 오버라이드 ── */
.app.light .tam-modal { html.theme-light .tam-modal {
background: #ffffff; background: #ffffff;
border-color: rgba(25,118,210,0.35); border-color: rgba(25,118,210,0.35);
color: #212121; color: #212121;
box-shadow: 0 20px 60px rgba(0,0,0,0.18), 0 0 40px rgba(25,118,210,0.08); box-shadow: 0 20px 60px rgba(0,0,0,0.18), 0 0 40px rgba(25,118,210,0.08);
} }
.app.light .tam-header { background: #f5f7fa; border-color: rgba(0,0,0,0.1); } html.theme-light .tam-header { background: #f5f7fa; border-color: rgba(0,0,0,0.1); }
.app.light .tam-header-title { color: #1a237e; } html.theme-light .tam-header-title { color: #1a237e; }
.app.light .tam-header-ko { color: #546e7a; } html.theme-light .tam-header-ko { color: #546e7a; }
.app.light .tam-close { color: #78909c; } html.theme-light .tam-close { color: #78909c; }
.app.light .tam-close:hover { background: rgba(0,0,0,0.06); color: #37474f; } html.theme-light .tam-close:hover { background: rgba(0,0,0,0.06); color: #37474f; }
.app.light .tam-price-section { border-color: rgba(0,0,0,0.08); } html.theme-light .tam-price-section { border-color: rgba(0,0,0,0.08); }
.app.light .tam-market-name { color: #212121; } html.theme-light .tam-market-name { color: #212121; }
.app.light .tam-price-time { color: #78909c; } html.theme-light .tam-price-time { color: #78909c; }
.app.light .tam-strategy-card { background: #f5f7fa; border-color: rgba(0,0,0,0.1); } html.theme-light .tam-strategy-card { background: #f5f7fa; border-color: rgba(0,0,0,0.1); }
.app.light .tam-strategy-label { color: #78909c; } html.theme-light .tam-strategy-label { color: #78909c; }
.app.light .tam-strategy-val { color: #212121; } html.theme-light .tam-strategy-val { color: #212121; }
.app.light .tam-strategy-exec-val { color: #546e7a; } html.theme-light .tam-strategy-exec-val { color: #546e7a; }
.app.light .tam-field-label { color: #546e7a; } html.theme-light .tam-field-label { color: #546e7a; }
.app.light .tam-pct-btn2 { background: #f0f3f8; border-color: rgba(0,0,0,0.12); color: #546e7a; } html.theme-light .tam-pct-btn2 { background: #f0f3f8; border-color: rgba(0,0,0,0.12); color: #546e7a; }
.app.light .tam-pct-btn2:hover { background: #e3eaf6; } html.theme-light .tam-pct-btn2:hover { background: #e3eaf6; }
.app.light .tam-price-input, html.theme-light .tam-price-input,
.app.light .tam-memo-input { background: #f5f7fa; border-color: rgba(0,0,0,0.14); color: #212121; } html.theme-light .tam-memo-input { background: #f5f7fa; border-color: rgba(0,0,0,0.14); color: #212121; }
.app.light .tam-market-btn { background: #f0f3f8; border-color: rgba(0,0,0,0.12); color: #546e7a; } html.theme-light .tam-market-btn { background: #f0f3f8; border-color: rgba(0,0,0,0.12); color: #546e7a; }
.app.light .tam-market-btn:hover { background: #e3eaf6; color: #212121; } html.theme-light .tam-market-btn:hover { background: #e3eaf6; color: #212121; }
.app.light .tam-disclaimer { color: #90a4ae; } html.theme-light .tam-disclaimer { color: #90a4ae; }
/* /*
Trade signal notifications (tsn- / tnl- / tmb-notify-) Trade signal notifications (tsn- / tnl- / tmb-notify-)
+7
View File
@@ -88,6 +88,7 @@ import LoginModal from './components/LoginModal';
import SplashScreen from './components/SplashScreen'; import SplashScreen from './components/SplashScreen';
import { getAuthSession, setAuthSession, clearAuthSession, type AuthSession } from './utils/auth'; import { getAuthSession, setAuthSession, clearAuthSession, type AuthSession } from './utils/auth';
import { isAppEntered, setAppEntered, clearAppEntered } from './utils/appEntry'; import { isAppEntered, setAppEntered, clearAppEntered } from './utils/appEntry';
import { syncDocumentTheme } from './utils/documentTheme';
import { useMenuPermissions, invalidateMenuPermissionsCache } from './hooks/useMenuPermissions'; import { useMenuPermissions, invalidateMenuPermissionsCache } from './hooks/useMenuPermissions';
import { firstAllowedTopMenu, normalizeRole } from './utils/permissions'; import { firstAllowedTopMenu, normalizeRole } from './utils/permissions';
import { clearAdminUnlock } from './utils/adminUnlock'; import { clearAdminUnlock } from './utils/adminUnlock';
@@ -991,6 +992,11 @@ function App() {
saveState({ symbol, timeframe, chartType, theme, indicators, watchlist, alertPrices: alerts.map(a => a.price) }); saveState({ symbol, timeframe, chartType, theme, indicators, watchlist, alertPrices: alerts.map(a => a.price) });
}, [symbol, timeframe, chartType, theme, indicators, watchlist, alerts]); }, [symbol, timeframe, chartType, theme, indicators, watchlist, alerts]);
// body 포털(알림 팝업 등) 테마 CSS 변수 동기화
useEffect(() => {
syncDocumentTheme(theme);
}, [theme]);
// ── DB 워크스페이스 동기화 ───────────────────────────────────────────── // ── DB 워크스페이스 동기화 ─────────────────────────────────────────────
const workspaceState = useMemo(() => ({ const workspaceState = useMemo(() => ({
layoutId: layoutDef.id, layoutId: layoutDef.id,
@@ -1483,6 +1489,7 @@ function App() {
{menuPage === 'paper' && ( {menuPage === 'paper' && (
<PaperTradingPage <PaperTradingPage
theme={theme}
tickers={marketTickers} tickers={marketTickers}
refreshKey={paperRefreshKey} refreshKey={paperRefreshKey}
defaultMarket={symbol} defaultMarket={symbol}
+4 -1
View File
@@ -17,6 +17,7 @@ import type { TradeOrderFillRequest } from '../types';
import PaperMiniChart from './paper/PaperMiniChart'; import PaperMiniChart from './paper/PaperMiniChart';
import PaperIndicatorPanel from './paper/PaperIndicatorPanel'; import PaperIndicatorPanel from './paper/PaperIndicatorPanel';
import PaperCompactOrderbook from './paper/PaperCompactOrderbook'; import PaperCompactOrderbook from './paper/PaperCompactOrderbook';
import type { Theme } from '../types';
const COIN_COLORS: Record<string, string> = { const COIN_COLORS: Record<string, string> = {
BTC: '#f7931a', ETH: '#627eea', XRP: '#23292f', SOL: '#9945ff', BTC: '#f7931a', ETH: '#627eea', XRP: '#23292f', SOL: '#9945ff',
@@ -28,6 +29,7 @@ type HistoryTab = 'open' | 'recent';
type RightTab = 'trade' | 'orderbook'; type RightTab = 'trade' | 'orderbook';
interface Props { interface Props {
theme?: Theme;
tickers?: Map<string, { tradePrice: number | null }>; tickers?: Map<string, { tradePrice: number | null }>;
onGoChart?: (market: string) => void; onGoChart?: (market: string) => void;
refreshKey?: number; refreshKey?: number;
@@ -66,6 +68,7 @@ function coinIcon(code: string): string {
} }
const PaperTradingPage: React.FC<Props> = ({ const PaperTradingPage: React.FC<Props> = ({
theme = 'dark',
tickers, tickers,
refreshKey = 0, refreshKey = 0,
defaultMarket = 'KRW-BTC', defaultMarket = 'KRW-BTC',
@@ -264,7 +267,7 @@ const PaperTradingPage: React.FC<Props> = ({
<main className="ptd-col ptd-col--center"> <main className="ptd-col ptd-col--center">
<div className="ptd-card ptd-chart-card"> <div className="ptd-card ptd-chart-card">
<PaperMiniChart market={selectedMarket} /> <PaperMiniChart market={selectedMarket} theme={theme} />
<PaperIndicatorPanel market={selectedMarket} /> <PaperIndicatorPanel market={selectedMarket} />
</div> </div>
</main> </main>
@@ -93,8 +93,17 @@ export const TradeSignalSnackbar: React.FC<Props> = ({
setPageIndex(p => Math.min(totalPages - 1, p + 1)); setPageIndex(p => Math.min(totalPages - 1, p + 1));
}, [totalPages]); }, [totalPages]);
const dismissAll = useCallback(() => {
dismissAllToasts();
setPageIndex(0);
positionsRef.current = {};
}, [dismissAllToasts]);
const dismissCurrentPage = useCallback(() => { const dismissCurrentPage = useCallback(() => {
dismissToasts(pageItems.map(i => i.id)); dismissToasts(pageItems.map(i => i.id));
positionsRef.current = Object.fromEntries(
Object.entries(positionsRef.current).filter(([id]) => !pageItems.some(p => p.id === id)),
);
}, [dismissToasts, pageItems]); }, [dismissToasts, pageItems]);
const handleDragStart = useCallback((e: React.PointerEvent, key: string) => { const handleDragStart = useCallback((e: React.PointerEvent, key: string) => {
@@ -219,7 +228,7 @@ export const TradeSignalSnackbar: React.FC<Props> = ({
<button <button
type="button" type="button"
className="tsn-dismiss-all" className="tsn-dismiss-all"
onClick={dismissAllToasts} onClick={dismissAll}
title={`대기 중인 알림 ${toastNotifications.length}건 전체 닫기`} title={`대기 중인 알림 ${toastNotifications.length}건 전체 닫기`}
> >
({toastNotifications.length}) ({toastNotifications.length})
@@ -99,10 +99,10 @@ const BacktestAssetChart: React.FC<Props> = memo(({ curve, markers }) => {
{[0.25, 0.5, 0.75].map(r => { {[0.25, 0.5, 0.75].map(r => {
const y = pad.t + innerH * (1 - r); const y = pad.t + innerH * (1 - r);
return <line key={r} x1={pad.l} y1={y} x2={W - pad.r} y2={y} stroke="rgba(201,162,39,0.07)" strokeWidth="1" />; return <line key={r} x1={pad.l} y1={y} x2={W - pad.r} y2={y} className="btd-chart-grid" />;
})} })}
<text x={pad.l - 8} y={pad.t - 6} textAnchor="start" fill="#6272a4" fontSize="8"></text> <text x={pad.l - 8} y={pad.t - 6} textAnchor="start" className="btd-chart-label" fontSize="8"></text>
{chart.yTicks.map(v => ( {chart.yTicks.map(v => (
<text <text
@@ -111,7 +111,7 @@ const BacktestAssetChart: React.FC<Props> = memo(({ curve, markers }) => {
y={chart.toY(v)} y={chart.toY(v)}
textAnchor="end" textAnchor="end"
dominantBaseline="middle" dominantBaseline="middle"
fill="#6272a4" className="btd-chart-label"
fontSize="8.5" fontSize="8.5"
> >
{fmtY(v)} {fmtY(v)}
@@ -124,7 +124,7 @@ const BacktestAssetChart: React.FC<Props> = memo(({ curve, markers }) => {
key={i} key={i}
d={seg.d} d={seg.d}
fill="none" fill="none"
stroke={seg.up ? '#22c55e' : '#ef4444'} className={seg.up ? 'btd-chart-line-up' : 'btd-chart-line-down'}
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
@@ -152,7 +152,7 @@ const BacktestAssetChart: React.FC<Props> = memo(({ curve, markers }) => {
))} ))}
{chart.xLabels.map((l, i) => ( {chart.xLabels.map((l, i) => (
<text key={i} x={l.x} y={H - 5} textAnchor="middle" fill="#6272a4" fontSize="8"> <text key={i} x={l.x} y={H - 5} textAnchor="middle" className="btd-chart-label" fontSize="8">
{l.label} {l.label}
</text> </text>
))} ))}
@@ -1,44 +1,84 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { createChart, CandlestickSeries, type IChartApi, type ISeriesApi, type Time, ColorType } from 'lightweight-charts'; import { createChart, CandlestickSeries, type IChartApi, type ISeriesApi, type Time, ColorType } from 'lightweight-charts';
import type { Timeframe } from '../../types'; import type { Theme, Timeframe } from '../../types';
import { fetchUpbitCandles } from '../../utils/upbitApi'; import { fetchUpbitCandles } from '../../utils/upbitApi';
const TF_OPTIONS: Timeframe[] = ['1m', '5m', '15m', '1h', '4h', '1D']; const TF_OPTIONS: Timeframe[] = ['1m', '5m', '15m', '1h', '4h', '1D'];
interface Props { interface Props {
market: string; market: string;
theme?: Theme;
wsLabel?: string; wsLabel?: string;
} }
const PaperMiniChart: React.FC<Props> = ({ market, wsLabel = 'WebSocket <100ms' }) => { function readChartTheme(container: HTMLElement | null) {
const root = container?.closest('.app') ?? document.documentElement;
const s = getComputedStyle(root);
const v = (name: string, fallback: string) => s.getPropertyValue(name).trim() || fallback;
return {
bgColor: v('--bg3', '#1f2335'),
textColor: v('--text', '#c0caf5'),
gridColor: v('--sep', 'rgba(122, 162, 247, 0.15)'),
borderColor: v('--border', 'rgba(122, 162, 247, 0.15)'),
upColor: v('--up', '#ff6b6b'),
downColor: v('--down', '#4dabf7'),
};
}
const PaperMiniChart: React.FC<Props> = ({ market, theme = 'dark', wsLabel = 'WebSocket <100ms' }) => {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const chartRef = useRef<IChartApi | null>(null); const chartRef = useRef<IChartApi | null>(null);
const seriesRef = useRef<ISeriesApi<'Candlestick'> | null>(null); const seriesRef = useRef<ISeriesApi<'Candlestick'> | null>(null);
const [timeframe, setTimeframe] = useState<Timeframe>('1h'); const [timeframe, setTimeframe] = useState<Timeframe>('1h');
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
useEffect(() => { const applyTheme = useCallback((chart: IChartApi, series: ISeriesApi<'Candlestick'>) => {
if (!containerRef.current) return undefined; const t = readChartTheme(containerRef.current);
const chart = createChart(containerRef.current, { chart.applyOptions({
layout: { layout: {
background: { type: ColorType.Solid, color: '#121520' }, background: { type: ColorType.Solid, color: t.bgColor },
textColor: '#9aa5ce', textColor: t.textColor,
}, },
grid: { grid: {
vertLines: { color: 'rgba(122,162,247,0.06)' }, vertLines: { color: t.gridColor },
horzLines: { color: 'rgba(122,162,247,0.06)' }, horzLines: { color: t.gridColor },
}, },
rightPriceScale: { borderColor: 'rgba(122,162,247,0.12)' }, rightPriceScale: { borderColor: t.borderColor },
timeScale: { borderColor: 'rgba(122,162,247,0.12)' }, timeScale: { borderColor: t.borderColor },
});
series.applyOptions({
upColor: t.upColor,
downColor: t.downColor,
borderUpColor: t.upColor,
borderDownColor: t.downColor,
wickUpColor: t.upColor,
wickDownColor: t.downColor,
});
}, []);
useEffect(() => {
if (!containerRef.current) return undefined;
const t = readChartTheme(containerRef.current);
const chart = createChart(containerRef.current, {
layout: {
background: { type: ColorType.Solid, color: t.bgColor },
textColor: t.textColor,
},
grid: {
vertLines: { color: t.gridColor },
horzLines: { color: t.gridColor },
},
rightPriceScale: { borderColor: t.borderColor },
timeScale: { borderColor: t.borderColor },
crosshair: { mode: 1 }, crosshair: { mode: 1 },
}); });
const series = chart.addSeries(CandlestickSeries, { const series = chart.addSeries(CandlestickSeries, {
upColor: '#22c55e', upColor: t.upColor,
downColor: '#ef4444', downColor: t.downColor,
borderUpColor: '#22c55e', borderUpColor: t.upColor,
borderDownColor: '#ef4444', borderDownColor: t.downColor,
wickUpColor: '#22c55e', wickUpColor: t.upColor,
wickDownColor: '#ef4444', wickDownColor: t.downColor,
}); });
chartRef.current = chart; chartRef.current = chart;
seriesRef.current = series; seriesRef.current = series;
@@ -61,6 +101,12 @@ const PaperMiniChart: React.FC<Props> = ({ market, wsLabel = 'WebSocket <100ms'
}; };
}, []); }, []);
useEffect(() => {
if (chartRef.current && seriesRef.current) {
applyTheme(chartRef.current, seriesRef.current);
}
}, [theme, applyTheme]);
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
setLoading(true); setLoading(true);
@@ -10,6 +10,7 @@ import React, {
useContext, useContext,
useEffect, useEffect,
useMemo, useMemo,
useRef,
useState, useState,
} from 'react'; } from 'react';
import type { TradeSignalInfo } from '../components/TradeAlertModal'; import type { TradeSignalInfo } from '../components/TradeAlertModal';
@@ -121,6 +122,8 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
const alertSoundId = normalizeTradeAlertSoundId(soundId) as TradeAlertSoundId; const alertSoundId = normalizeTradeAlertSoundId(soundId) as TradeAlertSoundId;
const [readIds, setReadIds] = useState<Set<string>>(() => loadReadIds()); const [readIds, setReadIds] = useState<Set<string>>(() => loadReadIds());
const [toastNotifications, setToastNotifications] = useState<TradeNotificationItem[]>([]); const [toastNotifications, setToastNotifications] = useState<TradeNotificationItem[]>([]);
const toastNotificationsRef = useRef(toastNotifications);
toastNotificationsRef.current = toastNotifications;
const [allNotifications, setAllNotifications] = useState<TradeNotificationItem[]>([]); const [allNotifications, setAllNotifications] = useState<TradeNotificationItem[]>([]);
const [detailSignal, setDetailSignal] = useState<TradeSignalInfo | null>(null); const [detailSignal, setDetailSignal] = useState<TradeSignalInfo | null>(null);
const [detailCentered, setDetailCentered] = useState(false); const [detailCentered, setDetailCentered] = useState(false);
@@ -247,20 +250,10 @@ export const TradeNotificationProvider: React.FC<ProviderProps> = ({
}, []); }, []);
const dismissAllToasts = useCallback(() => { const dismissAllToasts = useCallback(() => {
setToastNotifications(prev => { const ids = toastNotificationsRef.current.map(n => n.id);
const ids = prev.map(n => n.id); if (ids.length === 0) return;
setReadIds(r => { dismissToasts(ids);
const next = new Set(r); }, [dismissToasts]);
ids.forEach(i => next.add(i));
saveReadIds(next);
return next;
});
setAllNotifications(all =>
all.map(n => (ids.includes(n.id) ? { ...n, isRead: true } : n)),
);
return [];
});
}, []);
const markAllAsRead = useCallback(() => { const markAllAsRead = useCallback(() => {
setAllNotifications(prev => { setAllNotifications(prev => {
+17 -1
View File
@@ -287,28 +287,44 @@
background: rgba(0, 0, 0, 0.45); background: rgba(0, 0, 0, 0.45);
} }
/* 라이트 테마 */ /* 라이트 테마 — body 포털(html.theme-light) · 앱 내부(.app.light) 공통 */
html.theme-light .app-popup-shell,
.app.light .app-popup-shell, .app.light .app-popup-shell,
html.theme-light .tam-modal,
.app.light .tam-modal, .app.light .tam-modal,
html.theme-light .lsp-modal,
.app.light .lsp-modal, .app.light .lsp-modal,
html.theme-light .gc-popup-panel,
.app.light .gc-popup-panel, .app.light .gc-popup-panel,
html.theme-light .bts-modal,
.app.light .bts-modal, .app.light .bts-modal,
html.theme-light .brm-modal,
.app.light .brm-modal, .app.light .brm-modal,
html.theme-light .ism-dialog,
.app.light .ism-dialog, .app.light .ism-dialog,
html.theme-light .login-modal,
.app.light .login-modal { .app.light .login-modal {
background: #ffffff; background: #ffffff;
border-color: rgba(25, 118, 210, 0.35); border-color: rgba(25, 118, 210, 0.35);
color: #212121; color: #212121;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.18), 0 0 40px rgba(25, 118, 210, 0.08); box-shadow: 0 20px 60px rgba(0, 0, 0, 0.18), 0 0 40px rgba(25, 118, 210, 0.08);
} }
html.theme-light .app-popup-header,
.app.light .app-popup-header, .app.light .app-popup-header,
html.theme-light .gc-popup-header,
.app.light .gc-popup-header { .app.light .gc-popup-header {
background: linear-gradient(180deg, rgba(25, 118, 210, 0.1) 0%, #f5f7fa 100%); background: linear-gradient(180deg, rgba(25, 118, 210, 0.1) 0%, #f5f7fa 100%);
border-color: rgba(0, 0, 0, 0.1); border-color: rgba(0, 0, 0, 0.1);
} }
html.theme-light .app-popup-header-title,
.app.light .app-popup-header-title, .app.light .app-popup-header-title,
html.theme-light .gc-popup-title,
.app.light .gc-popup-title { color: #1a237e; } .app.light .gc-popup-title { color: #1a237e; }
html.theme-light .app-popup-header-ko,
.app.light .app-popup-header-ko { color: #546e7a; } .app.light .app-popup-header-ko { color: #546e7a; }
html.theme-light .app-popup-card,
.app.light .app-popup-card { background: #f5f7fa; border-color: rgba(0, 0, 0, 0.1); } .app.light .app-popup-card { background: #f5f7fa; border-color: rgba(0, 0, 0, 0.1); }
html.theme-light .app-popup-input,
.app.light .app-popup-input, .app.light .app-popup-input,
html.theme-light .app-popup-select,
.app.light .app-popup-select { background: #f0f3f8; border-color: rgba(0, 0, 0, 0.12); color: #212121; } .app.light .app-popup-select { background: #f0f3f8; border-color: rgba(0, 0, 0, 0.12); color: #212121; }
+83 -51
View File
@@ -1,14 +1,30 @@
/* 백테스팅 대시보드 (btd-*) — 금색 포인트 다크 테마 */ /* 백테스팅 대시보드 (btd-*) — 앱 테마 변수 + 금색 포인트 */
.btd-page { .btd-page {
--btd-gold: #c9a227;
--btd-gold-light: #e8c547;
--btd-gold-dark: #b8860b;
--btd-gold-border: rgba(201, 162, 39, 0.18);
--btd-gold-border-strong: rgba(201, 162, 39, 0.35);
--btd-gold-bg: rgba(201, 162, 39, 0.08);
--btd-gold-bg-hover: rgba(201, 162, 39, 0.14);
display: flex; display: flex;
height: calc(100vh - var(--tmb-h, 46px)); height: calc(100vh - var(--tmb-h, 46px));
overflow: hidden; overflow: hidden;
background: #0a0e14; background: var(--bg);
color: #c8d0e0; color: var(--text);
font-family: var(--font, 'Noto Sans KR', sans-serif); font-family: var(--font, 'Noto Sans KR', sans-serif);
} }
.app.light .btd-page,
html.theme-light .btd-page {
--btd-gold-border: rgba(184, 134, 11, 0.28);
--btd-gold-border-strong: rgba(184, 134, 11, 0.45);
--btd-gold-bg: rgba(201, 162, 39, 0.1);
--btd-gold-bg-hover: rgba(201, 162, 39, 0.16);
}
/* ── 좌측 타임라인 사이드바 (~23%) ── */ /* ── 좌측 타임라인 사이드바 (~23%) ── */
.btd-sidebar { .btd-sidebar {
width: 23%; width: 23%;
@@ -17,8 +33,8 @@
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-right: 1px solid rgba(201, 162, 39, 0.15); border-right: 1px solid var(--btd-gold-border);
background: #0d1219; background: var(--bg2);
overflow: hidden; overflow: hidden;
} }
@@ -27,7 +43,7 @@
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 12px 12px 10px; padding: 12px 12px 10px;
border-bottom: 1px solid rgba(201, 162, 39, 0.12); border-bottom: 1px solid var(--btd-gold-border);
flex-shrink: 0; flex-shrink: 0;
} }
@@ -35,7 +51,7 @@
width: 3px; width: 3px;
height: 18px; height: 18px;
border-radius: 2px; border-radius: 2px;
background: linear-gradient(180deg, #e8c547, #b8860b); background: linear-gradient(180deg, var(--btd-gold-light), var(--btd-gold-dark));
flex-shrink: 0; flex-shrink: 0;
} }
@@ -44,7 +60,7 @@
flex: 1; flex: 1;
font-size: 13px; font-size: 13px;
font-weight: 800; font-weight: 800;
color: #f0e6c8; color: var(--text);
letter-spacing: -0.2px; letter-spacing: -0.2px;
} }
@@ -57,16 +73,16 @@
width: 28px; width: 28px;
height: 28px; height: 28px;
border-radius: 6px; border-radius: 6px;
border: 1px solid rgba(201, 162, 39, 0.2); border: 1px solid var(--btd-gold-border);
background: rgba(201, 162, 39, 0.06); background: var(--btd-gold-bg);
color: #c9a227; color: var(--btd-gold);
font-size: 13px; font-size: 13px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.btd-icon-btn:hover:not(:disabled) { background: rgba(201, 162, 39, 0.14); } .btd-icon-btn:hover:not(:disabled) { background: var(--btd-gold-bg-hover); }
.btd-icon-btn:disabled { opacity: 0.35; cursor: not-allowed; } .btd-icon-btn:disabled { opacity: 0.35; cursor: not-allowed; }
.btd-icon-btn--danger { color: #ef4444; border-color: rgba(239, 68, 68, 0.3); } .btd-icon-btn--danger { color: #ef4444; border-color: rgba(239, 68, 68, 0.3); }
@@ -78,7 +94,7 @@
justify-content: center; justify-content: center;
padding: 24px; padding: 24px;
text-align: center; text-align: center;
color: #6272a4; color: var(--text3);
font-size: 12px; font-size: 12px;
gap: 8px; gap: 8px;
} }
@@ -121,32 +137,32 @@
width: 10px; width: 10px;
height: 10px; height: 10px;
border-radius: 50%; border-radius: 50%;
background: #1a2030; background: var(--bg3);
border: 2px solid rgba(201, 162, 39, 0.45); border: 2px solid rgba(201, 162, 39, 0.45);
z-index: 1; z-index: 1;
} }
.btd-timeline-item--active .btd-timeline-node { .btd-timeline-item--active .btd-timeline-node {
background: #c9a227; background: var(--btd-gold);
border-color: #e8c547; border-color: var(--btd-gold-light);
box-shadow: 0 0 8px rgba(201, 162, 39, 0.5); box-shadow: 0 0 8px rgba(201, 162, 39, 0.5);
} }
.btd-timeline-card { .btd-timeline-card {
background: #121820; background: var(--bg3);
border: 1px solid rgba(201, 162, 39, 0.12); border: 1px solid var(--btd-gold-border);
border-radius: 8px; border-radius: 8px;
padding: 8px 10px; padding: 8px 10px;
transition: border-color 0.15s, box-shadow 0.15s; transition: border-color 0.15s, box-shadow 0.15s;
} }
.btd-timeline-item:hover .btd-timeline-card { .btd-timeline-item:hover .btd-timeline-card {
border-color: rgba(201, 162, 39, 0.28); border-color: var(--btd-gold-border-strong);
} }
.btd-timeline-item--active .btd-timeline-card { .btd-timeline-item--active .btd-timeline-card {
border-color: rgba(201, 162, 39, 0.65); border-color: rgba(201, 162, 39, 0.65);
box-shadow: 0 0 0 1px rgba(201, 162, 39, 0.2), 0 4px 16px rgba(0, 0, 0, 0.35); box-shadow: 0 0 0 1px rgba(201, 162, 39, 0.2), 0 4px 16px rgba(0, 0, 0, 0.15);
} }
.btd-timeline-top { .btd-timeline-top {
@@ -160,7 +176,7 @@
.btd-timeline-name { .btd-timeline-name {
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
color: #eef2ff; color: var(--text);
line-height: 1.3; line-height: 1.3;
} }
@@ -169,7 +185,7 @@
.btd-timeline-date { .btd-timeline-date {
display: block; display: block;
font-size: 10px; font-size: 10px;
color: #6272a4; color: var(--text3);
margin-bottom: 6px; margin-bottom: 6px;
} }
@@ -199,7 +215,7 @@
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: #0a0e14; background: var(--bg);
} }
.btd-dashboard { .btd-dashboard {
@@ -219,7 +235,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 10px; gap: 10px;
color: #6272a4; color: var(--text3);
font-size: 13px; font-size: 13px;
} }
.btd-empty span { font-size: 48px; opacity: 0.35; } .btd-empty span { font-size: 48px; opacity: 0.35; }
@@ -237,7 +253,7 @@
width: 4px; width: 4px;
height: 16px; height: 16px;
border-radius: 2px; border-radius: 2px;
background: linear-gradient(180deg, #e8c547, #b8860b); background: linear-gradient(180deg, var(--btd-gold-light), var(--btd-gold-dark));
flex-shrink: 0; flex-shrink: 0;
} }
@@ -245,7 +261,7 @@
margin: 0; margin: 0;
font-size: 13px; font-size: 13px;
font-weight: 800; font-weight: 800;
color: #f0e6c8; color: var(--text);
letter-spacing: -0.2px; letter-spacing: -0.2px;
line-height: 1.2; line-height: 1.2;
} }
@@ -253,11 +269,10 @@
.btd-section-sub { .btd-section-sub {
margin: 1px 0 0; margin: 1px 0 0;
font-size: 9px; font-size: 9px;
color: #6272a4; color: var(--text3);
font-weight: 500; font-weight: 500;
} }
/* KPI ~18% | 차트 ~44% | 테이블 ~32% (헤더 포함) */
.btd-section--kpi { .btd-section--kpi {
flex: 0 0 auto; flex: 0 0 auto;
} }
@@ -286,8 +301,8 @@
} }
.btd-kpi-card { .btd-kpi-card {
background: #121820; background: var(--bg2);
border: 1px solid rgba(201, 162, 39, 0.18); border: 1px solid var(--btd-gold-border);
border-radius: 10px; border-radius: 10px;
padding: 10px 14px; padding: 10px 14px;
min-height: 0; min-height: 0;
@@ -295,7 +310,7 @@
.btd-kpi-label { .btd-kpi-label {
font-size: 10px; font-size: 10px;
color: #9aa5ce; color: var(--text2);
margin-bottom: 4px; margin-bottom: 4px;
font-weight: 600; font-weight: 600;
} }
@@ -303,19 +318,19 @@
.btd-kpi-value { .btd-kpi-value {
font-size: 24px; font-size: 24px;
font-weight: 900; font-weight: 900;
color: #fff; color: var(--text);
line-height: 1; line-height: 1;
letter-spacing: -0.5px; letter-spacing: -0.5px;
} }
.btd-kpi-value.up { color: #22c55e; } .btd-kpi-value.up { color: #22c55e; }
.btd-kpi-value.down { color: #ef4444; } .btd-kpi-value.down { color: #ef4444; }
.btd-kpi-value.gold { color: #c9a227; } .btd-kpi-value.gold { color: var(--btd-gold); }
.btd-kpi-value.neutral { color: #e2e8f0; } .btd-kpi-value.neutral { color: var(--text2); }
.btd-kpi-desc { .btd-kpi-desc {
margin: 6px 0 0; margin: 6px 0 0;
font-size: 9px; font-size: 9px;
color: #6272a4; color: var(--text3);
line-height: 1.3; line-height: 1.3;
} }
@@ -329,7 +344,7 @@
.btd-kpi-icon { .btd-kpi-icon {
font-size: 20px; font-size: 20px;
opacity: 0.6; opacity: 0.6;
color: #c9a227; color: var(--btd-gold);
} }
.btd-kpi-dual { .btd-kpi-dual {
@@ -352,8 +367,8 @@
.btd-chart-card { .btd-chart-card {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
background: #121820; background: var(--bg2);
border: 1px solid rgba(201, 162, 39, 0.18); border: 1px solid var(--btd-gold-border);
border-radius: 10px; border-radius: 10px;
padding: 4px 8px 2px; padding: 4px 8px 2px;
display: flex; display: flex;
@@ -373,12 +388,24 @@
display: block; display: block;
} }
.btd-chart-grid {
stroke: var(--sep);
stroke-width: 1;
}
.btd-chart-label {
fill: var(--text3);
}
.btd-chart-line-up { stroke: #22c55e; }
.btd-chart-line-down { stroke: #ef4444; }
/* ── 거래 내역 테이블 ── */ /* ── 거래 내역 테이블 ── */
.btd-table-card { .btd-table-card {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
background: #121820; background: var(--bg2);
border: 1px solid rgba(201, 162, 39, 0.18); border: 1px solid var(--btd-gold-border);
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
@@ -399,7 +426,7 @@
} }
.btd-table thead { .btd-table thead {
background: rgba(201, 162, 39, 0.1); background: var(--btd-gold-bg);
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 1; z-index: 1;
@@ -410,19 +437,19 @@
text-align: left; text-align: left;
font-size: 10px; font-size: 10px;
font-weight: 700; font-weight: 700;
color: #c9a227; color: var(--btd-gold);
border-bottom: 1px solid rgba(201, 162, 39, 0.2); border-bottom: 1px solid var(--btd-gold-border);
white-space: nowrap; white-space: nowrap;
} }
.btd-table td { .btd-table td {
padding: 7px 12px; padding: 7px 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.04); border-bottom: 1px solid var(--sep);
color: #c8d0e0; color: var(--text);
} }
.btd-table tbody tr:hover td { .btd-table tbody tr:hover td {
background: rgba(201, 162, 39, 0.04); background: var(--btd-gold-bg);
} }
.btd-table tbody tr:last-child td { .btd-table tbody tr:last-child td {
@@ -430,19 +457,19 @@
} }
.btd-td-time { .btd-td-time {
color: #6272a4; color: var(--text3);
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
white-space: nowrap; white-space: nowrap;
} }
.btd-table td.buy { color: #3b82f6; font-weight: 700; } .btd-table td.buy { color: var(--down, #3b82f6); font-weight: 700; }
.btd-table td.sell { color: #ef4444; font-weight: 700; } .btd-table td.sell { color: var(--up, #ef4444); font-weight: 700; }
.btd-table td.up { color: #22c55e; font-weight: 700; } .btd-table td.up { color: #22c55e; font-weight: 700; }
.btd-table td.down { color: #ef4444; font-weight: 700; } .btd-table td.down { color: #ef4444; font-weight: 700; }
.btd-table-empty { .btd-table-empty {
text-align: center; text-align: center;
color: #6272a4; color: var(--text3);
padding: 24px !important; padding: 24px !important;
} }
@@ -452,6 +479,11 @@
@media (max-width: 860px) { @media (max-width: 860px) {
.btd-page { flex-direction: column; height: auto; overflow: auto; } .btd-page { flex-direction: column; height: auto; overflow: auto; }
.btd-sidebar { width: 100%; max-height: 240px; border-right: none; border-bottom: 1px solid rgba(201, 162, 39, 0.15); } .btd-sidebar {
width: 100%;
max-height: 240px;
border-right: none;
border-bottom: 1px solid var(--btd-gold-border);
}
.btd-kpi-grid { grid-template-columns: 1fr 1fr; } .btd-kpi-grid { grid-template-columns: 1fr 1fr; }
} }
+55 -55
View File
@@ -1,12 +1,12 @@
/* 모의투자 대시보드 (ptd-) — 전체 화면 고정 레이아웃 */ /* 모의투자 대시보드 (ptd-) — 전체 화면 고정 레이아웃, 앱 테마 변수 연동 */
.ptd-page { .ptd-page {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: #0d1117; background: var(--bg);
color: #c0caf5; color: var(--text);
padding: 8px 12px 10px; padding: 8px 12px 10px;
font-family: var(--font, 'Noto Sans KR', sans-serif); font-family: var(--font, 'Noto Sans KR', sans-serif);
} }
@@ -15,7 +15,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-height: 320px; min-height: 320px;
color: #9aa5ce; color: var(--text2);
} }
.ptd-topbar { .ptd-topbar {
@@ -26,18 +26,18 @@
margin-bottom: 8px; margin-bottom: 8px;
flex-shrink: 0; flex-shrink: 0;
} }
.ptd-title { margin: 0; font-size: 18px; font-weight: 800; color: #fff; } .ptd-title { margin: 0; font-size: 18px; font-weight: 800; color: var(--text); }
.ptd-topbar-actions { display: flex; gap: 8px; flex-shrink: 0; } .ptd-topbar-actions { display: flex; gap: 8px; flex-shrink: 0; }
.ptd-btn { .ptd-btn {
padding: 6px 10px; padding: 6px 10px;
border-radius: 8px; border-radius: 8px;
border: 1px solid rgba(122,162,247,0.2); border: 1px solid var(--border);
background: #1e222d; background: var(--bg2);
color: #c0caf5; color: var(--text);
font-size: 11px; font-size: 11px;
cursor: pointer; cursor: pointer;
} }
.ptd-btn:hover { background: #252a38; } .ptd-btn:hover { background: var(--bg3); }
.ptd-btn--danger { border-color: rgba(239,68,68,0.4); color: #ef4444; } .ptd-btn--danger { border-color: rgba(239,68,68,0.4); color: #ef4444; }
.ptd-banner { .ptd-banner {
@@ -68,13 +68,13 @@
gap: 8px; gap: 8px;
padding: 8px 10px; padding: 8px 10px;
border-radius: 10px; border-radius: 10px;
background: #1e222d; background: var(--bg2);
border: 1px solid rgba(122,162,247,0.12); border: 1px solid var(--border);
} }
.ptd-metric-icon { font-size: 16px; line-height: 1; } .ptd-metric-icon { font-size: 16px; line-height: 1; }
.ptd-metric-title { font-size: 10px; color: #6272a4; margin-bottom: 2px; } .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; } .ptd-metric-value { font-size: 17px; font-weight: 800; line-height: 1.1; color: var(--text); }
.ptd-metric-sub { font-size: 9px; color: #6272a4; margin-top: 2px; } .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; }
@@ -101,8 +101,8 @@
.ptd-col--right { min-width: 0; } .ptd-col--right { min-width: 0; }
.ptd-card { .ptd-card {
background: #1e222d; background: var(--bg2);
border: 1px solid rgba(122,162,247,0.12); border: 1px solid var(--border);
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
flex-shrink: 0; flex-shrink: 0;
@@ -111,22 +111,22 @@
padding: 8px 10px 0; padding: 8px 10px 0;
font-size: 11px; font-size: 11px;
font-weight: 700; font-weight: 700;
color: #9aa5ce; color: var(--text2);
} }
.ptd-asset-card { padding-bottom: 8px; } .ptd-asset-card { padding-bottom: 8px; }
.ptd-asset-total { .ptd-asset-total {
padding: 4px 10px 0; padding: 4px 10px 0;
font-size: 22px; font-size: 22px;
font-weight: 800; font-weight: 800;
color: #fff; color: var(--text);
} }
.ptd-asset-total span { font-size: 12px; color: #6272a4; font-weight: 600; } .ptd-asset-total span { font-size: 12px; color: var(--text3); font-weight: 600; }
.ptd-asset-ret { .ptd-asset-ret {
padding: 2px 10px 0; padding: 2px 10px 0;
font-size: 18px; font-size: 18px;
font-weight: 800; font-weight: 800;
} }
.ptd-asset-sub { padding: 4px 10px 0; font-size: 10px; color: #6272a4; } .ptd-asset-sub { padding: 4px 10px 0; font-size: 10px; color: var(--text3); }
.ptd-table { .ptd-table {
width: 100%; width: 100%;
@@ -136,12 +136,12 @@
.ptd-table th, .ptd-table td { .ptd-table th, .ptd-table td {
padding: 6px 8px; padding: 6px 8px;
text-align: left; text-align: left;
border-bottom: 1px solid rgba(122,162,247,0.08); border-bottom: 1px solid var(--sep);
} }
.ptd-table th { color: #6272a4; font-weight: 600; } .ptd-table th { color: var(--text3); font-weight: 600; }
.ptd-table tbody tr { cursor: pointer; } .ptd-table tbody tr { cursor: pointer; }
.ptd-table tbody tr:hover { background: rgba(63,126,245,0.06); } .ptd-table tbody tr:hover { background: var(--bg3); }
.ptd-row--sel { background: rgba(63,126,245,0.1); } .ptd-row--sel { background: var(--bg3); }
.ptd-coin { .ptd-coin {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -154,8 +154,8 @@
color: #fff; color: #fff;
margin-right: 4px; margin-right: 4px;
} }
.ptd-muted { color: #6272a4; text-align: center; padding: 12px; font-size: 11px; } .ptd-muted { color: var(--text3); text-align: center; padding: 12px; font-size: 11px; }
.ptd-time { color: #6272a4; white-space: nowrap; } .ptd-time { color: var(--text3); white-space: nowrap; }
.ptd-donut-card { padding-bottom: 8px; } .ptd-donut-card { padding-bottom: 8px; }
.ptd-donut-wrap { .ptd-donut-wrap {
@@ -175,20 +175,20 @@
height: 44px; height: 44px;
margin: 14px auto; margin: 14px auto;
border-radius: 50%; border-radius: 50%;
background: #1e222d; background: var(--bg2);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 10px; font-size: 10px;
font-weight: 700; font-weight: 700;
color: #c0caf5; color: var(--text);
} }
.ptd-donut-legend { .ptd-donut-legend {
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
font-size: 10px; font-size: 10px;
color: #9aa5ce; color: var(--text2);
} }
.ptd-donut-legend li { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; } .ptd-donut-legend li { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; }
.ptd-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; } .ptd-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
@@ -215,7 +215,7 @@
margin-bottom: 6px; margin-bottom: 6px;
flex-shrink: 0; flex-shrink: 0;
} }
.ptd-chart-title { font-size: 12px; font-weight: 700; } .ptd-chart-title { font-size: 12px; font-weight: 700; color: var(--text); }
.ptd-ws-badge { .ptd-ws-badge {
font-size: 9px; font-size: 9px;
padding: 2px 6px; padding: 2px 6px;
@@ -235,7 +235,7 @@
inset: 40% 0 auto; inset: 40% 0 auto;
text-align: center; text-align: center;
font-size: 11px; font-size: 11px;
color: #6272a4; color: var(--text3);
pointer-events: none; pointer-events: none;
} }
.ptd-tf-row { .ptd-tf-row {
@@ -248,16 +248,16 @@
.ptd-tf-btn { .ptd-tf-btn {
padding: 3px 7px; padding: 3px 7px;
border-radius: 6px; border-radius: 6px;
border: 1px solid rgba(122,162,247,0.15); border: 1px solid var(--border);
background: #121520; background: var(--bg3);
color: #9aa5ce; color: var(--text2);
font-size: 9px; font-size: 9px;
cursor: pointer; cursor: pointer;
} }
.ptd-tf-btn--active { .ptd-tf-btn--active {
border-color: #3f7ef5; border-color: var(--accent);
color: #3f7ef5; color: var(--accent);
background: rgba(63,126,245,0.12); background: var(--bg2);
} }
.ptd-indicators { .ptd-indicators {
@@ -265,16 +265,16 @@
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 6px; gap: 6px;
padding: 0 10px 8px; padding: 0 10px 8px;
border-top: 1px solid rgba(122,162,247,0.08); border-top: 1px solid var(--sep);
flex-shrink: 0; flex-shrink: 0;
} }
.ptd-ind-panel { .ptd-ind-panel {
background: #121520; background: var(--bg3);
border-radius: 8px; border-radius: 8px;
padding: 4px 6px; padding: 4px 6px;
min-height: 52px; min-height: 52px;
} }
.ptd-ind-label { font-size: 9px; color: #6272a4; margin-bottom: 2px; } .ptd-ind-label { font-size: 9px; color: var(--text3); margin-bottom: 2px; }
.ptd-ind-svg { width: 100%; height: 36px; display: block; } .ptd-ind-svg { width: 100%; height: 36px; display: block; }
/* 우측 패널 — 매매/호가 탭 */ /* 우측 패널 — 매매/호가 탭 */
@@ -283,14 +283,14 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; min-height: 0;
background: #1e222d; background: var(--bg2);
border: 1px solid rgba(122,162,247,0.12); border: 1px solid var(--border);
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
} }
.ptd-tabs--main { .ptd-tabs--main {
flex-shrink: 0; flex-shrink: 0;
background: #121520; background: var(--bg3);
} }
.ptd-tabs--main .ptd-tab { .ptd-tabs--main .ptd-tab {
padding: 10px; padding: 10px;
@@ -320,11 +320,11 @@
.ptd-order-card .top-panel { padding: 4px 2px; background: transparent; } .ptd-order-card .top-panel { padding: 4px 2px; background: transparent; }
.ptd-order-card .top-field { margin-bottom: 4px; } .ptd-order-card .top-field { margin-bottom: 4px; }
.ptd-order-card .top-submit--buy { background: #22c55e !important; border-color: #22c55e !important; } .ptd-order-card .top-submit--buy { background: #22c55e !important; border-color: #22c55e !important; }
.ptd-order-card .top-submit--sell { background: #3f7ef5 !important; border-color: #3f7ef5 !important; } .ptd-order-card .top-submit--sell { background: var(--accent) !important; border-color: var(--accent) !important; }
.ptd-ob { .ptd-ob {
background: #1e222d; background: var(--bg2);
border: 1px solid rgba(122,162,247,0.12); border: 1px solid var(--border);
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
} }
@@ -339,8 +339,8 @@
padding: 8px 10px; padding: 8px 10px;
font-size: 11px; font-size: 11px;
font-weight: 700; font-weight: 700;
color: #9aa5ce; color: var(--text2);
border-bottom: 1px solid rgba(122,162,247,0.08); border-bottom: 1px solid var(--sep);
flex-shrink: 0; flex-shrink: 0;
} }
.ptd-ob-body { .ptd-ob-body {
@@ -378,15 +378,15 @@
.ptd-ob-row--bid .ptd-ob-bar { background: #22c55e; left: 0; right: auto; } .ptd-ob-row--bid .ptd-ob-bar { background: #22c55e; left: 0; right: auto; }
.ptd-ob-row--ask .ptd-ob-price { color: #ef4444; position: relative; z-index: 1; } .ptd-ob-row--ask .ptd-ob-price { color: #ef4444; position: relative; z-index: 1; }
.ptd-ob-row--bid .ptd-ob-price { color: #22c55e; position: relative; z-index: 1; } .ptd-ob-row--bid .ptd-ob-price { color: #22c55e; position: relative; z-index: 1; }
.ptd-ob-size { position: relative; z-index: 1; color: #6272a4; } .ptd-ob-size { position: relative; z-index: 1; color: var(--text3); }
.ptd-ob-mid { .ptd-ob-mid {
text-align: center; text-align: center;
padding: 5px; padding: 5px;
font-size: 12px; font-size: 12px;
font-weight: 800; font-weight: 800;
color: #fff; color: var(--text);
border-top: 1px solid rgba(122,162,247,0.08); border-top: 1px solid var(--sep);
border-bottom: 1px solid rgba(122,162,247,0.08); border-bottom: 1px solid var(--sep);
} }
.ptd-history-card { .ptd-history-card {
@@ -402,17 +402,17 @@
flex: 1; flex: 1;
min-height: 0; min-height: 0;
} }
.ptd-tabs { display: flex; border-bottom: 1px solid rgba(122,162,247,0.1); flex-shrink: 0; } .ptd-tabs { display: flex; border-bottom: 1px solid var(--sep); flex-shrink: 0; }
.ptd-tab { .ptd-tab {
flex: 1; flex: 1;
padding: 7px; padding: 7px;
border: none; border: none;
background: transparent; background: transparent;
color: #6272a4; color: var(--text3);
font-size: 10px; font-size: 10px;
cursor: pointer; cursor: pointer;
} }
.ptd-tab.active { color: #3f7ef5; font-weight: 700; border-bottom: 2px solid #3f7ef5; } .ptd-tab.active { color: var(--accent); font-weight: 700; border-bottom: 2px solid var(--accent); }
.ptd-table--compact { font-size: 9px; } .ptd-table--compact { font-size: 9px; }
.ptd-table--compact th, .ptd-table--compact th,
.ptd-table--compact td { padding: 5px 6px; } .ptd-table--compact td { padding: 5px 6px; }
+11
View File
@@ -0,0 +1,11 @@
import type { Theme } from '../types';
const HTML_THEME_CLASSES = ['theme-dark', 'theme-light', 'theme-blue'] as const;
/** body 포털(알림 팝업 등)에서도 테마 CSS 변수가 적용되도록 html 클래스 동기화 */
export function syncDocumentTheme(theme: Theme): void {
if (typeof document === 'undefined') return;
const html = document.documentElement;
html.classList.remove(...HTML_THEME_CLASSES);
html.classList.add(`theme-${theme}`);
}