(layout): add global navigation, header, footer, and new assets

♻️ (binary): refactor to use BaseLayout and remove inline boilerplate

Migrate

♻️ (binary): refactor UI layout and extract inline script to external file

Extract the inline

 feat(binary): add binary calculator script for interactive UI

Introduce `binary.

 (binary-tool): add random generation, bit width, and toolbox controls

Add functions for

♻️ refactor: remove unused unsignedBinary.js and binary.css files

Delete the unsigned

Wait, the prompt gave a specific list of GitMojis:
 * 🐛, Fix

 feat: add styles for binary converter UI components and controls

Add CSS classes for buttons, inputs
This commit is contained in:
2026-03-01 13:58:19 +00:00
parent af131fc58a
commit 98671cdeee
11 changed files with 1107 additions and 859 deletions

View File

@@ -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"

View File

@@ -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)); }
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@@ -1,17 +1,47 @@
---
// src/layouts/BaseLayout.astro
import '../styles/global.css';
const { title = "Computing:Box" } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{title}</title>
<link rel="stylesheet" href="/styles.css" />
<link rel="stylesheet" href="/fonts/DSEG7Classic-Regular.css">
</head>
<body>
<slot />
</body>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>{title}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700;800;900&display=swap" rel="stylesheet">
</head>
<body>
<header class="siteNav">
<div class="navInner">
<a class="brand" href="/">
<img class="brandLogo" src="/images/computing-box-logo.svg" alt="Computing:Box logo" />
<span class="brandName">COMPUTING:BOX</span>
</a>
<nav class="navLinks" aria-label="Site navigation">
<a href="/about">ABOUT</a>
<a href="/binary">BINARY</a>
<a href="/hexadecimal">HEXADECIMAL</a>
<a href="/hex-colours">HEX COLOURS</a>
<a href="/logic-gates">LOGIC GATES</a>
</nav>
</div>
</header>
<main class="pageWrap">
<slot />
</main>
<footer class="siteFooter">
<div class="footerInner">
<div>COMPUTER SCIENCE CONCEPT SIMULATORS</div>
<div>© {new Date().getFullYear()} COMPUTING:BOX • CREATED WITH ♥ BY MR LYALL</div>
</div>
</footer>
</body>
</html>

View File

@@ -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";
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Binary | Computing:Box</title>
<BaseLayout title="Binary Simulator | Computing:Box">
<div class="binaryPage" id="binaryPage">
<button
id="toolboxToggle"
class="toolboxToggle"
type="button"
aria-expanded="true"
>
<span class="toolboxIcon" aria-hidden="true">🧰</span>
<span class="toolboxText">TOOLBOX</span>
</button>
<style is:global>
:root{
--bg: #1f2027;
--panel: #22242d;
--panel2:#262833;
--text: #e8e8ee;
--muted: #a9acb8;
--accent: #33ff7a;
--accent-dim: rgba(51,255,122,.15);
--line: rgba(255,255,255,.12);
}
<section class="topGrid">
<div class="leftCol">
<div class="readout">
<div class="label">Denary</div>
<div id="denaryNumber" class="num denaryValue">0</div>
@font-face{
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;
}
body{
margin:0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
background: var(--bg);
color: var(--text);
}
.wrap{
max-width: 1200px;
margin: 0 auto;
padding: 32px 20px 60px;
}
/* Layout: readout/bits on left, settings panel on right */
.layout{
display:grid;
grid-template-columns: 1fr 360px;
gap: 22px;
align-items:start;
}
.readout{
text-align:center;
padding: 10px 10px 0;
}
.label{
letter-spacing: .18em;
font-weight: 700;
color: var(--muted);
text-transform: uppercase;
font-size: 16px;
margin-top: 8px;
}
.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);
}
.value{
font-size: 70px;
line-height: 1.0;
margin: 6px 0 16px;
}
.binary{
font-size: 54px;
letter-spacing: .12em;
word-break: break-all;
}
.controls{
margin-top: 16px;
display:flex;
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: 160px;
}
.btn:active{ transform: translateY(1px); }
/* Settings panel cards */
.panel{
background: rgba(255,255,255,.04);
border: 1px solid rgba(255,255,255,.10);
border-radius: 14px;
padding: 16px 16px 14px;
margin-bottom: 14px;
}
.panelTitle{
font-size: 12px;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--muted);
font-weight: 800;
margin-bottom: 10px;
}
.panelRow{
display:flex;
align-items:center;
justify-content:space-between;
gap: 12px;
}
.hint{
color: var(--muted);
font-size: 12px;
margin-top: 10px;
line-height: 1.35;
}
/* Reusable toggle switch */
.switch{
position: relative;
width: 56px;
height: 34px;
display:inline-block;
flex: 0 0 auto;
}
.switch input{
opacity:0;
width:0;
height:0;
}
.slider{
position:absolute;
inset:0;
background: rgba(255,255,255,.10);
border: 1px solid rgba(255,255,255,.14);
border-radius: 999px;
transition: .18s ease;
}
.slider::before{
content:"";
position:absolute;
height: 28px;
width: 28px;
left: 3px;
top: 2px;
background: rgba(255,255,255,.92);
border-radius: 50%;
transition: .18s ease;
}
.switch input:checked + .slider{
background: rgba(51,255,122,.20);
border-color: rgba(51,255,122,.55);
}
.switch input:checked + .slider::before{
transform: translateX(22px);
background: var(--accent);
}
/* Bit width controls */
.bwControls{
display:grid;
grid-template-columns: 44px 1fr 44px;
gap: 10px;
align-items:center;
margin-top: 8px;
}
.bwBtn{
width: 44px;
height: 44px;
border-radius: 12px;
border: 1px solid rgba(255,255,255,.14);
background: rgba(255,255,255,.06);
color: #fff;
font-weight: 900;
cursor: pointer;
}
.bwBtn:active{ transform: translateY(1px); }
.bwInput{
display:flex;
align-items:center;
justify-content:space-between;
gap: 12px;
background: rgba(255,255,255,.05);
border: 1px solid rgba(255,255,255,.12);
border-radius: 12px;
padding: 10px 12px;
}
.bwLabel{
color: var(--muted);
font-size: 12px;
letter-spacing: .14em;
text-transform: uppercase;
font-weight: 800;
}
.bwField{
width: 100px;
text-align:right;
background: transparent;
border: none;
outline: none;
color: var(--accent);
font-family: "DSEG7ClassicRegular", ui-monospace, monospace;
font-size: 26px;
padding: 0;
}
.bwField::-webkit-outer-spin-button,
.bwField::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.bwField[type=number] { -moz-appearance: textfield; }
/* Bits grid */
.bits{
margin-top: 34px;
padding-top: 26px;
border-top: 1px solid var(--line);
display:grid;
gap: 18px;
align-items:end;
text-align:center;
}
.bit{
display:flex;
flex-direction:column;
align-items:center;
gap: 10px;
padding: 8px 4px;
}
.bitVal{
font-size: 34px;
color: var(--text);
opacity: .95;
}
/* Bulb */
.bulb{
width: 22px;
height: 22px;
border-radius: 50%;
background: rgba(255,255,255,.08);
border: 1px solid rgba(255,255,255,.12);
box-shadow: none;
margin-bottom: 6px;
}
.bulb.on{
background: #ffd86b;
border-color: rgba(255,216,107,.7);
box-shadow: 0 0 18px rgba(255,216,107,.6);
}
/* Responsive */
@media (max-width: 1000px){
.layout{ grid-template-columns: 1fr; }
.value{ font-size: 60px; }
.binary{ font-size: 44px; }
}
@media (max-width: 900px){
.value{ font-size: 54px; }
.binary{ font-size: 36px; }
.btn{ min-width: 140px; }
}
</style>
</head>
<body>
<main class="wrap">
<section class="layout">
<div>
<div class="readout">
<div class="label">Denary</div>
<div id="denaryNumber" class="value num">0</div>
<div class="label">Binary</div>
<div id="binaryNumber" class="value binary num">00000000</div>
<div class="controls">
<button class="btn" id="btnCustomBinary" type="button">Custom Binary</button>
<button class="btn" id="btnCustomDenary" type="button">Custom Denary</button>
<button class="btn" id="btnShiftLeft" type="button">Left Shift</button>
<button class="btn" id="btnShiftRight" type="button">Right Shift</button>
</div>
</div>
<section id="bitsGrid" class="bits" aria-label="Bit switches"></section>
<div class="label">Binary</div>
<div id="binaryNumber" class="num binaryValue">00000000</div>
</div>
<aside>
<div class="panel">
<div class="panelTitle">Mode</div>
<div class="panelRow">
<span style="font-weight:800;">Unsigned</span>
<label class="switch" aria-label="Toggle Two's complement mode">
<input id="modeToggle" type="checkbox" />
<span class="slider"></span>
</label>
<span style="font-weight:800;">Two's complement</span>
</div>
<div class="hint">
Tip: In two's complement, the left-most bit (MSB) represents a negative value.
</div>
<div class="divider"></div>
<section class="bitsWrap" aria-label="Bit switches">
<div class="bitsGrid" id="bitsGrid"></div>
</section>
</div>
<aside id="toolboxPanel" class="panelCol" aria-label="Toolbox">
<div class="card">
<div class="cardTitle">Settings</div>
<div class="toggleRow">
<div class="toggleLabel" id="lblUnsigned">Unsigned</div>
<label class="switch" aria-label="Toggle mode">
<input id="modeToggle" type="checkbox" />
<span class="slider"></span>
</label>
<div class="toggleLabel" id="lblTwos">Two's complement</div>
</div>
<div class="panel">
<div class="panelTitle">Bit width</div>
<div class="bwControls">
<button class="bwBtn" id="btnBitsDown" type="button" aria-label="Decrease bits"></button>
<div class="bwInput">
<div class="bwLabel">Bits</div>
<input id="bitsField" class="bwField" type="number" min="4" max="64" step="1" value="8" inputmode="numeric" />
<div class="hint" id="modeHint">
Tip: In unsigned binary, all bits represent positive values.
</div>
<div class="subCard">
<div class="subTitle">Bit width</div>
<div class="bitWidthRow">
<button class="miniBtn" id="btnBitsDown" type="button" aria-label="Decrease bits"></button>
<div class="bitInputWrap">
<div class="bitInputLabel">Bits</div>
<input
id="bitsInput"
class="bitInput"
type="number"
inputmode="numeric"
min="1"
max="64"
step="1"
value="8"
aria-label="Number of bits"
/>
</div>
<button class="bwBtn" id="btnBitsUp" type="button" aria-label="Increase bits">+</button>
</div>
<div class="hint">
Minimum 4 bits, maximum 64 bits.
<button class="miniBtn" id="btnBitsUp" type="button" aria-label="Increase bits">+</button>
</div>
</div>
</aside>
</div>
</section>
</main>
<div class="card">
<div class="cardTitle">Custom Number</div>
<div class="controlsRow">
<button class="btn btnAccent btnHalf" id="btnCustomBinary" type="button">Custom Binary</button>
<button class="btn btnAccent btnHalf" id="btnCustomDenary" type="button">Custom Denary</button>
</div>
<button class="btn btnWide" id="btnRandom" type="button">Random</button>
<div class="hint">Random runs briefly then stops automatically.</div>
</div>
<script type="module">
// ---------- State ----------
let bitsCount = 8;
let isTwos = false;
let bitStates = [];
<div class="card">
<div class="cardTitle">Tools</div>
<div class="toolRowCentered">
<button class="toolBtn toolSpin toolDec" id="btnDec" type="button" aria-label="Decrement">▼</button>
<button class="toolBtn toolSpin toolInc" id="btnInc" type="button" aria-label="Increment">▲</button>
</div>
<div class="toolRow2">
<button class="btn btnHalf" id="btnShiftLeft" type="button">Left Shift</button>
<button class="btn btnHalf" id="btnShiftRight" type="button">Right Shift</button>
</div>
<button class="btn btnReset btnWide" id="btnClear" type="button">Reset</button>
</div>
</aside>
</section>
</div>
// ---------- Helpers ----------
function clamp(n, min, max){ return Math.min(max, Math.max(min, n)); }
function pow2(n){ return 2 ** n; }
function getPlaceValues(){
const vals = [];
for (let i = 0; i < bitsCount; i++){
const power = bitsCount - 1 - i;
if (isTwos && i === 0){
vals.push(-pow2(power));
} else {
vals.push(pow2(power));
}
}
return vals;
}
function binaryString(){
return bitStates.map(b => (b ? "1" : "0")).join("");
}
function denaryValue(){
const values = getPlaceValues();
let sum = 0;
for (let i = 0; i < bitsCount; i++){
if (bitStates[i]) sum += values[i];
}
return sum;
}
function rangeForMode(){
if (!isTwos){
return { min: 0, max: pow2(bitsCount) - 1 };
}
return { min: -pow2(bitsCount - 1), max: pow2(bitsCount - 1) - 1 };
}
function updateReadout(){
document.getElementById("binaryNumber").innerText = binaryString();
document.getElementById("denaryNumber").innerText = String(denaryValue());
for (let i = 0; i < bitsCount; i++){
const bulb = document.getElementById(`bulb-${i}`);
if (bulb) bulb.classList.toggle("on", !!bitStates[i]);
}
}
// ---------- DOM build for bits grid ----------
function setBitsGridColumns(){
const grid = document.getElementById("bitsGrid");
const cols = clamp(bitsCount, 4, 16);
grid.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
}
function buildBitsGrid(){
const grid = document.getElementById("bitsGrid");
grid.innerHTML = "";
setBitsGridColumns();
const values = getPlaceValues();
for (let i = 0; i < bitsCount; i++){
const v = values[i];
const bit = document.createElement("div");
bit.className = "bit";
const bulb = document.createElement("div");
bulb.className = "bulb";
bulb.id = `bulb-${i}`;
bulb.setAttribute("aria-hidden", "true");
const val = document.createElement("div");
val.className = "bitVal num";
val.textContent = String(v);
const label = document.createElement("label");
label.className = "switch";
label.setAttribute("aria-label", `Toggle bit ${v}`);
const input = document.createElement("input");
input.type = "checkbox";
input.dataset.index = String(i);
input.checked = !!bitStates[i];
const slider = document.createElement("span");
slider.className = "slider";
input.addEventListener("change", () => {
bitStates[i] = input.checked;
updateReadout();
});
label.appendChild(input);
label.appendChild(slider);
bit.appendChild(bulb);
bit.appendChild(val);
bit.appendChild(label);
grid.appendChild(bit);
}
updateReadout();
}
// ---------- Set state from binary / denary ----------
function setFromBinary(bin){
const clean = String(bin).replace(/\s+/g, "");
if (!/^[01]+$/.test(clean)) return false;
const padded = clean.slice(-bitsCount).padStart(bitsCount, "0");
bitStates = [...padded].map(ch => ch === "1");
for (let i = 0; i < bitsCount; i++){
const toggle = document.querySelector(`input[type="checkbox"][data-index="${i}"]`);
if (toggle) toggle.checked = bitStates[i];
}
updateReadout();
return true;
}
function setFromDenary(n){
const num = Number(n);
if (!Number.isInteger(num)) return false;
const { min, max } = rangeForMode();
if (num < min || num > max) return false;
let unsignedVal;
if (!isTwos){
unsignedVal = num;
} else {
unsignedVal = num < 0 ? (pow2(bitsCount) + num) : num;
}
const newBits = [];
for (let i = 0; i < bitsCount; i++){
const power = bitsCount - 1 - i;
const bit = Math.floor(unsignedVal / pow2(power)) % 2;
newBits.push(bit === 1);
}
bitStates = newBits;
for (let i = 0; i < bitsCount; i++){
const toggle = document.querySelector(`input[type="checkbox"][data-index="${i}"]`);
if (toggle) toggle.checked = bitStates[i];
}
updateReadout();
return true;
}
// ---------- Shifts ----------
function shiftLeft(){
bitStates = bitStates.slice(1).concat(false);
buildBitsGrid();
}
function shiftRight(){
if (!isTwos){
bitStates = [false].concat(bitStates.slice(0, -1));
} else {
const msb = bitStates[0];
bitStates = [msb].concat(bitStates.slice(0, -1));
}
buildBitsGrid();
}
// ---------- Bit width ----------
function applyBitsCount(next){
const wanted = clamp(Number(next) || 8, 4, 64);
if (wanted === bitsCount) return;
const old = binaryString();
bitsCount = wanted;
const padded = old.slice(-bitsCount).padStart(bitsCount, "0");
bitStates = [...padded].map(ch => ch === "1");
document.getElementById("bitsField").value = String(bitsCount);
buildBitsGrid();
}
// ---------- Mode toggle ----------
function applyMode(nextIsTwos){
const prevDenary = denaryValue();
isTwos = !!nextIsTwos;
const { min, max } = rangeForMode();
const kept = clamp(prevDenary, min, max);
setFromDenary(kept);
buildBitsGrid();
}
// ---------- Wire up UI ----------
function wireUI(){
document.getElementById("btnShiftLeft").addEventListener("click", shiftLeft);
document.getElementById("btnShiftRight").addEventListener("click", shiftRight);
document.getElementById("btnCustomBinary").addEventListener("click", () => {
const val = prompt(`Enter a binary number (up to ${bitsCount} bits):`);
if (val === null) return;
if (!setFromBinary(val)) alert("Invalid input. Use only 0 and 1.");
});
document.getElementById("btnCustomDenary").addEventListener("click", () => {
const { min, max } = rangeForMode();
const val = prompt(`Enter a denary number (${min} to ${max}):`);
if (val === null) return;
if (!setFromDenary(val)) alert(`Invalid input. Enter an integer from ${min} to ${max}.`);
});
const modeToggle = document.getElementById("modeToggle");
modeToggle.addEventListener("change", () => applyMode(modeToggle.checked));
document.getElementById("btnBitsDown").addEventListener("click", () => applyBitsCount(bitsCount - 1));
document.getElementById("btnBitsUp").addEventListener("click", () => applyBitsCount(bitsCount + 1));
const bitsField = document.getElementById("bitsField");
bitsField.addEventListener("change", () => applyBitsCount(bitsField.value));
bitsField.addEventListener("blur", () => applyBitsCount(bitsField.value));
bitsField.addEventListener("keydown", (e) => {
if (e.key === "Enter") applyBitsCount(bitsField.value);
});
}
// ---------- Init ----------
function init(){
bitsCount = 8;
isTwos = false;
bitStates = Array(bitsCount).fill(false);
document.getElementById("modeToggle").checked = false;
document.getElementById("bitsField").value = String(bitsCount);
wireUI();
buildBitsGrid();
}
init();
</script>
</body>
</html>
<script src="../scripts/binary.js"></script>
</BaseLayout>

459
src/scripts/binary.js Normal file
View File

@@ -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 = `
<div class="bulb" id="bulb-${i}" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2.25a6.75 6.75 0 0 0-6.75 6.75c0 2.537 1.393 4.75 3.493 5.922l.507.282v1.546h5.5v-1.546l.507-.282A6.75 6.75 0 0 0 12 2.25Zm-2.25 16.5v.75a2.25 2.25 0 0 0 4.5 0v-.75h-4.5Z"/>
</svg>
</div>
<div class="bitVal" id="bitLabel-${i}"></div>
<label class="switch" aria-label="Toggle bit ${i}">
<input type="checkbox" data-index="${i}">
<span class="slider"></span>
</label>
`;
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);
})();

View File

@@ -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 (0255)"),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();

View File

@@ -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;
}

518
src/styles/global.css Normal file
View File

@@ -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)); }
}