#ifndef GRAPH_H
#define GRAPH_H

const char graph_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>CAN Signal Graph</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #43cea2 0%, #185a9d 100%);
            min-height: 100vh;
            padding: 10px;
        }
        .container {
            max-width: 1600px;
            margin: 0 auto;
            background: white;
            border-radius: 15px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            overflow: hidden;
        }
        .header {
            background: linear-gradient(135deg, #43cea2 0%, #185a9d 100%);
            color: white;
            padding: 20px;
            text-align: center;
        }
        .header h1 { font-size: 1.8em; margin-bottom: 5px; }
        .nav {
            background: #2c3e50;
            padding: 10px;
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
            justify-content: center;
        }
        .nav a {
            color: white;
            text-decoration: none;
            padding: 10px 15px;
            border-radius: 5px;
            transition: all 0.3s;
            font-size: 0.9em;
        }
        .nav a:hover { background: #34495e; }
        .nav a.active { background: #3498db; }
        .content { padding: 15px; }
        
        .dbc-upload {
            background: #f8f9fa;
            padding: 20px;
            border-radius: 10px;
            margin-bottom: 15px;
        }
        .upload-area {
            border: 3px dashed #43cea2;
            border-radius: 10px;
            padding: 30px 20px;
            text-align: center;
            cursor: pointer;
            transition: all 0.3s;
        }
        .upload-area:hover { background: #f0f9ff; border-color: #185a9d; }
        .upload-area input { display: none; }
        
        .signal-selector {
            background: #f8f9fa;
            padding: 15px;
            border-radius: 10px;
            margin-bottom: 15px;
        }
        
        .search-container {
            margin-bottom: 15px;
            position: relative;
        }
        .search-box {
            width: 100%;
            padding: 12px 12px 12px 40px;
            border: 2px solid #43cea2;
            border-radius: 8px;
            font-size: 0.95em;
            transition: all 0.3s;
        }
        .search-box:focus {
            outline: none;
            border-color: #185a9d;
            box-shadow: 0 0 0 3px rgba(67, 206, 162, 0.1);
        }
        .search-icon {
            position: absolute;
            left: 12px;
            top: 50%;
            transform: translateY(-50%);
            color: #43cea2;
            font-size: 1.2em;
        }
        .search-info {
            margin-top: 8px;
            padding: 8px 12px;
            background: #e3f2fd;
            border-radius: 5px;
            font-size: 0.85em;
            color: #185a9d;
            font-weight: 600;
        }
        .no-results {
            text-align: center;
            padding: 40px 20px;
            color: #666;
            font-size: 0.95em;
        }
        .no-results-icon {
            font-size: 3em;
            margin-bottom: 10px;
            opacity: 0.5;
        }
        
        .sort-controls {
            display: flex;
            gap: 10px;
            margin-bottom: 15px;
            align-items: center;
            flex-wrap: wrap;
        }
        .sort-label {
            font-weight: 600;
            color: #333;
            font-size: 0.9em;
        }
        .sort-btn {
            padding: 8px 15px;
            border: 2px solid #43cea2;
            background: white;
            border-radius: 5px;
            font-size: 0.85em;
            cursor: pointer;
            transition: all 0.3s;
            font-weight: 600;
        }
        .sort-btn:hover {
            background: #f0f9ff;
            transform: translateY(-2px);
        }
        .sort-btn.active {
            background: #43cea2;
            color: white;
        }
        .signal-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
            gap: 10px;
            margin-top: 15px;
        }
        .signal-item {
            background: white;
            padding: 12px;
            border-radius: 8px;
            border: 2px solid #ddd;
            cursor: pointer;
            transition: all 0.3s;
        }
        .signal-item:hover { border-color: #43cea2; transform: translateY(-2px); }
        .signal-item.selected { border-color: #185a9d; background: #e3f2fd; }
        .signal-name { font-weight: 600; color: #333; margin-bottom: 5px; font-size: 0.9em; }
        .signal-info { font-size: 0.8em; color: #666; line-height: 1.4; }
        .highlight {
            background-color: #ffeb3b;
            padding: 2px 0;
            font-weight: 600;
        }
        
        .controls {
            display: flex;
            gap: 8px;
            margin-bottom: 15px;
            flex-wrap: wrap;
        }
        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            font-size: 0.9em;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
        }
        .btn-primary { background: linear-gradient(135deg, #43cea2 0%, #185a9d 100%); color: white; }
        .btn-success { background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); color: white; }
        .btn-danger { background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%); color: white; }
        .btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0,0,0,0.3); }
        
        h2 {
            color: #333;
            margin: 20px 0 15px 0;
            padding-bottom: 8px;
            border-bottom: 3px solid #43cea2;
            font-size: 1.3em;
        }
        
        .status { padding: 12px; background: #fff3cd; border-radius: 5px; margin-bottom: 15px; font-size: 0.9em; }
        .status.success { background: #d4edda; color: #155724; }
        .status.error { background: #f8d7da; color: #721c24; }
        
        .selection-info {
            background: #e3f2fd;
            padding: 12px 15px;
            border-radius: 8px;
            margin-bottom: 15px;
            border-left: 4px solid #185a9d;
            font-size: 0.9em;
            color: #333;
        }
        .selection-info strong {
            color: #185a9d;
        }
        
        @media (max-width: 768px) {
            body { padding: 5px; }
            .header h1 { font-size: 1.5em; }
            .content { padding: 10px; }
            .signal-grid { grid-template-columns: 1fr; gap: 8px; }
            h2 { font-size: 1.1em; }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>CAN Signal Graph</h1>
            <p>Real-time Signal Visualization (Offline Mode)</p>
        </div>
        
        <div class="nav">
            <a href="/">Monitor</a>
            <a href="/transmit">Transmit</a>
            <a href="/graph" class="active">Graph</a>
        </div>
        
        <div class="content">
            <div id="status" class="status" style="display:none;"></div>
            
            <h2>Upload DBC File</h2>
            <div class="dbc-upload">
                <div class="upload-area" onclick="document.getElementById('dbc-file').click()">
                    <input type="file" id="dbc-file" accept=".dbc" onchange="loadDBCFile(event)">
                    <p style="font-size: 1.1em; margin-bottom: 8px;">Click to upload DBC</p>
                    <p style="color: #666; font-size: 0.85em;" id="dbc-status">No file loaded</p>
                </div>
            </div>
            
            <div id="signal-section" style="display:none;">
                <h2>Select Signals (Max 20)</h2>
                
                <div class="selection-info">
                    <strong>Selected: <span id="selected-count">0</span> / 20</strong>
                </div>
                
                <div class="controls">
                    <button class="btn btn-success" onclick="startGraphing()">Start</button>
                    <button class="btn btn-danger" onclick="stopGraphing()">Stop</button>
                    <button class="btn btn-primary" onclick="clearSelection()">Clear</button>
                    <button class="btn btn-primary" onclick="debugDBCInfo()">Debug Info</button>
                </div>
                
                <div class="signal-selector">
                    <div class="search-container">
                        <span class="search-icon">🔍</span>
                        <input type="text" 
                               id="search-box" 
                               class="search-box" 
                               placeholder="Search by signal name, CAN ID, message name, or unit..."
                               oninput="filterSignals()">
                        <div id="search-info" class="search-info" style="display:none;"></div>
                    </div>
                    
                    <div class="sort-controls">
                        <span class="sort-label">Sort by:</span>
                        <button class="sort-btn active" id="sort-selection" onclick="setSortMode('selection')">Selection Order</button>
                        <button class="sort-btn" id="sort-name-asc" onclick="setSortMode('name-asc')">Name (A→Z)</button>
                        <button class="sort-btn" id="sort-name-desc" onclick="setSortMode('name-desc')">Name (Z→A)</button>
                    </div>
                    <div id="signal-list" class="signal-grid"></div>
                </div>
                
                <div style="background: #e3f2fd; padding: 15px; border-radius: 8px; margin-top: 15px; border-left: 4px solid #185a9d;">
                    <p style="color: #333; font-size: 0.9em; margin: 0;">
                        <strong>ℹ️ Info:</strong> Click "Start" to open a new window with real-time graphs. 
                        Your selected signals will be saved automatically.
                    </p>
                </div>
            </div>
        </div>
    </div>
    
    <script>
        let ws;
        let dbcData = {};
        let selectedSignals = [];
        let allSignals = [];
        let filteredSignals = [];
        let sortMode = 'selection';
        let searchQuery = '';
        const MAX_SIGNALS = 20;
        
        function initWebSocket() {
            ws = new WebSocket('ws://' + window.location.hostname + ':81');
            
            ws.onopen = function() {
                console.log('WebSocket connected');
                showStatus('Connected', 'success');
            };
            
            ws.onclose = function() {
                console.log('WebSocket disconnected');
                showStatus('Disconnected - Reconnecting...', 'error');
                setTimeout(initWebSocket, 3000);
            };
            
            ws.onmessage = function(event) {
                try {
                    const data = JSON.parse(event.data);
                } catch(e) {
                    console.error('Error parsing WebSocket data:', e);
                }
            };
        }
        
        function loadDBCFile(event) {
            const file = event.target.files[0];
            if (!file) return;
            
            const reader = new FileReader();
            reader.onload = function(e) {
                const content = e.target.result;
                parseDBCContent(content);
                document.getElementById('dbc-status').textContent = file.name;
                
                saveDBCToLocalStorage(content, file.name);
            };
            reader.readAsText(file);
        }
        
        function saveDBCToLocalStorage(content, filename) {
            try {
                localStorage.setItem('dbc_content', content);
                localStorage.setItem('dbc_filename', filename);
                console.log('DBC saved to localStorage:', filename);
            } catch(e) {
                console.error('Failed to save DBC to localStorage:', e);
            }
        }
        
        function loadDBCFromLocalStorage() {
            try {
                const content = localStorage.getItem('dbc_content');
                const filename = localStorage.getItem('dbc_filename');
                
                if (content && filename) {
                    parseDBCContent(content);
                    document.getElementById('dbc-status').textContent = filename + ' (restored)';
                    showStatus('DBC file restored: ' + filename, 'success');
                    console.log('DBC restored from localStorage:', filename);
                    return true;
                }
            } catch(e) {
                console.error('Failed to load DBC from localStorage:', e);
            }
            return false;
        }
        
        function parseDBCContent(content) {
            dbcData = {messages: {}, valueTables: {}};
            allSignals = [];
            const lines = content.split('\n');
            let currentMessage = null;
            
            // 먼저 VAL_ 정의를 파싱
            for (let line of lines) {
                line = line.trim();
                
                if (line.startsWith('VAL_ ')) {
                    // VAL_ 형식: VAL_ <MessageID> <SignalName> <value> "<text>" ...;
                    const match = line.match(/VAL_\s+(\d+)\s+(\w+)\s+(.+);/);
                    if (match) {
                        const msgId = parseInt(match[1]);
                        const sigName = match[2];
                        const valuesStr = match[3];
                        
                        // Extended CAN ID 처리
                        const normalizedMsgId = (msgId & 0x80000000) ? (msgId & 0x1FFFFFFF) : msgId;
                        
                        const key = normalizedMsgId + '_' + sigName;
                        dbcData.valueTables[key] = {};
                        
                        // 값-텍스트 쌍 파싱
                        const valueMatches = valuesStr.matchAll(/(\d+)\s+"([^"]+)"/g);
                        for (let vm of valueMatches) {
                            dbcData.valueTables[key][parseInt(vm[1])] = vm[2];
                        }
                        
                        console.log('Parsed Value Table: ' + key, dbcData.valueTables[key]);
                    }
                }
            }
            
            // 메시지와 시그널 파싱
            for (let line of lines) {
                line = line.trim();
                
                if (line.startsWith('BO_ ')) {
                    const match = line.match(/BO_\s+(\d+)\s+(\w+)\s*:\s*(\d+)/);
                    if (match) {
                        let id = parseInt(match[1]);
                        const name = match[2];
                        const dlc = parseInt(match[3]);
                        
                        if (id & 0x80000000) {
                            id = id & 0x1FFFFFFF;
                        }
                        
                        currentMessage = {
                            id: id,
                            name: name,
                            dlc: dlc,
                            signals: []
                        };
                        dbcData.messages[id] = currentMessage;
                        
                        console.log('Parsed Message: ID=' + id + ' (0x' + id.toString(16).toUpperCase() + ') Name=' + name + ' DLC=' + dlc);
                    }
                }
                else if (line.startsWith('SG_ ') && currentMessage) {
                    const match = line.match(/SG_\s+(\w+)\s*:\s*(\d+)\|(\d+)@([01])([+-])\s*\(([^,]+),([^)]+)\)\s*\[([^\]]+)\]\s*"([^"]*)"/);
                    if (match) {
                        const signalName = match[1];
                        const signal = {
                            name: signalName,
                            startBit: parseInt(match[2]),
                            bitLength: parseInt(match[3]),
                            byteOrder: match[4] === '0' ? 'motorola' : 'intel',
                            signed: match[5] === '-',
                            factor: parseFloat(match[6]),
                            offset: parseFloat(match[7]),
                            minMax: match[8],
                            unit: match[9],
                            messageId: currentMessage.id,
                            messageName: currentMessage.name,
                            messageDlc: currentMessage.dlc
                        };
                        
                        // Value Table 연결
                        const vtKey = currentMessage.id + '_' + signalName;
                        if (dbcData.valueTables[vtKey]) {
                            signal.valueTable = dbcData.valueTables[vtKey];
                            console.log('  Signal with value table: ' + signalName, signal.valueTable);
                        }
                        
                        currentMessage.signals.push(signal);
                        allSignals.push(signal);
                        
                        console.log('  Signal: ' + signal.name + ' [' + signal.startBit + '|' + signal.bitLength + '] Unit: ' + signal.unit);
                    }
                }
            }
            
            filteredSignals = [...allSignals];
            displaySignals();
            showStatus('DBC loaded: ' + Object.keys(dbcData.messages).length + ' messages, ' + allSignals.length + ' signals', 'success');
        }
        
        function filterSignals() {
            searchQuery = document.getElementById('search-box').value.toLowerCase().trim();
            
            if (searchQuery === '') {
                filteredSignals = [...allSignals];
                document.getElementById('search-info').style.display = 'none';
            } else {
                filteredSignals = allSignals.filter(signal => {
                    const nameMatch = signal.name.toLowerCase().includes(searchQuery);
                    
                    const idDecMatch = signal.messageId.toString().includes(searchQuery);
                    
                    const idHex = signal.messageId.toString(16).toLowerCase();
                    const idHexMatch = idHex.includes(searchQuery) || 
                                      idHex.includes(searchQuery.replace('0x', ''));
                    
                    const msgNameMatch = signal.messageName.toLowerCase().includes(searchQuery);
                    
                    const unitMatch = signal.unit && signal.unit.toLowerCase().includes(searchQuery);
                    
                    return nameMatch || idDecMatch || idHexMatch || msgNameMatch || unitMatch;
                });
                
                const searchInfo = document.getElementById('search-info');
                searchInfo.textContent = 'Showing ' + filteredSignals.length + ' of ' + allSignals.length + ' signals';
                searchInfo.style.display = 'block';
            }
            
            displaySignals();
        }
        
        function highlightText(text, query) {
            if (!query) return text;
            
            const regex = new RegExp('(' + query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')', 'gi');
            return text.replace(regex, '<span class="highlight">$1</span>');
        }
        
        function setSortMode(mode) {
            sortMode = mode;
            
            document.querySelectorAll('.sort-btn').forEach(btn => btn.classList.remove('active'));
            document.getElementById('sort-' + mode).classList.add('active');
            
            displaySignals();
            
            console.log('Sort mode changed to:', mode);
        }
        
        function sortSignals(signals) {
            if (sortMode === 'name-asc') {
                return signals.sort((a, b) => a.name.localeCompare(b.name));
            } else if (sortMode === 'name-desc') {
                return signals.sort((a, b) => b.name.localeCompare(a.name));
            } else {
                const selected = signals.filter(s => isSignalSelected(s));
                const notSelected = signals.filter(s => !isSignalSelected(s));
                return [...selected, ...notSelected];
            }
        }
        
        function isSignalSelected(signal) {
            return selectedSignals.some(s => 
                s.messageId === signal.messageId && s.name === signal.name);
        }
        
        function displaySignals() {
            const signalList = document.getElementById('signal-list');
            signalList.innerHTML = '';
            
            const sortedSignals = sortSignals([...filteredSignals]);
            
            if (sortedSignals.length === 0) {
                signalList.innerHTML = '<div class="no-results">' +
                    '<div class="no-results-icon">🔍</div>' +
                    '<div><strong>No signals found</strong></div>' +
                    '<div style="margin-top: 8px; color: #999;">Try a different search term</div>' +
                    '</div>';
                return;
            }
            
            sortedSignals.forEach(signal => {
                const item = document.createElement('div');
                item.className = 'signal-item';
                
                if (isSignalSelected(signal)) {
                    item.classList.add('selected');
                }
                
                item.onclick = () => toggleSignal(signal, item);
                
                const canIdHex = '0x' + signal.messageId.toString(16).toUpperCase().padStart(3, '0');
                
                const highlightedName = highlightText(signal.name, searchQuery);
                const highlightedId = highlightText(canIdHex, searchQuery);
                const highlightedMsgName = highlightText(signal.messageName, searchQuery);
                const unitText = signal.unit ? highlightText(signal.unit, searchQuery) : 'no unit';
                
                item.innerHTML = 
                    '<div class="signal-name">' + highlightedName + '</div>' +
                    '<div class="signal-info">' +
                    'CAN ID: ' + highlightedId + ' (' + highlightedMsgName + ')<br>' +
                    'Bit: ' + signal.startBit + '|' + signal.bitLength + ' | ' + unitText +
                    '</div>';
                
                signalList.appendChild(item);
            });
            
            document.getElementById('signal-section').style.display = 'block';
            
            updateSelectionCount();
        }
        
        function updateSelectionCount() {
            document.getElementById('selected-count').textContent = selectedSignals.length;
        }
        
        function toggleSignal(signal, element) {
            const index = selectedSignals.findIndex(s => 
                s.messageId === signal.messageId && s.name === signal.name);
            
            if (index >= 0) {
                selectedSignals.splice(index, 1);
                element.classList.remove('selected');
            } else {
                if (selectedSignals.length >= MAX_SIGNALS) {
                    showStatus('Max ' + MAX_SIGNALS + ' signals!', 'error');
                    return;
                }
                selectedSignals.push(signal);
                element.classList.add('selected');
            }
            
            updateSelectionCount();
            saveSelectedSignals();
            
            if (sortMode === 'selection') {
                displaySignals();
            }
        }
        
        function clearSelection() {
            selectedSignals = [];
            document.querySelectorAll('.signal-item').forEach(item => {
                item.classList.remove('selected');
            });
            
            updateSelectionCount();
            saveSelectedSignals();
            
            if (sortMode === 'selection') {
                displaySignals();
            }
        }
        
        function saveSelectedSignals() {
            try {
                localStorage.setItem('selected_signals', JSON.stringify(selectedSignals));
                localStorage.setItem('sort_mode', sortMode);
                console.log('Saved', selectedSignals.length, 'signals with sort mode:', sortMode);
            } catch(e) {
                console.error('Failed to save selected signals:', e);
            }
        }
        
        function loadSelectedSignals() {
            try {
                const saved = localStorage.getItem('selected_signals');
                const savedSortMode = localStorage.getItem('sort_mode');
                
                if (savedSortMode) {
                    sortMode = savedSortMode;
                    document.querySelectorAll('.sort-btn').forEach(btn => btn.classList.remove('active'));
                    document.getElementById('sort-' + sortMode).classList.add('active');
                }
                
                if (saved) {
                    const signals = JSON.parse(saved);
                    
                    signals.forEach(savedSignal => {
                        let found = false;
                        for (let msgId in dbcData.messages) {
                            const msg = dbcData.messages[msgId];
                            const signal = msg.signals.find(s => 
                                s.messageId === savedSignal.messageId && s.name === savedSignal.name);
                            
                            if (signal) {
                                selectedSignals.push(signal);
                                found = true;
                                break;
                            }
                        }
                    });
                    
                    displaySignals();
                    
                    if (selectedSignals.length > 0) {
                        showStatus('Restored ' + selectedSignals.length + ' selected signals', 'success');
                    }
                }
            } catch(e) {
                console.error('Failed to load selected signals:', e);
            }
        }
        
        function startGraphing() {
            if (selectedSignals.length === 0) {
                showStatus('Select at least one signal!', 'error');
                return;
            }
            
            saveSelectedSignals();
            
            const width = 1400;
            const height = 900;
            const left = (screen.width - width) / 2;
            const top = (screen.height - height) / 2;
            
            window.open(
                '/graph-view',
                'CAN_Graph_Viewer',
                'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',resizable=yes,scrollbars=yes'
            );
            
            showStatus('Graph viewer opened in new window', 'success');
        }
        
        function stopGraphing() {
            showStatus('Use the stop button in the graph viewer window', 'error');
        }
        
        function debugDBCInfo() {
            console.log('=== DBC Debug Info ===');
            console.log('Total Messages: ' + Object.keys(dbcData.messages).length);
            console.log('Total Signals: ' + allSignals.length);
            console.log('Total Value Tables: ' + Object.keys(dbcData.valueTables).length);
            
            console.log('\n=== Value Tables ===');
            for (let key in dbcData.valueTables) {
                console.log(key + ':', dbcData.valueTables[key]);
            }
            
            console.log('\n=== Messages ===');
            Object.values(dbcData.messages).forEach(msg => {
                console.log('\nMessage: ' + msg.name);
                console.log('  ID: ' + msg.id + ' (0x' + msg.id.toString(16).toUpperCase() + ')');
                console.log('  DLC: ' + msg.dlc);
                console.log('  Signals: ' + msg.signals.length);
                
                msg.signals.forEach(sig => {
                    console.log('    - ' + sig.name + ': ' + sig.startBit + '|' + sig.bitLength + ' [' + sig.unit + ']');
                    if (sig.valueTable) {
                        console.log('      Value Table:', sig.valueTable);
                    }
                });
            });
            
            showStatus('Debug info printed to console (F12)', 'success');
        }
        
        function showStatus(message, type) {
            const status = document.getElementById('status');
            status.textContent = message;
            status.className = 'status ' + type;
            status.style.display = 'block';
            setTimeout(() => { status.style.display = 'none'; }, 5000);
        }
        
        const uploadArea = document.querySelector('.upload-area');
        ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
            uploadArea.addEventListener(eventName, e => {
                e.preventDefault();
                e.stopPropagation();
            });
        });
        
        ['dragenter', 'dragover'].forEach(eventName => {
            uploadArea.addEventListener(eventName, () => {
                uploadArea.style.borderColor = '#185a9d';
                uploadArea.style.background = '#f0f9ff';
            });
        });
        
        ['dragleave', 'drop'].forEach(eventName => {
            uploadArea.addEventListener(eventName, () => {
                uploadArea.style.borderColor = '#43cea2';
                uploadArea.style.background = '';
            });
        });
        
        uploadArea.addEventListener('drop', function(e) {
            const file = e.dataTransfer.files[0];
            if (file && file.name.endsWith('.dbc')) {
                const reader = new FileReader();
                reader.onload = function(ev) {
                    parseDBCContent(ev.target.result);
                    document.getElementById('dbc-status').textContent = file.name;
                    
                    saveDBCToLocalStorage(ev.target.result, file.name);
                };
                reader.readAsText(file);
            }
        });
        
        window.addEventListener('load', function() {
            if (loadDBCFromLocalStorage()) {
                setTimeout(() => loadSelectedSignals(), 100);
            }
        });
        
        initWebSocket();
    </script>
</body>
</html>
)rawliteral";

#endif