You've already forked computing-box
Compare commits
33 Commits
2.0
...
v26.03.01-
| Author | SHA1 | Date | |
|---|---|---|---|
|
e0e72c17e8
|
|||
|
ffab71cfcc
|
|||
| 4facee954c | |||
| da081e41e9 | |||
| ce92998667 | |||
| da28b5767d | |||
| aed8a80b02 | |||
| 13d15796cf | |||
| d9976c0a72 | |||
| 69420030a7 | |||
| ff63d9d8f5 | |||
| 273586137d | |||
| 89b0f733b1 | |||
| b90008d13c | |||
| 47fb7779d9 | |||
| 3de0ae1fd3 | |||
| fea9c3a6bf | |||
| e0c7dce9c8 | |||
| 9a6088fc9b | |||
| 3addaca2f2 | |||
| ce6c2298a1 | |||
| 8b3c58c8d9 | |||
| ae91944cb4 | |||
| 8bf8b44938 | |||
| 7fa90ab165 | |||
| f68aea4e33 | |||
| 91a07e49ae | |||
| 1519032f5b | |||
| 8d701a7704 | |||
| ac585701a3 | |||
| 002fbb8b6c | |||
| e6da9c8c98 | |||
| d4765b3788 |
194
.gitea/workflows/pre-release.yaml
Normal file
194
.gitea/workflows/pre-release.yaml
Normal file
@@ -0,0 +1,194 @@
|
||||
name: Pre-release on non-main branches
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore: [ main ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
prerelease:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout (full history + tags)
|
||||
uses: actions/checkout@v6
|
||||
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 (in runner only)
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
git-cliff --config cliff.toml --output CHANGELOG.md
|
||||
test -s CHANGELOG.md
|
||||
|
||||
- name: Extract newest changelog section for pre-release body
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
awk '
|
||||
/^## / { if (seen) exit; seen=1 }
|
||||
seen { print }
|
||||
' CHANGELOG.md > RELEASE_NOTES.md
|
||||
|
||||
sed -i 's/[[:space:]]*$//' RELEASE_NOTES.md
|
||||
test -s RELEASE_NOTES.md
|
||||
|
||||
- name: Create export zip (Computing:Box Website.zip)
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
if [ ! -d "dist" ]; then
|
||||
echo "❌ dist/ folder not found in repo root"
|
||||
ls -la
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f "Computing:Box Website.zip"
|
||||
(cd dist && zip -r "../Computing:Box Website.zip" .)
|
||||
test -s "Computing:Box Website.zip"
|
||||
ls -lh "Computing:Box Website.zip"
|
||||
|
||||
- name: Prepare pre-release tag + name
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
|
||||
# Get branch name from ref: refs/heads/feature/x -> feature/x
|
||||
ref="${GITHUB_REF#refs/heads/}"
|
||||
# Make it tag-safe: lowercase, / -> -, remove invalid chars, collapse repeats
|
||||
safe_branch="$(echo "$ref" | tr '[:upper:]' '[:lower:]' | sed -E 's#[^a-z0-9._-]+#-#g; s#-+#-#g; s#(^-|-$)##g')"
|
||||
|
||||
VERSION="$(date -u +'%y.%m.%d')"
|
||||
SHORT_SHA="$(git rev-parse --short HEAD)"
|
||||
|
||||
# Pre-release tag format:
|
||||
# vYY.MM.DD-pre.<branch>.<sha>
|
||||
TAG="v${VERSION}-pre.${safe_branch}.${SHORT_SHA}"
|
||||
|
||||
# Release name shown in UI
|
||||
RELEASE_NAME="Computing:Box pre-release (${ref}) v${VERSION}"
|
||||
|
||||
echo "TAG=$TAG" >> "$GITHUB_ENV"
|
||||
echo "RELEASE_NAME=$RELEASE_NAME" >> "$GITHUB_ENV"
|
||||
echo "ZIP_PATH=Computing:Box Website.zip" >> "$GITHUB_ENV"
|
||||
echo "BRANCH_NAME=$ref" >> "$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 pre-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"]
|
||||
branch = os.environ.get("BRANCH_NAME", "")
|
||||
|
||||
with open("RELEASE_NOTES.md", "r", encoding="utf-8") as f:
|
||||
body = f.read()
|
||||
|
||||
# Add a small pre-release banner at the top
|
||||
banner = f"⚠️ Pre-release build from branch `{branch}`\n\n"
|
||||
payload = {
|
||||
"tag_name": tag,
|
||||
"target_commitish": branch if branch else "main",
|
||||
"name": name,
|
||||
"body": banner + body,
|
||||
"draft": False,
|
||||
"prerelease": True,
|
||||
}
|
||||
|
||||
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 pre-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 "✅ Pre-release created: ${RELEASE_NAME} (tag: ${TAG}) with asset uploaded"
|
||||
235
.gitea/workflows/release.yaml
Normal file
235
.gitea/workflows/release.yaml
Normal file
@@ -0,0 +1,235 @@
|
||||
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@v6
|
||||
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 "dist" ]; then
|
||||
echo "❌ dist/ folder not found in repo root"
|
||||
ls -la
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f "Computing:Box Website.zip"
|
||||
(cd dist && 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"
|
||||
534
LICENSE
534
LICENSE
@@ -1,121 +1,437 @@
|
||||
Creative Commons Legal Code
|
||||
Attribution-NonCommercial-ShareAlike 4.0 International
|
||||
|
||||
CC0 1.0 Universal
|
||||
=======================================================================
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||
does not provide legal services or legal advice. Distribution of
|
||||
Creative Commons public licenses does not create a lawyer-client or
|
||||
other relationship. Creative Commons makes its licenses and related
|
||||
information available on an "as-is" basis. Creative Commons gives no
|
||||
warranties regarding its licenses, any material licensed under their
|
||||
terms and conditions, or any related information. Creative Commons
|
||||
disclaims all liability for damages resulting from their use to the
|
||||
fullest extent possible.
|
||||
|
||||
Statement of Purpose
|
||||
Using Creative Commons Public Licenses
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
Creative Commons public licenses provide a standard set of terms and
|
||||
conditions that creators and other rights holders may use to share
|
||||
original works of authorship and other material subject to copyright
|
||||
and certain other rights specified in the public license below. The
|
||||
following considerations are for informational purposes only, are not
|
||||
exhaustive, and do not form part of our licenses.
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
Considerations for licensors: Our public licenses are
|
||||
intended for use by those authorized to give the public
|
||||
permission to use material in ways otherwise restricted by
|
||||
copyright and certain other rights. Our licenses are
|
||||
irrevocable. Licensors should read and understand the terms
|
||||
and conditions of the license they choose before applying it.
|
||||
Licensors should also secure all rights necessary before
|
||||
applying our licenses so that the public can reuse the
|
||||
material as expected. Licensors should clearly mark any
|
||||
material not subject to the license. This includes other CC-
|
||||
licensed material, or material used under an exception or
|
||||
limitation to copyright. More considerations for licensors:
|
||||
wiki.creativecommons.org/Considerations_for_licensors
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
Considerations for the public: By using one of our public
|
||||
licenses, a licensor grants the public permission to use the
|
||||
licensed material under specified terms and conditions. If
|
||||
the licensor's permission is not necessary for any reason--for
|
||||
example, because of any applicable exception or limitation to
|
||||
copyright--then that use is not regulated by the license. Our
|
||||
licenses grant only permissions under copyright and certain
|
||||
other rights that a licensor has authority to grant. Use of
|
||||
the licensed material may still be restricted for other
|
||||
reasons, including because others have copyright or other
|
||||
rights in the material. A licensor may make special requests,
|
||||
such as asking that all changes be marked or described.
|
||||
Although not required by our licenses, you are encouraged to
|
||||
respect those requests where reasonable. More considerations
|
||||
for the public:
|
||||
wiki.creativecommons.org/Considerations_for_licensees
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
=======================================================================
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
|
||||
Public License
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
By exercising the Licensed Rights (defined below), You accept and agree
|
||||
to be bound by the terms and conditions of this Creative Commons
|
||||
Attribution-NonCommercial-ShareAlike 4.0 International Public License
|
||||
("Public License"). To the extent this Public License may be
|
||||
interpreted as a contract, You are granted the Licensed Rights in
|
||||
consideration of Your acceptance of these terms and conditions, and the
|
||||
Licensor grants You such rights in consideration of benefits the
|
||||
Licensor receives from making the Licensed Material available under
|
||||
these terms and conditions.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
Section 1 -- Definitions.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
a. Adapted Material means material subject to Copyright and Similar
|
||||
Rights that is derived from or based upon the Licensed Material
|
||||
and in which the Licensed Material is translated, altered,
|
||||
arranged, transformed, or otherwise modified in a manner requiring
|
||||
permission under the Copyright and Similar Rights held by the
|
||||
Licensor. For purposes of this Public License, where the Licensed
|
||||
Material is a musical work, performance, or sound recording,
|
||||
Adapted Material is always produced where the Licensed Material is
|
||||
synched in timed relation with a moving image.
|
||||
|
||||
b. Adapter's License means the license You apply to Your Copyright
|
||||
and Similar Rights in Your contributions to Adapted Material in
|
||||
accordance with the terms and conditions of this Public License.
|
||||
|
||||
c. BY-NC-SA Compatible License means a license listed at
|
||||
creativecommons.org/compatiblelicenses, approved by Creative
|
||||
Commons as essentially the equivalent of this Public License.
|
||||
|
||||
d. Copyright and Similar Rights means copyright and/or similar rights
|
||||
closely related to copyright including, without limitation,
|
||||
performance, broadcast, sound recording, and Sui Generis Database
|
||||
Rights, without regard to how the rights are labeled or
|
||||
categorized. For purposes of this Public License, the rights
|
||||
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||
Rights.
|
||||
|
||||
e. Effective Technological Measures means those measures that, in the
|
||||
absence of proper authority, may not be circumvented under laws
|
||||
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||
Treaty adopted on December 20, 1996, and/or similar international
|
||||
agreements.
|
||||
|
||||
f. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||
any other exception or limitation to Copyright and Similar Rights
|
||||
that applies to Your use of the Licensed Material.
|
||||
|
||||
g. License Elements means the license attributes listed in the name
|
||||
of a Creative Commons Public License. The License Elements of this
|
||||
Public License are Attribution, NonCommercial, and ShareAlike.
|
||||
|
||||
h. Licensed Material means the artistic or literary work, database,
|
||||
or other material to which the Licensor applied this Public
|
||||
License.
|
||||
|
||||
i. Licensed Rights means the rights granted to You subject to the
|
||||
terms and conditions of this Public License, which are limited to
|
||||
all Copyright and Similar Rights that apply to Your use of the
|
||||
Licensed Material and that the Licensor has authority to license.
|
||||
|
||||
j. Licensor means the individual(s) or entity(ies) granting rights
|
||||
under this Public License.
|
||||
|
||||
k. NonCommercial means not primarily intended for or directed towards
|
||||
commercial advantage or monetary compensation. For purposes of
|
||||
this Public License, the exchange of the Licensed Material for
|
||||
other material subject to Copyright and Similar Rights by digital
|
||||
file-sharing or similar means is NonCommercial provided there is
|
||||
no payment of monetary compensation in connection with the
|
||||
exchange.
|
||||
|
||||
l. Share means to provide material to the public by any means or
|
||||
process that requires permission under the Licensed Rights, such
|
||||
as reproduction, public display, public performance, distribution,
|
||||
dissemination, communication, or importation, and to make material
|
||||
available to the public including in ways that members of the
|
||||
public may access the material from a place and at a time
|
||||
individually chosen by them.
|
||||
|
||||
m. Sui Generis Database Rights means rights other than copyright
|
||||
resulting from Directive 96/9/EC of the European Parliament and of
|
||||
the Council of 11 March 1996 on the legal protection of databases,
|
||||
as amended and/or succeeded, as well as other essentially
|
||||
equivalent rights anywhere in the world.
|
||||
|
||||
n. You means the individual or entity exercising the Licensed Rights
|
||||
under this Public License. Your has a corresponding meaning.
|
||||
|
||||
|
||||
Section 2 -- Scope.
|
||||
|
||||
a. License grant.
|
||||
|
||||
1. Subject to the terms and conditions of this Public License,
|
||||
the Licensor hereby grants You a worldwide, royalty-free,
|
||||
non-sublicensable, non-exclusive, irrevocable license to
|
||||
exercise the Licensed Rights in the Licensed Material to:
|
||||
|
||||
a. reproduce and Share the Licensed Material, in whole or
|
||||
in part, for NonCommercial purposes only; and
|
||||
|
||||
b. produce, reproduce, and Share Adapted Material for
|
||||
NonCommercial purposes only.
|
||||
|
||||
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||
Exceptions and Limitations apply to Your use, this Public
|
||||
License does not apply, and You do not need to comply with
|
||||
its terms and conditions.
|
||||
|
||||
3. Term. The term of this Public License is specified in Section
|
||||
6(a).
|
||||
|
||||
4. Media and formats; technical modifications allowed. The
|
||||
Licensor authorizes You to exercise the Licensed Rights in
|
||||
all media and formats whether now known or hereafter created,
|
||||
and to make technical modifications necessary to do so. The
|
||||
Licensor waives and/or agrees not to assert any right or
|
||||
authority to forbid You from making technical modifications
|
||||
necessary to exercise the Licensed Rights, including
|
||||
technical modifications necessary to circumvent Effective
|
||||
Technological Measures. For purposes of this Public License,
|
||||
simply making modifications authorized by this Section 2(a)
|
||||
(4) never produces Adapted Material.
|
||||
|
||||
5. Downstream recipients.
|
||||
|
||||
a. Offer from the Licensor -- Licensed Material. Every
|
||||
recipient of the Licensed Material automatically
|
||||
receives an offer from the Licensor to exercise the
|
||||
Licensed Rights under the terms and conditions of this
|
||||
Public License.
|
||||
|
||||
b. Additional offer from the Licensor -- Adapted Material.
|
||||
Every recipient of Adapted Material from You
|
||||
automatically receives an offer from the Licensor to
|
||||
exercise the Licensed Rights in the Adapted Material
|
||||
under the conditions of the Adapter's License You apply.
|
||||
|
||||
c. No downstream restrictions. You may not offer or impose
|
||||
any additional or different terms or conditions on, or
|
||||
apply any Effective Technological Measures to, the
|
||||
Licensed Material if doing so restricts exercise of the
|
||||
Licensed Rights by any recipient of the Licensed
|
||||
Material.
|
||||
|
||||
6. No endorsement. Nothing in this Public License constitutes or
|
||||
may be construed as permission to assert or imply that You
|
||||
are, or that Your use of the Licensed Material is, connected
|
||||
with, or sponsored, endorsed, or granted official status by,
|
||||
the Licensor or others designated to receive attribution as
|
||||
provided in Section 3(a)(1)(A)(i).
|
||||
|
||||
b. Other rights.
|
||||
|
||||
1. Moral rights, such as the right of integrity, are not
|
||||
licensed under this Public License, nor are publicity,
|
||||
privacy, and/or other similar personality rights; however, to
|
||||
the extent possible, the Licensor waives and/or agrees not to
|
||||
assert any such rights held by the Licensor to the limited
|
||||
extent necessary to allow You to exercise the Licensed
|
||||
Rights, but not otherwise.
|
||||
|
||||
2. Patent and trademark rights are not licensed under this
|
||||
Public License.
|
||||
|
||||
3. To the extent possible, the Licensor waives any right to
|
||||
collect royalties from You for the exercise of the Licensed
|
||||
Rights, whether directly or through a collecting society
|
||||
under any voluntary or waivable statutory or compulsory
|
||||
licensing scheme. In all other cases the Licensor expressly
|
||||
reserves any right to collect such royalties, including when
|
||||
the Licensed Material is used other than for NonCommercial
|
||||
purposes.
|
||||
|
||||
|
||||
Section 3 -- License Conditions.
|
||||
|
||||
Your exercise of the Licensed Rights is expressly made subject to the
|
||||
following conditions.
|
||||
|
||||
a. Attribution.
|
||||
|
||||
1. If You Share the Licensed Material (including in modified
|
||||
form), You must:
|
||||
|
||||
a. retain the following if it is supplied by the Licensor
|
||||
with the Licensed Material:
|
||||
|
||||
i. identification of the creator(s) of the Licensed
|
||||
Material and any others designated to receive
|
||||
attribution, in any reasonable manner requested by
|
||||
the Licensor (including by pseudonym if
|
||||
designated);
|
||||
|
||||
ii. a copyright notice;
|
||||
|
||||
iii. a notice that refers to this Public License;
|
||||
|
||||
iv. a notice that refers to the disclaimer of
|
||||
warranties;
|
||||
|
||||
v. a URI or hyperlink to the Licensed Material to the
|
||||
extent reasonably practicable;
|
||||
|
||||
b. indicate if You modified the Licensed Material and
|
||||
retain an indication of any previous modifications; and
|
||||
|
||||
c. indicate the Licensed Material is licensed under this
|
||||
Public License, and include the text of, or the URI or
|
||||
hyperlink to, this Public License.
|
||||
|
||||
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||
reasonable manner based on the medium, means, and context in
|
||||
which You Share the Licensed Material. For example, it may be
|
||||
reasonable to satisfy the conditions by providing a URI or
|
||||
hyperlink to a resource that includes the required
|
||||
information.
|
||||
3. If requested by the Licensor, You must remove any of the
|
||||
information required by Section 3(a)(1)(A) to the extent
|
||||
reasonably practicable.
|
||||
|
||||
b. ShareAlike.
|
||||
|
||||
In addition to the conditions in Section 3(a), if You Share
|
||||
Adapted Material You produce, the following conditions also apply.
|
||||
|
||||
1. The Adapter's License You apply must be a Creative Commons
|
||||
license with the same License Elements, this version or
|
||||
later, or a BY-NC-SA Compatible License.
|
||||
|
||||
2. You must include the text of, or the URI or hyperlink to, the
|
||||
Adapter's License You apply. You may satisfy this condition
|
||||
in any reasonable manner based on the medium, means, and
|
||||
context in which You Share Adapted Material.
|
||||
|
||||
3. You may not offer or impose any additional or different terms
|
||||
or conditions on, or apply any Effective Technological
|
||||
Measures to, Adapted Material that restrict exercise of the
|
||||
rights granted under the Adapter's License You apply.
|
||||
|
||||
|
||||
Section 4 -- Sui Generis Database Rights.
|
||||
|
||||
Where the Licensed Rights include Sui Generis Database Rights that
|
||||
apply to Your use of the Licensed Material:
|
||||
|
||||
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||
to extract, reuse, reproduce, and Share all or a substantial
|
||||
portion of the contents of the database for NonCommercial purposes
|
||||
only;
|
||||
|
||||
b. if You include all or a substantial portion of the database
|
||||
contents in a database in which You have Sui Generis Database
|
||||
Rights, then the database in which You have Sui Generis Database
|
||||
Rights (but not its individual contents) is Adapted Material,
|
||||
including for purposes of Section 3(b); and
|
||||
|
||||
c. You must comply with the conditions in Section 3(a) if You Share
|
||||
all or a substantial portion of the contents of the database.
|
||||
|
||||
For the avoidance of doubt, this Section 4 supplements and does not
|
||||
replace Your obligations under this Public License where the Licensed
|
||||
Rights include other Copyright and Similar Rights.
|
||||
|
||||
|
||||
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||
|
||||
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
||||
|
||||
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
||||
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||
|
||||
c. The disclaimer of warranties and limitation of liability provided
|
||||
above shall be interpreted in a manner that, to the extent
|
||||
possible, most closely approximates an absolute disclaimer and
|
||||
waiver of all liability.
|
||||
|
||||
|
||||
Section 6 -- Term and Termination.
|
||||
|
||||
a. This Public License applies for the term of the Copyright and
|
||||
Similar Rights licensed here. However, if You fail to comply with
|
||||
this Public License, then Your rights under this Public License
|
||||
terminate automatically.
|
||||
|
||||
b. Where Your right to use the Licensed Material has terminated under
|
||||
Section 6(a), it reinstates:
|
||||
|
||||
1. automatically as of the date the violation is cured, provided
|
||||
it is cured within 30 days of Your discovery of the
|
||||
violation; or
|
||||
|
||||
2. upon express reinstatement by the Licensor.
|
||||
|
||||
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||
right the Licensor may have to seek remedies for Your violations
|
||||
of this Public License.
|
||||
|
||||
c. For the avoidance of doubt, the Licensor may also offer the
|
||||
Licensed Material under separate terms or conditions or stop
|
||||
distributing the Licensed Material at any time; however, doing so
|
||||
will not terminate this Public License.
|
||||
|
||||
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||
License.
|
||||
|
||||
|
||||
Section 7 -- Other Terms and Conditions.
|
||||
|
||||
a. The Licensor shall not be bound by any additional or different
|
||||
terms or conditions communicated by You unless expressly agreed.
|
||||
|
||||
b. Any arrangements, understandings, or agreements regarding the
|
||||
Licensed Material not stated herein are separate from and
|
||||
independent of the terms and conditions of this Public License.
|
||||
|
||||
|
||||
Section 8 -- Interpretation.
|
||||
|
||||
a. For the avoidance of doubt, this Public License does not, and
|
||||
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||
conditions on any use of the Licensed Material that could lawfully
|
||||
be made without permission under this Public License.
|
||||
|
||||
b. To the extent possible, if any provision of this Public License is
|
||||
deemed unenforceable, it shall be automatically reformed to the
|
||||
minimum extent necessary to make it enforceable. If the provision
|
||||
cannot be reformed, it shall be severed from this Public License
|
||||
without affecting the enforceability of the remaining terms and
|
||||
conditions.
|
||||
|
||||
c. No term or condition of this Public License will be waived and no
|
||||
failure to comply consented to unless expressly agreed to by the
|
||||
Licensor.
|
||||
|
||||
d. Nothing in this Public License constitutes or may be interpreted
|
||||
as a limitation upon, or waiver of, any privileges and immunities
|
||||
that apply to the Licensor or You, including from the legal
|
||||
processes of any jurisdiction or authority.
|
||||
|
||||
=======================================================================
|
||||
|
||||
Creative Commons is not a party to its public
|
||||
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||
its public licenses to material it publishes and in those instances
|
||||
will be considered the “Licensor.” The text of the Creative Commons
|
||||
public licenses is dedicated to the public domain under the CC0 Public
|
||||
Domain Dedication. Except for the limited purpose of indicating that
|
||||
material is shared under a Creative Commons public license or as
|
||||
otherwise permitted by the Creative Commons policies published at
|
||||
creativecommons.org/policies, Creative Commons does not authorize the
|
||||
use of the trademark "Creative Commons" or any other trademark or logo
|
||||
of Creative Commons without its prior written consent including,
|
||||
without limitation, in connection with any unauthorized modifications
|
||||
to any of its public licenses or any other arrangements,
|
||||
understandings, or agreements concerning use of licensed material. For
|
||||
the avoidance of doubt, this paragraph does not form part of the
|
||||
public licenses.
|
||||
|
||||
Creative Commons may be contacted at creativecommons.org.
|
||||
28
README.md
28
README.md
@@ -1,7 +1,13 @@
|
||||
# Computing:Box
|
||||
An evolution of Bit:Box & CS:Box to incorporate different elements of the UK Computing Curriculum
|
||||
|
||||

|
||||
### Available at:
|
||||
- [Personal Gitea Instance](https://git.adcmnetworks.co.uk/alexander.lyall/computing-box),
|
||||
- [GitHub](https://github.com/MrLyallCSIT/Computing-Box)
|
||||
- [The Official Computing:Box Website](https://www.computingbox.co.uk)
|
||||
|
||||
|
||||
<img src="/assets/img/ComputingBox-Logo.png" alt="Computing:Box Logo" width="250px" height="250px"/>
|
||||
|
||||
## Upcoming Features
|
||||
### Original Bit:Box Features (October 2024)
|
||||
@@ -22,12 +28,26 @@ An evolution of Bit:Box & CS:Box to incorporate different elements of the UK Com
|
||||
- [X] XNOR Gate Simulator
|
||||
|
||||
### Wave 3 CS:Box Features (Spring 2026)
|
||||
- [ ] New User Interface (Responsive)
|
||||
- [X] Two's Compliment Simulator
|
||||
- [ ] Extended Binary Simulator (Custom bit sizes)
|
||||
- [ ] Unified Binary Simulator (Unsigned & Two's Completment combined)
|
||||
- [X] Extended Binary Simulator (Custom bit sizes)
|
||||
- [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)
|
||||
- [ ] Compound Gate Simulator
|
||||
- [ ] Computer Components Simulator
|
||||
|
||||
## Version 1.0 Release Date: 1<sup>st</sup> September 2025
|
||||
## Version 2.0 Release Date (Goal): 1<sup>st</sup> February 2025
|
||||
## 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]
|
||||
|
||||
This work is licensed under a
|
||||
[Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License][cc-by-nc-sa].
|
||||
|
||||
[![CC BY-NC-SA 4.0][cc-by-nc-sa-image]][cc-by-nc-sa]
|
||||
|
||||
[cc-by-nc-sa]: http://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
[cc-by-nc-sa-image]: https://licensebuttons.net/l/by-nc-sa/4.0/88x31.png
|
||||
[cc-by-nc-sa-shield]: https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg
|
||||
12
dist/_astro/binary.astro_astro_type_script_index_0_lang.C_c_A3x5.js
vendored
Normal file
12
dist/_astro/binary.astro_astro_type_script_index_0_lang.C_c_A3x5.js
vendored
Normal 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)})();
|
||||
3
dist/binary/index.html
vendored
Normal file
3
dist/binary/index.html
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<!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">
|
||||
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> <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>
|
||||
9
dist/favicon.svg
vendored
Normal file
9
dist/favicon.svg
vendored
Normal 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 |
BIN
dist/fonts/DSEG7Classic-Regular.ttf
vendored
Normal file
BIN
dist/fonts/DSEG7Classic-Regular.ttf
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/DSEG7Classic-Regular.woff
vendored
Normal file
BIN
dist/fonts/DSEG7Classic-Regular.woff
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/Seven-Segment.woff
vendored
Normal file
BIN
dist/fonts/Seven-Segment.woff
vendored
Normal file
Binary file not shown.
BIN
dist/fonts/Seven-Segment.woff2
vendored
Normal file
BIN
dist/fonts/Seven-Segment.woff2
vendored
Normal file
Binary file not shown.
3
dist/hexadecimal/index.html
vendored
Normal file
3
dist/hexadecimal/index.html
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<!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;">
|
||||
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 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>
|
||||
1017
dist/images/computing-box-logo.svg
vendored
Normal file
1017
dist/images/computing-box-logo.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 732 KiB |
1017
dist/images/favicon.svg
vendored
Normal file
1017
dist/images/favicon.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 732 KiB |
7
dist/index.html
vendored
Normal file
7
dist/index.html
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<!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.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.
|
||||
</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
|
||||
improvements in Astro 5.0
|
||||
</p> </a> </div> </body></html>
|
||||
115
dist/js/binary/unsigned-binary.js
vendored
Normal file
115
dist/js/binary/unsigned-binary.js
vendored
Normal 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
72
dist/js/tools/unsigned-binary.js
vendored
Normal 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();
|
||||
});
|
||||
53
src/styles/md3-tokens.css → dist/styles.css
vendored
53
src/styles/md3-tokens.css → dist/styles.css
vendored
@@ -41,3 +41,56 @@
|
||||
--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
1035
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,6 @@
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^5.16.5"
|
||||
"astro": "^5.18.0"
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/* ---------- DSEG7 font ---------- */
|
||||
/* Put your font file here:
|
||||
public/fonts/DSEG7Classic-Regular.woff2
|
||||
*/
|
||||
@font-face {
|
||||
font-family: "DSEG7ClassicRegular";
|
||||
src: url("/fonts/DSEG7Classic-Regular.woff2") format("woff2");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* ---------- Layout ---------- */
|
||||
.tool-shell {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 1rem;
|
||||
background: #0b0f14;
|
||||
color: #e7eaf0;
|
||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.tool-card {
|
||||
width: min(1100px, 100%);
|
||||
background: #111824;
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: 18px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.tool-header h1 { margin: 0 0 .25rem 0; font-size: 1.4rem; }
|
||||
.tool-header p { margin: 0 0 1rem 0; opacity: .85; }
|
||||
|
||||
.display-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: .75rem;
|
||||
margin-bottom: .75rem;
|
||||
}
|
||||
|
||||
.display-box {
|
||||
background: #0b0f14;
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: 14px;
|
||||
padding: .75rem;
|
||||
}
|
||||
|
||||
.display-label { font-size: .9rem; opacity: .8; margin-bottom: .25rem; }
|
||||
|
||||
.sevenseg {
|
||||
font-family: "DSEG7ClassicRegular", monospace;
|
||||
font-size: clamp(2rem, 4vw, 3.2rem);
|
||||
letter-spacing: 0.08em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
/* Buttons under denary/binary (your request) */
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: .5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* ---------- Simple MD3-ish buttons ---------- */
|
||||
.md3-btn {
|
||||
border: 1px solid rgba(255,255,255,0.16);
|
||||
background: rgba(255,255,255,0.06);
|
||||
color: #e7eaf0;
|
||||
padding: .6rem .9rem;
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
}
|
||||
.md3-btn:hover { background: rgba(255,255,255,0.10); }
|
||||
.md3-btn--tonal { background: rgba(255,255,255,0.10); }
|
||||
|
||||
/* ---------- Switches row ---------- */
|
||||
.switch-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, minmax(90px, 1fr));
|
||||
gap: .75rem;
|
||||
}
|
||||
|
||||
.switch-col {
|
||||
display: grid;
|
||||
justify-items: center;
|
||||
gap: .35rem;
|
||||
}
|
||||
|
||||
.bit-label { opacity: .85; font-weight: 600; }
|
||||
|
||||
/* ---------- “Light switch” rocker ---------- */
|
||||
.rocker {
|
||||
position: relative;
|
||||
width: 70px;
|
||||
height: 46px;
|
||||
display: inline-block;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.rocker input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.rocker-body {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 12px;
|
||||
background: #1a2331;
|
||||
border: 1px solid rgba(255,255,255,0.14);
|
||||
box-shadow: inset 0 0 0 2px rgba(0,0,0,0.35);
|
||||
}
|
||||
|
||||
/* the “toggle” */
|
||||
.rocker-body::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
top: 6px;
|
||||
width: 58px;
|
||||
height: 18px;
|
||||
border-radius: 10px;
|
||||
background: rgba(255,255,255,0.20);
|
||||
transition: transform 180ms ease, background 180ms ease;
|
||||
}
|
||||
|
||||
/* ON position */
|
||||
.rocker input:checked + .rocker-body::after {
|
||||
transform: translateY(16px);
|
||||
background: rgba(255,255,255,0.55);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.switch-row { grid-template-columns: repeat(4, minmax(90px, 1fr)); }
|
||||
}
|
||||
BIN
public/fonts/Seven-Segment.woff
Normal file
BIN
public/fonts/Seven-Segment.woff
Normal file
Binary file not shown.
BIN
public/fonts/Seven-Segment.woff2
Normal file
BIN
public/fonts/Seven-Segment.woff2
Normal file
Binary file not shown.
1017
public/images/computing-box-logo.svg
Normal file
1017
public/images/computing-box-logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 732 KiB |
1017
public/images/favicon.svg
Normal file
1017
public/images/favicon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 732 KiB |
3
renovate.json
Normal file
3
renovate.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||
}
|
||||
@@ -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 |
@@ -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>
|
||||
@@ -1,381 +0,0 @@
|
||||
---
|
||||
const { mode = "unsigned", defaultBits = 8 } = Astro.props;
|
||||
|
||||
// For unsigned: min 1 bit, max 16 bits (tweak if you want)
|
||||
const minBits = 4;
|
||||
const maxBits = 16;
|
||||
const initialBits = Math.min(Math.max(defaultBits, minBits), maxBits);
|
||||
---
|
||||
|
||||
<section class="binary-sim" data-mode={mode} data-bits={initialBits}>
|
||||
<div class="top">
|
||||
<div class="left-brand">
|
||||
<!-- Replace with your logo/img -->
|
||||
<div class="brand-box" aria-hidden="true">Computing:Box</div>
|
||||
</div>
|
||||
|
||||
<div class="display" aria-live="polite">
|
||||
<div class="label">DENARY</div>
|
||||
<div id="denaryNumber" class="value">0</div>
|
||||
|
||||
<div class="label">BINARY</div>
|
||||
<div id="binaryNumber" class="value mono">00000000</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn" type="button" data-action="custom-binary">Custom Binary</button>
|
||||
<button class="btn" type="button" data-action="custom-denary">Custom Denary</button>
|
||||
<button class="btn" type="button" data-action="shift-left">Left Shift</button>
|
||||
<button class="btn" type="button" data-action="shift-right">Right Shift</button>
|
||||
|
||||
<div class="bits-control">
|
||||
<div class="bits-title">Bits</div>
|
||||
<div class="bits-buttons">
|
||||
<button class="btn small" type="button" data-action="bits-minus" aria-label="Decrease bits">−</button>
|
||||
<span id="bitsCount" class="bits-count">{initialBits}</span>
|
||||
<button class="btn small" type="button" data-action="bits-plus" aria-label="Increase bits">+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="bitsRow" class="bits-row" aria-label="Binary bit switches"></div>
|
||||
</section>
|
||||
|
||||
<script is:inline>
|
||||
(() => {
|
||||
const root = document.currentScript.closest(".binary-sim");
|
||||
const bitsRow = root.querySelector("#bitsRow");
|
||||
const binaryEl = root.querySelector("#binaryNumber");
|
||||
const denaryEl = root.querySelector("#denaryNumber");
|
||||
const bitsCountEl = root.querySelector("#bitsCount");
|
||||
|
||||
let bitCount = parseInt(root.dataset.bits || "8", 10);
|
||||
const minBits = 4;
|
||||
const maxBits = 16;
|
||||
|
||||
// state: array of 0/1, MSB at index 0
|
||||
let bits = new Array(bitCount).fill(0);
|
||||
|
||||
function placeValues(n) {
|
||||
// unsigned place values: [2^(n-1), ..., 2^0]
|
||||
return Array.from({ length: n }, (_, i) => 2 ** (n - 1 - i));
|
||||
}
|
||||
|
||||
function bitsToDenary(bitsArr) {
|
||||
const pv = placeValues(bitsArr.length);
|
||||
return bitsArr.reduce((acc, b, i) => acc + (b ? pv[i] : 0), 0);
|
||||
}
|
||||
|
||||
function render() {
|
||||
const binaryStr = bits.join("");
|
||||
binaryEl.textContent = binaryStr;
|
||||
denaryEl.textContent = String(bitsToDenary(bits));
|
||||
bitsCountEl.textContent = String(bitCount);
|
||||
|
||||
const pv = placeValues(bitCount);
|
||||
|
||||
bitsRow.innerHTML = pv.map((value, i) => {
|
||||
const id = `bit_${bitCount}_${i}`;
|
||||
const checked = bits[i] === 1 ? "checked" : "";
|
||||
return `
|
||||
<div class="bit-col">
|
||||
<div class="bulb ${bits[i] ? "on" : "off"}" aria-hidden="true"></div>
|
||||
<div class="place">${value}</div>
|
||||
|
||||
<label class="switch" for="${id}">
|
||||
<input id="${id}" type="checkbox" ${checked} data-index="${i}">
|
||||
<span class="slider" aria-hidden="true"></span>
|
||||
<span class="sr-only">Toggle bit value ${value}</span>
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function setBitsFromBinaryString(str) {
|
||||
// allow shorter input; pad left
|
||||
const clean = (str || "").trim();
|
||||
if (!/^[01]+$/.test(clean) || clean.length > bitCount) return false;
|
||||
const padded = clean.padStart(bitCount, "0");
|
||||
bits = padded.split("").map(c => c === "1" ? 1 : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
function setBitsFromDenary(num) {
|
||||
if (!Number.isInteger(num)) return false;
|
||||
const max = (2 ** bitCount) - 1;
|
||||
if (num < 0 || num > max) return false;
|
||||
|
||||
const bin = num.toString(2).padStart(bitCount, "0");
|
||||
bits = bin.split("").map(c => c === "1" ? 1 : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
function shiftLeft() {
|
||||
bits = bits.slice(1).concat(0);
|
||||
}
|
||||
|
||||
function shiftRight() {
|
||||
bits = [0].concat(bits.slice(0, -1));
|
||||
}
|
||||
|
||||
function resizeBits(newCount) {
|
||||
const clamped = Math.max(minBits, Math.min(maxBits, newCount));
|
||||
if (clamped === bitCount) return;
|
||||
|
||||
// keep value as best as possible: preserve LSBs when resizing
|
||||
const currentBinary = bits.join("");
|
||||
bitCount = clamped;
|
||||
bits = new Array(bitCount).fill(0);
|
||||
|
||||
const take = currentBinary.slice(-bitCount);
|
||||
setBitsFromBinaryString(take);
|
||||
}
|
||||
|
||||
// Switch input handler
|
||||
bitsRow.addEventListener("change", (e) => {
|
||||
const input = e.target;
|
||||
if (!(input instanceof HTMLInputElement)) return;
|
||||
const idx = parseInt(input.dataset.index || "-1", 10);
|
||||
if (idx < 0) return;
|
||||
bits[idx] = input.checked ? 1 : 0;
|
||||
render();
|
||||
});
|
||||
|
||||
// Button actions
|
||||
root.querySelector(".actions").addEventListener("click", (e) => {
|
||||
const btn = e.target.closest("button[data-action]");
|
||||
if (!btn) return;
|
||||
|
||||
const action = btn.dataset.action;
|
||||
|
||||
if (action === "custom-binary") {
|
||||
const entered = prompt(`Enter a ${bitCount}-bit binary value (0s and 1s):`, bits.join(""));
|
||||
if (entered === null) return;
|
||||
if (!setBitsFromBinaryString(entered)) {
|
||||
alert(`Invalid binary. Use only 0 and 1, up to ${bitCount} digits.`);
|
||||
return;
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
if (action === "custom-denary") {
|
||||
const max = (2 ** bitCount) - 1;
|
||||
const entered = prompt(`Enter a denary value (0 to ${max}):`, String(bitsToDenary(bits)));
|
||||
if (entered === null) return;
|
||||
const num = Number.parseInt(entered, 10);
|
||||
if (!setBitsFromDenary(num)) {
|
||||
alert(`Invalid denary. Enter a whole number from 0 to ${max}.`);
|
||||
return;
|
||||
}
|
||||
render();
|
||||
}
|
||||
|
||||
if (action === "shift-left") { shiftLeft(); render(); }
|
||||
if (action === "shift-right") { shiftRight(); render(); }
|
||||
|
||||
if (action === "bits-minus") { resizeBits(bitCount - 1); render(); }
|
||||
if (action === "bits-plus") { resizeBits(bitCount + 1); render(); }
|
||||
});
|
||||
|
||||
// initial
|
||||
render();
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Layout */
|
||||
.binary-sim {
|
||||
padding: 2rem 1rem 3rem;
|
||||
}
|
||||
|
||||
.top {
|
||||
display: grid;
|
||||
grid-template-columns: 260px 1fr 220px;
|
||||
gap: 1.5rem;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.top {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.actions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Brand placeholder */
|
||||
.brand-box {
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
border-radius: 12px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-weight: 700;
|
||||
opacity: 0.9;
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
background: rgba(255,255,255,0.04);
|
||||
}
|
||||
|
||||
/* Display */
|
||||
.display {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
letter-spacing: 0.12em;
|
||||
opacity: 0.85;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 3rem;
|
||||
line-height: 1.1;
|
||||
margin: 0.25rem 0 0.75rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: 0;
|
||||
border-radius: 10px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
background: rgba(255,255,255,0.08);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.btn:hover { background: rgba(255,255,255,0.12); }
|
||||
|
||||
.btn.small {
|
||||
padding: 0.45rem 0.75rem;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.bits-control {
|
||||
margin-top: 0.25rem;
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px solid rgba(255,255,255,0.08);
|
||||
}
|
||||
|
||||
.bits-title { opacity: 0.8; margin-bottom: 0.35rem; }
|
||||
|
||||
.bits-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.bits-count {
|
||||
min-width: 2ch;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Bits row */
|
||||
.bits-row {
|
||||
margin-top: 2rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
|
||||
gap: 1.25rem;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.bit-col {
|
||||
text-align: center;
|
||||
padding: 0.5rem 0.25rem;
|
||||
}
|
||||
|
||||
.place {
|
||||
margin: 0.5rem 0 0.5rem;
|
||||
font-size: 2rem;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
|
||||
/* Bulb */
|
||||
.bulb {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
margin: 0 auto 0.25rem;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(255,255,255,0.15);
|
||||
}
|
||||
.bulb.off { opacity: 0.15; }
|
||||
.bulb.on { opacity: 1; }
|
||||
|
||||
/* Light switch (accessible checkbox) */
|
||||
.switch {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64px;
|
||||
height: 36px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.slider {
|
||||
width: 64px;
|
||||
height: 36px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.10);
|
||||
border: 1px solid rgba(255,255,255,0.14);
|
||||
position: relative;
|
||||
transition: transform 120ms ease, background 120ms ease;
|
||||
}
|
||||
|
||||
.slider::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255,255,255,0.85);
|
||||
transition: transform 120ms ease;
|
||||
}
|
||||
|
||||
.switch input:checked + .slider {
|
||||
background: rgba(255,255,255,0.18);
|
||||
}
|
||||
|
||||
.switch input:checked + .slider::after {
|
||||
transform: translateX(28px);
|
||||
}
|
||||
|
||||
/* focus */
|
||||
.switch input:focus-visible + .slider {
|
||||
outline: 3px solid rgba(255,255,255,0.35);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden; clip: rect(0,0,0,0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,17 +1,47 @@
|
||||
---
|
||||
// src/layouts/BaseLayout.astro
|
||||
import '../styles/global.css';
|
||||
|
||||
const { title = "Computing:Box" } = Astro.props;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>{title}</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
<link rel="stylesheet" href="/fonts/DSEG7Classic-Regular.css">
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700;800;900&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<header class="siteNav">
|
||||
<div class="navInner">
|
||||
<a class="brand" href="/">
|
||||
<img class="brandLogo" src="/images/computing-box-logo.svg" alt="Computing:Box logo" />
|
||||
<span class="brandName">Computing:Box</span>
|
||||
</a>
|
||||
|
||||
<nav class="navLinks" aria-label="Site navigation">
|
||||
<a href="/about">About</a>
|
||||
<a href="/binary">Binary</a>
|
||||
<a href="/hexadecimal">Hexadecimal</a>
|
||||
<a href="/hex-colours">Hex Colours</a>
|
||||
<a href="/logic-gates">Logic Gates</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="pageWrap">
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<footer class="siteFooter">
|
||||
<div class="footerInner">
|
||||
<div>Computer Science Concept Simulators</div>
|
||||
<div>© {new Date().getFullYear()} Computing:Box • Created with ♥ by Mr A Lyall</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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>
|
||||
@@ -1,642 +1,101 @@
|
||||
---
|
||||
/**
|
||||
* src/pages/binary.astro
|
||||
* Single-file binary simulator (Unsigned + Two's complement)
|
||||
* Bit width: 4..64
|
||||
*
|
||||
* Requires:
|
||||
* public/fonts/DSEG7Classic-Regular.woff
|
||||
* public/fonts/DSEG7Classic-Regular.ttf
|
||||
*/
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Binary | Computing:Box</title>
|
||||
<BaseLayout title="Binary Simulator | Computing:Box">
|
||||
<div class="binaryPage" id="binaryPage">
|
||||
<button
|
||||
id="toolboxToggle"
|
||||
class="toolboxToggle"
|
||||
type="button"
|
||||
aria-expanded="true"
|
||||
>
|
||||
<span class="toolboxIcon" aria-hidden="true">🧰</span>
|
||||
<span class="toolboxText">TOOLBOX</span>
|
||||
</button>
|
||||
|
||||
<style>
|
||||
:root{
|
||||
--bg: #1f2027;
|
||||
--panel: #22242d;
|
||||
--panel2:#262833;
|
||||
--text: #e8e8ee;
|
||||
--muted: #a9acb8;
|
||||
--accent: #33ff7a;
|
||||
--accent-dim: rgba(51,255,122,.15);
|
||||
--line: rgba(255,255,255,.12);
|
||||
}
|
||||
|
||||
@font-face{
|
||||
font-family: "DSEG7ClassicRegular";
|
||||
src:
|
||||
url("/fonts/DSEG7Classic-Regular.woff") format("woff"),
|
||||
url("/fonts/DSEG7Classic-Regular.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
body{
|
||||
margin:0;
|
||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.wrap{
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 32px 20px 60px;
|
||||
}
|
||||
|
||||
/* Layout: readout left, settings panel right (like your screenshot) */
|
||||
.layout{
|
||||
display:grid;
|
||||
grid-template-columns: 1fr 360px;
|
||||
gap: 22px;
|
||||
align-items:start;
|
||||
}
|
||||
|
||||
.readout{
|
||||
text-align:center;
|
||||
padding: 10px 10px 0;
|
||||
}
|
||||
|
||||
.label{
|
||||
letter-spacing: .18em;
|
||||
font-weight: 700;
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
font-size: 16px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.num{
|
||||
font-family: "DSEG7ClassicRegular", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||
font-weight: 400;
|
||||
color: var(--accent);
|
||||
text-shadow: 0 0 18px var(--accent-dim);
|
||||
}
|
||||
|
||||
.value{
|
||||
font-size: 70px;
|
||||
line-height: 1.0;
|
||||
margin: 6px 0 16px;
|
||||
}
|
||||
|
||||
.binary{
|
||||
font-size: 54px;
|
||||
letter-spacing: .12em;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* Buttons under readout (as requested) */
|
||||
.controls{
|
||||
margin-top: 16px;
|
||||
display:flex;
|
||||
gap: 12px;
|
||||
justify-content:center;
|
||||
flex-wrap:wrap;
|
||||
}
|
||||
|
||||
.btn{
|
||||
background: rgba(255,255,255,.06);
|
||||
border: 1px solid rgba(255,255,255,.14);
|
||||
color: #fff;
|
||||
padding: 12px 14px;
|
||||
border-radius: 12px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
min-width: 160px;
|
||||
}
|
||||
.btn:active{ transform: translateY(1px); }
|
||||
|
||||
/* Settings panel cards */
|
||||
.panel{
|
||||
background: rgba(255,255,255,.04);
|
||||
border: 1px solid rgba(255,255,255,.10);
|
||||
border-radius: 14px;
|
||||
padding: 16px 16px 14px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.panelTitle{
|
||||
font-size: 12px;
|
||||
letter-spacing: .16em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
font-weight: 800;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.panelRow{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
.hint{
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
margin-top: 10px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
/* Reusable toggle switch (for MODE and for each BIT) */
|
||||
.switch{
|
||||
position: relative;
|
||||
width: 56px;
|
||||
height: 34px;
|
||||
display:inline-block;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.switch input{
|
||||
opacity:0;
|
||||
width:0;
|
||||
height:0;
|
||||
}
|
||||
.slider{
|
||||
position:absolute;
|
||||
inset:0;
|
||||
background: rgba(255,255,255,.10);
|
||||
border: 1px solid rgba(255,255,255,.14);
|
||||
border-radius: 999px;
|
||||
transition: .18s ease;
|
||||
}
|
||||
.slider::before{
|
||||
content:"";
|
||||
position:absolute;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
left: 3px;
|
||||
top: 2px;
|
||||
background: rgba(255,255,255,.92);
|
||||
border-radius: 50%;
|
||||
transition: .18s ease;
|
||||
}
|
||||
.switch input:checked + .slider{
|
||||
background: rgba(51,255,122,.20);
|
||||
border-color: rgba(51,255,122,.55);
|
||||
}
|
||||
.switch input:checked + .slider::before{
|
||||
transform: translateX(22px);
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
/* Bit width controls */
|
||||
.bwControls{
|
||||
display:grid;
|
||||
grid-template-columns: 44px 1fr 44px;
|
||||
gap: 10px;
|
||||
align-items:center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.bwBtn{
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255,255,255,.14);
|
||||
background: rgba(255,255,255,.06);
|
||||
color: #fff;
|
||||
font-weight: 900;
|
||||
cursor: pointer;
|
||||
}
|
||||
.bwBtn:active{ transform: translateY(1px); }
|
||||
|
||||
.bwInput{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
gap: 12px;
|
||||
background: rgba(255,255,255,.05);
|
||||
border: 1px solid rgba(255,255,255,.12);
|
||||
border-radius: 12px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
.bwLabel{
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
letter-spacing: .14em;
|
||||
text-transform: uppercase;
|
||||
font-weight: 800;
|
||||
}
|
||||
.bwField{
|
||||
width: 100px;
|
||||
text-align:right;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: var(--accent);
|
||||
font-family: "DSEG7ClassicRegular", ui-monospace, monospace;
|
||||
font-size: 26px;
|
||||
padding: 0;
|
||||
}
|
||||
.bwField::-webkit-outer-spin-button,
|
||||
.bwField::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
||||
.bwField[type=number] { -moz-appearance: textfield; }
|
||||
|
||||
/* Bits grid */
|
||||
.bits{
|
||||
margin-top: 34px;
|
||||
padding-top: 26px;
|
||||
border-top: 1px solid var(--line);
|
||||
display:grid;
|
||||
gap: 18px;
|
||||
align-items:end;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.bit{
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
align-items:center;
|
||||
gap: 10px;
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
.bitVal{
|
||||
font-size: 34px;
|
||||
color: var(--text);
|
||||
opacity: .95;
|
||||
}
|
||||
|
||||
/* Bulb (must come back!) */
|
||||
.bulb{
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,.08);
|
||||
border: 1px solid rgba(255,255,255,.12);
|
||||
box-shadow: none;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.bulb.on{
|
||||
background: #ffd86b;
|
||||
border-color: rgba(255,216,107,.7);
|
||||
box-shadow: 0 0 18px rgba(255,216,107,.6);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 1000px){
|
||||
.layout{ grid-template-columns: 1fr; }
|
||||
.value{ font-size: 60px; }
|
||||
.binary{ font-size: 44px; }
|
||||
}
|
||||
@media (max-width: 900px){
|
||||
.value{ font-size: 54px; }
|
||||
.binary{ font-size: 36px; }
|
||||
.btn{ min-width: 140px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="wrap">
|
||||
<section class="layout">
|
||||
<!-- LEFT: readout + buttons -->
|
||||
<div>
|
||||
<section class="topGrid">
|
||||
<div class="leftCol">
|
||||
<div class="readout">
|
||||
<div class="label">Denary</div>
|
||||
<div id="denaryNumber" class="value num">0</div>
|
||||
<div id="denaryNumber" class="num denaryValue">0</div>
|
||||
|
||||
<div class="label">Binary</div>
|
||||
<div id="binaryNumber" class="value binary num">00000000</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn" id="btnCustomBinary" type="button">Custom Binary</button>
|
||||
<button class="btn" id="btnCustomDenary" type="button">Custom Denary</button>
|
||||
<button class="btn" id="btnShiftLeft" type="button">Left Shift</button>
|
||||
<button class="btn" id="btnShiftRight" type="button">Right Shift</button>
|
||||
</div>
|
||||
<div id="binaryNumber" class="num binaryValue">00000000</div>
|
||||
</div>
|
||||
|
||||
<!-- Bits grid (built by JS so bit-width 4..64 works) -->
|
||||
<section id="bitsGrid" class="bits" aria-label="Bit switches"></section>
|
||||
<div class="divider"></div>
|
||||
|
||||
<section class="bitsWrap" aria-label="Bit switches">
|
||||
<div class="bitsGrid" id="bitsGrid"></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT: settings panel -->
|
||||
<aside>
|
||||
<div class="panel">
|
||||
<div class="panelTitle">Mode</div>
|
||||
<div class="panelRow">
|
||||
<span style="font-weight:800;">Unsigned</span>
|
||||
<aside id="toolboxPanel" class="panelCol" aria-label="Toolbox">
|
||||
<div class="card">
|
||||
<div class="cardTitle">Settings</div>
|
||||
|
||||
<!-- MODE TOGGLE uses SAME switch style -->
|
||||
<label class="switch" aria-label="Toggle Two's complement mode">
|
||||
<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>
|
||||
|
||||
<span style="font-weight:800;">Two’s complement</span>
|
||||
</div>
|
||||
<div class="hint">
|
||||
Tip: In two’s complement, the left-most bit (MSB) represents a negative value.
|
||||
</div>
|
||||
<div class="toggleLabel" id="lblTwos">Two's complement</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panelTitle">Bit width</div>
|
||||
<div class="hint" id="modeHint">
|
||||
Tip: In unsigned binary, all bits represent positive values.
|
||||
</div>
|
||||
|
||||
<div class="bwControls">
|
||||
<button class="bwBtn" id="btnBitsDown" type="button" aria-label="Decrease bits">−</button>
|
||||
|
||||
<div class="bwInput">
|
||||
<div class="bwLabel">Bits</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="bitsField"
|
||||
class="bwField"
|
||||
id="bitsInput"
|
||||
class="bitInput"
|
||||
type="number"
|
||||
min="4"
|
||||
inputmode="numeric"
|
||||
min="1"
|
||||
max="64"
|
||||
step="1"
|
||||
value="8"
|
||||
inputmode="numeric"
|
||||
aria-label="Number of bits"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button class="bwBtn" id="btnBitsUp" type="button" aria-label="Increase bits">+</button>
|
||||
<button class="miniBtn" id="btnBitsUp" type="button" aria-label="Increase bits">+</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hint">
|
||||
Minimum 4 bits, maximum 64 bits.
|
||||
<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>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
// ---------- State ----------
|
||||
let bitsCount = 8; // 4..64
|
||||
let isTwos = false; // false = unsigned
|
||||
let bitStates = []; // MSB -> LSB booleans
|
||||
|
||||
// ---------- Helpers ----------
|
||||
function clamp(n, min, max){ return Math.min(max, Math.max(min, n)); }
|
||||
|
||||
function pow2(n){
|
||||
// for up to 64 bits, Number is still OK for *visual simulator*.
|
||||
// if you later want exact 64-bit maths, swap to BigInt end-to-end.
|
||||
return 2 ** n;
|
||||
}
|
||||
|
||||
function getPlaceValues(){
|
||||
// MSB -> LSB
|
||||
// Unsigned: [2^(n-1), ..., 1]
|
||||
// Two's: [-2^(n-1), 2^(n-2), ..., 1]
|
||||
const vals = [];
|
||||
for (let i = 0; i < bitsCount; i++){
|
||||
const power = bitsCount - 1 - i;
|
||||
if (isTwos && i === 0){
|
||||
vals.push(-pow2(power));
|
||||
} else {
|
||||
vals.push(pow2(power));
|
||||
}
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
function binaryString(){
|
||||
return bitStates.map(b => (b ? "1" : "0")).join("");
|
||||
}
|
||||
|
||||
function denaryValue(){
|
||||
const values = getPlaceValues();
|
||||
let sum = 0;
|
||||
for (let i = 0; i < bitsCount; i++){
|
||||
if (bitStates[i]) sum += values[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
function rangeForMode(){
|
||||
if (!isTwos){
|
||||
return { min: 0, max: pow2(bitsCount) - 1 };
|
||||
}
|
||||
return { min: -pow2(bitsCount - 1), max: pow2(bitsCount - 1) - 1 };
|
||||
}
|
||||
|
||||
function updateReadout(){
|
||||
document.getElementById("binaryNumber").innerText = binaryString();
|
||||
document.getElementById("denaryNumber").innerText = String(denaryValue());
|
||||
|
||||
// bulb on/off updates:
|
||||
for (let i = 0; i < bitsCount; i++){
|
||||
const bulb = document.getElementById(`bulb-${i}`);
|
||||
if (bulb) bulb.classList.toggle("on", !!bitStates[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- DOM build for bits grid ----------
|
||||
function setBitsGridColumns(){
|
||||
const grid = document.getElementById("bitsGrid");
|
||||
// make it adapt (up to 16 per row max, then wraps)
|
||||
const cols = clamp(bitsCount, 4, 16);
|
||||
grid.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
|
||||
}
|
||||
|
||||
function buildBitsGrid(){
|
||||
const grid = document.getElementById("bitsGrid");
|
||||
grid.innerHTML = "";
|
||||
|
||||
setBitsGridColumns();
|
||||
|
||||
const values = getPlaceValues();
|
||||
|
||||
for (let i = 0; i < bitsCount; i++){
|
||||
const v = values[i];
|
||||
|
||||
const bit = document.createElement("div");
|
||||
bit.className = "bit";
|
||||
|
||||
const bulb = document.createElement("div");
|
||||
bulb.className = "bulb";
|
||||
bulb.id = `bulb-${i}`;
|
||||
bulb.setAttribute("aria-hidden", "true");
|
||||
|
||||
const val = document.createElement("div");
|
||||
val.className = "bitVal num";
|
||||
// show absolute label for MSB in twos? No — show the actual negative value (clear to students).
|
||||
val.textContent = String(v);
|
||||
|
||||
// IMPORTANT: bit switch must be the SAME style as MODE switch
|
||||
const label = document.createElement("label");
|
||||
label.className = "switch";
|
||||
label.setAttribute("aria-label", `Toggle bit ${v}`);
|
||||
|
||||
const input = document.createElement("input");
|
||||
input.type = "checkbox";
|
||||
input.dataset.index = String(i);
|
||||
input.checked = !!bitStates[i];
|
||||
|
||||
const slider = document.createElement("span");
|
||||
slider.className = "slider";
|
||||
|
||||
input.addEventListener("change", () => {
|
||||
bitStates[i] = input.checked;
|
||||
updateReadout();
|
||||
});
|
||||
|
||||
label.appendChild(input);
|
||||
label.appendChild(slider);
|
||||
|
||||
bit.appendChild(bulb);
|
||||
bit.appendChild(val);
|
||||
bit.appendChild(label);
|
||||
grid.appendChild(bit);
|
||||
}
|
||||
|
||||
updateReadout();
|
||||
}
|
||||
|
||||
// ---------- Set state from binary / denary ----------
|
||||
function setFromBinary(bin){
|
||||
const clean = String(bin).replace(/\s+/g, "");
|
||||
if (!/^[01]+$/.test(clean)) return false;
|
||||
|
||||
const padded = clean.slice(-bitsCount).padStart(bitsCount, "0");
|
||||
bitStates = [...padded].map(ch => ch === "1");
|
||||
|
||||
// update toggles without rebuilding
|
||||
for (let i = 0; i < bitsCount; i++){
|
||||
const toggle = document.querySelector(`input[type="checkbox"][data-index="${i}"]`);
|
||||
if (toggle) toggle.checked = bitStates[i];
|
||||
}
|
||||
updateReadout();
|
||||
return true;
|
||||
}
|
||||
|
||||
function setFromDenary(n){
|
||||
const num = Number(n);
|
||||
if (!Number.isInteger(num)) return false;
|
||||
|
||||
const { min, max } = rangeForMode();
|
||||
if (num < min || num > max) return false;
|
||||
|
||||
// convert to bit pattern
|
||||
let unsignedVal;
|
||||
if (!isTwos){
|
||||
unsignedVal = num;
|
||||
} else {
|
||||
// two's complement representation
|
||||
unsignedVal = num < 0 ? (pow2(bitsCount) + num) : num;
|
||||
}
|
||||
|
||||
// build MSB->LSB bits from unsignedVal
|
||||
const newBits = [];
|
||||
for (let i = 0; i < bitsCount; i++){
|
||||
const power = bitsCount - 1 - i;
|
||||
const bit = Math.floor(unsignedVal / pow2(power)) % 2;
|
||||
newBits.push(bit === 1);
|
||||
}
|
||||
bitStates = newBits;
|
||||
|
||||
for (let i = 0; i < bitsCount; i++){
|
||||
const toggle = document.querySelector(`input[type="checkbox"][data-index="${i}"]`);
|
||||
if (toggle) toggle.checked = bitStates[i];
|
||||
}
|
||||
updateReadout();
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------- Shifts ----------
|
||||
function shiftLeft(){
|
||||
// logical left shift: drop MSB, add 0 at LSB
|
||||
bitStates = bitStates.slice(1).concat(false);
|
||||
buildBitsGrid(); // rebuild keeps labels correct if mode changed (also updates bulbs)
|
||||
}
|
||||
|
||||
function shiftRight(){
|
||||
if (!isTwos){
|
||||
// logical right shift
|
||||
bitStates = [false].concat(bitStates.slice(0, -1));
|
||||
} else {
|
||||
// arithmetic right shift (preserve MSB)
|
||||
const msb = bitStates[0];
|
||||
bitStates = [msb].concat(bitStates.slice(0, -1));
|
||||
}
|
||||
buildBitsGrid();
|
||||
}
|
||||
|
||||
// ---------- Bit width ----------
|
||||
function applyBitsCount(next){
|
||||
const wanted = clamp(Number(next) || 8, 4, 64);
|
||||
if (wanted === bitsCount) return;
|
||||
|
||||
// keep the right-most (LSB) bits when resizing (feels natural)
|
||||
const old = binaryString();
|
||||
bitsCount = wanted;
|
||||
|
||||
// rebuild state from old binary, keeping LSBs
|
||||
const padded = old.slice(-bitsCount).padStart(bitsCount, "0");
|
||||
bitStates = [...padded].map(ch => ch === "1");
|
||||
|
||||
document.getElementById("bitsField").value = String(bitsCount);
|
||||
buildBitsGrid();
|
||||
}
|
||||
|
||||
// ---------- Mode toggle ----------
|
||||
function applyMode(nextIsTwos){
|
||||
const prevDenary = denaryValue(); // keep the *value* if possible
|
||||
isTwos = !!nextIsTwos;
|
||||
|
||||
// Try to keep the denary value, but clamp if it becomes invalid in the new mode
|
||||
const { min, max } = rangeForMode();
|
||||
const kept = clamp(prevDenary, min, max);
|
||||
setFromDenary(kept);
|
||||
|
||||
buildBitsGrid();
|
||||
}
|
||||
|
||||
// ---------- Wire up UI ----------
|
||||
function wireUI(){
|
||||
document.getElementById("btnShiftLeft").addEventListener("click", shiftLeft);
|
||||
document.getElementById("btnShiftRight").addEventListener("click", shiftRight);
|
||||
|
||||
document.getElementById("btnCustomBinary").addEventListener("click", () => {
|
||||
const val = prompt(`Enter a binary number (up to ${bitsCount} bits):`);
|
||||
if (val === null) return;
|
||||
if (!setFromBinary(val)) alert("Invalid input. Use only 0 and 1.");
|
||||
});
|
||||
|
||||
document.getElementById("btnCustomDenary").addEventListener("click", () => {
|
||||
const { min, max } = rangeForMode();
|
||||
const val = prompt(`Enter a denary number (${min} to ${max}):`);
|
||||
if (val === null) return;
|
||||
if (!setFromDenary(val)) alert(`Invalid input. Enter an integer from ${min} to ${max}.`);
|
||||
});
|
||||
|
||||
// Mode toggle
|
||||
const modeToggle = document.getElementById("modeToggle");
|
||||
modeToggle.addEventListener("change", () => applyMode(modeToggle.checked));
|
||||
|
||||
// Bit width +/- and manual entry
|
||||
document.getElementById("btnBitsDown").addEventListener("click", () => applyBitsCount(bitsCount - 1));
|
||||
document.getElementById("btnBitsUp").addEventListener("click", () => applyBitsCount(bitsCount + 1));
|
||||
|
||||
const bitsField = document.getElementById("bitsField");
|
||||
bitsField.addEventListener("change", () => applyBitsCount(bitsField.value));
|
||||
bitsField.addEventListener("blur", () => applyBitsCount(bitsField.value));
|
||||
bitsField.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") applyBitsCount(bitsField.value);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- Init ----------
|
||||
function init(){
|
||||
// default: 8-bit unsigned
|
||||
bitsCount = 8;
|
||||
isTwos = false;
|
||||
bitStates = Array(bitsCount).fill(false);
|
||||
|
||||
document.getElementById("modeToggle").checked = false;
|
||||
document.getElementById("bitsField").value = String(bitsCount);
|
||||
|
||||
wireUI();
|
||||
buildBitsGrid();
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<script src="../scripts/binary.js"></script>
|
||||
</BaseLayout>
|
||||
99
src/pages/hex-colours.astro
Normal file
99
src/pages/hex-colours.astro
Normal 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>
|
||||
94
src/pages/hexadecimal.astro
Normal file
94
src/pages/hexadecimal.astro
Normal file
@@ -0,0 +1,94 @@
|
||||
---
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
---
|
||||
|
||||
<BaseLayout title="Hexadecimal Simulator | Computing:Box">
|
||||
<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;">
|
||||
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>
|
||||
@@ -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>
|
||||
53
src/pages/logic-gates.astro
Normal file
53
src/pages/logic-gates.astro
Normal 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>
|
||||
459
src/scripts/binary.js
Normal file
459
src/scripts/binary.js
Normal file
@@ -0,0 +1,459 @@
|
||||
(() => {
|
||||
/* -----------------------------
|
||||
DOM
|
||||
----------------------------- */
|
||||
const bitsGrid = document.getElementById("bitsGrid");
|
||||
const denaryEl = document.getElementById("denaryNumber");
|
||||
const binaryEl = document.getElementById("binaryNumber");
|
||||
const bitsInput = document.getElementById("bitsInput");
|
||||
|
||||
const modeToggle = document.getElementById("modeToggle");
|
||||
const modeHint = document.getElementById("modeHint");
|
||||
|
||||
// Connect the text labels to the JS
|
||||
const lblUnsigned = document.getElementById("lblUnsigned");
|
||||
const lblTwos = document.getElementById("lblTwos");
|
||||
|
||||
const btnCustomBinary = document.getElementById("btnCustomBinary");
|
||||
const btnCustomDenary = document.getElementById("btnCustomDenary");
|
||||
const btnShiftLeft = document.getElementById("btnShiftLeft");
|
||||
const btnShiftRight = document.getElementById("btnShiftRight");
|
||||
|
||||
const btnDec = document.getElementById("btnDec");
|
||||
const btnInc = document.getElementById("btnInc");
|
||||
const btnClear = document.getElementById("btnClear");
|
||||
const btnRandom = document.getElementById("btnRandom");
|
||||
|
||||
const btnBitsUp = document.getElementById("btnBitsUp");
|
||||
const btnBitsDown = document.getElementById("btnBitsDown");
|
||||
|
||||
const toolboxToggle = document.getElementById("toolboxToggle");
|
||||
const binaryPage = document.getElementById("binaryPage");
|
||||
|
||||
/* -----------------------------
|
||||
STATE
|
||||
----------------------------- */
|
||||
let bitCount = clampInt(Number(bitsInput?.value ?? 8), 1, 64);
|
||||
let bits = new Array(bitCount).fill(false);
|
||||
let randomTimer = null;
|
||||
|
||||
/* -----------------------------
|
||||
HELPERS
|
||||
----------------------------- */
|
||||
function clampInt(n, min, max) {
|
||||
if (!Number.isFinite(n)) return min;
|
||||
return Math.max(min, Math.min(max, Math.trunc(n)));
|
||||
}
|
||||
|
||||
function isTwosMode() {
|
||||
return !!modeToggle?.checked;
|
||||
}
|
||||
|
||||
function pow2Big(n) {
|
||||
return 1n << BigInt(n);
|
||||
}
|
||||
|
||||
function unsignedMaxExclusive(nBits) {
|
||||
return pow2Big(nBits);
|
||||
}
|
||||
|
||||
function unsignedMaxValue(nBits) {
|
||||
return pow2Big(nBits) - 1n;
|
||||
}
|
||||
|
||||
function twosMin(nBits) {
|
||||
return -pow2Big(nBits - 1);
|
||||
}
|
||||
|
||||
function twosMax(nBits) {
|
||||
return pow2Big(nBits - 1) - 1n;
|
||||
}
|
||||
|
||||
function bitsToUnsignedBigInt() {
|
||||
let v = 0n;
|
||||
for (let i = 0; i < bitCount; i++) {
|
||||
if (bits[i]) v += pow2Big(i);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
function unsignedBigIntToBits(vUnsigned) {
|
||||
const span = unsignedMaxExclusive(bitCount);
|
||||
const v = ((vUnsigned % span) + span) % span;
|
||||
for (let i = 0; i < bitCount; i++) {
|
||||
bits[i] = ((v >> BigInt(i)) & 1n) === 1n;
|
||||
}
|
||||
}
|
||||
|
||||
function bitsToSignedBigIntTwos() {
|
||||
const u = bitsToUnsignedBigInt();
|
||||
const signBit = bits[bitCount - 1] === true;
|
||||
if (!signBit) return u;
|
||||
return u - pow2Big(bitCount);
|
||||
}
|
||||
|
||||
function signedBigIntToBitsTwos(vSigned) {
|
||||
const span = pow2Big(bitCount);
|
||||
let v = vSigned;
|
||||
v = ((v % span) + span) % span;
|
||||
unsignedBigIntToBits(v);
|
||||
}
|
||||
|
||||
function formatBinaryGrouped() {
|
||||
let s = "";
|
||||
for (let i = bitCount - 1; i >= 0; i--) {
|
||||
s += bits[i] ? "1" : "0";
|
||||
const posFromLeft = (bitCount - i);
|
||||
if (i !== 0 && posFromLeft % 4 === 0) s += " ";
|
||||
}
|
||||
return s.trimEnd();
|
||||
}
|
||||
|
||||
function updateModeHint() {
|
||||
if (!modeHint) return;
|
||||
if (isTwosMode()) {
|
||||
modeHint.textContent = "Tip: In two's complement, the left-most bit (MSB) represents a negative value.";
|
||||
} else {
|
||||
modeHint.textContent = "Tip: In unsigned binary, all bits represent positive values.";
|
||||
}
|
||||
}
|
||||
|
||||
/* -----------------------------
|
||||
RESPONSIVE GRID COLS
|
||||
----------------------------- */
|
||||
function computeColsForBitsGrid() {
|
||||
if (!bitsGrid) return;
|
||||
const wrap = bitsGrid.parentElement;
|
||||
if (!wrap) return;
|
||||
|
||||
const width = wrap.getBoundingClientRect().width;
|
||||
const minCell = 100;
|
||||
const cols = clampInt(Math.floor(width / minCell), 1, 12);
|
||||
bitsGrid.style.setProperty("--cols", String(Math.min(cols, bitCount)));
|
||||
}
|
||||
|
||||
/* -----------------------------
|
||||
BUILD UI (BITS)
|
||||
----------------------------- */
|
||||
function buildBits(count) {
|
||||
bitCount = clampInt(count, 1, 64);
|
||||
if (bitsInput) bitsInput.value = String(bitCount);
|
||||
|
||||
const oldBits = bits.slice();
|
||||
bits = new Array(bitCount).fill(false);
|
||||
for (let i = 0; i < Math.min(oldBits.length, bitCount); i++) bits[i] = oldBits[i];
|
||||
|
||||
bitsGrid.innerHTML = "";
|
||||
bitsGrid.classList.toggle("bitsFew", bitCount < 8);
|
||||
|
||||
for (let i = bitCount - 1; i >= 0; i--) {
|
||||
const bitEl = document.createElement("div");
|
||||
bitEl.className = "bit";
|
||||
|
||||
bitEl.innerHTML = `
|
||||
<div class="bulb" id="bulb-${i}" aria-hidden="true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M12 2.25a6.75 6.75 0 0 0-6.75 6.75c0 2.537 1.393 4.75 3.493 5.922l.507.282v1.546h5.5v-1.546l.507-.282A6.75 6.75 0 0 0 12 2.25Zm-2.25 16.5v.75a2.25 2.25 0 0 0 4.5 0v-.75h-4.5Z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="bitVal" id="bitLabel-${i}"></div>
|
||||
<label class="switch" aria-label="Toggle bit ${i}">
|
||||
<input type="checkbox" data-index="${i}">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
`;
|
||||
|
||||
bitsGrid.appendChild(bitEl);
|
||||
}
|
||||
|
||||
bitsGrid.querySelectorAll('input[type="checkbox"]').forEach((input) => {
|
||||
input.addEventListener("change", () => {
|
||||
const i = Number(input.dataset.index);
|
||||
bits[i] = input.checked;
|
||||
updateUI();
|
||||
});
|
||||
});
|
||||
|
||||
computeColsForBitsGrid();
|
||||
updateUI();
|
||||
}
|
||||
|
||||
/* -----------------------------
|
||||
UI UPDATE
|
||||
----------------------------- */
|
||||
function updateBitLabels() {
|
||||
for (let i = 0; i < bitCount; i++) {
|
||||
const label = document.getElementById(`bitLabel-${i}`);
|
||||
if (!label) continue;
|
||||
|
||||
let valStr;
|
||||
if (isTwosMode() && i === bitCount - 1) {
|
||||
valStr = `-${pow2Big(bitCount - 1).toString()}`;
|
||||
} else {
|
||||
valStr = pow2Big(i).toString();
|
||||
}
|
||||
label.textContent = valStr;
|
||||
label.style.setProperty('--len', valStr.length);
|
||||
}
|
||||
}
|
||||
|
||||
function syncSwitchesToBits() {
|
||||
bitsGrid.querySelectorAll('input[type="checkbox"]').forEach((input) => {
|
||||
const i = Number(input.dataset.index);
|
||||
input.checked = !!bits[i];
|
||||
});
|
||||
}
|
||||
|
||||
function updateBulbs() {
|
||||
for (let i = 0; i < bitCount; i++) {
|
||||
const bulb = document.getElementById(`bulb-${i}`);
|
||||
if (!bulb) continue;
|
||||
bulb.classList.toggle("on", bits[i] === true);
|
||||
}
|
||||
}
|
||||
|
||||
function updateReadout() {
|
||||
if (!denaryEl || !binaryEl) return;
|
||||
if (isTwosMode()) {
|
||||
denaryEl.textContent = bitsToSignedBigIntTwos().toString();
|
||||
} else {
|
||||
denaryEl.textContent = bitsToUnsignedBigInt().toString();
|
||||
}
|
||||
binaryEl.textContent = formatBinaryGrouped();
|
||||
}
|
||||
|
||||
function updateUI() {
|
||||
updateModeHint();
|
||||
|
||||
// Toggle the glowing CSS class on the active mode text
|
||||
if (lblUnsigned && lblTwos) {
|
||||
lblUnsigned.classList.toggle("activeMode", !isTwosMode());
|
||||
lblTwos.classList.toggle("activeMode", isTwosMode());
|
||||
}
|
||||
|
||||
updateBitLabels();
|
||||
syncSwitchesToBits();
|
||||
updateBulbs();
|
||||
updateReadout();
|
||||
}
|
||||
|
||||
/* -----------------------------
|
||||
SET FROM BINARY STRING
|
||||
----------------------------- */
|
||||
function setFromBinaryString(binStr) {
|
||||
const clean = String(binStr ?? "").replace(/\s+/g, "");
|
||||
if (!/^[01]+$/.test(clean)) return false;
|
||||
|
||||
const padded = clean.slice(-bitCount).padStart(bitCount, "0");
|
||||
for (let i = 0; i < bitCount; i++) {
|
||||
const charFromRight = padded[padded.length - 1 - i];
|
||||
bits[i] = charFromRight === "1";
|
||||
}
|
||||
|
||||
updateUI();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* -----------------------------
|
||||
SET FROM DENARY INPUT
|
||||
----------------------------- */
|
||||
function setFromDenaryInput(vStr) {
|
||||
const raw = String(vStr ?? "").trim();
|
||||
if (!raw) return false;
|
||||
|
||||
let v;
|
||||
try {
|
||||
if (!/^-?\d+$/.test(raw)) return false;
|
||||
v = BigInt(raw);
|
||||
} catch { return false; }
|
||||
|
||||
if (isTwosMode()) {
|
||||
const min = twosMin(bitCount);
|
||||
const max = twosMax(bitCount);
|
||||
if (v < min || v > max) return false;
|
||||
signedBigIntToBitsTwos(v);
|
||||
} else {
|
||||
if (v < 0n || v > unsignedMaxValue(bitCount)) return false;
|
||||
unsignedBigIntToBits(v);
|
||||
}
|
||||
|
||||
updateUI();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* -----------------------------
|
||||
SHIFTS
|
||||
----------------------------- */
|
||||
function shiftLeft() {
|
||||
for (let i = bitCount - 1; i >= 1; i--) { bits[i] = bits[i - 1]; }
|
||||
bits[0] = false;
|
||||
updateUI();
|
||||
}
|
||||
|
||||
function shiftRight() {
|
||||
const msb = bits[bitCount - 1];
|
||||
for (let i = 0; i < bitCount - 1; i++) { bits[i] = bits[i + 1]; }
|
||||
bits[bitCount - 1] = isTwosMode() ? msb : false;
|
||||
updateUI();
|
||||
}
|
||||
|
||||
/* -----------------------------
|
||||
CLEAR / INC / DEC
|
||||
----------------------------- */
|
||||
function clearAll() {
|
||||
bits = [];
|
||||
if (modeToggle) modeToggle.checked = false;
|
||||
buildBits(8);
|
||||
}
|
||||
|
||||
function increment() {
|
||||
if (isTwosMode()) {
|
||||
const min = twosMin(bitCount);
|
||||
const max = twosMax(bitCount);
|
||||
let v = bitsToSignedBigIntTwos() + 1n;
|
||||
if (v > max) v = min;
|
||||
signedBigIntToBitsTwos(v);
|
||||
} else {
|
||||
const span = unsignedMaxExclusive(bitCount);
|
||||
unsignedBigIntToBits((bitsToUnsignedBigInt() + 1n) % span);
|
||||
}
|
||||
updateUI();
|
||||
}
|
||||
|
||||
function decrement() {
|
||||
if (isTwosMode()) {
|
||||
const min = twosMin(bitCount);
|
||||
const max = twosMax(bitCount);
|
||||
let v = bitsToSignedBigIntTwos() - 1n;
|
||||
if (v < min) v = max;
|
||||
signedBigIntToBitsTwos(v);
|
||||
} else {
|
||||
const span = unsignedMaxExclusive(bitCount);
|
||||
unsignedBigIntToBits((bitsToUnsignedBigInt() - 1n + span) % span);
|
||||
}
|
||||
updateUI();
|
||||
}
|
||||
|
||||
/* -----------------------------
|
||||
RANDOM
|
||||
----------------------------- */
|
||||
function cryptoRandomBigInt(maxExclusive) {
|
||||
if (maxExclusive <= 0n) return 0n;
|
||||
const bitLen = maxExclusive.toString(2).length;
|
||||
const byteLen = Math.ceil(bitLen / 8);
|
||||
|
||||
while (true) {
|
||||
const bytes = new Uint8Array(byteLen);
|
||||
crypto.getRandomValues(bytes);
|
||||
let x = 0n;
|
||||
for (const b of bytes) x = (x << 8n) | BigInt(b);
|
||||
|
||||
const extraBits = BigInt(byteLen * 8 - bitLen);
|
||||
if (extraBits > 0n) x = x >> extraBits;
|
||||
|
||||
if (x < maxExclusive) return x;
|
||||
}
|
||||
}
|
||||
|
||||
function setRandomOnce() {
|
||||
const span = unsignedMaxExclusive(bitCount);
|
||||
const u = cryptoRandomBigInt(span);
|
||||
unsignedBigIntToBits(u);
|
||||
updateUI();
|
||||
}
|
||||
|
||||
function setRandomRunning(isRunning) {
|
||||
if (!btnRandom) return;
|
||||
btnRandom.classList.toggle("btnRandomRunning", !!isRunning);
|
||||
}
|
||||
|
||||
function runRandomBriefly() {
|
||||
if (randomTimer) {
|
||||
clearInterval(randomTimer);
|
||||
randomTimer = null;
|
||||
}
|
||||
|
||||
setRandomRunning(true);
|
||||
const start = Date.now();
|
||||
const durationMs = 1125;
|
||||
const tickMs = 80;
|
||||
|
||||
randomTimer = setInterval(() => {
|
||||
setRandomOnce();
|
||||
if (Date.now() - start >= durationMs) {
|
||||
clearInterval(randomTimer);
|
||||
randomTimer = null;
|
||||
setRandomRunning(false);
|
||||
}
|
||||
}, tickMs);
|
||||
}
|
||||
|
||||
/* -----------------------------
|
||||
BIT WIDTH CONTROLS
|
||||
----------------------------- */
|
||||
function setBitWidth(n) {
|
||||
const v = clampInt(n, 1, 64);
|
||||
buildBits(v);
|
||||
}
|
||||
|
||||
/* -----------------------------
|
||||
TOOLBOX TOGGLE
|
||||
----------------------------- */
|
||||
function setToolboxCollapsed(collapsed) {
|
||||
if (!binaryPage) return;
|
||||
binaryPage.classList.toggle("toolboxCollapsed", !!collapsed);
|
||||
const expanded = !collapsed;
|
||||
toolboxToggle?.setAttribute("aria-expanded", expanded ? "true" : "false");
|
||||
}
|
||||
|
||||
/* -----------------------------
|
||||
EVENTS
|
||||
----------------------------- */
|
||||
modeToggle?.addEventListener("change", updateUI);
|
||||
|
||||
btnCustomBinary?.addEventListener("click", () => {
|
||||
const v = prompt(`Enter binary (spaces allowed). Current width: ${bitCount} bits`);
|
||||
if (v === null) return;
|
||||
if (!setFromBinaryString(v)) alert("Invalid binary");
|
||||
});
|
||||
|
||||
btnCustomDenary?.addEventListener("click", () => {
|
||||
const v = prompt(
|
||||
isTwosMode()
|
||||
? `Enter denary (${twosMin(bitCount).toString()} to ${twosMax(bitCount).toString()}):`
|
||||
: `Enter denary (0 to ${unsignedMaxValue(bitCount).toString()}):`
|
||||
);
|
||||
if (v === null) return;
|
||||
if (!setFromDenaryInput(v)) alert("Invalid denary for current mode/bit width");
|
||||
});
|
||||
|
||||
btnShiftLeft?.addEventListener("click", shiftLeft);
|
||||
btnShiftRight?.addEventListener("click", shiftRight);
|
||||
|
||||
btnInc?.addEventListener("click", increment);
|
||||
btnDec?.addEventListener("click", decrement);
|
||||
|
||||
btnClear?.addEventListener("click", clearAll);
|
||||
btnRandom?.addEventListener("click", runRandomBriefly);
|
||||
|
||||
btnBitsUp?.addEventListener("click", () => setBitWidth(bitCount + 1));
|
||||
btnBitsDown?.addEventListener("click", () => setBitWidth(bitCount - 1));
|
||||
|
||||
bitsInput?.addEventListener("change", () => setBitWidth(Number(bitsInput.value)));
|
||||
|
||||
toolboxToggle?.addEventListener("click", () => {
|
||||
const isCollapsed = binaryPage?.classList.contains("toolboxCollapsed");
|
||||
setToolboxCollapsed(!isCollapsed);
|
||||
});
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
computeColsForBitsGrid();
|
||||
});
|
||||
|
||||
/* -----------------------------
|
||||
INIT
|
||||
----------------------------- */
|
||||
updateModeHint();
|
||||
buildBits(bitCount);
|
||||
setToolboxCollapsed(false);
|
||||
})();
|
||||
241
src/scripts/hexColours.js
Normal file
241
src/scripts/hexColours.js
Normal 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
312
src/scripts/hexadecimal.js
Normal 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
497
src/scripts/logicGates.js
Normal 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!
|
||||
})();
|
||||
@@ -1,54 +0,0 @@
|
||||
let bits = [128,64,32,16,8,4,2,1];
|
||||
let state = Array(8).fill(0);
|
||||
|
||||
const denaryEl = document.getElementById("denaryNumber");
|
||||
const binaryEl = document.getElementById("binaryNumber");
|
||||
const bitEls = document.querySelectorAll(".bit");
|
||||
|
||||
bitEls.forEach((el, i) => {
|
||||
el.addEventListener("click", () => {
|
||||
state[i] = state[i] ? 0 : 1;
|
||||
el.classList.toggle("on");
|
||||
update();
|
||||
});
|
||||
});
|
||||
|
||||
function update() {
|
||||
const denary = state.reduce((sum, bit, i) => sum + bit * bits[i], 0);
|
||||
denaryEl.textContent = denary;
|
||||
binaryEl.textContent = state.join("");
|
||||
}
|
||||
|
||||
function requestBinary() {
|
||||
const input = prompt("Enter 8-bit binary:");
|
||||
if (!/^[01]{8}$/.test(input)) return;
|
||||
state = input.split("").map(Number);
|
||||
bitEls.forEach((el,i)=>el.classList.toggle("on",state[i]));
|
||||
update();
|
||||
}
|
||||
|
||||
function requestDenary() {
|
||||
const input = parseInt(prompt("Enter denary (0–255)"),10);
|
||||
if (isNaN(input) || input < 0 || input > 255) return;
|
||||
|
||||
let value = input;
|
||||
state = bits.map(b => {
|
||||
if (value >= b) {
|
||||
value -= b;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
bitEls.forEach((el,i)=>el.classList.toggle("on",state[i]));
|
||||
update();
|
||||
}
|
||||
|
||||
function shiftBinary(dir) {
|
||||
if (dir === "left") state.shift(), state.push(0);
|
||||
if (dir === "right") state.pop(), state.unshift(0);
|
||||
bitEls.forEach((el,i)=>el.classList.toggle("on",state[i]));
|
||||
update();
|
||||
}
|
||||
|
||||
update();
|
||||
@@ -1,52 +0,0 @@
|
||||
/* 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); }
|
||||
@@ -1,68 +0,0 @@
|
||||
.binary-container {
|
||||
max-width: 1100px;
|
||||
margin: auto;
|
||||
padding: 2rem;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.display {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #00ff66;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.controls button {
|
||||
margin: 0.25rem;
|
||||
padding: 0.6rem 1rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.bits {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
gap: 1rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.bit {
|
||||
width: 40px;
|
||||
height: 80px;
|
||||
background: #333;
|
||||
border-radius: 20px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bit::after {
|
||||
content: "";
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: #555;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
left: 4px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.bit.on {
|
||||
background: #00c853;
|
||||
}
|
||||
|
||||
.bit.on::after {
|
||||
bottom: 42px;
|
||||
background: #eaffea;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
@font-face {
|
||||
font-family: "DSEG7";
|
||||
src: url("/fonts/DSEG7Classic-Regular.ttf") format("truetype");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.dseg {
|
||||
font-family: "DSEG7", monospace;
|
||||
letter-spacing: 0.15em;
|
||||
}
|
||||
190
src/styles/global.css
Normal file
190
src/styles/global.css
Normal file
@@ -0,0 +1,190 @@
|
||||
/* Global fonts */
|
||||
@font-face {
|
||||
font-family: "SevenSegment";
|
||||
src: url("/fonts/Seven-Segment.woff2") format("woff2"),
|
||||
url("/fonts/Seven-Segment.woff") format("woff");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "DSEG7Classic";
|
||||
src: url("/fonts/DSEG7Classic-Regular.woff") format("woff"),
|
||||
url("/fonts/DSEG7Classic-Regular.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
--nav-h: 92px;
|
||||
--bg: #1f2027;
|
||||
--text: #e8e8ee;
|
||||
--muted: #a9acb8;
|
||||
--line: rgba(255,255,255,.10);
|
||||
|
||||
--ui-font: "Inter", system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
|
||||
--num-font: "DSEG7Classic", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
--bit-font: "SevenSegment", monospace;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body { height: 100%; }
|
||||
body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--ui-font); display: flex; flex-direction: column; }
|
||||
|
||||
/* --- BASE LAYOUT --- */
|
||||
.siteNav { position: sticky; top: 0; z-index: 50; height: var(--nav-h); background: rgba(0,0,0,.10); border-bottom: 1px solid var(--line); backdrop-filter: blur(8px); }
|
||||
.navInner { height: 100%; max-width: 1400px; margin: 0 auto; padding: 0 20px; display: flex; align-items: center; justify-content: space-between; gap: 24px; }
|
||||
.brand { display: flex; align-items: center; gap: 12px; text-decoration: none; color: var(--text); }
|
||||
.brandLogo { width: 2.5em; height: 2.5em; image-rendering: pixelated; }
|
||||
.brandName { letter-spacing: .12em; font-weight: 900; 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; }
|
||||
}
|
||||
|
||||
@container (max-width: 800px) {
|
||||
.readoutContainer { flex-direction: column; gap: 24px; }
|
||||
.colorPreviewSide { padding-top: 0; }
|
||||
.colorGroupWrap { gap: 6px; }
|
||||
.colorGroup { padding: 6px; gap: 6px; border-radius: 12px; }
|
||||
.hexCard { padding: 8px 4px; width: 90px; gap: 8px; border-radius: 10px; }
|
||||
.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; }
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) { .binaryPage { --toolbox-w: 330px; } .denaryValue { font-size: 48px; } .hexValue { font-size: 40px; } .binaryValue { font-size: 32px; } }
|
||||
@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)); } }
|
||||
247
src/styles/logic-gates.css
Normal file
247
src/styles/logic-gates.css
Normal 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); }
|
||||
Reference in New Issue
Block a user