@@ -34,16 +95,14 @@ import binaryScriptUrl from "../scripts/binary.js?url";
-
0
Binary
- 0
+ 0000 0000
-
@@ -57,64 +116,10 @@ import binaryScriptUrl from "../scripts/binary.js?url";
-
-
-
-
-
-
+
+
diff --git a/src/scripts/binary.js b/src/scripts/binary.js
index 5d64896..73d63d2 100644
--- a/src/scripts/binary.js
+++ b/src/scripts/binary.js
@@ -1,6 +1,6 @@
// 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.
+// Matches IDs/classes in src/pages/binary.astro
(() => {
/* -----------------------------
@@ -13,8 +13,6 @@
const modeToggle = document.getElementById("modeToggle");
const modeHint = document.getElementById("modeHint");
- const lblUnsigned = document.getElementById("lblUnsigned");
- const lblTwos = document.getElementById("lblTwos");
const btnCustomBinary = document.getElementById("btnCustomBinary");
const btnCustomDenary = document.getElementById("btnCustomDenary");
@@ -29,6 +27,9 @@
const btnBitsUp = document.getElementById("btnBitsUp");
const btnBitsDown = document.getElementById("btnBitsDown");
+ const toolboxToggle = document.getElementById("toolboxToggle");
+ const toolboxPanel = document.getElementById("toolboxPanel");
+
/* -----------------------------
STATE
----------------------------- */
@@ -40,6 +41,10 @@
// Random run timer (brief)
let randomTimer = null;
+ // Dynamic wrapping for the big binary display
+ // "nibbles per row" recalculated on resize
+ let nibblesPerRow = 2; // default for small widths
+
/* -----------------------------
HELPERS
----------------------------- */
@@ -81,7 +86,8 @@
}
function unsignedBigIntToBits(vUnsigned) {
- const v = ((vUnsigned % unsignedMaxExclusive(bitCount)) + unsignedMaxExclusive(bitCount)) % unsignedMaxExclusive(bitCount);
+ const span = unsignedMaxExclusive(bitCount);
+ const v = ((vUnsigned % span) + span) % span;
for (let i = 0; i < bitCount; i++) {
bits[i] = ((v >> BigInt(i)) & 1n) === 1n;
}
@@ -91,48 +97,77 @@
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.";
+ 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.";
+ modeHint.textContent =
+ "Tip: In unsigned binary, all bits represent positive values.";
}
}
+ /* -----------------------------
+ BIG BINARY DISPLAY WRAP
+ - Determines how many nibbles (4 bits) fit per row
+ - Recalculates on window resize
+ ----------------------------- */
+ function computeNibblesPerRow() {
+ if (!binaryEl) return;
+
+ // available width for the binary number = element width
+ const w = binaryEl.getBoundingClientRect().width;
+
+ // Approximate "nibble width" in pixels:
+ // 4 digits + a space; use font size and letter-spacing to estimate.
+ // This doesn't need to be perfect, just stable and responsive.
+ const style = window.getComputedStyle(binaryEl);
+ const fontSize = parseFloat(style.fontSize || "40"); // px
+ const letterSpacing = parseFloat(style.letterSpacing || "0");
+ const digitW = fontSize * 0.62 + letterSpacing; // rough digit width
+ const nibbleW = digitW * 4 + digitW * 1.2; // include gap between nibbles
+
+ // Always allow at least 2 nibbles per row
+ const fit = Math.max(2, Math.floor(w / nibbleW));
+ nibblesPerRow = fit;
+ }
+
+ function formatBinaryGroupedWrapped() {
+ // Build MSB..LSB and group into nibbles
+ const nibbles = [];
+ let current = "";
+
+ for (let i = bitCount - 1; i >= 0; i--) {
+ current += bits[i] ? "1" : "0";
+ const posFromRight = (bitCount - i);
+ if (posFromRight % 4 === 0 || i === 0) {
+ // if last group is partial, left-pad inside the group
+ if (current.length < 4) current = current.padStart(4, "0");
+ nibbles.push(current);
+ current = "";
+ }
+ }
+
+ // Now wrap nibbles into lines
+ const lines = [];
+ for (let i = 0; i < nibbles.length; i += nibblesPerRow) {
+ lines.push(nibbles.slice(i, i + nibblesPerRow).join(" "));
+ }
+
+ return lines.join("\n");
+ }
+
/* -----------------------------
BUILD UI (BITS)
----------------------------- */
@@ -140,28 +175,18 @@
bitCount = clampInt(count, 1, 64);
if (bitsInput) bitsInput.value = String(bitCount);
- // reset bits array size, preserve existing LSBs where possible
+ // 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.style.removeProperty("--cols");
- }
-
// Render MSB..LSB left-to-right
for (let i = bitCount - 1; i >= 0; i--) {
const bitEl = document.createElement("div");
bitEl.className = "bit";
- // 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 = `
@@ -183,36 +208,13 @@
});
});
- // 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;
-
- // 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)
+ UI UPDATE
----------------------------- */
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++) {
@@ -235,14 +237,11 @@
}
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)";
@@ -266,7 +265,9 @@
denaryEl.textContent = unsigned.toString();
}
- binaryEl.textContent = formatBinaryGrouped();
+ // Ensure nibble wrapping is up-to-date
+ computeNibblesPerRow();
+ binaryEl.textContent = formatBinaryGroupedWrapped();
}
function updateUI() {
@@ -284,11 +285,8 @@
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";
}
@@ -304,10 +302,8 @@
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 {
@@ -315,17 +311,13 @@
}
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);
}
@@ -337,19 +329,13 @@
SHIFTS
----------------------------- */
function shiftLeft() {
- // logical left shift: bits move to higher index; LSB becomes 0
- 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() {
- // logical right shift: bits move to lower index; MSB becomes 0
- 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] = false;
updateUI();
}
@@ -367,7 +353,7 @@
const min = twosMin(bitCount);
const max = twosMax(bitCount);
let v = bitsToSignedBigIntTwos() + 1n;
- if (v > max) v = min; // wrap
+ if (v > max) v = min;
signedBigIntToBitsTwos(v);
} else {
const span = unsignedMaxExclusive(bitCount);
@@ -382,7 +368,7 @@
const min = twosMin(bitCount);
const max = twosMax(bitCount);
let v = bitsToSignedBigIntTwos() - 1n;
- if (v < min) v = max; // wrap
+ if (v < min) v = max;
signedBigIntToBitsTwos(v);
} else {
const span = unsignedMaxExclusive(bitCount);
@@ -393,26 +379,21 @@
}
/* -----------------------------
- RANDOM (FIXED: NO BigInt->Number Math.min)
+ RANDOM (BigInt-safe)
----------------------------- */
function cryptoRandomBigInt(maxExclusive) {
- // returns 0 <= x < maxExclusive
if (maxExclusive <= 0n) return 0n;
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);
- }
+ 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;
@@ -421,27 +402,20 @@
}
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);
- }
+ 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();
- const durationMs = 900; // brief run then stop
+ const durationMs = 900;
const tickMs = 80;
randomTimer = setInterval(() => {
@@ -454,30 +428,25 @@
}
/* -----------------------------
- BIT WIDTH CONTROLS
+ BIT WIDTH
----------------------------- */
function setBitWidth(n) {
const v = clampInt(n, 1, 64);
buildBits(v);
}
+ /* -----------------------------
+ TOOLBOX TOGGLE
+ ----------------------------- */
+ function setToolboxCollapsed(collapsed) {
+ document.body.classList.toggle("toolbox-collapsed", collapsed);
+ toolboxToggle?.setAttribute("aria-expanded", String(!collapsed));
+ }
+
/* -----------------------------
EVENTS
----------------------------- */
-
- // Collapsible right panel
- const sidePanel = document.getElementById("sidePanel");
- const btnPanelToggle = document.getElementById("btnPanelToggle");
-
- btnPanelToggle?.addEventListener("click", () => {
- sidePanel?.classList.toggle("isCollapsed");
- // flip the chevron
- btnPanelToggle.textContent = sidePanel?.classList.contains("isCollapsed") ? "❮" : "❯";
- });
-
- modeToggle?.addEventListener("change", () => {
- updateUI();
- });
+ modeToggle?.addEventListener("change", () => updateUI());
btnCustomBinary?.addEventListener("click", () => {
const v = prompt(`Enter binary (spaces allowed). Current width: ${bitCount} bits`);
@@ -507,14 +476,25 @@
btnBitsUp?.addEventListener("click", () => setBitWidth(bitCount + 1));
btnBitsDown?.addEventListener("click", () => setBitWidth(bitCount - 1));
- bitsInput?.addEventListener("change", () => {
- setBitWidth(Number(bitsInput.value));
+ bitsInput?.addEventListener("change", () => setBitWidth(Number(bitsInput.value)));
+
+ toolboxToggle?.addEventListener("click", () => {
+ const collapsed = document.body.classList.contains("toolbox-collapsed");
+ setToolboxCollapsed(!collapsed);
+ // ensure binary re-wraps after layout change
+ requestAnimationFrame(() => updateReadout());
+ });
+
+ // Recompute nibble wrapping on resize
+ window.addEventListener("resize", () => {
+ // throttled via rAF to avoid spam
+ requestAnimationFrame(() => updateReadout());
});
/* -----------------------------
INIT
----------------------------- */
updateModeHint();
+ setToolboxCollapsed(false);
buildBits(bitCount);
})();
-
diff --git a/src/styles/binary.css b/src/styles/binary.css
index 288f2ef..030b0f3 100644
--- a/src/styles/binary.css
+++ b/src/styles/binary.css
@@ -1,219 +1,218 @@
:root{
- --bg: #1f2027;
- --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);
+ --bg:#1f2027;
+ --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);
- /* right column sizing */
- --sideW: 720px;
- --sidePad: 24px;
-
- /* header offset (tweak if your header is taller/shorter) */
- --sideTop: 92px;
+ /* Toolbox sizing */
+ --toolboxW: 320px; /* default width */
+ --toolboxGap: 20px; /* gap from right edge */
+ --toolboxTop: 92px; /* below header area */
}
+/* ---------- Fonts ---------- */
+/* Numbers */
@font-face{
- font-family: "DSEG7ClassicRegular";
+ font-family:"DSEG7ClassicRegular";
src:
url("/fonts/DSEG7Classic-Regular.woff") format("woff"),
url("/fonts/DSEG7Classic-Regular.ttf") format("truetype");
- font-weight: 400;
- font-style: normal;
- font-display: swap;
+ font-weight:400;
+ font-style:normal;
+ font-display:swap;
}
+/* Text */
@font-face{
- font-family: "SevenSegment";
+ 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-weight:400;
+ font-style:normal;
+ font-display:swap;
}
+*{ box-sizing:border-box; }
+
body{
margin:0;
- font-family: "SevenSegment", system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
- background: var(--bg);
- color: var(--text);
+ background:var(--bg);
+ color:var(--text);
+ font-family:"SevenSegment", system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
}
-/* Leave enough room for the fixed right panel */
+/* When toolbox is collapsed, we remove the reserved padding */
+body.toolbox-collapsed{
+ --toolboxW: 0px;
+}
+
+/* ---------- Main layout ---------- */
.wrap{
max-width: 1200px;
margin: 0 auto;
padding: 32px 20px 60px;
- padding-right: calc(var(--sideW) + (var(--sidePad) * 2));
+
+ /* reserve room for the fixed toolbox so it NEVER overlaps */
+ padding-right: calc(20px + var(--toolboxW) + var(--toolboxGap));
+ transition: padding-right .2s ease;
+}
+
+body.toolbox-collapsed .wrap{
+ padding-right: 20px;
}
.topGrid{
display:grid;
grid-template-columns: 1fr;
- gap: 28px;
+ gap:28px;
align-items:start;
}
.readout{
text-align:center;
- padding: 10px 10px 0;
+ padding:10px 10px 0;
}
.label{
- letter-spacing: .18em;
- font-weight: 700;
- color: var(--muted);
- text-transform: uppercase;
- font-size: 14px;
- margin-top: 10px;
+ letter-spacing:.18em;
+ font-weight:700;
+ color:var(--muted);
+ text-transform:uppercase;
+ font-size:14px;
+ margin-top:10px;
}
+/* All numbers */
+.num,
+.bitVal,
+.bitInput{
+ font-family:"DSEG7ClassicRegular", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+ font-weight:400;
+}
+
+/* Denary/Binary display (25% smaller than before) */
.num{
- font-family: "DSEG7ClassicRegular", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
- font-weight: 400;
- color: var(--accent);
- text-shadow: 0 0 18px var(--accent-dim);
+ color:var(--accent);
+ text-shadow:0 0 18px var(--accent-dim);
+ letter-spacing: 0.03em; /* + a pixel-ish feel */
}
-/* 25% smaller */
.denaryValue{
- font-size: 53px;
- line-height: 1.0;
- margin: 6px 0 10px;
+ font-size: 52px; /* was ~70 */
+ line-height:1.0;
+ margin:6px 0 10px;
}
-/* 25% smaller + slightly wider spacing */
.binaryValue{
- font-size: 39px;
- letter-spacing: calc(.12em + 2px);
- line-height: 1.0;
- margin: 6px 0 14px;
- white-space: pre; /* preserve spaces */
+ font-size: 39px; /* was ~52 */
+ line-height:1.05;
+ margin:6px 0 14px;
+ white-space: pre-wrap;
+ word-break: normal;
+ overflow-wrap: normal;
+ letter-spacing: 0.06em; /* widen characters slightly */
}
+/* Buttons */
.controlsStack{
- margin-top: 10px;
+ margin-top:10px;
display:flex;
flex-direction:column;
- gap: 10px;
+ gap:10px;
align-items:center;
}
.controlsRow{
display:flex;
- gap: 12px;
+ gap:12px;
justify-content:center;
flex-wrap:wrap;
}
.btn{
- background: rgba(255,255,255,.06);
- border: 1px solid rgba(255,255,255,.14);
- color: #fff;
- padding: 12px 14px;
- border-radius: 12px;
- font-weight: 700;
- cursor: pointer;
- min-width: 170px;
- font-family: "SevenSegment", system-ui, sans-serif;
+ background:rgba(255,255,255,.06);
+ border:1px solid rgba(255,255,255,.14);
+ color:#fff;
+ padding:12px 14px;
+ border-radius:12px;
+ font-weight:700;
+ cursor:pointer;
+ min-width:170px;
+ font-family:"SevenSegment", system-ui, sans-serif;
+ letter-spacing: .06em;
}
.btn:active{ transform: translateY(1px); }
.btnAccent{
- background: rgba(51,255,122,.18);
- border-color: rgba(51,255,122,.45);
+ background:rgba(51,255,122,.18);
+ border-color:rgba(51,255,122,.45);
}
.divider{
- margin-top: 26px;
- border-top: 1px solid var(--line);
+ margin-top:26px;
+ border-top:1px solid var(--line);
}
-/* -------------------------
- RIGHT COLUMN: pinned + collapsible
-------------------------- */
-.panelCol{
- position: fixed;
- right: var(--sidePad);
- top: var(--sideTop);
- width: var(--sideW);
+/* ---------- Bits grid ---------- */
+.bitsWrap{ margin-top:22px; }
+
+/* auto-fit prevents overlap at large values without shrinking the digits */
+.bitsGrid{
+ display:grid;
+ gap:18px;
+ justify-content:center;
+ padding-top:18px;
+
+ /* each tile has a safe min width so large labels don't collide */
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
+ max-width: 100%;
+}
+
+.bit{
display:flex;
flex-direction:column;
- gap: 14px;
- z-index: 50;
- transition: transform .2s ease;
-}
-
-/* collapse leaves a small handle visible */
-.panelCol.isCollapsed{
- transform: translateX(calc(100% - 52px));
-}
-
-.panelToggle{
- position: absolute;
- left: -52px;
- top: 0;
- width: 44px;
- height: 44px;
- border-radius: 12px;
- border: 1px solid rgba(255,255,255,.14);
- background: rgba(255,255,255,.06);
- color: #fff;
- cursor: pointer;
- font-weight: 900;
- display:flex;
align-items:center;
- justify-content:center;
- font-family: "SevenSegment", system-ui, sans-serif;
+ gap:10px;
+ padding:8px 4px;
+ text-align:center;
+ min-width:120px;
}
-/* cards */
-.card{
- background: var(--panel2);
- border: 1px solid rgba(255,255,255,.10);
- border-radius: 14px;
- padding: 14px;
+.bulb{
+ /* NO circle/ring. Just the emoji. 25% bigger than prior ~26px -> ~33px */
+ font-size: 33px;
+ line-height:1;
+ background:transparent;
+ border:none;
+ width:auto;
+ height:auto;
+ opacity:.45;
+ filter: grayscale(1);
+ text-shadow:none;
+ user-select:none;
}
-.cardTitle{
- letter-spacing: .18em;
- font-weight: 800;
- color: var(--muted);
- text-transform: uppercase;
- font-size: 12px;
- margin: 0 0 10px;
+.bitVal{
+ font-size:28px;
+ color:var(--text);
+ opacity:.95;
+ line-height:1;
+ min-height:32px;
+ white-space: nowrap; /* stop wrapping into each other */
+ letter-spacing: 0.02em;
}
-.hint{
- color: var(--muted);
- font-size: 12px;
- margin-top: 8px;
- line-height: 1.35;
-}
-
-.toggleRow{
- display:flex;
- align-items:center;
- justify-content:space-between;
- gap: 10px;
-}
-
-.toggleLabel{
- color: var(--text);
- font-weight: 700;
- font-size: 14px;
-}
-
-/* Shared toggle switch */
+/* Shared toggle switch (bit switches + mode) */
.switch{
- position: relative;
- width: 56px;
- height: 34px;
+ position:relative;
+ width:56px;
+ height:34px;
display:inline-block;
- flex: 0 0 auto;
+ flex:0 0 auto;
}
.switch input{
opacity:0;
@@ -223,88 +222,198 @@ body{
.slider{
position:absolute;
inset:0;
- background: rgba(255,255,255,.10);
- border: 1px solid rgba(255,255,255,.14);
- border-radius: 999px;
- transition: .18s ease;
+ background:rgba(255,255,255,.10);
+ border:1px solid rgba(255,255,255,.14);
+ border-radius:999px;
+ transition:.18s ease;
}
.slider::before{
content:"";
position:absolute;
- height: 28px;
- width: 28px;
- left: 3px;
- top: 2px;
- background: rgba(255,255,255,.92);
- border-radius: 50%;
- transition: .18s ease;
+ height:28px;
+ width:28px;
+ left:3px;
+ top:2px;
+ background:rgba(255,255,255,.92);
+ border-radius:50%;
+ transition:.18s ease;
}
.switch input:checked + .slider{
- background: rgba(51,255,122,.20);
- border-color: rgba(51,255,122,.55);
+ background:rgba(51,255,122,.20);
+ border-color:rgba(51,255,122,.55);
}
.switch input:checked + .slider::before{
transform: translateX(22px);
- background: var(--accent);
+ background:var(--accent);
}
-/* Tools + Bit Width side-by-side */
-.sideRow{
- display:flex;
- gap: 14px;
-}
-.sideRow .card{
- flex: 1 1 0;
-}
+/* ---------- Toolbox (fixed, non-overlapping) ---------- */
+.toolboxToggle{
+ position: fixed;
+ top: 18px;
+ right: var(--toolboxGap);
+ z-index: 40;
-/* Tools layout: arrows row + reset/random row */
-.toolRow, .toolRow2{
display:flex;
- gap: 10px;
align-items:center;
-}
+ gap:10px;
-/* Reset/Random = 50% current width (explicit) */
-.toolBtn{
- height: 48px;
- border-radius: 12px;
background: rgba(255,255,255,.06);
- border: 1px solid rgba(255,255,255,.14);
- color: #fff;
- cursor: pointer;
- font-weight: 800;
- font-family: "SevenSegment", system-ui, sans-serif;
+ border:1px solid rgba(255,255,255,.14);
+ color:#fff;
+ border-radius: 12px;
+ padding: 10px 12px;
+ cursor:pointer;
+
+ font-family:"SevenSegment", system-ui, sans-serif;
+ letter-spacing:.06em;
}
-/* Reset/Random smaller */
-.toolBtn.toolHalf{
- width: 120px;
+.toolboxIcon{ font-size: 16px; line-height:1; }
+.toolboxText{ font-weight:800; font-size: 14px; }
+
+.toolboxFixed{
+ position: fixed;
+ top: var(--toolboxTop);
+ right: var(--toolboxGap);
+ width: var(--toolboxW);
+ z-index: 35;
+
+ transition: transform .2s ease, opacity .2s ease;
}
-/* Arrows half of that */
-.toolBtn.toolQuarter{
- width: 60px;
- font-size: 22px;
- padding: 0;
+body.toolbox-collapsed .toolboxFixed{
+ transform: translateX(calc(100% + var(--toolboxGap)));
+ opacity: 0;
+ pointer-events: none;
}
-/* colour arrows */
-.toolBtn.toolUp{
- background: rgba(51,255,122,.18);
- border-color: rgba(51,255,122,.45);
-}
-.toolBtn.toolDown{
- background: rgba(255,80,80,.18);
- border-color: rgba(255,80,80,.45);
+/* cards inside toolbox */
+.panelCol{
+ display:flex;
+ flex-direction:column;
+ gap:14px;
}
-/* Bit width control */
-.bitWidthRow{
+.card{
+ background: var(--panel2);
+ border:1px solid rgba(255,255,255,.10);
+ border-radius:14px;
+ padding:14px;
+ backdrop-filter: blur(6px);
+}
+
+.cardTitle{
+ letter-spacing:.18em;
+ font-weight:800;
+ color:var(--muted);
+ text-transform:uppercase;
+ font-size:12px;
+ margin:0 0 10px;
+}
+
+.hint{
+ color:var(--muted);
+ font-size:12px;
+ margin-top:8px;
+ line-height:1.35;
+}
+
+.toggleRow{
+ display:flex;
+ align-items:center;
+ justify-content:space-between;
+ gap:10px;
+}
+
+.toggleLabel{
+ color:var(--text);
+ font-weight:700;
+ font-size:14px;
+}
+
+/* ---------- Tools merged layout ---------- */
+.toolsTopRow{
+ display:grid;
+ grid-template-columns: 1fr 1fr;
+ gap:10px;
+ align-items:center;
+ margin-bottom: 10px;
+}
+
+.spinGroup{
+ display:grid;
+ grid-template-columns: 1fr 1fr;
+ gap:10px;
+}
+
+.toolsBottomRow{
+ display:grid;
+ grid-template-columns: 1.25fr 0.75fr; /* bit width gets more space than random */
+ gap:10px;
+ align-items:end;
+}
+
+.toolBtn{
+ height:48px;
+ border-radius:12px;
+ background:rgba(255,255,255,.06);
+ border:1px solid rgba(255,255,255,.14);
+ color:#fff;
+ cursor:pointer;
+ font-weight:800;
+ font-family:"SevenSegment", system-ui, sans-serif;
+ letter-spacing:.06em;
+}
+
+.toolSpin{
+ font-size:22px;
+ width: 100%; /* kept narrow via grid allocation */
+}
+
+.toolDown{
+ background: rgba(255, 70, 70, .20);
+ border-color: rgba(255, 70, 70, .45);
+}
+
+.toolUp{
+ background: rgba(51,255,122,.20);
+ border-color: rgba(51,255,122,.55);
+}
+
+.toolReset{
+ width: 100%;
+}
+
+.toolRandom{
+ width: 100%;
+ /* make random ~5% narrower by padding reduction (without breaking grid) */
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
+/* bit width mini block */
+.bitWidthMini{
+ display:flex;
+ flex-direction:column;
+ gap:6px;
+}
+
+.bitWidthMiniLabel{
+ color:var(--muted);
+ font-size:12px;
+ font-weight:800;
+ letter-spacing:.18em;
+ text-transform:uppercase;
+}
+
+.bitWidthMiniRow{
display:grid;
grid-template-columns: 44px 1fr 44px;
- gap: 10px;
+ gap:10px;
align-items:center;
}
+
.miniBtn{
height:44px;
width:44px;
@@ -315,107 +424,39 @@ body{
cursor:pointer;
font-weight:900;
font-size:18px;
- font-family: "SevenSegment", system-ui, sans-serif;
+ font-family:"SevenSegment", system-ui, sans-serif;
}
-.bitInputWrap{
+.bitInput{
+ width: 100%;
+ min-width: 64px; /* MUST fit 2 digits comfortably */
+ text-align:center;
background:rgba(255,255,255,.06);
border:1px solid rgba(255,255,255,.14);
border-radius:12px;
- padding:10px 12px;
- display:flex;
- align-items:center;
- justify-content:space-between;
- gap:12px;
-}
-.bitInputLabel{
- color:var(--muted);
- font-size:12px;
- font-weight:800;
- letter-spacing:.18em;
- text-transform:uppercase;
-}
-.bitInput{
- width:86px;
- text-align:right;
- background:transparent;
- border:none;
+ padding:10px 8px;
outline:none;
color:var(--accent);
- font-family:"DSEG7ClassicRegular", ui-monospace, monospace;
font-size:28px;
}
+
.bitInput::-webkit-outer-spin-button,
.bitInput::-webkit-inner-spin-button{
-webkit-appearance:none;
margin:0;
}
-/* Bits: wrap every 8, centred */
-.bitsWrap{
- margin-top: 22px;
-}
-.bitsGrid{
- display:grid;
- gap: 18px;
- justify-content:center;
- 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;
- flex-direction:column;
- align-items:center;
- gap: 10px;
- padding: 8px 4px;
- text-align:center;
-}
-
-/* Bulb: no circle here (JS controls the emoji), but keep sizing if used */
-.bulb{
- border: none;
- background: transparent;
- width: auto;
- height: auto;
- border-radius: 0;
- box-shadow: none;
- display:flex;
- align-items:center;
- justify-content:center;
- line-height: 1;
- opacity: .55;
- font-size: 33px; /* 25% larger from 26px */
-}
-
-.bitVal{
- font-family:"DSEG7ClassicRegular", ui-monospace, monospace;
- font-size: 28px;
- color: var(--text);
- opacity: .95;
- line-height: 1;
- min-height: 32px;
-}
-
-/* responsive: stop pinning on small screens */
+/* ---------- Responsive ---------- */
@media (max-width: 980px){
- .panelCol{
- position: static;
- width: auto;
- transform: none !important;
- }
- .panelToggle{ display:none; }
.wrap{
+ /* On small screens, don’t reserve fixed sidebar space; toolbox floats */
padding-right: 20px;
}
+ .toolboxFixed{
+ width: min(320px, calc(100vw - 40px));
+ }
}
@media (max-width: 520px){
- .btn{ min-width: 150px; }
- .bitsGrid{ grid-template-columns: repeat(4, minmax(90px, 1fr)); }
- .toolBtn.toolHalf{ width: 100px; }
- .toolBtn.toolQuarter{ width: 52px; }
+ .btn{ min-width:150px; }
}