From 004d6974eccd6482b28a1b66a40a669d5a869b20 Mon Sep 17 00:00:00 2001 From: pos Date: Mon, 28 Sep 2026 14:50:49 +0200 Subject: [PATCH 1/6] M18: add interactive bit visualizer --- interactive/bit-visualizer/index.html | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 interactive/bit-visualizer/index.html diff --git a/interactive/bit-visualizer/index.html b/interactive/bit-visualizer/index.html new file mode 100644 index 0000000..b3dad83 --- /dev/null +++ b/interactive/bit-visualizer/index.html @@ -0,0 +1,23 @@ + + + + +EduNumbers – Bit-visualisering + +
+

← EduNumbers

+

Bit-visualisering

+

Se hvordan et usignert heltall bygges opp av individuelle biter. Klikk på et bit for å slå det av eller på.

+
+ + + +
+
+

Desimal:

+

Hex:

+

Binær:

+
+
+

Hvert kort viser bitposisjon, 2-potens og bidraget til totalverdien. Tykkere mellomrom markerer bytegrenser.

+
\ No newline at end of file From 9c5584036db69caa74ea8d3a2f2f54b9fc1021f4 Mon Sep 17 00:00:00 2001 From: pos Date: Mon, 28 Sep 2026 14:50:51 +0200 Subject: [PATCH 2/6] M18: implement bit visualizer --- interactive/bit-visualizer/bits.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 interactive/bit-visualizer/bits.js diff --git a/interactive/bit-visualizer/bits.js b/interactive/bit-visualizer/bits.js new file mode 100644 index 0000000..ac6fdfd --- /dev/null +++ b/interactive/bit-visualizer/bits.js @@ -0,0 +1,29 @@ +"use strict"; +const valueEl=document.querySelector("#value"), widthEl=document.querySelector("#width"); +const bitsEl=document.querySelector("#bits"), errorEl=document.querySelector("#error"); +function maxForWidth(w){return (1n<max) throw new Error(`Verdien må være mellom 0 og ${max} for ${w} bit.`); + return n; +} +function formatBinary(n,w){return n.toString(2).padStart(w,"0").replace(/(.{8})(?=.)/g,"$1 ")} +function render(){ + const w=Number(widthEl.value); let n; + try{n=parseUnsigned(valueEl.value,w);errorEl.textContent=""}catch(e){errorEl.textContent=e.message;bitsEl.replaceChildren();return} + document.querySelector("#decimal").textContent=n.toString(10); + document.querySelector("#hex").textContent="0x"+n.toString(16).toUpperCase().padStart(w/4,"0"); + document.querySelector("#binary").textContent=formatBinary(n,w); + bitsEl.replaceChildren(); + for(let pos=w-1;pos>=0;pos--){ + const mask=1n<bit ${pos}${on?1:0}2${pos}${on?mask:0n}`; + b.addEventListener("click",()=>{valueEl.value=(n^mask).toString();render()}); + bitsEl.appendChild(b); + } +} +valueEl.addEventListener("input",render);widthEl.addEventListener("change",render);render(); From c967978c64c0a57c787c8b592452ab10c6b5e745 Mon Sep 17 00:00:00 2001 From: pos Date: Mon, 28 Sep 2026 14:50:53 +0200 Subject: [PATCH 3/6] M18: style bit visualizer --- interactive/bit-visualizer/style.css | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 interactive/bit-visualizer/style.css diff --git a/interactive/bit-visualizer/style.css b/interactive/bit-visualizer/style.css new file mode 100644 index 0000000..6f80a05 --- /dev/null +++ b/interactive/bit-visualizer/style.css @@ -0,0 +1,6 @@ +:root{font-family:system-ui,sans-serif;line-height:1.5;color-scheme:light dark}body{margin:0}main{max-width:72rem;margin:3rem auto;padding:0 1.25rem} +.controls{display:flex;gap:1.5rem;flex-wrap:wrap;align-items:end}.controls label{display:grid;gap:.3rem}input,select{font:inherit;padding:.45rem} +#error{flex-basis:100%;min-height:1.5em;margin:0;font-weight:600}.summary{display:flex;gap:1.5rem;flex-wrap:wrap}code{overflow-wrap:anywhere} +.bits{display:flex;flex-wrap:wrap;gap:.35rem;align-items:stretch}.bit{font:inherit;min-width:4.2rem;padding:.55rem .35rem;display:grid;gap:.15rem;text-align:center;border:1px solid #8888;border-radius:.4rem;background:transparent;cursor:pointer} +.bit[aria-pressed="true"]{outline:2px solid currentColor}.bit strong{font-size:1.5rem}.bit span{font-size:.75rem}.byte-start{margin-left:1rem}.contrib{opacity:.75}.hint{margin-top:1.5rem}a{color:inherit} +@media(max-width:600px){.byte-start{margin-left:.35rem}.bit{min-width:3.8rem}} From e8313730ea74406f2cc53106838cac5432fbbef7 Mon Sep 17 00:00:00 2001 From: pos Date: Mon, 28 Sep 2026 14:50:59 +0200 Subject: [PATCH 4/6] M18: test bit visualizer core --- tests/bit-visualizer.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/bit-visualizer.test.js diff --git a/tests/bit-visualizer.test.js b/tests/bit-visualizer.test.js new file mode 100644 index 0000000..d759d0f --- /dev/null +++ b/tests/bit-visualizer.test.js @@ -0,0 +1,11 @@ +const fs=require("fs"),vm=require("vm"); +const src=fs.readFileSync("interactive/bit-visualizer/bits.js","utf8"); +const start=src.indexOf("function maxForWidth"),end=src.indexOf("function render"); +const box={};vm.createContext(box);vm.runInContext(src.slice(start,end),box); +const eq=(a,b,m)=>{if(a!==b)throw new Error(m+": "+a+" != "+b)}; +eq(box.maxForWidth(8),255n,"8-bit max");eq(box.maxForWidth(16),65535n,"16-bit max"); +eq(box.maxForWidth(32),4294967295n,"32-bit max"); +eq(box.parseUnsigned("255",8),255n,"parse 255"); +eq(box.formatBinary(0x1234n,16),"00010010 00110100","binary grouping"); +for(const [v,w] of [["256",8],["-1",8],["12x",16]]){let ok=false;try{box.parseUnsigned(v,w)}catch{ok=true}if(!ok)throw new Error("Expected rejection "+v)} +console.log("Bit visualizer tests passed"); From a173e21acd22cd2a70e8fd19ef32bc4c0563c8a3 Mon Sep 17 00:00:00 2001 From: pos Date: Mon, 28 Sep 2026 14:51:08 +0200 Subject: [PATCH 5/6] M18: qualify bit visualizer in CI --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d90262c..8485160 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,9 @@ jobs: - name: Check sources run: make check - name: Test interactive tools - run: node tests/base-converter.test.js + run: | + node tests/base-converter.test.js + node tests/bit-visualizer.test.js - name: Build HTML, EPUB, PDF and Kindle run: make all - name: Upload course artifacts From 4e5bda7f680ca452ae22694d22e8f99b6f0ae3cb Mon Sep 17 00:00:00 2001 From: pos Date: Mon, 28 Sep 2026 14:51:11 +0200 Subject: [PATCH 6/6] M18: link bit visualizer from Pages --- .github/workflows/pages.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 0965328..3257d18 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -57,6 +57,7 @@ jobs: Norsk English Basekonverterer + Bit-visualisering