+ Astro Basics
To get started, open the src/pages
directory in your project.
Read our docs Join our Discord What's New in Astro 5.0?
From content layers to server islands, click to learn more about the new features and
diff --git a/dist/js/binary/unsigned-binary.js b/dist/js/binary/unsigned-binary.js
new file mode 100644
index 0000000..e28ab9f
--- /dev/null
+++ b/dist/js/binary/unsigned-binary.js
@@ -0,0 +1,115 @@
+// Browser-only script. Safe because it's loaded via
+
+
diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro
index 022fd45..6fa4a20 100644
--- a/src/layouts/BaseLayout.astro
+++ b/src/layouts/BaseLayout.astro
@@ -1,4 +1,6 @@
---
+import '../styles/global.css';
+
const { title = "Computing:Box" } = Astro.props;
---
@@ -8,105 +10,25 @@ const { title = "Computing:Box" } = Astro.props;
{title}
-
-
+
+
+
+
-
@@ -114,5 +36,12 @@ const { title = "Computing:Box" } = Astro.props;
+
+
-
+
\ No newline at end of file
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro
deleted file mode 100644
index e455c61..0000000
--- a/src/layouts/Layout.astro
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
- Astro Basics
-
-
-
-
-
-
-
diff --git a/src/pages/binary.astro b/src/pages/binary.astro
index e585a28..0caf8dc 100644
--- a/src/pages/binary.astro
+++ b/src/pages/binary.astro
@@ -1,24 +1,27 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
-import "../styles/binary.css";
---
-
-
+
+
+
-
-
-
+
Denary
0
Binary
- 0000 0000
+ 00000000
@@ -28,9 +31,7 @@ import "../styles/binary.css";
-
-
-
+
Settings
@@ -40,7 +41,7 @@ import "../styles/binary.css";
- Two’s complement
+ Two's complement
@@ -51,7 +52,6 @@ import "../styles/binary.css";
Bit width
-
Bits
-
-
- Custom
-
-
-
-
+ Custom Number
+
+
+
-
-
-
+
Random runs briefly then stops automatically.
-
Tools
-
-
-
-
+
+
+
-
-
-
-
+
+
+
-
-
+
-
+
-
-
+
+
\ No newline at end of file
diff --git a/src/pages/hex-colours.astro b/src/pages/hex-colours.astro
new file mode 100644
index 0000000..d1e7e40
--- /dev/null
+++ b/src/pages/hex-colours.astro
@@ -0,0 +1,99 @@
+---
+import BaseLayout from "../layouts/BaseLayout.astro";
+---
+
+
+
+
+
+
+
+
+
+
+
+ Denary (R, G, B)
+
+ 0
+ 0
+ 0
+
+
+
+
+ Hexadecimal
+
+ #00
+ 00
+ 00
+
+
+
+
+ Binary
+
+ 00000000
+ 00000000
+ 00000000
+
+
+
+
+
+
+
+ Colour
+
+
+
+ Inverted
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Info
+
+ Hexadecimal colours are made of 3 channels (Red, Green, Blue). Each channel is an 8-bit value ranging from 00 to FF (0 to 255).
+
+
+
+
+ Colour Tools
+
+
+
+
+
+
+
+
+ Random runs briefly then stops automatically.
+
+
+
+ Tools
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pages/hexadecimal.astro b/src/pages/hexadecimal.astro
index edfe098..e74ca17 100644
--- a/src/pages/hexadecimal.astro
+++ b/src/pages/hexadecimal.astro
@@ -1,8 +1,94 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
-import HexSimulator from "../components/simulators/HexSimulator.astro";
---
-
-
-
+
+
+
+
+
+
+
+ Denary
+ 0
+
+ Hexadecimal
+ 00
+
+ Binary
+ 00000000
+
+
+
+
+
+
+
+
+
+
+
+ Settings
+
+
+ Hexadecimal represents numbers using base 16 (0-9, A-F).
+
+
+
+ Digit width
+
+
+
+ Digits
+
+
+
+
+
+
+
+
+ Custom Number
+
+
+
+
+
+
+
+
+ Random runs briefly then stops automatically.
+
+
+
+ Tools
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pages/logic-gates.astro b/src/pages/logic-gates.astro
new file mode 100644
index 0000000..7eb4c54
--- /dev/null
+++ b/src/pages/logic-gates.astro
@@ -0,0 +1,63 @@
+---
+import BaseLayout from "../layouts/BaseLayout.astro";
+import "../styles/logic-gates.css";
+---
+
+
+
+
+
+
+
+
+
+
+ Interactive Simulator
+ LOGIC GATES
+
+
+ Drag items from the toolbox to the board. Drag from output ports to input ports to wire. Click a wire or node and press Delete to remove it.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Components
+
+
+
+
+
+
+ Live Truth Table
+
+ Show / Hide Table
+ Auto-generates based on current wiring. (Max 6 inputs)
+
+
+
+
+
+
+ Tools
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/scripts/binary.js b/src/scripts/binary.js
index a557ff9..e596092 100644
--- a/src/scripts/binary.js
+++ b/src/scripts/binary.js
@@ -1,6 +1,3 @@
-// src/scripts/binary.js
-// Computing:Box — Binary page logic (Unsigned + Two's Complement)
-
(() => {
/* -----------------------------
DOM
@@ -12,6 +9,10 @@
const modeToggle = document.getElementById("modeToggle");
const modeHint = document.getElementById("modeHint");
+
+ // Connect the text labels to the JS
+ const lblUnsigned = document.getElementById("lblUnsigned");
+ const lblTwos = document.getElementById("lblTwos");
const btnCustomBinary = document.getElementById("btnCustomBinary");
const btnCustomDenary = document.getElementById("btnCustomDenary");
@@ -27,14 +28,13 @@
const btnBitsDown = document.getElementById("btnBitsDown");
const toolboxToggle = document.getElementById("toolboxToggle");
- const toolboxPanel = document.getElementById("toolboxPanel");
+ const binaryPage = document.getElementById("binaryPage");
/* -----------------------------
STATE
----------------------------- */
let bitCount = clampInt(Number(bitsInput?.value ?? 8), 1, 64);
let bits = new Array(bitCount).fill(false);
-
let randomTimer = null;
/* -----------------------------
@@ -54,7 +54,7 @@
}
function unsignedMaxExclusive(nBits) {
- return pow2Big(nBits);
+ return pow2Big(nBits);
}
function unsignedMaxValue(nBits) {
@@ -94,7 +94,8 @@
function signedBigIntToBitsTwos(vSigned) {
const span = pow2Big(bitCount);
- let v = ((vSigned % span) + span) % span;
+ let v = vSigned;
+ v = ((v % span) + span) % span;
unsignedBigIntToBits(v);
}
@@ -102,17 +103,33 @@
let s = "";
for (let i = bitCount - 1; i >= 0; i--) {
s += bits[i] ? "1" : "0";
- const posFromRight = (bitCount - i);
- if (i !== 0 && posFromRight % 4 === 0) s += " ";
+ const posFromLeft = (bitCount - i);
+ if (i !== 0 && posFromLeft % 4 === 0) s += " ";
}
- return s;
+ return s.trimEnd();
}
function updateModeHint() {
if (!modeHint) return;
- modeHint.textContent = isTwosMode()
- ? "Tip: In two’s complement, the left-most bit (MSB) represents a negative value."
- : "Tip: In unsigned binary, all bits represent positive values.";
+ if (isTwosMode()) {
+ 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.";
+ }
+ }
+
+ /* -----------------------------
+ RESPONSIVE GRID COLS
+ ----------------------------- */
+ function computeColsForBitsGrid() {
+ if (!bitsGrid) return;
+ const wrap = bitsGrid.parentElement;
+ if (!wrap) return;
+
+ const width = wrap.getBoundingClientRect().width;
+ const minCell = 100;
+ const cols = clampInt(Math.floor(width / minCell), 1, 12);
+ bitsGrid.style.setProperty("--cols", String(Math.min(cols, bitCount)));
}
/* -----------------------------
@@ -127,18 +144,25 @@
for (let i = 0; i < Math.min(oldBits.length, bitCount); i++) bits[i] = oldBits[i];
bitsGrid.innerHTML = "";
+ bitsGrid.classList.toggle("bitsFew", bitCount < 8);
for (let i = bitCount - 1; i >= 0; i--) {
const bitEl = document.createElement("div");
bitEl.className = "bit";
+
bitEl.innerHTML = `
-
+
`;
+
bitsGrid.appendChild(bitEl);
}
@@ -150,6 +174,7 @@
});
});
+ computeColsForBitsGrid();
updateUI();
}
@@ -161,14 +186,14 @@
const label = document.getElementById(`bitLabel-${i}`);
if (!label) continue;
- // Keep label on ONE LINE (no wrapping)
- label.style.whiteSpace = "nowrap";
-
+ let valStr;
if (isTwosMode() && i === bitCount - 1) {
- label.textContent = `-${pow2Big(bitCount - 1).toString()}`;
+ valStr = `-${pow2Big(bitCount - 1).toString()}`;
} else {
- label.textContent = pow2Big(i).toString();
+ valStr = pow2Big(i).toString();
}
+ label.textContent = valStr;
+ label.style.setProperty('--len', valStr.length);
}
}
@@ -189,12 +214,23 @@
function updateReadout() {
if (!denaryEl || !binaryEl) return;
- denaryEl.textContent = (isTwosMode() ? bitsToSignedBigIntTwos() : bitsToUnsignedBigInt()).toString();
+ if (isTwosMode()) {
+ denaryEl.textContent = bitsToSignedBigIntTwos().toString();
+ } else {
+ denaryEl.textContent = bitsToUnsignedBigInt().toString();
+ }
binaryEl.textContent = formatBinaryGrouped();
}
function updateUI() {
updateModeHint();
+
+ // Toggle the glowing CSS class on the active mode text
+ if (lblUnsigned && lblTwos) {
+ lblUnsigned.classList.toggle("activeMode", !isTwosMode());
+ lblTwos.classList.toggle("activeMode", isTwosMode());
+ }
+
updateBitLabels();
syncSwitchesToBits();
updateBulbs();
@@ -202,20 +238,25 @@
}
/* -----------------------------
- INPUT SETTERS
+ SET FROM BINARY STRING
----------------------------- */
function setFromBinaryString(binStr) {
const clean = String(binStr ?? "").replace(/\s+/g, "");
if (!/^[01]+$/.test(clean)) return false;
+
const padded = clean.slice(-bitCount).padStart(bitCount, "0");
for (let i = 0; i < bitCount; i++) {
const charFromRight = padded[padded.length - 1 - i];
bits[i] = charFromRight === "1";
}
+
updateUI();
return true;
}
+ /* -----------------------------
+ SET FROM DENARY INPUT
+ ----------------------------- */
function setFromDenaryInput(vStr) {
const raw = String(vStr ?? "").trim();
if (!raw) return false;
@@ -224,9 +265,7 @@
try {
if (!/^-?\d+$/.test(raw)) return false;
v = BigInt(raw);
- } catch {
- return false;
- }
+ } catch { return false; }
if (isTwosMode()) {
const min = twosMin(bitCount);
@@ -234,8 +273,7 @@
if (v < min || v > max) return false;
signedBigIntToBitsTwos(v);
} else {
- if (v < 0n) return false;
- if (v > unsignedMaxValue(bitCount)) return false;
+ if (v < 0n || v > unsignedMaxValue(bitCount)) return false;
unsignedBigIntToBits(v);
}
@@ -247,18 +285,14 @@
SHIFTS
----------------------------- */
function shiftLeft() {
- for (let i = bitCount - 1; i >= 1; i--) bits[i] = bits[i - 1];
+ for (let i = bitCount - 1; i >= 1; i--) { bits[i] = bits[i - 1]; }
bits[0] = false;
updateUI();
}
function shiftRight() {
- // Unsigned: logical right shift (MSB becomes 0)
- // Two's complement: arithmetic right shift (MSB preserved)
const msb = bits[bitCount - 1];
-
- for (let i = 0; i < bitCount - 1; i++) bits[i] = bits[i + 1];
-
+ for (let i = 0; i < bitCount - 1; i++) { bits[i] = bits[i + 1]; }
bits[bitCount - 1] = isTwosMode() ? msb : false;
updateUI();
}
@@ -267,8 +301,9 @@
CLEAR / INC / DEC
----------------------------- */
function clearAll() {
- bits.fill(false);
- updateUI();
+ bits = [];
+ if (modeToggle) modeToggle.checked = false;
+ buildBits(8);
}
function increment() {
@@ -280,8 +315,7 @@
signedBigIntToBitsTwos(v);
} else {
const span = unsignedMaxExclusive(bitCount);
- const v = (bitsToUnsignedBigInt() + 1n) % span;
- unsignedBigIntToBits(v);
+ unsignedBigIntToBits((bitsToUnsignedBigInt() + 1n) % span);
}
updateUI();
}
@@ -295,25 +329,22 @@
signedBigIntToBitsTwos(v);
} else {
const span = unsignedMaxExclusive(bitCount);
- const v = (bitsToUnsignedBigInt() - 1n + span) % span;
- unsignedBigIntToBits(v);
+ unsignedBigIntToBits((bitsToUnsignedBigInt() - 1n + span) % span);
}
updateUI();
}
/* -----------------------------
- RANDOM (with running pulse + longer run)
+ RANDOM
----------------------------- */
function cryptoRandomBigInt(maxExclusive) {
if (maxExclusive <= 0n) return 0n;
-
const bitLen = maxExclusive.toString(2).length;
const byteLen = Math.ceil(bitLen / 8);
while (true) {
const bytes = new Uint8Array(byteLen);
crypto.getRandomValues(bytes);
-
let x = 0n;
for (const b of bytes) x = (x << 8n) | BigInt(b);
@@ -325,23 +356,26 @@
}
function setRandomOnce() {
- const span = unsignedMaxExclusive(bitCount); // 2^n
+ const span = unsignedMaxExclusive(bitCount);
const u = cryptoRandomBigInt(span);
unsignedBigIntToBits(u);
updateUI();
}
+ function setRandomRunning(isRunning) {
+ if (!btnRandom) return;
+ btnRandom.classList.toggle("btnRandomRunning", !!isRunning);
+ }
+
function runRandomBriefly() {
if (randomTimer) {
clearInterval(randomTimer);
randomTimer = null;
}
- // pulse while running
- btnRandom?.classList.add("is-running");
-
+ setRandomRunning(true);
const start = Date.now();
- const durationMs = 1125; // 25% longer than 900ms
+ const durationMs = 1125;
const tickMs = 80;
randomTimer = setInterval(() => {
@@ -349,25 +383,27 @@
if (Date.now() - start >= durationMs) {
clearInterval(randomTimer);
randomTimer = null;
- btnRandom?.classList.remove("is-running");
+ setRandomRunning(false);
}
}, tickMs);
}
/* -----------------------------
- BIT WIDTH
+ BIT WIDTH CONTROLS
----------------------------- */
function setBitWidth(n) {
- buildBits(clampInt(n, 1, 64));
+ const v = clampInt(n, 1, 64);
+ buildBits(v);
}
/* -----------------------------
- TOOLBOX VISIBILITY
+ TOOLBOX TOGGLE
----------------------------- */
- function setToolboxVisible(isVisible) {
- if (!toolboxPanel) return;
- toolboxPanel.style.display = isVisible ? "flex" : "none";
- toolboxToggle?.setAttribute("aria-expanded", String(isVisible));
+ function setToolboxCollapsed(collapsed) {
+ if (!binaryPage) return;
+ binaryPage.classList.toggle("toolboxCollapsed", !!collapsed);
+ const expanded = !collapsed;
+ toolboxToggle?.setAttribute("aria-expanded", expanded ? "true" : "false");
}
/* -----------------------------
@@ -384,8 +420,8 @@
btnCustomDenary?.addEventListener("click", () => {
const v = prompt(
isTwosMode()
- ? `Enter denary (${twosMin(bitCount)} to ${twosMax(bitCount)}):`
- : `Enter denary (0 to ${unsignedMaxValue(bitCount)}):`
+ ? `Enter denary (${twosMin(bitCount).toString()} to ${twosMax(bitCount).toString()}):`
+ : `Enter denary (0 to ${unsignedMaxValue(bitCount).toString()}):`
);
if (v === null) return;
if (!setFromDenaryInput(v)) alert("Invalid denary for current mode/bit width");
@@ -406,8 +442,12 @@
bitsInput?.addEventListener("change", () => setBitWidth(Number(bitsInput.value)));
toolboxToggle?.addEventListener("click", () => {
- const isOpen = toolboxToggle.getAttribute("aria-expanded") !== "false";
- setToolboxVisible(!isOpen);
+ const isCollapsed = binaryPage?.classList.contains("toolboxCollapsed");
+ setToolboxCollapsed(!isCollapsed);
+ });
+
+ window.addEventListener("resize", () => {
+ computeColsForBitsGrid();
});
/* -----------------------------
@@ -415,5 +455,5 @@
----------------------------- */
updateModeHint();
buildBits(bitCount);
- setToolboxVisible(true);
-})();
+ setToolboxCollapsed(false);
+})();
\ No newline at end of file
diff --git a/src/scripts/hexColours.js b/src/scripts/hexColours.js
new file mode 100644
index 0000000..e64be80
--- /dev/null
+++ b/src/scripts/hexColours.js
@@ -0,0 +1,241 @@
+// src/scripts/hexColours.js
+// Computing:Box — Hex Colours logic
+
+(() => {
+ /* -----------------------------
+ DOM
+ ----------------------------- */
+ const colorGrid = document.getElementById("colorGrid");
+ const denaryEl = document.getElementById("denaryNumber");
+ const binaryEl = document.getElementById("binaryNumber");
+ const hexEl = document.getElementById("hexNumber");
+ const previewColor = document.getElementById("previewColor");
+ const previewInverted = document.getElementById("previewInverted");
+
+ const btnCustomHex = document.getElementById("btnCustomHex");
+ const btnCustomRGB = document.getElementById("btnCustomRGB");
+ const btnInvert = document.getElementById("btnInvert");
+ const btnRandom = document.getElementById("btnRandom");
+ const btnClear = document.getElementById("btnClear");
+
+ const toolboxToggle = document.getElementById("toolboxToggle");
+ const colorPage = document.getElementById("colorPage");
+
+ /* -----------------------------
+ STATE
+ ----------------------------- */
+ // rgb[0]=Red, rgb[1]=Green, rgb[2]=Blue (Values 0-255)
+ let rgb = [0, 0, 0];
+ let randomTimer = null;
+
+ /* -----------------------------
+ BUILD UI
+ ----------------------------- */
+ function buildGrid() {
+ if (!colorGrid) return;
+ colorGrid.innerHTML = "";
+
+ const colorClasses = ['text-red', 'text-green', 'text-blue'];
+
+ for (let c = 0; c < 3; c++) {
+ const group = document.createElement("div");
+ group.className = "colorGroup";
+
+ for (let i = 1; i >= 0; i--) {
+ const col = document.createElement("div");
+ col.className = "hexCol";
+
+ let cardHTML = `
+
+
+
+
+
+ 0
+
+ `;
+
+ for (let j = 3; j >= 0; j--) {
+ cardHTML += `
+
+
+ ${1 << j}
+
+ `;
+ }
+
+ cardHTML += `
+
+
+ ${16 ** i}
+ `;
+
+ col.innerHTML = cardHTML;
+
+ const incBtn = col.querySelector(`#colorInc-${c}-${i}`);
+ const decBtn = col.querySelector(`#colorDec-${c}-${i}`);
+
+ incBtn.addEventListener("click", () => {
+ const weight = 16 ** i;
+ rgb[c] = (rgb[c] + weight) % 256;
+ updateUI();
+ });
+
+ decBtn.addEventListener("click", () => {
+ const weight = 16 ** i;
+ rgb[c] = (rgb[c] - weight + 256) % 256;
+ updateUI();
+ });
+
+ group.appendChild(col);
+ }
+ colorGrid.appendChild(group);
+ }
+ }
+
+ /* -----------------------------
+ UI UPDATE
+ ----------------------------- */
+ function updateUI() {
+ if (denaryEl) {
+ denaryEl.innerHTML = `
+ ${rgb[0]}
+ ${rgb[1]}
+ ${rgb[2]}
+ `;
+ }
+
+ const hexVals = rgb.map(v => v.toString(16).padStart(2, '0').toUpperCase());
+ const fullHexString = `#${hexVals.join('')}`;
+
+ if (hexEl) {
+ hexEl.innerHTML = `
+ #${hexVals[0]}
+ ${hexVals[1]}
+ ${hexVals[2]}
+ `;
+ }
+
+ if (binaryEl) {
+ binaryEl.innerHTML = `
+ ${rgb[0].toString(2).padStart(8, '0')}
+ ${rgb[1].toString(2).padStart(8, '0')}
+ ${rgb[2].toString(2).padStart(8, '0')}
+ `;
+ }
+
+ if (previewColor) previewColor.style.backgroundColor = fullHexString;
+
+ const invertedHexString = "#" + rgb.map(v => (255 - v).toString(16).padStart(2, '0').toUpperCase()).join('');
+ if (previewInverted) previewInverted.style.backgroundColor = invertedHexString;
+
+ for (let c = 0; c < 3; c++) {
+ const val = rgb[c];
+ const nibbles = [val % 16, Math.floor(val / 16)];
+
+ for (let i = 0; i < 2; i++) {
+ const display = document.getElementById(`colorDisplay-${c}-${i}`);
+ if (display) display.textContent = nibbles[i].toString(16).toUpperCase();
+
+ for (let j = 0; j < 4; j++) {
+ const bulb = document.getElementById(`colorBulb-${c}-${i}-${j}`);
+ if (bulb) {
+ const isOn = (nibbles[i] & (1 << j)) !== 0;
+ bulb.classList.toggle("on", isOn);
+ }
+ }
+ }
+ }
+ }
+
+ /* -----------------------------
+ ACTIONS
+ ----------------------------- */
+ function clearAll() {
+ rgb = [0, 0, 0];
+ updateUI();
+ }
+
+ function setRandomOnce() {
+ const arr = new Uint8Array(3);
+ crypto.getRandomValues(arr);
+ rgb = [arr[0], arr[1], arr[2]];
+ updateUI();
+ }
+
+ function runRandomBriefly() {
+ if (randomTimer) {
+ clearInterval(randomTimer);
+ randomTimer = null;
+ }
+ if (btnRandom) btnRandom.classList.add("btnRandomRunning");
+
+ const start = Date.now();
+ const durationMs = 1125;
+ const tickMs = 80;
+
+ randomTimer = setInterval(() => {
+ setRandomOnce();
+ if (Date.now() - start >= durationMs) {
+ clearInterval(randomTimer);
+ randomTimer = null;
+ if (btnRandom) btnRandom.classList.remove("btnRandomRunning");
+ }
+ }, tickMs);
+ }
+
+ /* -----------------------------
+ EVENTS
+ ----------------------------- */
+ btnCustomHex?.addEventListener("click", () => {
+ let v = prompt("Enter a 6-character hex code (e.g. FF0055):");
+ if (v === null) return;
+ v = v.replace(/\s+/g, "").replace(/^#/i, "").toUpperCase();
+
+ if (!/^[0-9A-F]{6}$/.test(v)) return alert("Invalid hex code. Please enter exactly 6 hexadecimal characters.");
+
+ rgb = [
+ parseInt(v.substring(0, 2), 16),
+ parseInt(v.substring(2, 4), 16),
+ parseInt(v.substring(4, 6), 16)
+ ];
+ updateUI();
+ });
+
+ btnCustomRGB?.addEventListener("click", () => {
+ const v = prompt("Enter R, G, B values (0-255) separated by commas (e.g. 255, 128, 0):");
+ if (v === null) return;
+
+ const parts = v.split(',').map(s => parseInt(s.trim(), 10));
+ if (parts.length !== 3 || parts.some(isNaN) || parts.some(n => n < 0 || n > 255)) {
+ return alert("Invalid input. Please provide three numbers between 0 and 255.");
+ }
+
+ rgb = parts;
+ updateUI();
+ });
+
+ btnInvert?.addEventListener("click", () => {
+ rgb = rgb.map(v => 255 - v);
+ updateUI();
+ });
+
+ btnClear?.addEventListener("click", clearAll);
+ btnRandom?.addEventListener("click", runRandomBriefly);
+
+ toolboxToggle?.addEventListener("click", () => {
+ const isCollapsed = colorPage?.classList.contains("toolboxCollapsed");
+ colorPage.classList.toggle("toolboxCollapsed", !isCollapsed);
+ toolboxToggle?.setAttribute("aria-expanded", isCollapsed ? "true" : "false");
+ });
+
+ /* -----------------------------
+ INIT
+ ----------------------------- */
+ buildGrid();
+ updateUI();
+})();
\ No newline at end of file
diff --git a/src/scripts/hexadecimal.js b/src/scripts/hexadecimal.js
new file mode 100644
index 0000000..8b79df1
--- /dev/null
+++ b/src/scripts/hexadecimal.js
@@ -0,0 +1,312 @@
+// src/scripts/hexadecimal.js
+// Computing:Box — Hexadecimal page logic
+
+(() => {
+ /* -----------------------------
+ DOM
+ ----------------------------- */
+ const hexGrid = document.getElementById("hexGrid");
+ const denaryEl = document.getElementById("denaryNumber");
+ const binaryEl = document.getElementById("binaryNumber");
+ const hexEl = document.getElementById("hexNumber");
+ const digitsInput = document.getElementById("digitsInput");
+
+ const btnCustomBinary = document.getElementById("btnCustomBinary");
+ const btnCustomDenary = document.getElementById("btnCustomDenary");
+ const btnCustomHex = document.getElementById("btnCustomHex");
+
+ const btnDec = document.getElementById("btnDec");
+ const btnInc = document.getElementById("btnInc");
+ const btnClear = document.getElementById("btnClear");
+ const btnRandom = document.getElementById("btnRandom");
+
+ const btnDigitsUp = document.getElementById("btnDigitsUp");
+ const btnDigitsDown = document.getElementById("btnDigitsDown");
+
+ const toolboxToggle = document.getElementById("toolboxToggle");
+ const hexPage = document.getElementById("hexPage");
+
+ /* -----------------------------
+ STATE
+ ----------------------------- */
+ let hexCount = clampInt(Number(digitsInput?.value ?? 2), 1, 16);
+ let nibbles = new Array(hexCount).fill(0);
+ let randomTimer = null;
+
+ /* -----------------------------
+ HELPERS
+ ----------------------------- */
+ function clampInt(n, min, max) {
+ if (!Number.isFinite(n)) return min;
+ return Math.max(min, Math.min(max, Math.trunc(n)));
+ }
+
+ function maxExclusive() {
+ return 1n << BigInt(hexCount * 4);
+ }
+
+ function maxValue() {
+ return maxExclusive() - 1n;
+ }
+
+ function getValue() {
+ let v = 0n;
+ for (let i = 0; i < hexCount; i++) {
+ v += BigInt(nibbles[i]) << BigInt(i * 4);
+ }
+ return v;
+ }
+
+ function setValue(v) {
+ if (v < 0n) return false;
+ if (v > maxValue()) return false;
+
+ for (let i = 0; i < hexCount; i++) {
+ nibbles[i] = Number((v >> BigInt(i * 4)) & 0xFn);
+ }
+ return true;
+ }
+
+ /* -----------------------------
+ RESPONSIVE GRID
+ ----------------------------- */
+ function computeColsForHexGrid() {
+ if (!hexGrid) return;
+ hexGrid.style.setProperty("--cols", String(Math.min(hexCount, 8)));
+ hexGrid.classList.toggle("bitsFew", hexCount < 4);
+ }
+
+ /* -----------------------------
+ BUILD UI (CARDS + BULBS)
+ ----------------------------- */
+ function buildGrid(count) {
+ hexCount = clampInt(count, 1, 16);
+ if (digitsInput) digitsInput.value = String(hexCount);
+
+ const oldNibbles = nibbles.slice();
+ nibbles = new Array(hexCount).fill(0);
+ for (let i = 0; i < Math.min(oldNibbles.length, hexCount); i++) {
+ nibbles[i] = oldNibbles[i];
+ }
+
+ hexGrid.innerHTML = "";
+
+ for (let i = hexCount - 1; i >= 0; i--) {
+ const col = document.createElement("div");
+ col.className = "hexCol";
+
+ let cardHTML = `
+
+
+
+
+
+ 0
+
+ `;
+
+ for(let j = 3; j >= 0; j--) {
+ cardHTML += `
+
+
+ ${1 << j}
+
+ `;
+ }
+
+ cardHTML += `
+
+
+ ${(1n << BigInt(i * 4)).toString()}
+ `;
+
+ col.innerHTML = cardHTML;
+
+ const incBtn = col.querySelector(`#hexInc-${i}`);
+ const decBtn = col.querySelector(`#hexDec-${i}`);
+
+ incBtn.addEventListener("click", () => {
+ const span = maxExclusive();
+ const weight = 1n << BigInt(i * 4);
+ setValue((getValue() + weight) % span);
+ updateUI();
+ });
+
+ decBtn.addEventListener("click", () => {
+ const span = maxExclusive();
+ const weight = 1n << BigInt(i * 4);
+ setValue((getValue() - weight + span) % span);
+ updateUI();
+ });
+
+ hexGrid.appendChild(col);
+ }
+
+ computeColsForHexGrid();
+ updateUI();
+ }
+
+ /* -----------------------------
+ UI UPDATE
+ ----------------------------- */
+ function updateUI() {
+ const val = getValue();
+
+ if (denaryEl) denaryEl.textContent = val.toString();
+ if (hexEl) hexEl.textContent = val.toString(16).toUpperCase().padStart(hexCount, '0');
+
+ if (binaryEl) {
+ let binStr = "";
+ for (let i = hexCount - 1; i >= 0; i--) {
+ binStr += nibbles[i].toString(2).padStart(4, '0') + " ";
+ }
+ binaryEl.textContent = binStr.trimEnd();
+ }
+
+ for (let i = 0; i < hexCount; i++) {
+ const display = document.getElementById(`hexDisplay-${i}`);
+ if (display) display.textContent = nibbles[i].toString(16).toUpperCase();
+
+ for (let j = 0; j < 4; j++) {
+ const bulb = document.getElementById(`hexBulb-${i}-${j}`);
+ if (bulb) {
+ const isOn = (nibbles[i] & (1 << j)) !== 0;
+ bulb.classList.toggle("on", isOn);
+ }
+ }
+ }
+ }
+
+ /* -----------------------------
+ CLEAR / INC / DEC
+ ----------------------------- */
+ function clearAll() {
+ nibbles.fill(0);
+ buildGrid(2);
+ }
+
+ function increment() {
+ const span = maxExclusive();
+ setValue((getValue() + 1n) % span);
+ updateUI();
+ }
+
+ function decrement() {
+ const span = maxExclusive();
+ setValue((getValue() - 1n + span) % span);
+ updateUI();
+ }
+
+ /* -----------------------------
+ RANDOM
+ ----------------------------- */
+ function cryptoRandomBigInt(maxExcl) {
+ if (maxExcl <= 0n) return 0n;
+ const bitLen = maxExcl.toString(2).length;
+ const byteLen = Math.ceil(bitLen / 8);
+
+ while (true) {
+ const bytes = new Uint8Array(byteLen);
+ crypto.getRandomValues(bytes);
+ let x = 0n;
+ for (const b of bytes) x = (x << 8n) | BigInt(b);
+
+ const extraBits = BigInt(byteLen * 8 - bitLen);
+ if (extraBits > 0n) x = x >> extraBits;
+ if (x < maxExcl) return x;
+ }
+ }
+
+ function setRandomOnce() {
+ const u = cryptoRandomBigInt(maxExclusive());
+ setValue(u);
+ updateUI();
+ }
+
+ function runRandomBriefly() {
+ if (randomTimer) {
+ clearInterval(randomTimer);
+ randomTimer = null;
+ }
+ if (btnRandom) btnRandom.classList.add("btnRandomRunning");
+
+ const start = Date.now();
+ const durationMs = 1125;
+ const tickMs = 80;
+
+ randomTimer = setInterval(() => {
+ setRandomOnce();
+ if (Date.now() - start >= durationMs) {
+ clearInterval(randomTimer);
+ randomTimer = null;
+ if (btnRandom) btnRandom.classList.remove("btnRandomRunning");
+ }
+ }, tickMs);
+ }
+
+ /* -----------------------------
+ EVENTS
+ ----------------------------- */
+ btnCustomHex?.addEventListener("click", () => {
+ const v = prompt(`Enter hexadecimal (0-9, A-F). Current width: ${hexCount} digits`);
+ if (v === null) return;
+ const clean = v.replace(/\s+/g, "").toUpperCase();
+
+ if (!/^[0-9A-F]+$/.test(clean)) return alert("Invalid hexadecimal.");
+ if (clean.length > hexCount) return alert("Value too large for current digit width.");
+
+ if (!setValue(BigInt("0x" + clean))) alert("Value out of range.");
+ else updateUI();
+ });
+
+ btnCustomBinary?.addEventListener("click", () => {
+ const v = prompt(`Enter binary (0, 1). Current width: ${hexCount * 4} bits`);
+ if (v === null) return;
+ const clean = v.replace(/\s+/g, "");
+
+ if (!/^[01]+$/.test(clean)) return alert("Invalid binary.");
+ if (clean.length > hexCount * 4) return alert("Value too large for current digit width.");
+
+ if (!setValue(BigInt("0b" + clean))) alert("Value out of range.");
+ else updateUI();
+ });
+
+ btnCustomDenary?.addEventListener("click", () => {
+ const v = prompt(`Enter denary (0 to ${maxValue().toString()}):`);
+ if (v === null) return;
+ const clean = v.trim();
+
+ if (!/^\d+$/.test(clean)) return alert("Invalid denary. Digits only.");
+ if (!setValue(BigInt(clean))) alert(`Value out of range. Enter a number between 0 and ${maxValue().toString()}.`);
+ else updateUI();
+ });
+
+ btnInc?.addEventListener("click", increment);
+ btnDec?.addEventListener("click", decrement);
+
+ btnClear?.addEventListener("click", clearAll);
+ btnRandom?.addEventListener("click", runRandomBriefly);
+
+ btnDigitsUp?.addEventListener("click", () => buildGrid(hexCount + 1));
+ btnDigitsDown?.addEventListener("click", () => buildGrid(hexCount - 1));
+
+ digitsInput?.addEventListener("change", () => buildGrid(Number(digitsInput.value)));
+
+ toolboxToggle?.addEventListener("click", () => {
+ const isCollapsed = hexPage?.classList.contains("toolboxCollapsed");
+ hexPage.classList.toggle("toolboxCollapsed", !isCollapsed);
+ toolboxToggle?.setAttribute("aria-expanded", isCollapsed ? "true" : "false");
+ });
+
+ window.addEventListener("resize", computeColsForHexGrid);
+
+ /* -----------------------------
+ INIT
+ ----------------------------- */
+ buildGrid(hexCount);
+
+})();
\ No newline at end of file
diff --git a/src/scripts/logicGates.js b/src/scripts/logicGates.js
new file mode 100644
index 0000000..a39d333
--- /dev/null
+++ b/src/scripts/logicGates.js
@@ -0,0 +1,489 @@
+// src/scripts/logicGates.js
+// Computing:Box — Drag & Drop Logic Builder
+
+(() => {
+ /* --- DOM Elements --- */
+ const workspace = document.getElementById("workspace");
+ const wireLayer = document.getElementById("wireLayer");
+ const ttContainer = document.getElementById("truthTableContainer");
+ const toolboxGrid = document.getElementById("toolboxGrid");
+
+ const btnClearBoard = document.getElementById("btnClearBoard");
+ const toolboxToggle = document.getElementById("toolboxToggle");
+ const logicPage = document.getElementById("logicPage");
+
+ /* --- ANSI Gate SVGs (Strict 100x50 with built-in tails) --- */
+ const GATE_SVGS = {
+ 'AND': ` `,
+ 'OR': ` `,
+ 'NOT': ` `,
+ 'NAND': ` `,
+ 'NOR': ` `,
+ 'XOR': ` `,
+ 'XNOR': ` `
+ };
+
+ const INPUT_SVG = ``;
+ const OUTPUT_SVG = ``;
+
+ /* --- State --- */
+ let nodes = {};
+ let connections = [];
+
+ let nextNodeId = 1;
+ let nextWireId = 1;
+ let inputCount = 0;
+ let outputCount = 0;
+
+ let isDraggingNode = null;
+ let dragOffset = { x: 0, y: 0 };
+ let clickStartX = 0, clickStartY = 0; // Fixes switch drag conflict
+
+ let wiringStart = null;
+ let tempWirePath = null;
+
+ let selectedWireId = null;
+ let selectedNodeId = null;
+
+ /* --- Setup Toolbox --- */
+ function initToolbox() {
+ if(!toolboxGrid) return;
+ let html = `
+
+
+ `;
+
+ Object.keys(GATE_SVGS).forEach(gate => {
+ html += `
+
+ `;
+ });
+
+ toolboxGrid.innerHTML = html;
+
+ document.querySelectorAll('.drag-item').forEach(item => {
+ item.addEventListener('dragstart', (e) => {
+ e.dataTransfer.setData('spawnType', item.dataset.spawn);
+ if(item.dataset.spawn === 'GATE') e.dataTransfer.setData('gateType', item.dataset.gate);
+ });
+ });
+ }
+
+ /* --- Math & Geometry --- */
+ 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 + (portRect.width / 2),
+ y: portRect.top - wsRect.top + (portRect.height / 2)
+ };
+ }
+
+ function drawBezier(x1, y1, x2, y2) {
+ const cpDist = Math.abs(x2 - x1) * 0.6 + 20;
+ return `M ${x1} ${y1} C ${x1 + cpDist} ${y1}, ${x2 - cpDist} ${y2}, ${x2} ${y2}`;
+ }
+
+ /* --- Rendering --- */
+ function renderWires() {
+ let svgHTML = '';
+
+ connections.forEach(conn => {
+ const from = getPortCoords(conn.fromNode, 'out');
+ const to = getPortCoords(conn.toNode, `in${conn.toPort}`);
+ const sourceNode = nodes[conn.fromNode];
+ const isActive = sourceNode && sourceNode.value === true;
+ const isSelected = conn.id === selectedWireId;
+
+ svgHTML += ` `;
+ });
+
+ if (wiringStart && tempWirePath) {
+ svgHTML += ` `;
+ }
+
+ wireLayer.innerHTML = svgHTML;
+ }
+
+ function updateNodePositions() {
+ Object.values(nodes).forEach(n => {
+ if (n.el) {
+ n.el.style.left = `${n.x}px`;
+ n.el.style.top = `${n.y}px`;
+ }
+ });
+ renderWires();
+ }
+
+ function clearSelection() {
+ selectedWireId = null;
+ selectedNodeId = null;
+ document.querySelectorAll('.lg-node.selected').forEach(el => el.classList.remove('selected'));
+ renderWires();
+ }
+
+ /* --- Logic Evaluation --- */
+ function evaluateGraph(overrideInputs = null) {
+ let context = {};
+
+ Object.values(nodes).filter(n => n.type === 'INPUT').forEach(n => {
+ context[n.id] = overrideInputs ? overrideInputs[n.id] : n.value;
+ });
+
+ let changed = true;
+ let loops = 0;
+
+ while (changed && loops < 10) {
+ changed = false;
+ loops++;
+
+ Object.values(nodes).filter(n => n.type === 'GATE').forEach(gate => {
+ let in1Conn = connections.find(c => c.toNode === gate.id && c.toPort === '1');
+ let in2Conn = connections.find(c => c.toNode === gate.id && c.toPort === '2');
+
+ let val1 = in1Conn ? (context[in1Conn.fromNode] || false) : false;
+ let val2 = in2Conn ? (context[in2Conn.fromNode] || false) : false;
+
+ let res = false;
+ switch(gate.gateType) {
+ case 'AND': res = val1 && val2; break;
+ case 'OR': res = val1 || val2; break;
+ case 'NOT': res = !val1; break;
+ case 'NAND': res = !(val1 && val2); break;
+ case 'NOR': res = !(val1 || val2); break;
+ case 'XOR': res = val1 !== val2; break;
+ case 'XNOR': res = val1 === val2; break;
+ }
+
+ if (context[gate.id] !== res) {
+ context[gate.id] = res;
+ changed = true;
+ }
+ });
+ }
+
+ let outStates = {};
+ Object.values(nodes).filter(n => n.type === 'OUTPUT').forEach(out => {
+ let conn = connections.find(c => c.toNode === out.id);
+ let res = conn ? (context[conn.fromNode] || false) : false;
+ outStates[out.id] = res;
+ });
+
+ if (!overrideInputs) {
+ Object.values(nodes).forEach(n => {
+ if (n.type === 'GATE') n.value = context[n.id] || false;
+ if (n.type === 'OUTPUT') {
+ n.value = outStates[n.id] || false;
+ const bulb = n.el.querySelector('.bulb');
+ if (bulb) bulb.classList.toggle('on', n.value);
+ }
+ });
+ }
+
+ return outStates;
+ }
+
+ /* --- Truth Table Generation --- */
+ function generateTruthTable() {
+ if (!ttContainer) return;
+
+ const inNodes = Object.values(nodes).filter(n => n.type === 'INPUT').sort((a,b) => a.label.localeCompare(b.label));
+ const outNodes = Object.values(nodes).filter(n => n.type === 'OUTPUT').sort((a,b) => a.label.localeCompare(b.label));
+
+ if (inNodes.length === 0 || outNodes.length === 0) {
+ ttContainer.innerHTML = 'Add inputs and outputs to generate table.';
+ return;
+ }
+ if (inNodes.length > 6) {
+ ttContainer.innerHTML = 'Maximum 6 inputs supported.';
+ return;
+ }
+
+ let html = '';
+ inNodes.forEach(n => html += `${n.label} `);
+ outNodes.forEach(n => html += `${n.label} `);
+ html += ' ';
+
+ const numRows = Math.pow(2, inNodes.length);
+
+ for (let i = 0; i < numRows; i++) {
+ let override = {};
+ inNodes.forEach((n, idx) => {
+ override[n.id] = ((i >> (inNodes.length - 1 - idx)) & 1) === 1;
+ });
+
+ let outStates = evaluateGraph(override);
+
+ html += '';
+ inNodes.forEach(n => {
+ let val = override[n.id];
+ html += `${val ? 1 : 0} `;
+ });
+ outNodes.forEach(n => {
+ let val = outStates[n.id];
+ html += `${val ? 1 : 0} `;
+ });
+ html += ' ';
+ }
+
+ html += '
';
+ ttContainer.innerHTML = html;
+ }
+
+ function runSimulation() {
+ evaluateGraph();
+ renderWires();
+ generateTruthTable();
+ }
+
+ /* --- Node Creation --- */
+ function createNodeElement(node) {
+ const el = document.createElement('div');
+ el.className = `lg-node`;
+ el.dataset.id = node.id;
+ el.style.left = `${node.x}px`;
+ el.style.top = `${node.y}px`;
+
+ let innerHTML = `${node.label}`;
+
+ if (node.type === 'INPUT') {
+ innerHTML += `
+
+ ${INPUT_SVG}
+
+ `;
+ }
+ else if (node.type === 'OUTPUT') {
+ innerHTML += `
+
+ ${OUTPUT_SVG}
+
+ `;
+ }
+ else if (node.type === 'GATE') {
+ const isNot = node.gateType === 'NOT';
+ innerHTML += `
+
+ ${!isNot ? `` : ''}
+
+
+ `;
+ }
+
+ innerHTML += ``;
+ el.innerHTML = innerHTML;
+ workspace.appendChild(el);
+ node.el = el;
+
+ if (node.type === 'INPUT') {
+ // Custom click handler to prevent dragging from toggling the switch
+ el.querySelector('.switch').addEventListener('click', (e) => {
+ const dist = Math.hypot(e.clientX - clickStartX, e.clientY - clickStartY);
+ if (isDraggingNode || dist > 3) {
+ e.preventDefault();
+ } else {
+ node.value = !node.value;
+ el.querySelector('.switch').classList.toggle('active-sim', node.value);
+ el.querySelector('.slider').style.background = node.value ? 'rgba(40,240,122,.25)' : '';
+ el.querySelector('.slider').style.borderColor = node.value ? 'rgba(40,240,122,.30)' : '';
+ el.querySelector('.slider').innerHTML = node.value ? `` : '';
+ runSimulation();
+ }
+ });
+ }
+
+ return el;
+ }
+
+ function spawnNode(type, gateType = null, dropX = null, dropY = null) {
+ let label = '';
+ if (type === 'INPUT') { inputCount++; label = String.fromCharCode(64 + inputCount); }
+ if (type === 'OUTPUT') { outputCount++; label = `Q${outputCount}`; }
+ if (type === 'GATE') { label = gateType; }
+
+ const id = `node_${nextNodeId++}`;
+
+ const offset = Math.floor(Math.random() * 40);
+ const x = dropX !== null ? dropX : (type === 'INPUT' ? 50 : (type === 'OUTPUT' ? 600 : 300) + offset);
+ const y = dropY !== null ? dropY : 150 + offset;
+
+ const node = { id, type, gateType, label, x, y, value: false, el: null };
+ nodes[id] = node;
+ createNodeElement(node);
+ runSimulation();
+ }
+
+ /* --- Global Interaction Handlers --- */
+
+ workspace.addEventListener('mousedown', (e) => {
+ clickStartX = e.clientX;
+ clickStartY = e.clientY;
+
+ const port = e.target.closest('.lg-port');
+ if (port) {
+ const nodeEl = port.closest('.lg-node');
+ const portId = port.dataset.port;
+
+ if (portId.startsWith('in')) {
+ const existingIdx = connections.findIndex(c => c.toNode === nodeEl.dataset.id && c.toPort === portId.replace('in', ''));
+ if (existingIdx !== -1) {
+ connections.splice(existingIdx, 1);
+ runSimulation();
+ return;
+ }
+ }
+
+ if (portId === 'out') {
+ const coords = getPortCoords(nodeEl.dataset.id, 'out');
+ wiringStart = { node: nodeEl.dataset.id, port: portId, x: coords.x, y: coords.y };
+ tempWirePath = { x: coords.x, y: coords.y };
+ return;
+ }
+ }
+
+ const wire = e.target.closest('.lg-wire');
+ if (wire && wire.dataset.connId) {
+ clearSelection();
+ selectedWireId = wire.dataset.connId;
+ renderWires();
+ e.stopPropagation();
+ return;
+ }
+
+ const nodeEl = e.target.closest('.lg-node');
+ if (nodeEl) {
+ clearSelection();
+ selectedNodeId = nodeEl.dataset.id;
+ nodeEl.classList.add('selected');
+
+ isDraggingNode = nodeEl.dataset.id;
+ const rect = nodeEl.getBoundingClientRect();
+ dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
+ return;
+ }
+
+ clearSelection();
+ });
+
+ window.addEventListener('mousemove', (e) => {
+ const wsRect = workspace.getBoundingClientRect();
+
+ if (isDraggingNode) {
+ const node = nodes[isDraggingNode];
+ let newX = e.clientX - wsRect.left - dragOffset.x;
+ let newY = e.clientY - wsRect.top - dragOffset.y;
+ node.x = Math.max(10, Math.min(newX, wsRect.width - 80));
+ node.y = Math.max(20, Math.min(newY, wsRect.height - 60));
+ updateNodePositions();
+ }
+
+ if (wiringStart) {
+ tempWirePath = {
+ x: e.clientX - wsRect.left,
+ y: e.clientY - wsRect.top
+ };
+ renderWires();
+ }
+ });
+
+ window.addEventListener('mouseup', (e) => {
+ isDraggingNode = null;
+
+ if (wiringStart) {
+ const port = e.target.closest('.lg-port');
+ if (port && port.dataset.port.startsWith('in')) {
+ const targetNodeId = port.closest('.lg-node').dataset.id;
+ const targetPortId = port.dataset.port.replace('in', '');
+
+ if (targetNodeId !== wiringStart.node) {
+ connections = connections.filter(c => !(c.toNode === targetNodeId && c.toPort === targetPortId));
+
+ connections.push({
+ id: `conn_${nextWireId++}`,
+ fromNode: wiringStart.node,
+ fromPort: 'out',
+ toNode: targetNodeId,
+ toPort: targetPortId
+ });
+ }
+ }
+ wiringStart = null;
+ tempWirePath = null;
+ runSimulation();
+ }
+ });
+
+ /* --- Deletion --- */
+ window.addEventListener('keydown', (e) => {
+ if (e.key === 'Delete' || e.key === 'Backspace') {
+ if (selectedWireId) {
+ connections = connections.filter(c => c.id !== selectedWireId);
+ clearSelection();
+ runSimulation();
+ }
+ else if (selectedNodeId) {
+ connections = connections.filter(c => c.fromNode !== selectedNodeId && c.toNode !== selectedNodeId);
+ if (nodes[selectedNodeId] && nodes[selectedNodeId].el) {
+ workspace.removeChild(nodes[selectedNodeId].el);
+ }
+ delete nodes[selectedNodeId];
+ clearSelection();
+ runSimulation();
+ }
+ }
+ });
+
+ /* --- Drag and Drop --- */
+ workspace.addEventListener('dragover', (e) => { e.preventDefault(); });
+ workspace.addEventListener('drop', (e) => {
+ e.preventDefault();
+ const spawnType = e.dataTransfer.getData('spawnType');
+ if (spawnType) {
+ const gateType = e.dataTransfer.getData('gateType');
+ const wsRect = workspace.getBoundingClientRect();
+ const x = e.clientX - wsRect.left - 40;
+ const y = e.clientY - wsRect.top - 30;
+ spawnNode(spawnType, gateType || null, x, y);
+ }
+ });
+
+ /* --- Init --- */
+ btnClearBoard?.addEventListener('click', () => {
+ workspace.querySelectorAll('.lg-node').forEach(el => el.remove());
+ nodes = {};
+ connections = [];
+ inputCount = 0;
+ outputCount = 0;
+ runSimulation();
+ });
+
+ toolboxToggle?.addEventListener("click", () => {
+ const isCollapsed = logicPage?.classList.contains("toolboxCollapsed");
+ logicPage.classList.toggle("toolboxCollapsed", !isCollapsed);
+ toolboxToggle?.setAttribute("aria-expanded", isCollapsed ? "true" : "false");
+ setTimeout(renderWires, 450);
+ });
+
+ initToolbox();
+ spawnNode('INPUT', null, 80, 150);
+ spawnNode('INPUT', null, 80, 250);
+ spawnNode('GATE', 'AND', 320, 200);
+ spawnNode('OUTPUT', null, 600, 200);
+
+})();
\ No newline at end of file
diff --git a/src/src/assets/astro.svg b/src/src/assets/astro.svg
deleted file mode 100644
index 8cf8fb0..0000000
--- a/src/src/assets/astro.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/src/assets/background.svg b/src/src/assets/background.svg
deleted file mode 100644
index 4b2be0a..0000000
--- a/src/src/assets/background.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/src/components/Footer.astro b/src/src/components/Footer.astro
deleted file mode 100644
index 2dbcdbd..0000000
--- a/src/src/components/Footer.astro
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
diff --git a/src/src/components/Header.astro b/src/src/components/Header.astro
deleted file mode 100644
index 6c42d5c..0000000
--- a/src/src/components/Header.astro
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
- Computing:Box
-
-
-
-
-
-
-
diff --git a/src/src/components/Welcome.astro b/src/src/components/Welcome.astro
deleted file mode 100644
index 52e0333..0000000
--- a/src/src/components/Welcome.astro
+++ /dev/null
@@ -1,210 +0,0 @@
----
-import astroLogo from '../assets/astro.svg';
-import background from '../assets/background.svg';
----
-
-
-
-
-
-
-
- To get started, open the src/pages
directory in your project.
-
-
- Read our docs
- Join our Discord
-
-
-
-
-
-
-
- What's New in Astro 5.0?
-
- From content layers to server islands, click to learn more about the new features and
- improvements in Astro 5.0
-
-
-
-
-
diff --git a/src/src/components/simulators/HexSimulator.astro b/src/src/components/simulators/HexSimulator.astro
deleted file mode 100644
index 4f212bb..0000000
--- a/src/src/components/simulators/HexSimulator.astro
+++ /dev/null
@@ -1,104 +0,0 @@
----
-import "./hex/hex-simulator.css";
----
-
-
-
-
- DENARY
- 0
-
- HEXADECIMAL
- 00
-
- BINARY
- 0000 0000
-
-
-
-
-
-
-
-
-
-
-
-
-
- SETTINGS
-
- HEX DIGIT WIDTH
-
-
-
-
-
- DIGITS
- 2
-
-
-
-
-
- = 8 bits
-
-
-
- CUSTOM NUMBER
-
-
-
-
-
-
-
-
-
-
-
-
- RANDOM RUNS BRIEFLY THEN STOPS AUTOMATICALLY.
-
-
-
- TOOLS
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/src/components/simulators/hex/hex-simulator.css b/src/src/components/simulators/hex/hex-simulator.css
deleted file mode 100644
index 8a15d61..0000000
--- a/src/src/components/simulators/hex/hex-simulator.css
+++ /dev/null
@@ -1,346 +0,0 @@
-/* ================= Fonts to match Binary ================= */
-/* Adjust paths to wherever you store fonts (commonly /public/fonts/...) */
-@font-face {
- font-family: "DSEG7Classic";
- src: url("/fonts/DSEG7Classic-Regular.woff") format("woff"),
- url("/fonts/DSEG7Classic-Regular.ttf") format("truetype");
- font-weight: 400;
- font-style: normal;
- font-display: swap;
-}
-@font-face {
- font-family: "SevenSegment";
- src: url("/fonts/Seven-Segment.woff2") format("woff2"),
- url("/fonts/Seven-Segment.woff") format("woff");
- font-weight: 400;
- font-style: normal;
- font-display: swap;
-}
-
-.hex-sim {
- min-height: 100vh;
- background: #14151c;
- color: #e7e8ee;
- padding: 28px;
-}
-
-.hex-font-number { font-family: "DSEG7Classic", ui-monospace, monospace; }
-.hex-font-mono { font-family: "SevenSegment", ui-monospace, monospace; }
-
-.hex-main { max-width: 1200px; margin: 0 auto; width: 100%; padding-top: 40px; }
-
-.hex-readout { text-align: center; }
-.hex-label {
- font-family: "SevenSegment", ui-sans-serif, system-ui;
- font-size: 12px;
- letter-spacing: 2px;
- opacity: 0.7;
-}
-.hex-mt { margin-top: 12px; }
-
-.hex-number {
- font-family: "DSEG7Classic", ui-monospace, monospace;
- font-size: 76px;
- line-height: 1;
- font-weight: 400;
- color: #46ff8a;
- text-shadow: 0 0 18px rgba(70,255,138,0.18);
-}
-.hex-number--small { font-size: 64px; }
-.hex-number--tiny { font-size: 54px; letter-spacing: 6px; }
-
-.hex-divider {
- margin: 26px auto 18px;
- height: 1px;
- width: min(760px, 90%);
- background: rgba(255,255,255,0.10);
-}
-
-/* ================= Main digit columns ================= */
-.hex-digits {
- margin-top: 18px;
- display: flex;
- justify-content: center;
- gap: 18px;
- flex-wrap: wrap;
-}
-
-.hex-digit-col {
- width: 160px;
- border-radius: 18px;
- background: rgba(255,255,255,0.03);
- border: 1px solid rgba(255,255,255,0.10);
- padding: 12px;
- display: grid;
- gap: 10px;
- justify-items: center;
-}
-
-.hex-digit-controls {
- width: 100%;
- display: flex;
- justify-content: center;
- gap: 10px;
-}
-
-.hex-digit-char {
- font-size: 64px;
- line-height: 1;
- color: #46ff8a;
- text-shadow: 0 0 18px rgba(70,255,138,0.18);
-}
-
-.hex-digit-place {
- font-family: "SevenSegment", ui-monospace, monospace;
- opacity: 0.65;
- font-size: 14px;
- letter-spacing: 1px;
-}
-
-/* ================= Bulbs (brightness changes) ================= */
-.hex-bulbs {
- width: 100%;
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: 10px;
- align-items: end;
-}
-
-.hex-bulb {
- display: grid;
- justify-items: center;
- gap: 6px;
- opacity: 0.35;
- filter: grayscale(30%);
- transition: opacity 160ms ease, filter 160ms ease;
-}
-
-.hex-bulb .hex-bulb-cap {
- width: 18px;
- height: 18px;
- border-radius: 999px;
- background: rgba(255,255,255,0.22);
- border: 1px solid rgba(255,255,255,0.14);
-}
-
-.hex-bulb .hex-bulb-glow {
- width: 18px;
- height: 10px;
- border-radius: 999px;
- background: rgba(70,255,138,0.0);
- box-shadow: 0 0 0 rgba(70,255,138,0.0);
- transition: background 160ms ease, box-shadow 160ms ease;
-}
-
-.hex-bulb .hex-bulb-label {
- font-family: "SevenSegment", ui-monospace, monospace;
- font-size: 12px;
- opacity: 0.8;
-}
-
-.hex-bulb.is-on {
- opacity: 1;
- filter: none;
-}
-.hex-bulb.is-on .hex-bulb-cap {
- background: rgba(255,255,255,0.35);
-}
-.hex-bulb.is-on .hex-bulb-glow {
- background: rgba(70,255,138,0.25);
- box-shadow: 0 0 18px rgba(70,255,138,0.35);
-}
-
-/* ================= Buttons (toolbox style reused everywhere) ================= */
-.hex-btn {
- padding: 10px 12px;
- border-radius: 14px;
- border: 1px solid rgba(255,255,255,0.14);
- background: rgba(255,255,255,0.06);
- color: #e7e8ee;
- font-weight: 800;
- cursor: pointer;
- font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
-}
-.hex-btn:hover { background: rgba(255,255,255,0.10); }
-
-.hex-btn--square {
- width: 48px;
- height: 48px;
- padding: 0;
- display: grid;
- place-items: center;
- font-size: 18px;
-}
-
-.hex-btn--wide { width: 100%; }
-
-.hex-btn--green {
- background: rgba(46, 200, 120, 0.18);
- border-color: rgba(46,200,120,0.35);
-}
-.hex-btn--green:hover { background: rgba(46, 200, 120, 0.26); }
-
-.hex-btn--green2 {
- background: rgba(46, 200, 120, 0.18);
- border-color: rgba(46,200,120,0.35);
-}
-
-.hex-btn--red {
- background: rgba(220, 60, 70, 0.18);
- border-color: rgba(220,60,70,0.35);
-}
-
-/* Random = green pulse while running */
-.hex-btn--random.is-running {
- border-color: rgba(80, 255, 160, 0.55);
- background: rgba(46, 200, 120, 0.22);
- box-shadow: 0 0 18px rgba(80, 255, 160, 0.35);
- animation: hexPulseGreen 900ms ease-in-out infinite;
-}
-@keyframes hexPulseGreen {
- 0%, 100% { box-shadow: 0 0 14px rgba(80, 255, 160, 0.25); }
- 50% { box-shadow: 0 0 26px rgba(80, 255, 160, 0.45); }
-}
-
-/* Reset = red background + pulse on hover */
-.hex-btn--reset:hover {
- background: rgba(220, 60, 70, 0.28);
- border-color: rgba(255, 80, 90, 0.55);
- animation: hexPulseRed 900ms ease-in-out infinite;
-}
-@keyframes hexPulseRed {
- 0%, 100% { box-shadow: 0 0 12px rgba(255, 80, 90, 0.20); }
- 50% { box-shadow: 0 0 22px rgba(255, 80, 90, 0.38); }
-}
-
-/* ================= Toolbox button + panel (slide) ================= */
-.hex-toolbox-btn {
- position: fixed;
- top: 88px;
- right: 28px;
- z-index: 30;
- display: inline-flex;
- align-items: center;
- gap: 10px;
- padding: 12px 16px;
- border-radius: 14px;
- border: 1px solid rgba(255,255,255,0.14);
- background: rgba(255,255,255,0.06);
- color: #e7e8ee;
- font-weight: 800;
- letter-spacing: 1px;
- cursor: pointer;
-}
-
-.hex-toolbox-icon {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 18px;
- height: 18px;
- color: #ff4fa6;
- filter: drop-shadow(0 0 10px rgba(255,79,166,0.35));
-}
-
-.hex-toolbox {
- position: fixed;
- top: 140px;
- right: 28px;
- width: 340px;
- display: grid;
- gap: 14px;
- z-index: 25;
-
- transform: translateX(0);
- opacity: 1;
- transition: transform 220ms ease, opacity 220ms ease;
-}
-.hex-toolbox:not(.is-open) {
- transform: translateX(380px);
- opacity: 0;
- pointer-events: none;
-}
-
-.hex-panel {
- border-radius: 16px;
- background: rgba(255,255,255,0.04);
- border: 1px solid rgba(255,255,255,0.10);
- padding: 14px;
-}
-.hex-panel-title {
- font-family: "SevenSegment", ui-sans-serif, system-ui;
- font-size: 12px;
- letter-spacing: 2px;
- opacity: 0.7;
- margin-bottom: 10px;
-}
-
-.hex-setting-title { font-weight: 900; opacity: 0.9; margin-bottom: 10px; }
-
-.hex-width {
- display: grid;
- grid-template-columns: 48px 1fr 48px;
- gap: 10px;
- align-items: center;
-}
-.hex-width-readout {
- border-radius: 14px;
- background: rgba(0,0,0,0.22);
- border: 1px solid rgba(255,255,255,0.10);
- padding: 10px 12px;
- display: flex;
- justify-content: space-between;
- align-items: baseline;
-}
-.hex-width-label {
- font-family: "SevenSegment", ui-sans-serif, system-ui;
- opacity: 0.7;
- font-weight: 800;
- letter-spacing: 1px;
- font-size: 12px;
-}
-.hex-width-number { font-size: 30px; font-weight: 900; color: #46ff8a; }
-
-.hex-hint { margin-top: 8px; opacity: 0.65; font-size: 12px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
-
-.hex-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
-.hex-mt-sm { margin-top: 10px; }
-
-.hex-tools-top { display: flex; gap: 10px; justify-content: center; margin-bottom: 10px; }
-.hex-tiny-note { margin-top: 8px; font-size: 11px; opacity: 0.6; letter-spacing: 1px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
-
-/* ================= Dialog ================= */
-.hex-dialog { border: none; padding: 0; background: transparent; }
-.hex-dialog::backdrop { background: rgba(0,0,0,0.55); }
-
-.hex-dialog-card {
- width: min(560px, 92vw);
- border-radius: 18px;
- background: #1a1b24;
- border: 1px solid rgba(255,255,255,0.12);
- padding: 16px;
- color: #e7e8ee;
-}
-
-.hex-dialog-title { font-weight: 900; letter-spacing: 1px; margin-bottom: 10px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
-
-.hex-dialog-input {
- width: 100%;
- padding: 12px 12px;
- border-radius: 14px;
- border: 1px solid rgba(255,255,255,0.14);
- background: rgba(0,0,0,0.25);
- color: #e7e8ee;
- font-size: 18px;
-}
-
-.hex-dialog-hint { margin-top: 10px; opacity: 0.7; font-size: 13px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
-.hex-dialog-error { margin-top: 8px; font-size: 13px; color: #ff6b6b; min-height: 18px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
-.hex-dialog-actions { margin-top: 14px; display: flex; gap: 10px; justify-content: flex-end; }
-
-@media (max-width: 900px) {
- .hex-toolbox { width: min(360px, 92vw); right: 16px; }
- .hex-toolbox-btn { right: 16px; }
- .hex-number { font-size: 60px; }
- .hex-number--tiny { font-size: 40px; letter-spacing: 4px; }
-}
diff --git a/src/src/components/simulators/hex/hex-simulator.ts b/src/src/components/simulators/hex/hex-simulator.ts
deleted file mode 100644
index 08bc8aa..0000000
--- a/src/src/components/simulators/hex/hex-simulator.ts
+++ /dev/null
@@ -1,232 +0,0 @@
-type DialogMode = "hex" | "den" | "bin";
-
-const root = document.querySelector("[data-hex-sim]");
-if (!root) throw new Error("Hex simulator root not found");
-
-const outDen = root.querySelector('[data-out="denary"]')!;
-const outHex = root.querySelector('[data-out="hex"]')!;
-const outBin = root.querySelector('[data-out="bin"]')!;
-const outDigitsRow = root.querySelector('[data-out="digitsRow"]')!;
-
-const toolbox = root.querySelector('[data-out="toolbox"]')!;
-const toolboxBtn = root.querySelector('[data-action="toggleToolbox"]')!;
-const digitsCount = root.querySelector('[data-out="digitsCount"]')!;
-const bitsHint = root.querySelector('[data-out="bitsHint"]')!;
-const randomBtn = root.querySelector("[data-random]")!;
-
-const dialog = root.querySelector('[data-out="dialog"]')!;
-const dialogTitle = root.querySelector('[data-out="dialogTitle"]')!;
-const dialogInput = root.querySelector('[data-out="dialogInput"]')!;
-const dialogHint = root.querySelector('[data-out="dialogHint"]')!;
-const dialogError = root.querySelector('[data-out="dialogError"]')!;
-
-let digits = 2; // 1..8
-let value = 0; // unsigned denary
-let randomTimer: number | null = null;
-let dialogMode: DialogMode | null = null;
-
-const clamp = (n: number, min: number, max: number) => Math.min(max, Math.max(min, n));
-const maxForDigits = (d: number) => (16 ** d) - 1;
-
-const padHex = (n: number, d: number) => n.toString(16).toUpperCase().padStart(d, "0");
-const padBin = (n: number, b: number) => n.toString(2).padStart(b, "0");
-const groupBin = (b: string) => b.replace(/(.{4})/g, "$1 ").trim();
-
-function stopRandom(): void {
- if (randomTimer !== null) window.clearInterval(randomTimer);
- randomTimer = null;
- randomBtn.classList.remove("is-running");
-}
-
-function startRandom(): void {
- stopRandom();
- const max = maxForDigits(digits);
- const start = Date.now();
-
- randomBtn.classList.add("is-running");
-
- randomTimer = window.setInterval(() => {
- value = Math.floor(Math.random() * (max + 1));
- render();
- if (Date.now() - start > 1600) stopRandom();
- }, 90);
-}
-
-function render(): void {
- const bits = digits * 4;
-
- digitsCount.textContent = String(digits);
- bitsHint.textContent = `= ${bits} bits`;
-
- outDen.textContent = String(value);
- outHex.textContent = padHex(value, digits);
- outBin.textContent = groupBin(padBin(value, bits));
-
- renderDigitsRow();
-}
-
-function renderDigitsRow(): void {
- const hex = padHex(value, digits);
- outDigitsRow.innerHTML = "";
-
- for (let i = 0; i < digits; i++) {
- const pow = digits - 1 - i;
- const placeValue = 16 ** pow;
-
- const digitChar = hex[i];
- const digitVal = parseInt(digitChar, 16);
- const nibbleBits = [(digitVal >> 3) & 1, (digitVal >> 2) & 1, (digitVal >> 1) & 1, digitVal & 1]; // 8 4 2 1
-
- const col = document.createElement("div");
- col.className = "hex-digit-col";
- col.innerHTML = `
-
-
-
-
-
- ${digitChar}
-
-
-
- ${[8,4,2,1].map((w, idx) => {
- const on = nibbleBits[idx] === 1;
- return `
-
-
-
- ${w}
-
- `;
- }).join("")}
-
-
- ${placeValue}
- `;
- outDigitsRow.appendChild(col);
- }
-}
-
-function openDialog(mode: DialogMode): void {
- stopRandom();
- dialogMode = mode;
-
- dialogError.textContent = "";
- dialogInput.value = "";
-
- if (mode === "hex") {
- dialogTitle.textContent = "Custom Hexadecimal";
- dialogHint.textContent = `Enter 1–${digits} hex digit(s) (0–9, A–F).`;
- dialogInput.placeholder = "A1";
- dialogInput.inputMode = "text";
- } else if (mode === "den") {
- dialogTitle.textContent = "Custom Denary";
- dialogHint.textContent = `Enter a whole number from 0 to ${maxForDigits(digits)}.`;
- dialogInput.placeholder = "42";
- dialogInput.inputMode = "numeric";
- } else {
- dialogTitle.textContent = "Custom Binary";
- dialogHint.textContent = `Enter up to ${digits * 4} bit(s) using 0 and 1.`;
- dialogInput.placeholder = "00101010";
- dialogInput.inputMode = "text";
- }
-
- dialog.showModal();
- window.setTimeout(() => dialogInput.focus(), 0);
-}
-
-function closeDialog(): void {
- dialogMode = null;
- dialogError.textContent = "";
- if (dialog.open) dialog.close();
-}
-
-function applyDialog(): void {
- const raw = (dialogInput.value || "").trim();
- if (!dialogMode) return closeDialog();
- if (raw.length === 0) return closeDialog();
-
- const max = maxForDigits(digits);
- const bits = digits * 4;
-
- if (dialogMode === "hex") {
- const v = raw.toUpperCase();
- if (!/^[0-9A-F]+$/.test(v)) { dialogError.textContent = "Hex must use 0–9 and A–F only."; return; }
- if (v.length > digits) { dialogError.textContent = `Max length is ${digits} hex digit(s).`; return; }
- value = clamp(parseInt(v, 16), 0, max);
- render();
- return closeDialog();
- }
-
- if (dialogMode === "den") {
- if (!/^\d+$/.test(raw)) { dialogError.textContent = "Denary must be whole numbers only."; return; }
- const n = Number(raw);
- if (!Number.isFinite(n)) { dialogError.textContent = "Invalid number."; return; }
- value = clamp(n, 0, max);
- render();
- return closeDialog();
- }
-
- // bin
- if (!/^[01]+$/.test(raw)) { dialogError.textContent = "Binary must use 0 and 1 only."; return; }
- if (raw.length > bits) { dialogError.textContent = `Max length is ${bits} bit(s).`; return; }
- value = clamp(parseInt(raw, 2), 0, max);
- render();
- return closeDialog();
-}
-
-function applyDigitDelta(i: number, delta: number): void {
- stopRandom();
- const hexArr = padHex(value, digits).split("");
- let v = parseInt(hexArr[i], 16);
- v = (v + delta) % 16;
- if (v < 0) v += 16;
- hexArr[i] = v.toString(16).toUpperCase();
- value = clamp(parseInt(hexArr.join(""), 16), 0, maxForDigits(digits));
- render();
-}
-
-// dialog cancel / backdrop
-dialog.addEventListener("cancel", (e) => { e.preventDefault(); closeDialog(); });
-dialog.addEventListener("click", (e) => {
- const card = dialog.querySelector(".hex-dialog-card");
- if (card && !card.contains(e.target as Node)) closeDialog();
-});
-dialogInput.addEventListener("keydown", (e) => {
- if (e.key === "Enter") applyDialog();
- if (e.key === "Escape") closeDialog();
-});
-
-// main click handler
-root.addEventListener("click", (e) => {
- const btn = (e.target as HTMLElement).closest("[data-action]");
- if (!btn) return;
- const action = btn.getAttribute("data-action")!;
-
- if (action === "toggleToolbox") {
- toolbox.classList.toggle("is-open");
- toolboxBtn.setAttribute("aria-expanded", toolbox.classList.contains("is-open") ? "true" : "false");
- return;
- }
-
- if (action === "digitsMinus") { digits = clamp(digits - 1, 1, 8); value = clamp(value, 0, maxForDigits(digits)); return render(); }
- if (action === "digitsPlus") { digits = clamp(digits + 1, 1, 8); value = clamp(value, 0, maxForDigits(digits)); return render(); }
-
- if (action === "increment") { stopRandom(); value = clamp(value + 1, 0, maxForDigits(digits)); return render(); }
- if (action === "decrement") { stopRandom(); value = clamp(value - 1, 0, maxForDigits(digits)); return render(); }
-
- if (action === "reset") { stopRandom(); value = 0; return render(); }
- if (action === "random") { return startRandom(); }
-
- if (action === "customHex") return openDialog("hex");
- if (action === "customDenary") return openDialog("den");
- if (action === "customBinary") return openDialog("bin");
-
- if (action === "dialogCancel") return closeDialog();
- if (action === "dialogApply") return applyDialog();
-
- if (action === "digitUp") return applyDigitDelta(Number(btn.getAttribute("data-i")), +1);
- if (action === "digitDown") return applyDigitDelta(Number(btn.getAttribute("data-i")), -1);
-});
-
-render();
diff --git a/src/src/layouts/BaseLayout.astro b/src/src/layouts/BaseLayout.astro
deleted file mode 100644
index 022fd45..0000000
--- a/src/src/layouts/BaseLayout.astro
+++ /dev/null
@@ -1,118 +0,0 @@
----
-const { title = "Computing:Box" } = Astro.props;
----
-
-
-
-
-
-
- {title}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/src/layouts/Layout.astro b/src/src/layouts/Layout.astro
deleted file mode 100644
index e455c61..0000000
--- a/src/src/layouts/Layout.astro
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
- Astro Basics
-
-
-
-
-
-
-
diff --git a/src/src/pages/binary.astro b/src/src/pages/binary.astro
deleted file mode 100644
index fc195e5..0000000
--- a/src/src/pages/binary.astro
+++ /dev/null
@@ -1,115 +0,0 @@
----
-import BaseLayout from "../layouts/BaseLayout.astro";
-import "../styles/binary.css";
----
-
-
-
-
-
-
-
-
-
-
- Denary
- 0
-
- Binary
-
- 00000000
-
-
-
-
-
-
-
-
-
-
-
-
-
- Settings
-
-
- Unsigned
-
-
-
-
- Two's complement
-
-
-
- Tip: In unsigned binary, all bits represent positive values.
-
-
-
- Bit width
-
-
-
-
-
- Bits
-
-
-
-
-
-
-
-
-
-
- Custom Number
-
-
-
-
-
-
-
- Random runs briefly then stops automatically.
-
-
-
-
- Tools
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/src/pages/hexadecimal.astro b/src/src/pages/hexadecimal.astro
deleted file mode 100644
index edfe098..0000000
--- a/src/src/pages/hexadecimal.astro
+++ /dev/null
@@ -1,8 +0,0 @@
----
-import BaseLayout from "../layouts/BaseLayout.astro";
-import HexSimulator from "../components/simulators/HexSimulator.astro";
----
-
-
-
-
diff --git a/src/src/pages/index.astro b/src/src/pages/index.astro
deleted file mode 100644
index c04f360..0000000
--- a/src/src/pages/index.astro
+++ /dev/null
@@ -1,11 +0,0 @@
----
-import Welcome from '../components/Welcome.astro';
-import Layout from '../layouts/Layout.astro';
-
-// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
-// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
----
-
-
-
-
diff --git a/src/src/scripts/binary.js b/src/src/scripts/binary.js
deleted file mode 100644
index 41c1c50..0000000
--- a/src/src/scripts/binary.js
+++ /dev/null
@@ -1,522 +0,0 @@
-// src/scripts/binary.js
-// Computing:Box — Binary page logic (Unsigned + Two's Complement)
-
-(() => {
- /* -----------------------------
- DOM
- ----------------------------- */
- const bitsGrid = document.getElementById("bitsGrid");
- const denaryEl = document.getElementById("denaryNumber");
- const binaryEl = document.getElementById("binaryNumber");
- const bitsInput = document.getElementById("bitsInput");
-
- const modeToggle = document.getElementById("modeToggle");
- const modeHint = document.getElementById("modeHint");
-
- const btnCustomBinary = document.getElementById("btnCustomBinary");
- const btnCustomDenary = document.getElementById("btnCustomDenary");
- const btnShiftLeft = document.getElementById("btnShiftLeft");
- const btnShiftRight = document.getElementById("btnShiftRight");
-
- const btnDec = document.getElementById("btnDec");
- const btnInc = document.getElementById("btnInc");
- const btnClear = document.getElementById("btnClear");
- const btnRandom = document.getElementById("btnRandom");
-
- const btnBitsUp = document.getElementById("btnBitsUp");
- const btnBitsDown = document.getElementById("btnBitsDown");
-
- const toolboxToggle = document.getElementById("toolboxToggle");
- const toolboxPanel = document.getElementById("toolboxPanel");
-
- /* -----------------------------
- STATE
- ----------------------------- */
- let bitCount = clampInt(Number(bitsInput?.value ?? 8), 1, 64);
- let bits = new Array(bitCount).fill(false);
- let randomTimer = null;
-
- // For responsive wrapping of the top binary display
- let nibblesPerLine = null;
- let wrapMeasureSpan = null;
-
- /* -----------------------------
- HELPERS
- ----------------------------- */
- function clampInt(n, min, max) {
- if (!Number.isFinite(n)) return min;
- return Math.max(min, Math.min(max, Math.trunc(n)));
- }
-
- function isTwosMode() {
- return !!modeToggle?.checked;
- }
-
- function pow2Big(n) {
- return 1n << BigInt(n);
- }
-
- function unsignedMaxExclusive(nBits) {
- return pow2Big(nBits);
- }
-
- function unsignedMaxValue(nBits) {
- return pow2Big(nBits) - 1n;
- }
-
- function twosMin(nBits) {
- return -pow2Big(nBits - 1);
- }
-
- function twosMax(nBits) {
- return pow2Big(nBits - 1) - 1n;
- }
-
- function bitsToUnsignedBigInt() {
- let v = 0n;
- for (let i = 0; i < bitCount; i++) {
- if (bits[i]) v += pow2Big(i);
- }
- return v;
- }
-
- function unsignedBigIntToBits(vUnsigned) {
- const span = unsignedMaxExclusive(bitCount);
- const v = ((vUnsigned % span) + span) % span;
-
- for (let i = 0; i < bitCount; i++) {
- bits[i] = ((v >> BigInt(i)) & 1n) === 1n;
- }
- }
-
- function bitsToSignedBigIntTwos() {
- const u = bitsToUnsignedBigInt();
- const signBit = bits[bitCount - 1] === true;
- if (!signBit) return u;
- return u - pow2Big(bitCount);
- }
-
- function signedBigIntToBitsTwos(vSigned) {
- const span = pow2Big(bitCount);
- let v = ((vSigned % span) + span) % span;
- unsignedBigIntToBits(v);
- }
-
- function updateModeHint() {
- if (!modeHint) return;
- modeHint.textContent = isTwosMode()
- ? "Tip: In two’s complement, the left-most bit (MSB) represents a negative value."
- : "Tip: In unsigned binary, all bits represent positive values.";
- }
-
- /* -----------------------------
- TOP BINARY DISPLAY: responsive wrap by nibble count
- ----------------------------- */
- function ensureWrapMeasurer() {
- if (wrapMeasureSpan || !binaryEl) return;
- wrapMeasureSpan = document.createElement("span");
- wrapMeasureSpan.style.position = "absolute";
- wrapMeasureSpan.style.visibility = "hidden";
- wrapMeasureSpan.style.whiteSpace = "pre";
- wrapMeasureSpan.style.pointerEvents = "none";
- // Inherit font/letterspacing from binaryEl
- wrapMeasureSpan.style.font = getComputedStyle(binaryEl).font;
- wrapMeasureSpan.style.letterSpacing = getComputedStyle(binaryEl).letterSpacing;
- document.body.appendChild(wrapMeasureSpan);
- }
-
- function computeNibblesPerLine() {
- if (!binaryEl) return null;
- ensureWrapMeasurer();
-
- // Available width = width of the readout area (binaryEl parent)
- const host = binaryEl.parentElement;
- if (!host) return null;
-
- const hostW = host.getBoundingClientRect().width;
- if (!Number.isFinite(hostW) || hostW <= 0) return null;
-
- // Measure one nibble including trailing space ("0000 ")
- wrapMeasureSpan.textContent = "0000 ";
- const nibbleW = wrapMeasureSpan.getBoundingClientRect().width || 1;
-
- // Safety: keep at least 1 nibble per line
- const max = Math.max(1, Math.floor(hostW / nibbleW));
- return max;
- }
-
- function formatBinaryWrapped() {
- // EXACT bitCount digits (no padding to 4)
- let raw = "";
- for (let i = bitCount - 1; i >= 0; i--) raw += bits[i] ? "1" : "0";
-
- // If <= 4 bits, do NOT insert spaces/newlines at all
- if (bitCount <= 4) return raw;
-
- const groups = [];
- for (let i = 0; i < raw.length; i += 4) {
- groups.push(raw.slice(i, i + 4));
- }
-
- const perLine = nibblesPerLine ?? groups.length;
- if (perLine >= groups.length) return groups.join(" ");
-
- const lines = [];
- for (let i = 0; i < groups.length; i += perLine) {
- lines.push(groups.slice(i, i + perLine).join(" "));
- }
- return lines.join("\n");
- }
-
- function refreshBinaryWrap() {
- const next = computeNibblesPerLine();
- // Only update if it actually changes (prevents jitter)
- if (next !== nibblesPerLine) nibblesPerLine = next;
- updateReadout(); // re-render with new wrap
- }
-
- /* -----------------------------
- BUILD UI (BITS)
- ----------------------------- */
- function buildBits(count) {
- bitCount = clampInt(count, 1, 64);
- if (bitsInput) bitsInput.value = String(bitCount);
-
- const oldBits = bits.slice();
- bits = new Array(bitCount).fill(false);
- for (let i = 0; i < Math.min(oldBits.length, bitCount); i++) bits[i] = oldBits[i];
-
- bitsGrid.innerHTML = "";
-
- bitsGrid.classList.toggle("bitsFew", bitCount < 8);
- if (bitCount < 8) {
- bitsGrid.style.setProperty("--cols", String(bitCount));
- } else {
- bitsGrid.style.removeProperty("--cols");
- }
-
- for (let i = bitCount - 1; i >= 0; i--) {
- const bitEl = document.createElement("div");
- bitEl.className = "bit";
-
- bitEl.innerHTML = `
-
-
-
- `;
-
- bitsGrid.appendChild(bitEl);
- }
-
- bitsGrid.querySelectorAll('input[type="checkbox"]').forEach((input) => {
- input.addEventListener("change", () => {
- const i = Number(input.dataset.index);
- bits[i] = input.checked;
- updateUI();
- });
- });
-
- // bulb styling + 25% bigger (vs 26px previously)
- for (let i = 0; i < bitCount; i++) {
- const bulb = document.getElementById(`bulb-${i}`);
- if (!bulb) continue;
- bulb.style.width = "auto";
- bulb.style.height = "auto";
- bulb.style.border = "none";
- bulb.style.background = "transparent";
- bulb.style.borderRadius = "0";
- bulb.style.boxShadow = "none";
- bulb.style.opacity = "0.45";
- bulb.style.fontSize = "32px";
- bulb.style.lineHeight = "1";
- bulb.style.display = "flex";
- bulb.style.alignItems = "center";
- bulb.style.justifyContent = "center";
- bulb.style.filter = "grayscale(1)";
- bulb.textContent = "💡";
- }
-
- // wrapping may change when bit width changes
- refreshBinaryWrap();
- updateUI();
- }
-
- /* -----------------------------
- UI UPDATE
- ----------------------------- */
- function updateBitLabels() {
- for (let i = 0; i < bitCount; i++) {
- const label = document.getElementById(`bitLabel-${i}`);
- if (!label) continue;
-
- if (isTwosMode() && i === bitCount - 1) {
- // Keep on one line (CSS: white-space:nowrap)
- label.textContent = `-${pow2Big(bitCount - 1).toString()}`;
- } else {
- label.textContent = pow2Big(i).toString();
- }
- }
- }
-
- function syncSwitchesToBits() {
- bitsGrid.querySelectorAll('input[type="checkbox"]').forEach((input) => {
- const i = Number(input.dataset.index);
- input.checked = !!bits[i];
- });
- }
-
- function updateBulbs() {
- for (let i = 0; i < bitCount; i++) {
- const bulb = document.getElementById(`bulb-${i}`);
- if (!bulb) continue;
-
- const on = bits[i] === true;
- if (on) {
- bulb.style.opacity = "1";
- bulb.style.filter = "grayscale(0)";
- bulb.style.textShadow = "0 0 18px rgba(255,216,107,.75), 0 0 30px rgba(255,216,107,.45)";
- } else {
- bulb.style.opacity = "0.45";
- bulb.style.filter = "grayscale(1)";
- bulb.style.textShadow = "none";
- }
- }
- }
-
- function updateReadout() {
- if (!denaryEl || !binaryEl) return;
-
- denaryEl.textContent = (isTwosMode() ? bitsToSignedBigIntTwos() : bitsToUnsignedBigInt()).toString();
- binaryEl.textContent = formatBinaryWrapped();
- }
-
- function updateUI() {
- updateModeHint();
- updateBitLabels();
- syncSwitchesToBits();
- updateBulbs();
- updateReadout();
- }
-
- /* -----------------------------
- SET FROM INPUT
- ----------------------------- */
- function setFromBinaryString(binStr) {
- const clean = String(binStr ?? "").replace(/\s+/g, "");
- if (!/^[01]+$/.test(clean)) return false;
-
- const padded = clean.slice(-bitCount).padStart(bitCount, "0");
- for (let i = 0; i < bitCount; i++) {
- const charFromRight = padded[padded.length - 1 - i];
- bits[i] = charFromRight === "1";
- }
-
- updateUI();
- return true;
- }
-
- function setFromDenaryInput(vStr) {
- const raw = String(vStr ?? "").trim();
- if (!raw) return false;
-
- let v;
- try {
- if (!/^-?\d+$/.test(raw)) return false;
- v = BigInt(raw);
- } catch {
- return false;
- }
-
- if (isTwosMode()) {
- const min = twosMin(bitCount);
- const max = twosMax(bitCount);
- if (v < min || v > max) return false;
- signedBigIntToBitsTwos(v);
- } else {
- if (v < 0n) return false;
- if (v > unsignedMaxValue(bitCount)) return false;
- unsignedBigIntToBits(v);
- }
-
- updateUI();
- return true;
- }
-
- /* -----------------------------
- SHIFTS
- ----------------------------- */
- function shiftLeft() {
- for (let i = bitCount - 1; i >= 1; i--) bits[i] = bits[i - 1];
- bits[0] = false;
- updateUI();
- }
-
- function shiftRight() {
- if (isTwosMode()) {
- // arithmetic right shift: keep MSB
- const msb = bits[bitCount - 1];
- for (let i = 0; i < bitCount - 1; i++) bits[i] = bits[i + 1];
- bits[bitCount - 1] = msb;
- } else {
- for (let i = 0; i < bitCount - 1; i++) bits[i] = bits[i + 1];
- bits[bitCount - 1] = false;
- }
- updateUI();
- }
-
- /* -----------------------------
- CLEAR / INC / DEC
- ----------------------------- */
- function clearAll() {
- bits.fill(false);
- updateUI();
- }
-
- function increment() {
- if (isTwosMode()) {
- const min = twosMin(bitCount);
- const max = twosMax(bitCount);
- let v = bitsToSignedBigIntTwos() + 1n;
- if (v > max) v = min;
- signedBigIntToBitsTwos(v);
- } else {
- const span = unsignedMaxExclusive(bitCount);
- unsignedBigIntToBits((bitsToUnsignedBigInt() + 1n) % span);
- }
- updateUI();
- }
-
- function decrement() {
- if (isTwosMode()) {
- const min = twosMin(bitCount);
- const max = twosMax(bitCount);
- let v = bitsToSignedBigIntTwos() - 1n;
- if (v < min) v = max;
- signedBigIntToBitsTwos(v);
- } else {
- const span = unsignedMaxExclusive(bitCount);
- unsignedBigIntToBits((bitsToUnsignedBigInt() - 1n + span) % span);
- }
- updateUI();
- }
-
- /* -----------------------------
- RANDOM
- ----------------------------- */
- function cryptoRandomBigInt(maxExclusive) {
- if (maxExclusive <= 0n) return 0n;
- const bitLen = maxExclusive.toString(2).length;
- const byteLen = Math.ceil(bitLen / 8);
-
- while (true) {
- const bytes = new Uint8Array(byteLen);
- crypto.getRandomValues(bytes);
-
- let x = 0n;
- for (const b of bytes) x = (x << 8n) | BigInt(b);
-
- const extraBits = BigInt(byteLen * 8 - bitLen);
- if (extraBits > 0n) x = x >> extraBits;
- if (x < maxExclusive) return x;
- }
- }
-
- function setRandomOnce() {
- const span = unsignedMaxExclusive(bitCount);
- const u = cryptoRandomBigInt(span);
- unsignedBigIntToBits(u);
- updateUI();
- }
-
- function runRandomBriefly() {
- if (randomTimer) {
- clearInterval(randomTimer);
- randomTimer = null;
- }
-
- const start = Date.now();
- const durationMs = 1125; // (your “~25% longer” vs 900ms)
- const tickMs = 80;
-
- randomTimer = setInterval(() => {
- setRandomOnce();
- if (Date.now() - start >= durationMs) {
- clearInterval(randomTimer);
- randomTimer = null;
- }
- }, tickMs);
- }
-
- /* -----------------------------
- BIT WIDTH CONTROLS
- ----------------------------- */
- function setBitWidth(n) {
- buildBits(clampInt(n, 1, 64));
- }
-
- /* -----------------------------
- TOOLBOX TOGGLE (simple open/close state)
- ----------------------------- */
- function setToolboxOpen(open) {
- document.body.classList.toggle("toolboxClosed", !open);
- toolboxToggle?.setAttribute("aria-expanded", open ? "true" : "false");
- refreshBinaryWrap(); // width changes when toolbox closes/opens
- }
-
- /* -----------------------------
- EVENTS
- ----------------------------- */
- modeToggle?.addEventListener("change", () => updateUI());
-
- btnCustomBinary?.addEventListener("click", () => {
- const v = prompt(`Enter binary (spaces allowed). Current width: ${bitCount} bits`);
- if (v === null) return;
- if (!setFromBinaryString(v)) alert("Invalid binary");
- });
-
- btnCustomDenary?.addEventListener("click", () => {
- const v = prompt(
- isTwosMode()
- ? `Enter denary (${twosMin(bitCount).toString()} to ${twosMax(bitCount).toString()}):`
- : `Enter denary (0 to ${unsignedMaxValue(bitCount).toString()}):`
- );
- if (v === null) return;
- if (!setFromDenaryInput(v)) alert("Invalid denary for current mode/bit width");
- });
-
- btnShiftLeft?.addEventListener("click", shiftLeft);
- btnShiftRight?.addEventListener("click", shiftRight);
-
- btnInc?.addEventListener("click", increment);
- btnDec?.addEventListener("click", decrement);
-
- btnClear?.addEventListener("click", clearAll);
- btnRandom?.addEventListener("click", runRandomBriefly);
-
- btnBitsUp?.addEventListener("click", () => setBitWidth(bitCount + 1));
- btnBitsDown?.addEventListener("click", () => setBitWidth(bitCount - 1));
-
- bitsInput?.addEventListener("change", () => setBitWidth(Number(bitsInput.value)));
-
- toolboxToggle?.addEventListener("click", () => {
- const isOpen = !document.body.classList.contains("toolboxClosed");
- setToolboxOpen(!isOpen);
- });
-
- // Recompute wrapping live when the window size changes
- let resizeT = null;
- window.addEventListener("resize", () => {
- if (resizeT) clearTimeout(resizeT);
- resizeT = setTimeout(() => refreshBinaryWrap(), 60);
- });
-
- /* -----------------------------
- INIT
- ----------------------------- */
- updateModeHint();
- buildBits(bitCount);
- setToolboxOpen(true);
-})();
diff --git a/src/src/styles/binary.css b/src/src/styles/binary.css
deleted file mode 100644
index 4398c01..0000000
--- a/src/src/styles/binary.css
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- Binary page styles (keeps the last-working simulator markup + binary.js).
-
- Goals:
- - Do NOT change any IDs/classes expected by src/scripts/binary.js
- - Toolbox button toggles the ENTIRE right-hand column via body.toolboxClosed
- - Fix toolbox button positioning (no overlap, consistent with header container)
- - Fix spacing/consistency of cards + buttons
- - Keep binary readout wrapping/bit-width behaviour from JS (\n in output)
-*/
-
-:root{
- --panel-w: 360px;
- --gap: 22px;
-}
-
-/* Page wrapper (inside BaseLayout .pageWrap) */
-.wrap{
- max-width: 1400px;
- margin: 0 auto;
- padding: 22px 20px 48px;
- display: flex;
- flex-direction: column;
- gap: 14px;
-}
-
-/* Toolbox toggle button (sits below navbar, aligned right, never overlaps) */
-.toolboxToggle{
- align-self: flex-end;
- position: sticky;
- top: calc(var(--nav-h, 108px) + 14px);
- z-index: 30;
-
- display: inline-flex;
- align-items: center;
- gap: 10px;
- height: 40px;
- padding: 0 14px;
- border-radius: 12px;
- border: 1px solid rgba(255,255,255,.14);
- background: rgba(255,255,255,.06);
- color: rgba(255,255,255,.92);
- cursor: pointer;
-}
-.toolboxToggle:hover{ background: rgba(255,255,255,.08); }
-.toolboxText{
- letter-spacing: .12em;
- font-weight: 900;
-}
-
-/* Main layout grid */
-.topGrid{
- display: grid;
- grid-template-columns: 1fr var(--panel-w);
- gap: var(--gap);
- align-items: start;
-}
-
-/* Hide ENTIRE toolbox column when toggled closed */
-body.toolboxClosed .topGrid{ grid-template-columns: 1fr; }
-body.toolboxClosed #toolboxPanel{ display: none; }
-
-.mainCol{ min-width: 0; }
-
-/* Readout */
-.readout{
- text-align: center;
- margin-top: 8px;
-}
-
-.label{
- opacity: .8;
- letter-spacing: .12em;
- text-transform: uppercase;
- font-size: 12px;
-}
-
-/* IMPORTANT: allow shrinking below 4 bits (no min-width!) */
-.num{
- display: inline-block;
- width: fit-content;
- max-width: 100%;
- white-space: pre-line; /* allows JS \n wraps */
- letter-spacing: 2px;
-}
-
-.denaryValue{
- font-size: 54px;
- margin: 6px 0 10px;
-}
-
-.binaryValue{
- font-size: 56px;
- margin: 4px 0 18px;
-}
-
-.divider{
- height: 1px;
- background: rgba(255,255,255,.10);
- margin: 14px auto 24px;
- max-width: 900px;
-}
-
-/* Bits area */
-.bitsWrap{ padding-top: 6px; }
-
-.bitsGrid{
- display: grid;
- gap: 24px;
- justify-content: center;
- grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
- max-width: 1200px;
- margin: 0 auto;
-}
-
-.bitsGrid.bitsFew{ justify-content: center; }
-
-.bit{
- display: grid;
- justify-items: center;
- gap: 8px;
-}
-
-.bulb{
- font-size: 32px; /* JS also bumps this */
- line-height: 1;
- opacity: .45;
-}
-
-.bitVal{
- font-size: 22px;
- line-height: 1.05;
- text-align: center;
- white-space: nowrap; /* keep -128 on one line */
-}
-
-/* Switch (existing classes assumed) */
-.switch{
- position: relative;
- display: inline-block;
- width: 52px;
- height: 28px;
-}
-.switch input{ display:none; }
-.slider{
- position:absolute;
- inset:0;
- border-radius:999px;
- background: rgba(255,255,255,.18);
- border: 1px solid rgba(255,255,255,.14);
-}
-.slider:before{
- content:"";
- position:absolute;
- height: 22px;
- width: 22px;
- left: 3px;
- top: 2.5px;
- border-radius: 999px;
- background: #fff;
- transition: transform .18s ease;
-}
-.switch input:checked + .slider:before{ transform: translateX(22px); }
-
-/* Toolbox column */
-.panelCol{
- position: sticky;
- top: calc(var(--nav-h, 108px) + 72px); /* leaves space for sticky toolbox button */
- align-self: start;
- display: grid;
- gap: 16px;
-}
-
-/* Cards */
-.card{
- border: 1px solid rgba(255,255,255,.12);
- border-radius: 16px;
- background: rgba(255,255,255,.05);
- padding: 14px;
-}
-
-.cardTitle{
- opacity: .8;
- letter-spacing: .14em;
- text-transform: uppercase;
- font-size: 12px;
- margin-bottom: 10px;
-}
-
-.hint{
- opacity: .7;
- font-size: 11px;
- margin-top: 10px;
- line-height: 1.35;
-}
-
-/* Keep mode labels on one line */
-.toggleRow{
- display: grid;
- grid-template-columns: 1fr auto 1fr;
- gap: 10px;
- align-items: center;
-}
-
-.toggleLabel{
- font-size: 12px;
- font-weight: 800;
- letter-spacing: .12em;
- text-transform: uppercase;
- white-space: nowrap;
-}
-
-.subCard{
- margin-top: 12px;
- border: 1px solid rgba(255,255,255,.10);
- border-radius: 14px;
- background: rgba(0,0,0,.12);
- padding: 12px;
-}
-
-.subTitle{
- opacity: .8;
- letter-spacing: .14em;
- text-transform: uppercase;
- font-size: 11px;
- margin-bottom: 10px;
-}
-
-.bitWidthRow{
- display: grid;
- grid-template-columns: 44px 1fr 44px;
- gap: 10px;
- align-items: center;
-}
-
-.bitInputWrap{
- display: grid;
- grid-template-columns: auto 1fr;
- gap: 10px;
- align-items: center;
- padding: 10px 12px;
- border: 1px solid rgba(255,255,255,.10);
- border-radius: 12px;
- background: rgba(255,255,255,.04);
-}
-
-.bitInputLabel{
- opacity: .75;
- letter-spacing: .14em;
- text-transform: uppercase;
- font-size: 11px;
- white-space: nowrap;
-}
-
-.bitInput{
- width: 100%;
- min-width: 0;
- background: transparent;
- border: none;
- outline: none;
- color: inherit;
- font-size: 20px;
- text-align: right;
-}
-
-.miniBtn{
- height: 44px;
- border-radius: 12px;
- border: 1px solid rgba(255,255,255,.12);
- background: rgba(255,255,255,.06);
- color: rgba(255,255,255,.9);
- font-size: 18px;
- cursor: pointer;
-}
-.miniBtn:hover{ background: rgba(255,255,255,.08); }
-
-/* Buttons */
-.controlsRow{
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 10px;
- margin-bottom: 10px;
-}
-
-.btn{
- border-radius: 12px;
- border: 1px solid rgba(255,255,255,.12);
- background: rgba(255,255,255,.06);
- color: rgba(255,255,255,.92);
- padding: 12px 12px;
- font-weight: 800;
- letter-spacing: .10em;
- text-transform: uppercase;
- cursor: pointer;
-}
-.btn:hover{ background: rgba(255,255,255,.08); }
-
-.btnWide{ width: 100%; }
-
-.btnAccent{
- background: rgba(0,255,140,.12);
- border-color: rgba(0,255,140,.22);
-}
-.btnAccent:hover{ background: rgba(0,255,140,.16); }
-
-.toolRowCentered{
- display: flex;
- justify-content: center;
- gap: 12px;
- margin: 10px 0 12px;
-}
-
-.toolBtn{
- width: 56px;
- height: 56px;
- border-radius: 14px;
- border: 1px solid rgba(255,255,255,.12);
- background: rgba(255,255,255,.06);
- color: rgba(255,255,255,.92);
- font-size: 18px;
- cursor: pointer;
-}
-
-.toolDec{ background: rgba(255,0,0,.14); border-color: rgba(255,0,0,.20); }
-.toolInc{ background: rgba(0,255,140,.14); border-color: rgba(0,255,140,.20); }
-
-.toolRow2{
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 10px;
- margin-bottom: 12px;
-}
-
-/* Reset stays white text */
-.btnReset{ color: rgba(255,255,255,.92); }
-
-/* Responsive */
-@media (max-width: 980px){
- .topGrid{ grid-template-columns: 1fr; }
- .panelCol{ position: static; }
- .toolboxToggle{ position: static; align-self: flex-start; }
-}
diff --git a/src/src/styles/global.css b/src/src/styles/global.css
deleted file mode 100644
index a71bbdf..0000000
--- a/src/src/styles/global.css
+++ /dev/null
@@ -1,85 +0,0 @@
-:root{
- --bg: #1f2027;
- --panel: #22242d;
- --panel2: rgba(255,255,255,.04);
- --text: #e8e8ee;
- --muted: #a9acb8;
- --accent: #33ff7a;
- --accent-dim: rgba(51,255,122,.15);
- --line: rgba(255,255,255,.12);
-}
-
-*{ box-sizing:border-box; }
-
-body{
- margin:0;
- font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
- background: var(--bg);
- color: var(--text);
-}
-
-.siteHeader{
- position: sticky;
- top: 0;
- z-index: 10;
- background: rgba(0,0,0,.15);
- backdrop-filter: blur(8px);
- border-bottom: 1px solid rgba(255,255,255,.06);
-}
-
-.siteHeaderInner{
- max-width: 1200px;
- margin: 0 auto;
- padding: 14px 20px;
- display:flex;
- align-items:center;
- justify-content:space-between;
- gap: 16px;
-}
-
-.brand{
- color: var(--text);
- text-decoration:none;
- font-weight: 900;
- letter-spacing:.02em;
-}
-
-.nav{
- display:flex;
- gap: 14px;
- flex-wrap:wrap;
- justify-content:flex-end;
-}
-.nav a{
- color: var(--muted);
- text-decoration:none;
- font-weight: 700;
- font-size: 14px;
-}
-.nav a:hover{ color: var(--text); }
-
-.siteMain{
- min-height: calc(100vh - 140px);
-}
-
-.siteFooter{
- border-top: 1px solid rgba(255,255,255,.08);
- margin-top: 32px;
- background: rgba(0,0,0,.10);
-}
-
-.siteFooterInner{
- max-width: 1200px;
- margin: 0 auto;
- padding: 18px 20px 26px;
- color: var(--muted);
- font-size: 12px;
- line-height: 1.6;
-}
-
-.footerTitle{
- color: var(--text);
- opacity:.9;
- font-weight: 800;
- margin-bottom: 6px;
-}
diff --git a/src/src/styles/site.css b/src/src/styles/site.css
deleted file mode 100644
index bcaa838..0000000
--- a/src/src/styles/site.css
+++ /dev/null
@@ -1,75 +0,0 @@
-:root{
- --bg: #1f2027;
- --panel: rgba(255,255,255,.04);
- --panel-border: rgba(255,255,255,.10);
- --text: #e8e8ee;
- --muted: #a9acb8;
- --accent: #33ff7a;
- --accent-dim: rgba(51,255,122,.15);
- --line: rgba(255,255,255,.12);
-}
-
-*{ box-sizing: border-box; }
-
-body{
- margin:0;
- font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
- background: var(--bg);
- color: var(--text);
-}
-
-.site-header{
- border-bottom: 1px solid rgba(255,255,255,.08);
- background: rgba(0,0,0,.12);
-}
-
-.site-header__inner{
- max-width: 1200px;
- margin: 0 auto;
- padding: 14px 20px;
- display:flex;
- align-items:center;
- justify-content:space-between;
- gap: 18px;
-}
-
-.brand{
- font-weight: 800;
- letter-spacing: .02em;
-}
-
-.nav{
- display:flex;
- gap: 14px;
- flex-wrap: wrap;
- justify-content:flex-end;
-}
-
-.nav__link{
- color: var(--muted);
- text-decoration:none;
- font-weight: 700;
- font-size: 13px;
-}
-.nav__link:hover{ color: var(--text); }
-
-.site-main{
- max-width: 1200px;
- margin: 0 auto;
- padding: 28px 20px 40px;
- min-height: calc(100vh - 140px);
-}
-
-.site-footer{
- border-top: 1px solid rgba(255,255,255,.08);
- background: rgba(0,0,0,.10);
-}
-
-.site-footer__inner{
- max-width: 1200px;
- margin: 0 auto;
- padding: 16px 20px;
- color: var(--muted);
- font-size: 12px;
- line-height: 1.5;
-}
diff --git a/src/styles/base.css b/src/styles/base.css
new file mode 100644
index 0000000..46400b8
--- /dev/null
+++ b/src/styles/base.css
@@ -0,0 +1,52 @@
+/* src/styles/base.css */
+@import "./md3-tokens.css";
+html, body{ height:100%; }
+body{
+ margin:0;
+ font-family: var(--font-sans);
+ background: var(--md-surface-2);
+ color: var(--md-on-surface);
+}
+a{ color: var(--md-primary); text-decoration: none; }
+a:hover{ text-decoration: underline; }
+.container{
+ max-width: 1100px;
+ margin: 0 auto;
+ padding: 16px;
+}
+.card{
+ background: var(--md-surface);
+ border: 1px solid var(--md-outline);
+ border-radius: var(--radius-2);
+ box-shadow: var(--shadow-1);
+ padding: 16px;
+}
+.btn{
+ display:inline-flex;
+ gap:8px;
+ align-items:center;
+ justify-content:center;
+ border-radius: 999px;
+ border: 1px solid var(--md-outline);
+ background: var(--md-surface);
+ color: var(--md-on-surface);
+ padding: 10px 14px;
+ font-weight: 600;
+ cursor: pointer;
+}
+.btn:hover{ filter: brightness(0.98); }
+.btn:focus{ outline:none; box-shadow: var(--md-focus); }
+.btn-primary{
+ background: var(--md-primary);
+ color: var(--md-on-primary);
+ border-color: transparent;
+}
+.badge{
+ display:inline-block;
+ padding: 2px 10px;
+ border-radius: 999px;
+ font-size: 12px;
+ border: 1px solid var(--md-outline);
+ background: var(--md-surface-2);
+}
+code, pre{ font-family: var(--font-mono); }
diff --git a/src/styles/binary.css b/src/styles/binary.css
deleted file mode 100644
index 6655a70..0000000
--- a/src/styles/binary.css
+++ /dev/null
@@ -1,326 +0,0 @@
-/* Binary page styles (moved OUT of binary.astro) */
-
-:root{
- --panel-w: 360px;
- --gap: 22px;
-}
-
-.wrap{
- max-width: 1400px;
- margin: 0 auto;
- padding: 26px 20px 48px;
- position: relative;
-}
-
-.topGrid{
- display: grid;
- grid-template-columns: 1fr var(--panel-w);
- gap: var(--gap);
- align-items: start;
-}
-
-/* When toolbox is hidden, reclaim space + centre content */
-body.toolboxClosed .topGrid{
- grid-template-columns: 1fr;
-}
-body.toolboxClosed #toolboxPanel{
- display: none;
-}
-
-.mainCol{
- min-width: 0;
-}
-
-.readout{
- text-align: center;
- margin-top: 8px;
-}
-
-.label{
- opacity: .8;
- letter-spacing: .12em;
- text-transform: uppercase;
- font-size: 12px;
-}
-
-/* IMPORTANT: allow shrinking below 4 bits (no min-width!) */
-.num{
- display: inline-block;
- width: fit-content;
- max-width: 100%;
- white-space: pre-line; /* allows JS \n wraps */
- letter-spacing: 2px;
-}
-
-.denaryValue{
- font-size: 54px;
- margin: 6px 0 10px;
-}
-
-.binaryValue{
- font-size: 56px;
- margin: 4px 0 18px;
-}
-
-.divider{
- height: 1px;
- background: rgba(255,255,255,.10);
- margin: 14px auto 24px;
- max-width: 900px;
-}
-
-.bitsWrap{
- padding-top: 6px;
-}
-
-.bitsGrid{
- display: grid;
- gap: 24px;
- justify-content: center;
-}
-
-/* Default: a single row of bits (will wrap automatically as bit count grows) */
-.bitsGrid{
- grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
- max-width: 1200px;
- margin: 0 auto;
-}
-
-.bitsGrid.bitsFew{
- justify-content: center;
-}
-
-/* Bit tile */
-.bit{
- display: grid;
- justify-items: center;
- gap: 8px;
-}
-
-.bulb{
- font-size: 32px; /* JS also bumps this */
- line-height: 1;
- opacity: .45;
-}
-
-.bitVal{
- font-size: 22px;
- line-height: 1.05;
- text-align: center;
- white-space: nowrap; /* keep -128 on one line */
-}
-
-/* Switch (existing classes assumed) */
-.switch{
- position: relative;
- display: inline-block;
- width: 52px;
- height: 28px;
-}
-.switch input{ display:none; }
-.slider{
- position:absolute;
- inset:0;
- border-radius:999px;
- background: rgba(255,255,255,.18);
- border: 1px solid rgba(255,255,255,.14);
-}
-.slider:before{
- content:"";
- position:absolute;
- height: 22px;
- width: 22px;
- left: 3px;
- top: 2.5px;
- border-radius: 999px;
- background: #fff;
- transition: transform .18s ease;
-}
-.switch input:checked + .slider:before{
- transform: translateX(22px);
-}
-
-/* Toolbox toggle button */
-.toolboxToggle{
- position: absolute;
- right: 20px;
- top: 18px;
- z-index: 20;
- display: inline-flex;
- align-items: center;
- gap: 10px;
- padding: 10px 14px;
- border-radius: 12px;
- border: 1px solid rgba(255,255,255,.14);
- background: rgba(255,255,255,.06);
- color: rgba(255,255,255,.92);
- cursor: pointer;
-}
-.toolboxText{
- letter-spacing: .12em;
- font-weight: 900;
-}
-
-/* Toolbox panel */
-.panelCol{
- position: sticky;
- top: calc(var(--nav-h, 72px) + 18px);
- align-self: start;
- display: grid;
- gap: 16px;
-}
-
-/* Cards */
-.card{
- border: 1px solid rgba(255,255,255,.12);
- border-radius: 16px;
- background: rgba(255,255,255,.05);
- padding: 14px;
-}
-.cardTitle{
- opacity: .8;
- letter-spacing: .14em;
- text-transform: uppercase;
- font-size: 12px;
- margin-bottom: 10px;
-}
-
-.hint{
- opacity: .7;
- font-size: 11px;
- margin-top: 10px;
- line-height: 1.35;
-}
-
-/* Keep mode labels on one line */
-.toggleRow{
- display: grid;
- grid-template-columns: 1fr auto 1fr;
- gap: 10px;
- align-items: center;
-}
-.toggleLabel{
- font-size: 12px;
- font-weight: 800;
- letter-spacing: .12em;
- text-transform: uppercase;
- white-space: nowrap;
-}
-
-.subCard{
- margin-top: 12px;
- border: 1px solid rgba(255,255,255,.10);
- border-radius: 14px;
- background: rgba(0,0,0,.12);
- padding: 12px;
-}
-.subTitle{
- opacity: .8;
- letter-spacing: .14em;
- text-transform: uppercase;
- font-size: 11px;
- margin-bottom: 10px;
-}
-
-.bitWidthRow{
- display: grid;
- grid-template-columns: 44px 1fr 44px;
- gap: 10px;
- align-items: center;
-}
-
-.bitInputWrap{
- display: grid;
- grid-template-columns: auto 1fr;
- gap: 10px;
- align-items: center;
- padding: 10px 12px;
- border: 1px solid rgba(255,255,255,.10);
- border-radius: 12px;
- background: rgba(255,255,255,.04);
-}
-.bitInputLabel{
- opacity: .75;
- letter-spacing: .14em;
- text-transform: uppercase;
- font-size: 11px;
- white-space: nowrap;
-}
-.bitInput{
- width: 100%;
- min-width: 0;
- background: transparent;
- border: none;
- outline: none;
- color: inherit;
- font-size: 20px;
- text-align: right;
-}
-
-.miniBtn{
- height: 44px;
- border-radius: 12px;
- border: 1px solid rgba(255,255,255,.12);
- background: rgba(255,255,255,.06);
- color: rgba(255,255,255,.9);
- font-size: 18px;
- cursor: pointer;
-}
-
-/* Buttons */
-.controlsRow{
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 10px;
- margin-bottom: 10px;
-}
-
-.btn{
- border-radius: 12px;
- border: 1px solid rgba(255,255,255,.12);
- background: rgba(255,255,255,.06);
- color: rgba(255,255,255,.92);
- padding: 12px 12px;
- font-weight: 800;
- letter-spacing: .10em;
- text-transform: uppercase;
- cursor: pointer;
-}
-.btnWide{ width: 100%; }
-
-.btnAccent{
- background: rgba(0,255,140,.12);
- border-color: rgba(0,255,140,.22);
-}
-
-.toolRowCentered{
- display: flex;
- justify-content: center;
- gap: 12px;
- margin: 10px 0 12px;
-}
-
-.toolBtn{
- width: 56px;
- height: 56px;
- border-radius: 14px;
- border: 1px solid rgba(255,255,255,.12);
- background: rgba(255,255,255,.06);
- color: rgba(255,255,255,.92);
- font-size: 18px;
- cursor: pointer;
-}
-.toolDec{ background: rgba(255,0,0,.14); border-color: rgba(255,0,0,.20); }
-.toolInc{ background: rgba(0,255,140,.14); border-color: rgba(0,255,140,.20); }
-
-.toolRow2{
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 10px;
- margin-bottom: 12px;
-}
-
-/* Reset stays white text */
-.btnReset{
- color: rgba(255,255,255,.92);
-}
diff --git a/src/styles/fonts.css b/src/styles/fonts.css
new file mode 100644
index 0000000..768cb39
--- /dev/null
+++ b/src/styles/fonts.css
@@ -0,0 +1,11 @@
+@font-face {
+ font-family: "DSEG7";
+ src: url("/fonts/DSEG7Classic-Regular.ttf") format("truetype");
+ font-weight: normal;
+ font-style: normal;
+}
+
+.dseg {
+ font-family: "DSEG7", monospace;
+ letter-spacing: 0.15em;
+}
diff --git a/src/styles/global.css b/src/styles/global.css
index a71bbdf..0a6b418 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -1,85 +1,190 @@
-:root{
+/* Global fonts */
+@font-face {
+ font-family: "SevenSegment";
+ src: url("/fonts/Seven-Segment.woff2") format("woff2"),
+ url("/fonts/Seven-Segment.woff") format("woff");
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: "DSEG7Classic";
+ src: url("/fonts/DSEG7Classic-Regular.woff") format("woff"),
+ url("/fonts/DSEG7Classic-Regular.ttf") format("truetype");
+ font-weight: 400;
+ font-style: normal;
+ font-display: swap;
+}
+
+:root {
+ --nav-h: 92px;
--bg: #1f2027;
- --panel: #22242d;
- --panel2: rgba(255,255,255,.04);
--text: #e8e8ee;
--muted: #a9acb8;
- --accent: #33ff7a;
- --accent-dim: rgba(51,255,122,.15);
- --line: rgba(255,255,255,.12);
+ --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;
+ --bit-font: "SevenSegment", monospace;
}
-*{ box-sizing:border-box; }
+* { 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; }
-body{
- margin:0;
- font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
- background: var(--bg);
- color: var(--text);
+/* --- 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; }
+.brand { display: flex; align-items: center; gap: 12px; text-decoration: none; color: var(--text); }
+.brandLogo { width: 2.5em; height: 2.5em; image-rendering: pixelated; }
+.brandName { letter-spacing: .12em; font-weight: 900; font-size: 18px; }
+.navLinks { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
+.navLinks a { color: var(--muted); text-decoration: none; font-weight: 800; letter-spacing: .12em; font-size: 16px; }
+.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; }
+
+/* --- APP LAYOUT --- */
+.binaryPage {
+ --toolbox-w: 360px;
+ --toolbox-gap: 22px;
+ --toolbox-toggle-top: calc(var(--nav-h) + 16px);
+ --toolbox-top: calc(var(--toolbox-toggle-top) + 60px);
+ position: relative; padding-top: 16px; flex: 1; display: flex; flex-direction: column;
+}
+.binaryPage:not(.toolboxCollapsed) { padding-right: calc(var(--toolbox-w) + var(--toolbox-gap)); }
+.binaryPage.toolboxCollapsed { padding-right: 0; }
+.topGrid { display: flex; align-items: stretch; gap: 28px; flex: 1; }
+.leftCol { flex: 1 1 auto; min-width: 0; container-type: inline-size; display: flex; flex-direction: column; }
+
+/* --- READOUT FORMATTING --- */
+.readoutContainer { display: flex; align-items: center; justify-content: center; gap: 64px; width: 100%; }
+.readout { display: flex; flex-direction: column; align-items: center; gap: 16px; padding-top: 4px; }
+.readoutBlock { display: flex; flex-direction: column; align-items: center; gap: 8px; }
+.label { font-family: var(--bit-font); letter-spacing: .14em; text-transform: uppercase; font-size: 18px; opacity: .75; margin: 0; }
+.num { font-family: var(--num-font); color: #28f07a; text-shadow: 0 0 18px rgba(40,240,122,.35); letter-spacing: 2px; }
+
+.denaryValue, .hexValue, .binaryValue { display: flex; gap: 16px; justify-content: center; white-space: pre-wrap; text-align: center; margin: 0; line-height: 1; }
+.denaryValue { font-size: 56px; }
+.hexValue { font-size: 48px; }
+.binaryValue { font-size: 40px; }
+.divider { height: 1px; background: rgba(255,255,255,.08); margin: 16px 0 16px; }
+
+/* --- GRIDS & BITS --- */
+.bitsWrap { width: 100%; }
+.bitsGrid { --cols: 8; display: grid; grid-template-columns: repeat(var(--cols), minmax(92px, 1fr)); gap: 26px 22px; align-items: start; justify-items: center; }
+.bitsGrid.bitsFew { justify-content: center; }
+.bit { width: 100%; max-width: 140px; display: flex; flex-direction: column; align-items: center; gap: 8px; container-type: inline-size; }
+.bitVal { font-family: var(--bit-font); font-size: min(32px, calc(140cqw / var(--len, 1))); letter-spacing: 2px; color: rgba(232,232,238,.85); white-space: nowrap; line-height: 1; }
+
+.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; }
+
+.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); }
+
+/* --- HEXADECIMAL --- */
+.hexGrid { --cols: 4; display: grid; grid-template-columns: repeat(var(--cols), minmax(160px, 1fr)); gap: 32px 20px; align-items: start; justify-items: center; width: 100%; }
+.hexGrid.bitsFew { justify-content: center; }
+.hexCol { display: flex; flex-direction: column; align-items: center; width: 100%; }
+
+/* --- HEX COLOURS SPECIFIC --- */
+.colorGroupWrap { display: flex; flex-wrap: nowrap; gap: 16px; justify-content: center; width: 100%; }
+.colorGroup { display: flex; gap: 12px; padding: 12px; background: rgba(255,255,255,.02); border-radius: 20px; border: 1px solid rgba(255,255,255,.05); flex: 0 1 auto; min-width: 0; }
+.colorPreviewSide { display: flex; gap: 24px; align-items: center; justify-content: center; }
+.colorBoxWrap { display: flex; flex-direction: column; align-items: center; gap: 8px; }
+.colorBox { width: 60px; height: 60px; border-radius: 12px; border: 2px solid rgba(255,255,255,.15); box-shadow: 0 4px 16px rgba(0,0,0,.4); background-color: #000000; transition: background-color 0.2s ease; }
+.colorBoxLabel { font-family: var(--ui-font); font-size: 11px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
+
+.text-red { color: #ff5555 !important; text-shadow: 0 0 18px rgba(255,85,85,.35) !important; }
+.text-green { color: #28f07a !important; text-shadow: 0 0 18px rgba(40,240,122,.35) !important; }
+.text-blue { color: #55aaff !important; text-shadow: 0 0 18px rgba(85,170,255,.35) !important; }
+
+/* HEX CARD */
+.hexCard { background: rgba(255,255,255,.03); border: 1px solid rgba(255,255,255,.08); border-radius: 16px; padding: 16px 14px; display: flex; flex-direction: column; align-items: center; gap: 16px; width: 100%; max-width: 190px; flex: 0 1 auto; min-width: 0; box-shadow: 0 4px 24px rgba(0,0,0,.2); backdrop-filter: blur(10px); }
+.hexCardButtons { display: flex; gap: 10px; }
+.hexCardBtn { width: 38px; height: 38px; border-radius: 10px; border: 1px solid rgba(255,255,255,.12); font-family: var(--bit-font); font-size: 16px; font-weight: 900; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0; color: rgba(232,232,238,.92); transition: all 0.2s ease; }
+.hexCardBtn.inc { background: rgba(40,240,122,.15); border-color: rgba(40,240,122,.25); }
+.hexCardBtn.inc:hover { background: rgba(40,240,122,.25); border-color: rgba(40,240,122,.4); }
+.hexCardBtn.dec { background: rgba(255,80,80,.18); border-color: rgba(255,80,80,.25); }
+.hexCardBtn.dec:hover { background: rgba(255,80,80,.28); border-color: rgba(255,80,80,.4); }
+.hexDigitDisplay { font-family: var(--num-font); font-size: 48px; color: #28f07a; text-shadow: 0 0 18px rgba(40,240,122,.35); line-height: 1; }
+.hexNibbleRow { display: flex; gap: 10px; justify-content: center; width: 100%; }
+.hexNibbleBit { display: flex; flex-direction: column; align-items: center; gap: 6px; }
+.hexNibbleBulb { width: 32px !important; height: 32px !important; margin-bottom: 2px !important; }
+.hexNibbleLabel { font-family: var(--bit-font); font-size: 24px; color: rgba(232,232,238,.6); }
+.hexColWeight { font-family: var(--bit-font); font-size: 40px; color: rgba(232,232,238,.6); margin-top: 14px; }
+
+
+/* --- TOOLBOX --- */
+.toolboxToggle { position: fixed; top: var(--toolbox-toggle-top); 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); }
+.panelCol { position: fixed; top: var(--toolbox-top); right: 22px; width: var(--toolbox-w); z-index: 80; display: flex; flex-direction: column; gap: 16px; transform: translateX(0); opacity: 1; transition: transform 420ms cubic-bezier(.2,.9,.2,1), opacity 220ms ease; }
+.binaryPage.toolboxCollapsed .panelCol { transform: translateX(calc(var(--toolbox-w) + 32px)); opacity: 0; pointer-events: none; }
+.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; }
+.toggleRow { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
+.toggleLabel { font-family: var(--bit-font); font-size: 16px; letter-spacing: .12em; text-transform: uppercase; white-space: nowrap; color: var(--muted); transition: color 0.2s, text-shadow 0.2s; }
+.toggleLabel.activeMode { color: #28f07a; text-shadow: 0 0 12px rgba(40,240,122,.45); }
+.subCard { margin-top: 12px; padding: 12px; border-radius: 14px; border: 1px solid rgba(255,255,255,.10); background: rgba(0,0,0,.12); }
+.subTitle { font-family: var(--bit-font); font-weight: 900; letter-spacing: .14em; text-transform: uppercase; font-size: 16px; margin-bottom: 10px; opacity: .85; }
+.bitWidthRow { display: flex; align-items: center; gap: 10px; }
+.miniBtn { width: 44px; height: 44px; border-radius: 12px; border: 1px solid rgba(255,255,255,.12); background: rgba(255,255,255,.06); color: rgba(232,232,238,.9); font-family: var(--bit-font); font-weight: 900; font-size: 22px; cursor: pointer; }
+.miniBtn:hover { border-color: rgba(255,255,255,.22); }
+.bitInputWrap { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 10px; border-radius: 12px; border: 1px solid rgba(255,255,255,.10); background: rgba(255,255,255,.04); padding: 10px 12px; }
+.bitInputLabel { font-family: var(--bit-font); font-size: 16px; letter-spacing: .12em; text-transform: uppercase; opacity: .7; }
+.bitInput { width: 70px; text-align: right; font-family: var(--num-font); font-size: 28px; letter-spacing: 2px; color: #28f07a; background: transparent; border: none; outline: none; }
+.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%; }
+.controlsRow { display: flex; gap: 12px; margin-bottom: 12px; }
+.toolRowCentered { display: flex; justify-content: center; gap: 10px; margin-bottom: 10px; }
+.toolBtn { width: 46px; height: 46px; border-radius: 12px; border: 1px solid rgba(255,255,255,.12); background: rgba(255,255,255,.06); color: rgba(232,232,238,.92); font-family: var(--bit-font); font-size: 16px; font-weight: 900; cursor: pointer; }
+.toolDec { background: rgba(255,80,80,.20); border-color: rgba(255,80,80,.25); }
+.toolInc { background: rgba(40,240,122,.18); border-color: rgba(40,240,122,.25); }
+.btnReset { color: rgba(232,232,238,.95); }
+.btnReset:hover { background: rgba(255,80,80,.18); border-color: rgba(255,80,80,.35); }
+
+/* === CONTAINER QUERIES === */
+@container (max-width: 1050px) {
+ .readoutContainer { gap: 40px; }
+ .colorGroupWrap { gap: 10px; }
+ .colorGroup { padding: 10px; gap: 8px; border-radius: 16px; }
+ .hexCard { padding: 12px 8px; width: 140px; gap: 12px; }
+ .hexDigitDisplay { font-size: 40px; }
+ .hexNibbleBulb { width: 24px !important; height: 24px !important; }
+ .hexNibbleLabel { font-size: 20px; }
+ .hexColWeight { font-size: 26px; margin-top: 10px; }
+ .hexCardBtn { width: 34px; height: 34px; font-size: 14px; }
}
-.siteHeader{
- position: sticky;
- top: 0;
- z-index: 10;
- background: rgba(0,0,0,.15);
- backdrop-filter: blur(8px);
- border-bottom: 1px solid rgba(255,255,255,.06);
+@container (max-width: 800px) {
+ .readoutContainer { flex-direction: column; gap: 24px; }
+ .colorPreviewSide { padding-top: 0; }
+ .colorGroupWrap { gap: 6px; }
+ .colorGroup { padding: 6px; gap: 6px; border-radius: 12px; }
+ .hexCard { padding: 8px 4px; width: 90px; gap: 8px; border-radius: 10px; }
+ .hexDigitDisplay { font-size: 32px; }
+ .hexNibbleBulb { width: 16px !important; height: 16px !important; }
+ .hexNibbleLabel { font-size: 16px; }
+ .hexColWeight { font-size: 20px; margin-top: 6px; }
+ .hexCardBtn { width: 28px; height: 28px; font-size: 12px; }
+ .denaryValue, .hexValue, .binaryValue { font-size: 32px; gap: 10px; }
}
-.siteHeaderInner{
- max-width: 1200px;
- margin: 0 auto;
- padding: 14px 20px;
- display:flex;
- align-items:center;
- justify-content:space-between;
- gap: 16px;
-}
-
-.brand{
- color: var(--text);
- text-decoration:none;
- font-weight: 900;
- letter-spacing:.02em;
-}
-
-.nav{
- display:flex;
- gap: 14px;
- flex-wrap:wrap;
- justify-content:flex-end;
-}
-.nav a{
- color: var(--muted);
- text-decoration:none;
- font-weight: 700;
- font-size: 14px;
-}
-.nav a:hover{ color: var(--text); }
-
-.siteMain{
- min-height: calc(100vh - 140px);
-}
-
-.siteFooter{
- border-top: 1px solid rgba(255,255,255,.08);
- margin-top: 32px;
- background: rgba(0,0,0,.10);
-}
-
-.siteFooterInner{
- max-width: 1200px;
- margin: 0 auto;
- padding: 18px 20px 26px;
- color: var(--muted);
- font-size: 12px;
- line-height: 1.6;
-}
-
-.footerTitle{
- color: var(--text);
- opacity:.9;
- font-weight: 800;
- margin-bottom: 6px;
-}
+@media (max-width: 1100px) { .binaryPage { --toolbox-w: 330px; } .denaryValue { font-size: 48px; } .hexValue { font-size: 40px; } .binaryValue { font-size: 32px; } }
+@media (max-width: 900px) { .binaryPage { --toolbox-w: 320px; } .bitsGrid { grid-template-columns: repeat(var(--cols), minmax(84px, 1fr)); } .hexGrid { grid-template-columns: repeat(var(--cols), minmax(130px, 1fr)); } }
\ No newline at end of file
diff --git a/src/styles/logic-gates.css b/src/styles/logic-gates.css
new file mode 100644
index 0000000..36d054d
--- /dev/null
+++ b/src/styles/logic-gates.css
@@ -0,0 +1,163 @@
+/* === LOGIC GATES CANVAS CSS === */
+.lg-workspace {
+ position: relative;
+ flex: 1;
+ width: 100%;
+ min-height: 750px;
+ background-color: transparent;
+ background-image: radial-gradient(rgba(255,255,255,0.12) 1px, transparent 1px);
+ background-size: 24px 24px;
+ border: none;
+ border-radius: 0;
+ box-shadow: none;
+ overflow: hidden;
+}
+
+.lg-svg-layer {
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 1;
+}
+
+/* Wires */
+.lg-wire {
+ stroke: rgba(255,255,255,0.25);
+ stroke-width: 6;
+ fill: none;
+ stroke-linecap: round;
+ transition: stroke 0.1s ease, filter 0.1s ease, stroke-width 0.1s ease;
+ pointer-events: stroke; /* Allows wires to be clicked */
+ cursor: pointer;
+}
+.lg-wire:hover {
+ stroke: rgba(255,255,255,0.6);
+ stroke-width: 10;
+}
+.lg-wire.active {
+ stroke: #28f07a;
+ filter: drop-shadow(0 0 6px rgba(40,240,122,0.6));
+}
+.lg-wire.active:hover { stroke: #5dff9e; }
+.lg-wire.selected {
+ stroke: #ff5555 !important;
+ stroke-width: 8 !important;
+ stroke-dasharray: 8 8;
+ filter: drop-shadow(0 0 8px rgba(255,85,85,0.8)) !important;
+ animation: wireDash 1s linear infinite;
+}
+@keyframes wireDash { to { stroke-dashoffset: -16; } }
+
+.lg-wire-temp {
+ stroke: rgba(255,255,255,0.4);
+ stroke-dasharray: 8 8;
+ pointer-events: none;
+}
+
+/* Nodes (Borderless & Transparent) */
+.lg-node {
+ position: absolute;
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ padding: 4px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ cursor: grab;
+ z-index: 10;
+ box-shadow: none;
+ backdrop-filter: none;
+ user-select: none;
+ transition: filter 0.2s;
+}
+.lg-node:active { cursor: grabbing; z-index: 20; }
+.lg-node.selected {
+ filter: drop-shadow(0 0 10px rgba(255,85,85,0.8));
+}
+
+/* Node Labels (Seven-Segment, +2 Sizes Bigger) */
+.lg-header {
+ font-size: 24px;
+ color: var(--muted);
+ font-family: var(--bit-font);
+ letter-spacing: 2px;
+ pointer-events: none;
+ margin-bottom: 6px;
+}
+
+/* Container mapping SVGs to absolutely positioned connection dots */
+.lg-gate-container {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+}
+
+.lg-gate-svg {
+ width: 100px;
+ height: 50px;
+ display: block;
+}
+
+.lg-line-svg {
+ width: 30px;
+ height: 50px;
+ display: block;
+}
+
+/* Connection Ports */
+.lg-port {
+ width: 16px; height: 16px; background: #a9acb8; border-radius: 50%; cursor: crosshair;
+ border: 3px solid var(--bg); box-shadow: 0 0 0 1px rgba(255,255,255,0.2); transition: all 0.2s;
+ position: absolute; z-index: 5;
+ transform: translate(-50%, -50%); /* Centers the dot exactly over the coordinate */
+}
+.lg-port:hover { transform: translate(-50%, -50%) scale(1.3); background: #fff; }
+.lg-port.active { background: #28f07a; box-shadow: 0 0 12px rgba(40,240,122,0.8); border-color: #1f2027; }
+
+/* Draggable Toolbox Visual Gates Grid */
+.tb-icon-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 12px;
+}
+.tb-icon-box {
+ background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.15);
+ border-radius: 12px; width: 100%; padding: 12px 0;
+ display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
+ cursor: grab; transition: background 0.2s, border-color 0.2s;
+}
+.tb-icon-box:hover { background: rgba(255,255,255,0.1); border-color: rgba(255,255,255,0.3); }
+.tb-icon-label { font-family: var(--ui-font); font-size: 11px; font-weight: 800; color: var(--text); letter-spacing: 1px; text-transform: uppercase; }
+
+/* Toolbox Scroll Fix */
+.panelCol {
+ max-height: calc(100vh - var(--nav-h) - 30px) !important;
+ overflow-y: auto;
+ padding-bottom: 30px;
+ pointer-events: auto;
+}
+
+/* Truth Table */
+.tt-summary {
+ font-family: var(--ui-font); font-size: 14px; font-weight: 800;
+ color: var(--accent, #28f07a); cursor: pointer; user-select: none;
+ outline: none; margin-bottom: 10px; text-transform: uppercase;
+}
+.tt-table-wrap {
+ width: 100%; max-height: 300px; overflow-y: auto; overflow-x: auto;
+ border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(0,0,0,0.2);
+}
+.tt-table {
+ width: 100%; border-collapse: collapse; text-align: center;
+ font-family: var(--num-font); font-size: 14px; color: #e8e8ee;
+}
+.tt-table th {
+ position: sticky; top: 0; background: rgba(31,32,39,0.95);
+ padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.15);
+ color: var(--muted); font-family: var(--bit-font); font-weight: normal;
+}
+.tt-table td { padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.05); }
+.tt-table .tt-on { color: #28f07a; text-shadow: 0 0 8px rgba(40,240,122,0.5); }
\ No newline at end of file
diff --git a/src/styles/md3-tokens.css b/src/styles/md3-tokens.css
new file mode 100644
index 0000000..beb9cf3
--- /dev/null
+++ b/src/styles/md3-tokens.css
@@ -0,0 +1,43 @@
+/* src/styles/md3-tokens.css */
+/* MD3-inspired tokens tuned for education: high readability, clear contrast, calm surfaces */
+:root{
+ /* Typography */
+ --font-sans: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
+ /* Spacing + shape */
+ --radius-1: 10px;
+ --radius-2: 16px;
+ --radius-3: 22px;
+ --shadow-1: 0 1px 2px rgba(0,0,0,.08), 0 2px 8px rgba(0,0,0,.06);
+ /* Color roles (keep simple) */
+ --md-surface: #ffffff;
+ --md-surface-2: #f6f7fb;
+ --md-on-surface: #111318;
+ --md-primary: #2f6fed; /* calm blue */
+ --md-on-primary: #ffffff;
+ --md-secondary: #5a5f72; /* muted */
+ --md-on-secondary: #ffffff;
+ --md-tertiary: #0f766e; /* teal for "practical" tools */
+ --md-on-tertiary: #ffffff;
+ --md-outline: #d7dbe7;
+ --md-success: #1a7f37;
+ --md-warning: #b54708;
+ --md-danger: #b42318;
+ /* Focus ring for accessibility */
+ --md-focus: 0 0 0 3px rgba(47,111,237,.28);
+}
+@media (prefers-color-scheme: dark){
+ :root{
+ --md-surface: #0b0e14;
+ --md-surface-2: #121725;
+ --md-on-surface: #e8eaf2;
+ --md-primary: #9bb6ff;
+ --md-on-primary: #0b0e14;
+ --md-secondary: #b8bccd;
+ --md-on-secondary: #0b0e14;
+ --md-tertiary: #4fd1c5;
+ --md-on-tertiary: #0b0e14;
+ --md-outline: #2b3244;
+ --md-focus: 0 0 0 3px rgba(155,182,255,.25);
+ }
+}
To get started, open the src/pages
directory in your project.
src/pages
What's New in Astro 5.0?
From content layers to server islands, click to learn more about the new features and diff --git a/dist/js/binary/unsigned-binary.js b/dist/js/binary/unsigned-binary.js new file mode 100644 index 0000000..e28ab9f --- /dev/null +++ b/dist/js/binary/unsigned-binary.js @@ -0,0 +1,115 @@ +// Browser-only script. Safe because it's loaded via + + diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 022fd45..6fa4a20 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -1,4 +1,6 @@ --- +import '../styles/global.css'; + const { title = "Computing:Box" } = Astro.props; --- @@ -8,105 +10,25 @@ const { title = "Computing:Box" } = Astro.props;
Show / Hide Table
+| ${n.label} | `); + outNodes.forEach(n => html += `${n.label} | `); + html += '
|---|---|
| ${val ? 1 : 0} | `; + }); + outNodes.forEach(n => { + let val = outStates[n.id]; + html += `${val ? 1 : 0} | `; + }); + html += '
- To get started, open the src/pages
directory in your project.
-
- src/pages
What's New in Astro 5.0?
-- From content layers to server islands, click to learn more about the new features and - improvements in Astro 5.0 -
- -