📄 neural-network.html
/home/palash/git/build-your-own-nn/visualizers/neural-network.html
Language: html • Lines: 1439
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Neural Network Visualizer | Interactive Machine Learning Lab - Palash Kanti Kundu</title>
    <meta name="title" content="Neural Network Visualizer | Interactive Machine Learning Lab">
    <meta name="description"
        content="A real-time, from-scratch neural network engine. Build architectures, tune hyperparameters, and visualize weight traces and decision boundaries in your browser.">
    <meta name="keywords"
        content="Neural Network, Machine Learning, JavaScript, AI Visualization, Deep Learning Tutorial, XOR Problem, Backpropagation Visualizer">

    <meta property="og:type" content="website">
    <meta property="og:url" content="https://palashkantikundu.in">
    <meta property="og:title" content="Neural Network Demystified: Interactive Trace Engine">
    <meta property="og:description"
        content="Explore the math behind AI. Watch synapses fire and weights adjust in real-time with this browser-based neural network architect.">

    <meta property="twitter:card" content="summary_large_image">
    <meta property="twitter:title" content="Neural Network Demystified: Interactive Trace Engine">
    <meta property="twitter:description"
        content="Real-time neural network training and weight tracing in pure JavaScript. Build, train, and demystify AI architecture.">
    <script src="https://cdn.jsdelivr.net/npm/plotly.js-dist@3.3.1/plotly.min.js"></script>
    <style>
        :root {
            --bg: #12161d;
            --panel: #1d1f2e;
            --border: #334155;
            --neon-cyan: #06b6d4;
            --neon-purple: #8b5cf6;
            --text: #f1f5f9;
            --sub-text: #94a3b8;
            --danger: #ef4444;
        }

        input:disabled {
            cursor: not-allowed;
            color: var(--neon-cyan);
            border-color: var(--border);
            font-weight: bold;
            text-align: center;
        }

        #inputX,
        #inputY {
            width: 100%;
            margin-bottom: 12px;
            /* Gap after input boxes */
            display: block;
        }

        * {
            box-sizing: border-box;
        }

        @media (max-width: 1024px) {
            .dashboard {
                display: flex;
                flex-direction: column;
                width: 100%;
                padding: 10px;
            }

            .panel {
                width: 100% !important;
                margin-bottom: 15px;
                /* Prevent plotly from stretching the container */
                overflow: hidden;
            }

            #plot,
            #lossPlot {
                width: 100% !important;
                height: 350px !important;
                /* Slightly shorter for mobile screens */
            }
        }

        body {
            font-family: 'Inter', 'Consolas', monospace;
            background: var(--bg);
            color: var(--text);
            padding: 20px;
            margin: 0;
            letter-spacing: 0.5px;
        }

        .spinner {
            /* Size of the spinner */
            width: 30px;
            height: 30px;
            align-self: center;
            align-items: center;

            /* Create a circle */
            border-radius: 50%;

            /* Define the border: solid, color, and thickness */
            border: 5px solid rgba(0, 0, 0, 0.1);
            /* Light gray border for the track */

            /* Apply a contrasting color to only one part of the border */
            border-top-color: #3498db;
            /* Blue color for the moving part */

            /* Apply the animation */
            animation: spin 1s linear infinite;
        }

        /* Define the rotation animation */
        @keyframes spin {
            0% {
                transform: rotate(0deg);
            }

            100% {
                transform: rotate(360deg);
            }
        }


        .dashboard {
            display: grid;
            grid-template-columns: 1fr;
            gap: 20px;
            width: 95vw;
            max-width: 1800px;
            margin: auto;
        }

        .config-row {
            display: flex;
            gap: 8px;
            margin-bottom: 8px;
            align-items: center;
            width: 100%;
        }

        /* Fixed Sidebar Vibe */
        .controls {
            grid-column: 1;

            display: flex;
            flex-direction: column;
            gap: 12px;
            background: var(--panel);
            padding: 20px;
            border: 1px solid var(--border);
            border-radius: 12px;
            height: auto;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
        }

        @media (min-width: 1024px) {
            .dashboard {
                grid-template-columns: 320px 1fr 1fr;
            }

            .controls {
                grid-row: 1 / span 3;
                /* Sidebar spans the height again */
            }

            .panel[style*="grid-column: span 2;"] {
                grid-column: span 2;
            }
        }

        /* Tablet: 2 columns for plots, sidebar on top */
        @media (min-width: 768px) and (max-width: 1023px) {
            .dashboard {
                grid-template-columns: 1fr;
            }

            .controls {
                grid-row: auto;
                grid-column: 1;
            }

            .panel {
                grid-column: 1 !important;
                /* Force plots to take full width */
            }
        }

        .controls>div {
            border-bottom: 1px solid var(--border);
            padding-bottom: 5px;
            margin-bottom: 5px;
        }

        .training-grid {
            display: grid;
            grid-template-columns: 1fr 1fr 1fr;
            gap: 10px;
            margin-bottom: 15px;
        }

        .training-grid input {
            width: 100%;
            /* Prevents horizontal overflow */
        }

        /* Dynamic border for layer rows */
        .layer-row-container {
            border-left: 6px solid #00F0FF;
            /* Default Tanh */
            padding-left: 10px;
            transition: border-color 0.3s ease;
        }

        /* Visual feedback for paused state */
        .btn-paused {
            animation: pulse-yellow 1.5s infinite;
            border-color: #fbbf24 !important;
            color: #fbbf24 !important;
        }

        @keyframes pulse-yellow {
            0% {
                opacity: 1;
            }

            50% {
                opacity: 0.6;
            }

            100% {
                opacity: 1;
            }
        }

        .panel {
            background: var(--panel);
            border: 1px solid var(--border);
            padding: 15px;
            border-radius: 12px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.8);
            width: 100%;
            /* Force panel to fill grid cell */
            min-width: 0;
            /* Prevents grid blowout */
        }

        #plot,
        #lossPlot {
            width: 100% !important;
            /* Force plotly container to fill panel */
            height: 400px;
        }

        /* High-Visibility Training Button */
        #trainBtn {
            width: 100%;
            margin-bottom: 8px;
            color: #10b981;
            border-color: #10b981;
        }


        /* Dataset and Control Buttons */
        .btn-group {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 4px;
        }

        .btn-group button {
            font-size: 0.65rem;
            padding: 6px 2px;
        }

        /* Fix Overflows */
        .config-row {
            display: flex;
            gap: 4px;
            margin-bottom: 6px;
            align-items: center;
        }

        .l-size {
            width: 60px !important;
        }

        /* Prevents crowding */
        .l-act {
            flex: 1;
        }

        .delete-btn {
            background: rgba(239, 68, 68, 0.1);
            color: var(--danger);
            border: 1px solid var(--danger);
            padding: 4px 8px;
        }

        .active-dataset {
            background: rgba(6, 182, 212, 0.2) !important;
            /* Subtle Cyan tint */
            color: var(--neon-cyan) !important;
            border: 1px solid var(--neon-cyan) !important;
            box-shadow: 0 0 8px rgba(6, 182, 212, 0.3);
            font-weight: 600;
        }

        canvas {
            background: #1e1e24;
            width: 100%;
            height: 400px;
            border: 1px solid var(--border);
            border-radius: 8px;
            margin-top: 10px;
        }

        button {
            cursor: pointer;
            font-family: inherit;
            border-radius: 6px;
            padding: 8px 12px;
            font-size: 0.75rem;
            font-weight: 600;
            transition: all 0.2s;
            border: 1px solid var(--border);
            background: #334155;
            color: var(--text);
            text-transform: uppercase;
            margin-top: 5px;
            margin-bottom: 5px;
        }

        button:hover {
            background: #475569;
        }

        btn-primary {
            background: var(--neon-cyan);
            color: #000;
            border: none;
        }

        .btn-primary:hover {
            background: #22d3ee;
            box-shadow: 0 0 12px rgba(6, 182, 212, 0.4);
        }

        input,
        select,
        button {
            background: #272c30;
            border: 1px solid var(--border);
            color: var(--text);
            padding: 8px;
            border-radius: 4px;
            transition: all 0.2s ease;
        }

        input:focus,
        select:focus {
            border-color: var(--neon-cyan);
            box-shadow: 0 0 8px rgba(0, 240, 255, 0.2);
            outline: none;
        }

        /* Numerical Matrix Styling */
        #weightInspector {
            grid-column: 2 / span 2;
            max-height: 250px;
            overflow-x: auto;
            white-space: nowrap;
            border-top: 2px solid var(--border);
            padding-top: 15px;
        }

        .matrix-table {
            border-collapse: collapse;
            margin: 5px;
            display: inline-block;
            vertical-align: top;
            border: 1px solid var(--border);
            background: #0c0c0b;
            font-size: 0.7rem;
        }

        .matrix-table td {
            border: 1px solid #011b27;
            padding: 4px 8px;
            min-width: 50px;
            font-family: 'Consolas', monospace;
        }

        .layer-title {
            color: var(--neon-cyan);
            text-transform: uppercase;
            font-size: 0.75rem;
            padding: 5px;
            background: #0a0a0a;
            border-bottom: 1px solid var(--border);
        }

        label {
            font-size: 0.7rem;
            color: var(--sub-text);
            text-transform: uppercase;
            font-weight: 600;
            display: block;
            margin-bottom: 4px;
        }

        .btn-group button {
            flex: 1 1 calc(33% - 4px);
            font-size: 0.65rem;
            padding: 6px 2px;
            cursor: pointer;
        }


        .delete-btn {
            flex: 0 0 30px;
            background: rgba(255, 0, 85, 0.1);
            border: 1px solid #ff0055;
            color: #ff0055;
            cursor: pointer;
        }
    </style>
</head>

<body>
    <h2 style="text-align: center;">Neural Network Visualizer</h2>
    <p style="text-align: center;">This tool accompanies the learning guide: <a href="/#neural-network">Neural Network</a></p>
    <div class="dashboard">
        <div class="controls">
            <div>
                <label>Network Structure</label>
                <div class="config-row"
                    style="opacity: 0.7; margin-bottom: 10px; border-bottom: 1px dashed var(--border); padding-bottom: 10px;">
                    <span style="font-size: 0.7rem; width: 60px;">INPUT:</span>
                    <input type="text" id="inputFeaturesCount" disabled value="2" class="l-size"
                        style="background: transparent; border-style: dotted;">
                    <span style="font-size: 0.6rem; color: var(--sub-text);">Features (based on input data)</span>
                </div>
                <div id="layersConfig">
                    <div class="layer-row-container" style="display:flex; gap:2px; margin-bottom: 20px;">
                        <input type="number" class="l-size network-ctl" onchange="buildNetwork()" value="4">
                        <select class="l-act network-ctl" onchange="updateLayerColor(this)">
                            <option value="tanh">Tanh</option>
                            <option value="relu">ReLU</option>
                            <option value="lrelu">Leaky ReLU</option>
                        </select>
                    </div>
                </div>
                <button onclick="addLayerUI()">+ ADD LAYER</button>
                <div style="margin-top: 20px; margin-bottom: 10px; border-top: 0px solid #444;">
                    <label>Output Layer</label>
                    <div class="layer-row-container"
                        style="display: flex; gap: 4px; align-items: center;  border-left:  solid #FF0055;">
                        <input type="text" id="outputNeuronsCount" disabled value="1" class="l-size"
                            style="width: 50px !important; background: transparent; border-style: dotted;">
                        <select id="outputAct" class="network-ctl" onchange="updateLayerColor(this)">
                            <option value="sigmoid">Sigmoid (Classification 0-1)</option>
                            <option value="softmax">Softmax (Multi-class)</option>
                            <option value="linear">Regression (Linear)</option>
                        </select>
                    </div>
                </div>
            </div>
            <div>
                <label>Data Management</label>
                <input id="inputX" value="0,0; 0,1; 1,0; 1,1">
                <input id="inputY" value="1; 0; 0; 1">
                <div class="btn-group" style="margin-bottom: 15px;">
                    <button onclick="setDataset('OR', this)">OR</button>
                    <button onclick="setDataset('XOR', this)">XOR</button>
                    <button onclick="setDataset('XNOR', this)">XNOR</button>
                    <button onclick="setDataset('XOR3', this)">3 Input XOR</button>
                    <button onclick="setDataset('XNOR3', this)">3 Input XNOR</button>
                    <button onclick="setDataset('SIN', this)">SIN</button>
                    <button onclick="setDataset('COS', this)">COS</button>
                    <button onclick="setDataset('MOON', this)">MOONS</button>
                    <button onclick="setColorDataset(this)">Color Temp</button>
                </div>
            </div>
            <div>
                <label>Training Parameters</label>
                <div class="training-grid">
                    <div>
                        <label>Epochs</label>
                        <input type="number" id="epochs" value="5000" class="network-ctl">
                    </div>
                    <div>
                        <label>LR</label>
                        <input type="number" id="lr" value="0.1" class="network-ctl" step="0.01" style="width: 100%;">
                    </div>
                    <div>
                        <label>Delay</label>
                        <input type="number" id="delay" value="10" class="network-ctl" />
                    </div>
                </div>
            </div>
            <div>
                <div class="btn-group">
                    <button id="trainBtn" class="control-btn" onclick="handleTrainClick()"
                        style="font-size: 20px; grid-column: span 2;" title="Start/Pause">
                        &#9658;
                    </button>
                    <button onclick="resetEverything()" style="font-size: 20px; border-color: #ef4444; color: #ef4444;"
                        class="control-btn" title="STOP">
                        &#x23F9;
                    </button>
                </div>
                <div id="status" style="margin-top:10px;">Awaiting Initialization</div>
            </div>
            <div>
                <label>Model Inference & Live Test</label>
                <div id="dynamicTestUI"
                    style="background: #101029; padding: 10px; border: 1px solid #333; margin-top: 5px; min-height: 80px;">
                </div>
                <div id="predictionResult" style="margin-top: 10px; font-size: 0.85rem;">
                    Result: awaiting input...
                </div>
            </div>
        </div>
        <div class="panel" style="grid-column: span 2;">
            <label>Topology & Weight Magnitude (Live)</label>
            <canvas id="netCanvas"></canvas>
        </div>
        <div class="panel"><label>Prediction Plot</label>
            <div id="plot" style="height: 400px;"></div>
        </div>
        <div class="panel"><label>Loss History</label>
            <div id="lossPlot" style="height: 400px;"></div>
        </div>
        <div id="weightInspector" class="panel">
            <label style="margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid var(--border);">Numerical
                Weight & Bias Matrices (Trace)</label>
            <div id="matrixDisplay"></div>
        </div>
    </div>

    <script>
        let layers = [], isTraining = false, isPaused = false, stopFlag = false, lossHistory = [];

        const actColors = {
            input: '#ffffff',
            relu: '#7000FF',    // Neon Purple
            tanh: '#00F0FF',    // Neon Cyan
            sigmoid: '#FF0055', // Neon Pink
            lrelu: '#f97316',
            softmax: '#00ff41',
            linear: '#FDE047'
        };


        const Act = {
            relu: { f: x => Math.max(0, x), df: x => x > 0 ? 1 : 0 },
            tanh: { f: x => Math.tanh(x), df: x => 1 - Math.pow(Math.tanh(x), 2) },
            sigmoid: { f: x => 1 / (1 + Math.exp(-x)), df: x => { let s = 1 / (1 + Math.exp(-x)); return s * (1 - s); } },
            lrelu: { f: x => x > 0 ? x : 0.01 * x, df: x => x > 0 ? 1 : 0.01 },
            softmax: {
                // Softmax is applied to a whole row (array)
                f: (arr) => {
                    const max = Math.max(...arr); // For numerical stability
                    const exps = arr.map(v => Math.exp(v - max));
                    const sum = exps.reduce((a, b) => a + b);
                    return exps.map(v => v / sum);
                },
                df: x => 1
            },
            linear: { f: x => x, df: x => 1 }
        };

        class Layer {
            constructor(inD, outD, act) {
                this.act = act;
                let s = Math.sqrt(2 / (inD + outD)); // Xavier Init
                this.w = Array.from({ length: inD }, () => Array.from({ length: outD }, () => (Math.random() * 2 - 1) * s));
                this.b = new Array(outD).fill(0);
                this.lastZ = []; this.lastA = [];
            }
            forward(X) {
                // Linear Step: Z = XW + B
                this.lastZ = X.map(row => this.b.map((b, j) => row.reduce((sum, v, i) => sum + v * this.w[i][j], 0) + b));

                // Activation Step
                if (this.act === 'softmax') {
                    this.lastA = this.lastZ.map(row => Act.softmax.f(row));
                } else {
                    this.lastA = this.lastZ.map(row => row.map(v => Act[this.act].f(v)));
                }
                return this.lastA;
            }
        }

        function updateLayerColor(selectElement) {
            const color = actColors[selectElement.value] || '#ffffff';
            selectElement.parentElement.style.borderLeft = `6px solid ${color}`;
            buildNetwork();
        }

        let currentMode = 'LOGIC'; // Default

        function updateTestUI(type) {
            const container = document.getElementById('dynamicTestUI');
            container.innerHTML = ''; // Clear previous
            currentMode = type;

            if (['OR', 'XOR', 'XNOR', 'XOR3', 'XNOR3'].includes(type)) {
                const inputs = type.includes('3') ? 3 : 2;
                let html = '<div style="display:flex; gap:15px; align-items:center;">';
                for (let i = 0; i < inputs; i++) {
                    html += `<label style="color:#888">BIT ${i} <input type="checkbox" class="logic-sw" onchange="runLiveTest()" style="width:20px; height:20px;"></label>`;
                }
                html += '<div id="bulb" style="width:30px; height:30px; border-radius:50%; background:#272c30; border:2px solid #555; margin-left:20px; transition: 0.2s;"></div>';
                html += '<span id="logicVal" style="margin-left:10px; font-weight:bold;">0.00</span></div>';
                container.innerHTML = html;
            }
            else if (['SIN', 'COS'].includes(type)) {
                container.innerHTML = `
                    <div style="display:flex; flex-direction:column; gap:8px;">
                        <label>Range Evaluation:</label>
                        <div style="display:flex; flex-wrap: wrap; gap:5px;">
                            <input type="number" id="rangeStart" value="0" step="0.1" style="flex: 1 1 60px; min-width: 0;" oninput="runLiveTest()">
                            <input type="number" id="rangeEnd" value="6.28" step="0.1" style="flex: 1 1 60px; min-width: 0;" oninput="runLiveTest()">
                            <input type="number" id="rangeSteps" value="50" style="flex: 0.5 1 40px; min-width: 0;" oninput="runLiveTest()">
                        </div>
                        <div id="mathResultDisplay" style="color:var(--neon-cyan); font-size:0.8rem; margin-top:5px;">
                            Generating curve...
                        </div>
                    </div>`;
            }
            else if (type === 'MOON') {
                container.innerHTML = `
                    <div style="display:flex; flex-direction:column; gap:10px;">
                        <div style="display:flex; flex-wrap: wrap; gap:8px;">
                            <div style="flex:1; min-width: 80px;">
                                <label style="display:block; font-size:9px;">INPUT 0 (X)</label>
                                <input type="number" id="moonX" value="0.5" step="0.1" oninput="runLiveTest()" style="width:100%; min-width:0;">
                            </div>
                            <div style="flex:1; min-width: 80px;">
                                <label style="display:block; font-size:9px;">INPUT 1 (Y)</label>
                                <input type="number" id="moonY" value="0.2" step="0.1" oninput="runLiveTest()" style="width:100%; min-width:0;">
                            </div>
                        </div>
                        <div style="display:flex; justify-content:space-around; align-items:center; background:#000; padding:10px; border-radius:5px; border: 1px solid #333;">
                            <div style="text-align:center">
                                <div id="orbA" style="width:25px; height:25px; border-radius:50%; background:#333; border:2px solid #ff3131; margin:auto;"></div>
                                <label style="font-size:10px">CLASS A</label>
                            </div>
                            <div style="text-align:center">
                                <div id="orbB" style="width:25px; height:25px; border-radius:50%; background:#333; border:2px solid #00ff41; margin:auto;"></div>
                                <label style="font-size:10px">CLASS B</label>
                            </div>
                        </div>
                    </div>`;
            }
            else if (type === 'COLOR') {
                container.innerHTML = `
            <label>Pick Color:</label>
            <input type="color" id="colPick" oninput="runLiveTest()" style="height:40px;">`;
            }
        }
        function updateMathLabel(val) {
            const unit = document.getElementById('mathUnit').value;
            const displayVal = unit === 'deg' ? (val * 180 / Math.PI).toFixed(1) + "°" : val + " rad";
            document.getElementById('mathValDisplay').innerText = displayVal;
            runLiveTest();
        }
        // Helper to run prediction automatically on UI change
        function runLiveTest() {

            if (['SIN', 'COS'].includes(currentMode)) {
                const start = parseFloat(document.getElementById('rangeStart').value);
                const end = parseFloat(document.getElementById('rangeEnd').value);
                const steps = parseInt(document.getElementById('rangeSteps').value);

                let rangeInputs = [];
                let xValues = [];

                for (let i = 0; i <= steps; i++) {
                    let val = start + (i * (end - start) / steps);
                    xValues.push(val);
                    rangeInputs.push([val]); // Network expects array of arrays [[x1], [x2]...]
                }

                if (layers.length > 0) executeRangeInference(xValues, rangeInputs);
                return
            }

            let inputVector = [];

            if (['OR', 'XOR', 'XNOR', 'XOR3', 'XNOR3'].includes(currentMode)) {
                inputVector = Array.from(document.querySelectorAll('.logic-sw')).map(sw => sw.checked ? 1 : 0);
            } else if (['SIN', 'COS'].includes(currentMode)) {
                inputVector = [parseFloat(document.querySelector('#dynamicTestUI input[type="range"]').value)];
            } else if (currentMode === 'MOON') {
                inputVector = [parseFloat(document.getElementById('moonX').value), parseFloat(document.getElementById('moonY').value)];
            } else if (currentMode === 'COLOR') {
                const hex = document.getElementById('colPick').value;
                const r = parseInt(hex.slice(1, 3), 16) / 255;
                const g = parseInt(hex.slice(3, 5), 16) / 255;
                const b = parseInt(hex.slice(5, 7), 16) / 255;
                inputVector = [r, g, b];
            }

            if (layers.length > 0) executeInference(inputVector);
        }

        function executeRangeInference(xValues, rangeInputs) {
            let currentA = rangeInputs;
            for (let L of layers) currentA = L.forward(currentA);

            // Rescale outputs back to -1 to 1 range for visualization
            const yValues = currentA.map(out => (out[0] * 2) - 1);

            const trace = {
                x: xValues,
                y: yValues,
                mode: 'lines',
                name: 'Model Range Prediction',
                line: { color: '#00ff41', width: 3 }
            };

            const layout = {
                paper_bgcolor: 'rgba(0,0,0,0)',
                plot_bgcolor: 'rgba(0,0,0,0)',
                font: { color: '#0f0' },
                margin: { t: 10, b: 30, l: 30, r: 10 },
                xaxis: {
                    gridcolor: 'rgba(51, 65, 85, 0.3)', // Very faint grid lines
                    zerolinecolor: 'rgba(51, 65, 85, 0.5)', title: 'Input (rad)'
                },
                yaxis: {
                    gridcolor: 'rgba(51, 65, 85, 0.3)', // Very faint grid lines
                    zerolinecolor: 'rgba(51, 65, 85, 0.5)', title: 'f(x)'
                }
            };

            Plotly.react('plot', [trace], layout);
            document.getElementById('mathResultDisplay').innerText = `Plotted ${xValues.length} points across range.`;
        }

        function executeInference(inputVector) {
            if (['SIN', 'COS'].includes(currentMode)) {
                inputVector = [inputVector[0] % (Math.PI * 2)];
            }
            let currentA = [inputVector];
            for (let L of layers) currentA = L.forward(currentA);
            const outputs = currentA[0];

            const resultDiv = document.getElementById('predictionResult');

            if (['OR', 'XOR', 'XNOR', 'XOR3', 'XNOR3'].includes(currentMode)) {
                const val = outputs[0];
                document.getElementById('bulb').style.background = `rgba(0, 255, 65, ${val})`;
                document.getElementById('bulb').style.boxShadow = val > 0.5 ? `0 0 15px #00ff41` : 'none';
                document.getElementById('logicVal').innerText = val.toFixed(3);
                resultDiv.innerHTML = `Signal: ${(val * 100).toFixed(1)}%`;

                resultDiv.innerHTML = `
                    <div style="margin-top: 15px;">
                        <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
                            <label style="font-size: 0.7rem; color: var(--sub-text); letter-spacing: 1px;">SIGNAL STRENGTH</label>
                            <span style="color: #00ff41; font-family: monospace; font-weight: bold;">${(val * 100).toFixed(1)}%</span>
                        </div>
                        <div style="width: 100%; height: 8px; background: #1a1a1a; border-radius: 4px; border: 1px solid #333; overflow: hidden;">
                            <div id="signalLine" style="
                                width: ${val * 100}%; 
                                height: 100%; 
                                background: linear-gradient(90deg, #00441b, #00ff41); 
                                transition: width 0.2s ease-out;
                                box-shadow: 0 0 10px rgba(0, 255, 65, 0.4);
                            "></div>
                        </div>
                    </div>`;
            }
            else if (currentMode === 'MOON') {
                const val = outputs[0]; // Assuming 1 output neuron for binary
                const probB = val;
                const probA = 1 - val;

                // Update Orb A (Red - Class 0)
                document.getElementById('orbA').style.background = `rgba(255, 49, 49, ${probA})`;
                document.getElementById('orbA').style.boxShadow = probA > 0.5 ? `0 0 15px #ff3131` : 'none';

                // Update Orb B (Green - Class 1)
                document.getElementById('orbB').style.background = `rgba(0, 255, 65, ${probB})`;
                document.getElementById('orbB').style.boxShadow = probB > 0.5 ? `0 0 15px #00ff41` : 'none';

                resultDiv.innerHTML = `Classification: ${val > 0.5 ? 'Moon B' : 'Moon A'} (${(val * 100).toFixed(1)}%)`;
            }
            else if (['SIN', 'COS'].includes(currentMode)) {
                const rawVal = outputs[0];
                const finalVal = (rawVal * 2) - 1;
                document.getElementById('mathResultDisplay').innerText = `f(x) ≈ ${finalVal.toFixed(4)}`;
                resultDiv.innerHTML = `Regression Output: ${rawVal.toFixed(4)}`;
            }
            else {
                displayOutputResults(outputs); // Handles Color
            }
        }

        function displayOutputResults(outputs) {
            let html = `<div style="color: #fff; margin-top:10px;">Network Output:</div>`;
            outputs.forEach((val, i) => {
                // Map index to labels
                const labels = ['WARM', 'COOL', 'NEUTRAL'];
                const barColor = i === 0 ? '#ef4444' : i === 1 ? '#06b6d4' : '#94a3b8'; // Red, Cyan, Gray

                html += `
                <div style="margin-bottom: 4px;">
                    <span style="font-size:0.7rem">${labels[i]}: ${(val * 100).toFixed(1)}%</span>
                    <div style="background: #1e293b; width: 100%; height: 8px; border-radius: 4px; overflow: hidden;">
                        <div style="background: ${barColor}; width: ${val * 100}%; height: 100%; transition: width 0.3s ease;"></div>
                    </div>
                </div>`;
            });
            document.getElementById('predictionResult').innerHTML = html;
        }

        // --- DATASET GENERATORS ---
        function setDataset(type, btnElement) {
            // 1. UI Highlight Logic
            document.querySelectorAll('.btn-group button').forEach(btn => btn.classList.remove('active-dataset'));
            if (btnElement) btnElement.classList.add('active-dataset');

            // 2. Clear previous inference/results
            document.getElementById('predictionResult').innerHTML = "Result: awaiting input...";
            lossHistory = []; // Reset loss history for the new dataset

            // 3. Dataset Definitions
            if (type === 'OR') {
                document.getElementById('inputX').value = "0,0; 0,1; 1,0; 1,1";
                document.getElementById('inputY').value = "0; 1; 1; 1";
            } else if (type === 'XOR') {
                document.getElementById('inputX').value = "0,0; 0,1; 1,0; 1,1";
                document.getElementById('inputY').value = "0; 1; 1; 0";
            } else if (type === 'XNOR') {
                document.getElementById('inputX').value = "0,0; 0,1; 1,0; 1,1";
                document.getElementById('inputY').value = "1; 0; 0; 1";
            } else if (type === 'XOR3') {
                document.getElementById('inputX').value = "0,0,0; 0,0,1; 0,1,0; 0,1,1; 1,0,0; 1,0,1; 1,1,0; 1,1,1";
                document.getElementById('inputY').value = "0; 1; 1; 0; 1; 0; 0; 1";
            } else if (type === 'XNOR3') {
                document.getElementById('inputX').value = "0,0,0; 0,0,1; 0,1,0; 0,1,1; 1,0,0; 1,0,1; 1,1,0; 1,1,1";
                document.getElementById('inputY').value = "1; 0; 0; 1; 0; 1; 1; 0";
            } else if (type === 'COS') {
                let x = [], y = [];
                for (let i = 0; i <= 20; i++) {
                    x.push((i / 5).toFixed(4));
                    y.push(((Math.cos(i / 5 * Math.PI) + 1) / 2).toFixed(4));
                }
                document.getElementById('inputX').value = x.join('; ');
                document.getElementById('inputY').value = y.join('; ');
            } else if (type === 'SIN') {
                let x = [], y = [];
                for (let i = 0; i <= 20; i++) {
                    x.push((i / 5).toFixed(4));
                    y.push(((Math.sin(i / 5 * Math.PI) + 1) / 2).toFixed(4));
                }
                document.getElementById('inputX').value = x.join('; ');
                document.getElementById('inputY').value = y.join('; ');
            } else if (type === 'MOON') {
                let x = [], y = [];
                for (let i = 0; i < 40; i++) {
                    let p = (i / 40) * Math.PI;
                    // Restricting to 2 decimal places for cleaner input strings
                    let x1 = Math.cos(p).toFixed(2);
                    let y1 = Math.sin(p).toFixed(2);
                    let x2 = (1 - Math.cos(p)).toFixed(2);
                    let y2 = (0.5 - Math.sin(p)).toFixed(2);

                    x.push(`${x1},${y1}`); y.push(0);
                    x.push(`${x2},${y2}`); y.push(1);
                }
                document.getElementById('inputX').value = x.join('; ');
                document.getElementById('inputY').value = y.join('; ');
            }

            updateTestUI(type);
            buildNetwork();
        }

        function addLayerUI() {
            const div = document.createElement('div');
            div.className = "config-row layer-row-container"; // Uses the new flex row logic
            div.style.borderLeft = `6px solid ${actColors.tanh}`;
            div.innerHTML = `
                    <input type="number" onchange="buildNetwork()" class="l-size network-ctl" value="4"> 
                    <select class="l-act network-ctl" onchange="updateLayerColor(this)">
                        <option value="tanh">Tanh</option>
                        <option value="relu">ReLU</option>
                        <option value="lrelu">Leaky ReLU</option>
                    </select> 
                    <button class="delete-btn" onclick="this.parentElement.remove()">x</button>
                `;
            document.getElementById('layersConfig').appendChild(div);
            buildNetwork();
        }

        async function handleTrainClick() {
            const btn = document.getElementById('trainBtn');

            if (!isTraining) {
                // Start fresh training
                toggleTraining();
                btn.innerHTML = "&#9208;"; // Switch to Pause Icon
            } else {
                // Toggle the paused state
                isPaused = !isPaused;
                if (isPaused) {
                    btn.innerHTML = "&#9658;"; // Show Play to resume
                    btn.classList.add('btn-paused');
                    document.getElementById('status').innerHTML = "<small>Paused: </small>" + document.getElementById('status').innerHTML;
                } else {
                    btn.innerHTML = "&#9208;"; // Show Pause while running
                    btn.classList.remove('btn-paused');
                }
            }
        }

        function buildNetwork() {
            if (isTraining) return; // Prevent rebuilding during training

            console.log("Building Network...");
            document.getElementById('predictionResult').innerHTML = "<small>Train The Network First</small>";
            const xRaw = document.getElementById('inputX').value.split(';').map(r => r.split(',').map(v => parseFloat(v.trim())));
            const yRaw = document.getElementById('inputY').value.split(';').map(r => r.split(',').map(v => parseFloat(v.trim())));

            document.getElementById('inputFeaturesCount').value = xRaw[0].length;
            document.getElementById('outputNeuronsCount').value = yRaw[0].length;

            layers = [];
            let curIn = xRaw[0].length;

            // Build Hidden Layers
            document.querySelectorAll('#layersConfig > div:not([style*="border-top"])').forEach(el => {
                let sInput = el.querySelector('.l-size');
                if (!sInput) return; // Skip the output config div
                let s = parseInt(sInput.value);
                let a = el.querySelector('.l-act').value;
                layers.push(new Layer(curIn, s, a));
                curIn = s;
            });

            // NEW: Build Output Layer based on User Choice
            const outputDim = yRaw[0].length;
            const finalAct = document.getElementById('outputAct').value;
            layers.push(new Layer(curIn, outputDim, finalAct));

            renderWeights();
            initPlots();
            drawNet();
            document.getElementById('status').innerHTML = "<small>Network Built. Ready to Train.</small>";
        }


        function predictUniversal() {
            if (layers.length === 0) {
                document.getElementById('predictionResult').innerHTML = "<small>Error: Build and train the network first.</small>";
                return;
            }

            // 1. Parse the input string
            const inputStr = document.getElementById('testInput').value;
            const inputVector = inputStr.split(',').map(v => parseFloat(v.trim()));

            // Validation: Ensure input length matches the first layer's weights
            if (inputVector.length !== layers[0].w.length) {
                document.getElementById('predictionResult').innerHTML =
                    `<small>Error: Expected ${layers[0].w.length} inputs, but got ${inputVector.length}.</small>`;
                return;
            }

            // 2. Forward Pass: Pass the input through all layers
            let currentA = [inputVector];
            for (let L of layers) {
                currentA = L.forward(currentA);
            }

            // 3. Display Results
            const outputs = currentA[0];
            let html = `<div style="color: #fff; margin-bottom: 5px;">Output Activations:</div>`;

            outputs.forEach((val, i) => {
                const percentage = (val * 100).toFixed(2);
                // Create a simple visual bar for each output
                html += `
                    <div style="margin-bottom: 4px;">
                        <span style="display:inline-block; width: 80px;">Out[${i}]:</span>
                        <span style="color: #00ff41;">${percentage}%</span>
                        <div style="background: #333; width: 100%; height: 4px; margin-top: 2px;">
                            <div style="background: #00ff41; width: ${val * 100}%; height: 100%;"></div>
                        </div>
                    </div>`;
            });

            // For classification: Highlight the winner
            const maxIdx = outputs.indexOf(Math.max(...outputs));
            html += `<div style="margin-top: 8px; border-top: 1px solid #444; padding-top: 5px;">
                Primary Class: <strong style="color:#00ff41;">Index ${maxIdx}</strong>
             </div>`;

            document.getElementById('predictionResult').innerHTML = html;
        }

        function toggleDatasetButtons(disabled) {
            console.log("Toggling dataset buttons. Disabled:", disabled);
            const buttons = document.querySelectorAll('.btn-group button:not(.control-btn), .controls button:not(.control-btn), .network-ctl, #outputAct');
            console.log("Buttons found:", buttons);
            buttons.forEach(btn => {
                btn.disabled = disabled;
                btn.style.opacity = disabled ? "0.5" : "1";
                btn.style.cursor = disabled ? "not-allowed" : "pointer";
            });

        }

        function resetEverything() {
            stopFlag = true;
            isTraining = false;
            isPaused = false;
            lossHistory = [];

            document.getElementById('matrixDisplay').innerHTML = "";

            const trainBtn = document.getElementById('trainBtn');

            trainBtn.innerHTML = "&#9658;"; // Restore Play icon
            trainBtn.classList.remove('btn-paused'); // Stop the pulsing

            // Reset UI Elements
            document.getElementById('status').innerHTML = "<small>Reset - Awaiting Initialization</small>";
            document.getElementById('predictionResult').innerHTML = `<small>Train the Network First</small>`;

            // Clear Plots
            // initPlots();

            // Re-enable buttons
            toggleDatasetButtons(false);
        }

        function setColorDataset(btnElement) {
            document.querySelectorAll('.btn-group button').forEach(btn => btn.classList.remove('active-dataset'));
            if (btnElement) btnElement.classList.add('active-dataset');
            const data = [
                // --- WARM COLORS (Output: 1,0,0) ---
                // Characteristics: High Red, Low Blue
                { x: "1,0,0", y: "1,0,0" },       // Pure Red
                { x: "1,0.5,0", y: "1,0,0" },     // Orange
                { x: "1,1,0", y: "1,0,0" },       // Yellow
                { x: "0.8,0.2,0.1", y: "1,0,0" }, // Brick Red
                { x: "0.6,0,0", y: "1,0,0" },     // Deep Maroon
                { x: "1,0.3,0.3", y: "1,0,0" },   // Coral/Pink
                { x: "0.9,0.4,0.1", y: "1,0,0" }, // Burnt Orange
                { x: "0.5,0.2,0", y: "1,0,0" },   // Brown

                // --- COOL COLORS (Output: 0,1,0) ---
                // Characteristics: High Blue or Green, Low Red
                { x: "0,0,1", y: "0,1,0" },       // Pure Blue
                { x: "0,1,0", y: "0,1,0" },       // Pure Green
                { x: "0,1,1", y: "0,1,0" },       // Cyan
                { x: "0.5,0,1", y: "0,1,0" },     // Purple (Moved to Cool)
                { x: "0.2,0.2,0.8", y: "0,1,0" }, // Soft Blue
                { x: "0,0.5,0.5", y: "0,1,0" },   // Teal
                { x: "0.1,0.8,0.2", y: "0,1,0" }, // Bright Green
                { x: "0.7,0.3,1", y: "0,1,0" },   // Violet

                // --- NEUTRAL/MUTED (Output: 0,0,1) ---
                // Characteristics: R, G, and B are nearly equal
                { x: "0,0,0", y: "0,0,1" },       // Black
                { x: "1,1,1", y: "0,0,1" },       // White
                { x: "0.5,0.5,0.5", y: "0,0,1" }, // Mid Gray
                { x: "0.2,0.2,0.2", y: "0,0,1" }, // Dark Gray
                { x: "0.8,0.8,0.8", y: "0,0,1" }, // Light Gray
                { x: "0.4,0.4,0.4", y: "0,0,1" }, // Charcoal
                { x: "0.6,0.6,0.6", y: "0,0,1" }, // Silver
                { x: "0.1,0.1,0.1", y: "0,0,1" }  // Near Black
            ];

            document.getElementById('inputX').value = data.map(d => d.x).join('; ');
            document.getElementById('inputY').value = data.map(d => d.y).join('; ');

            updateTestUI('COLOR');
            buildNetwork();

        }

        async function toggleTraining() {
            if (isTraining) return;
            if (layers.length === 0) buildNetwork();

            isTraining = true;
            stopFlag = false;
            toggleDatasetButtons(true);
            document.getElementById('predictionResult').innerHTML = "<small>Training in progress...</small>";
            document.getElementById('trainBtn').innerHTML = '<div class="spinner"></div>';

            const xRaw = document.getElementById('inputX').value.split(';').map(r => r.split(',').map(v => parseFloat(v.trim())));
            const yRaw = document.getElementById('inputY').value.split(';').map(r => r.split(',').filter(v => v.trim() !== "").map(v => parseFloat(v.trim())));

            const maxEpochs = parseInt(document.getElementById('epochs').value);
            const delay = parseInt(document.getElementById('delay').value);
            const lr = parseFloat(document.getElementById('lr').value);

            lossHistory = [];
            for (let ep = 0; ep <= maxEpochs; ep++) {
                while (isPaused) await new Promise(r => setTimeout(r, 100));
                if (stopFlag) break;

                // Forward
                let acts = [xRaw];
                for (let L of layers) acts.push(L.forward(acts[acts.length - 1]));

                // Backward
                let out = acts[acts.length - 1];
                let error = out.map((row, i) => row.map((v, j) => v - yRaw[i][j]));
                let mse = error.flat().reduce((a, b) => a + b * b, 0) / xRaw.length;
                lossHistory.push(mse);

                let dZ = error.map((row, i) => row.map((err, j) => err * (2 / xRaw.length) * Act[layers[layers.length - 1].act].df(layers[layers.length - 1].lastZ[i][j])));

                for (let i = layers.length - 1; i >= 0; i--) {
                    let L = layers[i], prevA = acts[i];
                    let dW = L.w.map((row, rI) => row.map((_, cI) => dZ.reduce((s, dzR, rI2) => s + dzR[cI] * prevA[rI2][rI], 0)));
                    let dB = dZ[0].map((_, cI) => dZ.reduce((s, dzR) => s + dzR[cI], 0));

                    if (i > 0) {
                        let prevL = layers[i - 1];
                        dZ = dZ.map((dzR, rI) => prevL.w[0].map((_, cI) => {
                            let da = dzR.reduce((s, dzV, curCI) => s + dzV * L.w[cI][curCI], 0);
                            return da * Act[prevL.act].df(prevL.lastZ[rI][cI]);
                        }));
                    }
                    L.w = L.w.map((row, r) => row.map((v, c) => v - lr * dW[r][c]));
                    L.b = L.b.map((v, c) => v - lr * dB[c]);
                }

                if (ep % 20 === 0) {
                    updateViz(xRaw, yRaw, out, mse, ep);
                    renderWeights();
                    await new Promise(r => setTimeout(r, delay));
                }
            }
            isTraining = false;
            document.getElementById('trainBtn').innerHTML = "&#9658;";
            toggleDatasetButtons(false);
            document.getElementById('trainBtn').innerHTML = "&#9658;";
        }

        function updateViz(x, y, preds, loss, ep) {
            document.getElementById('status').innerHTML = `<small>Epoch: ${ep} | Loss: ${loss.toFixed(4)}</small>`;
            const inDim = x[0].length;
            const outDim = y[0].length;
            let traces = [];
            let layout = {
                paper_bgcolor: 'rgba(0,0,0,0)', plot_bgcolor: 'rgba(0,0,0,0)',
                font: { color: '#0f0' }, margin: { t: 0, b: 0, l: 0, r: 0 },
                xaxis: {
                    gridcolor: 'rgba(51, 65, 85, 0.3)', // Very faint grid lines
                    zerolinecolor: 'rgba(51, 65, 85, 0.5)'
                },
                yaxis: {
                    gridcolor: 'rgba(51, 65, 85, 0.3)',
                    zerolinecolor: 'rgba(51, 65, 85, 0.5)'
                }
            };

            if (inDim === 1) {
                // --- 1D REGRESSION (SIN/COS) ---
                traces.push({ x: x.map(r => r[0]), y: y.map(r => r[0]), mode: 'markers', name: 'Target' });
                traces.push({ x: x.map(r => r[0]), y: preds.map(r => r[0]), mode: 'lines', name: 'Network' });
            }
            else if (inDim === 2) {
                // --- 2D CLASSIFICATION (XOR/MOONS) ---
                const isXOR = currentMode.includes('XOR');
                const rangeLimit = isXOR ? 1.1 : 1.5; // Tighter view for XOR, wider for Moons
                const rangeStart = isXOR ? -0.1 : -0.7;

                // 1. Update Layout for Ticks and Visibility
                layout.margin = { t: 20, b: 40, l: 50, r: 20 }; // Increased margin for tick labels

                layout.xaxis = {
                    title: { text: "Input X", font: { size: 10 } },
                    range: [rangeStart, rangeLimit],
                    gridcolor: '#222',
                    showticklabels: true,
                    tickfont: { color: '#00ff41', size: 10 },
                    tickmode: 'auto',
                    nticks: 10,
                    zeroline: true,
                    zerolinecolor: '#444'
                };
                layout.yaxis = {
                    title: { text: "Input Y", font: { size: 10 } },
                    range: [rangeStart, rangeLimit],
                    gridcolor: '#222',
                    showticklabels: true,
                    tickfont: { color: '#00ff41', size: 10 },
                    tickmode: 'auto',
                    nticks: 10,
                    zeroline: true,
                    zerolinecolor: '#444'
                };

                // 2. Generate Heatmap Grid
                const resolution = 30;
                let gridX = [], gridY = [], gridZ = [];

                for (let i = 0; i <= resolution; i++) {
                    gridX.push(rangeStart + (i / resolution) * (rangeLimit - rangeStart));
                    gridY.push(rangeStart + (i / resolution) * (rangeLimit - rangeStart));
                }

                for (let j = 0; j <= resolution; j++) {
                    let py = gridY[j];
                    let rowZ = [];
                    for (let i = 0; i <= resolution; i++) {
                        let px = gridX[i];

                        // Forward pass
                        let out = [px, py];
                        for (let L of layers) out = L.forward([out])[0];

                        // Push prediction
                        rowZ.push(outDim === 3 ? out[0] - out[1] : out[0]);
                    }
                    gridZ.push(rowZ); // Now gridZ[j][i] matches [Y][X]
                }

                traces.push({
                    x: gridX,
                    y: gridY,
                    z: gridZ,
                    type: 'heatmap',
                    transpose: false,
                    colorscale: [
                        [0, '#FF4136'], // Red
                        [1, '#2ECC40']  // Green
                    ],
                    reversescale: false,
                    opacity: 0.4,
                    showscale: false,
                    hoverinfo: 'skip'
                });

                // 3. Add Data Points
                traces.push({
                    x: x.map(r => r[0]), y: x.map(r => r[1]),
                    mode: 'markers',
                    marker: {
                        color: y.map(r => r[0] > 0.5 ? '#00ff41' : '#ff3131'),
                        size: 9,
                        line: { color: '#fff', width: 1 }
                    },
                    name: 'Data'
                });
            } else if (inDim === 3) {
                // --- 3D CLASSIFICATION (XOR3/COLOR) ---
                // 1. Data Points
                traces.push({
                    x: x.map(r => r[0]), y: x.map(r => r[1]), z: x.map(r => r[2]),
                    type: 'scatter3d', mode: 'markers',
                    marker: {
                        color: currentMode === 'COLOR' ? x.map(r => `rgb(${r[0] * 255},${r[1] * 255},${r[2] * 255})`) : y.map(r => r[0] > 0.5 ? '#00ff41' : '#ff3131'),
                        size: 5, symbol: 'circle', line: { color: '#fff', width: 1 }
                    }
                });

                // 2. Decision Boundary (Slice Plane at Z=0.5)
                let sliceX = [], sliceY = [], sliceZ = [];
                for (let i = 0; i <= 10; i++) {
                    let rowX = [], rowY = [], rowZ = [];
                    for (let j = 0; j <= 10; j++) {
                        let px = i / 10, py = j / 10, pz = 0.5;
                        let out = [px, py, pz];
                        for (let L of layers) out = L.forward([out])[0];
                        rowX.push(px); rowY.push(py); rowZ.push(out[0]);
                    }
                    sliceX.push(rowX); sliceY.push(rowY); sliceZ.push(rowZ);
                }
                // This adds a translucent "prediction sheet" inside the 3D cube
                traces.push({
                    x: sliceX, y: sliceY, z: sliceZ,
                    type: 'surface', opacity: 0.3, showscale: false,
                    colorscale: [
                        [0, '#FF4136'], // Red
                        [1, '#2ECC40']  // Green
                    ],
                });

                layout.scene = {
                    xaxis: { title: 'R / In1', backgroundcolor: "#000", gridcolor: "#333" },
                    yaxis: { title: 'G / In2', backgroundcolor: "#000", gridcolor: "#333" },
                    zaxis: { title: 'B / In3', backgroundcolor: "#000", gridcolor: "#333" },
                    aspectmode: 'cube'
                };
            }

            Plotly.react('plot', traces, layout);
            Plotly.react('lossPlot', [{ y: lossHistory, type: 'scatter', line: { color: 'red' } }],
                { ...layout, margin: { t: 10, b: 30, l: 30, r: 10 }, yaxis: { type: 'log' } });

            // This ensures the neuron/weight drawing still happens!
            drawNet();
        }

        function renderWeights() {
            let html = "";
            layers.forEach((L, i) => {
                // Create a container for each layer's weights
                html += `<div class="matrix-table">
                    <div class="layer-title">Layer ${i} → ${i + 1} (${L.act})</div>
                    <table>`;

                // Rows = Input Neurons, Cols = Output Neurons
                L.w.forEach((row, rIdx) => {
                    html += "<tr>";
                    row.forEach(v => {
                        // Color coding: Green for positive, Red for negative
                        const color = isNaN(v) ? '#fbbf24' : v > 0 ? '#10b981' : '#f43f5e';
                        const opacity = Math.min(1, Math.abs(v));
                        html += `<td style="color:${color}; opacity:${0.3 + opacity}">${v.toFixed(4)}</td>`;
                    });
                    html += "</tr>";
                });

                html += `</table><div class="layer-title" style="background:#1e293b; padding:2px;">Biases</div><table style="width:100%"><tr>`;
                L.b.forEach(v => {
                    html += `<td>${v.toFixed(4)}</td>`;
                });
                html += "</tr></table></div>";
            });
            document.getElementById('matrixDisplay').innerHTML = html;
        }

        function drawNet() {


            const canvas = document.getElementById('netCanvas');
            const ctx = canvas.getContext('2d');
            canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight;
            if (layers.length === 0) return;
            const counts = [layers[0].w.length, ...layers.map(l => l.w[0].length)];
            const xStep = canvas.width / (counts.length + 1);
            const pos = counts.map((c, i) => Array.from({ length: c }, (_, j) => ({ x: xStep * (i + 1), y: (canvas.height / (c + 1)) * (j + 1) })));

            layers.forEach((L, lIdx) => {
                L.w.forEach((row, i) => row.forEach((w, j) => {
                    ctx.beginPath(); ctx.moveTo(pos[lIdx][i].x, pos[lIdx][i].y); ctx.lineTo(pos[lIdx + 1][j].x, pos[lIdx + 1][j].y);
                    ctx.strokeStyle = isNaN(w) ? '#fbbf24' : w > 0 ? `rgba(16, 185, 129, ${Math.abs(w) * 0.4})` : `rgba(244, 63, 94, ${Math.abs(w) * 0.4})`;
                    ctx.lineWidth = Math.min(5, Math.abs(w) * 2); ctx.stroke();
                }));
            });

            pos.forEach((layerNodes, lIdx) => {
                // Determine color: first layer is 'input', others come from layers[lIdx-1]
                const actType = lIdx === 0 ? 'input' : layers[lIdx - 1].act;
                const nodeColor = actColors[actType] || '#ffffff';

                layerNodes.forEach(p => {
                    ctx.beginPath();
                    ctx.arc(p.x, p.y, 10, 0, 7); // Slightly larger nodes


                    ctx.shadowBlur = 16;
                    ctx.shadowColor = nodeColor; // Adds a subtle glow per activation type

                    ctx.strokeStyle = nodeColor;
                    ctx.lineWidth = 2.5; // Thickness of the ring
                    ctx.stroke();

                    ctx.fillStyle = nodeColor + '33'; // Adds 22 hex transparency (approx 13%)
                    ctx.fill();

                    ctx.shadowBlur = 0; // Reset shadow for next draw
                });
            });
        }

        function initPlots() {
            const config = {
                responsive: true,
                displayModeBar: false, // Hides the annoying floating menu on mobile
                scrollZoom: false      // Prevents chart from capturing scroll gestures
            };

            const emptyLayout = {
                paper_bgcolor: 'rgba(0,0,0,0)',
                plot_bgcolor: 'rgba(0,0,0,0)',
                xaxis: {
                    gridcolor: 'rgba(51, 65, 85, 0.3)', // Very faint grid lines
                    zerolinecolor: 'rgba(51, 65, 85, 0.5)'
                },
                margin: { t: 20, b: 40, l: 40, r: 20 },
                yaxis: {
                    gridcolor: 'rgba(51, 65, 85, 0.3)',
                    zerolinecolor: 'rgba(51, 65, 85, 0.5)'
                }
            };
            Plotly.newPlot('plot', [], emptyLayout);
            Plotly.newPlot('lossPlot', [], emptyLayout);
        }

        // Automatically build the initial network and UI on load
        window.onload = () => {
            initPlots();
            const xorBtn = Array.from(document.querySelectorAll('.btn-group button'))
                .find(btn => btn.innerText === 'XOR');

            console.log('Initializing with XOR dataset...', xorBtn);
            xorBtn.classList.add('active-dataset');
            setDataset('XOR', xorBtn); // This will trigger buildNetwork() and updateTestUI()
        };

        // This forces all Plotly charts to resize whenever the window size changes
        window.addEventListener('resize', function () {
            Plotly.Plots.resize('plot');
            Plotly.Plots.resize('lossPlot');
        });

        // Also call it inside your updateViz function to ensure 
        // they snap to the correct size during training
        function forceResize() {
            Plotly.Plots.resize('plot');
            Plotly.Plots.resize('lossPlot');
        }

        // Force charts to re-calculate their width for touch devices
        const resizeObserver = new ResizeObserver(() => {
            Plotly.Plots.resize('plot');
            Plotly.Plots.resize('lossPlot');
            drawNet(); // Also keep your canvas in sync
        });

        // Start observing the dashboard container
        resizeObserver.observe(document.querySelector('.dashboard'));
    </script>
</body>

</html>