You've already forked computing-box
@@ -1,10 +1,3 @@
|
|||||||
/* Binary simulator for Computing:Box
|
|
||||||
- Wrap bits every 8 (CSS handles layout)
|
|
||||||
- Bit width 4..64
|
|
||||||
- Unsigned + Two’s complement toggle (WORKING)
|
|
||||||
- Bulbs + toggle switches for each bit
|
|
||||||
*/
|
|
||||||
|
|
||||||
const bitsGrid = document.getElementById("bitsGrid");
|
const bitsGrid = document.getElementById("bitsGrid");
|
||||||
const denaryEl = document.getElementById("denaryNumber");
|
const denaryEl = document.getElementById("denaryNumber");
|
||||||
const binaryEl = document.getElementById("binaryNumber");
|
const binaryEl = document.getElementById("binaryNumber");
|
||||||
@@ -21,42 +14,87 @@ const btnShiftRight = document.getElementById("btnShiftRight");
|
|||||||
const btnCustomBinary = document.getElementById("btnCustomBinary");
|
const btnCustomBinary = document.getElementById("btnCustomBinary");
|
||||||
const btnCustomDenary = document.getElementById("btnCustomDenary");
|
const btnCustomDenary = document.getElementById("btnCustomDenary");
|
||||||
|
|
||||||
let bitCount = clampInt(Number(bitsInput?.value ?? 8), 4, 64);
|
let bitCount = 8; // 4..64
|
||||||
let isTwos = false;
|
let mode = "unsigned"; // "unsigned" | "twos"
|
||||||
|
|
||||||
// bits[0] is MSB, bits[bitCount-1] is LSB
|
// bits[0] is MSB, bits[bitCount-1] is LSB
|
||||||
let bits = new Array(bitCount).fill(false);
|
let bits = new Array(bitCount).fill(false);
|
||||||
|
|
||||||
|
/* -----------------------------
|
||||||
|
Helpers
|
||||||
|
----------------------------- */
|
||||||
function clampInt(n, min, max) {
|
function clampInt(n, min, max) {
|
||||||
n = Number(n);
|
n = Number(n);
|
||||||
if (!Number.isInteger(n)) n = min;
|
if (!Number.isFinite(n)) return min;
|
||||||
return Math.max(min, Math.min(max, n));
|
return Math.max(min, Math.min(max, Math.trunc(n)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function pow2(exp) {
|
function maxUnsigned(nBits) {
|
||||||
// safe up to 2^63 in JS integer precision? (JS uses float) but our usage is display/control, ok.
|
return (2 ** nBits) - 1;
|
||||||
return 2 ** exp;
|
}
|
||||||
|
|
||||||
|
function minTwos(nBits) {
|
||||||
|
return -(2 ** (nBits - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
function maxTwos(nBits) {
|
||||||
|
return (2 ** (nBits - 1)) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bitsToUnsigned() {
|
||||||
|
// MSB..LSB
|
||||||
|
let v = 0;
|
||||||
|
for (let i = 0; i < bitCount; i++) {
|
||||||
|
if (bits[i]) v += 2 ** (bitCount - 1 - i);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bitsToSigned() {
|
||||||
|
const u = bitsToUnsigned();
|
||||||
|
if (mode !== "twos") return u;
|
||||||
|
// if MSB is 1 => negative
|
||||||
|
if (!bits[0]) return u;
|
||||||
|
return u - (2 ** bitCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBitsFromUnsigned(u) {
|
||||||
|
u = clampInt(u, 0, maxUnsigned(bitCount));
|
||||||
|
for (let i = 0; i < bitCount; i++) {
|
||||||
|
const place = 2 ** (bitCount - 1 - i);
|
||||||
|
bits[i] = u >= place;
|
||||||
|
if (bits[i]) u -= place;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBitsFromSigned(s) {
|
||||||
|
// convert signed -> unsigned representation
|
||||||
|
const min = minTwos(bitCount);
|
||||||
|
const max = maxTwos(bitCount);
|
||||||
|
s = clampInt(s, min, max);
|
||||||
|
const u = s < 0 ? (2 ** bitCount) + s : s;
|
||||||
|
setBitsFromUnsigned(u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -----------------------------
|
/* -----------------------------
|
||||||
Build UI (bulbs + switches)
|
Build UI
|
||||||
----------------------------- */
|
----------------------------- */
|
||||||
function buildBits(count) {
|
function buildBitsUI() {
|
||||||
bitCount = clampInt(count, 4, 64);
|
|
||||||
bits = resizeBits(bits, bitCount);
|
|
||||||
|
|
||||||
bitsGrid.innerHTML = "";
|
bitsGrid.innerHTML = "";
|
||||||
|
|
||||||
|
// Force wrap every 8 bits visually
|
||||||
|
// CSS already does repeat(8,...). Nothing else needed.
|
||||||
|
|
||||||
for (let i = 0; i < bitCount; i++) {
|
for (let i = 0; i < bitCount; i++) {
|
||||||
const exp = bitCount - 1 - i; // MSB has highest exponent
|
const placeValue = 2 ** (bitCount - 1 - i);
|
||||||
const value = pow2(exp);
|
|
||||||
|
|
||||||
const bit = document.createElement("div");
|
const bit = document.createElement("div");
|
||||||
bit.className = "bit";
|
bit.className = "bit";
|
||||||
|
|
||||||
bit.innerHTML = `
|
bit.innerHTML = `
|
||||||
<div class="bulb" id="bulb-${i}" aria-hidden="true"></div>
|
<div class="bulb" id="bulb-${i}" aria-hidden="true">💡</div>
|
||||||
<div class="bitVal">${value}</div>
|
<div class="bitVal num">${placeValue}</div>
|
||||||
<label class="switch" aria-label="Toggle bit ${value}">
|
<label class="switch" aria-label="Toggle bit ${placeValue}">
|
||||||
<input type="checkbox" data-index="${i}">
|
<input type="checkbox" data-index="${i}">
|
||||||
<span class="slider"></span>
|
<span class="slider"></span>
|
||||||
</label>
|
</label>
|
||||||
@@ -65,114 +103,65 @@ function buildBits(count) {
|
|||||||
bitsGrid.appendChild(bit);
|
bitsGrid.appendChild(bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
hookSwitches();
|
// Hook switches
|
||||||
syncUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resizeBits(oldBits, newCount) {
|
|
||||||
// keep LSB end stable when changing bit width:
|
|
||||||
// align old bits to the right (LSB)
|
|
||||||
const out = new Array(newCount).fill(false);
|
|
||||||
const copy = Math.min(oldBits.length, newCount);
|
|
||||||
|
|
||||||
for (let k = 0; k < copy; k++) {
|
|
||||||
// copy from end (LSB)
|
|
||||||
out[newCount - 1 - k] = oldBits[oldBits.length - 1 - k];
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hookSwitches() {
|
|
||||||
bitsGrid.querySelectorAll('input[type="checkbox"][data-index]').forEach((input) => {
|
bitsGrid.querySelectorAll('input[type="checkbox"][data-index]').forEach((input) => {
|
||||||
input.addEventListener("change", () => {
|
input.addEventListener("change", () => {
|
||||||
const i = Number(input.dataset.index);
|
const i = Number(input.dataset.index);
|
||||||
bits[i] = input.checked;
|
bits[i] = input.checked;
|
||||||
updateReadout();
|
updateReadout();
|
||||||
updateBulb(i);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
syncUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -----------------------------
|
/* -----------------------------
|
||||||
Mode toggle (Unsigned <-> Two’s)
|
Sync + Display
|
||||||
----------------------------- */
|
----------------------------- */
|
||||||
function setModeTwos(on) {
|
|
||||||
isTwos = !!on;
|
|
||||||
|
|
||||||
modeHint.textContent = isTwos
|
|
||||||
? "Tip: In two’s complement, the left-most bit (MSB) represents a negative value."
|
|
||||||
: "Tip: In unsigned binary, all bits represent positive values.";
|
|
||||||
|
|
||||||
// Just re-calc denary using current bit pattern
|
|
||||||
updateReadout();
|
|
||||||
}
|
|
||||||
|
|
||||||
modeToggle?.addEventListener("change", () => setModeTwos(modeToggle.checked));
|
|
||||||
|
|
||||||
/* -----------------------------
|
|
||||||
Calculations
|
|
||||||
----------------------------- */
|
|
||||||
function getUnsignedValue() {
|
|
||||||
let n = 0;
|
|
||||||
for (let i = 0; i < bitCount; i++) {
|
|
||||||
if (!bits[i]) continue;
|
|
||||||
const exp = bitCount - 1 - i;
|
|
||||||
n += pow2(exp);
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTwosValue() {
|
|
||||||
// MSB has negative weight
|
|
||||||
let n = 0;
|
|
||||||
for (let i = 0; i < bitCount; i++) {
|
|
||||||
if (!bits[i]) continue;
|
|
||||||
const exp = bitCount - 1 - i;
|
|
||||||
if (i === 0) n -= pow2(exp); // MSB
|
|
||||||
else n += pow2(exp);
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDenary() {
|
|
||||||
return isTwos ? getTwosValue() : getUnsignedValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getBinaryString() {
|
|
||||||
return bits.map(b => (b ? "1" : "0")).join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -----------------------------
|
|
||||||
UI updates
|
|
||||||
----------------------------- */
|
|
||||||
function updateBulb(i) {
|
|
||||||
const bulb = document.getElementById(`bulb-${i}`);
|
|
||||||
if (bulb) bulb.classList.toggle("on", bits[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateReadout() {
|
|
||||||
denaryEl.textContent = String(getDenary());
|
|
||||||
binaryEl.textContent = getBinaryString();
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncUI() {
|
function syncUI() {
|
||||||
bitsGrid.querySelectorAll('input[type="checkbox"][data-index]').forEach((input) => {
|
bitsGrid.querySelectorAll('input[type="checkbox"][data-index]').forEach((input) => {
|
||||||
const i = Number(input.dataset.index);
|
const i = Number(input.dataset.index);
|
||||||
input.checked = !!bits[i];
|
input.checked = !!bits[i];
|
||||||
updateBulb(i);
|
|
||||||
|
const bulb = document.getElementById(`bulb-${i}`);
|
||||||
|
if (bulb) bulb.classList.toggle("on", !!bits[i]);
|
||||||
});
|
});
|
||||||
|
|
||||||
updateReadout();
|
updateReadout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateReadout() {
|
||||||
|
const binaryStr = bits.map((b) => (b ? "1" : "0")).join("");
|
||||||
|
binaryEl.textContent = binaryStr;
|
||||||
|
|
||||||
|
const d = bitsToSigned();
|
||||||
|
denaryEl.textContent = String(d);
|
||||||
|
|
||||||
|
// hint text
|
||||||
|
if (mode === "twos") {
|
||||||
|
modeHint.textContent =
|
||||||
|
"Tip: In two’s complement, the left-most bit (MSB) represents a negative value.";
|
||||||
|
} else {
|
||||||
|
modeHint.textContent =
|
||||||
|
"Tip: In unsigned binary, all bits represent positive values.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// bulbs
|
||||||
|
for (let i = 0; i < bitCount; i++) {
|
||||||
|
const bulb = document.getElementById(`bulb-${i}`);
|
||||||
|
if (bulb) bulb.classList.toggle("on", !!bits[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* -----------------------------
|
/* -----------------------------
|
||||||
Set from Binary / Denary
|
Input setters
|
||||||
----------------------------- */
|
----------------------------- */
|
||||||
function setFromBinary(bin) {
|
function setFromBinary(bin) {
|
||||||
const clean = String(bin).replace(/\s+/g, "");
|
const clean = String(bin).replace(/\s+/g, "");
|
||||||
if (!/^[01]+$/.test(clean)) return false;
|
if (!/^[01]+$/.test(clean)) return false;
|
||||||
|
|
||||||
const padded = clean.slice(-bitCount).padStart(bitCount, "0");
|
const padded = clean.slice(-bitCount).padStart(bitCount, "0");
|
||||||
bits = [...padded].map(ch => ch === "1");
|
bits = [...padded].map((ch) => ch === "1");
|
||||||
syncUI();
|
syncUI();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -180,47 +169,13 @@ function setFromBinary(bin) {
|
|||||||
function setFromDenary(n) {
|
function setFromDenary(n) {
|
||||||
if (!Number.isInteger(n)) return false;
|
if (!Number.isInteger(n)) return false;
|
||||||
|
|
||||||
if (!isTwos) {
|
if (mode === "twos") {
|
||||||
const min = 0;
|
if (n < minTwos(bitCount) || n > maxTwos(bitCount)) return false;
|
||||||
const max = pow2(bitCount) - 1;
|
setBitsFromSigned(n);
|
||||||
if (n < min || n > max) return false;
|
} else {
|
||||||
|
if (n < 0 || n > maxUnsigned(bitCount)) return false;
|
||||||
// unsigned fill from MSB->LSB
|
setBitsFromUnsigned(n);
|
||||||
let remaining = n;
|
|
||||||
bits = bits.map((_, i) => {
|
|
||||||
const exp = bitCount - 1 - i;
|
|
||||||
const value = pow2(exp);
|
|
||||||
if (remaining >= value) {
|
|
||||||
remaining -= value;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
syncUI();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Two's complement bounds
|
|
||||||
const min = -pow2(bitCount - 1);
|
|
||||||
const max = pow2(bitCount - 1) - 1;
|
|
||||||
if (n < min || n > max) return false;
|
|
||||||
|
|
||||||
// Convert to raw unsigned representation:
|
|
||||||
// if negative, represent as 2^bitCount + n
|
|
||||||
let raw = n;
|
|
||||||
if (raw < 0) raw = pow2(bitCount) + raw;
|
|
||||||
|
|
||||||
let remaining = raw;
|
|
||||||
bits = bits.map((_, i) => {
|
|
||||||
const exp = bitCount - 1 - i;
|
|
||||||
const value = pow2(exp);
|
|
||||||
if (remaining >= value) {
|
|
||||||
remaining -= value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
syncUI();
|
syncUI();
|
||||||
return true;
|
return true;
|
||||||
@@ -230,72 +185,89 @@ function setFromDenary(n) {
|
|||||||
Shifts
|
Shifts
|
||||||
----------------------------- */
|
----------------------------- */
|
||||||
function shiftLeft() {
|
function shiftLeft() {
|
||||||
// drop MSB, append 0 at LSB
|
// left shift: drop MSB, append 0
|
||||||
bits.shift();
|
bits.shift();
|
||||||
bits.push(false);
|
bits.push(false);
|
||||||
syncUI();
|
syncUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
function shiftRight() {
|
function shiftRight() {
|
||||||
// unsigned: logical shift right (prepend 0)
|
// right shift:
|
||||||
// twos: arithmetic shift right (prepend old MSB)
|
// unsigned = logical (prepend 0)
|
||||||
|
// twos = arithmetic (preserve MSB)
|
||||||
const msb = bits[0];
|
const msb = bits[0];
|
||||||
bits.pop();
|
bits.pop();
|
||||||
bits.unshift(isTwos ? msb : false);
|
bits.unshift(mode === "twos" ? msb : false);
|
||||||
syncUI();
|
syncUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -----------------------------
|
/* -----------------------------
|
||||||
Bit width controls
|
Mode + Bit width
|
||||||
----------------------------- */
|
----------------------------- */
|
||||||
function applyBitCount(next) {
|
function setMode(nextMode) {
|
||||||
const v = clampInt(next, 4, 64);
|
if (nextMode !== "unsigned" && nextMode !== "twos") return;
|
||||||
bitsInput.value = String(v);
|
|
||||||
buildBits(v);
|
// keep the *bit pattern* the same, just interpret differently
|
||||||
|
mode = nextMode;
|
||||||
|
updateReadout();
|
||||||
}
|
}
|
||||||
|
|
||||||
btnBitsUp?.addEventListener("click", () => applyBitCount(bitCount + 1));
|
function setBitCount(nextCount) {
|
||||||
btnBitsDown?.addEventListener("click", () => applyBitCount(bitCount - 1));
|
nextCount = clampInt(nextCount, 4, 64);
|
||||||
|
|
||||||
bitsInput?.addEventListener("change", () => {
|
if (nextCount === bitCount) return;
|
||||||
applyBitCount(Number(bitsInput.value));
|
|
||||||
});
|
// preserve current binary string as much as possible (keep least significant bits)
|
||||||
|
const currentBinary = bits.map((b) => (b ? "1" : "0")).join("");
|
||||||
|
bitCount = nextCount;
|
||||||
|
bitsInput.value = String(bitCount);
|
||||||
|
|
||||||
|
bits = new Array(bitCount).fill(false);
|
||||||
|
buildBitsUI();
|
||||||
|
setFromBinary(currentBinary); // reapply into new width (LSB-preserving)
|
||||||
|
}
|
||||||
|
|
||||||
/* -----------------------------
|
/* -----------------------------
|
||||||
Buttons
|
Wire up controls
|
||||||
----------------------------- */
|
----------------------------- */
|
||||||
btnShiftLeft?.addEventListener("click", shiftLeft);
|
modeToggle.addEventListener("change", () => {
|
||||||
btnShiftRight?.addEventListener("click", shiftRight);
|
setMode(modeToggle.checked ? "twos" : "unsigned");
|
||||||
|
|
||||||
btnCustomBinary?.addEventListener("click", () => {
|
|
||||||
const v = prompt(`Enter a ${bitCount}-bit binary number:`);
|
|
||||||
if (v === null) return;
|
|
||||||
if (!setFromBinary(v)) alert("Invalid input. Use only 0 and 1.");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
btnCustomDenary?.addEventListener("click", () => {
|
btnBitsUp.addEventListener("click", () => setBitCount(bitCount + 1));
|
||||||
const min = isTwos ? -pow2(bitCount - 1) : 0;
|
btnBitsDown.addEventListener("click", () => setBitCount(bitCount - 1));
|
||||||
const max = isTwos ? (pow2(bitCount - 1) - 1) : (pow2(bitCount) - 1);
|
|
||||||
|
bitsInput.addEventListener("change", () => setBitCount(bitsInput.value));
|
||||||
|
bitsInput.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Enter") setBitCount(bitsInput.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
btnShiftLeft.addEventListener("click", shiftLeft);
|
||||||
|
btnShiftRight.addEventListener("click", shiftRight);
|
||||||
|
|
||||||
|
btnCustomBinary.addEventListener("click", () => {
|
||||||
|
const v = prompt(`Enter a ${bitCount}-bit binary number:`);
|
||||||
|
if (v === null) return;
|
||||||
|
if (!setFromBinary(v)) alert("Invalid binary. Use only 0 and 1.");
|
||||||
|
});
|
||||||
|
|
||||||
|
btnCustomDenary.addEventListener("click", () => {
|
||||||
|
const min = mode === "twos" ? minTwos(bitCount) : 0;
|
||||||
|
const max = mode === "twos" ? maxTwos(bitCount) : maxUnsigned(bitCount);
|
||||||
|
|
||||||
const v = prompt(`Enter a denary number (${min} to ${max}):`);
|
const v = prompt(`Enter a denary number (${min} to ${max}):`);
|
||||||
if (v === null) return;
|
if (v === null) return;
|
||||||
|
|
||||||
const n = Number(v);
|
const n = Number(v);
|
||||||
if (!Number.isInteger(n) || !setFromDenary(n)) {
|
if (!Number.isInteger(n) || !setFromDenary(n)) {
|
||||||
alert(`Invalid input. Enter an integer from ${min} to ${max}.`);
|
alert(`Invalid denary. Enter an integer from ${min} to ${max}.`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* -----------------------------
|
/* -----------------------------
|
||||||
INIT
|
Init
|
||||||
----------------------------- */
|
----------------------------- */
|
||||||
function init() {
|
bitsInput.value = String(bitCount);
|
||||||
// default mode: unsigned
|
modeToggle.checked = false; // unsigned by default
|
||||||
setModeTwos(false);
|
buildBitsUI();
|
||||||
modeToggle.checked = false;
|
updateReadout();
|
||||||
|
|
||||||
// build initial bits
|
|
||||||
applyBitCount(bitCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
init();
|
|
||||||
|
|||||||
13
src/components/Footer.astro
Normal file
13
src/components/Footer.astro
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<footer class="siteFooter">
|
||||||
|
<div class="inner">
|
||||||
|
<div class="muted">Computer Science Concept Simulators</div>
|
||||||
|
<div class="muted">© {new Date().getFullYear()} Computing:Box · Created with ❤️ by Mr Lyall</div>
|
||||||
|
<div class="muted">Powered by ADCM Networks</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.siteFooter{ border-top:1px solid rgba(255,255,255,.10); background:rgba(0,0,0,.12); }
|
||||||
|
.inner{ max-width:1200px; margin:0 auto; padding:18px 20px; display:flex; flex-direction:column; gap:6px; }
|
||||||
|
.muted{ color:rgba(255,255,255,.60); font-size:13px; }
|
||||||
|
</style>
|
||||||
|
</footer>
|
||||||
22
src/components/Header.astro
Normal file
22
src/components/Header.astro
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<header class="siteHeader">
|
||||||
|
<div class="inner">
|
||||||
|
<a class="brand" href="/">Computing:Box</a>
|
||||||
|
|
||||||
|
<nav class="nav">
|
||||||
|
<a href="/binary">Binary</a>
|
||||||
|
<a href="/hexadecimal/gcse-hexadecimal">Hexadecimal</a>
|
||||||
|
<a href="/hex-colours">Hex Colours</a>
|
||||||
|
<a href="/logic-gates">Logic Gates</a>
|
||||||
|
<a href="/about">About</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.siteHeader{ border-bottom:1px solid rgba(255,255,255,.10); background:rgba(0,0,0,.12); }
|
||||||
|
.inner{ max-width:1200px; margin:0 auto; padding:14px 20px; display:flex; align-items:center; justify-content:space-between; gap:16px; }
|
||||||
|
.brand{ color:#fff; text-decoration:none; font-weight:800; letter-spacing:.02em; }
|
||||||
|
.nav{ display:flex; gap:14px; flex-wrap:wrap; }
|
||||||
|
.nav a{ color:rgba(255,255,255,.85); text-decoration:none; font-weight:700; font-size:14px; }
|
||||||
|
.nav a:hover{ color:#fff; text-decoration:underline; }
|
||||||
|
</style>
|
||||||
|
</header>
|
||||||
@@ -1,26 +1,29 @@
|
|||||||
---
|
---
|
||||||
import SiteHeader from "../components/SiteHeader.astro";
|
import Header from "../components/Header.astro";
|
||||||
import SiteFooter from "../components/SiteFooter.astro";
|
import Footer from "../components/Footer.astro";
|
||||||
|
|
||||||
const { title = "Computing:Box" } = Astro.props;
|
const { title = "Computing:Box" } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<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>{title}</title>
|
<title>{title}</title>
|
||||||
|
|
||||||
<!-- Global site styles -->
|
|
||||||
<link rel="stylesheet" href="/styles/global.css" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
|
||||||
<SiteHeader />
|
|
||||||
|
|
||||||
<main class="site-main">
|
<body>
|
||||||
|
<Header />
|
||||||
|
<main class="page">
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
|
<Footer />
|
||||||
|
|
||||||
<SiteFooter />
|
<style>
|
||||||
|
:root{ --bg:#1f2027; --text:#e8e8ee; }
|
||||||
|
body{ margin:0; background:var(--bg); color:var(--text); font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif; }
|
||||||
|
.page{ min-height: calc(100vh - 140px); }
|
||||||
|
</style>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||||
|
import "../styles/binary.css";
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title="Binary | Computing:Box">
|
<BaseLayout title="Binary | Computing:Box">
|
||||||
<!-- Page-specific CSS -->
|
<div class="wrap">
|
||||||
<link rel="stylesheet" href="/styles/binary.css" />
|
|
||||||
|
|
||||||
<main class="wrap">
|
|
||||||
<section class="topGrid">
|
<section class="topGrid">
|
||||||
<!-- LEFT: readout + buttons + bits -->
|
<!-- LEFT -->
|
||||||
<div>
|
<div>
|
||||||
<div class="readout">
|
<div class="readout">
|
||||||
<div class="label">Denary</div>
|
<div class="label">Denary</div>
|
||||||
<div id="denaryNumber" class="num denary">0</div>
|
<div id="denaryNumber" class="num">0</div>
|
||||||
|
|
||||||
<div class="label">Binary</div>
|
<div class="label">Binary</div>
|
||||||
<div id="binaryNumber" class="num binary">00000000</div>
|
<div id="binaryNumber" class="num">00000000</div>
|
||||||
|
|
||||||
<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>
|
||||||
@@ -27,25 +25,23 @@ import BaseLayout from "../layouts/BaseLayout.astro";
|
|||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
||||||
<!-- Bits render here (JS builds bulbs + switches) -->
|
|
||||||
<section class="bits" id="bitsGrid" aria-label="Bit switches"></section>
|
<section class="bits" id="bitsGrid" aria-label="Bit switches"></section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- RIGHT: mode + bit width -->
|
<!-- RIGHT -->
|
||||||
<aside class="panelCol">
|
<aside class="panelCol">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="cardTitle">Mode</div>
|
<div class="cardTitle">Mode</div>
|
||||||
|
|
||||||
<div class="toggleRow">
|
<div class="toggleRow">
|
||||||
<div class="toggleLabel" id="lblUnsigned">Unsigned</div>
|
<div class="toggleLabel">Unsigned</div>
|
||||||
|
|
||||||
<!-- SAME SWITCH STYLE as bit switches -->
|
|
||||||
<label class="switch" aria-label="Toggle mode">
|
<label class="switch" aria-label="Toggle mode">
|
||||||
<input id="modeToggle" type="checkbox" />
|
<input id="modeToggle" type="checkbox" />
|
||||||
<span class="slider"></span>
|
<span class="slider"></span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="toggleLabel" id="lblTwos">Two’s complement</div>
|
<div class="toggleLabel">Two’s complement</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hint" id="modeHint">
|
<div class="hint" id="modeHint">
|
||||||
@@ -77,12 +73,11 @@ import BaseLayout from "../layouts/BaseLayout.astro";
|
|||||||
<button class="miniBtn" id="btnBitsUp" type="button" aria-label="Increase bits">+</button>
|
<button class="miniBtn" id="btnBitsUp" type="button" aria-label="Increase bits">+</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="hint">Minimum 4 bits, maximum 64 bits. Display wraps every 8 bits.</div>
|
<div class="hint">Minimum 4 bits, maximum 64 bits.</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</div>
|
||||||
|
|
||||||
<!-- Page-specific JS -->
|
|
||||||
<script type="module" src="/scripts/binary.js"></script>
|
<script type="module" src="/scripts/binary.js"></script>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|||||||
@@ -1,68 +1,274 @@
|
|||||||
.binary-container {
|
:root{
|
||||||
max-width: 1100px;
|
--bg:#1f2027;
|
||||||
margin: auto;
|
--panel2: rgba(255,255,255,.04);
|
||||||
padding: 2rem;
|
--text:#e8e8ee;
|
||||||
color: #e0e0e0;
|
--muted:#a9acb8;
|
||||||
|
--accent:#33ff7a;
|
||||||
|
--accent-dim: rgba(51,255,122,.15);
|
||||||
|
--line: rgba(255,255,255,.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.display {
|
@font-face{
|
||||||
text-align: center;
|
font-family:"DSEG7ClassicRegular";
|
||||||
|
src:
|
||||||
|
url("/fonts/DSEG7Classic-Regular.woff") format("woff"),
|
||||||
|
url("/fonts/DSEG7Classic-Regular.ttf") format("truetype");
|
||||||
|
font-weight:400;
|
||||||
|
font-style:normal;
|
||||||
|
font-display:swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.wrap{
|
||||||
font-size: 1.2rem;
|
max-width:1200px;
|
||||||
opacity: 0.7;
|
margin:0 auto;
|
||||||
|
padding:32px 20px 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.number {
|
.topGrid{
|
||||||
font-size: 3rem;
|
display:grid;
|
||||||
margin-bottom: 1rem;
|
grid-template-columns: 1fr 340px;
|
||||||
color: #00ff66;
|
gap:28px;
|
||||||
|
align-items:start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.readout{
|
||||||
margin-top: 1rem;
|
text-align:center;
|
||||||
|
padding:10px 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls button {
|
.label{
|
||||||
margin: 0.25rem;
|
letter-spacing:.18em;
|
||||||
padding: 0.6rem 1rem;
|
font-weight:800;
|
||||||
font-size: 1rem;
|
color:var(--muted);
|
||||||
|
text-transform:uppercase;
|
||||||
|
font-size:14px;
|
||||||
|
margin-top:10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bits {
|
.num{
|
||||||
display: grid;
|
font-family:"DSEG7ClassicRegular", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||||
grid-template-columns: repeat(8, 1fr);
|
font-weight:400;
|
||||||
gap: 1rem;
|
color:var(--accent);
|
||||||
margin-top: 3rem;
|
text-shadow: 0 0 18px var(--accent-dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bit {
|
#denaryNumber{
|
||||||
width: 40px;
|
font-size:84px;
|
||||||
height: 80px;
|
line-height:1.0;
|
||||||
background: #333;
|
margin:6px 0 16px;
|
||||||
border-radius: 20px;
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bit::after {
|
#binaryNumber{
|
||||||
content: "";
|
font-size:62px;
|
||||||
width: 32px;
|
letter-spacing:.12em;
|
||||||
height: 32px;
|
line-height:1.0;
|
||||||
background: #555;
|
margin:6px 0 18px;
|
||||||
border-radius: 50%;
|
word-break:break-word;
|
||||||
position: absolute;
|
|
||||||
bottom: 6px;
|
|
||||||
left: 4px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bit.on {
|
.controls{
|
||||||
background: #00c853;
|
margin-top:10px;
|
||||||
|
display:flex;
|
||||||
|
gap:12px;
|
||||||
|
justify-content:center;
|
||||||
|
flex-wrap:wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bit.on::after {
|
.btn{
|
||||||
bottom: 42px;
|
background:rgba(255,255,255,.06);
|
||||||
background: #eaffea;
|
border:1px solid rgba(255,255,255,.14);
|
||||||
|
color:#fff;
|
||||||
|
padding:12px 14px;
|
||||||
|
border-radius:12px;
|
||||||
|
font-weight:800;
|
||||||
|
cursor:pointer;
|
||||||
|
min-width:160px;
|
||||||
|
}
|
||||||
|
.btn:active{ transform:translateY(1px); }
|
||||||
|
|
||||||
|
.divider{
|
||||||
|
margin-top:26px;
|
||||||
|
border-top:1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Right panels */
|
||||||
|
.panelCol{ display:flex; flex-direction:column; gap:14px; }
|
||||||
|
.card{
|
||||||
|
background:var(--panel2);
|
||||||
|
border:1px solid rgba(255,255,255,.10);
|
||||||
|
border-radius:14px;
|
||||||
|
padding:14px;
|
||||||
|
}
|
||||||
|
.cardTitle{
|
||||||
|
letter-spacing:.18em;
|
||||||
|
font-weight:900;
|
||||||
|
color:var(--muted);
|
||||||
|
text-transform:uppercase;
|
||||||
|
font-size:12px;
|
||||||
|
margin:0 0 10px;
|
||||||
|
}
|
||||||
|
.hint{
|
||||||
|
color:var(--muted);
|
||||||
|
font-size:12px;
|
||||||
|
margin-top:8px;
|
||||||
|
line-height:1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toggle switch (used for Mode AND each bit) */
|
||||||
|
.toggleRow{
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:space-between;
|
||||||
|
gap:10px;
|
||||||
|
}
|
||||||
|
.toggleLabel{
|
||||||
|
color:var(--text);
|
||||||
|
font-weight:800;
|
||||||
|
font-size:14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch{
|
||||||
|
position:relative;
|
||||||
|
width:56px;
|
||||||
|
height:34px;
|
||||||
|
display:inline-block;
|
||||||
|
flex:0 0 auto;
|
||||||
|
}
|
||||||
|
.switch input{
|
||||||
|
opacity:0;
|
||||||
|
width:0;
|
||||||
|
height:0;
|
||||||
|
position:absolute;
|
||||||
|
}
|
||||||
|
.slider{
|
||||||
|
position:absolute;
|
||||||
|
inset:0;
|
||||||
|
background:rgba(255,255,255,.10);
|
||||||
|
border:1px solid rgba(255,255,255,.14);
|
||||||
|
border-radius:999px;
|
||||||
|
transition:.18s ease;
|
||||||
|
}
|
||||||
|
.slider::before{
|
||||||
|
content:"";
|
||||||
|
position:absolute;
|
||||||
|
height:28px;
|
||||||
|
width:28px;
|
||||||
|
left:3px;
|
||||||
|
top:2px;
|
||||||
|
background:rgba(255,255,255,.92);
|
||||||
|
border-radius:50%;
|
||||||
|
transition:.18s ease;
|
||||||
|
}
|
||||||
|
.switch input:checked + .slider{
|
||||||
|
background:rgba(51,255,122,.20);
|
||||||
|
border-color:rgba(51,255,122,.55);
|
||||||
|
}
|
||||||
|
.switch input:checked + .slider::before{
|
||||||
|
transform:translateX(22px);
|
||||||
|
background:var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bit width control */
|
||||||
|
.bitWidthRow{
|
||||||
|
display:grid;
|
||||||
|
grid-template-columns:44px 1fr 44px;
|
||||||
|
gap:10px;
|
||||||
|
align-items:center;
|
||||||
|
}
|
||||||
|
.miniBtn{
|
||||||
|
height:44px;
|
||||||
|
width:44px;
|
||||||
|
border-radius:12px;
|
||||||
|
background:rgba(255,255,255,.06);
|
||||||
|
border:1px solid rgba(255,255,255,.14);
|
||||||
|
color:#fff;
|
||||||
|
cursor:pointer;
|
||||||
|
font-weight:900;
|
||||||
|
font-size:18px;
|
||||||
|
}
|
||||||
|
.bitInputWrap{
|
||||||
|
background:rgba(255,255,255,.06);
|
||||||
|
border:1px solid rgba(255,255,255,.14);
|
||||||
|
border-radius:12px;
|
||||||
|
padding:10px 12px;
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:space-between;
|
||||||
|
gap:12px;
|
||||||
|
}
|
||||||
|
.bitInputLabel{
|
||||||
|
color:var(--muted);
|
||||||
|
font-size:12px;
|
||||||
|
font-weight:900;
|
||||||
|
letter-spacing:.18em;
|
||||||
|
text-transform:uppercase;
|
||||||
|
}
|
||||||
|
.bitInput{
|
||||||
|
width:86px;
|
||||||
|
text-align:right;
|
||||||
|
background:transparent;
|
||||||
|
border:none;
|
||||||
|
outline:none;
|
||||||
|
color:var(--accent);
|
||||||
|
font-family:"DSEG7ClassicRegular", ui-monospace, monospace;
|
||||||
|
font-size:28px;
|
||||||
|
}
|
||||||
|
.bitInput::-webkit-outer-spin-button,
|
||||||
|
.bitInput::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }
|
||||||
|
|
||||||
|
/* Bits grid — force rows of 8, NO horizontal scrollbar */
|
||||||
|
.bits{
|
||||||
|
margin-top:26px;
|
||||||
|
padding-top:22px;
|
||||||
|
display:grid;
|
||||||
|
grid-template-columns: repeat(8, minmax(90px, 1fr));
|
||||||
|
gap:18px;
|
||||||
|
align-items:end;
|
||||||
|
text-align:center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Each bit */
|
||||||
|
.bit{
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
align-items:center;
|
||||||
|
gap:10px;
|
||||||
|
padding:8px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 💡 bulb */
|
||||||
|
.bulb{
|
||||||
|
font-size:22px;
|
||||||
|
line-height:1;
|
||||||
|
opacity:.18;
|
||||||
|
filter: grayscale(100%);
|
||||||
|
transform: translateY(2px);
|
||||||
|
user-select:none;
|
||||||
|
}
|
||||||
|
.bulb.on{
|
||||||
|
opacity:1;
|
||||||
|
filter: none;
|
||||||
|
text-shadow: 0 0 14px rgba(255,216,107,.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bitVal{
|
||||||
|
font-family:"DSEG7ClassicRegular", ui-monospace, monospace;
|
||||||
|
font-size:30px;
|
||||||
|
color:var(--text);
|
||||||
|
opacity:.95;
|
||||||
|
line-height:1;
|
||||||
|
min-height:34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 980px){
|
||||||
|
.topGrid{ grid-template-columns: 1fr; }
|
||||||
|
#denaryNumber{ font-size:72px; }
|
||||||
|
#binaryNumber{ font-size:52px; }
|
||||||
|
.bits{ grid-template-columns: repeat(4, minmax(90px, 1fr)); } /* still neat on phones */
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 520px){
|
||||||
|
#denaryNumber{ font-size:62px; }
|
||||||
|
#binaryNumber{ font-size:44px; }
|
||||||
|
.btn{ min-width:140px; }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user