GNU/_PAGE/chart/upbit/whale/short-term_volume.php
<?php require_once '/home/www/GNU/_PAGE/head.php'; ?>
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>관측 코어 - 거래량 특화형 (V11-Final-Score)</title>
    <!-- 웹 폰트 및 아이콘 로드 -->
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
    
    <style>
        body { 
            background-color: #020617; 
            color: #f8fafc; 
            margin: 0; 
            padding: 0; 
            font-family: 'Inter', 'Pretendard', sans-serif; 
            overflow-y: auto; 
            user-select: none; 
        }
        
        #chart-container { 
            position: relative; 
            width: 100%; 
            height: 450px; 
            border-bottom: 2px solid #1e293b; 
            background: #020617; 
            overflow: hidden; 
        }
        
        canvas { 
            image-rendering: auto; 
            display: block; 
            cursor: grab; 
            width: 100%; 
            height: 100%; 
            position: absolute;
            top: 0;
            left: 0;
            z-index: 5;
        }
        canvas:active { cursor: grabbing; }
        
        .info-overlay { 
            position: absolute; top: 15px; left: 15px; z-index: 50; 
            background: rgba(15, 23, 42, 0.9); padding: 18px; border-radius: 4px;
            border: 1px solid #334155; pointer-events: auto; backdrop-filter: blur(12px);
            cursor: move; width: 340px; box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.5);
        }
        
        #depth-bar { 
            position: absolute; right: 0; top: 0; width: 45px; height: 450px; 
            z-index: 10; display: flex; flex-direction: column; 
            border-left: 1px solid #1e293b; background: #020617; 
        }
        .depth-label { 
            position: absolute; width: 100%; text-align: center; 
            font-size: 9px; font-weight: bold; color: white; 
            text-shadow: 1px 1px 2px black; z-index: 11; 
        }
        #bid-label { top: 5px; }
        #ask-label { bottom: 5px; }

        .status-bar { position: absolute; bottom: 10px; right: 55px; font-size: 10px; color: #475569; display: flex; gap: 15px; z-index: 20; }
        .btn-tool { background: #1e293b; color: #94a3b8; padding: 2px 8px; border-radius: 2px; font-size: 10px; border: 1px solid #475569; transition: all 0.2s; cursor: pointer; }
        .btn-tool:hover { background: #334155; color: #f8fafc; }
        
        .briefing-card { background: #0f172a; border: 1px solid #1e293b; border-radius: 4px; padding: 12px; display: flex; flex-direction: column; height: 100%; }
        #briefing-content { height: 150px; overflow-y: auto; scroll-behavior: smooth; }
        #briefing-content::-webkit-scrollbar { width: 4px; }
        #briefing-content::-webkit-scrollbar-thumb { background-color: #334155; border-radius: 10px; }
        
        #ratio-monitor, #score-monitor { width: 100%; height: 25px; background: #010409; display: flex; align-items: center; padding: 0 10px; border-bottom: 1px solid #1e293b; }
        .ratio-bar-container, .score-bar-container { flex: 1; height: 4px; background: #111827; border-radius: 10px; overflow: hidden; position: relative; border: 1px solid #1e293b; margin: 0 10px; }
        #buy-ratio-fill { height: 100%; background: #f43f5e; transition: width 0.5s ease-out; }
        #sell-ratio-fill { height: 100%; background: #3b82f6; transition: width 0.5s ease-out; }
        #score-fill { position: absolute; left: 50%; height: 100%; width: 0%; transition: all 0.4s cubic-bezier(0.1, 0.7, 1.0, 0.1); }
        
        #log-container { padding: 1.5rem; display: grid; grid-template-columns: repeat(5, 1fr); gap: 1rem; background: #010409; max-height: 250px; overflow: hidden; }
        #manual-container { height: 130px; background: #020617; border-top: 1px solid #1e293b; padding: 12px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; overflow: hidden; }
        .manual-box { background: #0f172a; border: 1px solid #334155; border-radius: 4px; padding: 10px; }
        .score-text-block { font-family: 'JetBrains Mono', monospace; font-size: 12px; line-height: 1.4; border-bottom: 1px solid #1e293b; padding-bottom: 8px; margin-bottom: 8px; }
    </style>
</head>
<body>

    <div id="chart-container">
        <div id="depth-bar">
            <span id="bid-label" class="depth-label">0%</span>
            <div id="bid-wall" style="height: 50%; background: #f43f5e; opacity: 0.6; transition: height 0.5s;"></div>
            <div id="ask-wall" style="height: 50%; background: #3b82f6; opacity: 0.6; transition: height 0.5s;"></div>
            <span id="ask-label" class="depth-label">0%</span>
        </div>

        <div id="draggable-info" class="info-overlay shadow-2xl">
            <div id="score-text-display" class="score-text-block">
                <div class="text-white">종합 점수 : <span id="txt-global-score">+0.00</span></div>
                <div class="text-slate-400">거래량 강도 : <span id="txt-vol-strength">+0.00</span></div>
                <div class="text-slate-400">비율 강도 : <span id="txt-ratio-strength">+0.00</span></div>
            </div>

            <div class="flex justify-between items-start gap-6 mb-2">
                <h1 class="text-[10px] font-bold text-blue-400 uppercase tracking-widest opacity-80"><i class="fa-solid fa-crosshairs mr-1"></i> 관측 코어 V11-스코어</h1>
                <div class="flex gap-1">
                    <button onclick="exportData()" class="btn-tool" title="데이터 복사"><i class="fa-solid fa-copy"></i></button>
                    <button onclick="captureCanvas()" class="btn-tool" title="차트 캡처"><i class="fa-solid fa-camera"></i></button>
                    <button onclick="location.reload()" class="btn-tool" title="새로고침"><i class="fa-solid fa-rotate"></i></button>
                </div>
            </div>
            <div class="flex justify-between items-center mb-1">
                <div class="flex items-baseline gap-3">
                    <span id="price" class="text-4xl font-mono font-bold text-emerald-400 tracking-tighter leading-none">0</span>
                    <span id="percent" class="text-base text-slate-500 font-mono">0.00%</span>
                </div>
                <div class="text-right">
                    <div class="text-[9px] text-slate-500 font-bold uppercase">종합 스코어</div>
                    <div id="global-score-text" class="text-2xl font-mono font-bold text-slate-400">0.00</div>
                </div>
            </div>
            <div id="stats" class="grid grid-cols-2 gap-x-6 gap-y-2 mt-4 text-[10px] text-slate-500 uppercase font-medium border-t border-slate-800 pt-4">
                <div class="flex justify-between"><span>실시간 거래량</span><span id="v-val" class="text-amber-400 font-bold">-</span></div>
                <div class="flex justify-between"><span>예상 거래대금</span><span id="val-val" class="text-slate-300">-</span></div>
                <div class="flex justify-between"><span>체결 강도</span><span id="speed-val" class="text-emerald-500 font-bold">-</span></div>
                <div class="flex justify-between"><span>상하 변동폭</span><span class="text-slate-300"><span id="h-val" class="text-rose-500">-</span>/<span id="l-val" class="text-blue-500">-</span></span></div>
            </div>
        </div>

        <div class="status-bar">
            <span id="whale-alert" class="hidden font-bold text-amber-500"><i class="fa-solid fa-whale mr-1"></i>대량 수량 체결</span>
            <span id="drag-mode" class="text-amber-500 font-bold hidden"><i class="fa-solid fa-hand-back-point-left mr-1"></i>과거 탐색 중</span>
            <span id="alert-text" class="hidden font-bold alert-blink"><i class="fa-solid fa-triangle-exclamation mr-1"></i>거래량 스파이크</span>
            <span id="zoom-stat">확대: 120봉</span>
            <span id="conn-stat"><span class="inline-block w-2 h-2 bg-emerald-500 rounded-full mr-1"></span>관측 엔진 가동 중</span>
        </div>
        <canvas id="mainChart"></canvas>
    </div>

    <div id="ratio-monitor">
        <div class="text-[9px] font-bold text-slate-500 uppercase w-32">실시간 거래량 점유율</div>
        <div class="ratio-bar-container">
            <div id="buy-ratio-fill" style="width: 50%;"></div>
            <div id="sell-ratio-fill" style="width: 50%;"></div>
        </div>
        <div id="ratio-text" class="text-[10px] font-mono text-slate-400 w-32 text-right">50% : 50%</div>
    </div>

    <div id="score-monitor">
        <div class="text-[9px] font-bold text-slate-500 uppercase w-32">종합 모멘텀 지수</div>
        <div class="score-bar-container">
            <div id="score-fill"></div>
        </div>
        <div id="score-val-label" class="text-[10px] font-mono text-slate-400 w-32 text-right">0.00</div>
    </div>

    <div id="log-container">
        <div class="briefing-card">
            <div class="text-[10px] text-slate-500 mb-1 font-bold uppercase tracking-widest">체결 모멘텀</div>
            <div class="flex items-center gap-3 mb-4">
                <div id="m-dot" class="momentum-dot bg-slate-700"></div>
                <div id="m-text" class="text-xs font-bold text-slate-400">데이터 로드...</div>
            </div>
            <div class="text-[10px] text-slate-500 mb-1 font-bold uppercase tracking-widest">거래 밀도</div>
            <div id="vix-gauge" class="text-xl font-mono font-bold text-emerald-400">0.00</div>
        </div>
        
        <div class="briefing-card md:col-span-2 flex flex-col overflow-hidden">
            <div class="text-[10px] text-slate-500 mb-2 font-bold uppercase flex justify-between shrink-0">
                <span><i class="fa-solid fa-list-ul mr-1"></i> 거래량 변동 브리핑 로그</span>
                <span id="last-update" class="text-slate-700">-</span>
            </div>
            <div id="briefing-content" class="text-sm font-mono space-y-1"></div>
        </div>

        <div class="briefing-card md:col-span-2">
            <div class="text-[10px] text-slate-500 mb-3 font-bold uppercase tracking-widest flex justify-between border-b border-slate-800 pb-2">
                <span>실시간 수량 주도권 분석</span>
                <span class="text-blue-400 animate-pulse"><i class="fa-solid fa-microchip"></i> 스코어 AI</span>
            </div>
            <div class="flex-1 flex flex-col justify-between">
                <div id="market-sentiment" class="text-base font-bold text-slate-100 leading-tight">거래량 데이터 대기 중...</div>
                <div class="grid grid-cols-3 gap-2 mt-4 bg-black/30 p-3 rounded">
                    <div class="flex flex-col"><span class="val-label">수량비율</span><span id="val-ratio" class="val-data">-</span></div>
                    <div class="flex flex-col"><span class="val-label">유입속도</span><span id="val-delta" class="val-data">-</span></div>
                    <div class="flex flex-col"><span class="val-label">체결강도</span><span id="val-speed" class="val-data">-</span></div>
                </div>
            </div>
        </div>
    </div>

    <div id="manual-container">
        <div class="manual-box">
            <span class="manual-title"><i class="fa-solid fa-gamepad"></i> 인터랙션 제어</span>
            <div class="manual-item">
                <p><span class="key-badge">창 이동</span> 상단 정보창 드래그 이동</p>
                <p><span class="key-badge">드래그</span> 차트 가로 탐색</p>
                <p><span class="key-badge">휠 조작</span> 캔들 확대/축소 (수량 데이터 연동)</p>
            </div>
        </div>
        <div class="manual-box">
            <span class="manual-title"><i class="fa-solid fa-chart-line"></i> 거래량 데이터 독해</span>
            <div class="manual-item">
                <p>■ <span class="text-rose-500 font-bold">비율 바</span>: 현재 구간 내 매수 체결수량 비중</p>
                <p>■ <span class="text-amber-400 font-bold">대량 체결</span>: 평균 대비 320% 수량 발생 시 경보</p>
                <p>■ <span class="text-emerald-500 font-bold">종합 점수</span>: 거래량 편차와 체결비율 가중 합산</p>
            </div>
        </div>
        <div class="manual-box">
            <span class="manual-title"><i class="fa-solid fa-shield-halved"></i> 시스템 시그널 안내</span>
            <div class="manual-item">
                <p>■ <span class="text-violet-400 font-bold">관성 이탈</span>: 수량 급감과 함께 추세 이탈 감지</p>
                <p>■ <span class="text-amber-500 font-bold">스파이크</span>: 실시간 거래량 폭증 로그 기록</p>
                <p>■ <span class="text-emerald-500 font-bold">AI 분석</span>: 체결수량 기반 주도 세력 추정</p>
            </div>
        </div>
    </div>

    <script>
        // 핵심 설정값
        const CONFIG = {
            SPIKE_FACTOR: 3.2,
            WHALE_VOL_THRESHOLD: 40,
            MAX_LOGS: 50,
            REFRESH_INTERVAL: 1000,
            DEPTH_BAR_WIDTH: 45,
            BASE_ZOOM: 120,
            Y_PADDING: 40 // 상하단 여백 고정
        };

        const canvas = document.getElementById('mainChart');
        const ctx = canvas.getContext('2d');
        const chartData = [];
        let zoomPoints = CONFIG.BASE_ZOOM, offset = 0, isDragging = false, lastX = 0;
        let basePrice = 100000000, sessionStartPrice = 100000000, lastPriceTime = Date.now();
        let audioCtx = null, lastBuyRatio = 50;

        const dragInfo = document.getElementById('draggable-info');
        let isMoving = false, moveStartX, moveStartY;

        // 초기화 및 리사이즈 대응
        function init() {
            const dpr = window.devicePixelRatio || 1;
            const container = document.getElementById('chart-container');
            const rect = container.getBoundingClientRect();
            
            // 캔버스 크기를 컨테이너 실제 크기와 DPR에 맞춰 설정
            canvas.width = rect.width * dpr;
            canvas.height = rect.height * dpr;
            
            ctx.setTransform(1, 0, 0, 1, 0, 0); 
            ctx.scale(dpr, dpr);
            
            draw();
        }

        window.addEventListener('resize', init);

        function handleUserInteraction() {
            if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
            if (audioCtx.state === 'suspended') audioCtx.resume();
        }

        // 정보창 드래그
        dragInfo.addEventListener('mousedown', (e) => {
            if (e.target.closest('button')) return;
            handleUserInteraction();
            isMoving = true;
            moveStartX = e.clientX - dragInfo.offsetLeft;
            moveStartY = e.clientY - dragInfo.offsetTop;
            dragInfo.style.opacity = "0.8";
        });

        window.addEventListener('mousemove', (e) => {
            if (!isMoving) return;
            dragInfo.style.left = (e.clientX - moveStartX) + 'px';
            dragInfo.style.top = (e.clientY - moveStartY) + 'px';
        });

        window.addEventListener('mouseup', () => { isMoving = false; dragInfo.style.opacity = "0.9"; });

        function playSound(up, intense = false) {
            try {
                if(!audioCtx || audioCtx.state !== 'running') return;
                const osc = audioCtx.createOscillator(), g = audioCtx.createGain();
                osc.frequency.setValueAtTime(up ? (intense ? 1200 : 900) : (intense ? 200 : 350), audioCtx.currentTime);
                g.gain.setValueAtTime(intense ? 0.03 : 0.015, audioCtx.currentTime);
                g.gain.exponentialRampToValueAtTime(0.00001, audioCtx.currentTime + 0.05);
                osc.connect(g); g.connect(audioCtx.destination);
                osc.start(); osc.stop(audioCtx.currentTime + 0.05);
            } catch(e) {}
        }

        function addBriefing(msg, type = "normal") {
            const content = document.getElementById('briefing-content');
            const colorMap = { spike: "text-amber-500 font-bold", breakout: "text-violet-400 font-bold", whale: "text-yellow-400 font-bold", normal: "text-slate-400" };
            const logEntry = document.createElement('div');
            logEntry.className = colorMap[type] || colorMap.normal;
            logEntry.innerText = `[${new Date().toLocaleTimeString('ko-KR',{hour12:false})}] ${msg}`;
            content.insertBefore(logEntry, content.firstChild);
            if(content.children.length > CONFIG.MAX_LOGS) content.removeChild(content.lastChild);
        }

        function update() {
            const last = chartData.length > 0 ? chartData[chartData.length-1].p : basePrice;
            const now = Date.now();
            const next = last + (Math.random()*600000 - 300000);
            const speed = parseFloat((Math.abs(next - last) / (now - lastPriceTime) * 100).toFixed(2));
            lastPriceTime = now;
            
            const vol = Math.random()*45+5;
            const tradeValue = vol * next;
            const currentSide = Math.random() > 0.48 ? 'B' : 'S';
            const isSpike = chartData.length > 0 && vol > chartData[chartData.length-1].v * CONFIG.SPIKE_FACTOR;
            const isWhale = vol > CONFIG.WHALE_VOL_THRESHOLD; 
            
            const viewForB = chartData.slice(-35);
            const bMax = viewForB.length ? Math.max(...viewForB.map(d=>d.p)) : next;
            const bMin = viewForB.length ? Math.min(...viewForB.map(d=>d.p)) : next;
            let isBreakout = (next > bMax + 95000 || next < bMin - 95000);

            if (offset > 0) offset++;

            chartData.push({ p: next, v: vol, val: tradeValue, s: currentSide, spd: speed, spike: isSpike, breakout: isBreakout, whale: isWhale });
            if(chartData.length > 5000) chartData.shift();

            document.getElementById('price').innerText = Math.floor(next).toLocaleString();
            document.getElementById('percent').innerText = `${(((next/sessionStartPrice)-1)*100).toFixed(2)}%`;
            document.getElementById('v-val').innerText = vol.toFixed(2);
            document.getElementById('val-val').innerText = (tradeValue / 100000000).toFixed(1) + "억";
            document.getElementById('speed-val').innerText = speed;
            document.getElementById('vix-gauge').innerText = (speed / 100).toFixed(2);
            
            if(isSpike) addBriefing(`수량 스파이크 감지`, "spike");
            if(isBreakout) { addBriefing(`가격 관성 이탈`, "breakout"); playSound(next > last); }
            if(isWhale) { addBriefing(`대량 수량 체결 포착: ${vol.toFixed(2)} unit`, "whale"); playSound(next > last, true); }
            
            document.getElementById('whale-alert').classList.toggle('hidden', !isWhale);
            document.getElementById('alert-text').classList.toggle('hidden', !isSpike && !isBreakout);

            const viewRange = chartData.slice(-(zoomPoints + offset), offset === 0 ? undefined : -offset);
            if(viewRange.length > 0) {
                const buyV = viewRange.filter(x => x.s === 'B').reduce((a, b) => a + b.v, 0);
                const totalV = viewRange.reduce((a, b) => a + b.v, 0);
                const buyRatio = totalV > 0 ? (buyV / totalV * 100) : 50;

                document.getElementById('buy-ratio-fill').style.width = buyRatio + "%";
                document.getElementById('sell-ratio-fill').style.width = (100 - buyRatio) + "%";
                document.getElementById('ratio-text').innerText = `${Math.round(buyRatio)}% : ${Math.round(100 - buyRatio)}%`;
                
                // Depth Bar 업데이트 (height 방식)
                document.getElementById('bid-wall').style.height = buyRatio + "%";
                document.getElementById('ask-wall').style.height = (100 - buyRatio) + "%";
                document.getElementById('bid-label').innerText = Math.round(buyRatio) + "%"; 
                document.getElementById('ask-label').innerText = Math.round(100 - buyRatio) + "%";

                const avgV = totalV / viewRange.length || 1;
                const strength_v = Math.min(1, Math.max(-1, (vol - avgV) / avgV));
                const strength_r = Math.min(1, Math.max(-1, (buyRatio - 50) / 50));
                const finalScore = Math.min(1, Math.max(-1, (strength_v * 0.6) + (strength_r * 0.4)));

                updateScoreUI(finalScore, strength_v, strength_r);
                updateMarketSentiment(buyRatio, last, next, speed);
                lastBuyRatio = buyRatio;
            }
            
            requestAnimationFrame(draw);
        }

        function updateScoreUI(score, sv, sr) {
            const scoreTextEl = document.getElementById('global-score-text');
            const scoreFillEl = document.getElementById('score-fill');
            const fmt = (v) => (v >= 0 ? "+" : "") + v.toFixed(2);
            document.getElementById('txt-global-score').innerText = fmt(score);
            document.getElementById('txt-vol-strength').innerText = fmt(sv);
            document.getElementById('txt-ratio-strength').innerText = fmt(sr);
            scoreTextEl.innerText = score.toFixed(2);
            const color = score > 0.1 ? "#f43f5e" : (score < -0.1 ? "#3b82f6" : "#94a3b8");
            scoreTextEl.style.color = color;
            scoreFillEl.style.background = color;
            if(score >= 0) { scoreFillEl.style.left = "50%"; scoreFillEl.style.width = (Math.min(1, score) * 50) + "%"; } 
            else { const w = Math.min(1, Math.abs(score)) * 50; scoreFillEl.style.left = (50 - w) + "%"; scoreFillEl.style.width = w + "%"; }
            document.getElementById('score-val-label').innerText = fmt(score);
        }

        function updateMarketSentiment(ratio, prevPrice, curPrice, speed) {
            const sentimentEl = document.getElementById('market-sentiment');
            const delta = ratio - lastBuyRatio;
            let dominance = ratio > 58 ? "수량 매수 압도" : (ratio > 52 ? "수량 매수 우위" : (ratio < 42 ? "수량 매도 압도" : (ratio < 48 ? "수량 매도 우위" : "수급 중립")));
            sentimentEl.innerText = `${Math.abs(delta) > 2.5 ? "폭발적" : "활발한"} ${dominance}`;
            sentimentEl.style.color = ratio > 52 ? "#f43f5e" : (ratio < 48 ? "#3b82f6" : "#f1f5f9");
            document.getElementById('val-ratio').innerText = Math.round(ratio) + "%";
            document.getElementById('val-delta').innerText = (delta > 0 ? "+" : "") + delta.toFixed(1);
            document.getElementById('val-speed').innerText = speed;
        }

        function draw() {
            const dpr = window.devicePixelRatio || 1;
            const w = canvas.width / dpr;
            const h = canvas.height / dpr;
            ctx.clearRect(0, 0, w, h);
            
            const drawWidth = w - CONFIG.DEPTH_BAR_WIDTH;
            const startIndex = Math.max(0, chartData.length - zoomPoints - offset);
            const view = chartData.slice(startIndex, startIndex + zoomPoints);
            if(view.length < 2) return;

            const prices = view.map(d => d.p);
            const maxP = Math.max(...prices);
            const minP = Math.min(...prices);
            const range = (maxP - minP) || 1;
            const maxV = Math.max(...view.map(d => d.v)) || 1;

            const getX = (i) => i * (drawWidth / (view.length - 1));
            // Container 기준 패딩 적용된 정밀 Y 좌표
            const getY = (p) => (h - CONFIG.Y_PADDING) - ((p - minP) / range * (h - CONFIG.Y_PADDING * 2));

            // 그리드
            ctx.strokeStyle = '#0f172a';
            ctx.lineWidth = 1;
            for(let i=0; i<=5; i++){ 
                const gy = CONFIG.Y_PADDING + i * ((h - CONFIG.Y_PADDING * 2) / 5);
                ctx.beginPath(); ctx.moveTo(0, gy); ctx.lineTo(drawWidth, gy); ctx.stroke(); 
            }

            // 현재가 가이드선
            const curP = view[view.length-1].p, curY = getY(curP);
            ctx.setLineDash([2, 4]); ctx.strokeStyle = '#64748b'; 
            ctx.beginPath(); ctx.moveTo(0, curY); ctx.lineTo(drawWidth, curY); ctx.stroke();
            ctx.setLineDash([]); ctx.font = "11px 'JetBrains Mono'"; ctx.fillStyle = '#10b981'; 
            ctx.fillText(Math.floor(curP).toLocaleString(), drawWidth - 95, curY-6);

            // 거래량 막대
            view.forEach((d, i) => {
                const x = getX(i);
                const bw = Math.max(1, (drawWidth / view.length) * 0.8);
                const bh = (Math.log10(1 + d.v) / (Math.log10(1 + maxV) * 1.5)) * (h * 0.4);
                ctx.fillStyle = d.spike ? '#fbbf24' : (d.s === 'B' ? '#f43f5e' : '#3b82f6');
                ctx.globalAlpha = 0.25;
                ctx.fillRect(x - (bw/2), h - bh, bw, bh);
                ctx.globalAlpha = 1.0;
            });

            // 가격 라인
            ctx.beginPath(); ctx.strokeStyle = '#10b981'; ctx.lineWidth = 2;
            view.forEach((d, i) => { if(i===0) ctx.moveTo(getX(i), getY(d.p)); else ctx.lineTo(getX(i), getY(d.p)); });
            ctx.stroke(); 
        }

        // 인터랙션 (드래그, 휠)
        canvas.addEventListener('mousedown', (e) => { handleUserInteraction(); isDragging = true; lastX = e.clientX; });
        window.addEventListener('mouseup', () => { isDragging = false; });
        window.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            const dx = e.clientX - lastX;
            if (Math.abs(dx) > 1) {
                const sensitivity = zoomPoints / 600;
                offset = Math.max(0, Math.min(chartData.length - zoomPoints, offset - Math.round(dx * sensitivity)));
                lastX = e.clientX; 
                document.getElementById('drag-mode').classList.toggle('hidden', offset === 0);
                draw();
            }
        });

        canvas.addEventListener('wheel', (e) => {
            e.preventDefault(); handleUserInteraction();
            zoomPoints = Math.min(2000, Math.max(20, zoomPoints + (e.deltaY > 0 ? 20 : -20)));
            offset = Math.max(0, Math.min(chartData.length - zoomPoints, offset));
            document.getElementById('zoom-stat').innerText = `확대: ${zoomPoints}봉`; 
            draw();
        }, { passive: false });

        // 초기 데이터 및 실행
        for(let i=0; i<1200; i++) { 
            basePrice += (Math.random()*200000-100000); 
            chartData.push({p:basePrice, v:Math.random()*15+5, s:Math.random()>0.48?'B':'S', spike:false, breakout:false, whale:false}); 
        }
        init(); 
        setInterval(update, CONFIG.REFRESH_INTERVAL);

        function exportData() { navigator.clipboard.writeText(JSON.stringify(chartData.slice(-zoomPoints))); alert("데이터 복사 완료."); }
        function captureCanvas() { const a = document.createElement('a'); a.href = canvas.toDataURL(); a.download = `V11_SCORE_${Date.now()}.png`; a.click(); }
    </script>
</body>
</html>
<?php require_once '/home/www/GNU/_PAGE/tail.php'; ?>