Update to product roadmap

Signed-off-by: Alexander Lyall <alex@adcm.uk>
This commit is contained in:
2025-12-14 17:11:21 +00:00
parent 58202ca853
commit 460126dccc
2 changed files with 453 additions and 180 deletions

View File

@@ -1,50 +1,3 @@
# Astro Starter Kit: Basics
```sh
npm create astro@latest -- --template basics
```
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```text
/
├── public/
│ └── favicon.svg
├── src
│   ├── assets
│   │   └── astro.svg
│   ├── components
│   │   └── Welcome.astro
│   ├── layouts
│   │   └── Layout.astro
│   └── pages
│   └── index.astro
└── package.json
```
To learn more about the folder structure of an Astro project, refer to [our guide on project structure](https://docs.astro.build/en/basics/project-structure/).
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
# Computing:Box # Computing:Box
An evolution of Bit:Box & CS:Box to incorporate different elements of the UK Computing Curriculum An evolution of Bit:Box & CS:Box to incorporate different elements of the UK Computing Curriculum
@@ -68,9 +21,13 @@ An evolution of Bit:Box & CS:Box to incorporate different elements of the UK Com
- [X] XOR Gate Simulator - [X] XOR Gate Simulator
- [X] XNOR Gate Simulator - [X] XNOR Gate Simulator
### Wave 3 CS:Box Features (TBC) ### Wave 3 CS:Box Features (Spring 2026)
- [X] Two's Compliment Simulator
- [ ] Extended Binary Simulator (Custom bit sizes)
- [ ] Unified Binary Simulator (Unsigned & Two's Completment combined)
- [ ] Enhanced Gate Simulator (Truth Table Creator)
- [ ] Compound Gate Simulator - [ ] Compound Gate Simulator
- [ ] Two's Compliment Simulator - [ ] Computer Components Simulator
## Version 1.0 Release Date: 1<sup>st</sup> September 2025 ## Version 1.0 Release Date: 1<sup>st</sup> September 2025
## Version 2.0 Release Date (Goal): 1<sup>st</sup> February 2025 ## Version 2.0 Release Date (Goal): 1<sup>st</sup> February 2025

View File

@@ -1,17 +1,26 @@
--- ---
const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1]; /**
* src/pages/binary.astro
* Single-file binary simulator (Unsigned + Two's complement)
* Bit width: 4..64
*
* Requires:
* public/fonts/DSEG7Classic-Regular.woff
* public/fonts/DSEG7Classic-Regular.ttf
*/
--- ---
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Unsigned Binary | Computing:Box</title> <title>Binary | Computing:Box</title>
<style> <style>
:root{ :root{
--bg: #1f2027; --bg: #1f2027;
--panel: #22242d; --panel: #22242d;
--panel2:#262833;
--text: #e8e8ee; --text: #e8e8ee;
--muted: #a9acb8; --muted: #a9acb8;
--accent: #33ff7a; --accent: #33ff7a;
@@ -19,12 +28,11 @@ const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
--line: rgba(255,255,255,.12); --line: rgba(255,255,255,.12);
} }
/* DSEG7ClassicRegular font */
@font-face{ @font-face{
font-family: "DSEG7ClassicRegular"; font-family: "DSEG7ClassicRegular";
src: src:
url("/fonts/DSEG7Classic-Regular.ttf") format("truetype"), url("/fonts/DSEG7Classic-Regular.woff") format("woff"),
url("/fonts/DSEG7Classic-Regular.woff") format("woff"); url("/fonts/DSEG7Classic-Regular.ttf") format("truetype");
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
@@ -43,15 +51,15 @@ const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
padding: 32px 20px 60px; padding: 32px 20px 60px;
} }
.top{ /* Layout: readout left, settings panel right (like your screenshot) */
.layout{
display:grid; display:grid;
grid-template-columns: 1fr; grid-template-columns: 1fr 360px;
gap: 22px; gap: 22px;
align-items:start; align-items:start;
} }
.readout{ .readout{
background: transparent;
text-align:center; text-align:center;
padding: 10px 10px 0; padding: 10px 10px 0;
} }
@@ -65,7 +73,6 @@ const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
margin-top: 8px; margin-top: 8px;
} }
/* All numbers use DSEG */
.num{ .num{
font-family: "DSEG7ClassicRegular", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-family: "DSEG7ClassicRegular", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-weight: 400; font-weight: 400;
@@ -74,17 +81,18 @@ const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
} }
.value{ .value{
font-size: 64px; font-size: 70px;
line-height: 1.0; line-height: 1.0;
margin: 6px 0 16px; margin: 6px 0 16px;
} }
.binary{ .binary{
font-size: 44px; font-size: 54px;
letter-spacing: .12em; letter-spacing: .12em;
word-break: break-all;
} }
/* Buttons now UNDER the readout */ /* Buttons under readout (as requested) */
.controls{ .controls{
margin-top: 16px; margin-top: 16px;
display:flex; display:flex;
@@ -105,53 +113,42 @@ const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
} }
.btn:active{ transform: translateY(1px); } .btn:active{ transform: translateY(1px); }
.bits{ /* Settings panel cards */
margin-top: 34px; .panel{
padding-top: 26px; background: rgba(255,255,255,.04);
border-top: 1px solid var(--line); border: 1px solid rgba(255,255,255,.10);
display:grid; border-radius: 14px;
grid-template-columns: repeat(8, 1fr); padding: 16px 16px 14px;
gap: 18px; margin-bottom: 14px;
align-items:end;
text-align:center;
} }
.panelTitle{
.bit{ font-size: 12px;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--muted);
font-weight: 800;
margin-bottom: 10px;
}
.panelRow{
display:flex; display:flex;
flex-direction:column;
align-items:center; align-items:center;
gap: 10px; justify-content:space-between;
padding: 8px 4px; gap: 12px;
}
.hint{
color: var(--muted);
font-size: 12px;
margin-top: 10px;
line-height: 1.35;
} }
.bitVal{ /* Reusable toggle switch (for MODE and for each BIT) */
font-size: 34px;
color: var(--text);
opacity: .95;
}
/* Bulb */
.bulb{
width: 22px;
height: 22px;
border-radius: 50%;
background: rgba(255,255,255,.08);
border: 1px solid rgba(255,255,255,.12);
box-shadow: none;
margin-bottom: 6px;
}
.bulb.on{
background: #ffd86b;
border-color: rgba(255,216,107,.7);
box-shadow: 0 0 18px rgba(255,216,107,.6);
}
/* Light switch */
.switch{ .switch{
position: relative; position: relative;
width: 56px; width: 56px;
height: 34px; height: 34px;
display:inline-block; display:inline-block;
flex: 0 0 auto;
} }
.switch input{ .switch input{
opacity:0; opacity:0;
@@ -186,17 +183,118 @@ const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
background: var(--accent); background: var(--accent);
} }
/* Bit width controls */
.bwControls{
display:grid;
grid-template-columns: 44px 1fr 44px;
gap: 10px;
align-items:center;
margin-top: 8px;
}
.bwBtn{
width: 44px;
height: 44px;
border-radius: 12px;
border: 1px solid rgba(255,255,255,.14);
background: rgba(255,255,255,.06);
color: #fff;
font-weight: 900;
cursor: pointer;
}
.bwBtn:active{ transform: translateY(1px); }
.bwInput{
display:flex;
align-items:center;
justify-content:space-between;
gap: 12px;
background: rgba(255,255,255,.05);
border: 1px solid rgba(255,255,255,.12);
border-radius: 12px;
padding: 10px 12px;
}
.bwLabel{
color: var(--muted);
font-size: 12px;
letter-spacing: .14em;
text-transform: uppercase;
font-weight: 800;
}
.bwField{
width: 100px;
text-align:right;
background: transparent;
border: none;
outline: none;
color: var(--accent);
font-family: "DSEG7ClassicRegular", ui-monospace, monospace;
font-size: 26px;
padding: 0;
}
.bwField::-webkit-outer-spin-button,
.bwField::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.bwField[type=number] { -moz-appearance: textfield; }
/* Bits grid */
.bits{
margin-top: 34px;
padding-top: 26px;
border-top: 1px solid var(--line);
display:grid;
gap: 18px;
align-items:end;
text-align:center;
}
.bit{
display:flex;
flex-direction:column;
align-items:center;
gap: 10px;
padding: 8px 4px;
}
.bitVal{
font-size: 34px;
color: var(--text);
opacity: .95;
}
/* Bulb (must come back!) */
.bulb{
width: 22px;
height: 22px;
border-radius: 50%;
background: rgba(255,255,255,.08);
border: 1px solid rgba(255,255,255,.12);
box-shadow: none;
margin-bottom: 6px;
}
.bulb.on{
background: #ffd86b;
border-color: rgba(255,216,107,.7);
box-shadow: 0 0 18px rgba(255,216,107,.6);
}
/* Responsive */
@media (max-width: 1000px){
.layout{ grid-template-columns: 1fr; }
.value{ font-size: 60px; }
.binary{ font-size: 44px; }
}
@media (max-width: 900px){ @media (max-width: 900px){
.bits{ grid-template-columns: repeat(4, 1fr); }
.value{ font-size: 54px; } .value{ font-size: 54px; }
.binary{ font-size: 36px; } .binary{ font-size: 36px; }
.btn{ min-width: 140px; }
} }
</style> </style>
</head> </head>
<body> <body>
<main class="wrap"> <main class="wrap">
<section class="top"> <section class="layout">
<!-- LEFT: readout + buttons -->
<div>
<div class="readout"> <div class="readout">
<div class="label">Denary</div> <div class="label">Denary</div>
<div id="denaryNumber" class="value num">0</div> <div id="denaryNumber" class="value num">0</div>
@@ -204,7 +302,6 @@ const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
<div class="label">Binary</div> <div class="label">Binary</div>
<div id="binaryNumber" class="value binary num">00000000</div> <div id="binaryNumber" class="value binary num">00000000</div>
<!-- Buttons moved HERE (underneath) -->
<div class="controls"> <div class="controls">
<button class="btn" id="btnCustomBinary" type="button">Custom Binary</button> <button class="btn" id="btnCustomBinary" type="button">Custom Binary</button>
<button class="btn" id="btnCustomDenary" type="button">Custom Denary</button> <button class="btn" id="btnCustomDenary" type="button">Custom Denary</button>
@@ -212,115 +309,334 @@ const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
<button class="btn" id="btnShiftRight" type="button">Right Shift</button> <button class="btn" id="btnShiftRight" type="button">Right Shift</button>
</div> </div>
</div> </div>
</section>
<section class="bits" aria-label="Bit switches"> <!-- Bits grid (built by JS so bit-width 4..64 works) -->
{bitLabels.map((v) => ( <section id="bitsGrid" class="bits" aria-label="Bit switches"></section>
<div class="bit"> </div>
<div class="bulb" id={`bulb-${v}`} aria-hidden="true"></div>
<!-- bit place value is a number too --> <!-- RIGHT: settings panel -->
<div class="bitVal num">{v}</div> <aside>
<div class="panel">
<div class="panelTitle">Mode</div>
<div class="panelRow">
<span style="font-weight:800;">Unsigned</span>
<label class="switch" aria-label={`Toggle bit ${v}`}> <!-- MODE TOGGLE uses SAME switch style -->
<input type="checkbox" data-bit={v} /> <label class="switch" aria-label="Toggle Two's complement mode">
<input id="modeToggle" type="checkbox" />
<span class="slider"></span> <span class="slider"></span>
</label> </label>
<span style="font-weight:800;">Twos complement</span>
</div> </div>
))} <div class="hint">
Tip: In twos complement, the left-most bit (MSB) represents a negative value.
</div>
</div>
<div class="panel">
<div class="panelTitle">Bit width</div>
<div class="bwControls">
<button class="bwBtn" id="btnBitsDown" type="button" aria-label="Decrease bits"></button>
<div class="bwInput">
<div class="bwLabel">Bits</div>
<input
id="bitsField"
class="bwField"
type="number"
min="4"
max="64"
step="1"
value="8"
inputmode="numeric"
/>
</div>
<button class="bwBtn" id="btnBitsUp" type="button" aria-label="Increase bits">+</button>
</div>
<div class="hint">
Minimum 4 bits, maximum 64 bits.
</div>
</div>
</aside>
</section> </section>
</main> </main>
<script type="module"> <script type="module">
const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1]; // ---------- State ----------
const bits = new Map(bitLabels.map(v => [v, false])); let bitsCount = 8; // 4..64
let isTwos = false; // false = unsigned
let bitStates = []; // MSB -> LSB booleans
// ---------- Helpers ----------
function clamp(n, min, max){ return Math.min(max, Math.max(min, n)); }
function pow2(n){
// for up to 64 bits, Number is still OK for *visual simulator*.
// if you later want exact 64-bit maths, swap to BigInt end-to-end.
return 2 ** n;
}
function getPlaceValues(){
// MSB -> LSB
// Unsigned: [2^(n-1), ..., 1]
// Two's: [-2^(n-1), 2^(n-2), ..., 1]
const vals = [];
for (let i = 0; i < bitsCount; i++){
const power = bitsCount - 1 - i;
if (isTwos && i === 0){
vals.push(-pow2(power));
} else {
vals.push(pow2(power));
}
}
return vals;
}
function binaryString(){
return bitStates.map(b => (b ? "1" : "0")).join("");
}
function denaryValue(){
const values = getPlaceValues();
let sum = 0;
for (let i = 0; i < bitsCount; i++){
if (bitStates[i]) sum += values[i];
}
return sum;
}
function rangeForMode(){
if (!isTwos){
return { min: 0, max: pow2(bitsCount) - 1 };
}
return { min: -pow2(bitsCount - 1), max: pow2(bitsCount - 1) - 1 };
}
function updateReadout(){ function updateReadout(){
let denary = 0; document.getElementById("binaryNumber").innerText = binaryString();
let binary = ""; document.getElementById("denaryNumber").innerText = String(denaryValue());
for (const v of bitLabels){ // bulb on/off updates:
const on = bits.get(v); for (let i = 0; i < bitsCount; i++){
if (on) denary += v; const bulb = document.getElementById(`bulb-${i}`);
binary += on ? "1" : "0"; if (bulb) bulb.classList.toggle("on", !!bitStates[i]);
const bulb = document.getElementById(`bulb-${v}`); }
if (bulb) bulb.classList.toggle("on", on);
} }
document.getElementById("denaryNumber").innerText = denary.toString(); // ---------- DOM build for bits grid ----------
document.getElementById("binaryNumber").innerText = binary; function setBitsGridColumns(){
const grid = document.getElementById("bitsGrid");
// make it adapt (up to 16 per row max, then wraps)
const cols = clamp(bitsCount, 4, 16);
grid.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
} }
function setFromBinary(bin){ function buildBitsGrid(){
const clean = bin.replace(/\s+/g, ""); const grid = document.getElementById("bitsGrid");
if (!/^[01]+$/.test(clean)) return false; grid.innerHTML = "";
const padded = clean.slice(-8).padStart(8, "0"); setBitsGridColumns();
[...padded].forEach((ch, i) => {
const v = bitLabels[i]; const values = getPlaceValues();
const on = ch === "1";
bits.set(v, on); for (let i = 0; i < bitsCount; i++){
const input = document.querySelector(`input[data-bit="${v}"]`); const v = values[i];
if (input) input.checked = on;
const bit = document.createElement("div");
bit.className = "bit";
const bulb = document.createElement("div");
bulb.className = "bulb";
bulb.id = `bulb-${i}`;
bulb.setAttribute("aria-hidden", "true");
const val = document.createElement("div");
val.className = "bitVal num";
// show absolute label for MSB in twos? No — show the actual negative value (clear to students).
val.textContent = String(v);
// IMPORTANT: bit switch must be the SAME style as MODE switch
const label = document.createElement("label");
label.className = "switch";
label.setAttribute("aria-label", `Toggle bit ${v}`);
const input = document.createElement("input");
input.type = "checkbox";
input.dataset.index = String(i);
input.checked = !!bitStates[i];
const slider = document.createElement("span");
slider.className = "slider";
input.addEventListener("change", () => {
bitStates[i] = input.checked;
updateReadout();
}); });
label.appendChild(input);
label.appendChild(slider);
bit.appendChild(bulb);
bit.appendChild(val);
bit.appendChild(label);
grid.appendChild(bit);
}
updateReadout();
}
// ---------- Set state from binary / denary ----------
function setFromBinary(bin){
const clean = String(bin).replace(/\s+/g, "");
if (!/^[01]+$/.test(clean)) return false;
const padded = clean.slice(-bitsCount).padStart(bitsCount, "0");
bitStates = [...padded].map(ch => ch === "1");
// update toggles without rebuilding
for (let i = 0; i < bitsCount; i++){
const toggle = document.querySelector(`input[type="checkbox"][data-index="${i}"]`);
if (toggle) toggle.checked = bitStates[i];
}
updateReadout(); updateReadout();
return true; return true;
} }
function setFromDenary(n){ function setFromDenary(n){
n = Number(n); const num = Number(n);
if (!Number.isInteger(n) || n < 0 || n > 255) return false; if (!Number.isInteger(num)) return false;
for (const v of bitLabels){ const { min, max } = rangeForMode();
const on = n >= v; if (num < min || num > max) return false;
bits.set(v, on);
if (on) n -= v;
const input = document.querySelector(`input[data-bit="${v}"]`); // convert to bit pattern
if (input) input.checked = on; let unsignedVal;
if (!isTwos){
unsignedVal = num;
} else {
// two's complement representation
unsignedVal = num < 0 ? (pow2(bitsCount) + num) : num;
} }
// build MSB->LSB bits from unsignedVal
const newBits = [];
for (let i = 0; i < bitsCount; i++){
const power = bitsCount - 1 - i;
const bit = Math.floor(unsignedVal / pow2(power)) % 2;
newBits.push(bit === 1);
}
bitStates = newBits;
for (let i = 0; i < bitsCount; i++){
const toggle = document.querySelector(`input[type="checkbox"][data-index="${i}"]`);
if (toggle) toggle.checked = bitStates[i];
}
updateReadout(); updateReadout();
return true; return true;
} }
// ---------- Shifts ----------
function shiftLeft(){ function shiftLeft(){
const bin = document.getElementById("binaryNumber").innerText; // logical left shift: drop MSB, add 0 at LSB
setFromBinary(bin.slice(1) + "0"); bitStates = bitStates.slice(1).concat(false);
buildBitsGrid(); // rebuild keeps labels correct if mode changed (also updates bulbs)
} }
function shiftRight(){ function shiftRight(){
const bin = document.getElementById("binaryNumber").innerText; if (!isTwos){
setFromBinary("0" + bin.slice(0, -1)); // logical right shift
bitStates = [false].concat(bitStates.slice(0, -1));
} else {
// arithmetic right shift (preserve MSB)
const msb = bitStates[0];
bitStates = [msb].concat(bitStates.slice(0, -1));
}
buildBitsGrid();
} }
// switches // ---------- Bit width ----------
document.querySelectorAll('input[type="checkbox"][data-bit]').forEach(input => { function applyBitsCount(next){
input.addEventListener("change", () => { const wanted = clamp(Number(next) || 8, 4, 64);
const v = Number(input.dataset.bit); if (wanted === bitsCount) return;
bits.set(v, input.checked);
updateReadout();
});
});
// buttons // keep the right-most (LSB) bits when resizing (feels natural)
const old = binaryString();
bitsCount = wanted;
// rebuild state from old binary, keeping LSBs
const padded = old.slice(-bitsCount).padStart(bitsCount, "0");
bitStates = [...padded].map(ch => ch === "1");
document.getElementById("bitsField").value = String(bitsCount);
buildBitsGrid();
}
// ---------- Mode toggle ----------
function applyMode(nextIsTwos){
const prevDenary = denaryValue(); // keep the *value* if possible
isTwos = !!nextIsTwos;
// Try to keep the denary value, but clamp if it becomes invalid in the new mode
const { min, max } = rangeForMode();
const kept = clamp(prevDenary, min, max);
setFromDenary(kept);
buildBitsGrid();
}
// ---------- Wire up UI ----------
function wireUI(){
document.getElementById("btnShiftLeft").addEventListener("click", shiftLeft); document.getElementById("btnShiftLeft").addEventListener("click", shiftLeft);
document.getElementById("btnShiftRight").addEventListener("click", shiftRight); document.getElementById("btnShiftRight").addEventListener("click", shiftRight);
document.getElementById("btnCustomBinary").addEventListener("click", () => { document.getElementById("btnCustomBinary").addEventListener("click", () => {
const val = prompt("Enter an 8-bit binary number (e.g. 01011001):"); const val = prompt(`Enter a binary number (up to ${bitsCount} bits):`);
if (val === null) return; if (val === null) return;
if (!setFromBinary(val)) alert("Invalid input. Use only 0 and 1 (up to 8 digits)."); if (!setFromBinary(val)) alert("Invalid input. Use only 0 and 1.");
}); });
document.getElementById("btnCustomDenary").addEventListener("click", () => { document.getElementById("btnCustomDenary").addEventListener("click", () => {
const val = prompt("Enter a denary number (0 to 255):"); const { min, max } = rangeForMode();
const val = prompt(`Enter a denary number (${min} to ${max}):`);
if (val === null) return; if (val === null) return;
if (!setFromDenary(val)) alert("Invalid input. Enter an integer from 0 to 255."); if (!setFromDenary(val)) alert(`Invalid input. Enter an integer from ${min} to ${max}.`);
}); });
updateReadout(); // Mode toggle
const modeToggle = document.getElementById("modeToggle");
modeToggle.addEventListener("change", () => applyMode(modeToggle.checked));
// Bit width +/- and manual entry
document.getElementById("btnBitsDown").addEventListener("click", () => applyBitsCount(bitsCount - 1));
document.getElementById("btnBitsUp").addEventListener("click", () => applyBitsCount(bitsCount + 1));
const bitsField = document.getElementById("bitsField");
bitsField.addEventListener("change", () => applyBitsCount(bitsField.value));
bitsField.addEventListener("blur", () => applyBitsCount(bitsField.value));
bitsField.addEventListener("keydown", (e) => {
if (e.key === "Enter") applyBitsCount(bitsField.value);
});
}
// ---------- Init ----------
function init(){
// default: 8-bit unsigned
bitsCount = 8;
isTwos = false;
bitStates = Array(bitsCount).fill(false);
document.getElementById("modeToggle").checked = false;
document.getElementById("bitsField").value = String(bitsCount);
wireUI();
buildBitsGrid();
}
init();
</script> </script>
</body> </body>
</html> </html>