| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Perceptron Passive Radar</title> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| font-family: monospace; |
| } |
| |
| body { |
| background: #001; |
| color: #0f8; |
| min-height: 100vh; |
| overflow: hidden; |
| } |
| |
| .main-container { |
| display: grid; |
| grid-template-columns: 1fr 400px; |
| gap: 10px; |
| padding: 10px; |
| height: 100vh; |
| } |
| |
| .radar-container { |
| position: relative; |
| border: 2px solid #0f8; |
| border-radius: 50%; |
| background: radial-gradient(circle, rgba(0,255,136,0.05) 0%, rgba(0,0,0,0.9) 100%); |
| overflow: hidden; |
| } |
| |
| .sweep-line { |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| width: 50%; |
| height: 2px; |
| background: linear-gradient(90deg, #0f8, transparent); |
| transform-origin: left; |
| animation: radar-sweep 4s linear infinite; |
| } |
| |
| .grid-overlay { |
| position: absolute; |
| width: 100%; |
| height: 100%; |
| background: |
| linear-gradient(#0f81 1px, transparent 1px), |
| linear-gradient(90deg, #0f81 1px, transparent 1px); |
| background-size: 50px 50px; |
| } |
| |
| @keyframes radar-sweep { |
| from { transform: rotate(0deg); } |
| to { transform: rotate(360deg); } |
| } |
| |
| .control-panel { |
| display: flex; |
| flex-direction: column; |
| gap: 10px; |
| } |
| |
| .panel { |
| background: rgba(0,255,136,0.1); |
| border: 1px solid #0f8; |
| padding: 10px; |
| } |
| |
| .perceptron-layer { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| height: 200px; |
| margin: 10px 0; |
| position: relative; |
| } |
| |
| .layer { |
| display: flex; |
| flex-direction: column; |
| gap: 4px; |
| } |
| |
| .neuron { |
| width: 12px; |
| height: 12px; |
| background: #0f8; |
| border-radius: 50%; |
| position: relative; |
| opacity: 0.3; |
| transition: all 0.2s; |
| } |
| |
| .neuron.active { |
| opacity: 1; |
| box-shadow: 0 0 10px #0f8; |
| } |
| |
| .connections { |
| position: absolute; |
| width: 100%; |
| height: 100%; |
| pointer-events: none; |
| } |
| |
| .spectrum { |
| height: 120px; |
| display: flex; |
| align-items: flex-end; |
| gap: 2px; |
| } |
| |
| .bar { |
| flex: 1; |
| background: #0f8; |
| transition: height 0.1s; |
| } |
| |
| .signal { |
| position: absolute; |
| width: 8px; |
| height: 8px; |
| background: #0f8; |
| border-radius: 50%; |
| transform: translate(-50%, -50%); |
| } |
| |
| .signal-echo { |
| position: absolute; |
| border: 1px solid #0f8; |
| border-radius: 50%; |
| transform: translate(-50%, -50%); |
| animation: echo 2s ease-out forwards; |
| } |
| |
| @keyframes echo { |
| 0% { |
| width: 8px; |
| height: 8px; |
| opacity: 1; |
| } |
| 100% { |
| width: 60px; |
| height: 60px; |
| opacity: 0; |
| } |
| } |
| |
| button { |
| background: transparent; |
| border: 1px solid #0f8; |
| color: #0f8; |
| padding: 8px; |
| cursor: pointer; |
| transition: 0.3s; |
| } |
| |
| button:hover { |
| background: #0f8; |
| color: #001; |
| } |
| |
| .stats { |
| font-size: 12px; |
| line-height: 1.5; |
| } |
| |
| canvas { |
| margin-top: 10px; |
| border: 1px solid #0f8; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="main-container"> |
| <div class="radar-container"> |
| <div class="grid-overlay"></div> |
| <div class="sweep-line"></div> |
| <div id="signals"></div> |
| </div> |
|
|
| <div class="control-panel"> |
| <div class="panel"> |
| <h3>Perceptron Network</h3> |
| <div class="perceptron-layer"> |
| <canvas id="networkCanvas"></canvas> |
| </div> |
| <div class="stats" id="networkStats"> |
| Accuracy: 0%<br> |
| Detections: 0<br> |
| Signal Strength: 0 |
| </div> |
| </div> |
|
|
| <div class="panel"> |
| <h3>Frequency Analysis</h3> |
| <div class="spectrum" id="spectrum"></div> |
| </div> |
|
|
| <div class="panel"> |
| <button onclick="startRadar()">Start Detection</button> |
| <button onclick="stopRadar()">Stop</button> |
| <button onclick="trainPerceptrons()">Train Network</button> |
| <button onclick="toggleLearning()">Toggle Learning</button> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| class Perceptron { |
| constructor(inputs) { |
| this.weights = new Array(inputs).fill(0).map(() => Math.random() * 2 - 1); |
| this.bias = Math.random() * 2 - 1; |
| this.learningRate = 0.1; |
| } |
| |
| activate(sum) { |
| return sum > 0 ? 1 : 0; |
| } |
| |
| predict(inputs) { |
| const sum = inputs.reduce((sum, input, i) => sum + input * this.weights[i], 0) + this.bias; |
| return this.activate(sum); |
| } |
| |
| train(inputs, target) { |
| const prediction = this.predict(inputs); |
| const error = target - prediction; |
| |
| for(let i = 0; i < this.weights.length; i++) { |
| this.weights[i] += error * inputs[i] * this.learningRate; |
| } |
| this.bias += error * this.learningRate; |
| |
| return Math.abs(error); |
| } |
| } |
| |
| class PerceptronNetwork { |
| constructor(inputSize, hiddenSize, outputSize) { |
| this.inputLayer = new Array(hiddenSize) |
| .fill(0) |
| .map(() => new Perceptron(inputSize)); |
| |
| this.outputLayer = new Array(outputSize) |
| .fill(0) |
| .map(() => new Perceptron(hiddenSize)); |
| } |
| |
| predict(inputs) { |
| const hiddenOutputs = this.inputLayer.map(p => p.predict(inputs)); |
| return this.outputLayer.map(p => p.predict(hiddenOutputs)); |
| } |
| |
| train(inputs, targets) { |
| const hiddenOutputs = this.inputLayer.map(p => p.predict(inputs)); |
| const finalOutputs = this.outputLayer.map(p => p.predict(hiddenOutputs)); |
| |
| let error = 0; |
| this.outputLayer.forEach((p, i) => { |
| error += p.train(hiddenOutputs, targets[i]); |
| }); |
| this.inputLayer.forEach(p => { |
| error += p.train(inputs, 1); |
| }); |
| |
| return error / (this.inputLayer.length + this.outputLayer.length); |
| } |
| } |
| |
| |
| const INPUT_SIZE = 32; |
| const HIDDEN_SIZE = 16; |
| const OUTPUT_SIZE = 4; |
| |
| let isRunning = false; |
| let isLearning = false; |
| let network = new PerceptronNetwork(INPUT_SIZE, HIDDEN_SIZE, OUTPUT_SIZE); |
| let audioContext, analyser, dataArray; |
| |
| |
| const spectrum = document.getElementById('spectrum'); |
| for(let i = 0; i < INPUT_SIZE; i++) { |
| const bar = document.createElement('div'); |
| bar.className = 'bar'; |
| spectrum.appendChild(bar); |
| } |
| |
| |
| const canvas = document.getElementById('networkCanvas'); |
| canvas.width = 350; |
| canvas.height = 180; |
| const ctx = canvas.getContext('2d'); |
| |
| function drawNetwork(inputs, hiddenOutputs, finalOutputs) { |
| ctx.clearRect(0, 0, canvas.width, canvas.height); |
| ctx.strokeStyle = '#0f8'; |
| ctx.lineWidth = 0.5; |
| |
| const drawLayer = (nodes, x, active) => { |
| const spacing = canvas.height / (nodes.length + 1); |
| return nodes.map((value, i) => { |
| const y = spacing * (i + 1); |
| ctx.beginPath(); |
| ctx.arc(x, y, 4, 0, Math.PI * 2); |
| ctx.fillStyle = `rgba(0,255,136,${active ? value : 0.3})`; |
| ctx.fill(); |
| return {x, y}; |
| }); |
| }; |
| |
| const inputNodes = drawLayer(inputs, 30, true); |
| const hiddenNodes = drawLayer(hiddenOutputs, canvas.width/2, true); |
| const outputNodes = drawLayer(finalOutputs, canvas.width - 30, true); |
| |
| |
| ctx.beginPath(); |
| inputNodes.forEach(input => { |
| hiddenNodes.forEach(hidden => { |
| ctx.moveTo(input.x, input.y); |
| ctx.lineTo(hidden.x, hidden.y); |
| }); |
| }); |
| hiddenNodes.forEach(hidden => { |
| outputNodes.forEach(output => { |
| ctx.moveTo(hidden.x, hidden.y); |
| ctx.lineTo(output.x, output.y); |
| }); |
| }); |
| ctx.stroke(); |
| } |
| |
| async function startRadar() { |
| try { |
| if(!audioContext) { |
| audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
| const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); |
| const source = audioContext.createMediaStreamSource(stream); |
| analyser = audioContext.createAnalyser(); |
| analyser.fftSize = 64; |
| source.connect(analyser); |
| dataArray = new Uint8Array(analyser.frequencyBinCount); |
| } |
| |
| isRunning = true; |
| updateRadar(); |
| } catch(err) { |
| console.error('Error accessing microphone:', err); |
| } |
| } |
| |
| function stopRadar() { |
| isRunning = false; |
| } |
| |
| function updateRadar() { |
| if(!isRunning) return; |
| |
| analyser.getByteFrequencyData(dataArray); |
| |
| |
| const bars = spectrum.children; |
| for(let i = 0; i < bars.length; i++) { |
| const height = (dataArray[i] / 255) * 120; |
| bars[i].style.height = height + 'px'; |
| } |
| |
| |
| const inputs = Array.from(dataArray).map(x => x / 255); |
| |
| |
| const hiddenOutputs = network.inputLayer.map(p => p.predict(inputs)); |
| const outputs = network.outputLayer.map(p => p.predict(hiddenOutputs)); |
| |
| |
| drawNetwork(inputs, hiddenOutputs, outputs); |
| |
| |
| outputs.forEach((output, i) => { |
| if(output > 0.7) { |
| createSignal(i); |
| } |
| }); |
| |
| |
| const avgOutput = outputs.reduce((a,b) => a+b) / outputs.length; |
| document.getElementById('networkStats').innerHTML = |
| `Accuracy: ${Math.round(avgOutput * 100)}%<br>` + |
| `Detections: ${outputs.filter(x => x > 0.7).length}<br>` + |
| `Signal Strength: ${Math.round(inputs.reduce((a,b) => a+b) / inputs.length * 100)}`; |
| |
| if(isLearning) { |
| const target = Array(OUTPUT_SIZE).fill(0); |
| target[Math.floor(Math.random() * OUTPUT_SIZE)] = 1; |
| network.train(inputs, target); |
| } |
| |
| requestAnimationFrame(updateRadar); |
| } |
| |
| function createSignal(type) { |
| const radar = document.querySelector('.radar-container'); |
| const angle = Math.random() * Math.PI * 2; |
| const distance = Math.random() * (radar.offsetWidth / 3); |
| |
| const x = Math.cos(angle) * distance + radar.offsetWidth / 2; |
| const y = Math.sin(angle) * distance + radar.offsetHeight / 2; |
| |
| const signal = document.createElement('div'); |
| signal.className = 'signal'; |
| signal.style.left = x + 'px'; |
| signal.style.top = y + 'px'; |
| radar.appendChild(signal); |
| |
| const echo = document.createElement('div'); |
| echo.className = 'signal-echo'; |
| echo.style.left = x + 'px'; |
| echo.style.top = y + 'px'; |
| radar.appendChild(echo); |
| |
| setTimeout(() => { |
| signal.remove(); |
| echo.remove(); |
| }, 2000); |
| } |
| |
| function trainPerceptrons() { |
| let error = 0; |
| for(let i = 0; i < 100; i++) { |
| const inputs = Array(INPUT_SIZE).fill(0).map(() => Math.random()); |
| const targets = Array(OUTPUT_SIZE).fill(0); |
| targets[Math.floor(Math.random() * OUTPUT_SIZE)] = 1; |
| error += network.train(inputs, targets); |
| } |
| document.getElementById('networkStats').innerHTML = |
| `Training complete<br>Error: ${Math.round(error * 100) / 100}<br>Network ready`; |
| } |
| |
| function toggleLearning() { |
| isLearning = !isLearning; |
| } |
| </script> |
| </body> |
| </html> |