diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 3204a7f..2f370d8 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -95,14 +95,14 @@ jobs: shell: bash run: | set -e - if [ ! -d "export" ]; then - echo "❌ export/ folder not found in repo root" + if [ ! -d "dist" ]; then + echo "❌ dist/ folder not found in repo root" ls -la exit 1 fi rm -f "Computing:Box Website.zip" - (cd export && zip -r "../Computing:Box Website.zip" .) + (cd dist && zip -r "../Computing:Box Website.zip" .) test -s "Computing:Box Website.zip" ls -lh "Computing:Box Website.zip" diff --git a/public/css/tools/binary.css b/public/css/tools/binary.css deleted file mode 100644 index f300e8f..0000000 --- a/public/css/tools/binary.css +++ /dev/null @@ -1,137 +0,0 @@ -/* ---------- DSEG7 font ---------- */ -/* Put your font file here: - public/fonts/DSEG7Classic-Regular.woff2 -*/ -@font-face { - font-family: "DSEG7ClassicRegular"; - src: url("/fonts/DSEG7Classic-Regular.woff2") format("woff2"); - font-display: swap; -} - -/* ---------- Layout ---------- */ -.tool-shell { - min-height: 100vh; - display: grid; - place-items: center; - padding: 1rem; - background: #0b0f14; - color: #e7eaf0; - font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; -} - -.tool-card { - width: min(1100px, 100%); - background: #111824; - border: 1px solid rgba(255,255,255,0.08); - border-radius: 18px; - padding: 1rem; -} - -.tool-header h1 { margin: 0 0 .25rem 0; font-size: 1.4rem; } -.tool-header p { margin: 0 0 1rem 0; opacity: .85; } - -.display-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: .75rem; - margin-bottom: .75rem; -} - -.display-box { - background: #0b0f14; - border: 1px solid rgba(255,255,255,0.08); - border-radius: 14px; - padding: .75rem; -} - -.display-label { font-size: .9rem; opacity: .8; margin-bottom: .25rem; } - -.sevenseg { - font-family: "DSEG7ClassicRegular", monospace; - font-size: clamp(2rem, 4vw, 3.2rem); - letter-spacing: 0.08em; - line-height: 1.1; -} - -/* Buttons under denary/binary (your request) */ -.actions { - display: flex; - flex-wrap: wrap; - gap: .5rem; - margin-bottom: 1rem; -} - -/* ---------- Simple MD3-ish buttons ---------- */ -.md3-btn { - border: 1px solid rgba(255,255,255,0.16); - background: rgba(255,255,255,0.06); - color: #e7eaf0; - padding: .6rem .9rem; - border-radius: 999px; - cursor: pointer; - font-weight: 600; -} -.md3-btn:hover { background: rgba(255,255,255,0.10); } -.md3-btn--tonal { background: rgba(255,255,255,0.10); } - -/* ---------- Switches row ---------- */ -.switch-row { - display: grid; - grid-template-columns: repeat(8, minmax(90px, 1fr)); - gap: .75rem; -} - -.switch-col { - display: grid; - justify-items: center; - gap: .35rem; -} - -.bit-label { opacity: .85; font-weight: 600; } - -/* ---------- “Light switch” rocker ---------- */ -.rocker { - position: relative; - width: 70px; - height: 46px; - display: inline-block; - user-select: none; -} - -.rocker input { - opacity: 0; - width: 0; - height: 0; -} - -.rocker-body { - position: absolute; - inset: 0; - border-radius: 12px; - background: #1a2331; - border: 1px solid rgba(255,255,255,0.14); - box-shadow: inset 0 0 0 2px rgba(0,0,0,0.35); -} - -/* the “toggle” */ -.rocker-body::after { - content: ""; - position: absolute; - left: 6px; - top: 6px; - width: 58px; - height: 18px; - border-radius: 10px; - background: rgba(255,255,255,0.20); - transition: transform 180ms ease, background 180ms ease; -} - -/* ON position */ -.rocker input:checked + .rocker-body::after { - transform: translateY(16px); - background: rgba(255,255,255,0.55); -} - -@media (max-width: 900px) { - .switch-row { grid-template-columns: repeat(4, minmax(90px, 1fr)); } -} diff --git a/public/fonts/Seven-Segment.woff b/public/fonts/Seven-Segment.woff new file mode 100644 index 0000000..5a388a9 Binary files /dev/null and b/public/fonts/Seven-Segment.woff differ diff --git a/public/fonts/Seven-Segment.woff2 b/public/fonts/Seven-Segment.woff2 new file mode 100644 index 0000000..7c7e36f Binary files /dev/null and b/public/fonts/Seven-Segment.woff2 differ diff --git a/public/images/computing-box-logo.svg b/public/images/computing-box-logo.svg new file mode 100644 index 0000000..054ef8f Binary files /dev/null and b/public/images/computing-box-logo.svg differ diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index bcf045f..edce928 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -1,17 +1,47 @@ --- -// src/layouts/BaseLayout.astro +import '../styles/global.css'; + const { title = "Computing:Box" } = Astro.props; --- + - - - - {title} - - - - - - + + + + {title} + + + + + + + + +
+ +
+ + + \ No newline at end of file diff --git a/src/pages/binary.astro b/src/pages/binary.astro index 0e66993..0caf8dc 100644 --- a/src/pages/binary.astro +++ b/src/pages/binary.astro @@ -1,601 +1,101 @@ --- -/** - * src/pages/binary.astro - * Single-file binary simulator (Unsigned + Two's complement) - * Bit width: 4..64 - * - * Requires: - * public/fonts/DSEG7Classic-Regular.woff - * public/fonts/DSEG7Classic-Regular.ttf - */ +import BaseLayout from "../layouts/BaseLayout.astro"; --- - - - - - Binary | Computing:Box + +
+ - - - - -
-
- -
-
-
Denary
-
0
- -
Binary
-
00000000
- -
- - - - -
-
- -
+
Binary
+
00000000
-
- - +
+
Custom Number
+
+ + +
+ +
Random runs briefly then stops automatically.
+
- - - \ No newline at end of file + +
\ No newline at end of file diff --git a/src/scripts/binary.js b/src/scripts/binary.js new file mode 100644 index 0000000..e596092 --- /dev/null +++ b/src/scripts/binary.js @@ -0,0 +1,459 @@ +(() => { + /* ----------------------------- + 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"); + + // 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"); + 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 binaryPage = document.getElementById("binaryPage"); + + /* ----------------------------- + STATE + ----------------------------- */ + let bitCount = clampInt(Number(bitsInput?.value ?? 8), 1, 64); + let bits = new Array(bitCount).fill(false); + 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 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; + v = ((v % span) + span) % span; + unsignedBigIntToBits(v); + } + + function formatBinaryGrouped() { + let s = ""; + for (let i = bitCount - 1; i >= 0; i--) { + s += bits[i] ? "1" : "0"; + const posFromLeft = (bitCount - i); + if (i !== 0 && posFromLeft % 4 === 0) s += " "; + } + return s.trimEnd(); + } + + 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."; + } + } + + /* ----------------------------- + 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))); + } + + /* ----------------------------- + 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); + + 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(); + }); + }); + + computeColsForBitsGrid(); + updateUI(); + } + + /* ----------------------------- + UI UPDATE + ----------------------------- */ + function updateBitLabels() { + for (let i = 0; i < bitCount; i++) { + const label = document.getElementById(`bitLabel-${i}`); + if (!label) continue; + + let valStr; + if (isTwosMode() && i === bitCount - 1) { + valStr = `-${pow2Big(bitCount - 1).toString()}`; + } else { + valStr = pow2Big(i).toString(); + } + label.textContent = valStr; + label.style.setProperty('--len', valStr.length); + } + } + + 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; + bulb.classList.toggle("on", bits[i] === true); + } + } + + function updateReadout() { + if (!denaryEl || !binaryEl) return; + 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(); + updateReadout(); + } + + /* ----------------------------- + 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; + + 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 || 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() { + const msb = bits[bitCount - 1]; + for (let i = 0; i < bitCount - 1; i++) { bits[i] = bits[i + 1]; } + bits[bitCount - 1] = isTwosMode() ? msb : false; + updateUI(); + } + + /* ----------------------------- + CLEAR / INC / DEC + ----------------------------- */ + function clearAll() { + bits = []; + if (modeToggle) modeToggle.checked = false; + buildBits(8); + } + + 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 setRandomRunning(isRunning) { + if (!btnRandom) return; + btnRandom.classList.toggle("btnRandomRunning", !!isRunning); + } + + function runRandomBriefly() { + if (randomTimer) { + clearInterval(randomTimer); + randomTimer = null; + } + + setRandomRunning(true); + const start = Date.now(); + const durationMs = 1125; + const tickMs = 80; + + randomTimer = setInterval(() => { + setRandomOnce(); + if (Date.now() - start >= durationMs) { + clearInterval(randomTimer); + randomTimer = null; + setRandomRunning(false); + } + }, tickMs); + } + + /* ----------------------------- + BIT WIDTH CONTROLS + ----------------------------- */ + function setBitWidth(n) { + const v = clampInt(n, 1, 64); + buildBits(v); + } + + /* ----------------------------- + TOOLBOX TOGGLE + ----------------------------- */ + function setToolboxCollapsed(collapsed) { + if (!binaryPage) return; + binaryPage.classList.toggle("toolboxCollapsed", !!collapsed); + const expanded = !collapsed; + toolboxToggle?.setAttribute("aria-expanded", expanded ? "true" : "false"); + } + + /* ----------------------------- + 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 isCollapsed = binaryPage?.classList.contains("toolboxCollapsed"); + setToolboxCollapsed(!isCollapsed); + }); + + window.addEventListener("resize", () => { + computeColsForBitsGrid(); + }); + + /* ----------------------------- + INIT + ----------------------------- */ + updateModeHint(); + buildBits(bitCount); + setToolboxCollapsed(false); +})(); \ No newline at end of file diff --git a/src/scripts/unsignedBinary.js b/src/scripts/unsignedBinary.js deleted file mode 100644 index 58abb25..0000000 --- a/src/scripts/unsignedBinary.js +++ /dev/null @@ -1,54 +0,0 @@ -let bits = [128,64,32,16,8,4,2,1]; -let state = Array(8).fill(0); - -const denaryEl = document.getElementById("denaryNumber"); -const binaryEl = document.getElementById("binaryNumber"); -const bitEls = document.querySelectorAll(".bit"); - -bitEls.forEach((el, i) => { - el.addEventListener("click", () => { - state[i] = state[i] ? 0 : 1; - el.classList.toggle("on"); - update(); - }); -}); - -function update() { - const denary = state.reduce((sum, bit, i) => sum + bit * bits[i], 0); - denaryEl.textContent = denary; - binaryEl.textContent = state.join(""); -} - -function requestBinary() { - const input = prompt("Enter 8-bit binary:"); - if (!/^[01]{8}$/.test(input)) return; - state = input.split("").map(Number); - bitEls.forEach((el,i)=>el.classList.toggle("on",state[i])); - update(); -} - -function requestDenary() { - const input = parseInt(prompt("Enter denary (0–255)"),10); - if (isNaN(input) || input < 0 || input > 255) return; - - let value = input; - state = bits.map(b => { - if (value >= b) { - value -= b; - return 1; - } - return 0; - }); - - bitEls.forEach((el,i)=>el.classList.toggle("on",state[i])); - update(); -} - -function shiftBinary(dir) { - if (dir === "left") state.shift(), state.push(0); - if (dir === "right") state.pop(), state.unshift(0); - bitEls.forEach((el,i)=>el.classList.toggle("on",state[i])); - update(); -} - -update(); diff --git a/src/styles/binary.css b/src/styles/binary.css deleted file mode 100644 index 6a03738..0000000 --- a/src/styles/binary.css +++ /dev/null @@ -1,68 +0,0 @@ -.binary-container { - max-width: 1100px; - margin: auto; - padding: 2rem; - color: #e0e0e0; -} - -.display { - text-align: center; -} - -.label { - font-size: 1.2rem; - opacity: 0.7; -} - -.number { - font-size: 3rem; - margin-bottom: 1rem; - color: #00ff66; -} - -.controls { - margin-top: 1rem; -} - -.controls button { - margin: 0.25rem; - padding: 0.6rem 1rem; - font-size: 1rem; -} - -.bits { - display: grid; - grid-template-columns: repeat(8, 1fr); - gap: 1rem; - margin-top: 3rem; -} - -.bit { - width: 40px; - height: 80px; - background: #333; - border-radius: 20px; - position: relative; - cursor: pointer; -} - -.bit::after { - content: ""; - width: 32px; - height: 32px; - background: #555; - border-radius: 50%; - position: absolute; - bottom: 6px; - left: 4px; - transition: all 0.2s ease; -} - -.bit.on { - background: #00c853; -} - -.bit.on::after { - bottom: 42px; - background: #eaffea; -} diff --git a/src/styles/global.css b/src/styles/global.css new file mode 100644 index 0000000..9e7d095 --- /dev/null +++ b/src/styles/global.css @@ -0,0 +1,518 @@ +/* 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; + --text: #e8e8ee; + --muted: #a9acb8; + --line: rgba(255,255,255,.10); + + /* Fonts */ + --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; } + +html, body { height: 100%; } + +body { + margin: 0; + background: var(--bg); + color: var(--text); + font-family: var(--ui-font); + display: flex; + flex-direction: column; +} + +/* --- 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; + text-transform: uppercase; + 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; + text-transform: uppercase; +} +.navLinks a:hover { color: var(--text); } + +.pageWrap { + flex: 1; + max-width: 1400px; + margin: 0 auto; + padding: 0 20px 40px; + width: 100%; +} + +.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; + text-transform: uppercase; + display: flex; + flex-direction: column; + gap: 6px; +} + +/* --- BINARY 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: 26px; +} + +.binaryPage:not(.toolboxCollapsed) { + padding-right: calc(var(--toolbox-w) + var(--toolbox-gap)); +} +.binaryPage.toolboxCollapsed { + padding-right: 0; +} + +.topGrid { + display: flex; + align-items: flex-start; + gap: 28px; +} +.leftCol { + flex: 1 1 auto; + min-width: 0; +} + +.readout { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding-top: 14px; +} +.label { + font-family: var(--bit-font); + letter-spacing: .14em; + text-transform: uppercase; + font-size: 18px; + opacity: .75; +} +.num { + font-family: var(--num-font); + color: #28f07a; + text-shadow: 0 0 18px rgba(40,240,122,.35); + letter-spacing: 2px; +} +.denaryValue { + font-size: 68px; /* Increased */ + line-height: 1; +} +.binaryValue { + font-size: 54px; /* Increased */ + line-height: 1.1; + white-space: pre-wrap; + text-align: center; +} + +.divider { + height: 1px; + background: rgba(255,255,255,.08); + margin: 22px 0 18px; +} + +.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; +} + +.bulb { + width: 52px; /* Increased */ + height: 52px; /* Increased */ + 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; + filter: drop-shadow(0 0 14px rgba(255, 216, 107, 1)); +} + +.bitVal { + font-family: var(--bit-font); + font-size: min(44px, calc(160cqw / var(--len, 1))); /* Increased size and scale */ + letter-spacing: 2px; + color: rgba(232,232,238,.85); + white-space: nowrap; + line-height: 1; +} + +.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; +} +.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); +} + +.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; /* Increased */ + letter-spacing: .08em; + text-transform: uppercase; + color: rgba(232,232,238,.55); + margin-top: 10px; + line-height: 1.35; /* Restored line height so wraps look good */ + /* Removed nowrap so it can wrap naturally */ +} + +.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; } + +.btnRandomRunning { + background: rgba(40,240,122,.18) !important; + border-color: rgba(40,240,122,.35) !important; + animation: randomPulse 900ms ease-in-out infinite; +} +@keyframes randomPulse { + 0% { box-shadow: 0 0 0 rgba(40,240,122,0); } + 50% { box-shadow: 0 0 22px rgba(40,240,122,.35); } + 100% { box-shadow: 0 0 0 rgba(40,240,122,0); } +} + +.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); } +.toolRow2 { display: flex; gap: 10px; margin-bottom: 10px; } + +.btnReset { color: rgba(232,232,238,.95); } +.btnReset:hover { + background: rgba(255,80,80,.18); + border-color: rgba(255,80,80,.35); + animation: resetPulse 750ms ease-in-out infinite; +} +@keyframes resetPulse { + 0% { box-shadow: 0 0 0 rgba(255,80,80,0); } + 50% { box-shadow: 0 0 22px rgba(255,80,80,.38); } + 100% { box-shadow: 0 0 0 rgba(255,80,80,0); } +} + +/* Updated dynamic scaling so scaled items stay proportionately larger */ +.bits.size-sm .bulb { width: 36px; height: 36px; margin-bottom: 4px; } /* Scaled up */ +.bits.size-sm .bitVal { font-size: min(34px, calc(160cqw / var(--len, 1))); } /* Scaled up */ + +.bits.size-xs .bulb { width: 24px; height: 24px; margin-bottom: 2px; } /* Scaled up */ +.bits.size-xs .bitVal { font-size: min(22px, calc(160cqw / var(--len, 1))); } /* Scaled up */ + +@media (max-width: 1100px) { + .binaryPage { --toolbox-w: 330px; } + .denaryValue { font-size: 56px; } + .binaryValue { font-size: 44px; } +} +@media (max-width: 900px) { + .binaryPage { --toolbox-w: 320px; } + .bitsGrid { grid-template-columns: repeat(var(--cols), minmax(84px, 1fr)); } +} \ No newline at end of file