From 1519032f5b6a2c88e3551cf46e1a70e59c370fc8 Mon Sep 17 00:00:00 2001 From: Alexander Lyall Date: Tue, 16 Dec 2025 14:27:51 +0000 Subject: [PATCH] Fixed the dual bulb issue, added new font to typeface package Signed-off-by: Alexander Lyall --- public/fonts/Seven-Segment.woff | Bin 0 -> 8828 bytes public/fonts/Seven-Segment.woff2 | Bin 0 -> 6760 bytes src/pages/binary.astro | 31 +- src/scripts/binary.js | 671 +++++++++++++++++++------------ src/styles/binary.css | 115 ++++-- 5 files changed, 508 insertions(+), 309 deletions(-) create mode 100644 public/fonts/Seven-Segment.woff create mode 100644 public/fonts/Seven-Segment.woff2 diff --git a/public/fonts/Seven-Segment.woff b/public/fonts/Seven-Segment.woff new file mode 100644 index 0000000000000000000000000000000000000000..5a388a972668a42795a218737d3686d85071f186 GIT binary patch literal 8828 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 @@ -11,26 +19,31 @@ import "../styles/binary.css"; - +
- +
Denary
0
Binary
-
0000 0000
+
0
+
+
@@ -40,13 +53,12 @@ import "../styles/binary.css";
-
- +
+
Tools
-
-
@@ -115,7 +126,7 @@ import "../styles/binary.css";
- - + + diff --git a/src/scripts/binary.js b/src/scripts/binary.js index d3b25bd..6fb1103 100644 --- a/src/scripts/binary.js +++ b/src/scripts/binary.js @@ -1,353 +1,508 @@ // src/scripts/binary.js +// Computing:Box — Binary page logic (Unsigned + Two's Complement) +// NOTE: This file is written to match the IDs/classes in your current binary.astro HTML. -document.addEventListener("DOMContentLoaded", () => { +(() => { + /* ----------------------------- + 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 bitsInput = document.getElementById("bitsInput"); - const btnBitsUp = document.getElementById("btnBitsUp"); - const btnBitsDown = document.getElementById("btnBitsDown"); + const lblUnsigned = document.getElementById("lblUnsigned"); + const lblTwos = document.getElementById("lblTwos"); 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 btnInc = document.getElementById("btnInc"); - const btnDec = document.getElementById("btnDec"); - let bitCount = clampInt(Number(bitsInput.value || 8), 1, 64); - let isTwos = false; + const btnBitsUp = document.getElementById("btnBitsUp"); + const btnBitsDown = document.getElementById("btnBitsDown"); - // Bits stored MSB -> LSB (index 0 is MSB) + /* ----------------------------- + STATE + ----------------------------- */ + let bitCount = clampInt(Number(bitsInput?.value ?? 8), 1, 64); + + // bits[i] is bit value 2^i (LSB at i=0) let bits = new Array(bitCount).fill(false); - // Random timer + // Random run timer (brief) let randomTimer = null; + /* ----------------------------- + HELPERS + ----------------------------- */ function clampInt(n, min, max) { - n = Number(n); if (!Number.isFinite(n)) return min; - n = Math.floor(n); - return Math.max(min, Math.min(max, n)); + return Math.max(min, Math.min(max, Math.trunc(n))); } - function pow2(exp) { - // exp can be up to 63; JS Number is fine for display and basic use here - return 2 ** exp; + function isTwosMode() { + return !!modeToggle?.checked; } - function buildBits(count) { - bitsGrid.innerHTML = ""; - bits = new Array(count).fill(false); - bitCount = count; + function pow2Big(n) { + return 1n << BigInt(n); + } - // Grid wrap at 8 bits per row; also center for small counts - if (count < 8) { - bitsGrid.classList.add("bitsFew"); - bitsGrid.style.setProperty("--cols", String(count)); + function unsignedMaxExclusive(nBits) { + return pow2Big(nBits); // 2^n + } + + 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 v = ((vUnsigned % unsignedMaxExclusive(bitCount)) + unsignedMaxExclusive(bitCount)) % unsignedMaxExclusive(bitCount); + 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; + + // negative: u - 2^n + return u - pow2Big(bitCount); + } + + function signedBigIntToBitsTwos(vSigned) { + // wrap into range [-2^(n-1), 2^(n-1)-1] + const min = twosMin(bitCount); + const max = twosMax(bitCount); + const span = pow2Big(bitCount); // 2^n + + let v = vSigned; + + // wrap using modular arithmetic on signed domain + // Convert to unsigned representative: v mod 2^n + v = ((v % span) + span) % span; + + unsignedBigIntToBits(v); + // labels/denary will show signed later + // (No further action needed here) + } + + function formatBinaryGrouped() { + // MSB..LSB with a space every 4 bits (matches your screenshot 0000 0000) + 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 += " "; + } + return s; + } + + function updateModeHint() { + if (!modeHint) return; + 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."; + } + } + + /* ----------------------------- + BUILD UI (BITS) + ----------------------------- */ + function buildBits(count) { + bitCount = clampInt(count, 1, 64); + if (bitsInput) bitsInput.value = String(bitCount); + + // reset bits array size, preserve existing LSBs where possible + 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 = ""; + + // If less than 8 bits, centre nicely using your CSS helper + bitsGrid.classList.toggle("bitsFew", bitCount < 8); + if (bitCount < 8) { + bitsGrid.style.setProperty("--cols", String(bitCount)); } else { - bitsGrid.classList.remove("bitsFew"); bitsGrid.style.removeProperty("--cols"); } - for (let i = 0; i < count; i++) { - const isMSB = i === 0; - const valueUnsigned = pow2(count - 1 - i); // MSB is 2^(n-1) + // Render MSB..LSB left-to-right + for (let i = bitCount - 1; i >= 0; i--) { + const bitEl = document.createElement("div"); + bitEl.className = "bit"; - const bit = document.createElement("div"); - bit.className = "bit"; - - bit.innerHTML = ` - -
${valueUnsigned}
+ // IMPORTANT: We render the bulb as an emoji with NO circle/ring. + // We do not rely on the .bulb CSS ring/background at all. + bitEl.innerHTML = ` + +
`; - bitsGrid.appendChild(bit); + bitsGrid.appendChild(bitEl); } - hookSwitches(); - updateModeLabels(); - updateReadout(); - } - - function hookSwitches() { - bitsGrid.querySelectorAll('input[type="checkbox"][data-index]').forEach((input) => { + // Hook switches + bitsGrid.querySelectorAll('input[type="checkbox"]').forEach((input) => { input.addEventListener("change", () => { const i = Number(input.dataset.index); bits[i] = input.checked; - updateReadout(); + updateUI(); }); }); - } - function updateModeLabels() { - isTwos = Boolean(modeToggle.checked); - - modeHint.textContent = isTwos - ? "Tip: In two’s complement, the left-most bit (MSB) represents a negative value." - : "Tip: In unsigned binary, all bits represent positive values."; - - // Update the labels so the MSB shows negative weight in two's complement - for (let i = 0; i < bitCount; i++) { - const label = document.getElementById(`label-${i}`); - if (!label) continue; - - const unsignedWeight = pow2(bitCount - 1 - i); - - if (isTwos && i === 0) { - // MSB weight is negative - label.textContent = `-${unsignedWeight}`; - } else { - label.textContent = `${unsignedWeight}`; - } - } - } - - function formatBinaryString(raw) { - // group every 4 for readability (keeps your "0000 0000" look) - return raw.replace(/(.{4})/g, "$1 ").trim(); - } - - function computeUnsignedValue() { - let value = 0; - for (let i = 0; i < bitCount; i++) { - if (!bits[i]) continue; - value += pow2(bitCount - 1 - i); - } - return value; - } - - function computeTwosValue() { - // If MSB is 0 -> same as unsigned - const msb = bits[0] ? 1 : 0; - let value = computeUnsignedValue(); - if (msb === 1) { - // subtract 2^n to get signed negative value - value -= pow2(bitCount); - } - return value; - } - - function updateReadout() { - // Binary string (MSB->LSB) - const rawBinary = bits.map((b) => (b ? "1" : "0")).join(""); - binaryEl.textContent = formatBinaryString(rawBinary); - - // Denary value based on mode - const denary = isTwos ? computeTwosValue() : computeUnsignedValue(); - denaryEl.textContent = String(denary); - - // Bulbs MUST update in BOTH modes (this was your reported bug) + // Force the bulb to be "just the emoji" (removes the circle even if CSS adds it) for (let i = 0; i < bitCount; i++) { const bulb = document.getElementById(`bulb-${i}`); if (!bulb) continue; - bulb.classList.toggle("on", bits[i]); + + // Strip the ring/circle coming from CSS + 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 = "26px"; + bulb.style.lineHeight = "1"; + bulb.style.display = "flex"; + bulb.style.alignItems = "center"; + bulb.style.justifyContent = "center"; + bulb.style.filter = "grayscale(1)"; + bulb.textContent = "💡"; + } + + updateUI(); + } + + /* ----------------------------- + UI UPDATE (READOUT + LABELS + BULBS + SWITCHES) + ----------------------------- */ + function updateBitLabels() { + // Show weights under each bit. + // Unsigned: 2^i + // Two's: MSB is -2^(n-1), others are 2^i + for (let i = 0; i < bitCount; i++) { + const label = document.getElementById(`bitLabel-${i}`); + if (!label) continue; + + if (isTwosMode() && i === bitCount - 1) { + label.textContent = `-${pow2Big(bitCount - 1).toString()}`; + } else { + label.textContent = pow2Big(i).toString(); + } } } - function syncInputs() { - bitsGrid.querySelectorAll('input[type="checkbox"][data-index]').forEach((input) => { + function syncSwitchesToBits() { + bitsGrid.querySelectorAll('input[type="checkbox"]').forEach((input) => { const i = Number(input.dataset.index); - input.checked = Boolean(bits[i]); + input.checked = !!bits[i]; }); + } + + function updateBulbs() { + // Bulbs should ALWAYS reflect bits, regardless of mode. + for (let i = 0; i < bitCount; i++) { + const bulb = document.getElementById(`bulb-${i}`); + if (!bulb) continue; + + const on = bits[i] === true; + + // Make it look "lit" when on (no circle, just glow) + if (on) { + bulb.style.opacity = "1"; + bulb.style.filter = "grayscale(0)"; + bulb.style.textShadow = "0 0 14px rgba(255,216,107,.75), 0 0 26px 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; + + if (isTwosMode()) { + const signed = bitsToSignedBigIntTwos(); + denaryEl.textContent = signed.toString(); + } else { + const unsigned = bitsToUnsignedBigInt(); + denaryEl.textContent = unsigned.toString(); + } + + binaryEl.textContent = formatBinaryGrouped(); + } + + function updateUI() { + updateModeHint(); + updateBitLabels(); + syncSwitchesToBits(); + updateBulbs(); updateReadout(); } - function setAllBits(off = true) { - bits = bits.map(() => !off); - syncInputs(); + /* ----------------------------- + SET FROM BINARY STRING + ----------------------------- */ + function setFromBinaryString(binStr) { + const clean = String(binStr ?? "").replace(/\s+/g, ""); + if (!/^[01]+$/.test(clean)) return false; + + // Use rightmost bitCount bits; left pad with 0 + const padded = clean.slice(-bitCount).padStart(bitCount, "0"); + + for (let i = 0; i < bitCount; i++) { + // padded is MSB..LSB, bits[] is LSB..MSB + 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; + + // BigInt parse (supports negatives) + let v; + try { + // Allow normal integers only + if (!/^-?\d+$/.test(raw)) return false; + v = BigInt(raw); + } catch { + return false; + } + + if (isTwosMode()) { + // Clamp to representable range + const min = twosMin(bitCount); + const max = twosMax(bitCount); + if (v < min || v > max) return false; + + signedBigIntToBitsTwos(v); + } else { + // Unsigned only + if (v < 0n) return false; + if (v > unsignedMaxValue(bitCount)) return false; + + unsignedBigIntToBits(v); + } + + updateUI(); + return true; + } + + /* ----------------------------- + SHIFTS + ----------------------------- */ function shiftLeft() { - // left shift: drop MSB, append 0 at LSB - bits.shift(); - bits.push(false); - syncInputs(); + // logical left shift: bits move to higher index; LSB becomes 0 + for (let i = bitCount - 1; i >= 1; i--) { + bits[i] = bits[i - 1]; + } + bits[0] = false; + updateUI(); } function shiftRight() { - // right shift: drop LSB, prepend 0 at MSB - bits.pop(); - bits.unshift(false); - syncInputs(); - } - - function setFromBinary(input) { - const clean = String(input).replace(/\s+/g, ""); - if (!/^[01]+$/.test(clean)) return false; - - const padded = clean.slice(-bitCount).padStart(bitCount, "0"); - bits = [...padded].map((ch) => ch === "1"); - syncInputs(); - return true; - } - - function setFromDenary(input) { - let n = Number(input); - if (!Number.isInteger(n)) return false; - - // For unsigned mode: allow 0..(2^n - 1) - // For two's mode: allow -(2^(n-1))..(2^(n-1)-1) - const maxUnsigned = pow2(bitCount) - 1; - const minTwos = -pow2(bitCount - 1); - const maxTwos = pow2(bitCount - 1) - 1; - - if (!isTwos) { - if (n < 0 || n > maxUnsigned) return false; - // build bits from unsigned n - bits = new Array(bitCount).fill(false); - for (let i = 0; i < bitCount; i++) { - const weight = pow2(bitCount - 1 - i); - if (n >= weight) { - bits[i] = true; - n -= weight; - } - } - syncInputs(); - return true; + // logical right shift: bits move to lower index; MSB becomes 0 + for (let i = 0; i < bitCount - 1; i++) { + bits[i] = bits[i + 1]; } + bits[bitCount - 1] = false; + updateUI(); + } - // Two's complement: convert signed integer to n-bit representation - if (n < minTwos || n > maxTwos) return false; - - let u = n; - if (u < 0) u = pow2(bitCount) + u; // wrap into unsigned range - const bin = u.toString(2).padStart(bitCount, "0"); - bits = [...bin].map((ch) => ch === "1"); - syncInputs(); - return true; + /* ----------------------------- + CLEAR / INC / DEC + ----------------------------- */ + function clearAll() { + bits.fill(false); + updateUI(); } function increment() { - // increment the underlying value in current mode, wrap appropriately - if (!isTwos) { - const max = pow2(bitCount) - 1; - let v = computeUnsignedValue(); - v = (v + 1) % (max + 1); - setFromDenary(v); - return; + if (isTwosMode()) { + const min = twosMin(bitCount); + const max = twosMax(bitCount); + let v = bitsToSignedBigIntTwos() + 1n; + if (v > max) v = min; // wrap + signedBigIntToBitsTwos(v); + } else { + const span = unsignedMaxExclusive(bitCount); + const v = (bitsToUnsignedBigInt() + 1n) % span; + unsignedBigIntToBits(v); } - - const min = -pow2(bitCount - 1); - const max = pow2(bitCount - 1) - 1; - let v = computeTwosValue(); - v = v + 1; - if (v > max) v = min; // wrap - setFromDenary(v); + updateUI(); } function decrement() { - if (!isTwos) { - const max = pow2(bitCount) - 1; - let v = computeUnsignedValue(); - v = v - 1; - if (v < 0) v = max; - setFromDenary(v); - return; + if (isTwosMode()) { + const min = twosMin(bitCount); + const max = twosMax(bitCount); + let v = bitsToSignedBigIntTwos() - 1n; + if (v < min) v = max; // wrap + signedBigIntToBitsTwos(v); + } else { + const span = unsignedMaxExclusive(bitCount); + const v = (bitsToUnsignedBigInt() - 1n + span) % span; + unsignedBigIntToBits(v); } - - const min = -pow2(bitCount - 1); - const max = pow2(bitCount - 1) - 1; - let v = computeTwosValue(); - v = v - 1; - if (v < min) v = max; - setFromDenary(v); + updateUI(); } - function startAutoRandom() { - stopAutoRandom(); + /* ----------------------------- + RANDOM (FIXED: NO BigInt->Number Math.min) + ----------------------------- */ + function cryptoRandomBigInt(maxExclusive) { + // returns 0 <= x < maxExclusive + if (maxExclusive <= 0n) return 0n; - const durationMs = 1200; // runs briefly then stops - const tickMs = 90; + const bitLen = maxExclusive.toString(2).length; + const byteLen = Math.ceil(bitLen / 8); + + // Rejection sampling + while (true) { + const bytes = new Uint8Array(byteLen); + crypto.getRandomValues(bytes); + + let x = 0n; + for (const b of bytes) { + x = (x << 8n) | BigInt(b); + } + + // mask down to bitLen to reduce rejections slightly + const extraBits = BigInt(byteLen * 8 - bitLen); + if (extraBits > 0n) x = x >> extraBits; + + if (x < maxExclusive) return x; + } + } + + function setRandomOnce() { + if (isTwosMode()) { + const span = unsignedMaxExclusive(bitCount); // 2^n + const u = cryptoRandomBigInt(span); // 0..2^n-1 + unsignedBigIntToBits(u); + } else { + const span = unsignedMaxExclusive(bitCount); + const u = cryptoRandomBigInt(span); + unsignedBigIntToBits(u); + } + updateUI(); + } + + function runRandomBriefly() { + // stop any existing run + if (randomTimer) { + clearInterval(randomTimer); + randomTimer = null; + } const start = Date.now(); - randomTimer = window.setInterval(() => { - // pick a random representable number depending on mode - let target; - if (!isTwos) { - target = Math.floor(Math.random() * (pow2(bitCount))); - } else { - const min = -pow2(bitCount - 1); - const max = pow2(bitCount - 1) - 1; - target = min + Math.floor(Math.random() * (max - min + 1)); - } - setFromDenary(target); + const durationMs = 900; // brief run then stop + const tickMs = 80; - if (Date.now() - start >= durationMs) stopAutoRandom(); + randomTimer = setInterval(() => { + setRandomOnce(); + if (Date.now() - start >= durationMs) { + clearInterval(randomTimer); + randomTimer = null; + } }, tickMs); } - function stopAutoRandom() { - if (randomTimer !== null) { - window.clearInterval(randomTimer); - randomTimer = null; - } + /* ----------------------------- + BIT WIDTH CONTROLS + ----------------------------- */ + function setBitWidth(n) { + const v = clampInt(n, 1, 64); + buildBits(v); } - // MODE toggle - modeToggle.addEventListener("change", () => { - updateModeLabels(); - updateReadout(); + /* ----------------------------- + EVENTS + ----------------------------- */ + modeToggle?.addEventListener("change", () => { + updateUI(); }); - // Bit width - btnBitsUp.addEventListener("click", () => { - const next = clampInt(bitCount + 1, 1, 64); - bitsInput.value = String(next); - buildBits(next); + btnCustomBinary?.addEventListener("click", () => { + const v = prompt(`Enter binary (spaces allowed). Current width: ${bitCount} bits`); + if (v === null) return; + if (!setFromBinaryString(v)) alert("Invalid binary"); }); - btnBitsDown.addEventListener("click", () => { - const next = clampInt(bitCount - 1, 1, 64); - bitsInput.value = String(next); - buildBits(next); + 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"); }); - bitsInput.addEventListener("change", () => { - const next = clampInt(bitsInput.value, 1, 64); - bitsInput.value = String(next); - buildBits(next); + 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)); }); - // Buttons - btnShiftLeft.addEventListener("click", shiftLeft); - btnShiftRight.addEventListener("click", shiftRight); - - btnCustomBinary.addEventListener("click", () => { - const val = prompt(`Enter a ${bitCount}-bit binary number:`); - if (val === null) return; - if (!setFromBinary(val)) alert("Invalid binary input (use only 0 and 1)."); - }); - - btnCustomDenary.addEventListener("click", () => { - const modeRange = isTwos - ? `(${ -pow2(bitCount - 1) } to ${ pow2(bitCount - 1) - 1 })` - : `(0 to ${ pow2(bitCount) - 1 })`; - - const val = prompt(`Enter a denary number ${modeRange}:`); - if (val === null) return; - if (!setFromDenary(val)) alert("Invalid denary input for the current mode/bit width."); - }); - - btnClear.addEventListener("click", () => setAllBits(true)); - btnRandom.addEventListener("click", startAutoRandom); - - btnInc.addEventListener("click", increment); - btnDec.addEventListener("click", decrement); - - // INIT - modeToggle.checked = false; - updateModeLabels(); + /* ----------------------------- + INIT + ----------------------------- */ + updateModeHint(); buildBits(bitCount); -}); +})(); diff --git a/src/styles/binary.css b/src/styles/binary.css index 5c88b94..a62409a 100644 --- a/src/styles/binary.css +++ b/src/styles/binary.css @@ -1,19 +1,34 @@ :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); + + --danger: #e24444; + --danger-dim: rgba(226,68,68,.22); + --success: #2fd66b; + --success-dim: rgba(47,214,107,.22); } +/* -------- Fonts -------- */ @font-face{ font-family: "DSEG7ClassicRegular"; src: - url("/fonts/DSEG7Classic-Regular.ttf") format("truetype"), - url("/fonts/DSEG7Classic-Regular.woff") format("woff"); + url("/fonts/DSEG7Classic-Regular.woff") format("woff"), + url("/fonts/DSEG7Classic-Regular.ttf") format("truetype"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@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; @@ -21,7 +36,7 @@ body{ margin:0; - font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; + font-family: "SevenSegment", system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; background: var(--bg); color: var(--text); } @@ -40,7 +55,6 @@ body{ } .readout{ - background: transparent; text-align:center; padding: 10px 10px 0; } @@ -54,6 +68,7 @@ body{ margin-top: 10px; } +/* Anything that is a number uses DSEG7 */ .num{ font-family: "DSEG7ClassicRegular", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-weight: 400; @@ -62,17 +77,17 @@ body{ } .denaryValue{ - font-size: 70px; /* smaller than before */ + font-size: 70px; line-height: 1.0; margin: 6px 0 10px; } .binaryValue{ - font-size: 52px; /* smaller than before */ + font-size: 52px; letter-spacing: .12em; line-height: 1.0; margin: 6px 0 14px; - white-space: pre; /* keep spaces */ + white-space: pre; } .controlsStack{ @@ -99,6 +114,7 @@ body{ font-weight: 700; cursor: pointer; min-width: 170px; + font-family: "SevenSegment", system-ui, sans-serif; } .btn:active{ transform: translateY(1px); } @@ -152,9 +168,10 @@ body{ color: var(--text); font-weight: 700; font-size: 14px; + font-family: "SevenSegment", system-ui, sans-serif; } -/* Shared toggle switch (mode + bit switches) */ +/* Switch */ .switch{ position: relative; width: 56px; @@ -195,14 +212,13 @@ body{ background: var(--accent); } -/* Tools card layout */ +/* Tools layout */ .toolRow{ display:grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px; } - .toolRow2{ display:grid; grid-template-columns: 1fr 1fr; @@ -217,10 +233,26 @@ body{ color: #fff; cursor: pointer; font-weight: 800; + font-family: "SevenSegment", system-ui, sans-serif; } +/* Narrower arrow buttons (only the arrow pair) */ .toolSpin{ - font-size: 22px; /* bigger spin feature */ + font-size: 22px; + height: 48px; + max-width: 120px; /* narrower */ + justify-self: start; + padding: 0; +} + +/* Down = red, Up = green */ +#btnDec{ + background: var(--danger-dim); + border-color: rgba(226,68,68,.45); +} +#btnInc{ + background: var(--success-dim); + border-color: rgba(47,214,107,.45); } /* Bit width control */ @@ -240,6 +272,7 @@ body{ cursor:pointer; font-weight:900; font-size:18px; + font-family: "SevenSegment", system-ui, sans-serif; } .bitInputWrap{ @@ -258,6 +291,7 @@ body{ font-weight:800; letter-spacing:.18em; text-transform:uppercase; + font-family: "SevenSegment", system-ui, sans-serif; } .bitInput{ width:86px; @@ -275,18 +309,20 @@ body{ margin:0; } -/* Bits area (wrap every 8 bits, centered) */ +/* Bits: wrap every 8, centred */ .bitsWrap{ margin-top: 22px; } - .bitsGrid{ display:grid; gap: 18px; justify-content:center; - grid-template-columns: repeat(8, minmax(90px, 1fr)); /* wraps at 8 */ + grid-template-columns: repeat(8, minmax(90px, 1fr)); padding-top: 18px; } +.bitsGrid.bitsFew{ + grid-template-columns: repeat(var(--cols, 4), minmax(90px, 1fr)); +} .bit{ display:flex; @@ -297,36 +333,38 @@ body{ text-align:center; } -/* “Bulb like 💡” but consistent + bigger */ +/* Bulb (emoji only — no circle, no ::before so it won't duplicate) */ .bulb{ - width: 34px; /* bigger */ - height: 34px; /* bigger */ - border-radius: 50%; - background: rgba(255,255,255,.08); - border: 1px solid rgba(255,255,255,.12); + width: auto; + height: auto; + border: none; + background: transparent; + border-radius: 0; box-shadow: none; + display:flex; align-items:center; justify-content:center; - font-size: 20px; + + font-size: 26px; line-height: 1; - opacity: .55; -} -.bulb::before{ - content: "💡"; + opacity: .45; filter: grayscale(1); -} -.bulb.on{ - opacity: 1; - background: rgba(255,216,107,.18); - border-color: rgba(255,216,107,.55); - box-shadow: 0 0 18px rgba(255,216,107,.45); -} -.bulb.on::before{ - filter: grayscale(0); + text-shadow: none; } -/* Bit value (MSB becomes negative in two’s mode via JS label text) */ +/* IMPORTANT: remove the pseudo-element that was causing the 2nd bulb */ +.bulb::before{ + content: none; +} + +.bulb.on{ + opacity: 1; + filter: grayscale(0); + text-shadow: 0 0 14px rgba(255,216,107,.75), 0 0 26px rgba(255,216,107,.45); +} + +/* Bit value numbers use DSEG7 */ .bitVal{ font-family:"DSEG7ClassicRegular", ui-monospace, monospace; font-size: 28px; @@ -336,11 +374,6 @@ body{ min-height: 32px; } -/* Make sure small bit counts still look centered/nice */ -.bitsGrid.bitsFew{ - grid-template-columns: repeat(var(--cols, 4), minmax(90px, 1fr)); -} - @media (max-width: 980px){ .topGrid{ grid-template-columns: 1fr; } .denaryValue{ font-size: 62px; }