<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Fusible Studio</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<style>
@font-face {
font-family: 'Undefined';
src: url('/undefined-medium.otf') format('opentype');
font-weight: normal;
font-style: normal;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--bg: #000000;
--surface: #131313;
--surface-hover: #1a1a1a;
--border: #353535;
--border-subtle: #131313;
--green: #86A881;
--green-dark: #229147;
--olive: #A8B365;
--text: #FFFFFF;
--text-body: rgba(255, 255, 255, 0.80);
--text-subtle: rgba(255, 255, 255, 0.45);
--alert: #f43f5e;
--copper: #c87d32;
--gold: #d4af37;
--blue: #38bdf8;
}
body {
font-family: 'Undefined', monospace;
background: var(--bg);
color: var(--text);
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
user-select: none;
}
header {
height: 52px;
background: var(--surface);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
z-index: 20;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
}
.brand-title {
font-size: 14px;
letter-spacing: 0.05em;
color: var(--text);
font-weight: bold;
}
.brand-sub {
color: var(--olive);
font-size: 13px;
}
.board-name {
color: var(--text-subtle);
font-size: 11px;
padding: 3px 8px;
background: var(--surface-hover);
border: 1px solid var(--border);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.fuse-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 3px 10px;
font-size: 11px;
letter-spacing: 0.05em;
border: 1px solid;
}
.fuse-intact {
background: rgba(34, 145, 71, 0.15);
color: var(--green);
border-color: var(--green-dark);
}
.fuse-blown {
background: rgba(244, 63, 94, 0.15);
color: var(--alert);
border-color: var(--alert);
animation: fusePulse 1.5s infinite;
}
@keyframes fusePulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
.tabs {
display: flex;
background: var(--bg);
border: 1px solid var(--border);
}
.tab-btn {
background: transparent;
border: none;
color: var(--text-subtle);
padding: 6px 14px;
font-family: 'Undefined', monospace;
font-size: 11px;
letter-spacing: 0.05em;
cursor: pointer;
border-right: 1px solid var(--border);
transition: color 0.15s ease, background 0.15s ease;
}
.tab-btn:last-child {
border-right: none;
}
.tab-btn:hover {
color: var(--text);
background: var(--surface-hover);
}
.tab-btn.active {
background: var(--surface-hover);
color: var(--green);
border-bottom: 2px solid var(--green);
}
.controls {
display: flex;
align-items: center;
gap: 6px;
}
.btn {
background: var(--surface);
border: 1px solid var(--border);
color: var(--text-body);
font-family: 'Undefined', monospace;
padding: 5px 10px;
font-size: 11px;
letter-spacing: 0.05em;
cursor: pointer;
}
.btn:hover {
background: var(--surface-hover);
color: var(--text);
border-color: var(--olive);
}
main {
flex: 1;
display: flex;
position: relative;
overflow: hidden;
}
#viewport-container {
flex: 1;
position: relative;
background: var(--bg);
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
cursor: grab;
}
#viewport-container:active {
cursor: grabbing;
}
#viewport-grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(to right, rgba(53, 53, 53, 0.25) 1px, transparent 1px),
linear-gradient(to bottom, rgba(53, 53, 53, 0.25) 1px, transparent 1px);
background-size: 32px 32px;
pointer-events: none;
}
#canvas-content {
transform-origin: 0 0;
display: inline-block;
transition: transform 0.05s linear;
will-change: transform;
z-index: 2;
}
#canvas-content svg {
display: block;
filter: drop-shadow(0 10px 25px rgba(0,0,0,0.8));
}
#three-container {
position: absolute;
inset: 0;
z-index: 3;
display: none;
cursor: default;
}
#sidebar {
width: 320px;
background: var(--surface);
border-left: 1px solid var(--border);
display: flex;
flex-direction: column;
z-index: 10;
}
.sidebar-section {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
}
.section-title {
font-size: 11px;
letter-spacing: 0.1em;
color: var(--olive);
margin-bottom: 8px;
}
.layer-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 5px 0;
font-size: 11px;
letter-spacing: 0.05em;
color: var(--text-body);
}
.layer-label {
display: flex;
align-items: center;
gap: 8px;
}
.layer-color-dot {
width: 8px;
height: 8px;
border: 1px solid rgba(255,255,255,0.2);
}
.toggle-checkbox {
accent-color: var(--green);
cursor: pointer;
}
#diagnostics-panel {
flex: 1;
overflow-y: auto;
padding: 14px 16px;
}
.diag-card {
background: rgba(244, 63, 94, 0.06);
border: 1px solid rgba(244, 63, 94, 0.3);
padding: 10px;
margin-bottom: 10px;
font-size: 11px;
}
.diag-title {
color: var(--alert);
margin-bottom: 4px;
display: flex;
align-items: center;
gap: 6px;
font-weight: bold;
}
.diag-body {
color: var(--text-body);
line-height: 1.4;
white-space: pre-wrap;
}
.diag-help {
color: var(--olive);
margin-top: 4px;
line-height: 1.3;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px 8px;
color: var(--text-subtle);
font-size: 11px;
letter-spacing: 0.05em;
text-align: center;
}
.hud-toast {
position: absolute;
bottom: 16px;
left: 16px;
background: rgba(19, 19, 19, 0.9);
border: 1px solid var(--border);
padding: 6px 12px;
font-size: 11px;
color: var(--text-subtle);
display: flex;
align-items: center;
gap: 8px;
pointer-events: none;
z-index: 10;
}
.status-dot {
width: 6px;
height: 6px;
background: var(--green);
}
</style>
</head>
<body>
<header>
<div class="brand">
<span class="brand-title">FUSIBLE</span>
<span class="brand-sub">// DEV</span>
<span class="board-name" id="board-name">BOARD PREVIEW</span>
<div id="fuse-status" class="fuse-badge fuse-intact">
<span id="fuse-icon">âš¡</span>
<span id="fuse-text">FUSE INTACT</span>
</div>
</div>
<div class="tabs">
<button class="tab-btn active" data-view="pcb" onclick="switchView('pcb')">01 // PCB LAYOUT</button>
<button class="tab-btn" data-view="schematic" onclick="switchView('schematic')">02 // SCHEMATIC</button>
<button class="tab-btn" data-view="3d" onclick="switchView('3d')">03 // 3D VIEW</button>
</div>
<div class="controls" id="controls-2d">
<button class="btn" onclick="zoomIn()">[ZOOM +]</button>
<button class="btn" onclick="zoomOut()">[ZOOM -]</button>
<button class="btn" onclick="resetZoom()">[RESET]</button>
</div>
<div class="controls" id="controls-3d" style="display: none;">
<button class="btn" onclick="setCameraPreset('TOP')">[TOP]</button>
<button class="btn" onclick="setCameraPreset('ISO')">[ISO]</button>
<button class="btn" onclick="setCameraPreset('FRONT')">[FRONT]</button>
<button class="btn" onclick="setCameraPreset('RESET')">[RESET]</button>
</div>
</header>
<main>
<div id="viewport-container">
<div id="viewport-grid"></div>
<div id="canvas-content"></div>
<div id="three-container"></div>
<div class="hud-toast">
<div class="status-dot" id="live-dot"></div>
<span id="live-status">SSE // LIVE RELOAD CONNECTED</span>
</div>
</div>
<aside id="sidebar">
<div class="sidebar-section" id="layers-section">
<div class="section-title">00 // PCB LAYERS</div>
<div class="layer-item">
<div class="layer-label">
<span class="layer-color-dot" style="background: var(--copper);"></span>
<span>TOP COPPER</span>
</div>
<input type="checkbox" class="toggle-checkbox" checked onchange="toggleLayer('top_copper', this.checked)">
</div>
<div class="layer-item">
<div class="layer-label">
<span class="layer-color-dot" style="background: var(--blue);"></span>
<span>BOTTOM COPPER</span>
</div>
<input type="checkbox" class="toggle-checkbox" checked onchange="toggleLayer('bottom_copper', this.checked)">
</div>
<div class="layer-item">
<div class="layer-label">
<span class="layer-color-dot" style="background: var(--gold);"></span>
<span>PADS (ENIG GOLD)</span>
</div>
<input type="checkbox" class="toggle-checkbox" checked onchange="toggleLayer('pads', this.checked)">
</div>
<div class="layer-item">
<div class="layer-label">
<span class="layer-color-dot" style="background: #ffffff;"></span>
<span>SILKSCREEN</span>
</div>
<input type="checkbox" class="toggle-checkbox" checked onchange="toggleLayer('silkscreen', this.checked)">
</div>
</div>
<div class="sidebar-section">
<div class="section-title">04 // DIAGNOSTICS</div>
</div>
<div id="diagnostics-panel">
<div class="empty-state">
<span>NO ELECTRICAL VIOLATIONS DETECTED.</span>
</div>
</div>
</aside>
</main>
<script>
let currentView = 'pcb';
let pcbSvg = '';
let schematicSvg = '';
let lastCircuitJson = null;
let zoomLevel = 1.0;
let panX = 0;
let panY = 0;
let isDragging = false;
let startX = 0;
let startY = 0;
const viewport = document.getElementById('viewport-container');
const canvas = document.getElementById('canvas-content');
let scene = null;
let camera = null;
let renderer = null;
let controls = null;
let boardGroup = null;
let threeInitialized = false;
let boardWidth = 60;
let boardHeight = 40;
let boardCenterX = 0;
let boardCenterZ = 0;
function applyTransform() {
canvas.style.transform = `translate(${panX}px, ${panY}px) scale(${zoomLevel})`;
}
function zoomIn() {
zoomLevel *= 1.25;
applyTransform();
}
function zoomOut() {
zoomLevel /= 1.25;
applyTransform();
}
function resetZoom() {
zoomLevel = 1.0;
panX = 0;
panY = 0;
applyTransform();
}
viewport.addEventListener('wheel', (e) => {
if (currentView === '3d') return;
e.preventDefault();
const zoomFactor = e.deltaY < 0 ? 1.15 : 0.85;
zoomLevel *= zoomFactor;
applyTransform();
}, { passive: false });
viewport.addEventListener('mousedown', (e) => {
if (currentView === '3d') return;
if (e.button === 0) {
isDragging = true;
startX = e.clientX - panX;
startY = e.clientY - panY;
}
});
window.addEventListener('mousemove', (e) => {
if (currentView === '3d') return;
if (isDragging) {
panX = e.clientX - startX;
panY = e.clientY - startY;
applyTransform();
}
});
window.addEventListener('mouseup', () => {
isDragging = false;
});
function switchView(view) {
currentView = view;
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.toggle('active', btn.getAttribute('data-view') === view);
});
const is3d = view === '3d';
document.getElementById('canvas-content').style.display = is3d ? 'none' : 'inline-block';
document.getElementById('three-container').style.display = is3d ? 'block' : 'none';
document.getElementById('controls-2d').style.display = is3d ? 'none' : 'flex';
document.getElementById('controls-3d').style.display = is3d ? 'flex' : 'none';
document.getElementById('layers-section').style.display = view === 'pcb' ? 'block' : 'none';
if (is3d) {
initThree();
build3DScene();
setTimeout(onThreeResize, 10);
setCameraPreset('ISO');
} else {
renderCurrentView();
}
}
function renderCurrentView() {
if (currentView === 'pcb') {
canvas.innerHTML = pcbSvg || '<div class="empty-state">NO PCB LAYOUT AVAILABLE</div>';
} else if (currentView === 'schematic') {
canvas.innerHTML = schematicSvg || '<div class="empty-state">NO SCHEMATIC AVAILABLE</div>';
}
}
function toggleLayer(layerName, visible) {
const groups = canvas.querySelectorAll(`g[id="${layerName}"]`);
groups.forEach(g => {
g.style.display = visible ? '' : 'none';
});
}
function updateDiagnostics(fuseBlown, diagnostics) {
const statusEl = document.getElementById('fuse-status');
const textEl = document.getElementById('fuse-text');
const iconEl = document.getElementById('fuse-icon');
const diagPanel = document.getElementById('diagnostics-panel');
if (fuseBlown) {
statusEl.className = 'fuse-badge fuse-blown';
textEl.textContent = `FUSE BLOWN (${diagnostics.length})`;
iconEl.textContent = '💥';
if (diagnostics.length > 0) {
diagPanel.innerHTML = diagnostics.map(d => `
<div class="diag-card">
<div class="diag-title">[!] ${escapeHtml((d.rule || 'VIOLATION').toUpperCase())}</div>
<div class="diag-body">${escapeHtml(d.message || '')}</div>
${d.help ? `<div class="diag-help">> ${escapeHtml(d.help)}</div>` : ''}
</div>
`).join('');
}
} else {
statusEl.className = 'fuse-badge fuse-intact';
textEl.textContent = 'FUSE INTACT';
iconEl.textContent = 'âš¡';
diagPanel.innerHTML = '<div class="empty-state"><span>NO ELECTRICAL VIOLATIONS DETECTED.</span></div>';
}
}
function escapeHtml(str) {
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
}
function onThreeResize() {
if (!renderer || !camera) return;
const container = document.getElementById('three-container');
const w = container.clientWidth;
const h = container.clientHeight;
if (w === 0 || h === 0) return;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
}
function initThree() {
if (threeInitialized) return;
const container = document.getElementById('three-container');
const width = container.clientWidth || window.innerWidth;
const height = container.clientHeight || window.innerHeight;
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 2000);
camera.position.set(0, 100, 100);
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(width, height);
renderer.setPixelRatio(window.devicePixelRatio || 1);
container.appendChild(renderer.domElement);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
const ambientLight = new THREE.AmbientLight(0xffffff, 0.75);
scene.add(ambientLight);
const dirLight1 = new THREE.DirectionalLight(0xffffff, 0.9);
dirLight1.position.set(60, 120, 80);
scene.add(dirLight1);
const dirLight2 = new THREE.DirectionalLight(0x8899aa, 0.5);
dirLight2.position.set(-60, -80, -60);
scene.add(dirLight2);
const grid = new THREE.GridHelper(200, 40, 0x353535, 0x131313);
grid.position.y = -1.0;
scene.add(grid);
boardGroup = new THREE.Group();
scene.add(boardGroup);
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', onThreeResize);
threeInitialized = true;
}
function setCameraPreset(preset) {
if (!controls || !camera) return;
const dist = Math.max(boardWidth, boardHeight) * 1.8;
const tx = boardCenterX;
const tz = boardCenterZ;
controls.target.set(tx, 0, tz);
if (preset === 'TOP') {
camera.position.set(tx, dist, tz + 0.001);
camera.up.set(0, 0, -1);
} else if (preset === 'FRONT') {
camera.position.set(tx, dist * 0.1, tz + dist);
camera.up.set(0, 1, 0);
} else if (preset === 'ISO' || preset === 'RESET') {
camera.position.set(tx + dist * 0.7, dist * 0.8, tz + dist * 0.7);
camera.up.set(0, 1, 0);
}
controls.update();
}
function build3DScene() {
if (!boardGroup) return;
while (boardGroup.children.length > 0) {
const obj = boardGroup.children[0];
boardGroup.remove(obj);
if (obj.geometry) obj.geometry.dispose();
}
const elements = Array.isArray(lastCircuitJson)
? lastCircuitJson
: (lastCircuitJson && Array.isArray(lastCircuitJson.elements) ? lastCircuitJson.elements : []);
const boards = elements.filter(e => e.type === 'pcb_board');
boardWidth = 60;
boardHeight = 40;
boardCenterX = 0;
boardCenterZ = 0;
if (boards.length > 0) {
const b = boards[0];
boardWidth = b.width || 60;
boardHeight = b.height || 40;
boardCenterX = b.center?.x || 0;
boardCenterZ = b.center?.y || 0;
} else {
let minX = Infinity, maxX = -Infinity, minZ = Infinity, maxZ = -Infinity;
elements.forEach(e => {
const x = e.x ?? e.center?.x;
const z = e.y ?? e.center?.y;
if (x !== undefined && z !== undefined) {
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (z < minZ) minZ = z;
if (z > maxZ) maxZ = z;
}
});
if (minX !== Infinity) {
boardWidth = Math.max(20, (maxX - minX) + 20);
boardHeight = Math.max(20, (maxZ - minZ) + 20);
boardCenterX = (minX + maxX) / 2;
boardCenterZ = (minZ + maxZ) / 2;
}
}
const substrateThickness = 1.6;
const boardMat = new THREE.MeshStandardMaterial({
color: 0x112417,
roughness: 0.5,
metalness: 0.1
});
const boardGeo = new THREE.BoxGeometry(boardWidth, substrateThickness, boardHeight);
const boardMesh = new THREE.Mesh(boardGeo, boardMat);
boardMesh.position.set(boardCenterX, 0, boardCenterZ);
boardGroup.add(boardMesh);
const padMat = new THREE.MeshStandardMaterial({
color: 0xd4af37,
metalness: 0.85,
roughness: 0.25
});
elements.filter(e => e.type === 'pcb_smtpad').forEach(pad => {
const px = pad.x ?? pad.center?.x ?? 0;
const pz = pad.y ?? pad.center?.y ?? 0;
const isBottom = pad.layer === 'bottom';
const py = isBottom ? -(substrateThickness / 2 + 0.02) : (substrateThickness / 2 + 0.02);
const shape = pad.shape || (pad.radius ? 'circle' : 'rect');
let padMesh;
if (shape === 'circle') {
const r = pad.radius || ((pad.width || 1.0) / 2);
const geo = new THREE.CylinderGeometry(r, r, 0.04, 24);
padMesh = new THREE.Mesh(geo, padMat);
} else {
const pw = pad.width || 1.2;
const ph = pad.height || 1.2;
const geo = new THREE.BoxGeometry(pw, 0.04, ph);
padMesh = new THREE.Mesh(geo, padMat);
}
padMesh.position.set(px, py, pz);
boardGroup.add(padMesh);
});
const viaMat = new THREE.MeshStandardMaterial({
color: 0xc87d32,
metalness: 0.8,
roughness: 0.3
});
const holeMat = new THREE.MeshBasicMaterial({ color: 0x050505 });
elements.filter(e => e.type === 'pcb_via' || e.type === 'pcb_plated_hole').forEach(via => {
const vx = via.x ?? via.center?.x ?? 0;
const vz = via.y ?? via.center?.y ?? 0;
const outerR = ((via.outer_diameter || 0.6) / 2);
const holeR = ((via.hole_diameter || 0.3) / 2);
const viaGeo = new THREE.CylinderGeometry(outerR, outerR, substrateThickness + 0.06, 24);
const viaMesh = new THREE.Mesh(viaGeo, viaMat);
viaMesh.position.set(vx, 0, vz);
boardGroup.add(viaMesh);
const holeGeo = new THREE.CylinderGeometry(holeR, holeR, substrateThickness + 0.08, 16);
const holeMesh = new THREE.Mesh(holeGeo, holeMat);
holeMesh.position.set(vx, 0, vz);
boardGroup.add(holeMesh);
});
elements.filter(e => e.type === 'pcb_trace').forEach(trace => {
const route = trace.route || [];
if (route.length < 2) return;
const isBottom = route[0].layer === 'bottom' || trace.layer === 'bottom';
const color = isBottom ? 0x38bdf8 : 0xc87d32;
const py = isBottom ? -(substrateThickness / 2 + 0.03) : (substrateThickness / 2 + 0.03);
const traceMat = new THREE.MeshStandardMaterial({
color: color,
metalness: 0.8,
roughness: 0.35,
side: THREE.DoubleSide
});
for (let i = 0; i < route.length - 1; i++) {
const p1 = route[i];
const p2 = route[i + 1];
const w = p1.width || trace.width || 0.3;
const dx = p2.x - p1.x;
const dz = p2.y - p1.y;
const len = Math.sqrt(dx * dx + dz * dz);
if (len < 0.001) continue;
const angle = Math.atan2(dz, dx);
const geo = new THREE.PlaneGeometry(len, w);
geo.rotateX(-Math.PI / 2);
geo.rotateY(-angle);
const mesh = new THREE.Mesh(geo, traceMat);
mesh.position.set((p1.x + p2.x) / 2, py, (p1.y + p2.y) / 2);
boardGroup.add(mesh);
}
});
const chipMat = new THREE.MeshStandardMaterial({
color: 0x1a1a1a,
roughness: 0.6,
metalness: 0.2
});
const pinMat = new THREE.MeshStandardMaterial({
color: 0xe2e8f0,
roughness: 0.2,
metalness: 0.9
});
elements.filter(e => e.type === 'pcb_component').forEach(comp => {
const cx = comp.center?.x ?? comp.x ?? 0;
const cz = comp.center?.y ?? comp.y ?? 0;
const cw = comp.width || 6;
const ch = comp.height || 6;
const cThick = 1.6;
const isBottom = comp.layer === 'bottom';
const cy = isBottom ? -(substrateThickness / 2 + cThick / 2 + 0.05) : (substrateThickness / 2 + cThick / 2 + 0.05);
const chipGeo = new THREE.BoxGeometry(cw, cThick, ch);
const chipMesh = new THREE.Mesh(chipGeo, chipMat);
chipMesh.position.set(cx, cy, cz);
boardGroup.add(chipMesh);
const pinW = 0.6;
const pinLen = 1.0;
const pinH = 0.2;
const pinY = isBottom ? -(substrateThickness / 2 + 0.05) : (substrateThickness / 2 + 0.05);
const numPins = 4;
for (let p = 0; p < numPins; p++) {
const offset = (p - (numPins - 1) / 2) * (ch / numPins);
const leftPin = new THREE.Mesh(new THREE.BoxGeometry(pinLen, pinH, pinW), pinMat);
leftPin.position.set(cx - cw / 2, pinY, cz + offset);
boardGroup.add(leftPin);
const rightPin = new THREE.Mesh(new THREE.BoxGeometry(pinLen, pinH, pinW), pinMat);
rightPin.position.set(cx + cw / 2, pinY, cz + offset);
boardGroup.add(rightPin);
}
});
}
function connectSSE() {
const eventSource = new EventSource('/events');
const liveDot = document.getElementById('live-dot');
const liveStatus = document.getElementById('live-status');
eventSource.onopen = () => {
liveDot.style.background = 'var(--green)';
liveStatus.textContent = 'SSE // LIVE RELOAD CONNECTED';
};
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.board_name) {
document.getElementById('board-name').textContent = data.board_name.toUpperCase();
}
if (data.pcb_svg) pcbSvg = data.pcb_svg;
if (data.schematic_svg) schematicSvg = data.schematic_svg;
if (data.circuit_json) {
lastCircuitJson = data.circuit_json;
if (currentView === '3d') {
build3DScene();
}
}
updateDiagnostics(data.fuse_blown, data.diagnostics || []);
if (currentView !== '3d') {
renderCurrentView();
}
} catch (err) {
console.error(err);
}
};
eventSource.onerror = () => {
liveDot.style.background = 'var(--alert)';
liveStatus.textContent = 'SSE // RECONNECTING...';
};
}
connectSSE();
</script>
</body>
</html>