매매 시그널 알림 수정

This commit is contained in:
Macbook
2026-05-29 00:58:10 +09:00
parent e43b5cbd5a
commit e1ad1d626e
5 changed files with 94 additions and 62 deletions
+29 -16
View File
@@ -35,6 +35,28 @@
--accent-muted: color-mix(in srgb, var(--accent) 22%, transparent);
--popup-shadow: 0 8px 32px rgba(0, 0, 0, 0.55);
--overlay-scrim: rgba(0, 0, 0, 0.55);
/* 실시간 수신 형광 연두 — 가상매매·알림·차트 슬롯 공통 (얇은 외곽) */
--gc-receive-border: #69f0ae;
--gc-receive-shadow:
0 0 0 1px color-mix(in srgb, #69f0ae 92%, transparent),
0 0 6px 2px #69f0ae50,
0 0 14px 4px #b9f6ca26;
--gc-receive-shadow-inset:
0 0 0 1px color-mix(in srgb, #69f0ae 92%, transparent),
0 0 6px 2px #69f0ae50,
0 0 14px 4px #b9f6ca26,
inset 0 0 28px color-mix(in srgb, #b9f6ca 10%, transparent);
--gc-receive-shadow-inset-light:
0 0 0 1px color-mix(in srgb, #69f0ae 92%, transparent),
0 0 6px 2px #69f0ae50,
0 0 14px 4px #b9f6ca26,
inset 0 0 20px color-mix(in srgb, #b9f6ca 8%, transparent);
--gc-receive-panel-inset: inset 0 0 16px color-mix(in srgb, #b9f6ca 6%, transparent);
--gc-receive-dot-shadow:
0 0 4px 2px #69f0ae,
0 0 10px 4px #69f0ae55,
0 0 16px 6px #b9f6ca1a;
}
/* ── Dark Theme: 도쿄 나이트 (THEME_SETTINGS.md §2.2) ───────────── */
@@ -537,11 +559,8 @@ html.theme-blue {
.ws-dot.receiving {
animation: none !important; /* idle 애니메이션 일시 중단 */
background: #b9f6ca !important; /* 밝은 연두 */
transform: scale(1.4);
box-shadow:
0 0 6px 3px #69f0ae, /* 내부 glow */
0 0 14px 6px #69f0ae66, /* 외부 glow */
0 0 24px 10px #b9f6ca22; /* 넓은 음영 */
transform: scale(1.35);
box-shadow: var(--gc-receive-dot-shadow);
}
@keyframes ws-idle-lime {
@@ -4634,12 +4653,8 @@ html.theme-blue {
/* 실시간 수신 — 가상투자 vtd-card--receiving 와 동일 형광 연두 */
.multi-slot-wrap:has(.chart-slot--receiving),
.chart-slot--compact.chart-slot--receiving {
border-color: #69f0ae !important;
box-shadow:
0 0 0 1px #69f0ae,
0 0 10px 4px #69f0ae66,
0 0 22px 8px #b9f6ca33,
inset 0 0 48px color-mix(in srgb, #b9f6ca 14%, transparent);
border-color: var(--gc-receive-border) !important;
box-shadow: var(--gc-receive-shadow-inset);
transition: box-shadow 0.15s ease-out, border-color 0.15s ease-out;
}
@@ -4654,14 +4669,12 @@ html.theme-blue {
.chart-slot--compact.chart-slot--receiving.active {
box-shadow:
0 0 0 1px #69f0ae,
0 0 10px 4px #69f0ae66,
0 0 22px 8px #b9f6ca33,
inset 0 0 0 1px color-mix(in srgb, #69f0ae 55%, var(--accent, #2962ff));
var(--gc-receive-shadow),
inset 0 0 0 1px color-mix(in srgb, #69f0ae 50%, var(--accent, #2962ff));
}
.chart-slot--compact.chart-slot--receiving .slot-chart-wrap--compact {
box-shadow: inset 0 0 24px color-mix(in srgb, #b9f6ca 8%, transparent);
box-shadow: var(--gc-receive-panel-inset);
}
.slot-header--compact {
@@ -80,7 +80,7 @@ const TradeNotificationSignalSummary: React.FC<Props> = ({
headVariant={embedded ? 'inline-quote' : 'default'}
theme={theme}
ticker={ticker}
chartLiveReceiveHighlight
chartLiveReceiveHighlight={!embedded}
readOnlyStrategyLabel={strategyLabel}
/>
);
@@ -58,7 +58,10 @@ const TradeSignalMiniChart: React.FC<Props> = ({
pendingBarRef.current = null;
mgr.updateBar(pending);
const wrap = canvasWrapRef.current;
if (wrap && wrap.clientHeight > 0) mgr.resetPaneHeights(wrap.clientHeight);
if (wrap && wrap.clientWidth > 0 && wrap.clientHeight > 0) {
mgr.resetPaneHeights(wrap.clientHeight);
mgr.resize(wrap.clientWidth, wrap.clientHeight);
}
}, []);
const applyRealtimeBar = useCallback((bar: OHLCVBar, append: boolean) => {
@@ -104,11 +107,15 @@ const TradeSignalMiniChart: React.FC<Props> = ({
barsMarketRef.current = barsMarket;
const barsReady = bars.length >= 2 && barsMarket === market;
const applyPaneHeights = useCallback(() => {
const syncChartLayout = useCallback(() => {
const wrap = canvasWrapRef.current;
const mgr = managerRef.current;
if (!wrap || wrap.clientHeight <= 0 || !mgr?.hasMainSeries()) return;
mgr.resetPaneHeights(wrap.clientHeight);
if (!wrap || !mgr?.hasMainSeries()) return;
const w = wrap.clientWidth;
const h = wrap.clientHeight;
if (w <= 0 || h <= 0) return;
mgr.resetPaneHeights(h);
mgr.resize(w, h);
}, []);
useLayoutEffect(() => {
@@ -127,8 +134,12 @@ const TradeSignalMiniChart: React.FC<Props> = ({
if (!canvasWrapRef.current) return;
const h = canvasWrapRef.current.clientHeight;
if (h <= 0) return;
const w = canvasWrapRef.current.clientWidth;
if (managerRef.current?.hasMainSeries()) {
managerRef.current.resetPaneHeights(h);
if (w > 0) {
managerRef.current.resetPaneHeights(h);
managerRef.current.resize(w, h);
}
} else if (!chartReloadTriggeredRef.current && delay >= 600 && barsReady) {
chartReloadTriggeredRef.current = true;
setChartReloadTick(t => t + 1);
@@ -136,13 +147,13 @@ const TradeSignalMiniChart: React.FC<Props> = ({
}, delay),
);
const ro = new ResizeObserver(() => applyPaneHeights());
const ro = new ResizeObserver(() => syncChartLayout());
ro.observe(wrap);
return () => {
timers.forEach(clearTimeout);
ro.disconnect();
};
}, [market, timeframe, enabled, barsReady, applyPaneHeights, fillHeight]);
}, [market, timeframe, enabled, barsReady, syncChartLayout, fillHeight]);
useEffect(() => {
if (!enabled || !barsReady) return;
@@ -154,9 +165,9 @@ const TradeSignalMiniChart: React.FC<Props> = ({
useEffect(() => {
if (!enabled) return;
const t = setTimeout(() => applyPaneHeights(), 80);
const t = setTimeout(() => syncChartLayout(), 80);
return () => clearTimeout(t);
}, [fillHeight, enabled, applyPaneHeights]);
}, [fillHeight, enabled, syncChartLayout]);
useEffect(() => {
if (!enabled || !useUpbit) return;
+36 -18
View File
@@ -2,11 +2,8 @@
/* 실시간 WS/STOMP 수신 — 목록 행(.tnl-row) 외곽 아웃라인만 형광 연두 (내부 inset 없음) */
.tnl-row.tnl-row--receiving {
border-color: #69f0ae !important;
box-shadow:
0 0 0 1px #69f0ae,
0 0 10px 4px #69f0ae66,
0 0 22px 8px #b9f6ca33;
border-color: var(--gc-receive-border) !important;
box-shadow: var(--gc-receive-shadow);
transition: box-shadow 0.15s ease-out, border-color 0.15s ease-out;
}
@@ -36,6 +33,20 @@
box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent, #7aa2f7) 35%, transparent);
}
/* 수신 시 신호 상세(vtd-card) — 연두 테두리·inset 제거 (목록 행 외곽만 유지) */
.tnl-row.tnl-row--receiving .tnl-signal-slot .vtd-card--receiving,
.tnl-row.tnl-row--receiving .tnl-signal-slot .vtd-card.vtd-card--live {
border-color: color-mix(in srgb, var(--accent, #3f7ef5) 40%, var(--se-border, var(--border))) !important;
box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent, #3f7ef5) 12%, transparent) !important;
background: var(--se-panel-card-bg, var(--bg2)) !important;
}
.tnl-row.tnl-row--receiving .tnl-signal-slot .vtd-card--receiving .vtd-sig-panel,
.tnl-row.tnl-row--receiving .tnl-signal-slot .vtd-card--receiving .vtd-card-chart-panel,
.tnl-row.tnl-row--receiving .tnl-signal-slot .vtd-sig-panel-wrap--receiving .vtd-sig-panel {
box-shadow: none !important;
}
.tnl-row--gallery {
display: block;
width: 100%;
@@ -206,12 +217,12 @@
height: 100%;
}
/* 신호상세 — 가상매매 vtd-card 슬롯 (이퀄라이저·히트맵·신호등 최소 너비 확보) */
/* 신호상세 — 가상매매 vtd-card 슬롯 (차트보다 좁게 · 나머지는 캔들·지표) */
.tnl-signal-slot {
flex: 1.35 1 300px;
flex: 1.05 1 272px;
width: auto;
min-width: min(100%, 300px);
max-width: 42%;
min-width: min(100%, 272px);
max-width: 34%;
height: 280px;
max-height: 280px;
display: flex;
@@ -313,8 +324,9 @@
}
.tnl-charts-track > .tnl-signal-slot {
flex: 1.35 1 300px;
min-width: min(100%, 300px);
flex: 1.05 1 272px;
min-width: min(100%, 272px);
max-width: 34%;
}
/* 가상매매 투자대상 카드(vtd)와 동일한 3단 구성 */
@@ -622,7 +634,7 @@
}
.tnl-charts-track > .tnl-chart-card {
flex: 1 1 0;
flex: 1.12 1 0;
width: 0;
min-width: 0;
max-width: none;
@@ -685,13 +697,16 @@
.tnl-mini-chart-canvas {
position: relative;
flex: 0 0 236px;
height: 236px;
max-height: 236px;
flex: 1 1 auto;
width: 100%;
min-width: 0;
min-height: 0;
height: 100%;
max-height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
box-sizing: border-box;
}
.tnl-mini-chart-canvas--fill {
@@ -721,14 +736,17 @@
}
.tnl-mini-chart-placeholder {
flex: 0 0 236px;
height: 236px;
max-height: 236px;
flex: 1 1 auto;
width: 100%;
min-height: 120px;
height: 100%;
max-height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
color: var(--text3);
box-sizing: border-box;
}
.tnl-mini-chart-placeholder--fill {
@@ -720,11 +720,8 @@
}
.vtd-focus-wrap--receiving {
border-color: #69f0ae !important;
box-shadow:
0 0 0 1px #69f0ae,
0 0 10px 4px #69f0ae66,
inset 0 0 32px color-mix(in srgb, #b9f6ca 12%, transparent);
border-color: var(--gc-receive-border) !important;
box-shadow: var(--gc-receive-shadow-inset-light);
}
.vtd-focus-head {
@@ -917,7 +914,7 @@
}
.vtd-sig-panel-wrap--receiving .vtd-sig-panel {
box-shadow: inset 0 0 24px color-mix(in srgb, #b9f6ca 8%, transparent);
box-shadow: var(--gc-receive-panel-inset);
}
.vtd-focus-sig-meta {
@@ -1211,12 +1208,8 @@
/* 실시간 데이터 수신 — 실시간 차트 ws-dot.receiving 과 동일 형광 연두 음영 */
.vtd-card--receiving {
border-color: #69f0ae !important;
box-shadow:
0 0 0 1px #69f0ae,
0 0 10px 4px #69f0ae66,
0 0 22px 8px #b9f6ca33,
inset 0 0 48px color-mix(in srgb, #b9f6ca 14%, transparent);
border-color: var(--gc-receive-border) !important;
box-shadow: var(--gc-receive-shadow-inset);
background: linear-gradient(
165deg,
color-mix(in srgb, #b9f6ca 18%, var(--vtd-sig-card-gradient)) 0%,
@@ -1228,7 +1221,7 @@
.vtd-card--receiving .vtd-sig-panel,
.vtd-card--receiving .vtd-card-chart-panel {
box-shadow: inset 0 0 24px color-mix(in srgb, #b9f6ca 8%, transparent);
box-shadow: var(--gc-receive-panel-inset);
}
.vtd-card-head {
@@ -1535,11 +1528,8 @@
.vtd-live-badge-dot.receiving {
animation: none !important;
background: #b9f6ca !important;
transform: scale(1.35);
box-shadow:
0 0 6px 3px #69f0ae,
0 0 14px 6px #69f0ae66,
0 0 24px 10px #b9f6ca22;
transform: scale(1.3);
box-shadow: var(--gc-receive-dot-shadow);
}
.vtd-live-badge--receiving {