Broken code
All checks were successful
Pre-release on non-main branches / prerelease (push) Successful in 29s

Signed-off-by: Alexander Lyall <alex@adcm.uk>
This commit is contained in:
2026-03-01 17:52:36 +00:00
parent 09e0499ba3
commit bb8e0c1969
6 changed files with 132 additions and 155 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View File

@@ -2,36 +2,54 @@
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="About | Computing:Box">
<article style="max-width: 900px; margin: 0 auto;">
<div class="card" style="margin-bottom: 40px;">
<h1 class="brandName" style="font-size: 32px; color: var(--text); margin-bottom: 16px;">The New Computing:Box Experience</h1>
<p style="color: var(--muted); font-size: 18px; line-height: 1.6;">
The platform has been rebuilt from the ground up to provide a deeper, more professional simulation environment.
We've introduced several key simulators and interface upgrades to support the modern Computing classroom:
</p>
<div class="divider"></div>
<BaseLayout title="About - Computing:Box">
<article style="max-width: 1100px; margin: 0 auto; width: 100%;">
<div style="text-align: center; margin-bottom: 60px;">
<img src="/images/computing-box-logo.svg" alt="Computing:Box Logo" style="width: 250px; border-radius: 20px; box-shadow: 0 12px 40px rgba(0,0,0,0.5);" />
<h1 class="brandName" style="font-size: 42px; margin-top: 20px; color: var(--text);">The New Computing:Box Experience</h1>
</div>
<ul style="list-style: none; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div class="card" style="margin-bottom: 40px;">
<p style="color: var(--muted); font-size: 18px; line-height: 1.6; text-align: center;">
Rebuilt from the ground up to provide a deeper, more professional simulation environment for the UK Computing Curriculum.
</p>
<div class="divider"></div>
<ul style="list-style: none; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px;">
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> New User Interface (Responsive)</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Two's Complement Simulator</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Extended Binary Simulator (Custom bit sizes)</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Unified Binary Simulator (Unsigned & Two's Complement)</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Extended Binary Simulator (Custom sizes)</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Unified Binary Simulator</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Extended Hexadecimal Simulator</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Unified Hexadecimal Simulator (GCSE & A Level)</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Enhanced Gate Simulator (Truth Table Creator)</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Unified Hexadecimal Simulator</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Enhanced Gate Simulator (Truth Tables)</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Compound Gate Simulator</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Computer Components Simulator</li>
</ul>
</div>
<div class="card">
<h2 class="brandName" style="font-size: 24px; margin-bottom: 12px;">Educational Impact</h2>
<p style="color: var(--muted);">
Computing:Box is designed to help students learn computing concepts step by step, using clear visuals and simple interactions.
By changing values and seeing results straight away, students can build understanding at their own pace.
</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 40px; margin-bottom: 40px; align-items: center;">
<div class="card">
<h2 class="brandName" style="font-size: 24px; color: var(--accent);">From Bit:Box to Computing:Box</h2>
<p style="color: var(--muted); line-height: 1.6;">Computing:Box began as Bit:Box, a digital version of a physical Binary Light Box created by Mr Davis. The original device used lightbulbs to show how binary numbers are built.</p>
</div>
<img src="/images/BitBoxLogo.png" alt="Bit:Box Logo" style="width: 100%; border-radius: 16px; border: 1px solid var(--line);" />
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 40px; margin-bottom: 40px; align-items: center;">
<img src="/images/computingbox-concept-illustration.webp" alt="Simulator Illustration" style="width: 100%; border-radius: 16px; border: 1px solid var(--line);" />
<div class="card">
<h2 class="brandName" style="font-size: 24px; color: var(--accent);">Learning by Doing</h2>
<p style="color: var(--muted); line-height: 1.6;">Students can interact with simulations, change values, and see results instantly. This helps them understand how concepts work rather than memorising rules.</p>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 40px; align-items: center;">
<div class="card">
<h2 class="brandName" style="font-size: 24px; color: var(--accent);">Educational Impact</h2>
<p style="color: var(--muted); line-height: 1.6;">Designed for use in lessons, independent study, and revision, helping students build confidence as they explore how computers work.</p>
</div>
<img src="/images/Educational_Impact.webp" alt="Classroom Impact" style="width: 100%; border-radius: 16px; border: 1px solid var(--line);" />
</div>
</article>
</BaseLayout>

View File

@@ -4,13 +4,17 @@
(() => {
const workspace = document.getElementById("workspace");
const viewport = document.getElementById("viewport");
const wireLayer = document.getElementById("wireLayer");
const internalLayer = document.getElementById("wireLayerInternal");
const externalLayer = document.getElementById("wireLayerExternal");
const specsContainer = document.getElementById("buildSpecsContainer");
const toolboxGrid = document.getElementById("toolboxGrid");
const btnClearBoard = document.getElementById("btnClearBoard");
const toolboxToggle = document.getElementById("toolboxToggle");
const pcPage = document.getElementById("pcPage");
// Parts defined as "Outside the Chassis"
const EXTERNAL_TYPES = ['MONITOR', 'KEYBOARD', 'MOUSE', 'WEBCAM', 'SPEAKER', 'MIC', 'PRINTER'];
/* --- Extensive PC Component Library --- */
const PC_PARTS = {
'CASE': {
@@ -98,17 +102,11 @@
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 getPortCoords(nodeId, portId) {
const n = nodes[nodeId];
const pEl = n.el.querySelector(`[data-port="${portId}"]`);
const r = pEl.getBoundingClientRect(), w = workspace.getBoundingClientRect();
return { x: (r.left - w.left - panX) / zoom, y: (r.top - w.top - panY) / zoom };
}
function drawBezier(x1, y1, x2, y2) {
const cpDist = Math.abs(x2 - x1) * 0.6 + 20;
@@ -117,17 +115,22 @@
/* --- 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 += `<path class="pb-wire active ${isSelected ? 'selected' : ''}" d="${drawBezier(from.x, from.y, to.x, to.y)}" data-conn-id="${conn.id}" />`;
let intHtml = '', extHtml = '';
connections.forEach(c => {
const fromNode = nodes[c.fN];
const toNode = nodes[c.tN];
if (!fromNode || !toNode) return;
const p1 = getPortCoords(c.fN, c.fP);
const p2 = getPortCoords(c.tN, c.tP);
const path = `<path class="pb-wire active" d="M ${p1.x} ${p1.y} C ${p1.x+60} ${p1.y}, ${p2.x-60} ${p2.y}, ${p2.x} ${p2.y}" />`;
// Determine if cable is Internal (under panel) or External (on top)
(EXTERNAL_TYPES.includes(fromNode.type) || EXTERNAL_TYPES.includes(toNode.type)) ? extHtml += path : intHtml += path;
});
if (wiringStart && tempWirePath) {
svgHTML += `<path class="pb-wire pb-wire-temp" d="${drawBezier(wiringStart.x, wiringStart.y, tempWirePath.x, tempWirePath.y)}" />`;
}
wireLayer.innerHTML = svgHTML;
internalLayer.innerHTML = intHtml;
externalLayer.innerHTML = extHtml;
evaluateBuild();
}
function updateNodePositions() {
@@ -145,67 +148,10 @@
/* --- Seven-Segment Diagnostics Engine --- */
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 caseNode = Object.values(nodes).find(n => n.type === 'CASE');
let mbNode = Object.values(nodes).find(n => n.type === 'MB');
if (caseNode) {
hasCase = true;
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
}
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;
}
// Check Cables
connections.forEach(c => {
let n1 = nodes[c.fromNode], n2 = nodes[c.toNode];
if(!n1 || !n2) return;
let types = [n1.type, n2.type];
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('MB') || types.includes('GPU')) && types.includes('MONITOR')) dispConn = true;
});
const isBootable = (hasMB && hasCPU && hasCooler && hasRAM && hasPSU && hasStorage && mbPwr && (hasGPU ? gpuPwr : true) && dispConn);
specsContainer.innerHTML = `
<div class="diag-cat">Core System</div>
<div class="diag-row"><span>CHASSIS</span><span style="color: ${hasCase ? '#28f07a' : '#ff5555'}">${hasCase ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>MOTHERBOARD</span><span style="color: ${hasMB ? '#28f07a' : '#ff5555'}">${hasMB ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>CPU</span><span style="color: ${hasCPU ? '#28f07a' : '#ff5555'}">${hasCPU ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>COOLING</span><span style="color: ${hasCooler ? '#28f07a' : '#ff5555'}">${hasCooler ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>MEMORY</span><span style="color: ${hasRAM ? '#28f07a' : '#ff5555'}">${hasRAM ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>POWER SPLY</span><span style="color: ${hasPSU ? '#28f07a' : '#ff5555'}">${hasPSU ? 'OK' : 'ERR'}</span></div>
<div class="diag-cat">Connections</div>
<div class="diag-row"><span>MB POWER</span><span style="color: ${mbPwr ? '#28f07a' : '#ff5555'}">${mbPwr ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>STORAGE</span><span style="color: ${hasStorage ? '#28f07a' : '#ff5555'}">${hasStorage ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>GPU POWER</span><span style="color: ${!hasGPU ? '#888' : (gpuPwr ? '#28f07a' : '#ff5555')}">${!hasGPU ? 'N/A' : (gpuPwr ? 'OK' : 'ERR')}</span></div>
<div class="diag-row"><span>DISPLAY</span><span style="color: ${dispConn ? '#28f07a' : '#ff5555'}">${dispConn ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>USB DEVS</span><span style="color: #55aaff">${usbCount}</span></div>
<hr style="border-color: rgba(255,255,255,0.1); margin: 12px 0 8px 0;">
<div style="text-align:center; font-size: 28px; color: ${isBootable ? '#28f07a' : '#ff5555'}; font-family: var(--bit-font); letter-spacing: 2px;">
${isBootable ? 'BOOTING...' : 'HALTED'}
</div>
`;
const hasMB = Object.values(nodes).some(n => n.type === 'MB' && n.snappedTo);
const hasCPU = Object.values(nodes).some(n => n.type === 'CPU' && n.snappedTo);
const boot = hasMB && hasCPU && connections.some(c => nodes[c.fN].type === 'MONITOR' || nodes[c.tN].type === 'MONITOR');
workspace.classList.toggle('system-running', boot);
}
/* --- Node Creation & Snapping --- */
@@ -250,16 +196,12 @@
evaluateBuild();
}
// Recursive movement to handle nested snaps (MB inside CASE inside ...)
/* --- Movement & Snap Logic (Restored from your Verified Working Script) --- */
function moveNodeRecursive(nodeId, dx, dy) {
const n = nodes[nodeId];
if(!n) return;
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);
});
}
if(n.slots) { Object.keys(n.slots).forEach(k => { if(typeof n.slots[k] === 'string') moveNodeRecursive(n.slots[k], dx, dy); }); }
if(n.el) { n.el.style.left = n.x + 'px'; n.el.style.top = n.y + 'px'; }
}
/* --- Inspect Mode --- */

View File

@@ -23,58 +23,75 @@
--text: #e8e8ee;
--muted: #a9acb8;
--line: rgba(255,255,255,.10);
--ui-font: "Inter", system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
--num-font: "DSEG7Classic", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
--accent: #28f07a;
--ui-font: "Inter", system-ui, -apple-system, sans-serif;
--bit-font: "SevenSegment", monospace;
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--ui-font); display: flex; flex-direction: column; }
html, body { height: 100%; margin: 0; }
body { background: var(--bg); color: var(--text); font-family: var(--ui-font); display: flex; flex-direction: column; }
/* --- BASE LAYOUT --- */
.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); }
.navInner { height: 100%; max-width: 1400px; margin: 0 auto; padding: 0 20px; display: flex; align-items: center; justify-content: space-between; gap: 24px; }
/* --- SITE NAVIGATION (Fixed Height) --- */
.siteNav {
position: sticky;
top: 0;
z-index: 500;
height: var(--nav-h);
background: rgba(45, 44, 56, 0.95);
border-bottom: 1px solid var(--line);
backdrop-filter: blur(8px);
}
.navInner {
height: 100%;
max-width: 1400px;
margin: 0 auto;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
}
.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: 32px; height: 32px; }
.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; }
.navLinks a:hover, .navLinks a.active { color: #e8e8ee; }
.pageWrap { flex: 1; max-width: 1400px; margin: 0 auto; padding: 0 20px 40px; width: 100%; display: flex; flex-direction: column; }
.siteFooter { border-top: 1px solid var(--line); background: rgba(0,0,0,.08); }
.footerInner { max-width: 1400px; margin: 0 auto; padding: 18px 20px; color: var(--muted); font-size: 12px; letter-spacing: .08em; display: flex; flex-direction: column; gap: 6px; }
.navLinks { display: flex; gap: 24px; }
.navLinks a {
color: var(--muted);
text-decoration: none;
font-weight: 800;
font-size: 14px;
transition: color 0.2s;
}
/* --- SHARED UI COMPONENTS (Used by ALL Simulators) --- */
.divider { height: 1px; background: rgba(255,255,255,.08); margin: 16px 0 16px; }
/* Active Link Fix */
.navLinks a.active,
.navLinks a:hover {
color: #ffffff !important;
}
.bulb { width: 44px; height: 44px; color: rgba(255,255,255,.15); margin-bottom: 8px; flex-shrink: 0; transition: 0.2s ease; background: transparent; display: flex; align-items: center; justify-content: center; }
.bulb svg { width: 100%; height: 100%; display: block; }
.bulb.on { color: #ffd86b !important; filter: drop-shadow(0 0 14px rgba(255, 216, 107, 1)) !important; }
.bulb.on svg { fill: #ffd86b !important; }
/* --- LAYOUT --- */
.pageWrap { flex: 1; width: 100%; max-width: 1400px; margin: 0 auto; padding: 40px 20px; display: flex; flex-direction: column; }
.switch { position: relative; width: 56px; height: 28px; display: inline-block; }
.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::before { transform: translateX(28px); }
/* --- FOOTER (Centered Links at Top) --- */
.siteFooter { background: rgba(0,0,0,.08); border-top: 1px solid var(--line); padding: 40px 0; }
.footerInner {
max-width: 1400px;
margin: 0 auto;
padding: 0 20px;
color: var(--muted);
font-size: 13px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 12px;
}
.footerLegal { margin-bottom: 8px; }
.footerLegal a { color: var(--muted); text-decoration: underline; font-weight: 700; margin: 0 10px; }
.footerLegal a:hover { color: #fff; }
.card { background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.10); border-radius: 16px; padding: 16px; backdrop-filter: blur(10px); }
.cardTitle { font-family: var(--bit-font); font-weight: 900; letter-spacing: .14em; text-transform: uppercase; font-size: 18px; color: rgba(232,232,238,.9); margin-bottom: 12px; }
.hint { font-family: var(--bit-font); font-size: 13px; letter-spacing: .08em; text-transform: uppercase; color: rgba(232,232,238,.55); margin-top: 10px; line-height: 1.35; }
.btn { border: 1px solid rgba(255,255,255,.12); background: rgba(255,255,255,.06); color: rgba(232,232,238,.92); border-radius: 12px; padding: 10px 12px; font-family: var(--bit-font); font-size: 14px; letter-spacing: .12em; text-transform: uppercase; font-weight: 900; cursor: pointer; }
.btn:hover { border-color: rgba(255,255,255,.22); }
.btnAccent { background: rgba(40,240,122,.12); border-color: rgba(40,240,122,.22); }
.btnAccent:hover { border-color: rgba(40,240,122,.35); }
.btnHalf { width: calc(50% - 6px); }
.btnWide { width: 100%; }
.btnReset { color: rgba(232,232,238,.95); }
.btnReset:hover { background: rgba(255,80,80,.18); border-color: rgba(255,80,80,.35); }
.toolboxToggle { position: fixed; top: var(--toolbox-toggle-top, calc(var(--nav-h) + 16px)); right: 22px; z-index: 90; display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-radius: 12px; border: 1px solid rgba(255,255,255,.12); background: rgba(0,0,0,.15); backdrop-filter: blur(8px); color: rgba(232,232,238,.95); font-family: var(--bit-font); font-weight: 800; font-size: 16px; letter-spacing: .12em; text-transform: uppercase; cursor: pointer; }
.toolboxIcon { font-size: 20px; filter: drop-shadow(0 0 8px rgba(255,105,180,.35)); }
.toolboxToggle:hover { border-color: rgba(255,255,255,.22); }
/* Shared UI Components */
.card { background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.10); border-radius: 16px; padding: 24px; backdrop-filter: blur(10px); }
.divider { height: 1px; background: rgba(255,255,255,.08); margin: 16px 0 16px; }