Compare commits

...

2 Commits

Author SHA1 Message Date
e0e72c17e8 Fully functional logic gates page
All checks were successful
Pre-release on non-main branches / prerelease (push) Successful in 28s
Signed-off-by: Alexander Lyall <alex@adcm.uk>
2026-03-01 16:32:27 +00:00
ffab71cfcc Completed Wave 3 features:
All checks were successful
Pre-release on non-main branches / prerelease (push) Successful in 27s
- [X] New User Interface (Responsive)
- [X] Two's Compliment Simulator
- [X] Extended Binary Simulator (Custom bit sizes)
- [X] Unified Binary Simulator (Unsigned & Two's Completment combined)
- [X] Extended Hexadecimal Simulator
- [X] Unified Hexadecimal Simulator (For GCSE & A Level Specification)
- [X] Enhanced Gate Simulator (Truth Table Creator)
- [X] Compound Gate Simulator
- [ ] Computer Components Simulator

Signed-off-by: Alexander Lyall <alex@adcm.uk>
2026-03-01 16:22:58 +00:00
62 changed files with 3197 additions and 4500 deletions

View File

@@ -11,7 +11,7 @@ jobs:
steps: steps:
- name: Checkout (full history + tags) - name: Checkout (full history + tags)
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0

View File

@@ -11,7 +11,7 @@ jobs:
steps: steps:
- name: Checkout (full history + tags) - name: Checkout (full history + tags)
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
@@ -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"

View File

@@ -1,235 +0,0 @@
name: Changelog + Release on main
on:
push:
branches: [ main ]
workflow_dispatch:
jobs:
changelog_and_release:
runs-on: ubuntu-latest
steps:
- name: Checkout (full history + tags)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Stop if this is the bot changelog commit
shell: bash
run: |
set -e
msg="$(git log -1 --pretty=%B)"
echo "$msg" | tr -d '\r' | grep -qi "\[skip ci\]" && {
echo "Skipping (bot commit with [skip ci])"
exit 0
} || true
- name: Install git-cliff
shell: bash
run: |
set -e
GIT_CLIFF_VERSION="2.11.0"
URL="https://github.com/orhun/git-cliff/releases/download/v${GIT_CLIFF_VERSION}/git-cliff-${GIT_CLIFF_VERSION}-x86_64-unknown-linux-gnu.tar.gz"
curl -L "$URL" -o /tmp/git-cliff.tar.gz
tar -xzf /tmp/git-cliff.tar.gz -C /tmp
sudo install /tmp/git-cliff-*/git-cliff /usr/local/bin/git-cliff
git-cliff --version
- name: Generate CHANGELOG.md (Keep a Changelog)
shell: bash
run: |
set -e
git-cliff --config cliff.toml --output CHANGELOG.md
test -s CHANGELOG.md
- name: Commit and push CHANGELOG.md if changed (CHANGELOG_PAT)
shell: bash
env:
CHANGELOG_PAT: ${{ secrets.CHANGELOG_PAT }}
run: |
set -e
if git diff --quiet -- CHANGELOG.md; then
echo "No changelog changes."
else
git config user.name "changelog-bot"
git config user.email "changelog-bot@users.noreply.local"
git add CHANGELOG.md
git commit -m "docs(changelog): update changelog [skip ci]"
origin_url="$(git remote get-url origin)"
# Convert SSH origin to HTTPS if needed
if echo "$origin_url" | grep -q "^git@"; then
host="$(echo "$origin_url" | sed -E 's#git@([^:]+):.*#\1#')"
path="$(echo "$origin_url" | sed -E 's#git@[^:]+:(.*)#\1#')"
origin_url="https://$host/$path"
fi
authed_url="$(echo "$origin_url" | sed -E "s#^https://#https://oauth2:${CHANGELOG_PAT}@#")"
git push "$authed_url" HEAD:main
fi
- name: Extract newest changelog section for release body
shell: bash
run: |
set -e
# Extract the first "## ..." section (newest section) from CHANGELOG.md
# Includes the "## ..." heading and everything until the next "## ..." heading.
awk '
/^## / { if (seen) exit; seen=1 }
seen { print }
' CHANGELOG.md > RELEASE_NOTES.md
# Clean trailing whitespace/newlines a bit
sed -i 's/[[:space:]]*$//' RELEASE_NOTES.md
test -s RELEASE_NOTES.md
echo "---- RELEASE_NOTES.md ----"
head -n 60 RELEASE_NOTES.md
echo "--------------------------"
- name: Create export zip (Computing:Box Website.zip)
shell: bash
run: |
set -e
if [ ! -d "export" ]; then
echo "❌ export/ 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" .)
test -s "Computing:Box Website.zip"
ls -lh "Computing:Box Website.zip"
- name: Prepare YY.MM.DD letter-suffix tag + release name
shell: bash
run: |
set -e
# Version: YY.MM.DD (UTC). Swap to `date +...` if you prefer UK-local runner time.
VERSION="$(date -u +'%y.%m.%d')"
PREFIX="v${VERSION}."
last_letter="$(
git tag --list "${PREFIX}[a-z]" \
| sed -E "s/^${PREFIX}([a-z])$/\1/" \
| sort \
| tail -n 1
)"
if [ -z "$last_letter" ]; then
next_letter="a"
else
if [ "$last_letter" = "z" ]; then
echo "❌ Already have v${VERSION}.z today. Refusing to create more than 26 releases/day."
exit 1
fi
next_letter="$(printf "%b" "$(printf '\\%03o' "$(( $(printf '%d' "'$last_letter") + 1 ))")")"
fi
TAG="${PREFIX}${next_letter}"
RELEASE_NAME="Computing:Box v${VERSION}.${next_letter}"
echo "TAG=$TAG" >> "$GITHUB_ENV"
echo "RELEASE_NAME=$RELEASE_NAME" >> "$GITHUB_ENV"
echo "ZIP_PATH=Computing:Box Website.zip" >> "$GITHUB_ENV"
echo "Using tag: $TAG"
echo "Release name: $RELEASE_NAME"
- name: Create and push tag (CHANGELOG_PAT)
shell: bash
env:
CHANGELOG_PAT: ${{ secrets.CHANGELOG_PAT }}
run: |
set -e
git tag -f "$TAG"
origin_url="$(git remote get-url origin)"
# Convert SSH origin to HTTPS if needed
if echo "$origin_url" | grep -q "^git@"; then
host="$(echo "$origin_url" | sed -E 's#git@([^:]+):.*#\1#')"
path="$(echo "$origin_url" | sed -E 's#git@[^:]+:(.*)#\1#')"
origin_url="https://$host/$path"
fi
authed_url="$(echo "$origin_url" | sed -E "s#^https://#https://oauth2:${CHANGELOG_PAT}@#")"
git push "$authed_url" "refs/tags/$TAG" --force
- name: Create Gitea release + upload asset (CHANGELOG_PAT)
shell: bash
env:
CHANGELOG_PAT: ${{ secrets.CHANGELOG_PAT }}
run: |
set -e
origin_url="$(git remote get-url origin)"
if echo "$origin_url" | grep -q "^git@"; then
host="$(echo "$origin_url" | sed -E 's#git@([^:]+):.*#\1#')"
path="$(echo "$origin_url" | sed -E 's#git@[^:]+:(.*)#\1#')"
origin_url="https://$host/$path"
fi
base="$(echo "$origin_url" | sed -E 's#(https?://[^/]+)/.*#\1#')"
repo_path="$(echo "$origin_url" | sed -E 's#https?://[^/]+/##')"
repo_path="$(echo "$repo_path" | sed -E 's/\.git$//')"
owner="$(echo "$repo_path" | cut -d/ -f1)"
repo="$(echo "$repo_path" | cut -d/ -f2-)"
api="$base/api/v1"
python3 - <<'PY'
import json, os
tag = os.environ["TAG"]
name = os.environ["RELEASE_NAME"]
with open("RELEASE_NOTES.md", "r", encoding="utf-8") as f:
body = f.read()
payload = {
"tag_name": tag,
"target_commitish": "main",
"name": name,
"body": body, # newest section only
"draft": False,
"prerelease": False,
}
with open("release.json", "w", encoding="utf-8") as f:
json.dump(payload, f)
PY
curl -sS -X POST \
-H "Authorization: Bearer ${CHANGELOG_PAT}" \
-H "Content-Type: application/json" \
"${api}/repos/${owner}/${repo}/releases" \
--data-binary @release.json \
-o release_response.json
release_id="$(python3 - <<'PY'
import json
with open("release_response.json","r",encoding="utf-8") as f:
data=json.load(f)
rid=data.get("id")
if not rid:
raise SystemExit("No release id returned. Response:\n" + json.dumps(data, indent=2))
print(rid)
PY
)"
echo "Created release id: $release_id"
curl -sS -X POST \
-H "Authorization: Bearer ${CHANGELOG_PAT}" \
"${api}/repos/${owner}/${repo}/releases/${release_id}/assets?name=Computing%3ABox%20Website.zip" \
-F "attachment=@${ZIP_PATH}" \
>/dev/null
echo "✅ Release created: ${RELEASE_NAME} (tag: ${TAG}) with asset uploaded"

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
# build output # build output
# dist/ dist/
# generated types # generated types
.astro/ .astro/

View File

@@ -28,15 +28,18 @@ An evolution of Bit:Box & CS:Box to incorporate different elements of the UK Com
- [X] XNOR Gate Simulator - [X] XNOR Gate Simulator
### Wave 3 CS:Box Features (Spring 2026) ### Wave 3 CS:Box Features (Spring 2026)
- [ ] New User Interface (Responsive)
- [X] Two's Compliment Simulator - [X] Two's Compliment Simulator
- [ ] Extended Binary Simulator (Custom bit sizes) - [X] Extended Binary Simulator (Custom bit sizes)
- [ ] Unified Binary Simulator (Unsigned & Two's Completment combined) - [X] Unified Binary Simulator (Unsigned & Two's Completment combined)
- [ ] Extended Hexadecimal Simulator
- [ ] Unified Hexadecimal Simulator (For GCSE & A Level Specification)
- [ ] Enhanced Gate Simulator (Truth Table Creator) - [ ] Enhanced Gate Simulator (Truth Table Creator)
- [ ] Compound Gate Simulator - [ ] Compound Gate Simulator
- [ ] Computer Components Simulator - [ ] Computer Components Simulator
## Version 1.0 Release Date: 1<sup>st</sup> September 2025 ## Version 1.0 Release Date: 1<sup>st</sup> September 2025
## Version 2.0 Release Date (Goal): 1<sup>st</sup> April 2026 ## Version 2.0 Release Date (Goal): 1<sup>st</sup> May 2026
Shield: [![CC BY-NC-SA 4.0][cc-by-nc-sa-shield]][cc-by-nc-sa] Shield: [![CC BY-NC-SA 4.0][cc-by-nc-sa-shield]][cc-by-nc-sa]

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" width="115" height="48"><path fill="#17191E" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="url(#a)" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="#17191E" d="M.02 30.31s4.02-1.95 8.05-1.95l3.04-9.4c.11-.45.44-.76.82-.76.37 0 .7.31.82.76l3.04 9.4c4.77 0 8.05 1.95 8.05 1.95L17 11.71c-.2-.56-.53-.91-.98-.91H7.83c-.44 0-.76.35-.97.9L.02 30.31Zm42.37-5.97c0 1.64-2.05 2.62-4.88 2.62-1.85 0-2.5-.45-2.5-1.41 0-1 .8-1.49 2.65-1.49 1.67 0 3.09.03 4.73.23v.05Zm.03-2.04a21.37 21.37 0 0 0-4.37-.36c-5.32 0-7.82 1.25-7.82 4.18 0 3.04 1.71 4.2 5.68 4.2 3.35 0 5.63-.84 6.46-2.92h.14c-.03.5-.05 1-.05 1.4 0 1.07.18 1.16 1.06 1.16h4.15a16.9 16.9 0 0 1-.36-4c0-1.67.06-2.93.06-4.62 0-3.45-2.07-5.64-8.56-5.64-2.8 0-5.9.48-8.26 1.19.22.93.54 2.83.7 4.06 2.04-.96 4.95-1.37 7.2-1.37 3.11 0 3.97.71 3.97 2.15v.57Zm11.37 3c-.56.07-1.33.07-2.12.07-.83 0-1.6-.03-2.12-.1l-.02.58c0 2.85 1.87 4.52 8.45 4.52 6.2 0 8.2-1.64 8.2-4.55 0-2.74-1.33-4.09-7.2-4.39-4.58-.2-4.99-.7-4.99-1.28 0-.66.59-1 3.65-1 3.18 0 4.03.43 4.03 1.35v.2a46.13 46.13 0 0 1 4.24.03l.02-.55c0-3.36-2.8-4.46-8.2-4.46-6.08 0-8.13 1.49-8.13 4.39 0 2.6 1.64 4.23 7.48 4.48 4.3.14 4.77.62 4.77 1.28 0 .7-.7 1.03-3.71 1.03-3.47 0-4.35-.48-4.35-1.47v-.13Zm19.82-12.05a17.5 17.5 0 0 1-6.24 3.48c.03.84.03 2.4.03 3.24l1.5.02c-.02 1.63-.04 3.6-.04 4.9 0 3.04 1.6 5.32 6.58 5.32 2.1 0 3.5-.23 5.23-.6a43.77 43.77 0 0 1-.46-4.13c-1.03.34-2.34.53-3.78.53-2 0-2.82-.55-2.82-2.13 0-1.37 0-2.65.03-3.84 2.57.02 5.13.07 6.64.11-.02-1.18.03-2.9.1-4.04-2.2.04-4.65.07-6.68.07l.07-2.93h-.16Zm13.46 6.04a767.33 767.33 0 0 1 .07-3.18H82.6c.07 1.96.07 3.98.07 6.92 0 2.95-.03 4.99-.07 6.93h5.18c-.09-1.37-.11-3.68-.11-5.65 0-3.1 1.26-4 4.12-4 1.33 0 2.28.16 3.1.46.03-1.16.26-3.43.4-4.43-.86-.25-1.81-.41-2.96-.41-2.46-.03-4.26.98-5.1 3.38l-.17-.02Zm22.55 3.65c0 2.5-1.8 3.66-4.64 3.66-2.81 0-4.61-1.1-4.61-3.66s1.82-3.52 4.61-3.52c2.82 0 4.64 1.03 4.64 3.52Zm4.71-.11c0-4.96-3.87-7.18-9.35-7.18-5.5 0-9.23 2.22-9.23 7.18 0 4.94 3.49 7.59 9.21 7.59 5.77 0 9.37-2.65 9.37-7.6Z"/><defs><linearGradient id="a" x1="6.33" x2="19.43" y1="40.8" y2="34.6" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient></defs></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="1024" fill="none"><path fill="url(#a)" fill-rule="evenodd" d="M-217.58 475.75c91.82-72.02 225.52-29.38 341.2-44.74C240 415.56 372.33 315.14 466.77 384.9c102.9 76.02 44.74 246.76 90.31 366.31 29.83 78.24 90.48 136.14 129.48 210.23 57.92 109.99 169.67 208.23 155.9 331.77-13.52 121.26-103.42 264.33-224.23 281.37-141.96 20.03-232.72-220.96-374.06-196.99-151.7 25.73-172.68 330.24-325.85 315.72-128.6-12.2-110.9-230.73-128.15-358.76-12.16-90.14 65.87-176.25 44.1-264.57-26.42-107.2-167.12-163.46-176.72-273.45-10.15-116.29 33.01-248.75 124.87-320.79Z" clip-rule="evenodd" style="opacity:.154"/><path fill="url(#b)" fill-rule="evenodd" d="M1103.43 115.43c146.42-19.45 275.33-155.84 413.5-103.59 188.09 71.13 409 212.64 407.06 413.88-1.94 201.25-259.28 278.6-414.96 405.96-130 106.35-240.24 294.39-405.6 265.3-163.7-28.8-161.93-274.12-284.34-386.66-134.95-124.06-436-101.46-445.82-284.6-9.68-180.38 247.41-246.3 413.54-316.9 101.01-42.93 207.83 21.06 316.62 6.61Z" clip-rule="evenodd" style="opacity:.154"/><defs><linearGradient id="b" x1="373" x2="1995.44" y1="1100" y2="118.03" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient><linearGradient id="a" x1="107.37" x2="1130.66" y1="1993.35" y2="1026.31" gradientUnits="userSpaceOnUse"><stop stop-color="#3245FF"/><stop offset="1" stop-color="#BC52EE"/></linearGradient></defs></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,12 @@
(()=>{const d=document.getElementById("bitsGrid"),h=document.getElementById("denaryNumber"),M=document.getElementById("binaryNumber"),f=document.getElementById("bitsInput"),m=document.getElementById("modeToggle"),E=document.getElementById("modeHint"),T=document.getElementById("lblUnsigned"),k=document.getElementById("lblTwos"),H=document.getElementById("btnCustomBinary"),G=document.getElementById("btnCustomDenary"),P=document.getElementById("btnShiftLeft"),V=document.getElementById("btnShiftRight"),q=document.getElementById("btnDec"),Z=document.getElementById("btnInc"),z=document.getElementById("btnClear"),I=document.getElementById("btnRandom"),O=document.getElementById("btnBitsUp"),W=document.getElementById("btnBitsDown"),R=document.getElementById("toolboxToggle"),w=document.getElementById("binaryPage");let i=b(Number(f?.value??8),1,64),s=new Array(i).fill(!1),u=null;function b(t,n,e){return Number.isFinite(t)?Math.max(n,Math.min(e,Math.trunc(t))):n}function l(){return!!m?.checked}function a(t){return 1n<<BigInt(t)}function y(t){return a(t)}function $(t){return a(t)-1n}function B(t){return-a(t-1)}function p(t){return a(t-1)-1n}function v(){let t=0n;for(let n=0;n<i;n++)s[n]&&(t+=a(n));return t}function g(t){const n=y(i),e=(t%n+n)%n;for(let o=0;o<i;o++)s[o]=(e>>BigInt(o)&1n)===1n}function L(){const t=v();return s[i-1]===!0?t-a(i):t}function x(t){const n=a(i);let e=t;e=(e%n+n)%n,g(e)}function j(){let t="";for(let n=i-1;n>=0;n--){t+=s[n]?"1":"0";const e=i-n;n!==0&&e%4===0&&(t+=" ")}return t.trimEnd()}function D(){E&&(l()?E.textContent="Tip: In two's complement, the left-most bit (MSB) represents a negative value.":E.textContent="Tip: In unsigned binary, all bits represent positive values.")}function A(){if(!d)return;const t=d.parentElement;if(!t)return;const n=t.getBoundingClientRect().width,o=b(Math.floor(n/100),1,12);d.style.setProperty("--cols",String(Math.min(o,i)))}function C(t){i=b(t,1,64),f&&(f.value=String(i));const n=s.slice();s=new Array(i).fill(!1);for(let e=0;e<Math.min(n.length,i);e++)s[e]=n[e];d.innerHTML="",d.classList.toggle("bitsFew",i<8);for(let e=i-1;e>=0;e--){const o=document.createElement("div");o.className="bit",o.innerHTML=`
<div class="bulb" id="bulb-${e}" 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-${e}"></div>
<label class="switch" aria-label="Toggle bit ${e}">
<input type="checkbox" data-index="${e}">
<span class="slider"></span>
</label>
`,d.appendChild(o)}d.querySelectorAll('input[type="checkbox"]').forEach(e=>{e.addEventListener("change",()=>{const o=Number(e.dataset.index);s[o]=e.checked,r()})}),A(),r()}function J(){for(let t=0;t<i;t++){const n=document.getElementById(`bitLabel-${t}`);if(!n)continue;let e;l()&&t===i-1?e=`-${a(i-1).toString()}`:e=a(t).toString(),n.textContent=e,n.style.setProperty("--len",e.length)}}function K(){d.querySelectorAll('input[type="checkbox"]').forEach(t=>{const n=Number(t.dataset.index);t.checked=!!s[n]})}function Q(){for(let t=0;t<i;t++){const n=document.getElementById(`bulb-${t}`);n&&n.classList.toggle("on",s[t]===!0)}}function X(){!h||!M||(l()?h.textContent=L().toString():h.textContent=v().toString(),M.textContent=j())}function r(){D(),T&&k&&(T.classList.toggle("activeMode",!l()),k.classList.toggle("activeMode",l())),J(),K(),Q(),X()}function Y(t){const n=String(t??"").replace(/\s+/g,"");if(!/^[01]+$/.test(n))return!1;const e=n.slice(-i).padStart(i,"0");for(let o=0;o<i;o++){const c=e[e.length-1-o];s[o]=c==="1"}return r(),!0}function _(t){const n=String(t??"").trim();if(!n)return!1;let e;try{if(!/^-?\d+$/.test(n))return!1;e=BigInt(n)}catch{return!1}if(l()){const o=B(i),c=p(i);if(e<o||e>c)return!1;x(e)}else{if(e<0n||e>$(i))return!1;g(e)}return r(),!0}function tt(){for(let t=i-1;t>=1;t--)s[t]=s[t-1];s[0]=!1,r()}function nt(){const t=s[i-1];for(let n=0;n<i-1;n++)s[n]=s[n+1];s[i-1]=l()?t:!1,r()}function et(){s=[],m&&(m.checked=!1),C(8)}function it(){if(l()){const t=B(i),n=p(i);let e=L()+1n;e>n&&(e=t),x(e)}else{const t=y(i);g((v()+1n)%t)}r()}function ot(){if(l()){const t=B(i),n=p(i);let e=L()-1n;e<t&&(e=n),x(e)}else{const t=y(i);g((v()-1n+t)%t)}r()}function st(t){if(t<=0n)return 0n;const n=t.toString(2).length,e=Math.ceil(n/8);for(;;){const o=new Uint8Array(e);crypto.getRandomValues(o);let c=0n;for(const ct of o)c=c<<8n|BigInt(ct);const U=BigInt(e*8-n);if(U>0n&&(c=c>>U),c<t)return c}}function lt(){const t=y(i),n=st(t);g(n),r()}function N(t){I&&I.classList.toggle("btnRandomRunning",!!t)}function rt(){u&&(clearInterval(u),u=null),N(!0);const t=Date.now(),n=1125;u=setInterval(()=>{lt(),Date.now()-t>=n&&(clearInterval(u),u=null,N(!1))},80)}function S(t){const n=b(t,1,64);C(n)}function F(t){if(!w)return;w.classList.toggle("toolboxCollapsed",!!t);const n=!t;R?.setAttribute("aria-expanded",n?"true":"false")}m?.addEventListener("change",r),H?.addEventListener("click",()=>{const t=prompt(`Enter binary (spaces allowed). Current width: ${i} bits`);t!==null&&(Y(t)||alert("Invalid binary"))}),G?.addEventListener("click",()=>{const t=prompt(l()?`Enter denary (${B(i).toString()} to ${p(i).toString()}):`:`Enter denary (0 to ${$(i).toString()}):`);t!==null&&(_(t)||alert("Invalid denary for current mode/bit width"))}),P?.addEventListener("click",tt),V?.addEventListener("click",nt),Z?.addEventListener("click",it),q?.addEventListener("click",ot),z?.addEventListener("click",et),I?.addEventListener("click",rt),O?.addEventListener("click",()=>S(i+1)),W?.addEventListener("click",()=>S(i-1)),f?.addEventListener("change",()=>S(Number(f.value))),R?.addEventListener("click",()=>{const t=w?.classList.contains("toolboxCollapsed");F(!t)}),window.addEventListener("resize",()=>{A()}),D(),C(i),F(!1)})();

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,3 @@
<!DOCTYPE html><html lang="en" data-astro-cid-37fxchfa> <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Binary Simulator</title><style>:root{--nav-h: 108px;--bg: #1f2027;--text: #e8e8ee;--muted: #a9acb8;--line: rgba(255,255,255,.1)}body{margin:0;background:var(--bg);color:var(--text)}.siteNav[data-astro-cid-37fxchfa]{position:sticky;top:0;z-index:50;height:var(--nav-h);background:#0000001a;border-bottom:1px solid var(--line);backdrop-filter:blur(8px)}.navInner[data-astro-cid-37fxchfa]{height:100%;max-width:1400px;margin:0 auto;padding:0 20px;display:flex;align-items:center;justify-content:space-between;gap:24px}.brand[data-astro-cid-37fxchfa]{display:flex;align-items:center;gap:12px;text-decoration:none;color:var(--text)}.brandLogo[data-astro-cid-37fxchfa]{width:2em;height:2em;image-rendering:pixelated}.brandName[data-astro-cid-37fxchfa]{letter-spacing:.12em;font-weight:900;text-transform:uppercase;font-size:18px}.navLinks[data-astro-cid-37fxchfa]{display:flex;align-items:center;gap:18px;flex-wrap:wrap}.navLinks[data-astro-cid-37fxchfa] a[data-astro-cid-37fxchfa]{color:var(--muted);text-decoration:none;font-weight:800;letter-spacing:.12em;font-size:16px;text-transform:uppercase}.navLinks[data-astro-cid-37fxchfa] a[data-astro-cid-37fxchfa]:hover{color:var(--text)}.pageWrap[data-astro-cid-37fxchfa]{max-width:1400px;margin:0 auto} <!DOCTYPE html><html lang="en"> <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Binary Simulator | Computing:Box</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"><link rel="stylesheet" href="/_astro/binary.BbxrcYRT.css"></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"> <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> <section class="topGrid"> <div class="leftCol"> <div class="readout"> <div class="label">Denary</div> <div id="denaryNumber" class="num denaryValue">0</div> <div class="label">Binary</div> <div id="binaryNumber" class="num binaryValue">00000000</div> </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="hint" id="modeHint">
:root{--panel-w: 360px;--gap: 22px}.wrap{max-width:1400px;margin:0 auto;padding:26px 20px 48px;position:relative}.topGrid{display:grid;grid-template-columns:1fr var(--panel-w);gap:var(--gap);align-items:start}body.toolboxClosed .topGrid{grid-template-columns:1fr}body.toolboxClosed #toolboxPanel{display:none}.mainCol{min-width:0}.readout{text-align:center;margin-top:8px}.label{opacity:.8;letter-spacing:.12em;text-transform:uppercase;font-size:12px}.num{display:inline-block;width:fit-content;max-width:100%;white-space:pre-line;letter-spacing:2px}.denaryValue{font-size:54px;margin:6px 0 10px}.binaryValue{font-size:56px;margin:4px 0 18px}.divider{height:1px;background:#ffffff1a;margin:14px auto 24px;max-width:900px}.bitsWrap{padding-top:6px}.bitsGrid{display:grid;gap:24px;justify-content:center}.bitsGrid{grid-template-columns:repeat(auto-fit,minmax(110px,1fr));max-width:1200px;margin:0 auto}.bitsGrid.bitsFew{justify-content:center}.bit{display:grid;justify-items:center;gap:8px}.bulb{font-size:32px;line-height:1;opacity:.45}.bitVal{font-size:22px;line-height:1.05;text-align:center;white-space:nowrap}.switch{position:relative;display:inline-block;width:52px;height:28px}.switch input{display:none}.slider{position:absolute;inset:0;border-radius:999px;background:#ffffff2e;border:1px solid rgba(255,255,255,.14)}.slider:before{content:"";position:absolute;height:22px;width:22px;left:3px;top:2.5px;border-radius:999px;background:#fff;transition:transform .18s ease}.switch input:checked+.slider:before{transform:translate(22px)}.toolboxToggle{position:absolute;right:20px;top:18px;z-index:20;display:inline-flex;align-items:center;gap:10px;padding:10px 14px;border-radius:12px;border:1px solid rgba(255,255,255,.14);background:#ffffff0f;color:#ffffffeb;cursor:pointer}.toolboxText{letter-spacing:.12em;font-weight:900}.panelCol{position:sticky;top:calc(var(--nav-h, 72px) + 18px);align-self:start;display:grid;gap:16px}.card{border:1px solid rgba(255,255,255,.12);border-radius:16px;background:#ffffff0d;padding:14px}.cardTitle{opacity:.8;letter-spacing:.14em;text-transform:uppercase;font-size:12px;margin-bottom:10px}.hint{opacity:.7;font-size:11px;margin-top:10px;line-height:1.35}.toggleRow{display:grid;grid-template-columns:1fr auto 1fr;gap:10px;align-items:center}.toggleLabel{font-size:12px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;white-space:nowrap}.subCard{margin-top:12px;border:1px solid rgba(255,255,255,.1);border-radius:14px;background:#0000001f;padding:12px}.subTitle{opacity:.8;letter-spacing:.14em;text-transform:uppercase;font-size:11px;margin-bottom:10px}.bitWidthRow{display:grid;grid-template-columns:44px 1fr 44px;gap:10px;align-items:center}.bitInputWrap{display:grid;grid-template-columns:auto 1fr;gap:10px;align-items:center;padding:10px 12px;border:1px solid rgba(255,255,255,.1);border-radius:12px;background:#ffffff0a}.bitInputLabel{opacity:.75;letter-spacing:.14em;text-transform:uppercase;font-size:11px;white-space:nowrap}.bitInput{width:100%;min-width:0;background:transparent;border:none;outline:none;color:inherit;font-size:20px;text-align:right}.miniBtn{height:44px;border-radius:12px;border:1px solid rgba(255,255,255,.12);background:#ffffff0f;color:#ffffffe6;font-size:18px;cursor:pointer}.controlsRow{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:10px}.btn{border-radius:12px;border:1px solid rgba(255,255,255,.12);background:#ffffff0f;color:#ffffffeb;padding:12px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;cursor:pointer}.btnWide{width:100%}.btnAccent{background:#00ff8c1f;border-color:#00ff8c38}.toolRowCentered{display:flex;justify-content:center;gap:12px;margin:10px 0 12px}.toolBtn{width:56px;height:56px;border-radius:14px;border:1px solid rgba(255,255,255,.12);background:#ffffff0f;color:#ffffffeb;font-size:18px;cursor:pointer}.toolDec{background:#ff000024;border-color:#f003}.toolInc{background:#00ff8c24;border-color:#00ff8c33}.toolRow2{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:12px}.btnReset{color:#ffffffeb}
</style></head> <body data-astro-cid-37fxchfa> <header class="siteNav" data-astro-cid-37fxchfa> <div class="navInner" data-astro-cid-37fxchfa> <a class="brand" href="/" data-astro-cid-37fxchfa> <img class="brandLogo" src="/images/computing-box-logo.svg" alt="Computing:Box logo" data-astro-cid-37fxchfa> <span class="brandName" data-astro-cid-37fxchfa>COMPUTING:BOX</span> </a> <nav class="navLinks" aria-label="Site navigation" data-astro-cid-37fxchfa> <a href="/about" data-astro-cid-37fxchfa>ABOUT</a> <a href="/binary" data-astro-cid-37fxchfa>BINARY</a> <a href="/hexadecimal" data-astro-cid-37fxchfa>HEXADECIMAL</a> <a href="/hex-colours" data-astro-cid-37fxchfa>HEX COLOURS</a> <a href="/logic-gates" data-astro-cid-37fxchfa>LOGIC GATES</a> </nav> </div> </header> <main class="pageWrap" data-astro-cid-37fxchfa> <button id="toolboxToggle" class="toolboxToggle" type="button" aria-expanded="true"> <span class="toolboxIcon" aria-hidden="true">🧰</span> <span class="toolboxLabel">TOOLBOX</span> </button> <main class="wrap"> <section class="topGrid"> <!-- LEFT --> <div> <div class="readout"> <div class="label">Denary</div> <div id="denaryNumber" class="num denaryValue">0</div> <div class="label">Binary</div> <div id="binaryNumber" class="num binaryValue">0000 0000</div> </div> <div class="divider"></div> <section class="bitsWrap" aria-label="Bit switches"> <div class="bitsGrid" id="bitsGrid"></div> </section> </div> <!-- RIGHT TOOLBOX --> <aside id="toolbox" class="panelCol" aria-label="Toolbox"> <!-- SETTINGS --> <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&rsquo;s complement</div> </div> <div class="hint" id="modeHint">
Tip: In unsigned binary, all bits represent positive values. 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="miniBtn" id="btnBitsUp" type="button" aria-label="Increase bits">+</button> </div> </div> </div> <!-- CUSTOM --> <div class="card"> <div class="cardTitle">Custom</div> <div class="twoBtnRow"> <button class="btn btnAccent" id="btnCustomBinary" type="button">Custom Binary</button> <button class="btn btnAccent" id="btnCustomDenary" type="button">Custom Denary</button> </div> <button class="toolBtn toolWide toolRandom" id="btnRandom" type="button"> </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="miniBtn" id="btnBitsUp" type="button" aria-label="Increase bits">+</button> </div> </div> </div> <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> <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> <script type="module" src="/_astro/binary.astro_astro_type_script_index_0_lang.C_c_A3x5.js"></script> </main> <footer class="siteFooter"> <div class="footerInner"> <div>COMPUTER SCIENCE CONCEPT SIMULATORS</div> <div>© 2026 COMPUTING:BOX • CREATED WITH ♥ BY MR LYALL</div> </div> </footer> </body></html>
Random
</button> <div class="hint">Random runs briefly then stops automatically.</div> </div> <!-- TOOLS --> <div class="card"> <div class="cardTitle">Tools</div> <div class="toolsTopRow"> <button class="toolBtn toolArrow toolDown" id="btnDec" type="button" aria-label="Decrement"></button> <button class="toolBtn toolArrow toolUp" id="btnInc" type="button" aria-label="Increment"></button> </div> <div class="twoBtnRow"> <button class="btn" id="btnShiftLeft" type="button">Left Shift</button> <button class="btn" id="btnShiftRight" type="button">Right Shift</button> </div> <button class="toolBtn toolWide toolReset" id="btnClear" type="button">Reset</button> </div> </aside> </section> </main> <script type="module" src="/src/scripts/binary.js"></script> </main> </body></html>

9
dist/favicon.svg vendored Normal file
View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 749 B

View File

@@ -1,5 +1,3 @@
<!DOCTYPE html><html lang="en" data-astro-cid-37fxchfa> <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Hexadecimal | Computing:Box</title><style>:root{--nav-h: 108px;--bg: #1f2027;--text: #e8e8ee;--muted: #a9acb8;--line: rgba(255,255,255,.1)}body{margin:0;background:var(--bg);color:var(--text)}.siteNav[data-astro-cid-37fxchfa]{position:sticky;top:0;z-index:50;height:var(--nav-h);background:#0000001a;border-bottom:1px solid var(--line);backdrop-filter:blur(8px)}.navInner[data-astro-cid-37fxchfa]{height:100%;max-width:1400px;margin:0 auto;padding:0 20px;display:flex;align-items:center;justify-content:space-between;gap:24px}.brand[data-astro-cid-37fxchfa]{display:flex;align-items:center;gap:12px;text-decoration:none;color:var(--text)}.brandLogo[data-astro-cid-37fxchfa]{width:2em;height:2em;image-rendering:pixelated}.brandName[data-astro-cid-37fxchfa]{letter-spacing:.12em;font-weight:900;text-transform:uppercase;font-size:18px}.navLinks[data-astro-cid-37fxchfa]{display:flex;align-items:center;gap:18px;flex-wrap:wrap}.navLinks[data-astro-cid-37fxchfa] a[data-astro-cid-37fxchfa]{color:var(--muted);text-decoration:none;font-weight:800;letter-spacing:.12em;font-size:16px;text-transform:uppercase}.navLinks[data-astro-cid-37fxchfa] a[data-astro-cid-37fxchfa]:hover{color:var(--text)}.pageWrap[data-astro-cid-37fxchfa]{max-width:1400px;margin:0 auto} <!DOCTYPE html><html lang="en"> <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Hexadecimal Simulator | Computing:Box</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"><link rel="stylesheet" href="/_astro/binary.BbxrcYRT.css"></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"> <div class="binaryPage" id="hexPage"> <button id="toolboxToggle" class="toolboxToggle" type="button" aria-expanded="true"> <span class="toolboxIcon" aria-hidden="true">🧰</span> <span class="toolboxText">TOOLBOX</span> </button> <section class="topGrid"> <div class="leftCol"> <div class="readout"> <div class="label">Denary</div> <div id="denaryNumber" class="num denaryValue">0</div> <div class="label">Hexadecimal</div> <div id="hexNumber" class="num hexValue">00</div> <div class="label">Binary</div> <div id="binaryNumber" class="num binaryValue">00000000</div> </div> <div class="divider"></div> <section class="bitsWrap" aria-label="Hexadecimal sliders"> <div class="hexGrid" id="hexGrid"></div> </section> </div> <aside id="toolboxPanel" class="panelCol" aria-label="Toolbox"> <div class="card"> <div class="cardTitle">Settings</div> <div class="hint" style="margin-top: 0; margin-bottom: 14px;">
</style> Hexadecimal represents numbers using base 16 (0-9, A-F).
<link rel="stylesheet" href="/_astro/hexadecimal.C_Opoo6d.css"></head> <body data-astro-cid-37fxchfa> <header class="siteNav" data-astro-cid-37fxchfa> <div class="navInner" data-astro-cid-37fxchfa> <a class="brand" href="/" data-astro-cid-37fxchfa> <img class="brandLogo" src="/images/computing-box-logo.svg" alt="Computing:Box logo" data-astro-cid-37fxchfa> <span class="brandName" data-astro-cid-37fxchfa>COMPUTING:BOX</span> </a> <nav class="navLinks" aria-label="Site navigation" data-astro-cid-37fxchfa> <a href="/about" data-astro-cid-37fxchfa>ABOUT</a> <a href="/binary" data-astro-cid-37fxchfa>BINARY</a> <a href="/hexadecimal" data-astro-cid-37fxchfa>HEXADECIMAL</a> <a href="/hex-colours" data-astro-cid-37fxchfa>HEX COLOURS</a> <a href="/logic-gates" data-astro-cid-37fxchfa>LOGIC GATES</a> </nav> </div> </header> <main class="pageWrap" data-astro-cid-37fxchfa> <section class="hex-sim" data-hex-sim> <div class="hex-main"> <div class="hex-readout"> <div class="hex-label">DENARY</div> <div class="hex-number" data-out="denary">0</div> <div class="hex-label hex-mt">HEXADECIMAL</div> <div class="hex-number hex-number--small" data-out="hex">00</div> <div class="hex-label hex-mt">BINARY</div> <div class="hex-number hex-number--tiny" data-out="bin">0000 0000</div> </div> <div class="hex-divider"></div> <div class="hex-digits" data-out="digitsRow"></div> </div> <!-- Toolbox button --> <button class="hex-toolbox-btn" type="button" data-action="toggleToolbox" aria-controls="hex-toolbox" aria-expanded="true"> <span class="hex-toolbox-icon" aria-hidden="true"> <!-- toolbox icon --> <svg viewBox="0 0 24 24" width="18" height="18" fill="none"> <path d="M9 7V6a3 3 0 0 1 6 0v1" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path> <path d="M4 9h16l-1.3 10.4A2 2 0 0 1 16.7 21H7.3a2 2 0 0 1-1.98-1.6L4 9Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"></path> <path d="M10 13h4" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path> </svg> </span> </div> <div class="subCard"> <div class="subTitle">Digit width</div> <div class="bitWidthRow"> <button class="miniBtn" id="btnDigitsDown" type="button" aria-label="Decrease digits"></button> <div class="bitInputWrap"> <div class="bitInputLabel">Digits</div> <input id="digitsInput" class="bitInput" type="number" inputmode="numeric" min="1" max="16" step="1" value="2" aria-label="Number of hex digits"> </div> <button class="miniBtn" id="btnDigitsUp" type="button" aria-label="Increase digits">+</button> </div> </div> </div> <div class="card"> <div class="cardTitle">Custom Number</div> <div class="controlsRow"> <button class="btn btnAccent btnHalf" id="btnCustomHex" type="button">Custom Hex</button> <button class="btn btnAccent btnHalf" id="btnCustomDenary" type="button">Custom Denary</button> </div> <div class="controlsRow"> <button class="btn btnAccent btnWide" id="btnCustomBinary" type="button">Custom Binary</button> </div> <button class="btn btnWide" id="btnRandom" type="button">Random</button> <div class="hint">Random runs briefly then stops automatically.</div> </div> <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> <button class="btn btnReset btnWide" id="btnClear" type="button">Reset</button> </div> </aside> </section> </div> <script type="module" src="/_astro/hexadecimal.astro_astro_type_script_index_0_lang.C4Wx7oaX.js"></script> </main> <footer class="siteFooter"> <div class="footerInner"> <div>COMPUTER SCIENCE CONCEPT SIMULATORS</div> <div>© 2026 COMPUTING:BOX • CREATED WITH ♥ BY MR LYALL</div> </div> </footer> </body></html>
TOOLBOX
</button> <!-- Toolbox panel --> <aside class="hex-toolbox is-open" id="hex-toolbox" data-out="toolbox"> <div class="hex-panel"> <div class="hex-panel-title">SETTINGS</div> <div class="hex-setting-title">HEX DIGIT WIDTH</div> <div class="hex-width"> <button class="hex-btn hex-btn--square" type="button" data-action="digitsMinus"></button> <div class="hex-width-readout"> <div class="hex-width-label">DIGITS</div> <div class="hex-width-number" data-out="digitsCount">2</div> </div> <button class="hex-btn hex-btn--square" type="button" data-action="digitsPlus">+</button> </div> <div class="hex-hint" data-out="bitsHint">= 8 bits</div> </div> <div class="hex-panel"> <div class="hex-panel-title">CUSTOM NUMBER</div> <div class="hex-grid-2"> <button class="hex-btn hex-btn--green" type="button" data-action="customHex">Custom Hexadecimal</button> <button class="hex-btn hex-btn--green" type="button" data-action="customDenary">Custom Denary</button> </div> <!-- Custom Binary + Random on SAME row, same size --> <div class="hex-grid-2 hex-mt-sm"> <button class="hex-btn hex-btn--green" type="button" data-action="customBinary">Custom Binary</button> <button class="hex-btn hex-btn--wide hex-btn--random" type="button" data-action="random" data-random>Random</button> </div> <div class="hex-tiny-note">RANDOM RUNS BRIEFLY THEN STOPS AUTOMATICALLY.</div> </div> <div class="hex-panel"> <div class="hex-panel-title">TOOLS</div> <div class="hex-tools-top"> <button class="hex-btn hex-btn--square hex-btn--red" type="button" data-action="decrement" title="Decrement"></button> <button class="hex-btn hex-btn--square hex-btn--green2" type="button" data-action="increment" title="Increment"></button> </div> <button class="hex-btn hex-btn--wide hex-btn--reset" type="button" data-action="reset">Reset</button> </div> </aside> <!-- Custom number dialog --> <dialog class="hex-dialog" data-out="dialog"> <div class="hex-dialog-card"> <div class="hex-dialog-title" data-out="dialogTitle">Custom</div> <input class="hex-dialog-input hex-font-mono" data-out="dialogInput"> <div class="hex-dialog-hint" data-out="dialogHint"></div> <div class="hex-dialog-error" data-out="dialogError" aria-live="polite"></div> <div class="hex-dialog-actions"> <button class="hex-btn" type="button" data-action="dialogCancel">Cancel</button> <button class="hex-btn hex-btn--green" type="button" data-action="dialogApply">Apply</button> </div> </div> </dialog> <script type="module" src="/src/components/simulators/hex/hex-simulator.ts"></script> </section> </main> </body></html>

4
dist/index.html vendored
View File

@@ -1,5 +1,5 @@
<!DOCTYPE html><html lang="en" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v5.16.6"><title>Astro Basics</title><style>#background[data-astro-cid-mmc7otgs]{position:fixed;top:0;left:0;width:100%;height:100%;z-index:-1;filter:blur(100px)}#container[data-astro-cid-mmc7otgs]{font-family:Inter,Roboto,Helvetica Neue,Arial Nova,Nimbus Sans,Arial,sans-serif;height:100%}main[data-astro-cid-mmc7otgs]{height:100%;display:flex;justify-content:center}#hero[data-astro-cid-mmc7otgs]{display:flex;align-items:start;flex-direction:column;justify-content:center;padding:16px}h1[data-astro-cid-mmc7otgs]{font-size:22px;margin-top:.25em}#links[data-astro-cid-mmc7otgs]{display:flex;gap:16px}#links[data-astro-cid-mmc7otgs] a[data-astro-cid-mmc7otgs]{display:flex;align-items:center;padding:10px 12px;color:#111827;text-decoration:none;transition:color .2s}#links[data-astro-cid-mmc7otgs] a[data-astro-cid-mmc7otgs]:hover{color:#4e5056}#links[data-astro-cid-mmc7otgs] a[data-astro-cid-mmc7otgs] svg[data-astro-cid-mmc7otgs]{height:1em;margin-left:8px}#links[data-astro-cid-mmc7otgs] a[data-astro-cid-mmc7otgs].button{color:#fff;background:linear-gradient(83.21deg,#3245ff,#bc52ee);box-shadow:inset 0 0 0 1px #ffffff1f,inset 0 -2px #0000003d;border-radius:10px}#links[data-astro-cid-mmc7otgs] a[data-astro-cid-mmc7otgs].button:hover{color:#e6e6e6;box-shadow:none}pre[data-astro-cid-mmc7otgs]{font-family:ui-monospace,Cascadia Code,Source Code Pro,Menlo,Consolas,DejaVu Sans Mono,monospace;font-weight:400;background:linear-gradient(14deg,#d83333,#f041ff);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin:0}h2[data-astro-cid-mmc7otgs]{margin:0 0 1em;font-weight:400;color:#111827;font-size:20px}p[data-astro-cid-mmc7otgs]{color:#4b5563;font-size:16px;line-height:24px;letter-spacing:-.006em;margin:0}code[data-astro-cid-mmc7otgs]{display:inline-block;background:linear-gradient(66.77deg,#f3cddd,#f5cee7) padding-box,linear-gradient(155deg,#d83333,#f041ff 18%,#f5cee7 45%) border-box;border-radius:8px;border:1px solid transparent;padding:6px 8px}.box[data-astro-cid-mmc7otgs]{padding:16px;background:#fff;border-radius:16px;border:1px solid white}#news[data-astro-cid-mmc7otgs]{position:absolute;bottom:16px;right:16px;max-width:300px;text-decoration:none;transition:background .2s;backdrop-filter:blur(50px)}#news[data-astro-cid-mmc7otgs]:hover{background:#ffffff8c}@media screen and (max-height:368px){#news[data-astro-cid-mmc7otgs]{display:none}}@media screen and (max-width:768px){#container[data-astro-cid-mmc7otgs]{display:flex;flex-direction:column}#hero[data-astro-cid-mmc7otgs]{display:block;padding-top:10%}#links[data-astro-cid-mmc7otgs]{flex-wrap:wrap}#links[data-astro-cid-mmc7otgs] a[data-astro-cid-mmc7otgs].button{padding:14px 18px}#news[data-astro-cid-mmc7otgs]{right:16px;left:16px;bottom:2.5rem;max-width:100%}h1[data-astro-cid-mmc7otgs]{line-height:1.5}}html,body{margin:0;width:100%;height:100%} <!DOCTYPE html><html lang="en" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v5.18.0"><title>Astro Basics</title><style>#background[data-astro-cid-mmc7otgs]{position:fixed;top:0;left:0;width:100%;height:100%;z-index:-1;filter:blur(100px)}#container[data-astro-cid-mmc7otgs]{font-family:Inter,Roboto,Helvetica Neue,Arial Nova,Nimbus Sans,Arial,sans-serif;height:100%}main[data-astro-cid-mmc7otgs]{height:100%;display:flex;justify-content:center}#hero[data-astro-cid-mmc7otgs]{display:flex;align-items:start;flex-direction:column;justify-content:center;padding:16px}h1[data-astro-cid-mmc7otgs]{font-size:22px;margin-top:.25em}#links[data-astro-cid-mmc7otgs]{display:flex;gap:16px}#links[data-astro-cid-mmc7otgs] a[data-astro-cid-mmc7otgs]{display:flex;align-items:center;padding:10px 12px;color:#111827;text-decoration:none;transition:color .2s}#links[data-astro-cid-mmc7otgs] a[data-astro-cid-mmc7otgs]:hover{color:#4e5056}#links[data-astro-cid-mmc7otgs] a[data-astro-cid-mmc7otgs] svg[data-astro-cid-mmc7otgs]{height:1em;margin-left:8px}#links[data-astro-cid-mmc7otgs] a[data-astro-cid-mmc7otgs].button{color:#fff;background:linear-gradient(83.21deg,#3245ff,#bc52ee);box-shadow:inset 0 0 0 1px #ffffff1f,inset 0 -2px #0000003d;border-radius:10px}#links[data-astro-cid-mmc7otgs] a[data-astro-cid-mmc7otgs].button:hover{color:#e6e6e6;box-shadow:none}pre[data-astro-cid-mmc7otgs]{font-family:ui-monospace,Cascadia Code,Source Code Pro,Menlo,Consolas,DejaVu Sans Mono,monospace;font-weight:400;background:linear-gradient(14deg,#d83333,#f041ff);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;margin:0}h2[data-astro-cid-mmc7otgs]{margin:0 0 1em;font-weight:400;color:#111827;font-size:20px}p[data-astro-cid-mmc7otgs]{color:#4b5563;font-size:16px;line-height:24px;letter-spacing:-.006em;margin:0}code[data-astro-cid-mmc7otgs]{display:inline-block;background:linear-gradient(66.77deg,#f3cddd,#f5cee7) padding-box,linear-gradient(155deg,#d83333,#f041ff 18%,#f5cee7 45%) border-box;border-radius:8px;border:1px solid transparent;padding:6px 8px}.box[data-astro-cid-mmc7otgs]{padding:16px;background:#fff;border-radius:16px;border:1px solid white}#news[data-astro-cid-mmc7otgs]{position:absolute;bottom:16px;right:16px;max-width:300px;text-decoration:none;transition:background .2s;backdrop-filter:blur(50px)}#news[data-astro-cid-mmc7otgs]:hover{background:#ffffff8c}@media screen and (max-height:368px){#news[data-astro-cid-mmc7otgs]{display:none}}@media screen and (max-width:768px){#container[data-astro-cid-mmc7otgs]{display:flex;flex-direction:column}#hero[data-astro-cid-mmc7otgs]{display:block;padding-top:10%}#links[data-astro-cid-mmc7otgs]{flex-wrap:wrap}#links[data-astro-cid-mmc7otgs] a[data-astro-cid-mmc7otgs].button{padding:14px 18px}#news[data-astro-cid-mmc7otgs]{right:16px;left:16px;bottom:2.5rem;max-width:100%}h1[data-astro-cid-mmc7otgs]{line-height:1.5}}html,body{margin:0;width:100%;height:100%}
</style></head> <body data-astro-cid-sckkx6r4> <div id="container" data-astro-cid-mmc7otgs> <img id="background" src="/_astro/background.Mahwsfbs.svg" alt="" fetchpriority="high" data-astro-cid-mmc7otgs> <main data-astro-cid-mmc7otgs> <section id="hero" data-astro-cid-mmc7otgs> <a href="https://astro.build" data-astro-cid-mmc7otgs><img src="/_astro/astro.CXuftnGC.svg" width="115" height="48" alt="Astro Homepage" data-astro-cid-mmc7otgs></a> <h1 data-astro-cid-mmc7otgs> </style></head> <body data-astro-cid-sckkx6r4> <div id="container" data-astro-cid-mmc7otgs> <img id="background" src="/_astro/background.BPKAcmfN.svg" alt="" fetchpriority="high" data-astro-cid-mmc7otgs> <main data-astro-cid-mmc7otgs> <section id="hero" data-astro-cid-mmc7otgs> <a href="https://astro.build" data-astro-cid-mmc7otgs><img src="/_astro/astro.Dm8K3lV8.svg" width="115" height="48" alt="Astro Homepage" data-astro-cid-mmc7otgs></a> <h1 data-astro-cid-mmc7otgs>
To get started, open the <code data-astro-cid-mmc7otgs><pre data-astro-cid-mmc7otgs>src/pages</pre></code> directory in your project. To get started, open the <code data-astro-cid-mmc7otgs><pre data-astro-cid-mmc7otgs>src/pages</pre></code> directory in your project.
</h1> <section id="links" data-astro-cid-mmc7otgs> <a class="button" href="https://docs.astro.build" data-astro-cid-mmc7otgs>Read our docs</a> <a href="https://astro.build/chat" data-astro-cid-mmc7otgs>Join our Discord <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36" data-astro-cid-mmc7otgs><path fill="currentColor" d="M107.7 8.07A105.15 105.15 0 0 0 81.47 0a72.06 72.06 0 0 0-3.36 6.83 97.68 97.68 0 0 0-29.11 0A72.37 72.37 0 0 0 45.64 0a105.89 105.89 0 0 0-26.25 8.09C2.79 32.65-1.71 56.6.54 80.21a105.73 105.73 0 0 0 32.17 16.15 77.7 77.7 0 0 0 6.89-11.11 68.42 68.42 0 0 1-10.85-5.18c.91-.66 1.8-1.34 2.66-2a75.57 75.57 0 0 0 64.32 0c.87.71 1.76 1.39 2.66 2a68.68 68.68 0 0 1-10.87 5.19 77 77 0 0 0 6.89 11.1 105.25 105.25 0 0 0 32.19-16.14c2.64-27.38-4.51-51.11-18.9-72.15ZM42.45 65.69C36.18 65.69 31 60 31 53s5-12.74 11.43-12.74S54 46 53.89 53s-5.05 12.69-11.44 12.69Zm42.24 0C78.41 65.69 73.25 60 73.25 53s5-12.74 11.44-12.74S96.23 46 96.12 53s-5.04 12.69-11.43 12.69Z" data-astro-cid-mmc7otgs></path></svg> </a> </section> </section> </main> <a href="https://astro.build/blog/astro-5/" id="news" class="box" data-astro-cid-mmc7otgs> <svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg" data-astro-cid-mmc7otgs><path d="M24.667 12c1.333 1.414 2 3.192 2 5.334 0 4.62-4.934 5.7-7.334 12C18.444 28.567 18 27.456 18 26c0-4.642 6.667-7.053 6.667-14Zm-5.334-5.333c1.6 1.65 2.4 3.43 2.4 5.333 0 6.602-8.06 7.59-6.4 17.334C13.111 27.787 12 25.564 12 22.666c0-4.434 7.333-8 7.333-16Zm-6-5.333C15.111 3.555 16 5.556 16 7.333c0 8.333-11.333 10.962-5.333 22-3.488-.774-6-4-6-8 0-8.667 8.666-10 8.666-20Z" fill="#111827" data-astro-cid-mmc7otgs></path></svg> <h2 data-astro-cid-mmc7otgs>What's New in Astro 5.0?</h2> <p data-astro-cid-mmc7otgs> </h1> <section id="links" data-astro-cid-mmc7otgs> <a class="button" href="https://docs.astro.build" data-astro-cid-mmc7otgs>Read our docs</a> <a href="https://astro.build/chat" data-astro-cid-mmc7otgs>Join our Discord <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36" data-astro-cid-mmc7otgs><path fill="currentColor" d="M107.7 8.07A105.15 105.15 0 0 0 81.47 0a72.06 72.06 0 0 0-3.36 6.83 97.68 97.68 0 0 0-29.11 0A72.37 72.37 0 0 0 45.64 0a105.89 105.89 0 0 0-26.25 8.09C2.79 32.65-1.71 56.6.54 80.21a105.73 105.73 0 0 0 32.17 16.15 77.7 77.7 0 0 0 6.89-11.11 68.42 68.42 0 0 1-10.85-5.18c.91-.66 1.8-1.34 2.66-2a75.57 75.57 0 0 0 64.32 0c.87.71 1.76 1.39 2.66 2a68.68 68.68 0 0 1-10.87 5.19 77 77 0 0 0 6.89 11.1 105.25 105.25 0 0 0 32.19-16.14c2.64-27.38-4.51-51.11-18.9-72.15ZM42.45 65.69C36.18 65.69 31 60 31 53s5-12.74 11.43-12.74S54 46 53.89 53s-5.05 12.69-11.44 12.69Zm42.24 0C78.41 65.69 73.25 60 73.25 53s5-12.74 11.44-12.74S96.23 46 96.12 53s-5.04 12.69-11.43 12.69Z" data-astro-cid-mmc7otgs></path></svg> </a> </section> </section> </main> <a href="https://astro.build/blog/astro-5/" id="news" class="box" data-astro-cid-mmc7otgs> <svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg" data-astro-cid-mmc7otgs><path d="M24.667 12c1.333 1.414 2 3.192 2 5.334 0 4.62-4.934 5.7-7.334 12C18.444 28.567 18 27.456 18 26c0-4.642 6.667-7.053 6.667-14Zm-5.334-5.333c1.6 1.65 2.4 3.43 2.4 5.333 0 6.602-8.06 7.59-6.4 17.334C13.111 27.787 12 25.564 12 22.666c0-4.434 7.333-8 7.333-16Zm-6-5.333C15.111 3.555 16 5.556 16 7.333c0 8.333-11.333 10.962-5.333 22-3.488-.774-6-4-6-8 0-8.667 8.666-10 8.666-20Z" fill="#111827" data-astro-cid-mmc7otgs></path></svg> <h2 data-astro-cid-mmc7otgs>What's New in Astro 5.0?</h2> <p data-astro-cid-mmc7otgs>
From content layers to server islands, click to learn more about the new features and From content layers to server islands, click to learn more about the new features and

115
dist/js/binary/unsigned-binary.js vendored Normal file
View File

@@ -0,0 +1,115 @@
// Browser-only script. Safe because it's loaded via <script> (not server-imported).
const BIT_COUNT = 8; // unsigned page = 8 bits
const bitValues = [128, 64, 32, 16, 8, 4, 2, 1];
const elDenary = document.getElementById("denaryNumber");
const elBinary = document.getElementById("binaryNumber");
const elSwitches = document.getElementById("bitSwitches");
const btnCustomDenary = document.getElementById("btnCustomDenary");
const btnCustomBinary = document.getElementById("btnCustomBinary");
const btnReset = document.getElementById("btnReset");
let bits = new Array(BIT_COUNT).fill(false);
function renderSwitches() {
elSwitches.innerHTML = "";
bitValues.forEach((value, index) => {
const id = `bit-${value}`;
const wrapper = document.createElement("div");
wrapper.className = "switch-col";
const labelTop = document.createElement("div");
labelTop.className = "bit-label";
labelTop.textContent = value;
const label = document.createElement("label");
label.className = "rocker";
label.setAttribute("for", id);
const input = document.createElement("input");
input.type = "checkbox";
input.id = id;
input.checked = bits[index];
input.addEventListener("change", () => {
bits[index] = input.checked;
updateNumbers();
});
const span = document.createElement("span");
span.className = "rocker-body";
span.setAttribute("aria-hidden", "true");
label.appendChild(input);
label.appendChild(span);
wrapper.appendChild(labelTop);
wrapper.appendChild(label);
elSwitches.appendChild(wrapper);
});
}
function updateNumbers() {
const binary = bits.map(b => (b ? "1" : "0")).join("");
const denary = bits.reduce((acc, b, i) => acc + (b ? bitValues[i] : 0), 0);
elBinary.textContent = binary;
elDenary.textContent = denary.toString();
}
function resetAll() {
bits = new Array(BIT_COUNT).fill(false);
renderSwitches();
updateNumbers();
}
function requestCustomDenary() {
let input = prompt(`Enter a denary number (0 to 255):`);
if (input === null) return;
const n = Number.parseInt(input, 10);
if (Number.isNaN(n) || n < 0 || n > 255) {
alert("Invalid input. Enter a number from 0 to 255.");
return;
}
let remaining = n;
bits = bitValues.map(v => {
if (remaining >= v) {
remaining -= v;
return true;
}
return false;
});
renderSwitches();
updateNumbers();
}
function requestCustomBinary() {
let input = prompt(`Enter an ${BIT_COUNT}-bit binary number (e.g. 01010101):`);
if (input === null) return;
input = input.trim();
const re = new RegExp(`^[01]{${BIT_COUNT}}$`);
if (!re.test(input)) {
alert(`Invalid input. Enter exactly ${BIT_COUNT} digits using only 0 or 1.`);
return;
}
bits = input.split("").map(c => c === "1");
renderSwitches();
updateNumbers();
}
btnCustomDenary?.addEventListener("click", requestCustomDenary);
btnCustomBinary?.addEventListener("click", requestCustomBinary);
btnReset?.addEventListener("click", resetAll);
renderSwitches();
updateNumbers();

72
dist/js/tools/unsigned-binary.js vendored Normal file
View File

@@ -0,0 +1,72 @@
// public/js/tools/unsigned-binary.js
// Lightweight: no frameworks. Works on weak devices.
const BIT_COUNT = 8;
const MAX_DENARY = 255;
let bits = new Array(BIT_COUNT).fill(false);
function bitsToBinaryString(){
return bits.map(b => (b ? "1" : "0")).join("");
}
function bitsToDenary(){
// MSB on the left: 128..1
const weights = [128,64,32,16,8,4,2,1];
return bits.reduce((acc, b, i) => acc + (b ? weights[i] : 0), 0);
}
function render(){
const grid = document.getElementById("bitGrid");
grid.innerHTML = "";
const weights = [128,64,32,16,8,4,2,1];
bits.forEach((on, i) => {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "btn";
btn.style.width = "100%";
btn.style.justifyContent = "space-between";
btn.setAttribute("aria-pressed", on ? "true" : "false");
btn.innerHTML = `<span>${weights[i]}</span><b>${on ? "1" : "0"}</b>`;
btn.addEventListener("click", () => {
bits[i] = !bits[i];
update();
});
grid.appendChild(btn);
});
}
function update(){
document.getElementById("binaryNumber").innerText = bitsToBinaryString();
document.getElementById("denaryNumber").innerText = bitsToDenary();
render();
}
function requestBinary(){
let v;
do{
v = prompt("Enter an 8-bit binary value (8 digits, only 0 or 1):", bitsToBinaryString());
if(v === null) return;
v = v.trim();
}while(!/^[01]{8}$/.test(v));
bits = v.split("").map(ch => ch === "1");
update();
}
function requestDenary(){
let v;
do{
v = prompt("Enter a denary value (0 to 255):", String(bitsToDenary()));
if(v === null) return;
v = Number(v);
}while(!Number.isInteger(v) || v < 0 || v > MAX_DENARY);
// set bits from MSB to LSB
const weights = [128,64,32,16,8,4,2,1];
bits = weights.map(w => {
if(v >= w){ v -= w; return true; }
return false;
});
update();
}
function reset(){
bits = new Array(BIT_COUNT).fill(false);
update();
}
document.addEventListener("DOMContentLoaded", () => {
document.getElementById("btnCustomBinary")?.addEventListener("click", requestBinary);
document.getElementById("btnCustomDenary")?.addEventListener("click", requestDenary);
document.getElementById("btnReset")?.addEventListener("click", reset);
update();
});

96
dist/styles.css vendored Normal file
View File

@@ -0,0 +1,96 @@
/* src/styles/md3-tokens.css */
/* MD3-inspired tokens tuned for education: high readability, clear contrast, calm surfaces */
:root{
/* Typography */
--font-sans: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
/* Spacing + shape */
--radius-1: 10px;
--radius-2: 16px;
--radius-3: 22px;
--shadow-1: 0 1px 2px rgba(0,0,0,.08), 0 2px 8px rgba(0,0,0,.06);
/* Color roles (keep simple) */
--md-surface: #ffffff;
--md-surface-2: #f6f7fb;
--md-on-surface: #111318;
--md-primary: #2f6fed; /* calm blue */
--md-on-primary: #ffffff;
--md-secondary: #5a5f72; /* muted */
--md-on-secondary: #ffffff;
--md-tertiary: #0f766e; /* teal for "practical" tools */
--md-on-tertiary: #ffffff;
--md-outline: #d7dbe7;
--md-success: #1a7f37;
--md-warning: #b54708;
--md-danger: #b42318;
/* Focus ring for accessibility */
--md-focus: 0 0 0 3px rgba(47,111,237,.28);
}
@media (prefers-color-scheme: dark){
:root{
--md-surface: #0b0e14;
--md-surface-2: #121725;
--md-on-surface: #e8eaf2;
--md-primary: #9bb6ff;
--md-on-primary: #0b0e14;
--md-secondary: #b8bccd;
--md-on-secondary: #0b0e14;
--md-tertiary: #4fd1c5;
--md-on-tertiary: #0b0e14;
--md-outline: #2b3244;
--md-focus: 0 0 0 3px rgba(155,182,255,.25);
}
}
/* src/styles/base.css */
@import "./md3-tokens.css";
html, body{ height:100%; }
body{
margin:0;
font-family: var(--font-sans);
background: var(--md-surface-2);
color: var(--md-on-surface);
}
a{ color: var(--md-primary); text-decoration: none; }
a:hover{ text-decoration: underline; }
.container{
max-width: 1100px;
margin: 0 auto;
padding: 16px;
}
.card{
background: var(--md-surface);
border: 1px solid var(--md-outline);
border-radius: var(--radius-2);
box-shadow: var(--shadow-1);
padding: 16px;
}
.btn{
display:inline-flex;
gap:8px;
align-items:center;
justify-content:center;
border-radius: 999px;
border: 1px solid var(--md-outline);
background: var(--md-surface);
color: var(--md-on-surface);
padding: 10px 14px;
font-weight: 600;
cursor: pointer;
}
.btn:hover{ filter: brightness(0.98); }
.btn:focus{ outline:none; box-shadow: var(--md-focus); }
.btn-primary{
background: var(--md-primary);
color: var(--md-on-primary);
border-color: transparent;
}
.badge{
display:inline-block;
padding: 2px 10px;
border-radius: 999px;
font-size: 12px;
border: 1px solid var(--md-outline);
background: var(--md-surface-2);
}
code, pre{ font-family: var(--font-mono); }

1035
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "Computing:Box", "name": "computing-box",
"type": "module", "type": "module",
"version": "2.0.0 Alpha", "version": "0.0.1",
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev",
"build": "astro build", "build": "astro build",
@@ -9,6 +9,6 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"astro": "^5.16.6" "astro": "^5.18.0"
} }
} }

9
public/favicon.svg Normal file
View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 749 B

View File

@@ -0,0 +1,115 @@
// Browser-only script. Safe because it's loaded via <script> (not server-imported).
const BIT_COUNT = 8; // unsigned page = 8 bits
const bitValues = [128, 64, 32, 16, 8, 4, 2, 1];
const elDenary = document.getElementById("denaryNumber");
const elBinary = document.getElementById("binaryNumber");
const elSwitches = document.getElementById("bitSwitches");
const btnCustomDenary = document.getElementById("btnCustomDenary");
const btnCustomBinary = document.getElementById("btnCustomBinary");
const btnReset = document.getElementById("btnReset");
let bits = new Array(BIT_COUNT).fill(false);
function renderSwitches() {
elSwitches.innerHTML = "";
bitValues.forEach((value, index) => {
const id = `bit-${value}`;
const wrapper = document.createElement("div");
wrapper.className = "switch-col";
const labelTop = document.createElement("div");
labelTop.className = "bit-label";
labelTop.textContent = value;
const label = document.createElement("label");
label.className = "rocker";
label.setAttribute("for", id);
const input = document.createElement("input");
input.type = "checkbox";
input.id = id;
input.checked = bits[index];
input.addEventListener("change", () => {
bits[index] = input.checked;
updateNumbers();
});
const span = document.createElement("span");
span.className = "rocker-body";
span.setAttribute("aria-hidden", "true");
label.appendChild(input);
label.appendChild(span);
wrapper.appendChild(labelTop);
wrapper.appendChild(label);
elSwitches.appendChild(wrapper);
});
}
function updateNumbers() {
const binary = bits.map(b => (b ? "1" : "0")).join("");
const denary = bits.reduce((acc, b, i) => acc + (b ? bitValues[i] : 0), 0);
elBinary.textContent = binary;
elDenary.textContent = denary.toString();
}
function resetAll() {
bits = new Array(BIT_COUNT).fill(false);
renderSwitches();
updateNumbers();
}
function requestCustomDenary() {
let input = prompt(`Enter a denary number (0 to 255):`);
if (input === null) return;
const n = Number.parseInt(input, 10);
if (Number.isNaN(n) || n < 0 || n > 255) {
alert("Invalid input. Enter a number from 0 to 255.");
return;
}
let remaining = n;
bits = bitValues.map(v => {
if (remaining >= v) {
remaining -= v;
return true;
}
return false;
});
renderSwitches();
updateNumbers();
}
function requestCustomBinary() {
let input = prompt(`Enter an ${BIT_COUNT}-bit binary number (e.g. 01010101):`);
if (input === null) return;
input = input.trim();
const re = new RegExp(`^[01]{${BIT_COUNT}}$`);
if (!re.test(input)) {
alert(`Invalid input. Enter exactly ${BIT_COUNT} digits using only 0 or 1.`);
return;
}
bits = input.split("").map(c => c === "1");
renderSwitches();
updateNumbers();
}
btnCustomDenary?.addEventListener("click", requestCustomDenary);
btnCustomBinary?.addEventListener("click", requestCustomBinary);
btnReset?.addEventListener("click", resetAll);
renderSwitches();
updateNumbers();

View File

@@ -0,0 +1,72 @@
// public/js/tools/unsigned-binary.js
// Lightweight: no frameworks. Works on weak devices.
const BIT_COUNT = 8;
const MAX_DENARY = 255;
let bits = new Array(BIT_COUNT).fill(false);
function bitsToBinaryString(){
return bits.map(b => (b ? "1" : "0")).join("");
}
function bitsToDenary(){
// MSB on the left: 128..1
const weights = [128,64,32,16,8,4,2,1];
return bits.reduce((acc, b, i) => acc + (b ? weights[i] : 0), 0);
}
function render(){
const grid = document.getElementById("bitGrid");
grid.innerHTML = "";
const weights = [128,64,32,16,8,4,2,1];
bits.forEach((on, i) => {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "btn";
btn.style.width = "100%";
btn.style.justifyContent = "space-between";
btn.setAttribute("aria-pressed", on ? "true" : "false");
btn.innerHTML = `<span>${weights[i]}</span><b>${on ? "1" : "0"}</b>`;
btn.addEventListener("click", () => {
bits[i] = !bits[i];
update();
});
grid.appendChild(btn);
});
}
function update(){
document.getElementById("binaryNumber").innerText = bitsToBinaryString();
document.getElementById("denaryNumber").innerText = bitsToDenary();
render();
}
function requestBinary(){
let v;
do{
v = prompt("Enter an 8-bit binary value (8 digits, only 0 or 1):", bitsToBinaryString());
if(v === null) return;
v = v.trim();
}while(!/^[01]{8}$/.test(v));
bits = v.split("").map(ch => ch === "1");
update();
}
function requestDenary(){
let v;
do{
v = prompt("Enter a denary value (0 to 255):", String(bitsToDenary()));
if(v === null) return;
v = Number(v);
}while(!Number.isInteger(v) || v < 0 || v > MAX_DENARY);
// set bits from MSB to LSB
const weights = [128,64,32,16,8,4,2,1];
bits = weights.map(w => {
if(v >= w){ v -= w; return true; }
return false;
});
update();
}
function reset(){
bits = new Array(BIT_COUNT).fill(false);
update();
}
document.addEventListener("DOMContentLoaded", () => {
document.getElementById("btnCustomBinary")?.addEventListener("click", requestBinary);
document.getElementById("btnCustomDenary")?.addEventListener("click", requestDenary);
document.getElementById("btnReset")?.addEventListener("click", reset);
update();
});

96
public/styles.css Normal file
View File

@@ -0,0 +1,96 @@
/* src/styles/md3-tokens.css */
/* MD3-inspired tokens tuned for education: high readability, clear contrast, calm surfaces */
:root{
/* Typography */
--font-sans: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
/* Spacing + shape */
--radius-1: 10px;
--radius-2: 16px;
--radius-3: 22px;
--shadow-1: 0 1px 2px rgba(0,0,0,.08), 0 2px 8px rgba(0,0,0,.06);
/* Color roles (keep simple) */
--md-surface: #ffffff;
--md-surface-2: #f6f7fb;
--md-on-surface: #111318;
--md-primary: #2f6fed; /* calm blue */
--md-on-primary: #ffffff;
--md-secondary: #5a5f72; /* muted */
--md-on-secondary: #ffffff;
--md-tertiary: #0f766e; /* teal for "practical" tools */
--md-on-tertiary: #ffffff;
--md-outline: #d7dbe7;
--md-success: #1a7f37;
--md-warning: #b54708;
--md-danger: #b42318;
/* Focus ring for accessibility */
--md-focus: 0 0 0 3px rgba(47,111,237,.28);
}
@media (prefers-color-scheme: dark){
:root{
--md-surface: #0b0e14;
--md-surface-2: #121725;
--md-on-surface: #e8eaf2;
--md-primary: #9bb6ff;
--md-on-primary: #0b0e14;
--md-secondary: #b8bccd;
--md-on-secondary: #0b0e14;
--md-tertiary: #4fd1c5;
--md-on-tertiary: #0b0e14;
--md-outline: #2b3244;
--md-focus: 0 0 0 3px rgba(155,182,255,.25);
}
}
/* src/styles/base.css */
@import "./md3-tokens.css";
html, body{ height:100%; }
body{
margin:0;
font-family: var(--font-sans);
background: var(--md-surface-2);
color: var(--md-on-surface);
}
a{ color: var(--md-primary); text-decoration: none; }
a:hover{ text-decoration: underline; }
.container{
max-width: 1100px;
margin: 0 auto;
padding: 16px;
}
.card{
background: var(--md-surface);
border: 1px solid var(--md-outline);
border-radius: var(--radius-2);
box-shadow: var(--shadow-1);
padding: 16px;
}
.btn{
display:inline-flex;
gap:8px;
align-items:center;
justify-content:center;
border-radius: 999px;
border: 1px solid var(--md-outline);
background: var(--md-surface);
color: var(--md-on-surface);
padding: 10px 14px;
font-weight: 600;
cursor: pointer;
}
.btn:hover{ filter: brightness(0.98); }
.btn:focus{ outline:none; box-shadow: var(--md-focus); }
.btn-primary{
background: var(--md-primary);
color: var(--md-on-primary);
border-color: transparent;
}
.badge{
display:inline-block;
padding: 2px 10px;
border-radius: 999px;
font-size: 12px;
border: 1px solid var(--md-outline);
background: var(--md-surface-2);
}
code, pre{ font-family: var(--font-mono); }

3
renovate.json Normal file
View File

@@ -0,0 +1,3 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" width="115" height="48"><path fill="#17191E" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="url(#a)" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="#17191E" d="M.02 30.31s4.02-1.95 8.05-1.95l3.04-9.4c.11-.45.44-.76.82-.76.37 0 .7.31.82.76l3.04 9.4c4.77 0 8.05 1.95 8.05 1.95L17 11.71c-.2-.56-.53-.91-.98-.91H7.83c-.44 0-.76.35-.97.9L.02 30.31Zm42.37-5.97c0 1.64-2.05 2.62-4.88 2.62-1.85 0-2.5-.45-2.5-1.41 0-1 .8-1.49 2.65-1.49 1.67 0 3.09.03 4.73.23v.05Zm.03-2.04a21.37 21.37 0 0 0-4.37-.36c-5.32 0-7.82 1.25-7.82 4.18 0 3.04 1.71 4.2 5.68 4.2 3.35 0 5.63-.84 6.46-2.92h.14c-.03.5-.05 1-.05 1.4 0 1.07.18 1.16 1.06 1.16h4.15a16.9 16.9 0 0 1-.36-4c0-1.67.06-2.93.06-4.62 0-3.45-2.07-5.64-8.56-5.64-2.8 0-5.9.48-8.26 1.19.22.93.54 2.83.7 4.06 2.04-.96 4.95-1.37 7.2-1.37 3.11 0 3.97.71 3.97 2.15v.57Zm11.37 3c-.56.07-1.33.07-2.12.07-.83 0-1.6-.03-2.12-.1l-.02.58c0 2.85 1.87 4.52 8.45 4.52 6.2 0 8.2-1.64 8.2-4.55 0-2.74-1.33-4.09-7.2-4.39-4.58-.2-4.99-.7-4.99-1.28 0-.66.59-1 3.65-1 3.18 0 4.03.43 4.03 1.35v.2a46.13 46.13 0 0 1 4.24.03l.02-.55c0-3.36-2.8-4.46-8.2-4.46-6.08 0-8.13 1.49-8.13 4.39 0 2.6 1.64 4.23 7.48 4.48 4.3.14 4.77.62 4.77 1.28 0 .7-.7 1.03-3.71 1.03-3.47 0-4.35-.48-4.35-1.47v-.13Zm19.82-12.05a17.5 17.5 0 0 1-6.24 3.48c.03.84.03 2.4.03 3.24l1.5.02c-.02 1.63-.04 3.6-.04 4.9 0 3.04 1.6 5.32 6.58 5.32 2.1 0 3.5-.23 5.23-.6a43.77 43.77 0 0 1-.46-4.13c-1.03.34-2.34.53-3.78.53-2 0-2.82-.55-2.82-2.13 0-1.37 0-2.65.03-3.84 2.57.02 5.13.07 6.64.11-.02-1.18.03-2.9.1-4.04-2.2.04-4.65.07-6.68.07l.07-2.93h-.16Zm13.46 6.04a767.33 767.33 0 0 1 .07-3.18H82.6c.07 1.96.07 3.98.07 6.92 0 2.95-.03 4.99-.07 6.93h5.18c-.09-1.37-.11-3.68-.11-5.65 0-3.1 1.26-4 4.12-4 1.33 0 2.28.16 3.1.46.03-1.16.26-3.43.4-4.43-.86-.25-1.81-.41-2.96-.41-2.46-.03-4.26.98-5.1 3.38l-.17-.02Zm22.55 3.65c0 2.5-1.8 3.66-4.64 3.66-2.81 0-4.61-1.1-4.61-3.66s1.82-3.52 4.61-3.52c2.82 0 4.64 1.03 4.64 3.52Zm4.71-.11c0-4.96-3.87-7.18-9.35-7.18-5.5 0-9.23 2.22-9.23 7.18 0 4.94 3.49 7.59 9.21 7.59 5.77 0 9.37-2.65 9.37-7.6Z"/><defs><linearGradient id="a" x1="6.33" x2="19.43" y1="40.8" y2="34.6" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient></defs></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1,210 +0,0 @@
---
import astroLogo from '../assets/astro.svg';
import background from '../assets/background.svg';
---
<div id="container">
<img id="background" src={background.src} alt="" fetchpriority="high" />
<main>
<section id="hero">
<a href="https://astro.build"
><img src={astroLogo.src} width="115" height="48" alt="Astro Homepage" /></a
>
<h1>
To get started, open the <code><pre>src/pages</pre></code> directory in your project.
</h1>
<section id="links">
<a class="button" href="https://docs.astro.build">Read our docs</a>
<a href="https://astro.build/chat"
>Join our Discord <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"
><path
fill="currentColor"
d="M107.7 8.07A105.15 105.15 0 0 0 81.47 0a72.06 72.06 0 0 0-3.36 6.83 97.68 97.68 0 0 0-29.11 0A72.37 72.37 0 0 0 45.64 0a105.89 105.89 0 0 0-26.25 8.09C2.79 32.65-1.71 56.6.54 80.21a105.73 105.73 0 0 0 32.17 16.15 77.7 77.7 0 0 0 6.89-11.11 68.42 68.42 0 0 1-10.85-5.18c.91-.66 1.8-1.34 2.66-2a75.57 75.57 0 0 0 64.32 0c.87.71 1.76 1.39 2.66 2a68.68 68.68 0 0 1-10.87 5.19 77 77 0 0 0 6.89 11.1 105.25 105.25 0 0 0 32.19-16.14c2.64-27.38-4.51-51.11-18.9-72.15ZM42.45 65.69C36.18 65.69 31 60 31 53s5-12.74 11.43-12.74S54 46 53.89 53s-5.05 12.69-11.44 12.69Zm42.24 0C78.41 65.69 73.25 60 73.25 53s5-12.74 11.44-12.74S96.23 46 96.12 53s-5.04 12.69-11.43 12.69Z"
></path></svg
>
</a>
</section>
</section>
</main>
<a href="https://astro.build/blog/astro-5/" id="news" class="box">
<svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg"
><path
d="M24.667 12c1.333 1.414 2 3.192 2 5.334 0 4.62-4.934 5.7-7.334 12C18.444 28.567 18 27.456 18 26c0-4.642 6.667-7.053 6.667-14Zm-5.334-5.333c1.6 1.65 2.4 3.43 2.4 5.333 0 6.602-8.06 7.59-6.4 17.334C13.111 27.787 12 25.564 12 22.666c0-4.434 7.333-8 7.333-16Zm-6-5.333C15.111 3.555 16 5.556 16 7.333c0 8.333-11.333 10.962-5.333 22-3.488-.774-6-4-6-8 0-8.667 8.666-10 8.666-20Z"
fill="#111827"></path></svg
>
<h2>What's New in Astro 5.0?</h2>
<p>
From content layers to server islands, click to learn more about the new features and
improvements in Astro 5.0
</p>
</a>
</div>
<style>
#background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
filter: blur(100px);
}
#container {
font-family: Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;
height: 100%;
}
main {
height: 100%;
display: flex;
justify-content: center;
}
#hero {
display: flex;
align-items: start;
flex-direction: column;
justify-content: center;
padding: 16px;
}
h1 {
font-size: 22px;
margin-top: 0.25em;
}
#links {
display: flex;
gap: 16px;
}
#links a {
display: flex;
align-items: center;
padding: 10px 12px;
color: #111827;
text-decoration: none;
transition: color 0.2s;
}
#links a:hover {
color: rgb(78, 80, 86);
}
#links a svg {
height: 1em;
margin-left: 8px;
}
#links a.button {
color: white;
background: linear-gradient(83.21deg, #3245ff 0%, #bc52ee 100%);
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.12),
inset 0 -2px 0 rgba(0, 0, 0, 0.24);
border-radius: 10px;
}
#links a.button:hover {
color: rgb(230, 230, 230);
box-shadow: none;
}
pre {
font-family:
ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono',
monospace;
font-weight: normal;
background: linear-gradient(14deg, #d83333 0%, #f041ff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0;
}
h2 {
margin: 0 0 1em;
font-weight: normal;
color: #111827;
font-size: 20px;
}
p {
color: #4b5563;
font-size: 16px;
line-height: 24px;
letter-spacing: -0.006em;
margin: 0;
}
code {
display: inline-block;
background:
linear-gradient(66.77deg, #f3cddd 0%, #f5cee7 100%) padding-box,
linear-gradient(155deg, #d83333 0%, #f041ff 18%, #f5cee7 45%) border-box;
border-radius: 8px;
border: 1px solid transparent;
padding: 6px 8px;
}
.box {
padding: 16px;
background: rgba(255, 255, 255, 1);
border-radius: 16px;
border: 1px solid white;
}
#news {
position: absolute;
bottom: 16px;
right: 16px;
max-width: 300px;
text-decoration: none;
transition: background 0.2s;
backdrop-filter: blur(50px);
}
#news:hover {
background: rgba(255, 255, 255, 0.55);
}
@media screen and (max-height: 368px) {
#news {
display: none;
}
}
@media screen and (max-width: 768px) {
#container {
display: flex;
flex-direction: column;
}
#hero {
display: block;
padding-top: 10%;
}
#links {
flex-wrap: wrap;
}
#links a.button {
padding: 14px 18px;
}
#news {
right: 16px;
left: 16px;
bottom: 2.5rem;
max-width: 100%;
}
h1 {
line-height: 1.5;
}
}
</style>

View File

@@ -1,104 +0,0 @@
---
import "./hex/hex-simulator.css";
---
<section class="hex-sim" data-hex-sim>
<div class="hex-main">
<div class="hex-readout">
<div class="hex-label">DENARY</div>
<div class="hex-number" data-out="denary">0</div>
<div class="hex-label hex-mt">HEXADECIMAL</div>
<div class="hex-number hex-number--small" data-out="hex">00</div>
<div class="hex-label hex-mt">BINARY</div>
<div class="hex-number hex-number--tiny" data-out="bin">0000 0000</div>
</div>
<div class="hex-divider"></div>
<div class="hex-digits" data-out="digitsRow"></div>
</div>
<!-- Toolbox button -->
<button class="hex-toolbox-btn" type="button" data-action="toggleToolbox" aria-controls="hex-toolbox" aria-expanded="true">
<span class="hex-toolbox-icon" aria-hidden="true">
<!-- toolbox icon -->
<svg viewBox="0 0 24 24" width="18" height="18" fill="none">
<path d="M9 7V6a3 3 0 0 1 6 0v1" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<path d="M4 9h16l-1.3 10.4A2 2 0 0 1 16.7 21H7.3a2 2 0 0 1-1.98-1.6L4 9Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
<path d="M10 13h4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</span>
TOOLBOX
</button>
<!-- Toolbox panel -->
<aside class="hex-toolbox is-open" id="hex-toolbox" data-out="toolbox">
<div class="hex-panel">
<div class="hex-panel-title">SETTINGS</div>
<div class="hex-setting-title">HEX DIGIT WIDTH</div>
<div class="hex-width">
<button class="hex-btn hex-btn--square" type="button" data-action="digitsMinus"></button>
<div class="hex-width-readout">
<div class="hex-width-label">DIGITS</div>
<div class="hex-width-number" data-out="digitsCount">2</div>
</div>
<button class="hex-btn hex-btn--square" type="button" data-action="digitsPlus">+</button>
</div>
<div class="hex-hint" data-out="bitsHint">= 8 bits</div>
</div>
<div class="hex-panel">
<div class="hex-panel-title">CUSTOM NUMBER</div>
<div class="hex-grid-2">
<button class="hex-btn hex-btn--green" type="button" data-action="customHex">Custom Hexadecimal</button>
<button class="hex-btn hex-btn--green" type="button" data-action="customDenary">Custom Denary</button>
</div>
<!-- Custom Binary + Random on SAME row, same size -->
<div class="hex-grid-2 hex-mt-sm">
<button class="hex-btn hex-btn--green" type="button" data-action="customBinary">Custom Binary</button>
<button class="hex-btn hex-btn--wide hex-btn--random" type="button" data-action="random" data-random>Random</button>
</div>
<div class="hex-tiny-note">RANDOM RUNS BRIEFLY THEN STOPS AUTOMATICALLY.</div>
</div>
<div class="hex-panel">
<div class="hex-panel-title">TOOLS</div>
<div class="hex-tools-top">
<button class="hex-btn hex-btn--square hex-btn--red" type="button" data-action="decrement" title="Decrement">▼</button>
<button class="hex-btn hex-btn--square hex-btn--green2" type="button" data-action="increment" title="Increment">▲</button>
</div>
<button class="hex-btn hex-btn--wide hex-btn--reset" type="button" data-action="reset">Reset</button>
</div>
</aside>
<!-- Custom number dialog -->
<dialog class="hex-dialog" data-out="dialog">
<div class="hex-dialog-card">
<div class="hex-dialog-title" data-out="dialogTitle">Custom</div>
<input class="hex-dialog-input hex-font-mono" data-out="dialogInput" />
<div class="hex-dialog-hint" data-out="dialogHint"></div>
<div class="hex-dialog-error" data-out="dialogError" aria-live="polite"></div>
<div class="hex-dialog-actions">
<button class="hex-btn" type="button" data-action="dialogCancel">Cancel</button>
<button class="hex-btn hex-btn--green" type="button" data-action="dialogApply">Apply</button>
</div>
</div>
</dialog>
<script type="module" src="/src/components/simulators/hex/hex-simulator.ts"></script>
</section>

View File

@@ -1,346 +0,0 @@
/* ================= Fonts to match Binary ================= */
/* Adjust paths to wherever you store fonts (commonly /public/fonts/...) */
@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;
}
@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;
}
.hex-sim {
min-height: 100vh;
background: #14151c;
color: #e7e8ee;
padding: 28px;
}
.hex-font-number { font-family: "DSEG7Classic", ui-monospace, monospace; }
.hex-font-mono { font-family: "SevenSegment", ui-monospace, monospace; }
.hex-main { max-width: 1200px; margin: 0 auto; width: 100%; padding-top: 40px; }
.hex-readout { text-align: center; }
.hex-label {
font-family: "SevenSegment", ui-sans-serif, system-ui;
font-size: 12px;
letter-spacing: 2px;
opacity: 0.7;
}
.hex-mt { margin-top: 12px; }
.hex-number {
font-family: "DSEG7Classic", ui-monospace, monospace;
font-size: 76px;
line-height: 1;
font-weight: 400;
color: #46ff8a;
text-shadow: 0 0 18px rgba(70,255,138,0.18);
}
.hex-number--small { font-size: 64px; }
.hex-number--tiny { font-size: 54px; letter-spacing: 6px; }
.hex-divider {
margin: 26px auto 18px;
height: 1px;
width: min(760px, 90%);
background: rgba(255,255,255,0.10);
}
/* ================= Main digit columns ================= */
.hex-digits {
margin-top: 18px;
display: flex;
justify-content: center;
gap: 18px;
flex-wrap: wrap;
}
.hex-digit-col {
width: 160px;
border-radius: 18px;
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.10);
padding: 12px;
display: grid;
gap: 10px;
justify-items: center;
}
.hex-digit-controls {
width: 100%;
display: flex;
justify-content: center;
gap: 10px;
}
.hex-digit-char {
font-size: 64px;
line-height: 1;
color: #46ff8a;
text-shadow: 0 0 18px rgba(70,255,138,0.18);
}
.hex-digit-place {
font-family: "SevenSegment", ui-monospace, monospace;
opacity: 0.65;
font-size: 14px;
letter-spacing: 1px;
}
/* ================= Bulbs (brightness changes) ================= */
.hex-bulbs {
width: 100%;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
align-items: end;
}
.hex-bulb {
display: grid;
justify-items: center;
gap: 6px;
opacity: 0.35;
filter: grayscale(30%);
transition: opacity 160ms ease, filter 160ms ease;
}
.hex-bulb .hex-bulb-cap {
width: 18px;
height: 18px;
border-radius: 999px;
background: rgba(255,255,255,0.22);
border: 1px solid rgba(255,255,255,0.14);
}
.hex-bulb .hex-bulb-glow {
width: 18px;
height: 10px;
border-radius: 999px;
background: rgba(70,255,138,0.0);
box-shadow: 0 0 0 rgba(70,255,138,0.0);
transition: background 160ms ease, box-shadow 160ms ease;
}
.hex-bulb .hex-bulb-label {
font-family: "SevenSegment", ui-monospace, monospace;
font-size: 12px;
opacity: 0.8;
}
.hex-bulb.is-on {
opacity: 1;
filter: none;
}
.hex-bulb.is-on .hex-bulb-cap {
background: rgba(255,255,255,0.35);
}
.hex-bulb.is-on .hex-bulb-glow {
background: rgba(70,255,138,0.25);
box-shadow: 0 0 18px rgba(70,255,138,0.35);
}
/* ================= Buttons (toolbox style reused everywhere) ================= */
.hex-btn {
padding: 10px 12px;
border-radius: 14px;
border: 1px solid rgba(255,255,255,0.14);
background: rgba(255,255,255,0.06);
color: #e7e8ee;
font-weight: 800;
cursor: pointer;
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
}
.hex-btn:hover { background: rgba(255,255,255,0.10); }
.hex-btn--square {
width: 48px;
height: 48px;
padding: 0;
display: grid;
place-items: center;
font-size: 18px;
}
.hex-btn--wide { width: 100%; }
.hex-btn--green {
background: rgba(46, 200, 120, 0.18);
border-color: rgba(46,200,120,0.35);
}
.hex-btn--green:hover { background: rgba(46, 200, 120, 0.26); }
.hex-btn--green2 {
background: rgba(46, 200, 120, 0.18);
border-color: rgba(46,200,120,0.35);
}
.hex-btn--red {
background: rgba(220, 60, 70, 0.18);
border-color: rgba(220,60,70,0.35);
}
/* Random = green pulse while running */
.hex-btn--random.is-running {
border-color: rgba(80, 255, 160, 0.55);
background: rgba(46, 200, 120, 0.22);
box-shadow: 0 0 18px rgba(80, 255, 160, 0.35);
animation: hexPulseGreen 900ms ease-in-out infinite;
}
@keyframes hexPulseGreen {
0%, 100% { box-shadow: 0 0 14px rgba(80, 255, 160, 0.25); }
50% { box-shadow: 0 0 26px rgba(80, 255, 160, 0.45); }
}
/* Reset = red background + pulse on hover */
.hex-btn--reset:hover {
background: rgba(220, 60, 70, 0.28);
border-color: rgba(255, 80, 90, 0.55);
animation: hexPulseRed 900ms ease-in-out infinite;
}
@keyframes hexPulseRed {
0%, 100% { box-shadow: 0 0 12px rgba(255, 80, 90, 0.20); }
50% { box-shadow: 0 0 22px rgba(255, 80, 90, 0.38); }
}
/* ================= Toolbox button + panel (slide) ================= */
.hex-toolbox-btn {
position: fixed;
top: 88px;
right: 28px;
z-index: 30;
display: inline-flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
border-radius: 14px;
border: 1px solid rgba(255,255,255,0.14);
background: rgba(255,255,255,0.06);
color: #e7e8ee;
font-weight: 800;
letter-spacing: 1px;
cursor: pointer;
}
.hex-toolbox-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
color: #ff4fa6;
filter: drop-shadow(0 0 10px rgba(255,79,166,0.35));
}
.hex-toolbox {
position: fixed;
top: 140px;
right: 28px;
width: 340px;
display: grid;
gap: 14px;
z-index: 25;
transform: translateX(0);
opacity: 1;
transition: transform 220ms ease, opacity 220ms ease;
}
.hex-toolbox:not(.is-open) {
transform: translateX(380px);
opacity: 0;
pointer-events: none;
}
.hex-panel {
border-radius: 16px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.10);
padding: 14px;
}
.hex-panel-title {
font-family: "SevenSegment", ui-sans-serif, system-ui;
font-size: 12px;
letter-spacing: 2px;
opacity: 0.7;
margin-bottom: 10px;
}
.hex-setting-title { font-weight: 900; opacity: 0.9; margin-bottom: 10px; }
.hex-width {
display: grid;
grid-template-columns: 48px 1fr 48px;
gap: 10px;
align-items: center;
}
.hex-width-readout {
border-radius: 14px;
background: rgba(0,0,0,0.22);
border: 1px solid rgba(255,255,255,0.10);
padding: 10px 12px;
display: flex;
justify-content: space-between;
align-items: baseline;
}
.hex-width-label {
font-family: "SevenSegment", ui-sans-serif, system-ui;
opacity: 0.7;
font-weight: 800;
letter-spacing: 1px;
font-size: 12px;
}
.hex-width-number { font-size: 30px; font-weight: 900; color: #46ff8a; }
.hex-hint { margin-top: 8px; opacity: 0.65; font-size: 12px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
.hex-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.hex-mt-sm { margin-top: 10px; }
.hex-tools-top { display: flex; gap: 10px; justify-content: center; margin-bottom: 10px; }
.hex-tiny-note { margin-top: 8px; font-size: 11px; opacity: 0.6; letter-spacing: 1px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
/* ================= Dialog ================= */
.hex-dialog { border: none; padding: 0; background: transparent; }
.hex-dialog::backdrop { background: rgba(0,0,0,0.55); }
.hex-dialog-card {
width: min(560px, 92vw);
border-radius: 18px;
background: #1a1b24;
border: 1px solid rgba(255,255,255,0.12);
padding: 16px;
color: #e7e8ee;
}
.hex-dialog-title { font-weight: 900; letter-spacing: 1px; margin-bottom: 10px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
.hex-dialog-input {
width: 100%;
padding: 12px 12px;
border-radius: 14px;
border: 1px solid rgba(255,255,255,0.14);
background: rgba(0,0,0,0.25);
color: #e7e8ee;
font-size: 18px;
}
.hex-dialog-hint { margin-top: 10px; opacity: 0.7; font-size: 13px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
.hex-dialog-error { margin-top: 8px; font-size: 13px; color: #ff6b6b; min-height: 18px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
.hex-dialog-actions { margin-top: 14px; display: flex; gap: 10px; justify-content: flex-end; }
@media (max-width: 900px) {
.hex-toolbox { width: min(360px, 92vw); right: 16px; }
.hex-toolbox-btn { right: 16px; }
.hex-number { font-size: 60px; }
.hex-number--tiny { font-size: 40px; letter-spacing: 4px; }
}

View File

@@ -1,232 +0,0 @@
type DialogMode = "hex" | "den" | "bin";
const root = document.querySelector<HTMLElement>("[data-hex-sim]");
if (!root) throw new Error("Hex simulator root not found");
const outDen = root.querySelector<HTMLElement>('[data-out="denary"]')!;
const outHex = root.querySelector<HTMLElement>('[data-out="hex"]')!;
const outBin = root.querySelector<HTMLElement>('[data-out="bin"]')!;
const outDigitsRow = root.querySelector<HTMLElement>('[data-out="digitsRow"]')!;
const toolbox = root.querySelector<HTMLElement>('[data-out="toolbox"]')!;
const toolboxBtn = root.querySelector<HTMLButtonElement>('[data-action="toggleToolbox"]')!;
const digitsCount = root.querySelector<HTMLElement>('[data-out="digitsCount"]')!;
const bitsHint = root.querySelector<HTMLElement>('[data-out="bitsHint"]')!;
const randomBtn = root.querySelector<HTMLButtonElement>("[data-random]")!;
const dialog = root.querySelector<HTMLDialogElement>('[data-out="dialog"]')!;
const dialogTitle = root.querySelector<HTMLElement>('[data-out="dialogTitle"]')!;
const dialogInput = root.querySelector<HTMLInputElement>('[data-out="dialogInput"]')!;
const dialogHint = root.querySelector<HTMLElement>('[data-out="dialogHint"]')!;
const dialogError = root.querySelector<HTMLElement>('[data-out="dialogError"]')!;
let digits = 2; // 1..8
let value = 0; // unsigned denary
let randomTimer: number | null = null;
let dialogMode: DialogMode | null = null;
const clamp = (n: number, min: number, max: number) => Math.min(max, Math.max(min, n));
const maxForDigits = (d: number) => (16 ** d) - 1;
const padHex = (n: number, d: number) => n.toString(16).toUpperCase().padStart(d, "0");
const padBin = (n: number, b: number) => n.toString(2).padStart(b, "0");
const groupBin = (b: string) => b.replace(/(.{4})/g, "$1 ").trim();
function stopRandom(): void {
if (randomTimer !== null) window.clearInterval(randomTimer);
randomTimer = null;
randomBtn.classList.remove("is-running");
}
function startRandom(): void {
stopRandom();
const max = maxForDigits(digits);
const start = Date.now();
randomBtn.classList.add("is-running");
randomTimer = window.setInterval(() => {
value = Math.floor(Math.random() * (max + 1));
render();
if (Date.now() - start > 1600) stopRandom();
}, 90);
}
function render(): void {
const bits = digits * 4;
digitsCount.textContent = String(digits);
bitsHint.textContent = `= ${bits} bits`;
outDen.textContent = String(value);
outHex.textContent = padHex(value, digits);
outBin.textContent = groupBin(padBin(value, bits));
renderDigitsRow();
}
function renderDigitsRow(): void {
const hex = padHex(value, digits);
outDigitsRow.innerHTML = "";
for (let i = 0; i < digits; i++) {
const pow = digits - 1 - i;
const placeValue = 16 ** pow;
const digitChar = hex[i];
const digitVal = parseInt(digitChar, 16);
const nibbleBits = [(digitVal >> 3) & 1, (digitVal >> 2) & 1, (digitVal >> 1) & 1, digitVal & 1]; // 8 4 2 1
const col = document.createElement("div");
col.className = "hex-digit-col";
col.innerHTML = `
<div class="hex-digit-controls">
<button class="hex-btn hex-btn--square hex-btn--green2" type="button" data-action="digitUp" data-i="${i}" title="Increase">▲</button>
<button class="hex-btn hex-btn--square hex-btn--red" type="button" data-action="digitDown" data-i="${i}" title="Decrease">▼</button>
</div>
<div class="hex-digit-char hex-font-number">${digitChar}</div>
<!-- bulbs: brightness changes based on nibble bits -->
<div class="hex-bulbs" aria-label="Nibble bits">
${[8,4,2,1].map((w, idx) => {
const on = nibbleBits[idx] === 1;
return `
<div class="hex-bulb ${on ? "is-on" : ""}">
<div class="hex-bulb-cap"></div>
<div class="hex-bulb-glow"></div>
<div class="hex-bulb-label">${w}</div>
</div>
`;
}).join("")}
</div>
<div class="hex-digit-place">${placeValue}</div>
`;
outDigitsRow.appendChild(col);
}
}
function openDialog(mode: DialogMode): void {
stopRandom();
dialogMode = mode;
dialogError.textContent = "";
dialogInput.value = "";
if (mode === "hex") {
dialogTitle.textContent = "Custom Hexadecimal";
dialogHint.textContent = `Enter 1${digits} hex digit(s) (09, AF).`;
dialogInput.placeholder = "A1";
dialogInput.inputMode = "text";
} else if (mode === "den") {
dialogTitle.textContent = "Custom Denary";
dialogHint.textContent = `Enter a whole number from 0 to ${maxForDigits(digits)}.`;
dialogInput.placeholder = "42";
dialogInput.inputMode = "numeric";
} else {
dialogTitle.textContent = "Custom Binary";
dialogHint.textContent = `Enter up to ${digits * 4} bit(s) using 0 and 1.`;
dialogInput.placeholder = "00101010";
dialogInput.inputMode = "text";
}
dialog.showModal();
window.setTimeout(() => dialogInput.focus(), 0);
}
function closeDialog(): void {
dialogMode = null;
dialogError.textContent = "";
if (dialog.open) dialog.close();
}
function applyDialog(): void {
const raw = (dialogInput.value || "").trim();
if (!dialogMode) return closeDialog();
if (raw.length === 0) return closeDialog();
const max = maxForDigits(digits);
const bits = digits * 4;
if (dialogMode === "hex") {
const v = raw.toUpperCase();
if (!/^[0-9A-F]+$/.test(v)) { dialogError.textContent = "Hex must use 09 and AF only."; return; }
if (v.length > digits) { dialogError.textContent = `Max length is ${digits} hex digit(s).`; return; }
value = clamp(parseInt(v, 16), 0, max);
render();
return closeDialog();
}
if (dialogMode === "den") {
if (!/^\d+$/.test(raw)) { dialogError.textContent = "Denary must be whole numbers only."; return; }
const n = Number(raw);
if (!Number.isFinite(n)) { dialogError.textContent = "Invalid number."; return; }
value = clamp(n, 0, max);
render();
return closeDialog();
}
// bin
if (!/^[01]+$/.test(raw)) { dialogError.textContent = "Binary must use 0 and 1 only."; return; }
if (raw.length > bits) { dialogError.textContent = `Max length is ${bits} bit(s).`; return; }
value = clamp(parseInt(raw, 2), 0, max);
render();
return closeDialog();
}
function applyDigitDelta(i: number, delta: number): void {
stopRandom();
const hexArr = padHex(value, digits).split("");
let v = parseInt(hexArr[i], 16);
v = (v + delta) % 16;
if (v < 0) v += 16;
hexArr[i] = v.toString(16).toUpperCase();
value = clamp(parseInt(hexArr.join(""), 16), 0, maxForDigits(digits));
render();
}
// dialog cancel / backdrop
dialog.addEventListener("cancel", (e) => { e.preventDefault(); closeDialog(); });
dialog.addEventListener("click", (e) => {
const card = dialog.querySelector(".hex-dialog-card");
if (card && !card.contains(e.target as Node)) closeDialog();
});
dialogInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") applyDialog();
if (e.key === "Escape") closeDialog();
});
// main click handler
root.addEventListener("click", (e) => {
const btn = (e.target as HTMLElement).closest<HTMLElement>("[data-action]");
if (!btn) return;
const action = btn.getAttribute("data-action")!;
if (action === "toggleToolbox") {
toolbox.classList.toggle("is-open");
toolboxBtn.setAttribute("aria-expanded", toolbox.classList.contains("is-open") ? "true" : "false");
return;
}
if (action === "digitsMinus") { digits = clamp(digits - 1, 1, 8); value = clamp(value, 0, maxForDigits(digits)); return render(); }
if (action === "digitsPlus") { digits = clamp(digits + 1, 1, 8); value = clamp(value, 0, maxForDigits(digits)); return render(); }
if (action === "increment") { stopRandom(); value = clamp(value + 1, 0, maxForDigits(digits)); return render(); }
if (action === "decrement") { stopRandom(); value = clamp(value - 1, 0, maxForDigits(digits)); return render(); }
if (action === "reset") { stopRandom(); value = 0; return render(); }
if (action === "random") { return startRandom(); }
if (action === "customHex") return openDialog("hex");
if (action === "customDenary") return openDialog("den");
if (action === "customBinary") return openDialog("bin");
if (action === "dialogCancel") return closeDialog();
if (action === "dialogApply") return applyDialog();
if (action === "digitUp") return applyDigitDelta(Number(btn.getAttribute("data-i")), +1);
if (action === "digitDown") return applyDigitDelta(Number(btn.getAttribute("data-i")), -1);
});
render();

View File

@@ -1,8 +0,0 @@
<footer class="site-footer">
<div class="site-footer__inner">
<div class="site-footer__text">
COMPUTER SCIENCE CONCEPT SIMULATORS<br />
© 2025 COMPUTING:BOX • CREATED WITH ♥ BY MR LYALL
</div>
</div>
</footer>

View File

@@ -1,25 +0,0 @@
---
const nav = [
{ href: "/about", label: "About" },
{ href: "/binary", label: "Binary" },
{ href: "/hexadecimal", label: "Hexadecimal" },
{ href: "/hex-colours", label: "Hex Colours" },
{ href: "/logic-gates", label: "Logic Gates" },
];
---
<header class="site-header">
<div class="site-header__inner">
<a class="site-header__brand" href="/" aria-label="Computing:Box home">
<img class="site-header__logo" src="/img/logo.png" alt="" width="26" height="26" />
<span class="site-header__name">COMPUTING:BOX</span>
</a>
<nav class="site-header__nav" aria-label="Primary">
{nav.map((i) => (
<a class="site-header__link" href={i.href}>
{i.label.toUpperCase()}
</a>
))}
</nav>
</div>
</header>

View File

@@ -1,4 +1,6 @@
--- ---
import '../styles/global.css';
const { title = "Computing:Box" } = Astro.props; const { title = "Computing:Box" } = Astro.props;
--- ---
@@ -8,105 +10,25 @@ const { title = "Computing:Box" } = Astro.props;
<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>
<style> <link rel="preconnect" href="https://fonts.googleapis.com">
:root{ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
--nav-h: 108px; /* 3x-ish height */ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700;800;900&display=swap" rel="stylesheet">
--bg: #1f2027;
--text: #e8e8ee;
--muted: #a9acb8;
--line: rgba(255,255,255,.10);
}
body{
margin:0;
background:var(--bg);
color:var(--text);
}
.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: 2em;
height: 2em;
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{
max-width: 1400px;
margin: 0 auto;
}
</style>
</head> </head>
<body> <body>
<header class="siteNav"> <header class="siteNav">
<div class="navInner"> <div class="navInner">
<a class="brand" href="/"> <a class="brand" href="/">
<img class="brandLogo" src="/images/computing-box-logo.svg" alt="Computing:Box logo" /> <img class="brandLogo" src="/images/computing-box-logo.svg" alt="Computing:Box logo" />
<span class="brandName">COMPUTING:BOX</span> <span class="brandName">Computing:Box</span>
</a> </a>
<nav class="navLinks" aria-label="Site navigation"> <nav class="navLinks" aria-label="Site navigation">
<a href="/about">ABOUT</a> <a href="/about">About</a>
<a href="/binary">BINARY</a> <a href="/binary">Binary</a>
<a href="/hexadecimal">HEXADECIMAL</a> <a href="/hexadecimal">Hexadecimal</a>
<a href="/hex-colours">HEX COLOURS</a> <a href="/hex-colours">Hex Colours</a>
<a href="/logic-gates">LOGIC GATES</a> <a href="/logic-gates">Logic Gates</a>
</nav> </nav>
</div> </div>
</header> </header>
@@ -114,5 +36,12 @@ const { title = "Computing:Box" } = Astro.props;
<main class="pageWrap"> <main class="pageWrap">
<slot /> <slot />
</main> </main>
<footer class="siteFooter">
<div class="footerInner">
<div>Computer Science Concept Simulators</div>
<div>© {new Date().getFullYear()} Computing:Box • Created with ♥ by Mr A Lyall</div>
</div>
</footer>
</body> </body>
</html> </html>

View File

@@ -1,22 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>Astro Basics</title>
</head>
<body>
<slot />
</body>
</html>
<style>
html,
body {
margin: 0;
width: 100%;
height: 100%;
}
</style>

View File

@@ -1,24 +1,27 @@
--- ---
import BaseLayout from "../layouts/BaseLayout.astro"; import BaseLayout from "../layouts/BaseLayout.astro";
import "../styles/binary.css";
--- ---
<BaseLayout title="Binary Simulator"> <BaseLayout title="Binary Simulator | Computing:Box">
<button id="toolboxToggle" class="toolboxToggle" type="button" aria-expanded="true"> <div class="binaryPage" id="binaryPage">
<span class="toolboxIcon" aria-hidden="true">🧰</span> <button
<span class="toolboxLabel">TOOLBOX</span> id="toolboxToggle"
</button> class="toolboxToggle"
type="button"
aria-expanded="true"
>
<span class="toolboxIcon" aria-hidden="true">🧰</span>
<span class="toolboxText">TOOLBOX</span>
</button>
<main class="wrap">
<section class="topGrid"> <section class="topGrid">
<!-- LEFT --> <div class="leftCol">
<div>
<div class="readout"> <div class="readout">
<div class="label">Denary</div> <div class="label">Denary</div>
<div id="denaryNumber" class="num denaryValue">0</div> <div id="denaryNumber" class="num denaryValue">0</div>
<div class="label">Binary</div> <div class="label">Binary</div>
<div id="binaryNumber" class="num binaryValue">0000 0000</div> <div id="binaryNumber" class="num binaryValue">00000000</div>
</div> </div>
<div class="divider"></div> <div class="divider"></div>
@@ -28,9 +31,7 @@ import "../styles/binary.css";
</section> </section>
</div> </div>
<!-- RIGHT TOOLBOX --> <aside id="toolboxPanel" class="panelCol" aria-label="Toolbox">
<aside id="toolbox" class="panelCol" aria-label="Toolbox">
<!-- SETTINGS -->
<div class="card"> <div class="card">
<div class="cardTitle">Settings</div> <div class="cardTitle">Settings</div>
@@ -40,7 +41,7 @@ import "../styles/binary.css";
<input id="modeToggle" type="checkbox" /> <input id="modeToggle" type="checkbox" />
<span class="slider"></span> <span class="slider"></span>
</label> </label>
<div class="toggleLabel" id="lblTwos">Two&rsquo;s complement</div> <div class="toggleLabel" id="lblTwos">Two's complement</div>
</div> </div>
<div class="hint" id="modeHint"> <div class="hint" id="modeHint">
@@ -51,7 +52,6 @@ import "../styles/binary.css";
<div class="subTitle">Bit width</div> <div class="subTitle">Bit width</div>
<div class="bitWidthRow"> <div class="bitWidthRow">
<button class="miniBtn" id="btnBitsDown" type="button" aria-label="Decrease bits"></button> <button class="miniBtn" id="btnBitsDown" type="button" aria-label="Decrease bits"></button>
<div class="bitInputWrap"> <div class="bitInputWrap">
<div class="bitInputLabel">Bits</div> <div class="bitInputLabel">Bits</div>
<input <input
@@ -66,47 +66,36 @@ import "../styles/binary.css";
aria-label="Number of bits" aria-label="Number of bits"
/> />
</div> </div>
<button class="miniBtn" id="btnBitsUp" type="button" aria-label="Increase bits">+</button> <button class="miniBtn" id="btnBitsUp" type="button" aria-label="Increase bits">+</button>
</div> </div>
</div> </div>
</div> </div>
<!-- CUSTOM -->
<div class="card"> <div class="card">
<div class="cardTitle">Custom</div> <div class="cardTitle">Custom Number</div>
<div class="controlsRow">
<div class="twoBtnRow"> <button class="btn btnAccent btnHalf" id="btnCustomBinary" type="button">Custom Binary</button>
<button class="btn btnAccent" id="btnCustomBinary" type="button">Custom Binary</button> <button class="btn btnAccent btnHalf" id="btnCustomDenary" type="button">Custom Denary</button>
<button class="btn btnAccent" id="btnCustomDenary" type="button">Custom Denary</button>
</div> </div>
<button class="btn btnWide" id="btnRandom" type="button">Random</button>
<button class="toolBtn toolWide toolRandom" id="btnRandom" type="button">
Random
</button>
<div class="hint">Random runs briefly then stops automatically.</div> <div class="hint">Random runs briefly then stops automatically.</div>
</div> </div>
<!-- TOOLS -->
<div class="card"> <div class="card">
<div class="cardTitle">Tools</div> <div class="cardTitle">Tools</div>
<div class="toolRowCentered">
<div class="toolsTopRow"> <button class="toolBtn toolSpin toolDec" id="btnDec" type="button" aria-label="Decrement">▼</button>
<button class="toolBtn toolArrow toolDown" id="btnDec" type="button" aria-label="Decrement"></button> <button class="toolBtn toolSpin toolInc" id="btnInc" type="button" aria-label="Increment"></button>
<button class="toolBtn toolArrow toolUp" id="btnInc" type="button" aria-label="Increment">▲</button>
</div> </div>
<div class="toolRow2">
<div class="twoBtnRow"> <button class="btn btnHalf" id="btnShiftLeft" type="button">Left Shift</button>
<button class="btn" id="btnShiftLeft" type="button">Left Shift</button> <button class="btn btnHalf" id="btnShiftRight" type="button">Right Shift</button>
<button class="btn" id="btnShiftRight" type="button">Right Shift</button>
</div> </div>
<button class="btn btnReset btnWide" id="btnClear" type="button">Reset</button>
<button class="toolBtn toolWide toolReset" id="btnClear" type="button">Reset</button>
</div> </div>
</aside> </aside>
</section> </section>
</main> </div>
<script type="module" src="/src/scripts/binary.js"></script> <script src="../scripts/binary.js"></script>
</BaseLayout> </BaseLayout>

View File

@@ -0,0 +1,99 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="Hex Colours Simulator | Computing:Box">
<div class="binaryPage" id="colorPage">
<button
id="toolboxToggle"
class="toolboxToggle"
type="button"
aria-expanded="true"
>
<span class="toolboxIcon" aria-hidden="true">🧰</span>
<span class="toolboxText">TOOLBOX</span>
</button>
<section class="topGrid">
<div class="leftCol">
<div class="readoutContainer">
<div class="readout">
<div class="readoutBlock">
<div class="label">Denary (R, G, B)</div>
<div id="denaryNumber" class="num denaryValue">
<span class="text-red">0</span>
<span class="text-green">0</span>
<span class="text-blue">0</span>
</div>
</div>
<div class="readoutBlock">
<div class="label">Hexadecimal</div>
<div id="hexNumber" class="num hexValue">
<span class="text-red"><span style="color:var(--muted)">#</span>00</span>
<span class="text-green">00</span>
<span class="text-blue">00</span>
</div>
</div>
<div class="readoutBlock">
<div class="label">Binary</div>
<div id="binaryNumber" class="num binaryValue">
<span class="text-red">00000000</span>
<span class="text-green">00000000</span>
<span class="text-blue">00000000</span>
</div>
</div>
</div>
<div class="colorPreviewSide">
<div class="colorBoxWrap">
<div id="previewColor" class="colorBox"></div>
<div class="colorBoxLabel">Colour</div>
</div>
<div class="colorBoxWrap">
<div id="previewInverted" class="colorBox"></div>
<div class="colorBoxLabel">Inverted</div>
</div>
</div>
</div>
<div class="divider"></div>
<section class="bitsWrap" aria-label="Hex Colour Cards">
<div class="colorGroupWrap" id="colorGrid"></div>
</section>
</div>
<aside id="toolboxPanel" class="panelCol" aria-label="Toolbox">
<div class="card">
<div class="cardTitle">Info</div>
<div class="hint" style="margin-top: 0;">
Hexadecimal colours are made of 3 channels (Red, Green, Blue). Each channel is an 8-bit value ranging from 00 to FF (0 to 255).
</div>
</div>
<div class="card">
<div class="cardTitle">Colour Tools</div>
<div class="controlsRow">
<button class="btn btnAccent btnHalf" id="btnCustomHex" type="button">Custom Hex</button>
<button class="btn btnAccent btnHalf" id="btnCustomRGB" type="button">Custom RGB</button>
</div>
<div class="controlsRow">
<button class="btn btnAccent btnWide" id="btnInvert" type="button">Invert Colour</button>
</div>
<button class="btn btnWide" id="btnRandom" type="button">Random Colour</button>
<div class="hint">Random runs briefly then stops automatically.</div>
</div>
<div class="card">
<div class="cardTitle">Tools</div>
<button class="btn btnReset btnWide" id="btnClear" type="button">Reset</button>
</div>
</aside>
</section>
</div>
<script src="../scripts/hexColours.js"></script>
</BaseLayout>

View File

@@ -1,8 +1,94 @@
--- ---
import BaseLayout from "../layouts/BaseLayout.astro"; import BaseLayout from "../layouts/BaseLayout.astro";
import HexSimulator from "../components/simulators/HexSimulator.astro";
--- ---
<BaseLayout title="Hexadecimal | Computing:Box"> <BaseLayout title="Hexadecimal Simulator | Computing:Box">
<HexSimulator /> <div class="binaryPage" id="hexPage">
</BaseLayout> <button
id="toolboxToggle"
class="toolboxToggle"
type="button"
aria-expanded="true"
>
<span class="toolboxIcon" aria-hidden="true">🧰</span>
<span class="toolboxText">TOOLBOX</span>
</button>
<section class="topGrid">
<div class="leftCol">
<div class="readout">
<div class="label">Denary</div>
<div id="denaryNumber" class="num denaryValue">0</div>
<div class="label">Hexadecimal</div>
<div id="hexNumber" class="num hexValue">00</div>
<div class="label">Binary</div>
<div id="binaryNumber" class="num binaryValue">00000000</div>
</div>
<div class="divider"></div>
<section class="bitsWrap" aria-label="Hexadecimal sliders">
<div class="hexGrid" id="hexGrid"></div>
</section>
</div>
<aside id="toolboxPanel" class="panelCol" aria-label="Toolbox">
<div class="card">
<div class="cardTitle">Settings</div>
<div class="hint" style="margin-top: 0; margin-bottom: 14px;">
Hexadecimal represents numbers using base 16 (0-9, A-F).
</div>
<div class="subCard">
<div class="subTitle">Digit width</div>
<div class="bitWidthRow">
<button class="miniBtn" id="btnDigitsDown" type="button" aria-label="Decrease digits"></button>
<div class="bitInputWrap">
<div class="bitInputLabel">Digits</div>
<input
id="digitsInput"
class="bitInput"
type="number"
inputmode="numeric"
min="1"
max="16"
step="1"
value="2"
aria-label="Number of hex digits"
/>
</div>
<button class="miniBtn" id="btnDigitsUp" type="button" aria-label="Increase digits">+</button>
</div>
</div>
</div>
<div class="card">
<div class="cardTitle">Custom Number</div>
<div class="controlsRow">
<button class="btn btnAccent btnHalf" id="btnCustomHex" type="button">Custom Hex</button>
<button class="btn btnAccent btnHalf" id="btnCustomDenary" type="button">Custom Denary</button>
</div>
<div class="controlsRow">
<button class="btn btnAccent btnWide" id="btnCustomBinary" type="button">Custom Binary</button>
</div>
<button class="btn btnWide" id="btnRandom" type="button">Random</button>
<div class="hint">Random runs briefly then stops automatically.</div>
</div>
<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>
<button class="btn btnReset btnWide" id="btnClear" type="button">Reset</button>
</div>
</aside>
</section>
</div>
<script src="../scripts/hexadecimal.js"></script>
</BaseLayout>

View File

@@ -1,11 +0,0 @@
---
import Welcome from '../components/Welcome.astro';
import Layout from '../layouts/Layout.astro';
// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
---
<Layout>
<Welcome />
</Layout>

View File

@@ -0,0 +1,53 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import "../styles/logic-gates.css";
---
<BaseLayout title="Logic Gate Builder | Computing:Box">
<div id="logicPage" class="lg-container">
<button id="toolboxToggle" class="toolboxToggle" type="button" aria-expanded="true">
<span class="toolboxIcon" aria-hidden="true">🧰</span>
<span class="toolboxText">TOOLBOX</span>
</button>
<div class="lg-top-header">
<div class="lg-title">Interactive Logic Circuit Builder</div>
<div class="lg-subtitle">
Drag items from the toolbox to the board. Drag from output ports to input ports to wire. Click a wire or node and press <kbd>Delete</kbd> to remove it.
</div>
</div>
<div class="lg-workspace" id="workspace">
<svg class="lg-svg-layer" id="wireLayer"></svg>
</div>
<aside id="toolboxPanel" class="lg-toolbox" aria-label="Toolbox">
<div class="card">
<div class="cardTitle">Components</div>
<div class="tb-icon-grid" id="toolboxGrid">
</div>
</div>
<div class="card">
<div class="cardTitle">Live Truth Table</div>
<details open>
<summary class="tt-summary">Show / Hide Table</summary>
<div style="font-family: var(--ui-font); font-size: 12px; color: var(--muted); margin-bottom: 12px;">Auto-generates based on current wiring. (Max 6 inputs)</div>
<div class="tt-table-wrap" id="truthTableContainer">
</div>
</details>
</div>
<div class="card">
<div class="cardTitle">Tools</div>
<button class="btn btnReset btnWide" id="btnClearBoard" type="button" style="margin-bottom:0;">Clear Board</button>
</div>
</aside>
</div>
<script src="../scripts/logicGates.js"></script>
</BaseLayout>

View File

@@ -1,6 +1,3 @@
// src/scripts/binary.js
// Computing:Box — Binary page logic (Unsigned + Two's Complement)
(() => { (() => {
/* ----------------------------- /* -----------------------------
DOM DOM
@@ -12,6 +9,10 @@
const modeToggle = document.getElementById("modeToggle"); const modeToggle = document.getElementById("modeToggle");
const modeHint = document.getElementById("modeHint"); 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 btnCustomBinary = document.getElementById("btnCustomBinary");
const btnCustomDenary = document.getElementById("btnCustomDenary"); const btnCustomDenary = document.getElementById("btnCustomDenary");
@@ -27,14 +28,13 @@
const btnBitsDown = document.getElementById("btnBitsDown"); const btnBitsDown = document.getElementById("btnBitsDown");
const toolboxToggle = document.getElementById("toolboxToggle"); const toolboxToggle = document.getElementById("toolboxToggle");
const toolboxPanel = document.getElementById("toolboxPanel"); const binaryPage = document.getElementById("binaryPage");
/* ----------------------------- /* -----------------------------
STATE STATE
----------------------------- */ ----------------------------- */
let bitCount = clampInt(Number(bitsInput?.value ?? 8), 1, 64); let bitCount = clampInt(Number(bitsInput?.value ?? 8), 1, 64);
let bits = new Array(bitCount).fill(false); let bits = new Array(bitCount).fill(false);
let randomTimer = null; let randomTimer = null;
/* ----------------------------- /* -----------------------------
@@ -54,7 +54,7 @@
} }
function unsignedMaxExclusive(nBits) { function unsignedMaxExclusive(nBits) {
return pow2Big(nBits); return pow2Big(nBits);
} }
function unsignedMaxValue(nBits) { function unsignedMaxValue(nBits) {
@@ -94,7 +94,8 @@
function signedBigIntToBitsTwos(vSigned) { function signedBigIntToBitsTwos(vSigned) {
const span = pow2Big(bitCount); const span = pow2Big(bitCount);
let v = ((vSigned % span) + span) % span; let v = vSigned;
v = ((v % span) + span) % span;
unsignedBigIntToBits(v); unsignedBigIntToBits(v);
} }
@@ -102,17 +103,33 @@
let s = ""; let s = "";
for (let i = bitCount - 1; i >= 0; i--) { for (let i = bitCount - 1; i >= 0; i--) {
s += bits[i] ? "1" : "0"; s += bits[i] ? "1" : "0";
const posFromRight = (bitCount - i); const posFromLeft = (bitCount - i);
if (i !== 0 && posFromRight % 4 === 0) s += " "; if (i !== 0 && posFromLeft % 4 === 0) s += " ";
} }
return s; return s.trimEnd();
} }
function updateModeHint() { function updateModeHint() {
if (!modeHint) return; if (!modeHint) return;
modeHint.textContent = isTwosMode() if (isTwosMode()) {
? "Tip: In twos complement, the left-most bit (MSB) represents a negative value." modeHint.textContent = "Tip: In two's complement, the left-most bit (MSB) represents a negative value.";
: "Tip: In unsigned binary, all bits represent positive values."; } 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)));
} }
/* ----------------------------- /* -----------------------------
@@ -127,18 +144,25 @@
for (let i = 0; i < Math.min(oldBits.length, bitCount); i++) bits[i] = oldBits[i]; for (let i = 0; i < Math.min(oldBits.length, bitCount); i++) bits[i] = oldBits[i];
bitsGrid.innerHTML = ""; bitsGrid.innerHTML = "";
bitsGrid.classList.toggle("bitsFew", bitCount < 8);
for (let i = bitCount - 1; i >= 0; i--) { for (let i = bitCount - 1; i >= 0; i--) {
const bitEl = document.createElement("div"); const bitEl = document.createElement("div");
bitEl.className = "bit"; bitEl.className = "bit";
bitEl.innerHTML = ` bitEl.innerHTML = `
<div class="bulb" id="bulb-${i}" aria-hidden="true">💡</div> <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> <div class="bitVal" id="bitLabel-${i}"></div>
<label class="switch" aria-label="Toggle bit ${i}"> <label class="switch" aria-label="Toggle bit ${i}">
<input type="checkbox" data-index="${i}"> <input type="checkbox" data-index="${i}">
<span class="slider"></span> <span class="slider"></span>
</label> </label>
`; `;
bitsGrid.appendChild(bitEl); bitsGrid.appendChild(bitEl);
} }
@@ -150,6 +174,7 @@
}); });
}); });
computeColsForBitsGrid();
updateUI(); updateUI();
} }
@@ -161,14 +186,14 @@
const label = document.getElementById(`bitLabel-${i}`); const label = document.getElementById(`bitLabel-${i}`);
if (!label) continue; if (!label) continue;
// Keep label on ONE LINE (no wrapping) let valStr;
label.style.whiteSpace = "nowrap";
if (isTwosMode() && i === bitCount - 1) { if (isTwosMode() && i === bitCount - 1) {
label.textContent = `-${pow2Big(bitCount - 1).toString()}`; valStr = `-${pow2Big(bitCount - 1).toString()}`;
} else { } else {
label.textContent = pow2Big(i).toString(); valStr = pow2Big(i).toString();
} }
label.textContent = valStr;
label.style.setProperty('--len', valStr.length);
} }
} }
@@ -189,12 +214,23 @@
function updateReadout() { function updateReadout() {
if (!denaryEl || !binaryEl) return; if (!denaryEl || !binaryEl) return;
denaryEl.textContent = (isTwosMode() ? bitsToSignedBigIntTwos() : bitsToUnsignedBigInt()).toString(); if (isTwosMode()) {
denaryEl.textContent = bitsToSignedBigIntTwos().toString();
} else {
denaryEl.textContent = bitsToUnsignedBigInt().toString();
}
binaryEl.textContent = formatBinaryGrouped(); binaryEl.textContent = formatBinaryGrouped();
} }
function updateUI() { function updateUI() {
updateModeHint(); 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(); updateBitLabels();
syncSwitchesToBits(); syncSwitchesToBits();
updateBulbs(); updateBulbs();
@@ -202,20 +238,25 @@
} }
/* ----------------------------- /* -----------------------------
INPUT SETTERS SET FROM BINARY STRING
----------------------------- */ ----------------------------- */
function setFromBinaryString(binStr) { function setFromBinaryString(binStr) {
const clean = String(binStr ?? "").replace(/\s+/g, ""); const clean = String(binStr ?? "").replace(/\s+/g, "");
if (!/^[01]+$/.test(clean)) return false; if (!/^[01]+$/.test(clean)) return false;
const padded = clean.slice(-bitCount).padStart(bitCount, "0"); const padded = clean.slice(-bitCount).padStart(bitCount, "0");
for (let i = 0; i < bitCount; i++) { for (let i = 0; i < bitCount; i++) {
const charFromRight = padded[padded.length - 1 - i]; const charFromRight = padded[padded.length - 1 - i];
bits[i] = charFromRight === "1"; bits[i] = charFromRight === "1";
} }
updateUI(); updateUI();
return true; return true;
} }
/* -----------------------------
SET FROM DENARY INPUT
----------------------------- */
function setFromDenaryInput(vStr) { function setFromDenaryInput(vStr) {
const raw = String(vStr ?? "").trim(); const raw = String(vStr ?? "").trim();
if (!raw) return false; if (!raw) return false;
@@ -224,9 +265,7 @@
try { try {
if (!/^-?\d+$/.test(raw)) return false; if (!/^-?\d+$/.test(raw)) return false;
v = BigInt(raw); v = BigInt(raw);
} catch { } catch { return false; }
return false;
}
if (isTwosMode()) { if (isTwosMode()) {
const min = twosMin(bitCount); const min = twosMin(bitCount);
@@ -234,8 +273,7 @@
if (v < min || v > max) return false; if (v < min || v > max) return false;
signedBigIntToBitsTwos(v); signedBigIntToBitsTwos(v);
} else { } else {
if (v < 0n) return false; if (v < 0n || v > unsignedMaxValue(bitCount)) return false;
if (v > unsignedMaxValue(bitCount)) return false;
unsignedBigIntToBits(v); unsignedBigIntToBits(v);
} }
@@ -247,18 +285,14 @@
SHIFTS SHIFTS
----------------------------- */ ----------------------------- */
function shiftLeft() { function shiftLeft() {
for (let i = bitCount - 1; i >= 1; i--) bits[i] = bits[i - 1]; for (let i = bitCount - 1; i >= 1; i--) { bits[i] = bits[i - 1]; }
bits[0] = false; bits[0] = false;
updateUI(); updateUI();
} }
function shiftRight() { function shiftRight() {
// Unsigned: logical right shift (MSB becomes 0)
// Two's complement: arithmetic right shift (MSB preserved)
const msb = bits[bitCount - 1]; const msb = bits[bitCount - 1];
for (let i = 0; i < bitCount - 1; i++) { bits[i] = bits[i + 1]; }
for (let i = 0; i < bitCount - 1; i++) bits[i] = bits[i + 1];
bits[bitCount - 1] = isTwosMode() ? msb : false; bits[bitCount - 1] = isTwosMode() ? msb : false;
updateUI(); updateUI();
} }
@@ -267,8 +301,9 @@
CLEAR / INC / DEC CLEAR / INC / DEC
----------------------------- */ ----------------------------- */
function clearAll() { function clearAll() {
bits.fill(false); bits = [];
updateUI(); if (modeToggle) modeToggle.checked = false;
buildBits(8);
} }
function increment() { function increment() {
@@ -280,8 +315,7 @@
signedBigIntToBitsTwos(v); signedBigIntToBitsTwos(v);
} else { } else {
const span = unsignedMaxExclusive(bitCount); const span = unsignedMaxExclusive(bitCount);
const v = (bitsToUnsignedBigInt() + 1n) % span; unsignedBigIntToBits((bitsToUnsignedBigInt() + 1n) % span);
unsignedBigIntToBits(v);
} }
updateUI(); updateUI();
} }
@@ -295,25 +329,22 @@
signedBigIntToBitsTwos(v); signedBigIntToBitsTwos(v);
} else { } else {
const span = unsignedMaxExclusive(bitCount); const span = unsignedMaxExclusive(bitCount);
const v = (bitsToUnsignedBigInt() - 1n + span) % span; unsignedBigIntToBits((bitsToUnsignedBigInt() - 1n + span) % span);
unsignedBigIntToBits(v);
} }
updateUI(); updateUI();
} }
/* ----------------------------- /* -----------------------------
RANDOM (with running pulse + longer run) RANDOM
----------------------------- */ ----------------------------- */
function cryptoRandomBigInt(maxExclusive) { function cryptoRandomBigInt(maxExclusive) {
if (maxExclusive <= 0n) return 0n; if (maxExclusive <= 0n) return 0n;
const bitLen = maxExclusive.toString(2).length; const bitLen = maxExclusive.toString(2).length;
const byteLen = Math.ceil(bitLen / 8); const byteLen = Math.ceil(bitLen / 8);
while (true) { while (true) {
const bytes = new Uint8Array(byteLen); const bytes = new Uint8Array(byteLen);
crypto.getRandomValues(bytes); crypto.getRandomValues(bytes);
let x = 0n; let x = 0n;
for (const b of bytes) x = (x << 8n) | BigInt(b); for (const b of bytes) x = (x << 8n) | BigInt(b);
@@ -325,23 +356,26 @@
} }
function setRandomOnce() { function setRandomOnce() {
const span = unsignedMaxExclusive(bitCount); // 2^n const span = unsignedMaxExclusive(bitCount);
const u = cryptoRandomBigInt(span); const u = cryptoRandomBigInt(span);
unsignedBigIntToBits(u); unsignedBigIntToBits(u);
updateUI(); updateUI();
} }
function setRandomRunning(isRunning) {
if (!btnRandom) return;
btnRandom.classList.toggle("btnRandomRunning", !!isRunning);
}
function runRandomBriefly() { function runRandomBriefly() {
if (randomTimer) { if (randomTimer) {
clearInterval(randomTimer); clearInterval(randomTimer);
randomTimer = null; randomTimer = null;
} }
// pulse while running setRandomRunning(true);
btnRandom?.classList.add("is-running");
const start = Date.now(); const start = Date.now();
const durationMs = 1125; // 25% longer than 900ms const durationMs = 1125;
const tickMs = 80; const tickMs = 80;
randomTimer = setInterval(() => { randomTimer = setInterval(() => {
@@ -349,25 +383,27 @@
if (Date.now() - start >= durationMs) { if (Date.now() - start >= durationMs) {
clearInterval(randomTimer); clearInterval(randomTimer);
randomTimer = null; randomTimer = null;
btnRandom?.classList.remove("is-running"); setRandomRunning(false);
} }
}, tickMs); }, tickMs);
} }
/* ----------------------------- /* -----------------------------
BIT WIDTH BIT WIDTH CONTROLS
----------------------------- */ ----------------------------- */
function setBitWidth(n) { function setBitWidth(n) {
buildBits(clampInt(n, 1, 64)); const v = clampInt(n, 1, 64);
buildBits(v);
} }
/* ----------------------------- /* -----------------------------
TOOLBOX VISIBILITY TOOLBOX TOGGLE
----------------------------- */ ----------------------------- */
function setToolboxVisible(isVisible) { function setToolboxCollapsed(collapsed) {
if (!toolboxPanel) return; if (!binaryPage) return;
toolboxPanel.style.display = isVisible ? "flex" : "none"; binaryPage.classList.toggle("toolboxCollapsed", !!collapsed);
toolboxToggle?.setAttribute("aria-expanded", String(isVisible)); const expanded = !collapsed;
toolboxToggle?.setAttribute("aria-expanded", expanded ? "true" : "false");
} }
/* ----------------------------- /* -----------------------------
@@ -384,8 +420,8 @@
btnCustomDenary?.addEventListener("click", () => { btnCustomDenary?.addEventListener("click", () => {
const v = prompt( const v = prompt(
isTwosMode() isTwosMode()
? `Enter denary (${twosMin(bitCount)} to ${twosMax(bitCount)}):` ? `Enter denary (${twosMin(bitCount).toString()} to ${twosMax(bitCount).toString()}):`
: `Enter denary (0 to ${unsignedMaxValue(bitCount)}):` : `Enter denary (0 to ${unsignedMaxValue(bitCount).toString()}):`
); );
if (v === null) return; if (v === null) return;
if (!setFromDenaryInput(v)) alert("Invalid denary for current mode/bit width"); if (!setFromDenaryInput(v)) alert("Invalid denary for current mode/bit width");
@@ -406,8 +442,12 @@
bitsInput?.addEventListener("change", () => setBitWidth(Number(bitsInput.value))); bitsInput?.addEventListener("change", () => setBitWidth(Number(bitsInput.value)));
toolboxToggle?.addEventListener("click", () => { toolboxToggle?.addEventListener("click", () => {
const isOpen = toolboxToggle.getAttribute("aria-expanded") !== "false"; const isCollapsed = binaryPage?.classList.contains("toolboxCollapsed");
setToolboxVisible(!isOpen); setToolboxCollapsed(!isCollapsed);
});
window.addEventListener("resize", () => {
computeColsForBitsGrid();
}); });
/* ----------------------------- /* -----------------------------
@@ -415,5 +455,5 @@
----------------------------- */ ----------------------------- */
updateModeHint(); updateModeHint();
buildBits(bitCount); buildBits(bitCount);
setToolboxVisible(true); setToolboxCollapsed(false);
})(); })();

241
src/scripts/hexColours.js Normal file
View File

@@ -0,0 +1,241 @@
// src/scripts/hexColours.js
// Computing:Box — Hex Colours logic
(() => {
/* -----------------------------
DOM
----------------------------- */
const colorGrid = document.getElementById("colorGrid");
const denaryEl = document.getElementById("denaryNumber");
const binaryEl = document.getElementById("binaryNumber");
const hexEl = document.getElementById("hexNumber");
const previewColor = document.getElementById("previewColor");
const previewInverted = document.getElementById("previewInverted");
const btnCustomHex = document.getElementById("btnCustomHex");
const btnCustomRGB = document.getElementById("btnCustomRGB");
const btnInvert = document.getElementById("btnInvert");
const btnRandom = document.getElementById("btnRandom");
const btnClear = document.getElementById("btnClear");
const toolboxToggle = document.getElementById("toolboxToggle");
const colorPage = document.getElementById("colorPage");
/* -----------------------------
STATE
----------------------------- */
// rgb[0]=Red, rgb[1]=Green, rgb[2]=Blue (Values 0-255)
let rgb = [0, 0, 0];
let randomTimer = null;
/* -----------------------------
BUILD UI
----------------------------- */
function buildGrid() {
if (!colorGrid) return;
colorGrid.innerHTML = "";
const colorClasses = ['text-red', 'text-green', 'text-blue'];
for (let c = 0; c < 3; c++) {
const group = document.createElement("div");
group.className = "colorGroup";
for (let i = 1; i >= 0; i--) {
const col = document.createElement("div");
col.className = "hexCol";
let cardHTML = `
<div class="hexCard">
<div class="hexCardButtons">
<button class="hexCardBtn inc" id="colorInc-${c}-${i}">▲</button>
<button class="hexCardBtn dec" id="colorDec-${c}-${i}">▼</button>
</div>
<div class="hexDigitDisplay num ${colorClasses[c]}" id="colorDisplay-${c}-${i}">0</div>
<div class="hexNibbleRow">
`;
for (let j = 3; j >= 0; j--) {
cardHTML += `
<div class="hexNibbleBit">
<div class="bulb hexNibbleBulb" id="colorBulb-${c}-${i}-${j}" 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="hexNibbleLabel">${1 << j}</div>
</div>
`;
}
cardHTML += `
</div>
</div>
<div class="hexColWeight ${colorClasses[c]}">${16 ** i}</div>
`;
col.innerHTML = cardHTML;
const incBtn = col.querySelector(`#colorInc-${c}-${i}`);
const decBtn = col.querySelector(`#colorDec-${c}-${i}`);
incBtn.addEventListener("click", () => {
const weight = 16 ** i;
rgb[c] = (rgb[c] + weight) % 256;
updateUI();
});
decBtn.addEventListener("click", () => {
const weight = 16 ** i;
rgb[c] = (rgb[c] - weight + 256) % 256;
updateUI();
});
group.appendChild(col);
}
colorGrid.appendChild(group);
}
}
/* -----------------------------
UI UPDATE
----------------------------- */
function updateUI() {
if (denaryEl) {
denaryEl.innerHTML = `
<span class="text-red">${rgb[0]}</span>
<span class="text-green">${rgb[1]}</span>
<span class="text-blue">${rgb[2]}</span>
`;
}
const hexVals = rgb.map(v => v.toString(16).padStart(2, '0').toUpperCase());
const fullHexString = `#${hexVals.join('')}`;
if (hexEl) {
hexEl.innerHTML = `
<span class="text-red"><span style="color:var(--muted)">#</span>${hexVals[0]}</span>
<span class="text-green">${hexVals[1]}</span>
<span class="text-blue">${hexVals[2]}</span>
`;
}
if (binaryEl) {
binaryEl.innerHTML = `
<span class="text-red">${rgb[0].toString(2).padStart(8, '0')}</span>
<span class="text-green">${rgb[1].toString(2).padStart(8, '0')}</span>
<span class="text-blue">${rgb[2].toString(2).padStart(8, '0')}</span>
`;
}
if (previewColor) previewColor.style.backgroundColor = fullHexString;
const invertedHexString = "#" + rgb.map(v => (255 - v).toString(16).padStart(2, '0').toUpperCase()).join('');
if (previewInverted) previewInverted.style.backgroundColor = invertedHexString;
for (let c = 0; c < 3; c++) {
const val = rgb[c];
const nibbles = [val % 16, Math.floor(val / 16)];
for (let i = 0; i < 2; i++) {
const display = document.getElementById(`colorDisplay-${c}-${i}`);
if (display) display.textContent = nibbles[i].toString(16).toUpperCase();
for (let j = 0; j < 4; j++) {
const bulb = document.getElementById(`colorBulb-${c}-${i}-${j}`);
if (bulb) {
const isOn = (nibbles[i] & (1 << j)) !== 0;
bulb.classList.toggle("on", isOn);
}
}
}
}
}
/* -----------------------------
ACTIONS
----------------------------- */
function clearAll() {
rgb = [0, 0, 0];
updateUI();
}
function setRandomOnce() {
const arr = new Uint8Array(3);
crypto.getRandomValues(arr);
rgb = [arr[0], arr[1], arr[2]];
updateUI();
}
function runRandomBriefly() {
if (randomTimer) {
clearInterval(randomTimer);
randomTimer = null;
}
if (btnRandom) btnRandom.classList.add("btnRandomRunning");
const start = Date.now();
const durationMs = 1125;
const tickMs = 80;
randomTimer = setInterval(() => {
setRandomOnce();
if (Date.now() - start >= durationMs) {
clearInterval(randomTimer);
randomTimer = null;
if (btnRandom) btnRandom.classList.remove("btnRandomRunning");
}
}, tickMs);
}
/* -----------------------------
EVENTS
----------------------------- */
btnCustomHex?.addEventListener("click", () => {
let v = prompt("Enter a 6-character hex code (e.g. FF0055):");
if (v === null) return;
v = v.replace(/\s+/g, "").replace(/^#/i, "").toUpperCase();
if (!/^[0-9A-F]{6}$/.test(v)) return alert("Invalid hex code. Please enter exactly 6 hexadecimal characters.");
rgb = [
parseInt(v.substring(0, 2), 16),
parseInt(v.substring(2, 4), 16),
parseInt(v.substring(4, 6), 16)
];
updateUI();
});
btnCustomRGB?.addEventListener("click", () => {
const v = prompt("Enter R, G, B values (0-255) separated by commas (e.g. 255, 128, 0):");
if (v === null) return;
const parts = v.split(',').map(s => parseInt(s.trim(), 10));
if (parts.length !== 3 || parts.some(isNaN) || parts.some(n => n < 0 || n > 255)) {
return alert("Invalid input. Please provide three numbers between 0 and 255.");
}
rgb = parts;
updateUI();
});
btnInvert?.addEventListener("click", () => {
rgb = rgb.map(v => 255 - v);
updateUI();
});
btnClear?.addEventListener("click", clearAll);
btnRandom?.addEventListener("click", runRandomBriefly);
toolboxToggle?.addEventListener("click", () => {
const isCollapsed = colorPage?.classList.contains("toolboxCollapsed");
colorPage.classList.toggle("toolboxCollapsed", !isCollapsed);
toolboxToggle?.setAttribute("aria-expanded", isCollapsed ? "true" : "false");
});
/* -----------------------------
INIT
----------------------------- */
buildGrid();
updateUI();
})();

312
src/scripts/hexadecimal.js Normal file
View File

@@ -0,0 +1,312 @@
// src/scripts/hexadecimal.js
// Computing:Box — Hexadecimal page logic
(() => {
/* -----------------------------
DOM
----------------------------- */
const hexGrid = document.getElementById("hexGrid");
const denaryEl = document.getElementById("denaryNumber");
const binaryEl = document.getElementById("binaryNumber");
const hexEl = document.getElementById("hexNumber");
const digitsInput = document.getElementById("digitsInput");
const btnCustomBinary = document.getElementById("btnCustomBinary");
const btnCustomDenary = document.getElementById("btnCustomDenary");
const btnCustomHex = document.getElementById("btnCustomHex");
const btnDec = document.getElementById("btnDec");
const btnInc = document.getElementById("btnInc");
const btnClear = document.getElementById("btnClear");
const btnRandom = document.getElementById("btnRandom");
const btnDigitsUp = document.getElementById("btnDigitsUp");
const btnDigitsDown = document.getElementById("btnDigitsDown");
const toolboxToggle = document.getElementById("toolboxToggle");
const hexPage = document.getElementById("hexPage");
/* -----------------------------
STATE
----------------------------- */
let hexCount = clampInt(Number(digitsInput?.value ?? 2), 1, 16);
let nibbles = new Array(hexCount).fill(0);
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 maxExclusive() {
return 1n << BigInt(hexCount * 4);
}
function maxValue() {
return maxExclusive() - 1n;
}
function getValue() {
let v = 0n;
for (let i = 0; i < hexCount; i++) {
v += BigInt(nibbles[i]) << BigInt(i * 4);
}
return v;
}
function setValue(v) {
if (v < 0n) return false;
if (v > maxValue()) return false;
for (let i = 0; i < hexCount; i++) {
nibbles[i] = Number((v >> BigInt(i * 4)) & 0xFn);
}
return true;
}
/* -----------------------------
RESPONSIVE GRID
----------------------------- */
function computeColsForHexGrid() {
if (!hexGrid) return;
hexGrid.style.setProperty("--cols", String(Math.min(hexCount, 8)));
hexGrid.classList.toggle("bitsFew", hexCount < 4);
}
/* -----------------------------
BUILD UI (CARDS + BULBS)
----------------------------- */
function buildGrid(count) {
hexCount = clampInt(count, 1, 16);
if (digitsInput) digitsInput.value = String(hexCount);
const oldNibbles = nibbles.slice();
nibbles = new Array(hexCount).fill(0);
for (let i = 0; i < Math.min(oldNibbles.length, hexCount); i++) {
nibbles[i] = oldNibbles[i];
}
hexGrid.innerHTML = "";
for (let i = hexCount - 1; i >= 0; i--) {
const col = document.createElement("div");
col.className = "hexCol";
let cardHTML = `
<div class="hexCard">
<div class="hexCardButtons">
<button class="hexCardBtn inc" id="hexInc-${i}">▲</button>
<button class="hexCardBtn dec" id="hexDec-${i}">▼</button>
</div>
<div class="hexDigitDisplay num" id="hexDisplay-${i}">0</div>
<div class="hexNibbleRow">
`;
for(let j = 3; j >= 0; j--) {
cardHTML += `
<div class="hexNibbleBit">
<div class="bulb hexNibbleBulb" id="hexBulb-${i}-${j}" 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="hexNibbleLabel">${1 << j}</div>
</div>
`;
}
cardHTML += `
</div>
</div>
<div class="hexColWeight">${(1n << BigInt(i * 4)).toString()}</div>
`;
col.innerHTML = cardHTML;
const incBtn = col.querySelector(`#hexInc-${i}`);
const decBtn = col.querySelector(`#hexDec-${i}`);
incBtn.addEventListener("click", () => {
const span = maxExclusive();
const weight = 1n << BigInt(i * 4);
setValue((getValue() + weight) % span);
updateUI();
});
decBtn.addEventListener("click", () => {
const span = maxExclusive();
const weight = 1n << BigInt(i * 4);
setValue((getValue() - weight + span) % span);
updateUI();
});
hexGrid.appendChild(col);
}
computeColsForHexGrid();
updateUI();
}
/* -----------------------------
UI UPDATE
----------------------------- */
function updateUI() {
const val = getValue();
if (denaryEl) denaryEl.textContent = val.toString();
if (hexEl) hexEl.textContent = val.toString(16).toUpperCase().padStart(hexCount, '0');
if (binaryEl) {
let binStr = "";
for (let i = hexCount - 1; i >= 0; i--) {
binStr += nibbles[i].toString(2).padStart(4, '0') + " ";
}
binaryEl.textContent = binStr.trimEnd();
}
for (let i = 0; i < hexCount; i++) {
const display = document.getElementById(`hexDisplay-${i}`);
if (display) display.textContent = nibbles[i].toString(16).toUpperCase();
for (let j = 0; j < 4; j++) {
const bulb = document.getElementById(`hexBulb-${i}-${j}`);
if (bulb) {
const isOn = (nibbles[i] & (1 << j)) !== 0;
bulb.classList.toggle("on", isOn);
}
}
}
}
/* -----------------------------
CLEAR / INC / DEC
----------------------------- */
function clearAll() {
nibbles.fill(0);
buildGrid(2);
}
function increment() {
const span = maxExclusive();
setValue((getValue() + 1n) % span);
updateUI();
}
function decrement() {
const span = maxExclusive();
setValue((getValue() - 1n + span) % span);
updateUI();
}
/* -----------------------------
RANDOM
----------------------------- */
function cryptoRandomBigInt(maxExcl) {
if (maxExcl <= 0n) return 0n;
const bitLen = maxExcl.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 < maxExcl) return x;
}
}
function setRandomOnce() {
const u = cryptoRandomBigInt(maxExclusive());
setValue(u);
updateUI();
}
function runRandomBriefly() {
if (randomTimer) {
clearInterval(randomTimer);
randomTimer = null;
}
if (btnRandom) btnRandom.classList.add("btnRandomRunning");
const start = Date.now();
const durationMs = 1125;
const tickMs = 80;
randomTimer = setInterval(() => {
setRandomOnce();
if (Date.now() - start >= durationMs) {
clearInterval(randomTimer);
randomTimer = null;
if (btnRandom) btnRandom.classList.remove("btnRandomRunning");
}
}, tickMs);
}
/* -----------------------------
EVENTS
----------------------------- */
btnCustomHex?.addEventListener("click", () => {
const v = prompt(`Enter hexadecimal (0-9, A-F). Current width: ${hexCount} digits`);
if (v === null) return;
const clean = v.replace(/\s+/g, "").toUpperCase();
if (!/^[0-9A-F]+$/.test(clean)) return alert("Invalid hexadecimal.");
if (clean.length > hexCount) return alert("Value too large for current digit width.");
if (!setValue(BigInt("0x" + clean))) alert("Value out of range.");
else updateUI();
});
btnCustomBinary?.addEventListener("click", () => {
const v = prompt(`Enter binary (0, 1). Current width: ${hexCount * 4} bits`);
if (v === null) return;
const clean = v.replace(/\s+/g, "");
if (!/^[01]+$/.test(clean)) return alert("Invalid binary.");
if (clean.length > hexCount * 4) return alert("Value too large for current digit width.");
if (!setValue(BigInt("0b" + clean))) alert("Value out of range.");
else updateUI();
});
btnCustomDenary?.addEventListener("click", () => {
const v = prompt(`Enter denary (0 to ${maxValue().toString()}):`);
if (v === null) return;
const clean = v.trim();
if (!/^\d+$/.test(clean)) return alert("Invalid denary. Digits only.");
if (!setValue(BigInt(clean))) alert(`Value out of range. Enter a number between 0 and ${maxValue().toString()}.`);
else updateUI();
});
btnInc?.addEventListener("click", increment);
btnDec?.addEventListener("click", decrement);
btnClear?.addEventListener("click", clearAll);
btnRandom?.addEventListener("click", runRandomBriefly);
btnDigitsUp?.addEventListener("click", () => buildGrid(hexCount + 1));
btnDigitsDown?.addEventListener("click", () => buildGrid(hexCount - 1));
digitsInput?.addEventListener("change", () => buildGrid(Number(digitsInput.value)));
toolboxToggle?.addEventListener("click", () => {
const isCollapsed = hexPage?.classList.contains("toolboxCollapsed");
hexPage.classList.toggle("toolboxCollapsed", !isCollapsed);
toolboxToggle?.setAttribute("aria-expanded", isCollapsed ? "true" : "false");
});
window.addEventListener("resize", computeColsForHexGrid);
/* -----------------------------
INIT
----------------------------- */
buildGrid(hexCount);
})();

497
src/scripts/logicGates.js Normal file
View File

@@ -0,0 +1,497 @@
// src/scripts/logicGates.js
// Computing:Box — Drag & Drop Logic Builder
(() => {
/* --- DOM Elements --- */
const workspace = document.getElementById("workspace");
const wireLayer = document.getElementById("wireLayer");
const ttContainer = document.getElementById("truthTableContainer");
const toolboxGrid = document.getElementById("toolboxGrid");
const btnClearBoard = document.getElementById("btnClearBoard");
const toolboxToggle = document.getElementById("toolboxToggle");
const logicPage = document.getElementById("logicPage");
/* --- ANSI Gate SVGs (Strict 100x50 with built-in tails) --- */
const GATE_SVGS = {
'AND': `<g stroke="#e8e8ee" stroke-width="3" fill="none"><path d="M0,15 L20,15 M0,35 L20,35 M70,25 L100,25"/><path d="M20,5 L50,5 A20,20 0 0 1 50,45 L20,45 Z" fill="var(--bg)"/></g>`,
'OR': `<g stroke="#e8e8ee" stroke-width="3" fill="none"><path d="M0,15 L26,15 M0,35 L26,35 M70,25 L100,25"/><path d="M20,5 Q55,5 70,25 Q55,45 20,45 Q40,25 20,5 Z" fill="var(--bg)"/></g>`,
'NOT': `<g stroke="#e8e8ee" stroke-width="3" fill="none"><path d="M0,25 L30,25 M71,25 L100,25"/><path d="M30,10 L60,25 L30,40 Z" fill="var(--bg)"/><circle cx="65.5" cy="25" r="4.5" fill="var(--bg)"/></g>`,
'NAND': `<g stroke="#e8e8ee" stroke-width="3" fill="none"><path d="M0,15 L20,15 M0,35 L20,35 M80,25 L100,25"/><path d="M20,5 L50,5 A20,20 0 0 1 50,45 L20,45 Z" fill="var(--bg)"/><circle cx="74.5" cy="25" r="4.5" fill="var(--bg)"/></g>`,
'NOR': `<g stroke="#e8e8ee" stroke-width="3" fill="none"><path d="M0,15 L26,15 M0,35 L26,35 M80,25 L100,25"/><path d="M20,5 Q55,5 70,25 Q55,45 20,45 Q40,25 20,5 Z" fill="var(--bg)"/><circle cx="74.5" cy="25" r="4.5" fill="var(--bg)"/></g>`,
'XOR': `<g stroke="#e8e8ee" stroke-width="3" fill="none"><path d="M0,15 L24,15 M0,35 L24,35 M75,25 L100,25"/><path d="M30,5 Q60,5 75,25 Q60,45 30,45 Q50,25 30,5 Z" fill="var(--bg)"/><path d="M20,5 Q40,25 20,45"/></g>`,
'XNOR': `<g stroke="#e8e8ee" stroke-width="3" fill="none"><path d="M0,15 L24,15 M0,35 L24,35 M85,25 L100,25"/><path d="M30,5 Q60,5 75,25 Q60,45 30,45 Q50,25 30,5 Z" fill="var(--bg)"/><path d="M20,5 Q40,25 20,45"/><circle cx="79.5" cy="25" r="4.5" fill="var(--bg)"/></g>`
};
const INPUT_SVG = `<svg class="lg-line-svg" viewBox="0 0 30 50"><path d="M0,25 L30,25" stroke="#e8e8ee" stroke-width="3" fill="none"/></svg>`;
const OUTPUT_SVG = `<svg class="lg-line-svg" viewBox="0 0 30 50"><path d="M0,25 L30,25" stroke="#e8e8ee" stroke-width="3" fill="none"/></svg>`;
/* --- State --- */
let nodes = {};
let connections = [];
let nextNodeId = 1;
let nextWireId = 1;
let isDraggingNode = null;
let dragOffset = { x: 0, y: 0 };
let clickStartX = 0, clickStartY = 0;
let wiringStart = null;
let tempWirePath = null;
let selectedWireId = null;
let selectedNodeId = null;
/* --- Setup Toolbox --- */
function initToolbox() {
if(!toolboxGrid) return;
let html = `
<div draggable="true" data-spawn="INPUT" class="drag-item tb-icon-box" title="Input Toggle">
<div class="switch" style="pointer-events:none;"><span class="slider"></span></div>
<div class="tb-icon-label">Input</div>
</div>
<div draggable="true" data-spawn="OUTPUT" class="drag-item tb-icon-box" title="Output Bulb">
<div class="bulb on" style="pointer-events:none; width:28px; height:28px;"><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="tb-icon-label">Output</div>
</div>
`;
Object.keys(GATE_SVGS).forEach(gate => {
html += `
<div draggable="true" data-spawn="GATE" data-gate="${gate}" class="drag-item tb-icon-box" title="${gate} Gate">
<svg viewBox="0 0 100 50" style="width:50px; height:25px; pointer-events:none;">${GATE_SVGS[gate]}</svg>
<div class="tb-icon-label">${gate}</div>
</div>
`;
});
toolboxGrid.innerHTML = html;
document.querySelectorAll('.drag-item').forEach(item => {
item.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('spawnType', item.dataset.spawn);
if(item.dataset.spawn === 'GATE') e.dataTransfer.setData('gateType', item.dataset.gate);
});
});
}
/* --- Math & Geometry --- */
function getPortCoords(nodeId, portDataAttr) {
const node = nodes[nodeId];
if (!node || !node.el) return {x:0, y:0};
const portEl = node.el.querySelector(`[data-port="${portDataAttr}"]`);
if (!portEl) return {x:0, y:0};
const wsRect = workspace.getBoundingClientRect();
const portRect = portEl.getBoundingClientRect();
return {
x: portRect.left - wsRect.left + (portRect.width / 2),
y: portRect.top - wsRect.top + (portRect.height / 2)
};
}
function drawBezier(x1, y1, x2, y2) {
const cpDist = Math.abs(x2 - x1) * 0.6 + 20;
return `M ${x1} ${y1} C ${x1 + cpDist} ${y1}, ${x2 - cpDist} ${y2}, ${x2} ${y2}`;
}
/* --- Rendering --- */
function renderWires() {
let svgHTML = '';
connections.forEach(conn => {
const from = getPortCoords(conn.fromNode, 'out');
const to = getPortCoords(conn.toNode, `in${conn.toPort}`);
const sourceNode = nodes[conn.fromNode];
const isActive = sourceNode && sourceNode.value === true;
const isSelected = conn.id === selectedWireId;
svgHTML += `<path class="lg-wire ${isActive ? 'active' : ''} ${isSelected ? 'selected' : ''}" d="${drawBezier(from.x, from.y, to.x, to.y)}" data-conn-id="${conn.id}" />`;
});
if (wiringStart && tempWirePath) {
svgHTML += `<path class="lg-wire lg-wire-temp" d="${drawBezier(wiringStart.x, wiringStart.y, tempWirePath.x, tempWirePath.y)}" />`;
}
wireLayer.innerHTML = svgHTML;
}
function updateNodePositions() {
Object.values(nodes).forEach(n => {
if (n.el) {
n.el.style.left = `${n.x}px`;
n.el.style.top = `${n.y}px`;
}
});
renderWires();
}
function clearSelection() {
selectedWireId = null;
selectedNodeId = null;
document.querySelectorAll('.lg-node.selected').forEach(el => el.classList.remove('selected'));
renderWires();
}
/* --- Logic Evaluation --- */
function evaluateGraph(overrideInputs = null) {
let context = {};
Object.values(nodes).filter(n => n.type === 'INPUT').forEach(n => {
context[n.id] = overrideInputs ? overrideInputs[n.id] : n.value;
});
let changed = true;
let loops = 0;
while (changed && loops < 10) {
changed = false;
loops++;
Object.values(nodes).filter(n => n.type === 'GATE').forEach(gate => {
let in1Conn = connections.find(c => c.toNode === gate.id && c.toPort === '1');
let in2Conn = connections.find(c => c.toNode === gate.id && c.toPort === '2');
let val1 = in1Conn ? (context[in1Conn.fromNode] || false) : false;
let val2 = in2Conn ? (context[in2Conn.fromNode] || false) : false;
let res = false;
switch(gate.gateType) {
case 'AND': res = val1 && val2; break;
case 'OR': res = val1 || val2; break;
case 'NOT': res = !val1; break;
case 'NAND': res = !(val1 && val2); break;
case 'NOR': res = !(val1 || val2); break;
case 'XOR': res = val1 !== val2; break;
case 'XNOR': res = val1 === val2; break;
}
if (context[gate.id] !== res) {
context[gate.id] = res;
changed = true;
}
});
}
let outStates = {};
Object.values(nodes).filter(n => n.type === 'OUTPUT').forEach(out => {
let conn = connections.find(c => c.toNode === out.id);
let res = conn ? (context[conn.fromNode] || false) : false;
outStates[out.id] = res;
});
if (!overrideInputs) {
Object.values(nodes).forEach(n => {
if (n.type === 'GATE') n.value = context[n.id] || false;
if (n.type === 'OUTPUT') {
n.value = outStates[n.id] || false;
const bulb = n.el.querySelector('.bulb');
if (bulb) bulb.classList.toggle('on', n.value);
}
});
}
return outStates;
}
/* --- Truth Table Generation --- */
function generateTruthTable() {
if (!ttContainer) return;
const inNodes = Object.values(nodes).filter(n => n.type === 'INPUT').sort((a,b) => a.label.localeCompare(b.label));
const outNodes = Object.values(nodes).filter(n => n.type === 'OUTPUT').sort((a,b) => a.label.localeCompare(b.label));
if (inNodes.length === 0 || outNodes.length === 0) {
ttContainer.innerHTML = '<div style="padding: 16px; color: var(--muted); text-align:center;">Add inputs and outputs to generate table.</div>';
return;
}
if (inNodes.length > 6) {
ttContainer.innerHTML = '<div style="padding: 16px; color: var(--muted); text-align:center;">Maximum 6 inputs supported.</div>';
return;
}
let html = '<table class="tt-table"><thead><tr>';
inNodes.forEach(n => html += `<th>${n.label}</th>`);
outNodes.forEach(n => html += `<th style="color:var(--text);">${n.label}</th>`);
html += '</tr></thead><tbody>';
const numRows = Math.pow(2, inNodes.length);
for (let i = 0; i < numRows; i++) {
let override = {};
inNodes.forEach((n, idx) => {
override[n.id] = ((i >> (inNodes.length - 1 - idx)) & 1) === 1;
});
let outStates = evaluateGraph(override);
html += '<tr>';
inNodes.forEach(n => {
let val = override[n.id];
html += `<td class="${val ? 'tt-on' : ''}">${val ? 1 : 0}</td>`;
});
outNodes.forEach(n => {
let val = outStates[n.id];
html += `<td class="${val ? 'tt-on' : ''}" style="font-weight:bold;">${val ? 1 : 0}</td>`;
});
html += '</tr>';
}
html += '</tbody></table>';
ttContainer.innerHTML = html;
}
function runSimulation() {
evaluateGraph();
renderWires();
generateTruthTable();
}
/* --- Smart Label Generation --- */
function getNextInputLabel() {
let charCode = 65; // Starts at 'A'
while (Object.values(nodes).some(n => n.type === 'INPUT' && n.label === String.fromCharCode(charCode))) {
charCode++;
}
return String.fromCharCode(charCode);
}
function getNextOutputLabel() {
let idx = 1;
while (Object.values(nodes).some(n => n.type === 'OUTPUT' && n.label === ('Q' + idx))) {
idx++;
}
return 'Q' + idx;
}
/* --- Node Creation --- */
function createNodeElement(node) {
const el = document.createElement('div');
el.className = `lg-node`;
el.dataset.id = node.id;
el.style.left = `${node.x}px`;
el.style.top = `${node.y}px`;
let innerHTML = `<div class="lg-header">${node.label}</div><div class="lg-gate-container">`;
if (node.type === 'INPUT') {
innerHTML += `
<div class="switch" style="margin:0;"><span class="slider"></span></div>
${INPUT_SVG}
<div class="lg-port port-out" data-port="out" style="top: 25px; left: 86px;"></div>
`;
}
else if (node.type === 'OUTPUT') {
innerHTML += `
<div class="lg-port port-in-1" data-port="in1" style="top: 25px; left: 0;"></div>
${OUTPUT_SVG}
<div class="bulb" style="margin:0;"><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>
`;
}
else if (node.type === 'GATE') {
const isNot = node.gateType === 'NOT';
innerHTML += `
<div class="lg-port port-in-1" data-port="in1" style="top: ${isNot ? '25px' : '15px'}; left: 0;"></div>
${!isNot ? `<div class="lg-port port-in-2" data-port="in2" style="top: 35px; left: 0;"></div>` : ''}
<svg class="lg-gate-svg" viewBox="0 0 100 50">${GATE_SVGS[node.gateType]}</svg>
<div class="lg-port port-out" data-port="out" style="top: 25px; left: 100px;"></div>
`;
}
innerHTML += `</div>`;
el.innerHTML = innerHTML;
workspace.appendChild(el);
node.el = el;
if (node.type === 'INPUT') {
el.querySelector('.switch').addEventListener('click', (e) => {
const dist = Math.hypot(e.clientX - clickStartX, e.clientY - clickStartY);
if (isDraggingNode || dist > 3) {
e.preventDefault();
} else {
node.value = !node.value;
el.querySelector('.switch').classList.toggle('active-sim', node.value);
el.querySelector('.slider').style.background = node.value ? 'rgba(40,240,122,.25)' : '';
el.querySelector('.slider').style.borderColor = node.value ? 'rgba(40,240,122,.30)' : '';
el.querySelector('.slider').innerHTML = node.value ? `<style>#${node.id} .slider::before { transform: translateX(28px); }</style>` : '';
runSimulation();
}
});
}
return el;
}
function spawnNode(type, gateType = null, dropX = null, dropY = null) {
let label = '';
if (type === 'INPUT') label = getNextInputLabel();
if (type === 'OUTPUT') label = getNextOutputLabel();
if (type === 'GATE') label = gateType;
const id = `node_${nextNodeId++}`;
const offset = Math.floor(Math.random() * 40);
const x = dropX !== null ? dropX : (type === 'INPUT' ? 50 : (type === 'OUTPUT' ? 600 : 300) + offset);
const y = dropY !== null ? dropY : 150 + offset;
const node = { id, type, gateType, label, x, y, value: false, el: null };
nodes[id] = node;
createNodeElement(node);
runSimulation();
}
/* --- Global Interaction Handlers --- */
workspace.addEventListener('mousedown', (e) => {
clickStartX = e.clientX;
clickStartY = e.clientY;
const port = e.target.closest('.lg-port');
if (port) {
const nodeEl = port.closest('.lg-node');
const portId = port.dataset.port;
if (portId.startsWith('in')) {
const existingIdx = connections.findIndex(c => c.toNode === nodeEl.dataset.id && c.toPort === portId.replace('in', ''));
if (existingIdx !== -1) {
connections.splice(existingIdx, 1);
runSimulation();
return;
}
}
if (portId === 'out') {
const coords = getPortCoords(nodeEl.dataset.id, 'out');
wiringStart = { node: nodeEl.dataset.id, port: portId, x: coords.x, y: coords.y };
tempWirePath = { x: coords.x, y: coords.y };
return;
}
}
const wire = e.target.closest('.lg-wire');
if (wire && wire.dataset.connId) {
clearSelection();
selectedWireId = wire.dataset.connId;
renderWires();
e.stopPropagation();
return;
}
const nodeEl = e.target.closest('.lg-node');
if (nodeEl) {
clearSelection();
selectedNodeId = nodeEl.dataset.id;
nodeEl.classList.add('selected');
isDraggingNode = nodeEl.dataset.id;
const rect = nodeEl.getBoundingClientRect();
dragOffset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
return;
}
clearSelection();
});
window.addEventListener('mousemove', (e) => {
const wsRect = workspace.getBoundingClientRect();
if (isDraggingNode) {
const node = nodes[isDraggingNode];
let newX = e.clientX - wsRect.left - dragOffset.x;
let newY = e.clientY - wsRect.top - dragOffset.y;
node.x = Math.max(10, Math.min(newX, wsRect.width - 80));
node.y = Math.max(20, Math.min(newY, wsRect.height - 60));
updateNodePositions();
}
if (wiringStart) {
tempWirePath = {
x: e.clientX - wsRect.left,
y: e.clientY - wsRect.top
};
renderWires();
}
});
window.addEventListener('mouseup', (e) => {
isDraggingNode = null;
if (wiringStart) {
const port = e.target.closest('.lg-port');
if (port && port.dataset.port.startsWith('in')) {
const targetNodeId = port.closest('.lg-node').dataset.id;
const targetPortId = port.dataset.port.replace('in', '');
if (targetNodeId !== wiringStart.node) {
connections = connections.filter(c => !(c.toNode === targetNodeId && c.toPort === targetPortId));
connections.push({
id: `conn_${nextWireId++}`,
fromNode: wiringStart.node,
fromPort: 'out',
toNode: targetNodeId,
toPort: targetPortId
});
}
}
wiringStart = null;
tempWirePath = null;
runSimulation();
}
});
/* --- Deletion --- */
window.addEventListener('keydown', (e) => {
if (e.key === 'Delete' || e.key === 'Backspace') {
if (selectedWireId) {
connections = connections.filter(c => c.id !== selectedWireId);
clearSelection();
runSimulation();
}
else if (selectedNodeId) {
connections = connections.filter(c => c.fromNode !== selectedNodeId && c.toNode !== selectedNodeId);
if (nodes[selectedNodeId] && nodes[selectedNodeId].el) {
workspace.removeChild(nodes[selectedNodeId].el);
}
delete nodes[selectedNodeId];
clearSelection();
runSimulation();
}
}
});
/* --- Drag and Drop --- */
workspace.addEventListener('dragover', (e) => { e.preventDefault(); });
workspace.addEventListener('drop', (e) => {
e.preventDefault();
const spawnType = e.dataTransfer.getData('spawnType');
if (spawnType) {
const gateType = e.dataTransfer.getData('gateType');
const wsRect = workspace.getBoundingClientRect();
const x = e.clientX - wsRect.left - 40;
const y = e.clientY - wsRect.top - 30;
spawnNode(spawnType, gateType || null, x, y);
}
});
/* --- Init --- */
btnClearBoard?.addEventListener('click', () => {
workspace.querySelectorAll('.lg-node').forEach(el => el.remove());
nodes = {};
connections = [];
runSimulation();
});
toolboxToggle?.addEventListener("click", () => {
const isCollapsed = logicPage?.classList.contains("toolboxCollapsed");
logicPage.classList.toggle("toolboxCollapsed", !isCollapsed);
toolboxToggle?.setAttribute("aria-expanded", isCollapsed ? "true" : "false");
setTimeout(renderWires, 450);
});
initToolbox();
// Starts completely blank as requested!
})();

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" width="115" height="48"><path fill="#17191E" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="url(#a)" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="#17191E" d="M.02 30.31s4.02-1.95 8.05-1.95l3.04-9.4c.11-.45.44-.76.82-.76.37 0 .7.31.82.76l3.04 9.4c4.77 0 8.05 1.95 8.05 1.95L17 11.71c-.2-.56-.53-.91-.98-.91H7.83c-.44 0-.76.35-.97.9L.02 30.31Zm42.37-5.97c0 1.64-2.05 2.62-4.88 2.62-1.85 0-2.5-.45-2.5-1.41 0-1 .8-1.49 2.65-1.49 1.67 0 3.09.03 4.73.23v.05Zm.03-2.04a21.37 21.37 0 0 0-4.37-.36c-5.32 0-7.82 1.25-7.82 4.18 0 3.04 1.71 4.2 5.68 4.2 3.35 0 5.63-.84 6.46-2.92h.14c-.03.5-.05 1-.05 1.4 0 1.07.18 1.16 1.06 1.16h4.15a16.9 16.9 0 0 1-.36-4c0-1.67.06-2.93.06-4.62 0-3.45-2.07-5.64-8.56-5.64-2.8 0-5.9.48-8.26 1.19.22.93.54 2.83.7 4.06 2.04-.96 4.95-1.37 7.2-1.37 3.11 0 3.97.71 3.97 2.15v.57Zm11.37 3c-.56.07-1.33.07-2.12.07-.83 0-1.6-.03-2.12-.1l-.02.58c0 2.85 1.87 4.52 8.45 4.52 6.2 0 8.2-1.64 8.2-4.55 0-2.74-1.33-4.09-7.2-4.39-4.58-.2-4.99-.7-4.99-1.28 0-.66.59-1 3.65-1 3.18 0 4.03.43 4.03 1.35v.2a46.13 46.13 0 0 1 4.24.03l.02-.55c0-3.36-2.8-4.46-8.2-4.46-6.08 0-8.13 1.49-8.13 4.39 0 2.6 1.64 4.23 7.48 4.48 4.3.14 4.77.62 4.77 1.28 0 .7-.7 1.03-3.71 1.03-3.47 0-4.35-.48-4.35-1.47v-.13Zm19.82-12.05a17.5 17.5 0 0 1-6.24 3.48c.03.84.03 2.4.03 3.24l1.5.02c-.02 1.63-.04 3.6-.04 4.9 0 3.04 1.6 5.32 6.58 5.32 2.1 0 3.5-.23 5.23-.6a43.77 43.77 0 0 1-.46-4.13c-1.03.34-2.34.53-3.78.53-2 0-2.82-.55-2.82-2.13 0-1.37 0-2.65.03-3.84 2.57.02 5.13.07 6.64.11-.02-1.18.03-2.9.1-4.04-2.2.04-4.65.07-6.68.07l.07-2.93h-.16Zm13.46 6.04a767.33 767.33 0 0 1 .07-3.18H82.6c.07 1.96.07 3.98.07 6.92 0 2.95-.03 4.99-.07 6.93h5.18c-.09-1.37-.11-3.68-.11-5.65 0-3.1 1.26-4 4.12-4 1.33 0 2.28.16 3.1.46.03-1.16.26-3.43.4-4.43-.86-.25-1.81-.41-2.96-.41-2.46-.03-4.26.98-5.1 3.38l-.17-.02Zm22.55 3.65c0 2.5-1.8 3.66-4.64 3.66-2.81 0-4.61-1.1-4.61-3.66s1.82-3.52 4.61-3.52c2.82 0 4.64 1.03 4.64 3.52Zm4.71-.11c0-4.96-3.87-7.18-9.35-7.18-5.5 0-9.23 2.22-9.23 7.18 0 4.94 3.49 7.59 9.21 7.59 5.77 0 9.37-2.65 9.37-7.6Z"/><defs><linearGradient id="a" x1="6.33" x2="19.43" y1="40.8" y2="34.6" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient></defs></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="1024" fill="none"><path fill="url(#a)" fill-rule="evenodd" d="M-217.58 475.75c91.82-72.02 225.52-29.38 341.2-44.74C240 415.56 372.33 315.14 466.77 384.9c102.9 76.02 44.74 246.76 90.31 366.31 29.83 78.24 90.48 136.14 129.48 210.23 57.92 109.99 169.67 208.23 155.9 331.77-13.52 121.26-103.42 264.33-224.23 281.37-141.96 20.03-232.72-220.96-374.06-196.99-151.7 25.73-172.68 330.24-325.85 315.72-128.6-12.2-110.9-230.73-128.15-358.76-12.16-90.14 65.87-176.25 44.1-264.57-26.42-107.2-167.12-163.46-176.72-273.45-10.15-116.29 33.01-248.75 124.87-320.79Z" clip-rule="evenodd" style="opacity:.154"/><path fill="url(#b)" fill-rule="evenodd" d="M1103.43 115.43c146.42-19.45 275.33-155.84 413.5-103.59 188.09 71.13 409 212.64 407.06 413.88-1.94 201.25-259.28 278.6-414.96 405.96-130 106.35-240.24 294.39-405.6 265.3-163.7-28.8-161.93-274.12-284.34-386.66-134.95-124.06-436-101.46-445.82-284.6-9.68-180.38 247.41-246.3 413.54-316.9 101.01-42.93 207.83 21.06 316.62 6.61Z" clip-rule="evenodd" style="opacity:.154"/><defs><linearGradient id="b" x1="373" x2="1995.44" y1="1100" y2="118.03" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient><linearGradient id="a" x1="107.37" x2="1130.66" y1="1993.35" y2="1026.31" gradientUnits="userSpaceOnUse"><stop stop-color="#3245FF"/><stop offset="1" stop-color="#BC52EE"/></linearGradient></defs></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,29 +0,0 @@
<footer class="siteFooter">
<div class="inner">
<div class="title">Computer Science Concept Simulators</div>
<div class="meta">
© 2025 Computing:Box · Created with 💗 by Mr Lyall<br />
Powered by ADCM Networks
</div>
</div>
</footer>
<style>
.siteFooter{
border-top: 1px solid rgba(255,255,255,.10);
background: rgba(0,0,0,.10);
}
.inner{
max-width:1200px;
margin:0 auto;
padding: 18px 20px;
color: rgba(255,255,255,.65);
font-size: 12px;
line-height: 1.6;
}
.title{
color: rgba(255,255,255,.80);
font-weight: 800;
margin-bottom: 6px;
}
</style>

View File

@@ -1,57 +0,0 @@
<header class="siteHeader">
<div class="inner">
<div class="brand">
<a href="/">Computing:Box</a>
</div>
<nav class="nav">
<a href="/binary">Binary</a>
<a href="/hexadecimal">Hexadecimal</a>
<a href="/hex-colours">Hex Colours</a>
<a href="/logic-gates">Logic Gates</a>
<a href="/about">About</a>
</nav>
</div>
</header>
<style>
.siteHeader{
height: 64px;
display:flex;
align-items:center;
border-bottom: 1px solid rgba(255,255,255,.10);
background: rgba(0,0,0,.10);
backdrop-filter: blur(8px);
}
.inner{
max-width: 1200px;
width: 100%;
margin: 0 auto;
padding: 0 20px;
display:flex;
align-items:center;
justify-content: space-between;
gap: 18px;
}
.brand a{
color:#fff;
text-decoration:none;
font-weight: 800;
letter-spacing: .02em;
}
.nav{
display:flex;
gap: 16px;
flex-wrap: wrap;
}
.nav a{
color: rgba(255,255,255,.78);
text-decoration:none;
font-weight: 700;
font-size: 13px;
letter-spacing:.02em;
}
.nav a:hover{
color:#fff;
}
</style>

View File

@@ -1,210 +0,0 @@
---
import astroLogo from '../assets/astro.svg';
import background from '../assets/background.svg';
---
<div id="container">
<img id="background" src={background.src} alt="" fetchpriority="high" />
<main>
<section id="hero">
<a href="https://astro.build"
><img src={astroLogo.src} width="115" height="48" alt="Astro Homepage" /></a
>
<h1>
To get started, open the <code><pre>src/pages</pre></code> directory in your project.
</h1>
<section id="links">
<a class="button" href="https://docs.astro.build">Read our docs</a>
<a href="https://astro.build/chat"
>Join our Discord <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"
><path
fill="currentColor"
d="M107.7 8.07A105.15 105.15 0 0 0 81.47 0a72.06 72.06 0 0 0-3.36 6.83 97.68 97.68 0 0 0-29.11 0A72.37 72.37 0 0 0 45.64 0a105.89 105.89 0 0 0-26.25 8.09C2.79 32.65-1.71 56.6.54 80.21a105.73 105.73 0 0 0 32.17 16.15 77.7 77.7 0 0 0 6.89-11.11 68.42 68.42 0 0 1-10.85-5.18c.91-.66 1.8-1.34 2.66-2a75.57 75.57 0 0 0 64.32 0c.87.71 1.76 1.39 2.66 2a68.68 68.68 0 0 1-10.87 5.19 77 77 0 0 0 6.89 11.1 105.25 105.25 0 0 0 32.19-16.14c2.64-27.38-4.51-51.11-18.9-72.15ZM42.45 65.69C36.18 65.69 31 60 31 53s5-12.74 11.43-12.74S54 46 53.89 53s-5.05 12.69-11.44 12.69Zm42.24 0C78.41 65.69 73.25 60 73.25 53s5-12.74 11.44-12.74S96.23 46 96.12 53s-5.04 12.69-11.43 12.69Z"
></path></svg
>
</a>
</section>
</section>
</main>
<a href="https://astro.build/blog/astro-5/" id="news" class="box">
<svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg"
><path
d="M24.667 12c1.333 1.414 2 3.192 2 5.334 0 4.62-4.934 5.7-7.334 12C18.444 28.567 18 27.456 18 26c0-4.642 6.667-7.053 6.667-14Zm-5.334-5.333c1.6 1.65 2.4 3.43 2.4 5.333 0 6.602-8.06 7.59-6.4 17.334C13.111 27.787 12 25.564 12 22.666c0-4.434 7.333-8 7.333-16Zm-6-5.333C15.111 3.555 16 5.556 16 7.333c0 8.333-11.333 10.962-5.333 22-3.488-.774-6-4-6-8 0-8.667 8.666-10 8.666-20Z"
fill="#111827"></path></svg
>
<h2>What's New in Astro 5.0?</h2>
<p>
From content layers to server islands, click to learn more about the new features and
improvements in Astro 5.0
</p>
</a>
</div>
<style>
#background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
filter: blur(100px);
}
#container {
font-family: Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;
height: 100%;
}
main {
height: 100%;
display: flex;
justify-content: center;
}
#hero {
display: flex;
align-items: start;
flex-direction: column;
justify-content: center;
padding: 16px;
}
h1 {
font-size: 22px;
margin-top: 0.25em;
}
#links {
display: flex;
gap: 16px;
}
#links a {
display: flex;
align-items: center;
padding: 10px 12px;
color: #111827;
text-decoration: none;
transition: color 0.2s;
}
#links a:hover {
color: rgb(78, 80, 86);
}
#links a svg {
height: 1em;
margin-left: 8px;
}
#links a.button {
color: white;
background: linear-gradient(83.21deg, #3245ff 0%, #bc52ee 100%);
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.12),
inset 0 -2px 0 rgba(0, 0, 0, 0.24);
border-radius: 10px;
}
#links a.button:hover {
color: rgb(230, 230, 230);
box-shadow: none;
}
pre {
font-family:
ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono',
monospace;
font-weight: normal;
background: linear-gradient(14deg, #d83333 0%, #f041ff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0;
}
h2 {
margin: 0 0 1em;
font-weight: normal;
color: #111827;
font-size: 20px;
}
p {
color: #4b5563;
font-size: 16px;
line-height: 24px;
letter-spacing: -0.006em;
margin: 0;
}
code {
display: inline-block;
background:
linear-gradient(66.77deg, #f3cddd 0%, #f5cee7 100%) padding-box,
linear-gradient(155deg, #d83333 0%, #f041ff 18%, #f5cee7 45%) border-box;
border-radius: 8px;
border: 1px solid transparent;
padding: 6px 8px;
}
.box {
padding: 16px;
background: rgba(255, 255, 255, 1);
border-radius: 16px;
border: 1px solid white;
}
#news {
position: absolute;
bottom: 16px;
right: 16px;
max-width: 300px;
text-decoration: none;
transition: background 0.2s;
backdrop-filter: blur(50px);
}
#news:hover {
background: rgba(255, 255, 255, 0.55);
}
@media screen and (max-height: 368px) {
#news {
display: none;
}
}
@media screen and (max-width: 768px) {
#container {
display: flex;
flex-direction: column;
}
#hero {
display: block;
padding-top: 10%;
}
#links {
flex-wrap: wrap;
}
#links a.button {
padding: 14px 18px;
}
#news {
right: 16px;
left: 16px;
bottom: 2.5rem;
max-width: 100%;
}
h1 {
line-height: 1.5;
}
}
</style>

View File

@@ -1,104 +0,0 @@
---
import "./hex/hex-simulator.css";
---
<section class="hex-sim" data-hex-sim>
<div class="hex-main">
<div class="hex-readout">
<div class="hex-label">DENARY</div>
<div class="hex-number" data-out="denary">0</div>
<div class="hex-label hex-mt">HEXADECIMAL</div>
<div class="hex-number hex-number--small" data-out="hex">00</div>
<div class="hex-label hex-mt">BINARY</div>
<div class="hex-number hex-number--tiny" data-out="bin">0000 0000</div>
</div>
<div class="hex-divider"></div>
<div class="hex-digits" data-out="digitsRow"></div>
</div>
<!-- Toolbox button -->
<button class="hex-toolbox-btn" type="button" data-action="toggleToolbox" aria-controls="hex-toolbox" aria-expanded="true">
<span class="hex-toolbox-icon" aria-hidden="true">
<!-- toolbox icon -->
<svg viewBox="0 0 24 24" width="18" height="18" fill="none">
<path d="M9 7V6a3 3 0 0 1 6 0v1" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<path d="M4 9h16l-1.3 10.4A2 2 0 0 1 16.7 21H7.3a2 2 0 0 1-1.98-1.6L4 9Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/>
<path d="M10 13h4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</span>
TOOLBOX
</button>
<!-- Toolbox panel -->
<aside class="hex-toolbox is-open" id="hex-toolbox" data-out="toolbox">
<div class="hex-panel">
<div class="hex-panel-title">SETTINGS</div>
<div class="hex-setting-title">HEX DIGIT WIDTH</div>
<div class="hex-width">
<button class="hex-btn hex-btn--square" type="button" data-action="digitsMinus"></button>
<div class="hex-width-readout">
<div class="hex-width-label">DIGITS</div>
<div class="hex-width-number" data-out="digitsCount">2</div>
</div>
<button class="hex-btn hex-btn--square" type="button" data-action="digitsPlus">+</button>
</div>
<div class="hex-hint" data-out="bitsHint">= 8 bits</div>
</div>
<div class="hex-panel">
<div class="hex-panel-title">CUSTOM NUMBER</div>
<div class="hex-grid-2">
<button class="hex-btn hex-btn--green" type="button" data-action="customHex">Custom Hexadecimal</button>
<button class="hex-btn hex-btn--green" type="button" data-action="customDenary">Custom Denary</button>
</div>
<!-- Custom Binary + Random on SAME row, same size -->
<div class="hex-grid-2 hex-mt-sm">
<button class="hex-btn hex-btn--green" type="button" data-action="customBinary">Custom Binary</button>
<button class="hex-btn hex-btn--wide hex-btn--random" type="button" data-action="random" data-random>Random</button>
</div>
<div class="hex-tiny-note">RANDOM RUNS BRIEFLY THEN STOPS AUTOMATICALLY.</div>
</div>
<div class="hex-panel">
<div class="hex-panel-title">TOOLS</div>
<div class="hex-tools-top">
<button class="hex-btn hex-btn--square hex-btn--red" type="button" data-action="decrement" title="Decrement">▼</button>
<button class="hex-btn hex-btn--square hex-btn--green2" type="button" data-action="increment" title="Increment">▲</button>
</div>
<button class="hex-btn hex-btn--wide hex-btn--reset" type="button" data-action="reset">Reset</button>
</div>
</aside>
<!-- Custom number dialog -->
<dialog class="hex-dialog" data-out="dialog">
<div class="hex-dialog-card">
<div class="hex-dialog-title" data-out="dialogTitle">Custom</div>
<input class="hex-dialog-input hex-font-mono" data-out="dialogInput" />
<div class="hex-dialog-hint" data-out="dialogHint"></div>
<div class="hex-dialog-error" data-out="dialogError" aria-live="polite"></div>
<div class="hex-dialog-actions">
<button class="hex-btn" type="button" data-action="dialogCancel">Cancel</button>
<button class="hex-btn hex-btn--green" type="button" data-action="dialogApply">Apply</button>
</div>
</div>
</dialog>
<script type="module" src="/src/components/simulators/hex/hex-simulator.ts"></script>
</section>

View File

@@ -1,346 +0,0 @@
/* ================= Fonts to match Binary ================= */
/* Adjust paths to wherever you store fonts (commonly /public/fonts/...) */
@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;
}
@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;
}
.hex-sim {
min-height: 100vh;
background: #14151c;
color: #e7e8ee;
padding: 28px;
}
.hex-font-number { font-family: "DSEG7Classic", ui-monospace, monospace; }
.hex-font-mono { font-family: "SevenSegment", ui-monospace, monospace; }
.hex-main { max-width: 1200px; margin: 0 auto; width: 100%; padding-top: 40px; }
.hex-readout { text-align: center; }
.hex-label {
font-family: "SevenSegment", ui-sans-serif, system-ui;
font-size: 12px;
letter-spacing: 2px;
opacity: 0.7;
}
.hex-mt { margin-top: 12px; }
.hex-number {
font-family: "DSEG7Classic", ui-monospace, monospace;
font-size: 76px;
line-height: 1;
font-weight: 400;
color: #46ff8a;
text-shadow: 0 0 18px rgba(70,255,138,0.18);
}
.hex-number--small { font-size: 64px; }
.hex-number--tiny { font-size: 54px; letter-spacing: 6px; }
.hex-divider {
margin: 26px auto 18px;
height: 1px;
width: min(760px, 90%);
background: rgba(255,255,255,0.10);
}
/* ================= Main digit columns ================= */
.hex-digits {
margin-top: 18px;
display: flex;
justify-content: center;
gap: 18px;
flex-wrap: wrap;
}
.hex-digit-col {
width: 160px;
border-radius: 18px;
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.10);
padding: 12px;
display: grid;
gap: 10px;
justify-items: center;
}
.hex-digit-controls {
width: 100%;
display: flex;
justify-content: center;
gap: 10px;
}
.hex-digit-char {
font-size: 64px;
line-height: 1;
color: #46ff8a;
text-shadow: 0 0 18px rgba(70,255,138,0.18);
}
.hex-digit-place {
font-family: "SevenSegment", ui-monospace, monospace;
opacity: 0.65;
font-size: 14px;
letter-spacing: 1px;
}
/* ================= Bulbs (brightness changes) ================= */
.hex-bulbs {
width: 100%;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
align-items: end;
}
.hex-bulb {
display: grid;
justify-items: center;
gap: 6px;
opacity: 0.35;
filter: grayscale(30%);
transition: opacity 160ms ease, filter 160ms ease;
}
.hex-bulb .hex-bulb-cap {
width: 18px;
height: 18px;
border-radius: 999px;
background: rgba(255,255,255,0.22);
border: 1px solid rgba(255,255,255,0.14);
}
.hex-bulb .hex-bulb-glow {
width: 18px;
height: 10px;
border-radius: 999px;
background: rgba(70,255,138,0.0);
box-shadow: 0 0 0 rgba(70,255,138,0.0);
transition: background 160ms ease, box-shadow 160ms ease;
}
.hex-bulb .hex-bulb-label {
font-family: "SevenSegment", ui-monospace, monospace;
font-size: 12px;
opacity: 0.8;
}
.hex-bulb.is-on {
opacity: 1;
filter: none;
}
.hex-bulb.is-on .hex-bulb-cap {
background: rgba(255,255,255,0.35);
}
.hex-bulb.is-on .hex-bulb-glow {
background: rgba(70,255,138,0.25);
box-shadow: 0 0 18px rgba(70,255,138,0.35);
}
/* ================= Buttons (toolbox style reused everywhere) ================= */
.hex-btn {
padding: 10px 12px;
border-radius: 14px;
border: 1px solid rgba(255,255,255,0.14);
background: rgba(255,255,255,0.06);
color: #e7e8ee;
font-weight: 800;
cursor: pointer;
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
}
.hex-btn:hover { background: rgba(255,255,255,0.10); }
.hex-btn--square {
width: 48px;
height: 48px;
padding: 0;
display: grid;
place-items: center;
font-size: 18px;
}
.hex-btn--wide { width: 100%; }
.hex-btn--green {
background: rgba(46, 200, 120, 0.18);
border-color: rgba(46,200,120,0.35);
}
.hex-btn--green:hover { background: rgba(46, 200, 120, 0.26); }
.hex-btn--green2 {
background: rgba(46, 200, 120, 0.18);
border-color: rgba(46,200,120,0.35);
}
.hex-btn--red {
background: rgba(220, 60, 70, 0.18);
border-color: rgba(220,60,70,0.35);
}
/* Random = green pulse while running */
.hex-btn--random.is-running {
border-color: rgba(80, 255, 160, 0.55);
background: rgba(46, 200, 120, 0.22);
box-shadow: 0 0 18px rgba(80, 255, 160, 0.35);
animation: hexPulseGreen 900ms ease-in-out infinite;
}
@keyframes hexPulseGreen {
0%, 100% { box-shadow: 0 0 14px rgba(80, 255, 160, 0.25); }
50% { box-shadow: 0 0 26px rgba(80, 255, 160, 0.45); }
}
/* Reset = red background + pulse on hover */
.hex-btn--reset:hover {
background: rgba(220, 60, 70, 0.28);
border-color: rgba(255, 80, 90, 0.55);
animation: hexPulseRed 900ms ease-in-out infinite;
}
@keyframes hexPulseRed {
0%, 100% { box-shadow: 0 0 12px rgba(255, 80, 90, 0.20); }
50% { box-shadow: 0 0 22px rgba(255, 80, 90, 0.38); }
}
/* ================= Toolbox button + panel (slide) ================= */
.hex-toolbox-btn {
position: fixed;
top: 88px;
right: 28px;
z-index: 30;
display: inline-flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
border-radius: 14px;
border: 1px solid rgba(255,255,255,0.14);
background: rgba(255,255,255,0.06);
color: #e7e8ee;
font-weight: 800;
letter-spacing: 1px;
cursor: pointer;
}
.hex-toolbox-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
color: #ff4fa6;
filter: drop-shadow(0 0 10px rgba(255,79,166,0.35));
}
.hex-toolbox {
position: fixed;
top: 140px;
right: 28px;
width: 340px;
display: grid;
gap: 14px;
z-index: 25;
transform: translateX(0);
opacity: 1;
transition: transform 220ms ease, opacity 220ms ease;
}
.hex-toolbox:not(.is-open) {
transform: translateX(380px);
opacity: 0;
pointer-events: none;
}
.hex-panel {
border-radius: 16px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.10);
padding: 14px;
}
.hex-panel-title {
font-family: "SevenSegment", ui-sans-serif, system-ui;
font-size: 12px;
letter-spacing: 2px;
opacity: 0.7;
margin-bottom: 10px;
}
.hex-setting-title { font-weight: 900; opacity: 0.9; margin-bottom: 10px; }
.hex-width {
display: grid;
grid-template-columns: 48px 1fr 48px;
gap: 10px;
align-items: center;
}
.hex-width-readout {
border-radius: 14px;
background: rgba(0,0,0,0.22);
border: 1px solid rgba(255,255,255,0.10);
padding: 10px 12px;
display: flex;
justify-content: space-between;
align-items: baseline;
}
.hex-width-label {
font-family: "SevenSegment", ui-sans-serif, system-ui;
opacity: 0.7;
font-weight: 800;
letter-spacing: 1px;
font-size: 12px;
}
.hex-width-number { font-size: 30px; font-weight: 900; color: #46ff8a; }
.hex-hint { margin-top: 8px; opacity: 0.65; font-size: 12px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
.hex-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.hex-mt-sm { margin-top: 10px; }
.hex-tools-top { display: flex; gap: 10px; justify-content: center; margin-bottom: 10px; }
.hex-tiny-note { margin-top: 8px; font-size: 11px; opacity: 0.6; letter-spacing: 1px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
/* ================= Dialog ================= */
.hex-dialog { border: none; padding: 0; background: transparent; }
.hex-dialog::backdrop { background: rgba(0,0,0,0.55); }
.hex-dialog-card {
width: min(560px, 92vw);
border-radius: 18px;
background: #1a1b24;
border: 1px solid rgba(255,255,255,0.12);
padding: 16px;
color: #e7e8ee;
}
.hex-dialog-title { font-weight: 900; letter-spacing: 1px; margin-bottom: 10px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
.hex-dialog-input {
width: 100%;
padding: 12px 12px;
border-radius: 14px;
border: 1px solid rgba(255,255,255,0.14);
background: rgba(0,0,0,0.25);
color: #e7e8ee;
font-size: 18px;
}
.hex-dialog-hint { margin-top: 10px; opacity: 0.7; font-size: 13px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
.hex-dialog-error { margin-top: 8px; font-size: 13px; color: #ff6b6b; min-height: 18px; font-family: "SevenSegment", ui-sans-serif, system-ui; }
.hex-dialog-actions { margin-top: 14px; display: flex; gap: 10px; justify-content: flex-end; }
@media (max-width: 900px) {
.hex-toolbox { width: min(360px, 92vw); right: 16px; }
.hex-toolbox-btn { right: 16px; }
.hex-number { font-size: 60px; }
.hex-number--tiny { font-size: 40px; letter-spacing: 4px; }
}

View File

@@ -1,232 +0,0 @@
type DialogMode = "hex" | "den" | "bin";
const root = document.querySelector<HTMLElement>("[data-hex-sim]");
if (!root) throw new Error("Hex simulator root not found");
const outDen = root.querySelector<HTMLElement>('[data-out="denary"]')!;
const outHex = root.querySelector<HTMLElement>('[data-out="hex"]')!;
const outBin = root.querySelector<HTMLElement>('[data-out="bin"]')!;
const outDigitsRow = root.querySelector<HTMLElement>('[data-out="digitsRow"]')!;
const toolbox = root.querySelector<HTMLElement>('[data-out="toolbox"]')!;
const toolboxBtn = root.querySelector<HTMLButtonElement>('[data-action="toggleToolbox"]')!;
const digitsCount = root.querySelector<HTMLElement>('[data-out="digitsCount"]')!;
const bitsHint = root.querySelector<HTMLElement>('[data-out="bitsHint"]')!;
const randomBtn = root.querySelector<HTMLButtonElement>("[data-random]")!;
const dialog = root.querySelector<HTMLDialogElement>('[data-out="dialog"]')!;
const dialogTitle = root.querySelector<HTMLElement>('[data-out="dialogTitle"]')!;
const dialogInput = root.querySelector<HTMLInputElement>('[data-out="dialogInput"]')!;
const dialogHint = root.querySelector<HTMLElement>('[data-out="dialogHint"]')!;
const dialogError = root.querySelector<HTMLElement>('[data-out="dialogError"]')!;
let digits = 2; // 1..8
let value = 0; // unsigned denary
let randomTimer: number | null = null;
let dialogMode: DialogMode | null = null;
const clamp = (n: number, min: number, max: number) => Math.min(max, Math.max(min, n));
const maxForDigits = (d: number) => (16 ** d) - 1;
const padHex = (n: number, d: number) => n.toString(16).toUpperCase().padStart(d, "0");
const padBin = (n: number, b: number) => n.toString(2).padStart(b, "0");
const groupBin = (b: string) => b.replace(/(.{4})/g, "$1 ").trim();
function stopRandom(): void {
if (randomTimer !== null) window.clearInterval(randomTimer);
randomTimer = null;
randomBtn.classList.remove("is-running");
}
function startRandom(): void {
stopRandom();
const max = maxForDigits(digits);
const start = Date.now();
randomBtn.classList.add("is-running");
randomTimer = window.setInterval(() => {
value = Math.floor(Math.random() * (max + 1));
render();
if (Date.now() - start > 1600) stopRandom();
}, 90);
}
function render(): void {
const bits = digits * 4;
digitsCount.textContent = String(digits);
bitsHint.textContent = `= ${bits} bits`;
outDen.textContent = String(value);
outHex.textContent = padHex(value, digits);
outBin.textContent = groupBin(padBin(value, bits));
renderDigitsRow();
}
function renderDigitsRow(): void {
const hex = padHex(value, digits);
outDigitsRow.innerHTML = "";
for (let i = 0; i < digits; i++) {
const pow = digits - 1 - i;
const placeValue = 16 ** pow;
const digitChar = hex[i];
const digitVal = parseInt(digitChar, 16);
const nibbleBits = [(digitVal >> 3) & 1, (digitVal >> 2) & 1, (digitVal >> 1) & 1, digitVal & 1]; // 8 4 2 1
const col = document.createElement("div");
col.className = "hex-digit-col";
col.innerHTML = `
<div class="hex-digit-controls">
<button class="hex-btn hex-btn--square hex-btn--green2" type="button" data-action="digitUp" data-i="${i}" title="Increase">▲</button>
<button class="hex-btn hex-btn--square hex-btn--red" type="button" data-action="digitDown" data-i="${i}" title="Decrease">▼</button>
</div>
<div class="hex-digit-char hex-font-number">${digitChar}</div>
<!-- bulbs: brightness changes based on nibble bits -->
<div class="hex-bulbs" aria-label="Nibble bits">
${[8,4,2,1].map((w, idx) => {
const on = nibbleBits[idx] === 1;
return `
<div class="hex-bulb ${on ? "is-on" : ""}">
<div class="hex-bulb-cap"></div>
<div class="hex-bulb-glow"></div>
<div class="hex-bulb-label">${w}</div>
</div>
`;
}).join("")}
</div>
<div class="hex-digit-place">${placeValue}</div>
`;
outDigitsRow.appendChild(col);
}
}
function openDialog(mode: DialogMode): void {
stopRandom();
dialogMode = mode;
dialogError.textContent = "";
dialogInput.value = "";
if (mode === "hex") {
dialogTitle.textContent = "Custom Hexadecimal";
dialogHint.textContent = `Enter 1${digits} hex digit(s) (09, AF).`;
dialogInput.placeholder = "A1";
dialogInput.inputMode = "text";
} else if (mode === "den") {
dialogTitle.textContent = "Custom Denary";
dialogHint.textContent = `Enter a whole number from 0 to ${maxForDigits(digits)}.`;
dialogInput.placeholder = "42";
dialogInput.inputMode = "numeric";
} else {
dialogTitle.textContent = "Custom Binary";
dialogHint.textContent = `Enter up to ${digits * 4} bit(s) using 0 and 1.`;
dialogInput.placeholder = "00101010";
dialogInput.inputMode = "text";
}
dialog.showModal();
window.setTimeout(() => dialogInput.focus(), 0);
}
function closeDialog(): void {
dialogMode = null;
dialogError.textContent = "";
if (dialog.open) dialog.close();
}
function applyDialog(): void {
const raw = (dialogInput.value || "").trim();
if (!dialogMode) return closeDialog();
if (raw.length === 0) return closeDialog();
const max = maxForDigits(digits);
const bits = digits * 4;
if (dialogMode === "hex") {
const v = raw.toUpperCase();
if (!/^[0-9A-F]+$/.test(v)) { dialogError.textContent = "Hex must use 09 and AF only."; return; }
if (v.length > digits) { dialogError.textContent = `Max length is ${digits} hex digit(s).`; return; }
value = clamp(parseInt(v, 16), 0, max);
render();
return closeDialog();
}
if (dialogMode === "den") {
if (!/^\d+$/.test(raw)) { dialogError.textContent = "Denary must be whole numbers only."; return; }
const n = Number(raw);
if (!Number.isFinite(n)) { dialogError.textContent = "Invalid number."; return; }
value = clamp(n, 0, max);
render();
return closeDialog();
}
// bin
if (!/^[01]+$/.test(raw)) { dialogError.textContent = "Binary must use 0 and 1 only."; return; }
if (raw.length > bits) { dialogError.textContent = `Max length is ${bits} bit(s).`; return; }
value = clamp(parseInt(raw, 2), 0, max);
render();
return closeDialog();
}
function applyDigitDelta(i: number, delta: number): void {
stopRandom();
const hexArr = padHex(value, digits).split("");
let v = parseInt(hexArr[i], 16);
v = (v + delta) % 16;
if (v < 0) v += 16;
hexArr[i] = v.toString(16).toUpperCase();
value = clamp(parseInt(hexArr.join(""), 16), 0, maxForDigits(digits));
render();
}
// dialog cancel / backdrop
dialog.addEventListener("cancel", (e) => { e.preventDefault(); closeDialog(); });
dialog.addEventListener("click", (e) => {
const card = dialog.querySelector(".hex-dialog-card");
if (card && !card.contains(e.target as Node)) closeDialog();
});
dialogInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") applyDialog();
if (e.key === "Escape") closeDialog();
});
// main click handler
root.addEventListener("click", (e) => {
const btn = (e.target as HTMLElement).closest<HTMLElement>("[data-action]");
if (!btn) return;
const action = btn.getAttribute("data-action")!;
if (action === "toggleToolbox") {
toolbox.classList.toggle("is-open");
toolboxBtn.setAttribute("aria-expanded", toolbox.classList.contains("is-open") ? "true" : "false");
return;
}
if (action === "digitsMinus") { digits = clamp(digits - 1, 1, 8); value = clamp(value, 0, maxForDigits(digits)); return render(); }
if (action === "digitsPlus") { digits = clamp(digits + 1, 1, 8); value = clamp(value, 0, maxForDigits(digits)); return render(); }
if (action === "increment") { stopRandom(); value = clamp(value + 1, 0, maxForDigits(digits)); return render(); }
if (action === "decrement") { stopRandom(); value = clamp(value - 1, 0, maxForDigits(digits)); return render(); }
if (action === "reset") { stopRandom(); value = 0; return render(); }
if (action === "random") { return startRandom(); }
if (action === "customHex") return openDialog("hex");
if (action === "customDenary") return openDialog("den");
if (action === "customBinary") return openDialog("bin");
if (action === "dialogCancel") return closeDialog();
if (action === "dialogApply") return applyDialog();
if (action === "digitUp") return applyDigitDelta(Number(btn.getAttribute("data-i")), +1);
if (action === "digitDown") return applyDigitDelta(Number(btn.getAttribute("data-i")), -1);
});
render();

View File

@@ -1,118 +0,0 @@
---
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>
<style>
:root{
--nav-h: 108px; /* 3x-ish height */
--bg: #1f2027;
--text: #e8e8ee;
--muted: #a9acb8;
--line: rgba(255,255,255,.10);
}
body{
margin:0;
background:var(--bg);
color:var(--text);
}
.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: 2em;
height: 2em;
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{
max-width: 1400px;
margin: 0 auto;
}
</style>
</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>
</body>
</html>

View File

@@ -1,22 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>Astro Basics</title>
</head>
<body>
<slot />
</body>
</html>
<style>
html,
body {
margin: 0;
width: 100%;
height: 100%;
}
</style>

View File

@@ -1,115 +0,0 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import "../styles/binary.css";
---
<BaseLayout title="Binary Simulator">
<main class="wrap">
<!-- Toolbox toggle sits below navbar (navbar is in BaseLayout) -->
<button id="toolboxToggle" class="toolboxToggle" type="button" aria-expanded="true">
<span class="toolboxIcon" aria-hidden="true">🧰</span>
<span class="toolboxText">TOOLBOX</span>
</button>
<section class="topGrid">
<!-- LEFT -->
<div class="mainCol">
<div class="readout">
<div class="label">Denary</div>
<div id="denaryNumber" class="num denaryValue">0</div>
<div class="label">Binary</div>
<!-- NOTE: JS writes exact bit-width here, so initial value doesn't matter much -->
<div id="binaryNumber" class="num binaryValue">00000000</div>
</div>
<div class="divider"></div>
<section class="bitsWrap" aria-label="Bit switches">
<div class="bitsGrid" id="bitsGrid"></div>
</section>
</div>
<!-- RIGHT (Toolbox panel) -->
<aside id="toolboxPanel" class="panelCol" aria-label="Toolbox">
<!-- Settings -->
<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>
<!-- keep this on ONE line -->
<div class="toggleLabel" id="lblTwos">Two&apos;s&nbsp;complement</div>
</div>
<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="miniBtn" id="btnBitsUp" type="button" aria-label="Increase bits">+</button>
</div>
</div>
</div>
<!-- Custom Number -->
<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>
<!-- Tools -->
<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>
</main>
<script type="module" src="/src/scripts/binary.js"></script>
</BaseLayout>

View File

@@ -1,8 +0,0 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import HexSimulator from "../components/simulators/HexSimulator.astro";
---
<BaseLayout title="Hexadecimal | Computing:Box">
<HexSimulator />
</BaseLayout>

View File

@@ -1,11 +0,0 @@
---
import Welcome from '../components/Welcome.astro';
import Layout from '../layouts/Layout.astro';
// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
---
<Layout>
<Welcome />
</Layout>

View File

@@ -1,522 +0,0 @@
// src/scripts/binary.js
// Computing:Box — Binary page logic (Unsigned + Two's Complement)
(() => {
/* -----------------------------
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");
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 toolboxPanel = document.getElementById("toolboxPanel");
/* -----------------------------
STATE
----------------------------- */
let bitCount = clampInt(Number(bitsInput?.value ?? 8), 1, 64);
let bits = new Array(bitCount).fill(false);
let randomTimer = null;
// For responsive wrapping of the top binary display
let nibblesPerLine = null;
let wrapMeasureSpan = 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 % span) + span) % span;
unsignedBigIntToBits(v);
}
function updateModeHint() {
if (!modeHint) return;
modeHint.textContent = isTwosMode()
? "Tip: In twos complement, the left-most bit (MSB) represents a negative value."
: "Tip: In unsigned binary, all bits represent positive values.";
}
/* -----------------------------
TOP BINARY DISPLAY: responsive wrap by nibble count
----------------------------- */
function ensureWrapMeasurer() {
if (wrapMeasureSpan || !binaryEl) return;
wrapMeasureSpan = document.createElement("span");
wrapMeasureSpan.style.position = "absolute";
wrapMeasureSpan.style.visibility = "hidden";
wrapMeasureSpan.style.whiteSpace = "pre";
wrapMeasureSpan.style.pointerEvents = "none";
// Inherit font/letterspacing from binaryEl
wrapMeasureSpan.style.font = getComputedStyle(binaryEl).font;
wrapMeasureSpan.style.letterSpacing = getComputedStyle(binaryEl).letterSpacing;
document.body.appendChild(wrapMeasureSpan);
}
function computeNibblesPerLine() {
if (!binaryEl) return null;
ensureWrapMeasurer();
// Available width = width of the readout area (binaryEl parent)
const host = binaryEl.parentElement;
if (!host) return null;
const hostW = host.getBoundingClientRect().width;
if (!Number.isFinite(hostW) || hostW <= 0) return null;
// Measure one nibble including trailing space ("0000 ")
wrapMeasureSpan.textContent = "0000 ";
const nibbleW = wrapMeasureSpan.getBoundingClientRect().width || 1;
// Safety: keep at least 1 nibble per line
const max = Math.max(1, Math.floor(hostW / nibbleW));
return max;
}
function formatBinaryWrapped() {
// EXACT bitCount digits (no padding to 4)
let raw = "";
for (let i = bitCount - 1; i >= 0; i--) raw += bits[i] ? "1" : "0";
// If <= 4 bits, do NOT insert spaces/newlines at all
if (bitCount <= 4) return raw;
const groups = [];
for (let i = 0; i < raw.length; i += 4) {
groups.push(raw.slice(i, i + 4));
}
const perLine = nibblesPerLine ?? groups.length;
if (perLine >= groups.length) return groups.join(" ");
const lines = [];
for (let i = 0; i < groups.length; i += perLine) {
lines.push(groups.slice(i, i + perLine).join(" "));
}
return lines.join("\n");
}
function refreshBinaryWrap() {
const next = computeNibblesPerLine();
// Only update if it actually changes (prevents jitter)
if (next !== nibblesPerLine) nibblesPerLine = next;
updateReadout(); // re-render with new wrap
}
/* -----------------------------
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);
if (bitCount < 8) {
bitsGrid.style.setProperty("--cols", String(bitCount));
} else {
bitsGrid.style.removeProperty("--cols");
}
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">💡</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();
});
});
// bulb styling + 25% bigger (vs 26px previously)
for (let i = 0; i < bitCount; i++) {
const bulb = document.getElementById(`bulb-${i}`);
if (!bulb) continue;
bulb.style.width = "auto";
bulb.style.height = "auto";
bulb.style.border = "none";
bulb.style.background = "transparent";
bulb.style.borderRadius = "0";
bulb.style.boxShadow = "none";
bulb.style.opacity = "0.45";
bulb.style.fontSize = "32px";
bulb.style.lineHeight = "1";
bulb.style.display = "flex";
bulb.style.alignItems = "center";
bulb.style.justifyContent = "center";
bulb.style.filter = "grayscale(1)";
bulb.textContent = "💡";
}
// wrapping may change when bit width changes
refreshBinaryWrap();
updateUI();
}
/* -----------------------------
UI UPDATE
----------------------------- */
function updateBitLabels() {
for (let i = 0; i < bitCount; i++) {
const label = document.getElementById(`bitLabel-${i}`);
if (!label) continue;
if (isTwosMode() && i === bitCount - 1) {
// Keep on one line (CSS: white-space:nowrap)
label.textContent = `-${pow2Big(bitCount - 1).toString()}`;
} else {
label.textContent = pow2Big(i).toString();
}
}
}
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;
const on = bits[i] === true;
if (on) {
bulb.style.opacity = "1";
bulb.style.filter = "grayscale(0)";
bulb.style.textShadow = "0 0 18px rgba(255,216,107,.75), 0 0 30px rgba(255,216,107,.45)";
} else {
bulb.style.opacity = "0.45";
bulb.style.filter = "grayscale(1)";
bulb.style.textShadow = "none";
}
}
}
function updateReadout() {
if (!denaryEl || !binaryEl) return;
denaryEl.textContent = (isTwosMode() ? bitsToSignedBigIntTwos() : bitsToUnsignedBigInt()).toString();
binaryEl.textContent = formatBinaryWrapped();
}
function updateUI() {
updateModeHint();
updateBitLabels();
syncSwitchesToBits();
updateBulbs();
updateReadout();
}
/* -----------------------------
SET FROM INPUT
----------------------------- */
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;
}
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) return false;
if (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() {
if (isTwosMode()) {
// arithmetic right shift: keep MSB
const msb = bits[bitCount - 1];
for (let i = 0; i < bitCount - 1; i++) bits[i] = bits[i + 1];
bits[bitCount - 1] = msb;
} else {
for (let i = 0; i < bitCount - 1; i++) bits[i] = bits[i + 1];
bits[bitCount - 1] = false;
}
updateUI();
}
/* -----------------------------
CLEAR / INC / DEC
----------------------------- */
function clearAll() {
bits.fill(false);
updateUI();
}
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 runRandomBriefly() {
if (randomTimer) {
clearInterval(randomTimer);
randomTimer = null;
}
const start = Date.now();
const durationMs = 1125; // (your “~25% longer” vs 900ms)
const tickMs = 80;
randomTimer = setInterval(() => {
setRandomOnce();
if (Date.now() - start >= durationMs) {
clearInterval(randomTimer);
randomTimer = null;
}
}, tickMs);
}
/* -----------------------------
BIT WIDTH CONTROLS
----------------------------- */
function setBitWidth(n) {
buildBits(clampInt(n, 1, 64));
}
/* -----------------------------
TOOLBOX TOGGLE (simple open/close state)
----------------------------- */
function setToolboxOpen(open) {
document.body.classList.toggle("toolboxClosed", !open);
toolboxToggle?.setAttribute("aria-expanded", open ? "true" : "false");
refreshBinaryWrap(); // width changes when toolbox closes/opens
}
/* -----------------------------
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 isOpen = !document.body.classList.contains("toolboxClosed");
setToolboxOpen(!isOpen);
});
// Recompute wrapping live when the window size changes
let resizeT = null;
window.addEventListener("resize", () => {
if (resizeT) clearTimeout(resizeT);
resizeT = setTimeout(() => refreshBinaryWrap(), 60);
});
/* -----------------------------
INIT
----------------------------- */
updateModeHint();
buildBits(bitCount);
setToolboxOpen(true);
})();

View File

@@ -1,342 +0,0 @@
/*
Binary page styles (keeps the last-working simulator markup + binary.js).
Goals:
- Do NOT change any IDs/classes expected by src/scripts/binary.js
- Toolbox button toggles the ENTIRE right-hand column via body.toolboxClosed
- Fix toolbox button positioning (no overlap, consistent with header container)
- Fix spacing/consistency of cards + buttons
- Keep binary readout wrapping/bit-width behaviour from JS (\n in output)
*/
:root{
--panel-w: 360px;
--gap: 22px;
}
/* Page wrapper (inside BaseLayout .pageWrap) */
.wrap{
max-width: 1400px;
margin: 0 auto;
padding: 22px 20px 48px;
display: flex;
flex-direction: column;
gap: 14px;
}
/* Toolbox toggle button (sits below navbar, aligned right, never overlaps) */
.toolboxToggle{
align-self: flex-end;
position: sticky;
top: calc(var(--nav-h, 108px) + 14px);
z-index: 30;
display: inline-flex;
align-items: center;
gap: 10px;
height: 40px;
padding: 0 14px;
border-radius: 12px;
border: 1px solid rgba(255,255,255,.14);
background: rgba(255,255,255,.06);
color: rgba(255,255,255,.92);
cursor: pointer;
}
.toolboxToggle:hover{ background: rgba(255,255,255,.08); }
.toolboxText{
letter-spacing: .12em;
font-weight: 900;
}
/* Main layout grid */
.topGrid{
display: grid;
grid-template-columns: 1fr var(--panel-w);
gap: var(--gap);
align-items: start;
}
/* Hide ENTIRE toolbox column when toggled closed */
body.toolboxClosed .topGrid{ grid-template-columns: 1fr; }
body.toolboxClosed #toolboxPanel{ display: none; }
.mainCol{ min-width: 0; }
/* Readout */
.readout{
text-align: center;
margin-top: 8px;
}
.label{
opacity: .8;
letter-spacing: .12em;
text-transform: uppercase;
font-size: 12px;
}
/* IMPORTANT: allow shrinking below 4 bits (no min-width!) */
.num{
display: inline-block;
width: fit-content;
max-width: 100%;
white-space: pre-line; /* allows JS \n wraps */
letter-spacing: 2px;
}
.denaryValue{
font-size: 54px;
margin: 6px 0 10px;
}
.binaryValue{
font-size: 56px;
margin: 4px 0 18px;
}
.divider{
height: 1px;
background: rgba(255,255,255,.10);
margin: 14px auto 24px;
max-width: 900px;
}
/* Bits area */
.bitsWrap{ padding-top: 6px; }
.bitsGrid{
display: grid;
gap: 24px;
justify-content: center;
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
max-width: 1200px;
margin: 0 auto;
}
.bitsGrid.bitsFew{ justify-content: center; }
.bit{
display: grid;
justify-items: center;
gap: 8px;
}
.bulb{
font-size: 32px; /* JS also bumps this */
line-height: 1;
opacity: .45;
}
.bitVal{
font-size: 22px;
line-height: 1.05;
text-align: center;
white-space: nowrap; /* keep -128 on one line */
}
/* Switch (existing classes assumed) */
.switch{
position: relative;
display: inline-block;
width: 52px;
height: 28px;
}
.switch input{ display:none; }
.slider{
position:absolute;
inset:0;
border-radius:999px;
background: rgba(255,255,255,.18);
border: 1px solid rgba(255,255,255,.14);
}
.slider:before{
content:"";
position:absolute;
height: 22px;
width: 22px;
left: 3px;
top: 2.5px;
border-radius: 999px;
background: #fff;
transition: transform .18s ease;
}
.switch input:checked + .slider:before{ transform: translateX(22px); }
/* Toolbox column */
.panelCol{
position: sticky;
top: calc(var(--nav-h, 108px) + 72px); /* leaves space for sticky toolbox button */
align-self: start;
display: grid;
gap: 16px;
}
/* Cards */
.card{
border: 1px solid rgba(255,255,255,.12);
border-radius: 16px;
background: rgba(255,255,255,.05);
padding: 14px;
}
.cardTitle{
opacity: .8;
letter-spacing: .14em;
text-transform: uppercase;
font-size: 12px;
margin-bottom: 10px;
}
.hint{
opacity: .7;
font-size: 11px;
margin-top: 10px;
line-height: 1.35;
}
/* Keep mode labels on one line */
.toggleRow{
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 10px;
align-items: center;
}
.toggleLabel{
font-size: 12px;
font-weight: 800;
letter-spacing: .12em;
text-transform: uppercase;
white-space: nowrap;
}
.subCard{
margin-top: 12px;
border: 1px solid rgba(255,255,255,.10);
border-radius: 14px;
background: rgba(0,0,0,.12);
padding: 12px;
}
.subTitle{
opacity: .8;
letter-spacing: .14em;
text-transform: uppercase;
font-size: 11px;
margin-bottom: 10px;
}
.bitWidthRow{
display: grid;
grid-template-columns: 44px 1fr 44px;
gap: 10px;
align-items: center;
}
.bitInputWrap{
display: grid;
grid-template-columns: auto 1fr;
gap: 10px;
align-items: center;
padding: 10px 12px;
border: 1px solid rgba(255,255,255,.10);
border-radius: 12px;
background: rgba(255,255,255,.04);
}
.bitInputLabel{
opacity: .75;
letter-spacing: .14em;
text-transform: uppercase;
font-size: 11px;
white-space: nowrap;
}
.bitInput{
width: 100%;
min-width: 0;
background: transparent;
border: none;
outline: none;
color: inherit;
font-size: 20px;
text-align: right;
}
.miniBtn{
height: 44px;
border-radius: 12px;
border: 1px solid rgba(255,255,255,.12);
background: rgba(255,255,255,.06);
color: rgba(255,255,255,.9);
font-size: 18px;
cursor: pointer;
}
.miniBtn:hover{ background: rgba(255,255,255,.08); }
/* Buttons */
.controlsRow{
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 10px;
}
.btn{
border-radius: 12px;
border: 1px solid rgba(255,255,255,.12);
background: rgba(255,255,255,.06);
color: rgba(255,255,255,.92);
padding: 12px 12px;
font-weight: 800;
letter-spacing: .10em;
text-transform: uppercase;
cursor: pointer;
}
.btn:hover{ background: rgba(255,255,255,.08); }
.btnWide{ width: 100%; }
.btnAccent{
background: rgba(0,255,140,.12);
border-color: rgba(0,255,140,.22);
}
.btnAccent:hover{ background: rgba(0,255,140,.16); }
.toolRowCentered{
display: flex;
justify-content: center;
gap: 12px;
margin: 10px 0 12px;
}
.toolBtn{
width: 56px;
height: 56px;
border-radius: 14px;
border: 1px solid rgba(255,255,255,.12);
background: rgba(255,255,255,.06);
color: rgba(255,255,255,.92);
font-size: 18px;
cursor: pointer;
}
.toolDec{ background: rgba(255,0,0,.14); border-color: rgba(255,0,0,.20); }
.toolInc{ background: rgba(0,255,140,.14); border-color: rgba(0,255,140,.20); }
.toolRow2{
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 12px;
}
/* Reset stays white text */
.btnReset{ color: rgba(255,255,255,.92); }
/* Responsive */
@media (max-width: 980px){
.topGrid{ grid-template-columns: 1fr; }
.panelCol{ position: static; }
.toolboxToggle{ position: static; align-self: flex-start; }
}

View File

@@ -1,85 +0,0 @@
:root{
--bg: #1f2027;
--panel: #22242d;
--panel2: rgba(255,255,255,.04);
--text: #e8e8ee;
--muted: #a9acb8;
--accent: #33ff7a;
--accent-dim: rgba(51,255,122,.15);
--line: rgba(255,255,255,.12);
}
*{ box-sizing:border-box; }
body{
margin:0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
background: var(--bg);
color: var(--text);
}
.siteHeader{
position: sticky;
top: 0;
z-index: 10;
background: rgba(0,0,0,.15);
backdrop-filter: blur(8px);
border-bottom: 1px solid rgba(255,255,255,.06);
}
.siteHeaderInner{
max-width: 1200px;
margin: 0 auto;
padding: 14px 20px;
display:flex;
align-items:center;
justify-content:space-between;
gap: 16px;
}
.brand{
color: var(--text);
text-decoration:none;
font-weight: 900;
letter-spacing:.02em;
}
.nav{
display:flex;
gap: 14px;
flex-wrap:wrap;
justify-content:flex-end;
}
.nav a{
color: var(--muted);
text-decoration:none;
font-weight: 700;
font-size: 14px;
}
.nav a:hover{ color: var(--text); }
.siteMain{
min-height: calc(100vh - 140px);
}
.siteFooter{
border-top: 1px solid rgba(255,255,255,.08);
margin-top: 32px;
background: rgba(0,0,0,.10);
}
.siteFooterInner{
max-width: 1200px;
margin: 0 auto;
padding: 18px 20px 26px;
color: var(--muted);
font-size: 12px;
line-height: 1.6;
}
.footerTitle{
color: var(--text);
opacity:.9;
font-weight: 800;
margin-bottom: 6px;
}

View File

@@ -1,75 +0,0 @@
:root{
--bg: #1f2027;
--panel: rgba(255,255,255,.04);
--panel-border: rgba(255,255,255,.10);
--text: #e8e8ee;
--muted: #a9acb8;
--accent: #33ff7a;
--accent-dim: rgba(51,255,122,.15);
--line: rgba(255,255,255,.12);
}
*{ box-sizing: border-box; }
body{
margin:0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
background: var(--bg);
color: var(--text);
}
.site-header{
border-bottom: 1px solid rgba(255,255,255,.08);
background: rgba(0,0,0,.12);
}
.site-header__inner{
max-width: 1200px;
margin: 0 auto;
padding: 14px 20px;
display:flex;
align-items:center;
justify-content:space-between;
gap: 18px;
}
.brand{
font-weight: 800;
letter-spacing: .02em;
}
.nav{
display:flex;
gap: 14px;
flex-wrap: wrap;
justify-content:flex-end;
}
.nav__link{
color: var(--muted);
text-decoration:none;
font-weight: 700;
font-size: 13px;
}
.nav__link:hover{ color: var(--text); }
.site-main{
max-width: 1200px;
margin: 0 auto;
padding: 28px 20px 40px;
min-height: calc(100vh - 140px);
}
.site-footer{
border-top: 1px solid rgba(255,255,255,.08);
background: rgba(0,0,0,.10);
}
.site-footer__inner{
max-width: 1200px;
margin: 0 auto;
padding: 16px 20px;
color: var(--muted);
font-size: 12px;
line-height: 1.5;
}

View File

@@ -1,326 +0,0 @@
/* Binary page styles (moved OUT of binary.astro) */
:root{
--panel-w: 360px;
--gap: 22px;
}
.wrap{
max-width: 1400px;
margin: 0 auto;
padding: 26px 20px 48px;
position: relative;
}
.topGrid{
display: grid;
grid-template-columns: 1fr var(--panel-w);
gap: var(--gap);
align-items: start;
}
/* When toolbox is hidden, reclaim space + centre content */
body.toolboxClosed .topGrid{
grid-template-columns: 1fr;
}
body.toolboxClosed #toolboxPanel{
display: none;
}
.mainCol{
min-width: 0;
}
.readout{
text-align: center;
margin-top: 8px;
}
.label{
opacity: .8;
letter-spacing: .12em;
text-transform: uppercase;
font-size: 12px;
}
/* IMPORTANT: allow shrinking below 4 bits (no min-width!) */
.num{
display: inline-block;
width: fit-content;
max-width: 100%;
white-space: pre-line; /* allows JS \n wraps */
letter-spacing: 2px;
}
.denaryValue{
font-size: 54px;
margin: 6px 0 10px;
}
.binaryValue{
font-size: 56px;
margin: 4px 0 18px;
}
.divider{
height: 1px;
background: rgba(255,255,255,.10);
margin: 14px auto 24px;
max-width: 900px;
}
.bitsWrap{
padding-top: 6px;
}
.bitsGrid{
display: grid;
gap: 24px;
justify-content: center;
}
/* Default: a single row of bits (will wrap automatically as bit count grows) */
.bitsGrid{
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
max-width: 1200px;
margin: 0 auto;
}
.bitsGrid.bitsFew{
justify-content: center;
}
/* Bit tile */
.bit{
display: grid;
justify-items: center;
gap: 8px;
}
.bulb{
font-size: 32px; /* JS also bumps this */
line-height: 1;
opacity: .45;
}
.bitVal{
font-size: 22px;
line-height: 1.05;
text-align: center;
white-space: nowrap; /* keep -128 on one line */
}
/* Switch (existing classes assumed) */
.switch{
position: relative;
display: inline-block;
width: 52px;
height: 28px;
}
.switch input{ display:none; }
.slider{
position:absolute;
inset:0;
border-radius:999px;
background: rgba(255,255,255,.18);
border: 1px solid rgba(255,255,255,.14);
}
.slider:before{
content:"";
position:absolute;
height: 22px;
width: 22px;
left: 3px;
top: 2.5px;
border-radius: 999px;
background: #fff;
transition: transform .18s ease;
}
.switch input:checked + .slider:before{
transform: translateX(22px);
}
/* Toolbox toggle button */
.toolboxToggle{
position: absolute;
right: 20px;
top: 18px;
z-index: 20;
display: inline-flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
border-radius: 12px;
border: 1px solid rgba(255,255,255,.14);
background: rgba(255,255,255,.06);
color: rgba(255,255,255,.92);
cursor: pointer;
}
.toolboxText{
letter-spacing: .12em;
font-weight: 900;
}
/* Toolbox panel */
.panelCol{
position: sticky;
top: calc(var(--nav-h, 72px) + 18px);
align-self: start;
display: grid;
gap: 16px;
}
/* Cards */
.card{
border: 1px solid rgba(255,255,255,.12);
border-radius: 16px;
background: rgba(255,255,255,.05);
padding: 14px;
}
.cardTitle{
opacity: .8;
letter-spacing: .14em;
text-transform: uppercase;
font-size: 12px;
margin-bottom: 10px;
}
.hint{
opacity: .7;
font-size: 11px;
margin-top: 10px;
line-height: 1.35;
}
/* Keep mode labels on one line */
.toggleRow{
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 10px;
align-items: center;
}
.toggleLabel{
font-size: 12px;
font-weight: 800;
letter-spacing: .12em;
text-transform: uppercase;
white-space: nowrap;
}
.subCard{
margin-top: 12px;
border: 1px solid rgba(255,255,255,.10);
border-radius: 14px;
background: rgba(0,0,0,.12);
padding: 12px;
}
.subTitle{
opacity: .8;
letter-spacing: .14em;
text-transform: uppercase;
font-size: 11px;
margin-bottom: 10px;
}
.bitWidthRow{
display: grid;
grid-template-columns: 44px 1fr 44px;
gap: 10px;
align-items: center;
}
.bitInputWrap{
display: grid;
grid-template-columns: auto 1fr;
gap: 10px;
align-items: center;
padding: 10px 12px;
border: 1px solid rgba(255,255,255,.10);
border-radius: 12px;
background: rgba(255,255,255,.04);
}
.bitInputLabel{
opacity: .75;
letter-spacing: .14em;
text-transform: uppercase;
font-size: 11px;
white-space: nowrap;
}
.bitInput{
width: 100%;
min-width: 0;
background: transparent;
border: none;
outline: none;
color: inherit;
font-size: 20px;
text-align: right;
}
.miniBtn{
height: 44px;
border-radius: 12px;
border: 1px solid rgba(255,255,255,.12);
background: rgba(255,255,255,.06);
color: rgba(255,255,255,.9);
font-size: 18px;
cursor: pointer;
}
/* Buttons */
.controlsRow{
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 10px;
}
.btn{
border-radius: 12px;
border: 1px solid rgba(255,255,255,.12);
background: rgba(255,255,255,.06);
color: rgba(255,255,255,.92);
padding: 12px 12px;
font-weight: 800;
letter-spacing: .10em;
text-transform: uppercase;
cursor: pointer;
}
.btnWide{ width: 100%; }
.btnAccent{
background: rgba(0,255,140,.12);
border-color: rgba(0,255,140,.22);
}
.toolRowCentered{
display: flex;
justify-content: center;
gap: 12px;
margin: 10px 0 12px;
}
.toolBtn{
width: 56px;
height: 56px;
border-radius: 14px;
border: 1px solid rgba(255,255,255,.12);
background: rgba(255,255,255,.06);
color: rgba(255,255,255,.92);
font-size: 18px;
cursor: pointer;
}
.toolDec{ background: rgba(255,0,0,.14); border-color: rgba(255,0,0,.20); }
.toolInc{ background: rgba(0,255,140,.14); border-color: rgba(0,255,140,.20); }
.toolRow2{
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 12px;
}
/* Reset stays white text */
.btnReset{
color: rgba(255,255,255,.92);
}

View File

@@ -1,85 +1,190 @@
:root{ /* 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; --bg: #1f2027;
--panel: #22242d;
--panel2: rgba(255,255,255,.04);
--text: #e8e8ee; --text: #e8e8ee;
--muted: #a9acb8; --muted: #a9acb8;
--accent: #33ff7a; --line: rgba(255,255,255,.10);
--accent-dim: rgba(51,255,122,.15);
--line: rgba(255,255,255,.12); --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; } * { 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; }
body{ /* --- BASE LAYOUT --- */
margin:0; .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); }
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; .navInner { height: 100%; max-width: 1400px; margin: 0 auto; padding: 0 20px; display: flex; align-items: center; justify-content: space-between; gap: 24px; }
background: var(--bg); .brand { display: flex; align-items: center; gap: 12px; text-decoration: none; color: var(--text); }
color: var(--text); .brandLogo { width: 2.5em; height: 2.5em; image-rendering: pixelated; }
.brandName { letter-spacing: .12em; font-weight: 900; 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; }
.navLinks a:hover, .navLinks a.active { color: #e8e8ee; }
.pageWrap { flex: 1; max-width: 1400px; margin: 0 auto; padding: 0 20px 40px; width: 100%; display: flex; flex-direction: column; }
.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; display: flex; flex-direction: column; gap: 6px; }
/* --- 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: 16px; flex: 1; display: flex; flex-direction: column;
}
.binaryPage:not(.toolboxCollapsed) { padding-right: calc(var(--toolbox-w) + var(--toolbox-gap)); }
.binaryPage.toolboxCollapsed { padding-right: 0; }
.topGrid { display: flex; align-items: stretch; gap: 28px; flex: 1; }
.leftCol { flex: 1 1 auto; min-width: 0; container-type: inline-size; display: flex; flex-direction: column; }
/* --- READOUT FORMATTING --- */
.readoutContainer { display: flex; align-items: center; justify-content: center; gap: 64px; width: 100%; }
.readout { display: flex; flex-direction: column; align-items: center; gap: 16px; padding-top: 4px; }
.readoutBlock { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.label { font-family: var(--bit-font); letter-spacing: .14em; text-transform: uppercase; font-size: 18px; opacity: .75; margin: 0; }
.num { font-family: var(--num-font); color: #28f07a; text-shadow: 0 0 18px rgba(40,240,122,.35); letter-spacing: 2px; }
.denaryValue, .hexValue, .binaryValue { display: flex; gap: 16px; justify-content: center; white-space: pre-wrap; text-align: center; margin: 0; line-height: 1; }
.denaryValue { font-size: 56px; }
.hexValue { font-size: 48px; }
.binaryValue { font-size: 40px; }
.divider { height: 1px; background: rgba(255,255,255,.08); margin: 16px 0 16px; }
/* --- GRIDS & BITS --- */
.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; }
.bitVal { font-family: var(--bit-font); font-size: min(32px, calc(140cqw / var(--len, 1))); letter-spacing: 2px; color: rgba(232,232,238,.85); white-space: nowrap; line-height: 1; }
.bulb { width: 44px; height: 44px; 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 !important; filter: drop-shadow(0 0 14px rgba(255, 216, 107, 1)) !important; }
.bulb.on svg { fill: #ffd86b !important; }
.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; pointer-events: none; }
.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); }
/* --- HEXADECIMAL --- */
.hexGrid { --cols: 4; display: grid; grid-template-columns: repeat(var(--cols), minmax(160px, 1fr)); gap: 32px 20px; align-items: start; justify-items: center; width: 100%; }
.hexGrid.bitsFew { justify-content: center; }
.hexCol { display: flex; flex-direction: column; align-items: center; width: 100%; }
/* --- HEX COLOURS SPECIFIC --- */
.colorGroupWrap { display: flex; flex-wrap: nowrap; gap: 16px; justify-content: center; width: 100%; }
.colorGroup { display: flex; gap: 12px; padding: 12px; background: rgba(255,255,255,.02); border-radius: 20px; border: 1px solid rgba(255,255,255,.05); flex: 0 1 auto; min-width: 0; }
.colorPreviewSide { display: flex; gap: 24px; align-items: center; justify-content: center; }
.colorBoxWrap { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.colorBox { width: 60px; height: 60px; border-radius: 12px; border: 2px solid rgba(255,255,255,.15); box-shadow: 0 4px 16px rgba(0,0,0,.4); background-color: #000000; transition: background-color 0.2s ease; }
.colorBoxLabel { font-family: var(--ui-font); font-size: 11px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.text-red { color: #ff5555 !important; text-shadow: 0 0 18px rgba(255,85,85,.35) !important; }
.text-green { color: #28f07a !important; text-shadow: 0 0 18px rgba(40,240,122,.35) !important; }
.text-blue { color: #55aaff !important; text-shadow: 0 0 18px rgba(85,170,255,.35) !important; }
/* HEX CARD */
.hexCard { background: rgba(255,255,255,.03); border: 1px solid rgba(255,255,255,.08); border-radius: 16px; padding: 16px 14px; display: flex; flex-direction: column; align-items: center; gap: 16px; width: 100%; max-width: 190px; flex: 0 1 auto; min-width: 0; box-shadow: 0 4px 24px rgba(0,0,0,.2); backdrop-filter: blur(10px); }
.hexCardButtons { display: flex; gap: 10px; }
.hexCardBtn { width: 38px; height: 38px; border-radius: 10px; border: 1px solid rgba(255,255,255,.12); font-family: var(--bit-font); font-size: 16px; font-weight: 900; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0; color: rgba(232,232,238,.92); transition: all 0.2s ease; }
.hexCardBtn.inc { background: rgba(40,240,122,.15); border-color: rgba(40,240,122,.25); }
.hexCardBtn.inc:hover { background: rgba(40,240,122,.25); border-color: rgba(40,240,122,.4); }
.hexCardBtn.dec { background: rgba(255,80,80,.18); border-color: rgba(255,80,80,.25); }
.hexCardBtn.dec:hover { background: rgba(255,80,80,.28); border-color: rgba(255,80,80,.4); }
.hexDigitDisplay { font-family: var(--num-font); font-size: 48px; color: #28f07a; text-shadow: 0 0 18px rgba(40,240,122,.35); line-height: 1; }
.hexNibbleRow { display: flex; gap: 10px; justify-content: center; width: 100%; }
.hexNibbleBit { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.hexNibbleBulb { width: 32px !important; height: 32px !important; margin-bottom: 2px !important; }
.hexNibbleLabel { font-family: var(--bit-font); font-size: 24px; color: rgba(232,232,238,.6); }
.hexColWeight { font-family: var(--bit-font); font-size: 40px; color: rgba(232,232,238,.6); margin-top: 14px; }
/* --- TOOLBOX --- */
.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; letter-spacing: .08em; text-transform: uppercase; color: rgba(232,232,238,.55); margin-top: 10px; line-height: 1.35; }
.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; }
.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); }
.btnReset { color: rgba(232,232,238,.95); }
.btnReset:hover { background: rgba(255,80,80,.18); border-color: rgba(255,80,80,.35); }
/* === CONTAINER QUERIES === */
@container (max-width: 1050px) {
.readoutContainer { gap: 40px; }
.colorGroupWrap { gap: 10px; }
.colorGroup { padding: 10px; gap: 8px; border-radius: 16px; }
.hexCard { padding: 12px 8px; width: 140px; gap: 12px; }
.hexDigitDisplay { font-size: 40px; }
.hexNibbleBulb { width: 24px !important; height: 24px !important; }
.hexNibbleLabel { font-size: 20px; }
.hexColWeight { font-size: 26px; margin-top: 10px; }
.hexCardBtn { width: 34px; height: 34px; font-size: 14px; }
} }
.siteHeader{ @container (max-width: 800px) {
position: sticky; .readoutContainer { flex-direction: column; gap: 24px; }
top: 0; .colorPreviewSide { padding-top: 0; }
z-index: 10; .colorGroupWrap { gap: 6px; }
background: rgba(0,0,0,.15); .colorGroup { padding: 6px; gap: 6px; border-radius: 12px; }
backdrop-filter: blur(8px); .hexCard { padding: 8px 4px; width: 90px; gap: 8px; border-radius: 10px; }
border-bottom: 1px solid rgba(255,255,255,.06); .hexDigitDisplay { font-size: 32px; }
.hexNibbleBulb { width: 16px !important; height: 16px !important; }
.hexNibbleLabel { font-size: 16px; }
.hexColWeight { font-size: 20px; margin-top: 6px; }
.hexCardBtn { width: 28px; height: 28px; font-size: 12px; }
.denaryValue, .hexValue, .binaryValue { font-size: 32px; gap: 10px; }
} }
.siteHeaderInner{ @media (max-width: 1100px) { .binaryPage { --toolbox-w: 330px; } .denaryValue { font-size: 48px; } .hexValue { font-size: 40px; } .binaryValue { font-size: 32px; } }
max-width: 1200px; @media (max-width: 900px) { .binaryPage { --toolbox-w: 320px; } .bitsGrid { grid-template-columns: repeat(var(--cols), minmax(84px, 1fr)); } .hexGrid { grid-template-columns: repeat(var(--cols), minmax(130px, 1fr)); } }
margin: 0 auto;
padding: 14px 20px;
display:flex;
align-items:center;
justify-content:space-between;
gap: 16px;
}
.brand{
color: var(--text);
text-decoration:none;
font-weight: 900;
letter-spacing:.02em;
}
.nav{
display:flex;
gap: 14px;
flex-wrap:wrap;
justify-content:flex-end;
}
.nav a{
color: var(--muted);
text-decoration:none;
font-weight: 700;
font-size: 14px;
}
.nav a:hover{ color: var(--text); }
.siteMain{
min-height: calc(100vh - 140px);
}
.siteFooter{
border-top: 1px solid rgba(255,255,255,.08);
margin-top: 32px;
background: rgba(0,0,0,.10);
}
.siteFooterInner{
max-width: 1200px;
margin: 0 auto;
padding: 18px 20px 26px;
color: var(--muted);
font-size: 12px;
line-height: 1.6;
}
.footerTitle{
color: var(--text);
opacity:.9;
font-weight: 800;
margin-bottom: 6px;
}

247
src/styles/logic-gates.css Normal file
View File

@@ -0,0 +1,247 @@
/* === FULL PAGE OVERRIDES FOR LOGIC GATES === */
body:has(#logicPage) {
overflow: hidden; /* Prevents the entire page from scrolling */
}
body:has(#logicPage) .pageWrap {
max-width: 100% !important; /* Forces edge-to-edge canvas */
padding: 0 !important;
margin: 0 !important;
height: calc(100vh - var(--nav-h));
display: flex;
flex-direction: column;
}
#logicPage {
padding: 0 !important; /* CRITICAL: Stops the page/header from shifting when toolbox opens */
margin: 0 !important;
}
/* === MAIN CONTAINER === */
.lg-container {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
/* === FIXED HEADER (Ultra Compact) === */
.lg-top-header {
width: 100%;
text-align: center;
padding: 8px 20px 8px; /* Extremely tight padding to maximize canvas */
background: var(--bg);
z-index: 10;
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-bottom: 1px solid rgba(255,255,255,0.08); /* Clean separation line */
}
.lg-title {
font-family: var(--bit-font);
font-size: 32px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--text);
margin: 0 0 2px 0; /* Minimal gap between title and subtitle */
line-height: 1;
}
.lg-subtitle {
color: var(--muted);
font-size: 14px;
font-family: var(--ui-font);
font-weight: 500;
margin: 0;
line-height: 1.2;
}
.lg-subtitle kbd {
background: rgba(255,255,255,0.1);
padding: 2px 6px;
border-radius: 4px;
font-family: var(--ui-font);
color: #e8e8ee;
}
/* === DYNAMIC CANVAS === */
.lg-workspace {
flex: 1; /* Automatically fills all remaining vertical space */
position: relative;
width: 100%;
background-color: transparent;
background-image: radial-gradient(rgba(255,255,255,0.12) 1px, transparent 1px);
background-size: 24px 24px;
overflow: hidden;
}
.lg-svg-layer {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
/* Wires */
.lg-wire {
stroke: rgba(255,255,255,0.25);
stroke-width: 6;
fill: none;
stroke-linecap: round;
transition: stroke 0.1s ease, filter 0.1s ease, stroke-width 0.1s ease;
pointer-events: stroke;
cursor: pointer;
}
.lg-wire:hover {
stroke: rgba(255,255,255,0.6);
stroke-width: 10;
}
.lg-wire.active {
stroke: #28f07a;
filter: drop-shadow(0 0 6px rgba(40,240,122,0.6));
}
.lg-wire.active:hover { stroke: #5dff9e; }
.lg-wire.selected {
stroke: #ff5555 !important;
stroke-width: 8 !important;
stroke-dasharray: 8 8;
filter: drop-shadow(0 0 8px rgba(255,85,85,0.8)) !important;
animation: wireDash 1s linear infinite;
}
@keyframes wireDash { to { stroke-dashoffset: -16; } }
.lg-wire-temp {
stroke: rgba(255,255,255,0.4);
stroke-dasharray: 8 8;
pointer-events: none;
}
/* Nodes */
.lg-node {
position: absolute;
background: transparent;
border: none;
border-radius: 0;
padding: 4px;
display: flex;
flex-direction: column;
align-items: center;
cursor: grab;
z-index: 10;
user-select: none;
transition: filter 0.2s;
}
.lg-node:active { cursor: grabbing; z-index: 20; }
.lg-node.selected { filter: drop-shadow(0 0 10px rgba(255,85,85,0.8)); }
.lg-header {
font-size: 24px;
color: var(--muted);
font-family: var(--bit-font);
letter-spacing: 2px;
pointer-events: none;
margin-bottom: 6px;
}
.lg-gate-container {
position: relative;
display: inline-flex;
align-items: center;
}
.lg-gate-svg { width: 100px; height: 50px; display: block; }
.lg-line-svg { width: 30px; height: 50px; display: block; }
/* Connection Ports */
.lg-port {
width: 16px; height: 16px; background: #a9acb8; border-radius: 50%; cursor: crosshair;
border: 3px solid var(--bg); box-shadow: 0 0 0 1px rgba(255,255,255,0.2); transition: all 0.2s;
position: absolute; z-index: 5;
transform: translate(-50%, -50%);
}
.lg-port:hover { transform: translate(-50%, -50%) scale(1.3); background: #fff; }
.lg-port.active { background: #28f07a; box-shadow: 0 0 12px rgba(40,240,122,0.8); border-color: #1f2027; }
/* === FLOATING TOOLBOX === */
.toolboxToggle {
position: absolute;
top: 10px; /* Snug inside the thinner header */
right: 20px;
z-index: 90;
display: flex; align-items: center; gap: 10px; padding: 8px 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); }
.lg-toolbox {
position: absolute;
top: 60px; /* Sits right under the new thin header */
right: 20px;
bottom: 20px; /* Constrains the height so it scrolls internally */
width: var(--toolbox-w, 360px);
z-index: 80;
display: flex;
flex-direction: column;
gap: 16px;
transform: translateX(0);
transition: transform 420ms cubic-bezier(.2,.9,.2,1), opacity 220ms ease;
overflow-y: auto;
pointer-events: auto;
padding-right: 6px;
}
/* Faded Subdued Scrollbars */
.lg-toolbox::-webkit-scrollbar { width: 6px; }
.lg-toolbox::-webkit-scrollbar-track { background: transparent; }
.lg-toolbox::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.05); border-radius: 10px; transition: background 0.3s; }
.lg-toolbox:hover::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); }
.lg-toolbox::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.4); }
.lg-container.toolboxCollapsed .lg-toolbox {
transform: translateX(calc(100% + 40px));
opacity: 0;
pointer-events: none;
}
/* Toolbox Grid */
.tb-icon-grid {
display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
}
.tb-icon-box {
background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.15);
border-radius: 12px; width: 100%; padding: 12px 0;
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
cursor: grab; transition: background 0.2s, border-color 0.2s;
}
.tb-icon-box:hover { background: rgba(255,255,255,0.1); border-color: rgba(255,255,255,0.3); }
.tb-icon-label { font-family: var(--ui-font); font-size: 11px; font-weight: 800; color: var(--text); letter-spacing: 1px; text-transform: uppercase; }
/* Truth Table */
.tt-summary {
font-family: var(--ui-font); font-size: 14px; font-weight: 800;
color: var(--accent, #28f07a); cursor: pointer; user-select: none;
outline: none; margin-bottom: 10px; text-transform: uppercase;
}
.tt-table-wrap {
width: 100%; max-height: 250px; overflow-y: auto; overflow-x: auto;
border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(0,0,0,0.2);
}
.tt-table-wrap::-webkit-scrollbar { width: 6px; height: 6px; }
.tt-table-wrap::-webkit-scrollbar-track { background: transparent; }
.tt-table-wrap::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
.tt-table-wrap:hover::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.3); }
.tt-table { width: 100%; border-collapse: collapse; text-align: center; font-family: var(--num-font); font-size: 14px; color: #e8e8ee; }
.tt-table th { position: sticky; top: 0; background: rgba(31,32,39,0.95); padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.15); color: var(--muted); font-family: var(--bit-font); font-weight: normal; }
.tt-table td { padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.05); }
.tt-table .tt-on { color: #28f07a; text-shadow: 0 0 8px rgba(40,240,122,0.5); }

View File

@@ -1,75 +0,0 @@
:root{
--bg: #1f2027;
--panel: rgba(255,255,255,.04);
--panel-border: rgba(255,255,255,.10);
--text: #e8e8ee;
--muted: #a9acb8;
--accent: #33ff7a;
--accent-dim: rgba(51,255,122,.15);
--line: rgba(255,255,255,.12);
}
*{ box-sizing: border-box; }
body{
margin:0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
background: var(--bg);
color: var(--text);
}
.site-header{
border-bottom: 1px solid rgba(255,255,255,.08);
background: rgba(0,0,0,.12);
}
.site-header__inner{
max-width: 1200px;
margin: 0 auto;
padding: 14px 20px;
display:flex;
align-items:center;
justify-content:space-between;
gap: 18px;
}
.brand{
font-weight: 800;
letter-spacing: .02em;
}
.nav{
display:flex;
gap: 14px;
flex-wrap: wrap;
justify-content:flex-end;
}
.nav__link{
color: var(--muted);
text-decoration:none;
font-weight: 700;
font-size: 13px;
}
.nav__link:hover{ color: var(--text); }
.site-main{
max-width: 1200px;
margin: 0 auto;
padding: 28px 20px 40px;
min-height: calc(100vh - 140px);
}
.site-footer{
border-top: 1px solid rgba(255,255,255,.08);
background: rgba(0,0,0,.10);
}
.site-footer__inner{
max-width: 1200px;
margin: 0 auto;
padding: 16px 20px;
color: var(--muted);
font-size: 12px;
line-height: 1.5;
}