diff --git a/dist/_astro/binary.astro_astro_type_script_index_0_lang.C_c_A3x5.js b/dist/_astro/binary.astro_astro_type_script_index_0_lang.C_c_A3x5.js deleted file mode 100644 index a23a7d5..0000000 --- a/dist/_astro/binary.astro_astro_type_script_index_0_lang.C_c_A3x5.js +++ /dev/null @@ -1,12 +0,0 @@ -(()=>{const d=document.getElementById("bitsGrid"),h=document.getElementById("denaryNumber"),M=document.getElementById("binaryNumber"),f=document.getElementById("bitsInput"),m=document.getElementById("modeToggle"),E=document.getElementById("modeHint"),T=document.getElementById("lblUnsigned"),k=document.getElementById("lblTwos"),H=document.getElementById("btnCustomBinary"),G=document.getElementById("btnCustomDenary"),P=document.getElementById("btnShiftLeft"),V=document.getElementById("btnShiftRight"),q=document.getElementById("btnDec"),Z=document.getElementById("btnInc"),z=document.getElementById("btnClear"),I=document.getElementById("btnRandom"),O=document.getElementById("btnBitsUp"),W=document.getElementById("btnBitsDown"),R=document.getElementById("toolboxToggle"),w=document.getElementById("binaryPage");let i=b(Number(f?.value??8),1,64),s=new Array(i).fill(!1),u=null;function b(t,n,e){return Number.isFinite(t)?Math.max(n,Math.min(e,Math.trunc(t))):n}function l(){return!!m?.checked}function a(t){return 1n<>BigInt(o)&1n)===1n}function L(){const t=v();return s[i-1]===!0?t-a(i):t}function x(t){const n=a(i);let e=t;e=(e%n+n)%n,g(e)}function j(){let t="";for(let n=i-1;n>=0;n--){t+=s[n]?"1":"0";const e=i-n;n!==0&&e%4===0&&(t+=" ")}return t.trimEnd()}function D(){E&&(l()?E.textContent="Tip: In two's complement, the left-most bit (MSB) represents a negative value.":E.textContent="Tip: In unsigned binary, all bits represent positive values.")}function A(){if(!d)return;const t=d.parentElement;if(!t)return;const n=t.getBoundingClientRect().width,o=b(Math.floor(n/100),1,12);d.style.setProperty("--cols",String(Math.min(o,i)))}function C(t){i=b(t,1,64),f&&(f.value=String(i));const n=s.slice();s=new Array(i).fill(!1);for(let e=0;e=0;e--){const o=document.createElement("div");o.className="bit",o.innerHTML=` - -
- - `,d.appendChild(o)}d.querySelectorAll('input[type="checkbox"]').forEach(e=>{e.addEventListener("change",()=>{const o=Number(e.dataset.index);s[o]=e.checked,r()})}),A(),r()}function J(){for(let t=0;t{const n=Number(t.dataset.index);t.checked=!!s[n]})}function Q(){for(let t=0;tc)return!1;x(e)}else{if(e<0n||e>$(i))return!1;g(e)}return r(),!0}function tt(){for(let t=i-1;t>=1;t--)s[t]=s[t-1];s[0]=!1,r()}function nt(){const t=s[i-1];for(let n=0;nn&&(e=t),x(e)}else{const t=y(i);g((v()+1n)%t)}r()}function ot(){if(l()){const t=B(i),n=p(i);let e=L()-1n;e0n&&(c=c>>U),c{lt(),Date.now()-t>=n&&(clearInterval(u),u=null,N(!1))},80)}function S(t){const n=b(t,1,64);C(n)}function F(t){if(!w)return;w.classList.toggle("toolboxCollapsed",!!t);const n=!t;R?.setAttribute("aria-expanded",n?"true":"false")}m?.addEventListener("change",r),H?.addEventListener("click",()=>{const t=prompt(`Enter binary (spaces allowed). Current width: ${i} bits`);t!==null&&(Y(t)||alert("Invalid binary"))}),G?.addEventListener("click",()=>{const t=prompt(l()?`Enter denary (${B(i).toString()} to ${p(i).toString()}):`:`Enter denary (0 to ${$(i).toString()}):`);t!==null&&(_(t)||alert("Invalid denary for current mode/bit width"))}),P?.addEventListener("click",tt),V?.addEventListener("click",nt),Z?.addEventListener("click",it),q?.addEventListener("click",ot),z?.addEventListener("click",et),I?.addEventListener("click",rt),O?.addEventListener("click",()=>S(i+1)),W?.addEventListener("click",()=>S(i-1)),f?.addEventListener("change",()=>S(Number(f.value))),R?.addEventListener("click",()=>{const t=w?.classList.contains("toolboxCollapsed");F(!t)}),window.addEventListener("resize",()=>{A()}),D(),C(i),F(!1)})(); diff --git a/dist/binary/index.html b/dist/binary/index.html index 669dde1..9b326ba 100644 --- a/dist/binary/index.html +++ b/dist/binary/index.html @@ -13,8 +13,81 @@ var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); })(); - -
Denary
0
Binary
00000000
Computer Science Concept Simulators
Version: +dev • © 2026 Computing:Box • Created with ♥ by Mr A Lyall
\ No newline at end of file diff --git a/dist/favicon.svg b/dist/favicon.svg deleted file mode 100644 index f157bd1..0000000 --- a/dist/favicon.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/dist/hexadecimal/index.html b/dist/hexadecimal/index.html index 2febed5..0f18a89 100644 --- a/dist/hexadecimal/index.html +++ b/dist/hexadecimal/index.html @@ -13,8 +13,81 @@ var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); })(); - -
Denary
0
Hexadecimal
00
Binary
00000000
+ + + \ No newline at end of file diff --git a/src/pages/binary.astro b/src/pages/binary.astro index 5233717..a0ee816 100644 --- a/src/pages/binary.astro +++ b/src/pages/binary.astro @@ -88,7 +88,7 @@ import "../styles/number-simulators.css"; -
+
diff --git a/src/pages/logic-gates.astro b/src/pages/logic-gates.astro index 072919c..8157e0d 100644 --- a/src/pages/logic-gates.astro +++ b/src/pages/logic-gates.astro @@ -38,12 +38,11 @@ import "../styles/logic-gates.css";
-
Live Truth Table
-
- Show / Hide Table -
Auto-generates based on current wiring. (Max 6 inputs)
-
-
+
Live Truth Table
+
Auto-generates based on current wiring.
+
+
+
Tools
diff --git a/src/pages/pc-builder.astro b/src/pages/pc-builder.astro index 6f3499f..e115780 100644 --- a/src/pages/pc-builder.astro +++ b/src/pages/pc-builder.astro @@ -31,20 +31,25 @@ import "../styles/pc-builder.css";
diff --git a/src/scripts/binary.js b/src/scripts/binary.js index e596092..552f36a 100644 --- a/src/scripts/binary.js +++ b/src/scripts/binary.js @@ -375,7 +375,7 @@ setRandomRunning(true); const start = Date.now(); - const durationMs = 1125; + const durationMs = 1500; const tickMs = 80; randomTimer = setInterval(() => { diff --git a/src/scripts/logicGates.js b/src/scripts/logicGates.js index a7f4571..692baf1 100644 --- a/src/scripts/logicGates.js +++ b/src/scripts/logicGates.js @@ -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 = '
Add inputs and outputs to generate table.
'; return; - } - if (inNodes.length > 6) { - ttContainer.innerHTML = '
Maximum 6 inputs supported.
'; return; + container.innerHTML = '
CONNECT INPUTS & OUTPUTS
'; + return; } - let html = ''; + // 4. Build Table within the styled wrapper + let html = '
'; + + // Headers inNodes.forEach(n => html += ``); - outNodes.forEach(n => html += ``); + outNodes.forEach(n => html += ``); html += ''; + // 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 += ''; - inNodes.forEach(n => { let val = override[n.id]; html += ``; }); - outNodes.forEach(n => { let val = outStates[n.id]; html += ``; }); + + // Input Cells + inNodes.forEach(n => { + let v = override[n.id]; + html += ``; + }); + + // Output Cells (Discovery Logic) + outNodes.forEach(n => { + if (isFound) { + let v = outResults[n.id]; + html += ``; + } else { + html += ``; + } + }); html += ''; } - html += '
${n.label}${n.label}${n.label}
${val ? 1 : 0}${val ? 1 : 0}${v ? 1 : 0}${v ? 1 : 0}?
'; - ttContainer.innerHTML = html; + + html += ''; + 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 ? `` : ''; - 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", () => { diff --git a/src/scripts/pcBuilder.js b/src/scripts/pcBuilder.js index b23e2b2..50a2a09 100644 --- a/src/scripts/pcBuilder.js +++ b/src/scripts/pcBuilder.js @@ -11,7 +11,7 @@ const toolboxToggle = document.getElementById("toolboxToggle"); const pcPage = document.getElementById("pcPage"); - /* --- Extensive PC Component Library --- */ + /* --- ULTRA-REALISTIC COMPONENT LIBRARY --- */ const PC_PARTS = { 'CASE': { name: 'ATX PC Case', w: 600, h: 550, z: 5, ports: [], @@ -29,8 +29,7 @@ name: 'Motherboard', w: 360, h: 400, z: 10, ports: [ { id: 'atx_pwr', x: 340, y: 150 }, { id: 'sata1', x: 340, y: 300 }, { id: 'sata2', x: 340, y: 330 }, - { id: 'usb1', x: 10, y: 40 }, { id: 'usb2', x: 10, y: 70 }, { id: 'usb3', x: 10, y: 100 }, { id: 'usb4', x: 10, y: 130 }, - { id: 'audio', x: 10, y: 170 }, { id: 'disp', x: 10, y: 210 } + { id: 'usb1', x: 10, y: 40 }, { id: 'usb2', x: 10, y: 70 }, { id: 'audio', x: 10, y: 170 }, { id: 'disp', x: 10, y: 210 } ], slots: { 'CPU1': { x: 120, y: 40, accepts: 'CPU' }, @@ -40,21 +39,56 @@ 'M2_1': { x: 120, y: 170, accepts: 'M2_SSD' }, 'M2_2': { x: 120, y: 250, accepts: 'M2_SSD' }, 'PCIE1': { x: 40, y: 200, accepts: 'GPU' }, 'PCIE2': { x: 40, y: 300, accepts: 'GPU' } }, - // Uses a lighter slate grey #2C303A to stand out from the case - svg: `` + svg: `M.2_1M.2_2` }, - 'CPU': { name: 'Processor', w: 80, h: 80, z: 20, ports: [], slots: {}, svg: `CPU` }, - 'COOLER': { name: 'CPU Fan', w: 120, h: 120, z: 30, ports: [], slots: {}, svg: `` }, - 'RAM': { name: 'DDR4 Memory', w: 15, h: 100, z: 20, ports: [], slots: {}, svg: `` }, - 'GPU': { name: 'Graphics Card', w: 280, h: 60, z: 40, slots: {}, ports: [{ id: 'pwr_in', x: 270, y: 10 }, { id: 'disp_out', x: 10, y: 30 }], svg: `` }, - 'M2_SSD': { name: 'M.2 NVMe SSD', w: 80, h: 15, z: 20, ports: [], slots: {}, svg: `` }, - 'SATA_SSD': { name: '2.5" SATA SSD', w: 100, h: 70, z: 20, slots: {}, ports: [{id:'data', x:90, y:20}, {id:'pwr', x:90, y:50}], svg: `SSD` }, - 'HDD': { name: '3.5" Mech HDD', w: 120, h: 140, z: 20, slots: {}, ports: [{id:'data', x:110, y:20}, {id:'pwr', x:110, y:120}], svg: `` }, - 'PSU': { name: 'Power Supply', w: 160, h: 90, z: 20, slots: {}, ports: [{id:'out1',x:150,y:20}, {id:'out2',x:150,y:40}, {id:'out3',x:150,y:60}, {id:'out4',x:150,y:80}], svg: `` }, - 'MONITOR': { name: 'Monitor', w: 240, h: 160, z: 30, slots: {}, ports: [{id:'disp', x:120, y:140}], svg: `` }, - 'KEYBOARD': { name: 'Keyboard', w: 180, h: 60, z: 30, slots: {}, ports: [{id:'usb', x:90, y:10}], svg: `` }, - 'MOUSE': { name: 'Mouse', w: 30, h: 50, z: 30, slots: {}, ports: [{id:'usb', x:15, y:5}], svg: `` }, - 'SPEAKER': { name: 'Speakers', w: 40, h: 80, z: 30, slots: {}, ports: [{id:'audio', x:20, y:10}], svg: `` } + 'CPU': { + name: 'Processor', w: 80, h: 80, z: 20, ports: [], slots: {}, + svg: `INTELCORE i914900K` + }, + 'COOLER': { + name: 'Liquid AIO', w: 120, h: 120, z: 30, ports: [], slots: {}, + svg: `32°C2400 RPM` + }, + 'RAM': { + name: 'RGB Memory', w: 15, h: 100, z: 20, ports: [], slots: {}, + svg: `` + }, + 'GPU': { + name: 'Graphics Card', w: 280, h: 80, z: 40, slots: {}, ports: [{ id: 'pwr_in', x: 270, y: 10 }, { id: 'disp_out', x: 10, y: 40 }], + svg: `GEFORCE RTX` + }, + 'M2_SSD': { + name: 'M.2 NVMe SSD', w: 80, h: 22, z: 20, ports: [], slots: {}, + svg: `990 PRO 2TB` + }, + 'SATA_SSD': { + name: '2.5" SATA SSD', w: 100, h: 70, z: 20, slots: {}, ports: [{id:'data', x:90, y:20}, {id:'pwr', x:90, y:50}], + svg: `SAMSUNG` + }, + 'HDD': { + name: '3.5" Mech HDD', w: 120, h: 140, z: 20, slots: {}, ports: [{id:'data', x:110, y:20}, {id:'pwr', x:110, y:120}], + svg: `WD BLACK12TB HDD` + }, + 'PSU': { + name: 'Power Supply', w: 160, h: 90, z: 20, slots: {}, ports: [{id:'out1',x:150,y:20}, {id:'out2',x:150,y:40}, {id:'out3',x:150,y:60}, {id:'out4',x:150,y:80}], + svg: `1200W` + }, + 'MONITOR': { + name: 'Monitor', w: 240, h: 180, z: 30, slots: {}, ports: [{id:'disp', x:120, y:140}], + svg: `ASUS` + }, + 'KEYBOARD': { + name: 'Keyboard', w: 180, h: 60, z: 30, slots: {}, ports: [{id:'usb', x:90, y:10}], + svg: `` + }, + 'MOUSE': { + name: 'Mouse', w: 30, h: 54, z: 30, slots: {}, ports: [{id:'usb', x:15, y:5}], + svg: `` + }, + 'SPEAKER': { + name: 'Speakers', w: 46, h: 90, z: 30, slots: {}, ports: [{id:'audio', x:23, y:10}], + svg: `` + } }; let nodes = {}; @@ -66,27 +100,22 @@ let selectedWireId = null, selectedNodeId = null; let panX = 0, panY = 0, zoom = 1; - let isPanning = false, panStart = { x: 0, y: 0 }; + let isPanning = false, panStart = { x: 0, y: 0 }, isSystemBooted = false; - /* --- Setup Toolbox --- */ + /* --- Toolbox & Base Init --- */ function initToolbox() { if(!toolboxGrid) return; let html = ''; Object.keys(PC_PARTS).forEach(partKey => { - html += ` -
+ html += `
${PC_PARTS[partKey].svg} -
${partKey}
-
- `; +
${partKey}
`; }); toolboxGrid.innerHTML = html; - document.querySelectorAll('.drag-item').forEach(item => { - item.addEventListener('dragstart', (e) => { e.dataTransfer.setData('spawnType', item.dataset.spawn); }); - }); + document.querySelectorAll('.drag-item').forEach(item => { item.addEventListener('dragstart', (e) => { e.dataTransfer.setData('spawnType', item.dataset.spawn); }); }); } - /* --- Camera Math --- */ + /* --- Viewport Math --- */ function updateViewport() { viewport.style.transform = `translate(${panX}px, ${panY}px) scale(${zoom})`; workspace.style.backgroundSize = `${32 * zoom}px ${32 * zoom}px`; @@ -94,63 +123,58 @@ } function zoomWorkspace(factor, mouseX, mouseY) { const newZoom = Math.min(Math.max(0.1, zoom * factor), 2); - panX = mouseX - (mouseX - panX) * (newZoom / zoom); - panY = mouseY - (mouseY - panY) * (newZoom / zoom); + panX = mouseX - (mouseX - panX) * (newZoom / zoom); panY = mouseY - (mouseY - panY) * (newZoom / zoom); zoom = newZoom; updateViewport(); } function getPortCoords(nodeId, portDataAttr) { - const node = nodes[nodeId]; - if (!node || !node.el) return {x:0, y:0}; - const portEl = node.el.querySelector(`[data-port="${portDataAttr}"]`); - if (!portEl) return {x:0, y:0}; - const wsRect = workspace.getBoundingClientRect(); - const portRect = portEl.getBoundingClientRect(); - return { - x: (portRect.left - wsRect.left - panX + portRect.width / 2) / zoom, - y: (portRect.top - wsRect.top - panY + portRect.height / 2) / zoom - }; - } - function drawBezier(x1, y1, x2, y2) { - const cpDist = Math.abs(x2 - x1) * 0.6 + 20; - return `M ${x1} ${y1} C ${x1 + cpDist} ${y1}, ${x2 - cpDist} ${y2}, ${x2} ${y2}`; + const node = nodes[nodeId]; if (!node || !node.el) return {x:0, y:0}; + const portEl = node.el.querySelector(`[data-port="${portDataAttr}"]`); if (!portEl) return {x:0, y:0}; + const wsRect = workspace.getBoundingClientRect(); const portRect = portEl.getBoundingClientRect(); + return { x: (portRect.left - wsRect.left - panX + portRect.width / 2) / zoom, y: (portRect.top - wsRect.top - panY + portRect.height / 2) / zoom }; } + function drawBezier(x1, y1, x2, y2) { const cpDist = Math.abs(x2 - x1) * 0.6 + 20; return `M ${x1} ${y1} C ${x1 + cpDist} ${y1}, ${x2 - cpDist} ${y2}, ${x2} ${y2}`; } /* --- Rendering --- */ function renderWires() { let svgHTML = ''; connections.forEach(conn => { - const from = getPortCoords(conn.fromNode, conn.fromPort); - const to = getPortCoords(conn.toNode, conn.toPort); - const isSelected = conn.id === selectedWireId; - svgHTML += ``; + const from = getPortCoords(conn.fromNode, conn.fromPort); const to = getPortCoords(conn.toNode, conn.toPort); + svgHTML += ``; }); - if (wiringStart && tempWirePath) { - svgHTML += ``; - } + if (wiringStart && tempWirePath) svgHTML += ``; wireLayer.innerHTML = svgHTML; } + function updateNodePositions() { Object.values(nodes).forEach(n => { if (n.el) { n.el.style.left = `${n.x}px`; n.el.style.top = `${n.y}px`; } }); renderWires(); } + function clearSelection() { selectedWireId = null; selectedNodeId = null; document.querySelectorAll('.pb-node.selected').forEach(el => el.classList.remove('selected')); renderWires(); } - function updateNodePositions() { - Object.values(nodes).forEach(n => { - if (n.el) { n.el.style.left = `${n.x}px`; n.el.style.top = `${n.y}px`; } - }); - renderWires(); + /* --- Node Logic --- */ + function createNodeElement(node) { + const el = document.createElement('div'); el.className = `pb-node`; el.dataset.id = node.id; + el.style.left = `${node.x}px`; el.style.top = `${node.y}px`; + el.style.width = `${PC_PARTS[node.type].w}px`; el.style.height = `${PC_PARTS[node.type].h}px`; el.style.zIndex = PC_PARTS[node.type].z; + let innerHTML = `${PC_PARTS[node.type].svg}`; + PC_PARTS[node.type].ports.forEach(p => { innerHTML += `
`; }); + el.innerHTML = innerHTML; viewport.appendChild(el); node.el = el; return el; } - function clearSelection() { - selectedWireId = null; selectedNodeId = null; - document.querySelectorAll('.pb-node.selected').forEach(el => el.classList.remove('selected')); - renderWires(); + function spawnNode(type, dropX = null, dropY = null) { + const id = `node_${nextNodeId++}`; + const x = dropX !== null ? dropX : 300 + Math.random()*40; const y = dropY !== null ? dropY : 150 + Math.random()*40; + const node = { id, type, x, y, snappedTo: null, el: null }; + if (PC_PARTS[type].slots) { node.slots = { ...PC_PARTS[type].slots }; for(let k in node.slots) { node.slots[k] = null; } } + nodes[id] = node; createNodeElement(node); evaluateBuild(); return id; } - /* --- Seven-Segment Diagnostics Engine --- */ + function moveNodeRecursive(nodeId, dx, dy) { + const n = nodes[nodeId]; if(!n) return; n.x += dx; n.y += dy; + if(n.slots) { Object.keys(n.slots).forEach(k => { if(typeof n.slots[k] === 'string') moveNodeRecursive(n.slots[k], dx, dy); }); } + } + + /* --- SYSTEM DIAGNOSTICS & VARIABLE BOOT SPEED --- */ function evaluateBuild() { if(!specsContainer) return; - - let hasCase = false, hasMB = false, hasCPU = false, hasCooler = false, hasRAM = false, hasPSU = false; - let hasStorage = false, hasGPU = false; - let mbPwr = false, gpuPwr = false; - let usbCount = 0, dispConn = false, audConn = false; + let hasCase=false, hasMB=false, hasCPU=false, hasCooler=false, hasRAM=false, hasPSU=false, hasStorage=false, hasGPU=false; + let mbPwr=false, gpuPwr=false, storPwr=false, storData=false, dispConn=false, usbCount=0; let caseNode = Object.values(nodes).find(n => n.type === 'CASE'); let mbNode = Object.values(nodes).find(n => n.type === 'MB'); @@ -160,145 +184,92 @@ if (caseNode.slots['MB1']) hasMB = true; if (caseNode.slots['PSU1']) hasPSU = true; if (caseNode.slots['HDD1'] || caseNode.slots['HDD2'] || caseNode.slots['SATA_SSD1'] || caseNode.slots['SATA_SSD2']) hasStorage = true; - } else if (mbNode) { - hasMB = true; // Motherboard exists outside case - } + } else if (mbNode) { hasMB = true; } if (mbNode) { if (mbNode.slots['CPU1']) hasCPU = true; if (mbNode.slots['COOLER1']) hasCooler = true; if (mbNode.slots['RAM1'] || mbNode.slots['RAM2'] || mbNode.slots['RAM3'] || mbNode.slots['RAM4']) hasRAM = true; if (mbNode.slots['PCIE1'] || mbNode.slots['PCIE2']) hasGPU = true; - if (mbNode.slots['M2_1'] || mbNode.slots['M2_2']) hasStorage = true; + if (mbNode.slots['M2_1'] || mbNode.slots['M2_2']) { hasStorage = true; storPwr = true; storData = true; } } - // Check Cables connections.forEach(c => { - let n1 = nodes[c.fromNode], n2 = nodes[c.toNode]; - if(!n1 || !n2) return; - let types = [n1.type, n2.type]; + let n1 = nodes[c.fromNode], n2 = nodes[c.toNode]; if(!n1 || !n2) return; + let types = [n1.type, n2.type], ports = [c.fromPort, c.toPort]; if(types.includes('MB') && types.includes('PSU')) mbPwr = true; if(types.includes('GPU') && types.includes('PSU')) gpuPwr = true; - if(types.includes('MB') && ['KEYBOARD','MOUSE','WEBCAM','MIC','PRINTER'].some(t => types.includes(t))) usbCount++; - if(types.includes('MB') && types.includes('SPEAKER')) audConn = true; + if(types.includes('PSU') && (types.includes('HDD') || types.includes('SATA_SSD')) && ports.includes('pwr')) storPwr = true; + if(types.includes('MB') && (types.includes('HDD') || types.includes('SATA_SSD')) && ports.includes('data')) storData = true; + if(types.includes('MB') && ['KEYBOARD','MOUSE'].some(t => types.includes(t))) usbCount++; if((types.includes('MB') || types.includes('GPU')) && types.includes('MONITOR')) dispConn = true; }); const isBootable = (hasMB && hasCPU && hasCooler && hasRAM && hasPSU && hasStorage && mbPwr && (hasGPU ? gpuPwr : true) && dispConn); + + // Determine the Boot Speed based on the connected drive + let bootSpeed = 8000; // Default slow HDD + let activeDrive = 'HDD'; + if (mbNode && (mbNode.slots['M2_1'] || mbNode.slots['M2_2'])) { + activeDrive = 'M2_SSD'; + } else { + Object.values(nodes).forEach(n => { + if ((n.type === 'SATA_SSD' || n.type === 'HDD') && n.snappedTo) activeDrive = n.type; + }); + } + + if (activeDrive === 'M2_SSD') bootSpeed = 1500; + else if (activeDrive === 'SATA_SSD') bootSpeed = 3500; + + // Auto-Trigger the Boot Animation + if (isBootable && !isSystemBooted) { isSystemBooted = true; triggerBootSequence(bootSpeed); } + else if (!isBootable) { isSystemBooted = false; resetMonitor(); } specsContainer.innerHTML = ` -
Core System
+
CORE SYSTEM
CHASSIS${hasCase ? 'OK' : 'ERR'}
MOTHERBOARD${hasMB ? 'OK' : 'ERR'}
CPU${hasCPU ? 'OK' : 'ERR'}
COOLING${hasCooler ? 'OK' : 'ERR'}
MEMORY${hasRAM ? 'OK' : 'ERR'}
POWER SPLY${hasPSU ? 'OK' : 'ERR'}
-
Connections
+
CONNECTIONS
MB POWER${mbPwr ? 'OK' : 'ERR'}
-
STORAGE${hasStorage ? 'OK' : 'ERR'}
+
STORAGE${(hasStorage && storPwr && storData) ? 'OK' : 'ERR'}
GPU POWER${!hasGPU ? 'N/A' : (gpuPwr ? 'OK' : 'ERR')}
DISPLAY${dispConn ? 'OK' : 'ERR'}
USB DEVS${usbCount}

-
- ${isBootable ? 'BOOTING...' : 'HALTED'} -
+
${isBootable ? 'BOOTING...' : 'HALTED'}
`; } - /* --- Node Creation & Snapping --- */ - function createNodeElement(node) { - const el = document.createElement('div'); - el.className = `pb-node`; el.dataset.id = node.id; - el.style.left = `${node.x}px`; el.style.top = `${node.y}px`; - el.style.width = `${PC_PARTS[node.type].w}px`; el.style.height = `${PC_PARTS[node.type].h}px`; - el.style.zIndex = PC_PARTS[node.type].z; - - let innerHTML = `${PC_PARTS[node.type].svg}`; - PC_PARTS[node.type].ports.forEach(p => { - innerHTML += `
`; - }); + function triggerBootSequence(duration) { + const monitor = Object.values(nodes).find(n => n.type === 'MONITOR'); if (!monitor) return; + const bootContent = monitor.el.querySelector('#boot-content'); + const durSeconds = (duration / 1000).toFixed(1); - // Debug Labels for bare parts - if(node.type !== 'CASE' && node.type !== 'MB') { - innerHTML += `
${node.type}
`; - } - - el.innerHTML = innerHTML; - viewport.appendChild(el); - node.el = el; - return el; - } - - function spawnNode(type, dropX = null, dropY = null) { - const id = `node_${nextNodeId++}`; - const x = dropX !== null ? dropX : 300 + Math.random()*40; - const y = dropY !== null ? dropY : 150 + Math.random()*40; - - const node = { id, type, x, y, snappedTo: null, el: null }; - if (PC_PARTS[type].slots) node.slots = { ...PC_PARTS[type].slots }; // Copy slots schema, values will be filled with IDs + bootContent.innerHTML = `Starting Windows`; - // Reset slot values to null - if(node.slots) { - for(let k in node.slots) { node.slots[k] = null; } - } - - nodes[id] = node; - createNodeElement(node); - evaluateBuild(); + setTimeout(() => { + bootContent.innerHTML = ``; + }, duration + 300); // Small buffer to let the bar finish } - // Recursive movement to handle nested snaps (MB inside CASE inside ...) - function moveNodeRecursive(nodeId, dx, dy) { - const n = nodes[nodeId]; - if(!n) return; - n.x += dx; n.y += dy; - if(n.slots) { - Object.keys(n.slots).forEach(k => { - if(typeof n.slots[k] === 'string') moveNodeRecursive(n.slots[k], dx, dy); - }); - } - } + function resetMonitor() { const monitor = Object.values(nodes).find(n => n.type === 'MONITOR'); if (monitor) monitor.el.querySelector('#boot-content').innerHTML = ''; } - /* --- Inspect Mode --- */ - let inspectZoom = 1, inspectRotX = 0, inspectRotY = 0; - workspace.addEventListener('dblclick', (e) => { - const nodeEl = e.target.closest('.pb-node'); - if (nodeEl) { - const node = nodes[nodeEl.dataset.id]; - document.getElementById('inspectModal').classList.add('active'); - document.getElementById('inspectObject').innerHTML = `${PC_PARTS[node.type].svg}`; - document.getElementById('inspectName').innerText = PC_PARTS[node.type].name; - inspectZoom = 1.5; inspectRotX = 0; inspectRotY = 0; updateInspectTransform(); clearSelection(); - } - }); - document.getElementById('inspectStage')?.addEventListener('mousemove', (e) => { - const rect = e.currentTarget.getBoundingClientRect(); - inspectRotY = (e.clientX - rect.left - rect.width/2) / 5; - inspectRotX = -(e.clientY - rect.top - rect.height/2) / 5; - updateInspectTransform(); - }); - document.getElementById('inspectStage')?.addEventListener('wheel', (e) => { - e.preventDefault(); inspectZoom += e.deltaY < 0 ? 0.1 : -0.1; - inspectZoom = Math.max(0.5, Math.min(inspectZoom, 4)); updateInspectTransform(); - }); - function updateInspectTransform() { const obj = document.getElementById('inspectObject'); if(obj) obj.style.transform = `scale(${inspectZoom}) rotateX(${inspectRotX}deg) rotateY(${inspectRotY}deg)`; } - document.getElementById('inspectClose')?.addEventListener('click', () => { document.getElementById('inspectModal').classList.remove('active'); }); - /* --- Interaction --- */ + /* --- INTERACTION (Drag, Drop, Snap, Wire) --- */ document.getElementById("btnZoomIn")?.addEventListener('click', () => { const r = workspace.getBoundingClientRect(); zoomWorkspace(1.2, r.width/2, r.height/2); }); document.getElementById("btnZoomOut")?.addEventListener('click', () => { const r = workspace.getBoundingClientRect(); zoomWorkspace(1/1.2, r.width/2, r.height/2); }); document.getElementById("btnZoomReset")?.addEventListener('click', () => { panX = 0; panY = 0; zoom = 1; updateViewport(); }); - workspace.addEventListener('wheel', (e) => { e.preventDefault(); const wsRect = workspace.getBoundingClientRect(); zoomWorkspace(e.deltaY < 0 ? 1.1 : (1/1.1), e.clientX - wsRect.left, e.clientY - wsRect.top); }); workspace.addEventListener('mousedown', (e) => { const port = e.target.closest('.pb-port'); if (port) { - const nodeEl = port.closest('.pb-node'); - const portId = port.dataset.port; + const nodeEl = port.closest('.pb-node'); const portId = port.dataset.port; const existingIdx = connections.findIndex(c => (c.toNode === nodeEl.dataset.id && c.toPort === portId) || (c.fromNode === nodeEl.dataset.id && c.fromPort === portId)); if (existingIdx !== -1) { connections.splice(existingIdx, 1); evaluateBuild(); renderWires(); return; } const coords = getPortCoords(nodeEl.dataset.id, portId); @@ -314,18 +285,14 @@ clearSelection(); selectedNodeId = nodeEl.dataset.id; nodeEl.classList.add('selected'); isDraggingNode = nodeEl.dataset.id; const rect = nodeEl.getBoundingClientRect(); dragOffset = { x: (e.clientX - rect.left) / zoom, y: (e.clientY - rect.top) / zoom }; - // Unsnap from parent when picked up const node = nodes[isDraggingNode]; if (node.snappedTo) { const parent = nodes[node.snappedTo.id]; if (parent && parent.slots[node.snappedTo.key] === node.id) parent.slots[node.snappedTo.key] = null; - node.snappedTo = null; - node.el.style.zIndex = PC_PARTS[node.type].z; // Reset Z - evaluateBuild(); + node.snappedTo = null; node.el.style.zIndex = PC_PARTS[node.type].z; evaluateBuild(); } return; } - clearSelection(); isPanning = true; panStart = { x: e.clientX - panX, y: e.clientY - panY }; }); @@ -334,20 +301,16 @@ if (isPanning) { panX = e.clientX - panStart.x; panY = e.clientY - panStart.y; updateViewport(); return; } if (isDraggingNode) { const node = nodes[isDraggingNode]; - let newX = (e.clientX - wsRect.left - panX) / zoom - dragOffset.x; - let newY = (e.clientY - wsRect.top - panY) / zoom - dragOffset.y; - moveNodeRecursive(node.id, newX - node.x, newY - node.y); - updateNodePositions(); + let newX = (e.clientX - wsRect.left - panX) / zoom - dragOffset.x; let newY = (e.clientY - wsRect.top - panY) / zoom - dragOffset.y; + moveNodeRecursive(node.id, newX - node.x, newY - node.y); updateNodePositions(); } if (wiringStart) { tempWirePath = { x: (e.clientX - wsRect.left - panX) / zoom, y: (e.clientY - wsRect.top - panY) / zoom }; renderWires(); } }); window.addEventListener('mouseup', (e) => { if (isDraggingNode) { - const node = nodes[isDraggingNode]; - let snapped = false; + const node = nodes[isDraggingNode]; let snapped = false; - // Check all other nodes for compatible slots Object.values(nodes).forEach(target => { if (target.slots && !snapped && target.id !== node.id) { for(let slotKey in target.slots) { @@ -358,7 +321,7 @@ moveNodeRecursive(node.id, tX - node.x, tY - node.y); node.snappedTo = { id: target.id, key: slotKey }; target.slots[slotKey] = node.id; - node.el.style.zIndex = PC_PARTS[target.type].z + 5; // Layer above parent + node.el.style.zIndex = PC_PARTS[target.type].z + 5; snapped = true; break; } } @@ -371,8 +334,7 @@ if (wiringStart) { const port = e.target.closest('.pb-port'); if (port) { - const targetNodeId = port.closest('.pb-node').dataset.id; - const targetPortId = port.dataset.port; + const targetNodeId = port.closest('.pb-node').dataset.id; const targetPortId = port.dataset.port; if (targetNodeId !== wiringStart.node) { connections.push({ id: `conn_${nextWireId++}`, fromNode: wiringStart.node, fromPort: wiringStart.port, toNode: targetNodeId, toPort: targetPortId }); } } wiringStart = null; tempWirePath = null; evaluateBuild(); renderWires(); @@ -380,7 +342,8 @@ isPanning = false; }); - /* --- Deletion (Recursive) --- */ + + /* --- Deletion & Toolbox UI --- */ function deleteNodeRecursive(id) { const n = nodes[id]; if(!n) return; if(n.slots) { Object.keys(n.slots).forEach(k => { if(typeof n.slots[k] === 'string') deleteNodeRecursive(n.slots[k]); }); } @@ -390,28 +353,17 @@ } window.addEventListener('keydown', (e) => { - if ((e.key === 'Delete' || e.key === 'Backspace') && selectedNodeId) { - deleteNodeRecursive(selectedNodeId); clearSelection(); evaluateBuild(); renderWires(); - } - if ((e.key === 'Delete' || e.key === 'Backspace') && selectedWireId) { - connections = connections.filter(c => c.id !== selectedWireId); clearSelection(); evaluateBuild(); renderWires(); - } + if ((e.key === 'Delete' || e.key === 'Backspace') && selectedNodeId) { deleteNodeRecursive(selectedNodeId); clearSelection(); evaluateBuild(); renderWires(); } + if ((e.key === 'Delete' || e.key === 'Backspace') && selectedWireId) { connections = connections.filter(c => c.id !== selectedWireId); clearSelection(); evaluateBuild(); renderWires(); } }); workspace.addEventListener('dragover', (e) => { e.preventDefault(); }); workspace.addEventListener('drop', (e) => { - e.preventDefault(); - const type = e.dataTransfer.getData('spawnType'); - if (type) { - const r = workspace.getBoundingClientRect(); - spawnNode(type, (e.clientX - r.left - panX) / zoom - (PC_PARTS[type].w / 2), (e.clientY - r.top - panY) / zoom - (PC_PARTS[type].h / 2)); - } + e.preventDefault(); const type = e.dataTransfer.getData('spawnType'); + if (type) { const r = workspace.getBoundingClientRect(); spawnNode(type, (e.clientX - r.left - panX) / zoom - (PC_PARTS[type].w / 2), (e.clientY - r.top - panY) / zoom - (PC_PARTS[type].h / 2)); } }); - btnClearBoard?.addEventListener('click', () => { - viewport.querySelectorAll('.pb-node').forEach(el => el.remove()); - nodes = {}; connections = []; evaluateBuild(); renderWires(); - }); + btnClearBoard?.addEventListener('click', () => { viewport.querySelectorAll('.pb-node').forEach(el => el.remove()); nodes = {}; connections = []; evaluateBuild(); renderWires(); }); toolboxToggle?.addEventListener("click", () => { const c = pcPage?.classList.contains("toolboxCollapsed"); @@ -419,5 +371,29 @@ toolboxToggle?.setAttribute("aria-expanded", c ? "true" : "false"); }); + /* --- Auto-Assemble Engine --- */ + function autoAssemble(sT) { + btnClearBoard.click(); + const mId = spawnNode('MONITOR', 200, 100), kId = spawnNode('KEYBOARD', 230, 320), moId = spawnNode('MOUSE', 450, 330), spId = spawnNode('SPEAKER', 150, 300); + const cId = spawnNode('CASE', 550, 100), mbId = spawnNode('MB', 1250, 250), pId = spawnNode('PSU', 1250, 100), cpId = spawnNode('CPU', 1450, 100), coId = spawnNode('COOLER', 1450, 250), rId = spawnNode('RAM', 1600, 100), gId = spawnNode('GPU', 1450, 400), stId = spawnNode(sT, 1600, 250); + + const plan = [{c:mbId,p:cId,s:'MB1'},{c:pId,p:cId,s:'PSU1'},{c:cpId,p:mbId,s:'CPU1'},{c:coId,p:mbId,s:'COOLER1'},{c:rId,p:mbId,s:'RAM1'},{c:gId,p:mbId,s:'PCIE1'}]; + if(sT==='HDD') plan.push({c:stId,p:cId,s:'HDD1'}); if(sT==='SATA_SSD') plan.push({c:stId,p:cId,s:'SATA_SSD1'}); if(sT==='M2_SSD') plan.push({c:stId,p:mbId,s:'M2_1'}); + + plan.forEach(s => { const ch = nodes[s.c], p = nodes[s.p]; const sD = PC_PARTS[p.type].slots[s.s]; moveNodeRecursive(ch.id, (p.x + sD.x) - ch.x, (p.y + sD.y) - ch.y); ch.snappedTo = { id: p.id, key: s.s }; p.slots[s.s] = ch.id; ch.el.style.zIndex = PC_PARTS[p.type].z + 5; }); + + const conn = (n1, p1, n2, p2) => connections.push({ id: `conn_${nextWireId++}`, fromNode: n1, fromPort: p1, toNode: n2, toPort: p2 }); + conn(pId, 'out1', mbId, 'atx_pwr'); conn(pId, 'out2', gId, 'pwr_in'); + if (sT !== 'M2_SSD') { conn(pId, 'out3', stId, 'pwr'); conn(mbId, 'sata1', stId, 'data'); } + conn(gId, 'disp_out', mId, 'disp'); conn(mbId, 'usb1', kId, 'usb'); conn(mbId, 'usb2', moId, 'usb'); conn(mbId, 'audio', spId, 'audio'); + + updateNodePositions(); + evaluateBuild(); + } + + document.getElementById('btnAssembleHDD')?.addEventListener('click', () => autoAssemble('HDD')); + document.getElementById('btnAssembleSATA')?.addEventListener('click', () => autoAssemble('SATA_SSD')); + document.getElementById('btnAssembleM2')?.addEventListener('click', () => autoAssemble('M2_SSD')); + initToolbox(); evaluateBuild(); })(); \ No newline at end of file diff --git a/src/styles/global.css b/src/styles/global.css index 10a39a9..c096721 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -37,7 +37,7 @@ body { margin: 0; background: var(--bg); color: var(--text); font-family: var(-- .siteNav { position: sticky; top: 0; z-index: 50; height: var(--nav-h); background: rgba(0,0,0,.10); border-bottom: 1px solid var(--line); backdrop-filter: blur(8px); margin-bottom: 25px; } .navInner { height: 90px; max-width: 1400px; margin: 0 auto; padding: 0 20px; display: flex; align-items: center; justify-content: space-between; gap: 24px; } .brand { display: flex; align-items: center; gap: 12px; text-decoration: none; color: var(--text); } -.brandLogo { width: 2.5em; height: 2.5em; image-rendering: pixelated; } +.brandLogo { width: 75px; height: 75px; image-rendering: pixelated; } .brandName { letter-spacing: .12em; font-weight: 900; font-size: 18px; } .navLinks { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; } .navLinks a { color: var(--muted); text-decoration: none; font-weight: 800; letter-spacing: .12em; font-size: 16px; } @@ -89,7 +89,7 @@ body { margin: 0; background: var(--bg); color: var(--text); font-family: var(-- .switch input { display: none; } .slider { position: absolute; inset: 0; background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.14); border-radius: 999px; transition: .2s ease; } .slider::before { content: ""; position: absolute; width: 22px; height: 22px; left: 3px; top: 2px; background: rgba(255,255,255,.92); border-radius: 999px; transition: .2s ease; pointer-events: none; } -.switch input:checked + .slider { background: rgba(40,240,122,.25); border-color: rgba(40,240,122,.30); } +.switch input:checked + .slider { background: rgba(40,240,122,.30); border-color: rgba(40,240,122,.30); } .switch input:checked + .slider::before { transform: translateX(28px); } /* --- HEXADECIMAL --- */ @@ -225,4 +225,148 @@ body { margin: 0; background: var(--bg); color: var(--text); font-family: var(-- font-weight: 400; line-height: 150%; margin: 0 0 2em 2em; +} + +.btnRandomRunning { + background: rgba(40,240,122,.18) !important; + border-color: rgba(40,240,122,.35) !important; + color: rgba(232,232,238,.95) !important; /* Added this so the text pops like the reset button */ + animation: randomPulse 750ms ease-in-out infinite; +} + +@keyframes randomPulse { + 0% { box-shadow: 0 0 0 rgba(40,240,122,0); } + 50% { box-shadow: 0 0 22px rgba(40,240,122,.38); } /* Matched the .38 opacity peak */ + 100% { box-shadow: 0 0 0 rgba(40,240,122,0); } +} + +.btnReset { color: rgba(232,232,238,.95); } +.btnReset:hover { + background: rgba(255,80,80,.18); + border-color: rgba(255,80,80,.35); + animation: resetPulse 750ms ease-in-out infinite; +} + +@keyframes resetPulse { + 0% { box-shadow: 0 0 0 rgba(255,80,80,0); } + 50% { box-shadow: 0 0 22px rgba(255,80,80,.38); } + 100% { box-shadow: 0 0 0 rgba(255,80,80,0); } +} + +/* --- ACCORDION ANIMATIONS FOR ALL TOOLBOXES --- */ +.panelCol .cardTitle, +.pb-toolbox .cardTitle, +.lg-toolbox .cardTitle { + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + user-select: none; +} + +.panelCol .cardTitle::after, +.pb-toolbox .cardTitle::after, +.lg-toolbox .cardTitle::after { + content: 'â–¼'; + font-size: 0.7em; + opacity: 0.6; + transition: transform 0.25s ease; +} + +.panelCol .card.collapsed .cardTitle::after, +.pb-toolbox .card.collapsed .cardTitle::after, +.lg-toolbox .card.collapsed .cardTitle::after { + transform: rotate(90deg); +} + +.panelCol .cardBody, +.pb-toolbox .cardBody, +.lg-toolbox .cardBody { + display: grid; + grid-template-rows: 1fr; + transition: grid-template-rows 350ms cubic-bezier(0.2, 0.9, 0.2, 1); + margin-top: 12px; +} + +.panelCol .card.collapsed .cardBody, +.pb-toolbox .card.collapsed .cardBody, +.lg-toolbox .card.collapsed .cardBody { + grid-template-rows: 0fr; + margin-top: 0; +} + +.panelCol .cardBodyInner, +.pb-toolbox .cardBodyInner, +.lg-toolbox .cardBodyInner { + overflow: hidden; + opacity: 1; + transition: opacity 250ms ease 100ms, visibility 0s 0s; +} + +.panelCol .card.collapsed .cardBodyInner, +.pb-toolbox .card.collapsed .cardBodyInner, +.lg-toolbox .card.collapsed .cardBodyInner { + opacity: 0; + visibility: hidden; + transition: opacity 200ms ease 0s, visibility 0s 200ms; +} + +/* --- BASE STATE: Lock the scale so it never stretches --- */ +.btnRandom, #btnRandom, +.btnReset, #btnReset { + transform: scale(1) !important; + transition: transform 0.1s ease-out, border-color 0.2s ease-out, color 0.2s ease-out !important; +} + +/* --- THE PULSE ANIMATIONS --- */ +@keyframes neonPulseGreen { + 0%, 100% { + background-color: rgba(40, 240, 122, 0.05); + box-shadow: inset 0 0 10px rgba(40, 240, 122, 0.4), + 0 0 5px rgba(40, 240, 122, 0.2); + } + 50% { + background-color: rgba(40, 240, 122, 0.15); + box-shadow: inset 0 0 22px rgba(40, 240, 122, 0.8), + 0 0 12px rgba(40, 240, 122, 0.5); + } +} + +@keyframes neonPulseRed { + 0%, 100% { + background-color: rgba(255, 85, 85, 0.05); + box-shadow: inset 0 0 10px rgba(255, 85, 85, 0.4), + 0 0 5px rgba(255, 85, 85, 0.2); + } + 50% { + background-color: rgba(255, 85, 85, 0.15); + box-shadow: inset 0 0 22px rgba(255, 85, 85, 0.8), + 0 0 12px rgba(255, 85, 85, 0.5); + } +} + +/* --- HOVER STATES: Trigger the infinite pulse --- */ +.btnRandom:hover, .btnRandom:focus, .btnRandom.active, +#btnRandom:hover, #btnRandom:focus, #btnRandom.active { + border-color: #28f07a !important; + color: #28f07a !important; + /* 1.5s cycle, infinite loop, smooth easing */ + animation: neonPulseGreen 1.5s infinite ease-in-out !important; +} + +.btnReset:hover, .btnReset:focus, .btnReset.active, +#btnReset:hover, #btnReset:focus, #btnReset.active { + border-color: #ff5555 !important; + color: #ff5555 !important; + /* 1.5s cycle, infinite loop, smooth easing */ + animation: neonPulseRed 1.5s infinite ease-in-out !important; +} + +/* --- CLICK STATE: Interrupt the pulse and shrink --- */ +.btnRandom:active, #btnRandom:active, +.btnReset:active, #btnReset:active { + transform: scale(0.96) !important; + animation: none !important; /* Immediately kill the pulse while clicked */ + background-color: transparent !important; + box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.8) !important; } \ No newline at end of file diff --git a/src/styles/logic-gates.css b/src/styles/logic-gates.css index fdba724..9707020 100644 --- a/src/styles/logic-gates.css +++ b/src/styles/logic-gates.css @@ -68,32 +68,65 @@ body:has(#logicPage) .pageWrap { } .lg-zoom-btn:hover { background: rgba(255,255,255,0.1); border-color: #28f07a; color: #28f07a; } +/* Update the SVG layer to sit ON TOP of nodes */ +/* Move the wire layer behind the nodes */ .lg-svg-layer { - position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1; + position: absolute; + inset: 0; + width: 100%; + height: 100%; + z-index: 1; /* Lower than nodes */ + pointer-events: none; } /* Wires */ +/* Wires - allow them to be clickable even behind the 'hit area' of ports */ .lg-wire { - stroke: rgba(255,255,255,0.25); stroke-width: 6; fill: none; stroke-linecap: round; - transition: stroke 0.1s ease, filter 0.1s ease, stroke-width 0.1s ease; - pointer-events: stroke; cursor: pointer; + stroke: #ffffff40; + stroke-width: 6; + fill: none; + stroke-linecap: round; + transition: stroke .1s ease,filter .1s ease,stroke-width .1s ease; + pointer-events: stroke; + cursor: pointer; + z-index: 1; +} +.lg-wire:hover { + stroke: #fff9; + stroke-width: 10 +} +.lg-wire.active { + stroke: #28f07a; + filter: drop-shadow(0 0 6px rgba(40,240,122,.6)) +} +.lg-wire.active:hover { + stroke: #5dff9e } -.lg-wire:hover { stroke: rgba(255,255,255,0.6); stroke-width: 10; } -.lg-wire.active { stroke: #28f07a; filter: drop-shadow(0 0 6px rgba(40,240,122,0.6)); } -.lg-wire.active:hover { stroke: #5dff9e; } .lg-wire.selected { - stroke: #ff5555 !important; stroke-width: 8 !important; stroke-dasharray: 8 8; - filter: drop-shadow(0 0 8px rgba(255,85,85,0.8)) !important; animation: wireDash 1s linear infinite; + stroke: #f55!important; + stroke-width: 8!important; + stroke-dasharray: 8 8; + filter: drop-shadow(0 0 8px rgba(255,85,85,.8))!important; + animation: wireDash 1s linear infinite +} +@keyframes wireDash { + to { + stroke-dashoffset: -16 + } +} +.lg-wire-temp { + stroke: #fff6; + stroke-dasharray: 8 8; + pointer-events: none } -@keyframes wireDash { to { stroke-dashoffset: -16; } } -.lg-wire-temp { stroke: rgba(255,255,255,0.4); stroke-dasharray: 8 8; pointer-events: none; } /* Nodes */ +/* Nodes - move them in front of wires */ .lg-node { - position: absolute; background: transparent; border: none; border-radius: 0; padding: 4px; - display: flex; flex-direction: column; align-items: center; cursor: grab; - z-index: 10; user-select: none; transition: filter 0.2s; - pointer-events: auto; /* Re-enables interaction inside the viewport */ + position: absolute; + z-index: 10; /* Higher than wires */ + pointer-events: auto; + user-select: none; } .lg-node:active { cursor: grabbing; z-index: 20; } .lg-node.selected { filter: drop-shadow(0 0 10px rgba(255,85,85,0.8)); } @@ -108,13 +141,24 @@ body:has(#logicPage) .pageWrap { .lg-line-svg { width: 30px; height: 50px; display: block; } /* Connection Ports */ +/* Update ports to sit even higher so they stay clickable */ +/* Ports - Ensure the dots are the top-most layer */ .lg-port { - width: 16px; height: 16px; background: #a9acb8; border-radius: 50%; cursor: crosshair; - border: 3px solid var(--bg); box-shadow: 0 0 0 1px rgba(255,255,255,0.2); transition: all 0.2s; - position: absolute; z-index: 5; transform: translate(-50%, -50%); + width: 12px; /* Balanced size */ + height: 12px; + background: #a9acb8; + border: 2px solid #1f2027; /* Dark border helps it 'pop' over glowing wires */ + border-radius: 50%; + position: absolute; + z-index: 100; /* Absolute top */ + transform: translate(-50%, -50%); + cursor: crosshair; } .lg-port:hover { transform: translate(-50%, -50%) scale(1.3); background: #fff; } -.lg-port.active { background: #28f07a; box-shadow: 0 0 12px rgba(40,240,122,0.8); border-color: #1f2027; } +.lg-port.active { + background: #28f07a; + box-shadow: 0 0 8px rgba(40,240,122,.45); +} /* === FLOATING TOOLBOX === */ .toolboxToggle { @@ -162,4 +206,15 @@ body:has(#logicPage) .pageWrap { .tt-table { width: 100%; border-collapse: collapse; text-align: center; font-family: var(--num-font); font-size: 14px; color: #e8e8ee; } .tt-table th { position: sticky; top: 0; background: rgba(31,32,39,0.95); padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.15); color: var(--muted); font-family: var(--bit-font); font-weight: normal; } .tt-table td { padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.05); } -.tt-table .tt-on { color: #28f07a; text-shadow: 0 0 8px rgba(40,240,122,0.5); } \ No newline at end of file +.tt-table .tt-on { color: #28f07a; text-shadow: 0 0 8px rgba(40,240,122,0.5); } + + +/* Ensure the active-sim class actually changes the color */ +.active-sim .slider { + background-color: rgba(40,240,122,.25) !important; /* Bright Green */ + box-shadow: 0 0 15px rgba(40, 240, 122, 0.5); +} + +.active-sim .slider::before { + transform: translateX(28px) !important; +} \ No newline at end of file diff --git a/src/styles/number-simulators.css b/src/styles/number-simulators.css index 68dc7d5..7e8b781 100644 --- a/src/styles/number-simulators.css +++ b/src/styles/number-simulators.css @@ -63,7 +63,38 @@ .hexColWeight { font-family: var(--bit-font); font-size: 40px; color: rgba(232,232,238,.6); margin-top: 14px; } /* --- TOOLBOX COMPONENTS FOR NUMBERS --- */ -.panelCol { position: fixed; top: var(--toolbox-top); right: 22px; width: var(--toolbox-w); z-index: 80; display: flex; flex-direction: column; gap: 16px; transform: translateX(0); opacity: 1; transition: transform 420ms cubic-bezier(.2,.9,.2,1), opacity 220ms ease; } +.panelCol { + /* Your original layout and animations */ + position: fixed; + top: var(--toolbox-top); + right: 22px; + width: var(--toolbox-w); + z-index: 80; + display: flex; + flex-direction: column; + gap: 16px; + transform: translateX(0); + opacity: 1; + transition: transform 420ms cubic-bezier(.2,.9,.2,1), opacity 220ms ease; + + /* THE FIX: Push the bottom edge higher up the screen */ + /* If your footer is ~140px tall, 170px gives you a perfect 30px safe gap */ + bottom: 110px; + + /* Let the inside scroll smoothly */ + overflow-y: auto; + scrollbar-width: none; +} + +/* Hide the scrollbar in Chrome/Safari */ +.panelCol::-webkit-scrollbar { + display: none; +} + +.panelCol .card { + flex-shrink: 0; +} + .binaryPage.toolboxCollapsed .panelCol { transform: translateX(calc(var(--toolbox-w) + 32px)); opacity: 0; pointer-events: none; } .toggleRow { display: flex; align-items: center; justify-content: space-between; gap: 12px; } diff --git a/src/styles/pc-builder.css b/src/styles/pc-builder.css index df4e5ff..814a0db 100644 --- a/src/styles/pc-builder.css +++ b/src/styles/pc-builder.css @@ -39,13 +39,15 @@ body:has(#pcPage) .pageWrap { .pb-zoom-btn:hover { background: rgba(255,255,255,0.1); border-color: #55aaff; color: #55aaff; } /* Wires sit at the VERY FRONT so they are never hidden in the case */ -.pb-svg-layer { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 100; pointer-events: none; } +/* Wire layer - sits above case but below ports */ +/* Wire layer - Physically on TOP of components */ +.pb-svg-layer { z-index: 100 !important; pointer-events: none; position: absolute; inset: 0; width: 100%; height: 100%; } /* Cables */ -.pb-wire { - stroke: rgba(255,255,255,0.25); stroke-width: 6; fill: none; stroke-linecap: round; - transition: stroke 0.1s ease, filter 0.1s ease, stroke-width 0.1s ease; pointer-events: stroke; cursor: pointer; -} +/* Cables - make sure they have a width and color */ +/* Cables - Styled for visibility */ +.pb-wire { stroke: #55aaff; stroke-width: 6; fill: none; pointer-events: stroke; } + .pb-wire:hover { stroke: rgba(255,255,255,0.6); stroke-width: 10; } .pb-wire.active { stroke: #55aaff; filter: drop-shadow(0 0 6px rgba(85,170,255,0.6)); } .pb-wire.active:hover { stroke: #88ccff; } @@ -58,6 +60,7 @@ body:has(#pcPage) .pageWrap { position: absolute; background: transparent; border: none; border-radius: 0; padding: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; cursor: grab; user-select: none; transition: filter 0.2s; pointer-events: auto; + z-index: 10; } .pb-node:active { cursor: grabbing; } .pb-node.selected { filter: drop-shadow(0 0 10px rgba(255,85,85,0.8)); } @@ -67,7 +70,7 @@ body:has(#pcPage) .pageWrap { .pb-port { width: 14px; height: 14px; background: #222; border-radius: 50%; cursor: crosshair; border: 2px solid #55aaff; box-shadow: 0 0 0 1px rgba(255,255,255,0.2); transition: all 0.2s; - position: absolute; z-index: 200; transform: translate(-50%, -50%); pointer-events: auto; + position: absolute; z-index: 200 !important; transform: translate(-50%, -50%); pointer-events: auto; } .pb-port:hover { transform: translate(-50%, -50%) scale(1.4); background: #fff; } .pb-port.active { background: #55aaff; box-shadow: 0 0 12px rgba(85,170,255,0.8); }