feat(app): update branding, improve button animations, and complete PC Components simulator

- Replace logo with updated Computing:Box branding assets
- Fix animations for Random and Reset buttons for smoother interaction
- Implement full first version of the PC Components simulator
- Update built output to reflect new assets, layout, and functionality
- Remove legacy assets and outdated build files

Signed-off-by: Alexander Lyall <alex@adcm.uk>
This commit is contained in:
2026-03-30 18:54:23 +01:00
parent 2deba8ba2f
commit f70120c2a0
31 changed files with 859 additions and 2347 deletions

View File

@@ -33,6 +33,7 @@
let nextNodeId = 1;
let nextWireId = 1;
let discoveredStates = new Set();
// Interaction State
let isDraggingNode = null;
@@ -199,41 +200,97 @@
}
/* --- Truth Table Generation --- */
function generateTruthTable() {
if (!ttContainer) return;
function generateTruthTable() {
// 1. Find the target container
let container = document.getElementById("truthTableContainer");
// Fail-safe: Find the card if the specific ID is missing
if (!container) {
const cards = document.querySelectorAll('.card');
const ttCard = Array.from(cards).find(c => c.innerText.includes('LIVE TRUTH TABLE'));
if (ttCard) {
container = ttCard.querySelector('.cardBodyInner') || ttCard;
}
}
const inNodes = Object.values(nodes).filter(n => n.type === 'INPUT').sort((a,b) => a.label.localeCompare(b.label));
const outNodes = Object.values(nodes).filter(n => n.type === 'OUTPUT').sort((a,b) => a.label.localeCompare(b.label));
if (!container) return;
// 2. Identify and sort Inputs and Outputs
const inNodes = Object.values(nodes)
.filter(n => n.type === 'INPUT')
.sort((a,b) => a.label.localeCompare(b.label));
const outNodes = Object.values(nodes)
.filter(n => n.type === 'OUTPUT')
.sort((a,b) => a.label.localeCompare(b.label));
// 3. Handle Empty State
if (inNodes.length === 0 || outNodes.length === 0) {
ttContainer.innerHTML = '<div style="padding: 16px; color: var(--muted); text-align:center;">Add inputs and outputs to generate table.</div>'; return;
}
if (inNodes.length > 6) {
ttContainer.innerHTML = '<div style="padding: 16px; color: var(--muted); text-align:center;">Maximum 6 inputs supported.</div>'; return;
container.innerHTML = '<div style="padding: 20px; color: var(--muted); text-align:center; font-family: var(--bit-font); font-size: 12px; letter-spacing: 1px;">CONNECT INPUTS & OUTPUTS</div>';
return;
}
let html = '<table class="tt-table"><thead><tr>';
// 4. Build Table within the styled wrapper
let html = '<div class="tt-table-wrap"><table class="tt-table"><thead><tr>';
// Headers
inNodes.forEach(n => html += `<th>${n.label}</th>`);
outNodes.forEach(n => html += `<th style="color:var(--text);">${n.label}</th>`);
outNodes.forEach(n => html += `<th style="color:var(--text); border-left: 1px solid rgba(255,255,255,0.1);">${n.label}</th>`);
html += '</tr></thead><tbody>';
// 5. Generate Rows
const numRows = Math.pow(2, inNodes.length);
for (let i = 0; i < numRows; i++) {
let override = {};
inNodes.forEach((n, idx) => { override[n.id] = ((i >> (inNodes.length - 1 - idx)) & 1) === 1; });
let outStates = evaluateGraph(override);
let stateArr = [];
// Calculate binary state for this row
inNodes.forEach((n, idx) => {
let val = ((i >> (inNodes.length - 1 - idx)) & 1) === 1;
override[n.id] = val;
stateArr.push(val ? '1' : '0');
});
let stateStr = stateArr.join('');
let isFound = discoveredStates.has(stateStr);
let outResults = evaluateGraph(override); // Simulate the board logic for this state
html += '<tr>';
inNodes.forEach(n => { let val = override[n.id]; html += `<td class="${val ? 'tt-on' : ''}">${val ? 1 : 0}</td>`; });
outNodes.forEach(n => { let val = outStates[n.id]; html += `<td class="${val ? 'tt-on' : ''}" style="font-weight:bold;">${val ? 1 : 0}</td>`; });
// Input Cells
inNodes.forEach(n => {
let v = override[n.id];
html += `<td class="${v ? 'tt-on' : ''}">${v ? 1 : 0}</td>`;
});
// Output Cells (Discovery Logic)
outNodes.forEach(n => {
if (isFound) {
let v = outResults[n.id];
html += `<td class="${v ? 'tt-on' : ''}" style="font-weight:bold; border-left: 1px solid rgba(255,255,255,0.05);">${v ? 1 : 0}</td>`;
} else {
html += `<td style="color: #444; border-left: 1px solid rgba(255,255,255,0.05); opacity: 0.6;">?</td>`;
}
});
html += '</tr>';
}
html += '</tbody></table>';
ttContainer.innerHTML = html;
html += '</tbody></table></div>';
container.innerHTML = html;
}
function runSimulation() {
function runSimulation(topologyChanged = false) {
// If you add/remove wires, reset the table memory because the logic changed
if (topologyChanged) discoveredStates.clear();
evaluateGraph();
// Check the current board state (e.g., "10") and save it to memory
const inNodes = Object.values(nodes).filter(n => n.type === 'INPUT').sort((a,b) => a.label.localeCompare(b.label));
if (inNodes.length > 0) {
let currentStateStr = inNodes.map(n => n.value ? '1' : '0').join('');
discoveredStates.add(currentStateStr);
}
renderWires();
generateTruthTable();
}
@@ -288,19 +345,18 @@
viewport.appendChild(el);
node.el = el;
if (node.type === 'INPUT') {
el.querySelector('.switch').addEventListener('click', (e) => {
const dist = Math.hypot(e.clientX - clickStartX, e.clientY - clickStartY);
if (dist > 3) {
e.preventDefault(); // Prevents toggle if it was a drag motion
} else {
node.value = !node.value;
el.querySelector('.switch').classList.toggle('active-sim', node.value);
el.querySelector('.slider').style.background = node.value ? 'rgba(40,240,122,.25)' : '';
el.querySelector('.slider').style.borderColor = node.value ? 'rgba(40,240,122,.30)' : '';
el.querySelector('.slider').innerHTML = node.value ? `<style>#logicPage [data-id="${node.id}"] .slider::before { transform: translateX(28px); }</style>` : '';
runSimulation();
}
if (node.type === 'INPUT') {
const sw = el.querySelector('.switch');
sw.addEventListener('click', (e) => {
// ... (keep your clickStartX/Y drag check) ...
node.value = !node.value;
// This targets the exact class your CSS needs for the glow and move
sw.classList.toggle('active-sim', node.value);
// This ensures the table and logic update
runSimulation();
});
}
return el;
@@ -312,7 +368,8 @@
if (type === 'OUTPUT') label = getNextOutputLabel();
if (type === 'GATE') label = gateType;
const id = `node_${nextNodeId++}`;
// Double check this line in logicGates.js
const id = `node_${Date.now()}_${nextNodeId++}`;
const offset = Math.floor(Math.random() * 40);
const x = dropX !== null ? dropX : (type === 'INPUT' ? 50 : (type === 'OUTPUT' ? 600 : 300) + offset);
const y = dropY !== null ? dropY : 150 + offset;
@@ -320,7 +377,8 @@
const node = { id, type, gateType, label, x, y, value: false, el: null };
nodes[id] = node;
createNodeElement(node);
runSimulation();
// Change the very last line to:
runSimulation(true);
}
/* --- Global Interaction Handlers --- */
@@ -433,8 +491,9 @@
connections.push({ id: `conn_${nextWireId++}`, fromNode: wiringStart.node, fromPort: 'out', toNode: targetNodeId, toPort: targetPortId });
}
}
// Change the very last line of the if(wiringStart) block to:
wiringStart = null; tempWirePath = null;
runSimulation();
runSimulation(true);
}
});
@@ -451,7 +510,8 @@
viewport.removeChild(nodes[selectedNodeId].el);
}
delete nodes[selectedNodeId];
clearSelection(); runSimulation();
// Change the two deletion triggers to:
clearSelection(); runSimulation(true);
}
}
});
@@ -471,10 +531,17 @@
});
/* --- Init --- */
btnClearBoard?.addEventListener('click', () => {
btnClearBoard?.addEventListener('click', () => {
viewport.querySelectorAll('.lg-node').forEach(el => el.remove());
nodes = {}; connections = [];
runSimulation();
// Target your specific SVG layer class
const svgLayer = document.querySelector('.lg-svg-layer');
if (svgLayer) svgLayer.innerHTML = '';
nodes = {};
connections = [];
discoveredStates.clear();
runSimulation(true);
});
toolboxToggle?.addEventListener("click", () => {