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
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] 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
- [ ] Two's Compliment Simulator
- [ ] Computer Components Simulator
## Version 1.0 Release Date: 1<sup>st</sup> September 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">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Unsigned Binary | Computing:Box</title>
<title>Binary | Computing:Box</title>
<style>
:root{
--bg: #1f2027;
--panel: #22242d;
--panel2:#262833;
--text: #e8e8ee;
--muted: #a9acb8;
--accent: #33ff7a;
@@ -19,12 +28,11 @@ const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
--line: rgba(255,255,255,.12);
}
/* DSEG7ClassicRegular font */
@font-face{
font-family: "DSEG7ClassicRegular";
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-style: normal;
font-display: swap;
@@ -43,15 +51,15 @@ const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
padding: 32px 20px 60px;
}
.top{
/* Layout: readout left, settings panel right (like your screenshot) */
.layout{
display:grid;
grid-template-columns: 1fr;
grid-template-columns: 1fr 360px;
gap: 22px;
align-items:start;
}
.readout{
background: transparent;
text-align:center;
padding: 10px 10px 0;
}
@@ -65,7 +73,6 @@ const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
margin-top: 8px;
}
/* All numbers use DSEG */
.num{
font-family: "DSEG7ClassicRegular", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-weight: 400;
@@ -74,17 +81,18 @@ const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
}
.value{
font-size: 64px;
font-size: 70px;
line-height: 1.0;
margin: 6px 0 16px;
}
.binary{
font-size: 44px;
font-size: 54px;
letter-spacing: .12em;
word-break: break-all;
}
/* Buttons now UNDER the readout */
/* Buttons under readout (as requested) */
.controls{
margin-top: 16px;
display:flex;
@@ -105,53 +113,42 @@ const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
}
.btn:active{ transform: translateY(1px); }
.bits{
margin-top: 34px;
padding-top: 26px;
border-top: 1px solid var(--line);
display:grid;
grid-template-columns: repeat(8, 1fr);
gap: 18px;
align-items:end;
text-align:center;
/* Settings panel cards */
.panel{
background: rgba(255,255,255,.04);
border: 1px solid rgba(255,255,255,.10);
border-radius: 14px;
padding: 16px 16px 14px;
margin-bottom: 14px;
}
.bit{
.panelTitle{
font-size: 12px;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--muted);
font-weight: 800;
margin-bottom: 10px;
}
.panelRow{
display:flex;
flex-direction:column;
align-items:center;
gap: 10px;
padding: 8px 4px;
justify-content:space-between;
gap: 12px;
}
.hint{
color: var(--muted);
font-size: 12px;
margin-top: 10px;
line-height: 1.35;
}
.bitVal{
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 */
/* Reusable toggle switch (for MODE and for each BIT) */
.switch{
position: relative;
width: 56px;
height: 34px;
display:inline-block;
flex: 0 0 auto;
}
.switch input{
opacity:0;
@@ -186,141 +183,460 @@ const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
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){
.bits{ grid-template-columns: repeat(4, 1fr); }
.value{ font-size: 54px; }
.binary{ font-size: 36px; }
.btn{ min-width: 140px; }
}
</style>
</head>
<body>
<main class="wrap">
<section class="top">
<div class="readout">
<div class="label">Denary</div>
<div id="denaryNumber" class="value num">0</div>
<section class="layout">
<!-- LEFT: readout + buttons -->
<div>
<div class="readout">
<div class="label">Denary</div>
<div id="denaryNumber" class="value num">0</div>
<div class="label">Binary</div>
<div id="binaryNumber" class="value binary num">00000000</div>
<div class="label">Binary</div>
<div id="binaryNumber" class="value binary num">00000000</div>
<!-- Buttons moved HERE (underneath) -->
<div class="controls">
<button class="btn" id="btnCustomBinary" type="button">Custom Binary</button>
<button class="btn" id="btnCustomDenary" type="button">Custom Denary</button>
<button class="btn" id="btnShiftLeft" type="button">Left Shift</button>
<button class="btn" id="btnShiftRight" type="button">Right Shift</button>
<div class="controls">
<button class="btn" id="btnCustomBinary" type="button">Custom Binary</button>
<button class="btn" id="btnCustomDenary" type="button">Custom Denary</button>
<button class="btn" id="btnShiftLeft" type="button">Left Shift</button>
<button class="btn" id="btnShiftRight" type="button">Right Shift</button>
</div>
</div>
<!-- Bits grid (built by JS so bit-width 4..64 works) -->
<section id="bitsGrid" class="bits" aria-label="Bit switches"></section>
</div>
</section>
<section class="bits" aria-label="Bit switches">
{bitLabels.map((v) => (
<div class="bit">
<div class="bulb" id={`bulb-${v}`} aria-hidden="true"></div>
<!-- RIGHT: settings panel -->
<aside>
<div class="panel">
<div class="panelTitle">Mode</div>
<div class="panelRow">
<span style="font-weight:800;">Unsigned</span>
<!-- bit place value is a number too -->
<div class="bitVal num">{v}</div>
<!-- MODE TOGGLE uses SAME switch style -->
<label class="switch" aria-label="Toggle Two's complement mode">
<input id="modeToggle" type="checkbox" />
<span class="slider"></span>
</label>
<label class="switch" aria-label={`Toggle bit ${v}`}>
<input type="checkbox" data-bit={v} />
<span class="slider"></span>
</label>
<span style="font-weight:800;">Twos complement</span>
</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>
</main>
<script type="module">
const bitLabels = [128, 64, 32, 16, 8, 4, 2, 1];
const bits = new Map(bitLabels.map(v => [v, false]));
// ---------- State ----------
let bitsCount = 8; // 4..64
let isTwos = false; // false = unsigned
let bitStates = []; // MSB -> LSB booleans
function updateReadout(){
let denary = 0;
let binary = "";
// ---------- Helpers ----------
function clamp(n, min, max){ return Math.min(max, Math.max(min, n)); }
for (const v of bitLabels){
const on = bits.get(v);
if (on) denary += v;
binary += on ? "1" : "0";
const bulb = document.getElementById(`bulb-${v}`);
if (bulb) bulb.classList.toggle("on", on);
}
document.getElementById("denaryNumber").innerText = denary.toString();
document.getElementById("binaryNumber").innerText = binary;
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(){
document.getElementById("binaryNumber").innerText = binaryString();
document.getElementById("denaryNumber").innerText = String(denaryValue());
// bulb on/off updates:
for (let i = 0; i < bitsCount; i++){
const bulb = document.getElementById(`bulb-${i}`);
if (bulb) bulb.classList.toggle("on", !!bitStates[i]);
}
}
// ---------- DOM build for bits grid ----------
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 buildBitsGrid(){
const grid = document.getElementById("bitsGrid");
grid.innerHTML = "";
setBitsGridColumns();
const values = getPlaceValues();
for (let i = 0; i < bitsCount; i++){
const v = values[i];
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 = bin.replace(/\s+/g, "");
const clean = String(bin).replace(/\s+/g, "");
if (!/^[01]+$/.test(clean)) return false;
const padded = clean.slice(-8).padStart(8, "0");
[...padded].forEach((ch, i) => {
const v = bitLabels[i];
const on = ch === "1";
bits.set(v, on);
const input = document.querySelector(`input[data-bit="${v}"]`);
if (input) input.checked = on;
});
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();
return true;
}
function setFromDenary(n){
n = Number(n);
if (!Number.isInteger(n) || n < 0 || n > 255) return false;
const num = Number(n);
if (!Number.isInteger(num)) return false;
for (const v of bitLabels){
const on = n >= v;
bits.set(v, on);
if (on) n -= v;
const { min, max } = rangeForMode();
if (num < min || num > max) return false;
const input = document.querySelector(`input[data-bit="${v}"]`);
if (input) input.checked = on;
// convert to bit pattern
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();
return true;
}
// ---------- Shifts ----------
function shiftLeft(){
const bin = document.getElementById("binaryNumber").innerText;
setFromBinary(bin.slice(1) + "0");
// logical left shift: drop MSB, add 0 at LSB
bitStates = bitStates.slice(1).concat(false);
buildBitsGrid(); // rebuild keeps labels correct if mode changed (also updates bulbs)
}
function shiftRight(){
const bin = document.getElementById("binaryNumber").innerText;
setFromBinary("0" + bin.slice(0, -1));
if (!isTwos){
// 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
document.querySelectorAll('input[type="checkbox"][data-bit]').forEach(input => {
input.addEventListener("change", () => {
const v = Number(input.dataset.bit);
bits.set(v, input.checked);
updateReadout();
// ---------- Bit width ----------
function applyBitsCount(next){
const wanted = clamp(Number(next) || 8, 4, 64);
if (wanted === bitsCount) return;
// 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("btnShiftRight").addEventListener("click", shiftRight);
document.getElementById("btnCustomBinary").addEventListener("click", () => {
const val = prompt(`Enter a binary number (up to ${bitsCount} bits):`);
if (val === null) return;
if (!setFromBinary(val)) alert("Invalid input. Use only 0 and 1.");
});
});
// buttons
document.getElementById("btnShiftLeft").addEventListener("click", shiftLeft);
document.getElementById("btnShiftRight").addEventListener("click", shiftRight);
document.getElementById("btnCustomDenary").addEventListener("click", () => {
const { min, max } = rangeForMode();
const val = prompt(`Enter a denary number (${min} to ${max}):`);
if (val === null) return;
if (!setFromDenary(val)) alert(`Invalid input. Enter an integer from ${min} to ${max}.`);
});
document.getElementById("btnCustomBinary").addEventListener("click", () => {
const val = prompt("Enter an 8-bit binary number (e.g. 01011001):");
if (val === null) return;
if (!setFromBinary(val)) alert("Invalid input. Use only 0 and 1 (up to 8 digits).");
});
// Mode toggle
const modeToggle = document.getElementById("modeToggle");
modeToggle.addEventListener("change", () => applyMode(modeToggle.checked));
document.getElementById("btnCustomDenary").addEventListener("click", () => {
const val = prompt("Enter a denary number (0 to 255):");
if (val === null) return;
if (!setFromDenary(val)) alert("Invalid input. Enter an integer from 0 to 255.");
});
// Bit width +/- and manual entry
document.getElementById("btnBitsDown").addEventListener("click", () => applyBitsCount(bitsCount - 1));
document.getElementById("btnBitsUp").addEventListener("click", () => applyBitsCount(bitsCount + 1));
updateReadout();
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>
</body>
</html>