GNU/skin/board/outline_target/view/view.script.php
<script>
<?php if ($board['bo_download_point'] < 0) { ?>
$(function() {
    $('a.view_file_download').on('click', function() {
        if(!g5_is_member) {
            alert('다운로드 권한이 없습니다.\n회원이시라면 로그인 후 이용해 보십시오.');
            return false;
        }
        var msg = '파일을 다운로드 하시면 포인트가 차감(<?php echo number_format($board['bo_download_point']) ?>점)됩니다.\n\n포인트는 게시물당 한번만 차감되며 다음에 다시 다운로드 하셔도 중복하여 차감하지 않습니다.\n\n그래도 다운로드 하시겠습니까?';
        if(confirm(msg)) {
            var href = $(this).attr('href') + '&js=on';
            $(this).attr('href', href);
            return true;
        }
        return false;
    });
});
<?php } ?>

function board_move(href) {
    window.open(href, 'boardmove', 'left=50, top=50, width=500, height=550, scrollbars=1');
}

$(function() {
    const x2TxtRaw = <?php echo json_encode(isset($x2_txt_raw) ? $x2_txt_raw : ''); ?>;
    const x2TxtFilename = <?php echo json_encode(isset($code_filename) ? $code_filename : 'additional.php'); ?>;
    const realtimeAssetUrl = '/GNU/_PAGE/asset/upbit/data_assets.php?ajax=1';

    $('.View-Body-Inner img').viewimageresize();

    $('a.view_image').on('click', function() {
        window.open(this.href, 'large_image', 'location=yes,links=no,toolbar=no,top=10,left=10,width=10,height=10,resizable=yes,scrollbars=no,status=no');
        return false;
    });

    $('#copyX2TxtBtn').on('click', async function() {
        if (!x2TxtRaw) return;
        try {
            await navigator.clipboard.writeText(x2TxtRaw);
        } catch (e) {
            const $temp = $('<textarea>').val(x2TxtRaw).appendTo('body').select();
            document.execCommand('copy');
            $temp.remove();
        }
    });

    $('#downloadX2TxtBtn').on('click', function() {
        if (!x2TxtRaw) return;
        const blob = new Blob([x2TxtRaw], { type: 'text/plain;charset=utf-8' });
        let safeFileName = (x2TxtFilename || 'additional.php').replace(/[\\/:*?"<>|]/g, '_');
        if (!/\.php$/i.test(safeFileName)) {
            safeFileName += '.php';
        }

        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = safeFileName;
        document.body.appendChild(a);
        a.click();
        a.remove();
        URL.revokeObjectURL(url);
    });

    function parseNumber(value) {
        const cleaned = String(value || '').replace(/[^0-9.-]/g, '');
        const parsed = parseFloat(cleaned);
        return Number.isFinite(parsed) ? parsed : 0;
    }

    function formatAmount(value) {
        if (!Number.isFinite(value)) return '-';
        return Math.round(value).toLocaleString('ko-KR');
    }

    function formatRate(value) {
        if (!Number.isFinite(value)) return '-';
        return value.toFixed(2) + '%';
    }

    function applyValueClass($el, plusClass, minusClass, isPlus) {
        $el.removeClass(plusClass + ' ' + minusClass);
        $el.addClass(isPlus ? plusClass : minusClass);
    }

    function updateLiveMetrics(currentAsset) {
        const $root = $('#x2_live_metrics');
        if ($root.length === 0) return;

        const base = parseFloat($root.data('base-num')) || 0;
        const target = parseFloat($root.data('target-num')) || 0;
        const liveCurrent = String($root.data('live-current')) === '1';
        const liveProfit = String($root.data('live-profit')) === '1';
        const liveProfitRate = String($root.data('live-profit-rate')) === '1';
        const liveReachRate = String($root.data('live-reach-rate')) === '1';

        const $current = $('#x2_live_current_asset');
        const $profitAmount = $('#x2_live_profit_amount');
        const $profitRate = $('#x2_live_profit_rate');
        const $reachRate = $('#x2_live_reach_rate');

        let current = liveCurrent ? currentAsset : parseNumber($current.text());
        if (liveCurrent) {
            $current.text(formatAmount(current));
        }
        applyValueClass($current, 'value-green', 'value-red', current >= base);

        let profitAmount = current - base;
        if (liveProfit) {
            $profitAmount.text(formatAmount(profitAmount));
        } else {
            profitAmount = parseNumber($profitAmount.text());
        }
        applyValueClass($profitAmount, 'value-green', 'value-red', profitAmount >= 0);

        let profitRate = (base !== 0) ? ((profitAmount / base) * 100) : 0;
        if (liveProfitRate) {
            $profitRate.text(formatRate(profitRate));
        } else {
            profitRate = parseNumber($profitRate.text());
        }
        applyValueClass($profitRate, 'value-green', 'value-red', profitRate >= 0);

        let reachRate = 0;
        const denominator = target - base;
        if (denominator !== 0) {
            reachRate = ((current - base) / denominator) * 100;
        }
        if (liveReachRate) {
            $reachRate.text(formatRate(reachRate));
        } else {
            reachRate = parseNumber($reachRate.text());
        }
        applyValueClass($reachRate, 'value-blue', 'value-red', reachRate >= 0);
    }

    function fetchRealtimeAsset() {
        $.ajax({
            url: realtimeAssetUrl,
            method: 'GET',
            dataType: 'json',
            cache: false
        }).done(function(res) {
            if (!res || !res.success || !res.summary || typeof res.summary.total_asset === 'undefined') {
                return;
            }
            const currentAsset = parseFloat(res.summary.total_asset);
            if (!Number.isFinite(currentAsset)) return;
            updateLiveMetrics(currentAsset);
        });
    }

    fetchRealtimeAsset();
    setInterval(fetchRealtimeAsset, 8000);
});
</script>