You've already forked computing-box
✨ (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:
@@ -95,14 +95,14 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
if [ ! -d "export" ]; then
|
if [ ! -d "dist" ]; then
|
||||||
echo "❌ export/ folder not found in repo root"
|
echo "❌ dist/ folder not found in repo root"
|
||||||
ls -la
|
ls -la
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "Computing:Box Website.zip"
|
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"
|
test -s "Computing:Box Website.zip"
|
||||||
ls -lh "Computing:Box Website.zip"
|
ls -lh "Computing:Box Website.zip"
|
||||||
|
|
||||||
|
|||||||
@@ -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)); }
|
|
||||||
}
|
|
||||||
BIN
public/fonts/Seven-Segment.woff
Normal file
BIN
public/fonts/Seven-Segment.woff
Normal file
Binary file not shown.
BIN
public/fonts/Seven-Segment.woff2
Normal file
BIN
public/fonts/Seven-Segment.woff2
Normal file
Binary file not shown.
BIN
public/images/computing-box-logo.svg
Normal file
BIN
public/images/computing-box-logo.svg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
@@ -1,17 +1,47 @@
|
|||||||
---
|
---
|
||||||
// src/layouts/BaseLayout.astro
|
import '../styles/global.css';
|
||||||
|
|
||||||
const { title = "Computing:Box" } = Astro.props;
|
const { title = "Computing:Box" } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<link rel="stylesheet" href="/styles.css" />
|
|
||||||
<link rel="stylesheet" href="/fonts/DSEG7Classic-Regular.css">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
</head>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<body>
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700;800;900&display=swap" rel="stylesheet">
|
||||||
<slot />
|
</head>
|
||||||
</body>
|
<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>
|
</html>
|
||||||
@@ -1,601 +1,101 @@
|
|||||||
---
|
---
|
||||||
/**
|
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en">
|
<BaseLayout title="Binary Simulator | Computing:Box">
|
||||||
<head>
|
<div class="binaryPage" id="binaryPage">
|
||||||
<meta charset="utf-8" />
|
<button
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
id="toolboxToggle"
|
||||||
<title>Binary | Computing:Box</title>
|
class="toolboxToggle"
|
||||||
|
type="button"
|
||||||
|
aria-expanded="true"
|
||||||
|
>
|
||||||
|
<span class="toolboxIcon" aria-hidden="true">🧰</span>
|
||||||
|
<span class="toolboxText">TOOLBOX</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<style is:global>
|
<section class="topGrid">
|
||||||
:root{
|
<div class="leftCol">
|
||||||
--bg: #1f2027;
|
<div class="readout">
|
||||||
--panel: #22242d;
|
<div class="label">Denary</div>
|
||||||
--panel2:#262833;
|
<div id="denaryNumber" class="num denaryValue">0</div>
|
||||||
--text: #e8e8ee;
|
|
||||||
--muted: #a9acb8;
|
|
||||||
--accent: #33ff7a;
|
|
||||||
--accent-dim: rgba(51,255,122,.15);
|
|
||||||
--line: rgba(255,255,255,.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face{
|
<div class="label">Binary</div>
|
||||||
font-family: "DSEG7ClassicRegular";
|
<div id="binaryNumber" class="num binaryValue">00000000</div>
|
||||||
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>
|
</div>
|
||||||
|
|
||||||
<aside>
|
<div class="divider"></div>
|
||||||
<div class="panel">
|
|
||||||
<div class="panelTitle">Mode</div>
|
<section class="bitsWrap" aria-label="Bit switches">
|
||||||
<div class="panelRow">
|
<div class="bitsGrid" id="bitsGrid"></div>
|
||||||
<span style="font-weight:800;">Unsigned</span>
|
</section>
|
||||||
<label class="switch" aria-label="Toggle Two's complement mode">
|
</div>
|
||||||
<input id="modeToggle" type="checkbox" />
|
|
||||||
<span class="slider"></span>
|
<aside id="toolboxPanel" class="panelCol" aria-label="Toolbox">
|
||||||
</label>
|
<div class="card">
|
||||||
<span style="font-weight:800;">Two's complement</span>
|
<div class="cardTitle">Settings</div>
|
||||||
</div>
|
|
||||||
<div class="hint">
|
<div class="toggleRow">
|
||||||
Tip: In two's complement, the left-most bit (MSB) represents a negative value.
|
<div class="toggleLabel" id="lblUnsigned">Unsigned</div>
|
||||||
</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>
|
||||||
|
|
||||||
<div class="panel">
|
<div class="hint" id="modeHint">
|
||||||
<div class="panelTitle">Bit width</div>
|
Tip: In unsigned binary, all bits represent positive values.
|
||||||
<div class="bwControls">
|
</div>
|
||||||
<button class="bwBtn" id="btnBitsDown" type="button" aria-label="Decrease bits">−</button>
|
|
||||||
<div class="bwInput">
|
<div class="subCard">
|
||||||
<div class="bwLabel">Bits</div>
|
<div class="subTitle">Bit width</div>
|
||||||
<input id="bitsField" class="bwField" type="number" min="4" max="64" step="1" value="8" inputmode="numeric" />
|
<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>
|
</div>
|
||||||
<button class="bwBtn" id="btnBitsUp" type="button" aria-label="Increase bits">+</button>
|
<button class="miniBtn" id="btnBitsUp" type="button" aria-label="Increase bits">+</button>
|
||||||
</div>
|
|
||||||
<div class="hint">
|
|
||||||
Minimum 4 bits, maximum 64 bits.
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</div>
|
||||||
|
|
||||||
</section>
|
<div class="card">
|
||||||
</main>
|
<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">
|
<div class="card">
|
||||||
// ---------- State ----------
|
<div class="cardTitle">Tools</div>
|
||||||
let bitsCount = 8;
|
<div class="toolRowCentered">
|
||||||
let isTwos = false;
|
<button class="toolBtn toolSpin toolDec" id="btnDec" type="button" aria-label="Decrement">▼</button>
|
||||||
let bitStates = [];
|
<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 ----------
|
<script src="../scripts/binary.js"></script>
|
||||||
function clamp(n, min, max){ return Math.min(max, Math.max(min, n)); }
|
</BaseLayout>
|
||||||
|
|
||||||
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>
|
|
||||||
459
src/scripts/binary.js
Normal file
459
src/scripts/binary.js
Normal 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);
|
||||||
|
})();
|
||||||
@@ -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();
|
|
||||||
@@ -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
518
src/styles/global.css
Normal 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)); }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user