Compare commits

...

3 Commits

Author SHA1 Message Date
09e0499ba3 Full initial 2.0 build
All checks were successful
Pre-release on non-main branches / prerelease (push) Successful in 31s
Signed-off-by: Alexander Lyall <alex@adcm.uk>
2026-03-01 17:30:41 +00:00
7ecd065305 Updates to canvas of logic gate simulator:
All checks were successful
Pre-release on non-main branches / prerelease (push) Successful in 29s
- Addition of zoom feature

Signed-off-by: Alexander Lyall <alex@adcm.uk>
2026-03-01 16:37:02 +00:00
e0e72c17e8 Fully functional logic gates page
All checks were successful
Pre-release on non-main branches / prerelease (push) Successful in 28s
Signed-off-by: Alexander Lyall <alex@adcm.uk>
2026-03-01 16:32:27 +00:00
33 changed files with 1167 additions and 1881 deletions

View File

@@ -1,3 +1,20 @@
<!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">
<!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><script>
var _paq = window._paq = window._paq || [];
_paq.push(["setCookieDomain", "*.www.computingbox.co.uk"]);
_paq.push(["setDomains", ["*.www.computingbox.co.uk","*.csbox.mrdaviscsit.uk","*.csbox.mrlyall.co.uk","*.csbox.mrlyall.uk"]]);
_paq.push(["enableCrossDomainLinking"]);
_paq.push(["disableCookies"]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//analytics.adcmnetworks.co.uk/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '2']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script><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/about.CswAWODG.css">
<link rel="stylesheet" href="/_astro/binary.9peKc0z2.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> <a href="/pc-builder">PC Components</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>
</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 Alexander Lyall</div> <div style="margin-top: 5px;"> <a href="/copyright" style="color: var(--muted); text-decoration: underline;">Copyright Notice</a>
<a href="/legal-code" style="color: var(--muted); text-decoration: underline;">Legal Code</a> </div> </div> </footer> </body></html>

View File

@@ -1,3 +1,20 @@
<!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;">
<!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><script>
var _paq = window._paq = window._paq || [];
_paq.push(["setCookieDomain", "*.www.computingbox.co.uk"]);
_paq.push(["setDomains", ["*.www.computingbox.co.uk","*.csbox.mrdaviscsit.uk","*.csbox.mrlyall.co.uk","*.csbox.mrlyall.uk"]]);
_paq.push(["enableCrossDomainLinking"]);
_paq.push(["disableCookies"]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//analytics.adcmnetworks.co.uk/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '2']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script><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/about.CswAWODG.css">
<link rel="stylesheet" href="/_astro/binary.9peKc0z2.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> <a href="/pc-builder">PC Components</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>
</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 Alexander Lyall</div> <div style="margin-top: 5px;"> <a href="/copyright" style="color: var(--muted); text-decoration: underline;">Copyright Notice</a>
<a href="/legal-code" style="color: var(--muted); text-decoration: underline;">Legal Code</a> </div> </div> </footer> </body></html>

26
dist/index.html vendored
View File

@@ -1,7 +1,19 @@
<!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>
<!DOCTYPE html><html lang="en"> <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Welcome | Computing:Box</title><script>
var _paq = window._paq = window._paq || [];
_paq.push(["setCookieDomain", "*.www.computingbox.co.uk"]);
_paq.push(["setDomains", ["*.www.computingbox.co.uk","*.csbox.mrdaviscsit.uk","*.csbox.mrlyall.co.uk","*.csbox.mrlyall.uk"]]);
_paq.push(["enableCrossDomainLinking"]);
_paq.push(["disableCookies"]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//analytics.adcmnetworks.co.uk/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '2']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script><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/about.CswAWODG.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> <a href="/pc-builder">PC Components</a> </nav> </div> </header> <main class="pageWrap"> <div style="display: flex; align-items: center; justify-content: space-between; gap: 40px; min-height: 60vh; padding: 40px 0;"> <div style="flex: 1;"> <p style="color: var(--accent); font-weight: 800; letter-spacing: 2px; text-transform: uppercase; margin-bottom: 10px;">Version 2.0 Now Live</p> <h1 class="brandName" style="font-size: 48px; line-height: 1.1; margin-bottom: 24px;">Understand Computing concepts better.</h1> <p style="font-size: 18px; color: var(--muted);">
Interactive simulators for Binary, Hexadecimal, Logic Gates, and Computer Components designed for the UK curriculum.
</p> <div style="display: flex; gap: 16px; margin-top: 32px;"> <a href="/about" class="btn btnAccent" style="text-decoration: none; padding: 14px 28px;">Learn More</a> <a href="/binary" class="btn" style="text-decoration: none; padding: 14px 28px;">Get Started</a> </div> </div> <div style="flex: 1; text-align: right;"> <img src="/images/computing-box-logo.svg" alt="Computing Box Logo" style="width: 100%; max-width: 450px; filter: drop-shadow(0 0 50px rgba(40, 240, 122, 0.15));"> </div> </div> </main> <footer class="siteFooter"> <div class="footerInner"> <div>Computer Science Concept Simulators</div> <div>© 2026 Computing:Box • Created with ♥ by Alexander Lyall</div> <div style="margin-top: 5px;"> <a href="/copyright" style="color: var(--muted); text-decoration: underline;">Copyright Notice</a>
<a href="/legal-code" style="color: var(--muted); text-decoration: underline;">Legal Code</a> </div> </div> </footer> </body></html>

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "computing-box",
"version": "0.0.1",
"version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {

View File

@@ -1,7 +1,7 @@
{
"name": "computing-box",
"type": "module",
"version": "0.0.1",
"version": "2.0.0",
"scripts": {
"dev": "astro dev",
"build": "astro build",

View File

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

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,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>

View File

@@ -1,6 +1,5 @@
---
import '../styles/global.css';
import "../styles/global.css";
const { title = "Computing:Box" } = Astro.props;
---
@@ -11,6 +10,22 @@ const { title = "Computing:Box" } = Astro.props;
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>{title}</title>
<script is:inline>
var _paq = window._paq = window._paq || [];
_paq.push(["setCookieDomain", "*.www.computingbox.co.uk"]);
_paq.push(["setDomains", ["*.www.computingbox.co.uk","*.csbox.mrdaviscsit.uk","*.csbox.mrlyall.co.uk","*.csbox.mrlyall.uk"]]);
_paq.push(["enableCrossDomainLinking"]);
_paq.push(["disableCookies"]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//analytics.adcmnetworks.co.uk/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '2']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<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">
@@ -29,6 +44,7 @@ const { title = "Computing:Box" } = Astro.props;
<a href="/hexadecimal">Hexadecimal</a>
<a href="/hex-colours">Hex Colours</a>
<a href="/logic-gates">Logic Gates</a>
<a href="/pc-builder">PC Components</a>
</nav>
</div>
</header>
@@ -40,7 +56,11 @@ const { title = "Computing:Box" } = Astro.props;
<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>© {new Date().getFullYear()} Computing:Box • Created with ♥ by Alexander Lyall</div>
<div style="margin-top: 5px;">
<a href="/copyright" style="color: var(--muted); text-decoration: underline;">Copyright Notice</a> •
<a href="/legal-code" style="color: var(--muted); text-decoration: underline;">Legal Code</a>
</div>
</div>
</footer>
</body>

37
src/pages/about.astro Normal file
View File

@@ -0,0 +1,37 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="About | Computing:Box">
<article style="max-width: 900px; margin: 0 auto;">
<div class="card" style="margin-bottom: 40px;">
<h1 class="brandName" style="font-size: 32px; color: var(--text); margin-bottom: 16px;">The New Computing:Box Experience</h1>
<p style="color: var(--muted); font-size: 18px; line-height: 1.6;">
The platform has been rebuilt from the ground up to provide a deeper, more professional simulation environment.
We've introduced several key simulators and interface upgrades to support the modern Computing classroom:
</p>
<div class="divider"></div>
<ul style="list-style: none; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> New User Interface (Responsive)</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Two's Complement Simulator</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Extended Binary Simulator (Custom bit sizes)</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Unified Binary Simulator (Unsigned & Two's Complement)</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Extended Hexadecimal Simulator</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Unified Hexadecimal Simulator (GCSE & A Level)</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Enhanced Gate Simulator (Truth Table Creator)</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Compound Gate Simulator</li>
<li style="color: var(--text);"><span style="color: var(--accent);">✔</span> Computer Components Simulator</li>
</ul>
</div>
<div class="card">
<h2 class="brandName" style="font-size: 24px; margin-bottom: 12px;">Educational Impact</h2>
<p style="color: var(--muted);">
Computing:Box is designed to help students learn computing concepts step by step, using clear visuals and simple interactions.
By changing values and seeing results straight away, students can build understanding at their own pace.
</p>
</div>
</article>
</BaseLayout>

View File

@@ -1,5 +1,6 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import "../styles/number-simulators.css";
---
<BaseLayout title="Binary Simulator | Computing:Box">

30
src/pages/copyright.astro Normal file
View File

@@ -0,0 +1,30 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="Copyright Notice | Computing:Box">
<div class="card" style="max-width: 800px; margin: 0 auto;">
<h1 class="brandName" style="color: var(--accent); margin-bottom: 20px;">Copyright Notice</h1>
<p style="color: var(--text);">
<a href="https://www.computingbox.co.uk" style="color: var(--accent); text-decoration: none;">Computing:Box</a>
© 2024 by <a href="https://git.adcmnetworks.co.uk/alexander.lyall" style="color: var(--accent); text-decoration: none;">Alexander Lyall</a> is licensed under
<strong>Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International</strong>.
</p>
<div class="divider"></div>
<h2 class="brandName" style="font-size: 20px;">What Does This Mean For You?</h2>
<p style="color: var(--muted); font-size: 1.2em;">You are free to:</p>
<ul style="color: var(--muted); line-height: 1.6; margin-bottom: 20px;">
<li><strong>Share</strong> — copy and redistribute the material in any medium or format.</li>
<li><strong>Adapt</strong> — remix, transform, and build upon the material.</li>
</ul>
<h2 class="brandName" style="font-size: 20px;">Under the following terms:</h2>
<ul style="color: var(--muted); line-height: 1.6;">
<li><strong>Attribution</strong> — You must give appropriate credit, provide a link to the license, and indicate if changes were made.</li>
<li><strong>NonCommercial</strong> — You may not use the material for commercial purposes.</li>
<li><strong>ShareAlike</strong> — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.</li>
</ul>
</div>
</BaseLayout>

View File

@@ -1,5 +1,6 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import "../styles/number-simulators.css";
---
<BaseLayout title="Hex Colours Simulator | Computing:Box">

View File

@@ -1,5 +1,6 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import "../styles/number-simulators.css";
---
<BaseLayout title="Hexadecimal Simulator | Computing:Box">

View File

@@ -1,11 +1,23 @@
---
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.
import BaseLayout from "../layouts/BaseLayout.astro";
---
<Layout>
<Welcome />
</Layout>
<BaseLayout title="Welcome | Computing:Box">
<div style="display: flex; align-items: center; justify-content: space-between; gap: 40px; min-height: 60vh; padding: 40px 0;">
<div style="flex: 1;">
<p style="color: var(--accent); font-weight: 800; letter-spacing: 2px; text-transform: uppercase; margin-bottom: 10px;">Version 2.0 Now Live</p>
<h1 class="brandName" style="font-size: 48px; line-height: 1.1; margin-bottom: 24px;">Understand Computing concepts better.</h1>
<p style="font-size: 18px; color: var(--muted);">
Interactive simulators for Binary, Hexadecimal, Logic Gates, and Computer Components designed for the UK curriculum.
</p>
<div style="display: flex; gap: 16px; margin-top: 32px;">
<a href="/about" class="btn btnAccent" style="text-decoration: none; padding: 14px 28px;">Learn More</a>
<a href="/binary" class="btn" style="text-decoration: none; padding: 14px 28px;">Get Started</a>
</div>
</div>
<div style="flex: 1; text-align: right;">
<img src="/images/computing-box-logo.svg" alt="Computing Box Logo" style="width: 100%; max-width: 450px; filter: drop-shadow(0 0 50px rgba(40, 240, 122, 0.15));" />
</div>
</div>
</BaseLayout>

View File

@@ -0,0 +1,20 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="Legal Code | Computing:Box">
<div class="card" style="max-width: 900px; margin: 0 auto;">
<h1 class="brandName" style="color: var(--accent); margin-bottom: 20px;">Legal Code</h1>
<div style="background: rgba(255, 193, 7, 0.1); border: 1px solid #ffc107; padding: 20px; border-radius: 12px; margin-bottom: 24px;">
<h3 style="color: #ffc107; margin-top: 0; font-family: var(--ui-font);">About the license and Creative Commons</h3>
<p style="color: #eee; margin-bottom: 0; font-size: 14px;">
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.
</p>
</div>
<p style="color: var(--muted); font-size: 14px; line-height: 1.6;">
By using this licensed material, you accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License.
</p>
</div>
</BaseLayout>

View File

@@ -4,58 +4,51 @@ import "../styles/logic-gates.css";
---
<BaseLayout title="Logic Gate Builder | Computing:Box">
<div class="binaryPage" id="logicPage">
<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>
<section class="topGrid" style="display:flex; flex-direction:column; height: 100%;">
<div class="lg-top-header">
<div class="lg-title">Interactive Logic Circuit Builder</div>
<div class="lg-subtitle">
Drag items from the toolbox. Drag from output ports to input ports to wire. Click a wire/node and press <kbd>Delete</kbd>. Scroll to Zoom.
</div>
</div>
<div class="lg-workspace" id="workspace">
<div class="readout">
<div class="readoutBlock">
<div class="label" style="margin-bottom: 8px;">Interactive Simulator</div>
<div class="num denaryValue" style="font-size: 56px; line-height: 1;">LOGIC GATES</div>
</div>
<div style="color: var(--muted); font-size: 15px; font-family: var(--ui-font); text-align: center; max-width: 800px; margin-top: 12px;">
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 style="background:rgba(255,255,255,0.1); padding:2px 6px; border-radius:4px; font-family:var(--ui-font);">Delete</kbd> to remove it.
</div>
<div class="lg-zoom-controls">
<button class="lg-zoom-btn" id="btnZoomIn" title="Zoom In">+</button>
<button class="lg-zoom-btn" id="btnZoomOut" title="Zoom Out"></button>
<button class="lg-zoom-btn" id="btnZoomReset" title="Reset View" style="font-size: 16px;">⌂</button>
</div>
<div class="divider"></div>
<div class="lg-workspace" id="workspace">
<div class="lg-viewport" id="viewport">
<svg class="lg-svg-layer" id="wireLayer"></svg>
</div>
</section>
<aside id="toolboxPanel" class="panelCol" aria-label="Toolbox">
</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 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>
<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>

View File

@@ -0,0 +1,65 @@
---
import BaseLayout from "../layouts/BaseLayout.astro";
import "../styles/pc-builder.css";
---
<BaseLayout title="PC Part Simulator | Computing:Box">
<div id="pcPage" class="pb-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="pb-top-header">
<div class="pb-title">PC Part Simulator</div>
<div class="pb-subtitle">
Build inside the Case! Snap the Motherboard into the chassis, then populate the slots. Add the side panel when done. <strong>Double-Click</strong> parts to inspect in 3D.
</div>
</div>
<div class="pb-workspace" id="workspace">
<div class="pb-zoom-controls">
<button class="pb-zoom-btn" id="btnZoomIn" title="Zoom In">+</button>
<button class="pb-zoom-btn" id="btnZoomOut" title="Zoom Out"></button>
<button class="pb-zoom-btn" id="btnZoomReset" title="Reset View" style="font-size: 16px;">⌂</button>
</div>
<div class="pb-viewport" id="viewport">
<svg class="pb-svg-layer pb-wire-internal" id="wireLayerInternal"></svg>
<svg class="pb-svg-layer pb-wire-external" id="wireLayerExternal"></svg>
</div>
</div>
<aside id="toolboxPanel" class="pb-toolbox" aria-label="Toolbox">
<div class="card">
<div class="cardTitle">Inventory</div>
<div class="tb-icon-grid" id="toolboxGrid"></div>
</div>
<div class="card">
<div class="cardTitle">System Diagnostics</div>
<div style="font-family: var(--ui-font); font-size: 12px; color: var(--muted); margin-bottom: 12px;">Live pre-flight boot analysis.</div>
<div class="specs-panel" id="buildSpecsContainer"></div>
</div>
<div class="card">
<div class="cardTitle">Tools</div>
<button class="btn btnReset btnWide" id="btnClearBoard" type="button" style="margin-bottom:0;">Disassemble PC</button>
</div>
</aside>
<div id="inspectModal" class="inspect-modal">
<div class="inspect-close" id="inspectClose">&times;</div>
<div id="inspectName" style="color: white; font-family: var(--ui-font); font-size: 28px; font-weight: 800; letter-spacing: 2px; text-transform: uppercase;"></div>
<div class="inspect-stage" id="inspectStage">
<div class="inspect-object" id="inspectObject"></div>
</div>
<div style="color: var(--muted); font-family: var(--ui-font); margin-top: 20px; font-size: 14px;">Move mouse to rotate component. Scroll to zoom.</div>
</div>
</div>
<script src="../scripts/pcBuilder.js"></script>
</BaseLayout>

View File

@@ -4,6 +4,7 @@
(() => {
/* --- DOM Elements --- */
const workspace = document.getElementById("workspace");
const viewport = document.getElementById("viewport");
const wireLayer = document.getElementById("wireLayer");
const ttContainer = document.getElementById("truthTableContainer");
const toolboxGrid = document.getElementById("toolboxGrid");
@@ -12,7 +13,7 @@
const toolboxToggle = document.getElementById("toolboxToggle");
const logicPage = document.getElementById("logicPage");
/* --- ANSI Gate SVGs (Strict 100x50 with built-in tails) --- */
/* --- ANSI Gate SVGs --- */
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>`,
@@ -32,19 +33,22 @@
let nextNodeId = 1;
let nextWireId = 1;
let inputCount = 0;
let outputCount = 0;
// Interaction State
let isDraggingNode = null;
let dragOffset = { x: 0, y: 0 };
let clickStartX = 0, clickStartY = 0; // Fixes switch drag conflict
let clickStartX = 0, clickStartY = 0;
let wiringStart = null;
let tempWirePath = null;
let selectedWireId = null;
let selectedNodeId = null;
// Camera State (Pan & Zoom)
let panX = 0, panY = 0, zoom = 1;
let isPanning = false;
let panStart = { x: 0, y: 0 };
/* --- Setup Toolbox --- */
function initToolbox() {
if(!toolboxGrid) return;
@@ -58,7 +62,6 @@
<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">
@@ -67,7 +70,6 @@
</div>
`;
});
toolboxGrid.innerHTML = html;
document.querySelectorAll('.drag-item').forEach(item => {
@@ -78,7 +80,21 @@
});
}
/* --- Math & Geometry --- */
/* --- Camera Math --- */
function updateViewport() {
viewport.style.transform = `translate(${panX}px, ${panY}px) scale(${zoom})`;
workspace.style.backgroundSize = `${24 * zoom}px ${24 * zoom}px`;
workspace.style.backgroundPosition = `${panX}px ${panY}px`;
}
function zoomWorkspace(factor, mouseX, mouseY) {
const newZoom = Math.min(Math.max(0.2, zoom * factor), 3);
panX = mouseX - (mouseX - panX) * (newZoom / zoom);
panY = mouseY - (mouseY - panY) * (newZoom / zoom);
zoom = newZoom;
updateViewport();
}
function getPortCoords(nodeId, portDataAttr) {
const node = nodes[nodeId];
if (!node || !node.el) return {x:0, y:0};
@@ -89,9 +105,10 @@
const wsRect = workspace.getBoundingClientRect();
const portRect = portEl.getBoundingClientRect();
// Calculate backwards through camera scale/pan to find true local coordinates
return {
x: portRect.left - wsRect.left + (portRect.width / 2),
y: portRect.top - wsRect.top + (portRect.height / 2)
x: (portRect.left - wsRect.left - panX + portRect.width / 2) / zoom,
y: (portRect.top - wsRect.top - panY + portRect.height / 2) / zoom
};
}
@@ -103,37 +120,30 @@
/* --- 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`;
}
if (n.el) { n.el.style.left = `${n.x}px`; n.el.style.top = `${n.y}px`; }
});
renderWires();
}
function clearSelection() {
selectedWireId = null;
selectedNodeId = null;
selectedWireId = null; selectedNodeId = null;
document.querySelectorAll('.lg-node.selected').forEach(el => el.classList.remove('selected'));
renderWires();
}
@@ -141,22 +151,16 @@
/* --- 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;
let changed = true; let loops = 0;
while (changed && loops < 10) {
changed = false;
loops++;
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;
@@ -170,11 +174,7 @@
case 'XOR': res = val1 !== val2; break;
case 'XNOR': res = val1 === val2; break;
}
if (context[gate.id] !== res) {
context[gate.id] = res;
changed = true;
}
if (context[gate.id] !== res) { context[gate.id] = res; changed = true; }
});
}
@@ -195,7 +195,6 @@
}
});
}
return outStates;
}
@@ -207,12 +206,10 @@
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;
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;
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>';
@@ -221,27 +218,16 @@
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;
});
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>`;
});
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;
}
@@ -252,13 +238,24 @@
generateTruthTable();
}
/* --- Smart Label Generation --- */
function getNextInputLabel() {
let charCode = 65;
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`;
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">`;
@@ -266,12 +263,12 @@
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>
<div class="lg-port" 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>
<div class="lg-port" 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>
`;
@@ -279,46 +276,43 @@
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>` : ''}
<div class="lg-port" data-port="in1" style="top: ${isNot ? '25px' : '15px'}; left: 0;"></div>
${!isNot ? `<div class="lg-port" 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>
<div class="lg-port" data-port="out" style="top: 25px; left: 100px;"></div>
`;
}
innerHTML += `</div>`;
el.innerHTML = innerHTML;
workspace.appendChild(el);
viewport.appendChild(el);
node.el = el;
if (node.type === 'INPUT') {
// Custom click handler to prevent dragging from toggling the switch
el.querySelector('.switch').addEventListener('click', (e) => {
const dist = Math.hypot(e.clientX - clickStartX, e.clientY - clickStartY);
if (isDraggingNode || dist > 3) {
e.preventDefault();
if (dist > 3) {
e.preventDefault(); // Prevents toggle if it was a drag motion
} 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>` : '';
el.querySelector('.slider').innerHTML = node.value ? `<style>#logicPage [data-id="${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') { inputCount++; label = String.fromCharCode(64 + inputCount); }
if (type === 'OUTPUT') { outputCount++; label = `Q${outputCount}`; }
if (type === 'GATE') { label = gateType; }
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;
@@ -330,10 +324,27 @@
}
/* --- Global Interaction Handlers --- */
// Camera Zoom Controls
document.getElementById("btnZoomIn")?.addEventListener('click', () => {
const r = workspace.getBoundingClientRect(); zoomWorkspace(1.2, r.width/2, r.height/2);
});
document.getElementById("btnZoomOut")?.addEventListener('click', () => {
const r = workspace.getBoundingClientRect(); zoomWorkspace(1/1.2, r.width/2, r.height/2);
});
document.getElementById("btnZoomReset")?.addEventListener('click', () => {
panX = 0; panY = 0; zoom = 1; updateViewport();
});
workspace.addEventListener('wheel', (e) => {
e.preventDefault();
const wsRect = workspace.getBoundingClientRect();
const factor = e.deltaY < 0 ? 1.1 : (1/1.1);
zoomWorkspace(factor, e.clientX - wsRect.left, e.clientY - wsRect.top);
});
workspace.addEventListener('mousedown', (e) => {
clickStartX = e.clientX;
clickStartY = e.clientY;
clickStartX = e.clientX; clickStartY = e.clientY;
const port = e.target.closest('.lg-port');
if (port) {
@@ -342,11 +353,7 @@
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 (existingIdx !== -1) { connections.splice(existingIdx, 1); runSimulation(); return; }
}
if (portId === 'out') {
@@ -371,32 +378,41 @@
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 };
dragOffset = { x: (e.clientX - rect.left) / zoom, y: (e.clientY - rect.top) / zoom };
return;
}
// Clicked empty space -> Pan Camera
clearSelection();
isPanning = true;
panStart = { x: e.clientX - panX, y: e.clientY - panY };
});
window.addEventListener('mousemove', (e) => {
const wsRect = workspace.getBoundingClientRect();
if (isPanning) {
panX = e.clientX - panStart.x;
panY = e.clientY - panStart.y;
updateViewport();
return;
}
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));
let newX = (e.clientX - wsRect.left - panX) / zoom - dragOffset.x;
let newY = (e.clientY - wsRect.top - panY) / zoom - dragOffset.y;
node.x = newX; node.y = newY;
updateNodePositions();
}
if (wiringStart) {
tempWirePath = {
x: e.clientX - wsRect.left,
y: e.clientY - wsRect.top
x: (e.clientX - wsRect.left - panX) / zoom,
y: (e.clientY - wsRect.top - panY) / zoom
};
renderWires();
}
@@ -404,6 +420,7 @@
window.addEventListener('mouseup', (e) => {
isDraggingNode = null;
isPanning = false;
if (wiringStart) {
const port = e.target.closest('.lg-port');
@@ -413,18 +430,10 @@
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
});
connections.push({ id: `conn_${nextWireId++}`, fromNode: wiringStart.node, fromPort: 'out', toNode: targetNodeId, toPort: targetPortId });
}
}
wiringStart = null;
tempWirePath = null;
wiringStart = null; tempWirePath = null;
runSimulation();
}
});
@@ -434,17 +443,15 @@
if (e.key === 'Delete' || e.key === 'Backspace') {
if (selectedWireId) {
connections = connections.filter(c => c.id !== selectedWireId);
clearSelection();
runSimulation();
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);
viewport.removeChild(nodes[selectedNodeId].el);
}
delete nodes[selectedNodeId];
clearSelection();
runSimulation();
clearSelection(); runSimulation();
}
}
});
@@ -457,19 +464,16 @@
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;
const x = (e.clientX - wsRect.left - panX) / zoom - 40;
const y = (e.clientY - wsRect.top - panY) / zoom - 30;
spawnNode(spawnType, gateType || null, x, y);
}
});
/* --- Init --- */
btnClearBoard?.addEventListener('click', () => {
workspace.querySelectorAll('.lg-node').forEach(el => el.remove());
nodes = {};
connections = [];
inputCount = 0;
outputCount = 0;
viewport.querySelectorAll('.lg-node').forEach(el => el.remove());
nodes = {}; connections = [];
runSimulation();
});
@@ -481,9 +485,4 @@
});
initToolbox();
spawnNode('INPUT', null, 80, 150);
spawnNode('INPUT', null, 80, 250);
spawnNode('GATE', 'AND', 320, 200);
spawnNode('OUTPUT', null, 600, 200);
})();

423
src/scripts/pcBuilder.js Normal file
View File

@@ -0,0 +1,423 @@
// src/scripts/pcBuilder.js
// Computing:Box — Advanced PC Sandbox
(() => {
const workspace = document.getElementById("workspace");
const viewport = document.getElementById("viewport");
const wireLayer = document.getElementById("wireLayer");
const specsContainer = document.getElementById("buildSpecsContainer");
const toolboxGrid = document.getElementById("toolboxGrid");
const btnClearBoard = document.getElementById("btnClearBoard");
const toolboxToggle = document.getElementById("toolboxToggle");
const pcPage = document.getElementById("pcPage");
/* --- Extensive PC Component Library --- */
const PC_PARTS = {
'CASE': {
name: 'ATX PC Case', w: 600, h: 550, z: 5, ports: [],
slots: {
'MB1': { x: 20, y: 20, accepts: 'MB' },
'PSU1': { x: 20, y: 440, accepts: 'PSU' },
'HDD1': { x: 440, y: 20, accepts: 'HDD' },
'HDD2': { x: 440, y: 170, accepts: 'HDD' },
'SATA_SSD1': { x: 440, y: 320, accepts: 'SATA_SSD' },
'SATA_SSD2': { x: 440, y: 400, accepts: 'SATA_SSD' }
},
svg: `<rect width="600" height="550" fill="#15171c" rx="10" stroke="#333" stroke-width="4"/><rect x="20" y="20" width="380" height="400" fill="none" stroke="#222" stroke-width="2"/><rect x="20" y="440" width="180" height="90" fill="none" stroke="#222" stroke-width="2"/><rect x="440" y="20" width="140" height="510" fill="none" stroke="#222" stroke-width="2"/>`
},
'MB': {
name: 'Motherboard', w: 360, h: 400, z: 10,
ports: [
{ id: 'atx_pwr', x: 340, y: 150 }, { id: 'sata1', x: 340, y: 300 }, { id: 'sata2', x: 340, y: 330 },
{ id: 'usb1', x: 10, y: 40 }, { id: 'usb2', x: 10, y: 70 }, { id: 'usb3', x: 10, y: 100 }, { id: 'usb4', x: 10, y: 130 },
{ id: 'audio', x: 10, y: 170 }, { id: 'disp', x: 10, y: 210 }
],
slots: {
'CPU1': { x: 120, y: 40, accepts: 'CPU' },
'COOLER1': { x: 100, y: 20, accepts: 'COOLER' },
'RAM1': { x: 230, y: 30, accepts: 'RAM' }, 'RAM2': { x: 250, y: 30, accepts: 'RAM' },
'RAM3': { x: 270, y: 30, accepts: 'RAM' }, 'RAM4': { x: 290, y: 30, accepts: 'RAM' },
'M2_1': { x: 120, y: 170, accepts: 'M2_SSD' }, 'M2_2': { x: 120, y: 250, accepts: 'M2_SSD' },
'PCIE1': { x: 40, y: 200, accepts: 'GPU' }, 'PCIE2': { x: 40, y: 300, accepts: 'GPU' }
},
// Uses a lighter slate grey #2C303A to stand out from the case
svg: `<rect width="360" height="400" fill="#2C303A" rx="8" stroke="#4b5060" stroke-width="3"/><rect x="120" y="40" width="80" height="80" fill="#1f2229" stroke="#4b5060"/><rect x="230" y="30" width="15" height="100" fill="#1f2229"/><rect x="250" y="30" width="15" height="100" fill="#1f2229"/><rect x="270" y="30" width="15" height="100" fill="#1f2229"/><rect x="290" y="30" width="15" height="100" fill="#1f2229"/><rect x="40" y="200" width="280" height="15" fill="#15171c"/><rect x="40" y="300" width="280" height="15" fill="#15171c"/><rect x="120" y="170" width="80" height="15" fill="#1f2229"/><rect x="120" y="250" width="80" height="15" fill="#1f2229"/>`
},
'CPU': { name: 'Processor', w: 80, h: 80, z: 20, ports: [], slots: {}, svg: `<rect width="80" height="80" fill="#0b381a"/><rect x="10" y="10" width="60" height="60" rx="4" fill="#d4d4d4"/><polygon points="5,75 15,75 5,65" fill="#ffd700"/><text x="40" y="45" fill="#555" font-family="sans-serif" font-size="14" font-weight="bold" text-anchor="middle">CPU</text>` },
'COOLER': { name: 'CPU Fan', w: 120, h: 120, z: 30, ports: [], slots: {}, svg: `<rect width="120" height="120" rx="60" fill="#1a1c23" stroke="#aaa" stroke-width="3"/><circle cx="60" cy="60" r="50" fill="#111"/><path d="M60,15 A45,45 0 0,1 105,60 L60,60 Z" fill="#444"/><path d="M105,60 A45,45 0 0,1 60,105 L60,60 Z" fill="#555"/><path d="M60,105 A45,45 0 0,1 15,60 L60,60 Z" fill="#444"/><path d="M15,60 A45,45 0 0,1 60,15 L60,60 Z" fill="#555"/><circle cx="60" cy="60" r="20" fill="#222"/>` },
'RAM': { name: 'DDR4 Memory', w: 15, h: 100, z: 20, ports: [], slots: {}, svg: `<rect width="15" height="100" fill="#111"/><rect x="2" y="5" width="11" height="80" fill="#2a2a2a"/><rect x="0" y="90" width="15" height="10" fill="#ffd700"/>` },
'GPU': { name: 'Graphics Card', w: 280, h: 60, z: 40, slots: {}, ports: [{ id: 'pwr_in', x: 270, y: 10 }, { id: 'disp_out', x: 10, y: 30 }], svg: `<rect width="280" height="60" rx="5" fill="#1a1a1a"/><circle cx="70" cy="30" r="22" fill="#111" stroke="#333" stroke-width="2"/><circle cx="140" cy="30" r="22" fill="#111" stroke="#333" stroke-width="2"/><circle cx="210" cy="30" r="22" fill="#111" stroke="#333" stroke-width="2"/><rect x="20" y="55" width="80" height="5" fill="#ffd700"/>` },
'M2_SSD': { name: 'M.2 NVMe SSD', w: 80, h: 15, z: 20, ports: [], slots: {}, svg: `<rect width="80" height="15" rx="1" fill="#000"/><rect x="10" y="2" width="20" height="11" fill="#1a1a1a"/><rect x="35" y="2" width="20" height="11" fill="#1a1a1a"/><rect x="60" y="2" width="10" height="11" fill="#ccc"/><rect x="0" y="0" width="4" height="15" fill="#ffd700"/>` },
'SATA_SSD': { name: '2.5" SATA SSD', w: 100, h: 70, z: 20, slots: {}, ports: [{id:'data', x:90, y:20}, {id:'pwr', x:90, y:50}], svg: `<rect width="100" height="70" fill="#111" rx="4" stroke="#444"/><rect x="10" y="10" width="80" height="50" fill="#1a1a1a" rx="2" stroke="#222"/><text x="50" y="40" fill="#888" font-family="sans-serif" font-size="14" font-weight="bold" text-anchor="middle">SSD</text>` },
'HDD': { name: '3.5" Mech HDD', w: 120, h: 140, z: 20, slots: {}, ports: [{id:'data', x:110, y:20}, {id:'pwr', x:110, y:120}], svg: `<rect width="120" height="140" fill="#d0d0d0" rx="4" stroke="#888"/><rect x="10" y="10" width="100" height="100" fill="#e0e0e0" rx="50"/><circle cx="60" cy="60" r="35" fill="#ddd" stroke="#aaa"/><circle cx="60" cy="60" r="10" fill="#999"/><rect x="30" y="120" width="60" height="10" fill="#111"/>` },
'PSU': { name: 'Power Supply', w: 160, h: 90, z: 20, slots: {}, ports: [{id:'out1',x:150,y:20}, {id:'out2',x:150,y:40}, {id:'out3',x:150,y:60}, {id:'out4',x:150,y:80}], svg: `<rect width="160" height="90" rx="4" fill="#1a1a1a" stroke="#333" stroke-width="2"/><circle cx="80" cy="45" r="35" fill="#0a0a0a" stroke="#222" stroke-width="2"/><line x1="80" y1="10" x2="80" y2="80" stroke="#333" stroke-width="2"/><line x1="45" y1="45" x2="115" y2="45" stroke="#333" stroke-width="2"/><circle cx="80" cy="45" r="10" fill="#222"/>` },
'MONITOR': { name: 'Monitor', w: 240, h: 160, z: 30, slots: {}, ports: [{id:'disp', x:120, y:140}], svg: `<rect width="240" height="160" fill="#111" rx="5"/><rect x="10" y="10" width="220" height="120" fill="#000"/><rect x="100" y="140" width="40" height="20" fill="#222"/><rect x="60" y="150" width="120" height="10" fill="#222"/>` },
'KEYBOARD': { name: 'Keyboard', w: 180, h: 60, z: 30, slots: {}, ports: [{id:'usb', x:90, y:10}], svg: `<rect width="180" height="60" fill="#111" rx="3"/><rect x="5" y="5" width="170" height="50" fill="#222" rx="2" stroke="#333" stroke-dasharray="8 8"/>` },
'MOUSE': { name: 'Mouse', w: 30, h: 50, z: 30, slots: {}, ports: [{id:'usb', x:15, y:5}], svg: `<rect width="30" height="50" fill="#111" rx="15"/><line x1="15" y1="0" x2="15" y2="20" stroke="#333" stroke-width="2"/><circle cx="15" cy="15" r="4" fill="#333"/>` },
'SPEAKER': { name: 'Speakers', w: 40, h: 80, z: 30, slots: {}, ports: [{id:'audio', x:20, y:10}], svg: `<rect width="40" height="80" fill="#111" rx="4"/><circle cx="20" cy="25" r="12" fill="#222"/><circle cx="20" cy="60" r="16" fill="#222"/>` }
};
let nodes = {};
let connections = [];
let nextNodeId = 1, nextWireId = 1;
let isDraggingNode = null, dragOffset = { x: 0, y: 0 };
let wiringStart = null, tempWirePath = null;
let selectedWireId = null, selectedNodeId = null;
let panX = 0, panY = 0, zoom = 1;
let isPanning = false, panStart = { x: 0, y: 0 };
/* --- Setup Toolbox --- */
function initToolbox() {
if(!toolboxGrid) return;
let html = '';
Object.keys(PC_PARTS).forEach(partKey => {
html += `
<div draggable="true" data-spawn="${partKey}" class="drag-item tb-icon-box" title="${PC_PARTS[partKey].name}">
<svg viewBox="0 0 ${PC_PARTS[partKey].w} ${PC_PARTS[partKey].h}" style="max-width:80%; max-height:40px; pointer-events:none;">${PC_PARTS[partKey].svg}</svg>
<div class="tb-icon-label">${partKey}</div>
</div>
`;
});
toolboxGrid.innerHTML = html;
document.querySelectorAll('.drag-item').forEach(item => {
item.addEventListener('dragstart', (e) => { e.dataTransfer.setData('spawnType', item.dataset.spawn); });
});
}
/* --- Camera Math --- */
function updateViewport() {
viewport.style.transform = `translate(${panX}px, ${panY}px) scale(${zoom})`;
workspace.style.backgroundSize = `${32 * zoom}px ${32 * zoom}px`;
workspace.style.backgroundPosition = `${panX}px ${panY}px`;
}
function zoomWorkspace(factor, mouseX, mouseY) {
const newZoom = Math.min(Math.max(0.1, zoom * factor), 2);
panX = mouseX - (mouseX - panX) * (newZoom / zoom);
panY = mouseY - (mouseY - panY) * (newZoom / zoom);
zoom = newZoom; updateViewport();
}
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 - panX + portRect.width / 2) / zoom,
y: (portRect.top - wsRect.top - panY + portRect.height / 2) / zoom
};
}
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, conn.fromPort);
const to = getPortCoords(conn.toNode, conn.toPort);
const isSelected = conn.id === selectedWireId;
svgHTML += `<path class="pb-wire active ${isSelected ? 'selected' : ''}" d="${drawBezier(from.x, from.y, to.x, to.y)}" data-conn-id="${conn.id}" />`;
});
if (wiringStart && tempWirePath) {
svgHTML += `<path class="pb-wire pb-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('.pb-node.selected').forEach(el => el.classList.remove('selected'));
renderWires();
}
/* --- Seven-Segment Diagnostics Engine --- */
function evaluateBuild() {
if(!specsContainer) return;
let hasCase = false, hasMB = false, hasCPU = false, hasCooler = false, hasRAM = false, hasPSU = false;
let hasStorage = false, hasGPU = false;
let mbPwr = false, gpuPwr = false;
let usbCount = 0, dispConn = false, audConn = false;
let caseNode = Object.values(nodes).find(n => n.type === 'CASE');
let mbNode = Object.values(nodes).find(n => n.type === 'MB');
if (caseNode) {
hasCase = true;
if (caseNode.slots['MB1']) hasMB = true;
if (caseNode.slots['PSU1']) hasPSU = true;
if (caseNode.slots['HDD1'] || caseNode.slots['HDD2'] || caseNode.slots['SATA_SSD1'] || caseNode.slots['SATA_SSD2']) hasStorage = true;
} else if (mbNode) {
hasMB = true; // Motherboard exists outside case
}
if (mbNode) {
if (mbNode.slots['CPU1']) hasCPU = true;
if (mbNode.slots['COOLER1']) hasCooler = true;
if (mbNode.slots['RAM1'] || mbNode.slots['RAM2'] || mbNode.slots['RAM3'] || mbNode.slots['RAM4']) hasRAM = true;
if (mbNode.slots['PCIE1'] || mbNode.slots['PCIE2']) hasGPU = true;
if (mbNode.slots['M2_1'] || mbNode.slots['M2_2']) hasStorage = true;
}
// Check Cables
connections.forEach(c => {
let n1 = nodes[c.fromNode], n2 = nodes[c.toNode];
if(!n1 || !n2) return;
let types = [n1.type, n2.type];
if(types.includes('MB') && types.includes('PSU')) mbPwr = true;
if(types.includes('GPU') && types.includes('PSU')) gpuPwr = true;
if(types.includes('MB') && ['KEYBOARD','MOUSE','WEBCAM','MIC','PRINTER'].some(t => types.includes(t))) usbCount++;
if(types.includes('MB') && types.includes('SPEAKER')) audConn = true;
if((types.includes('MB') || types.includes('GPU')) && types.includes('MONITOR')) dispConn = true;
});
const isBootable = (hasMB && hasCPU && hasCooler && hasRAM && hasPSU && hasStorage && mbPwr && (hasGPU ? gpuPwr : true) && dispConn);
specsContainer.innerHTML = `
<div class="diag-cat">Core System</div>
<div class="diag-row"><span>CHASSIS</span><span style="color: ${hasCase ? '#28f07a' : '#ff5555'}">${hasCase ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>MOTHERBOARD</span><span style="color: ${hasMB ? '#28f07a' : '#ff5555'}">${hasMB ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>CPU</span><span style="color: ${hasCPU ? '#28f07a' : '#ff5555'}">${hasCPU ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>COOLING</span><span style="color: ${hasCooler ? '#28f07a' : '#ff5555'}">${hasCooler ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>MEMORY</span><span style="color: ${hasRAM ? '#28f07a' : '#ff5555'}">${hasRAM ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>POWER SPLY</span><span style="color: ${hasPSU ? '#28f07a' : '#ff5555'}">${hasPSU ? 'OK' : 'ERR'}</span></div>
<div class="diag-cat">Connections</div>
<div class="diag-row"><span>MB POWER</span><span style="color: ${mbPwr ? '#28f07a' : '#ff5555'}">${mbPwr ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>STORAGE</span><span style="color: ${hasStorage ? '#28f07a' : '#ff5555'}">${hasStorage ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>GPU POWER</span><span style="color: ${!hasGPU ? '#888' : (gpuPwr ? '#28f07a' : '#ff5555')}">${!hasGPU ? 'N/A' : (gpuPwr ? 'OK' : 'ERR')}</span></div>
<div class="diag-row"><span>DISPLAY</span><span style="color: ${dispConn ? '#28f07a' : '#ff5555'}">${dispConn ? 'OK' : 'ERR'}</span></div>
<div class="diag-row"><span>USB DEVS</span><span style="color: #55aaff">${usbCount}</span></div>
<hr style="border-color: rgba(255,255,255,0.1); margin: 12px 0 8px 0;">
<div style="text-align:center; font-size: 28px; color: ${isBootable ? '#28f07a' : '#ff5555'}; font-family: var(--bit-font); letter-spacing: 2px;">
${isBootable ? 'BOOTING...' : 'HALTED'}
</div>
`;
}
/* --- Node Creation & Snapping --- */
function createNodeElement(node) {
const el = document.createElement('div');
el.className = `pb-node`; el.dataset.id = node.id;
el.style.left = `${node.x}px`; el.style.top = `${node.y}px`;
el.style.width = `${PC_PARTS[node.type].w}px`; el.style.height = `${PC_PARTS[node.type].h}px`;
el.style.zIndex = PC_PARTS[node.type].z;
let innerHTML = `<svg class="pb-part-svg" viewBox="0 0 ${PC_PARTS[node.type].w} ${PC_PARTS[node.type].h}">${PC_PARTS[node.type].svg}</svg>`;
PC_PARTS[node.type].ports.forEach(p => {
innerHTML += `<div class="pb-port" data-port="${p.id}" style="left: ${p.x}px; top: ${p.y}px;"></div>`;
});
// Debug Labels for bare parts
if(node.type !== 'CASE' && node.type !== 'MB') {
innerHTML += `<div style="position:absolute; top:-20px; font-family:var(--ui-font); font-size:12px; color:var(--muted);">${node.type}</div>`;
}
el.innerHTML = innerHTML;
viewport.appendChild(el);
node.el = el;
return el;
}
function spawnNode(type, dropX = null, dropY = null) {
const id = `node_${nextNodeId++}`;
const x = dropX !== null ? dropX : 300 + Math.random()*40;
const y = dropY !== null ? dropY : 150 + Math.random()*40;
const node = { id, type, x, y, snappedTo: null, el: null };
if (PC_PARTS[type].slots) node.slots = { ...PC_PARTS[type].slots }; // Copy slots schema, values will be filled with IDs
// Reset slot values to null
if(node.slots) {
for(let k in node.slots) { node.slots[k] = null; }
}
nodes[id] = node;
createNodeElement(node);
evaluateBuild();
}
// Recursive movement to handle nested snaps (MB inside CASE inside ...)
function moveNodeRecursive(nodeId, dx, dy) {
const n = nodes[nodeId];
if(!n) return;
n.x += dx; n.y += dy;
if(n.slots) {
Object.keys(n.slots).forEach(k => {
if(typeof n.slots[k] === 'string') moveNodeRecursive(n.slots[k], dx, dy);
});
}
}
/* --- Inspect Mode --- */
let inspectZoom = 1, inspectRotX = 0, inspectRotY = 0;
workspace.addEventListener('dblclick', (e) => {
const nodeEl = e.target.closest('.pb-node');
if (nodeEl) {
const node = nodes[nodeEl.dataset.id];
document.getElementById('inspectModal').classList.add('active');
document.getElementById('inspectObject').innerHTML = `<svg viewBox="0 0 ${PC_PARTS[node.type].w} ${PC_PARTS[node.type].h}" style="width:100%; height:100%;">${PC_PARTS[node.type].svg}</svg>`;
document.getElementById('inspectName').innerText = PC_PARTS[node.type].name;
inspectZoom = 1.5; inspectRotX = 0; inspectRotY = 0; updateInspectTransform(); clearSelection();
}
});
document.getElementById('inspectStage')?.addEventListener('mousemove', (e) => {
const rect = e.currentTarget.getBoundingClientRect();
inspectRotY = (e.clientX - rect.left - rect.width/2) / 5;
inspectRotX = -(e.clientY - rect.top - rect.height/2) / 5;
updateInspectTransform();
});
document.getElementById('inspectStage')?.addEventListener('wheel', (e) => {
e.preventDefault(); inspectZoom += e.deltaY < 0 ? 0.1 : -0.1;
inspectZoom = Math.max(0.5, Math.min(inspectZoom, 4)); updateInspectTransform();
});
function updateInspectTransform() { const obj = document.getElementById('inspectObject'); if(obj) obj.style.transform = `scale(${inspectZoom}) rotateX(${inspectRotX}deg) rotateY(${inspectRotY}deg)`; }
document.getElementById('inspectClose')?.addEventListener('click', () => { document.getElementById('inspectModal').classList.remove('active'); });
/* --- Interaction --- */
document.getElementById("btnZoomIn")?.addEventListener('click', () => { const r = workspace.getBoundingClientRect(); zoomWorkspace(1.2, r.width/2, r.height/2); });
document.getElementById("btnZoomOut")?.addEventListener('click', () => { const r = workspace.getBoundingClientRect(); zoomWorkspace(1/1.2, r.width/2, r.height/2); });
document.getElementById("btnZoomReset")?.addEventListener('click', () => { panX = 0; panY = 0; zoom = 1; updateViewport(); });
workspace.addEventListener('wheel', (e) => { e.preventDefault(); const wsRect = workspace.getBoundingClientRect(); zoomWorkspace(e.deltaY < 0 ? 1.1 : (1/1.1), e.clientX - wsRect.left, e.clientY - wsRect.top); });
workspace.addEventListener('mousedown', (e) => {
const port = e.target.closest('.pb-port');
if (port) {
const nodeEl = port.closest('.pb-node');
const portId = port.dataset.port;
const existingIdx = connections.findIndex(c => (c.toNode === nodeEl.dataset.id && c.toPort === portId) || (c.fromNode === nodeEl.dataset.id && c.fromPort === portId));
if (existingIdx !== -1) { connections.splice(existingIdx, 1); evaluateBuild(); renderWires(); return; }
const coords = getPortCoords(nodeEl.dataset.id, portId);
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('.pb-wire');
if (wire && wire.dataset.connId) { clearSelection(); selectedWireId = wire.dataset.connId; renderWires(); e.stopPropagation(); return; }
const nodeEl = e.target.closest('.pb-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) / zoom, y: (e.clientY - rect.top) / zoom };
// Unsnap from parent when picked up
const node = nodes[isDraggingNode];
if (node.snappedTo) {
const parent = nodes[node.snappedTo.id];
if (parent && parent.slots[node.snappedTo.key] === node.id) parent.slots[node.snappedTo.key] = null;
node.snappedTo = null;
node.el.style.zIndex = PC_PARTS[node.type].z; // Reset Z
evaluateBuild();
}
return;
}
clearSelection(); isPanning = true; panStart = { x: e.clientX - panX, y: e.clientY - panY };
});
window.addEventListener('mousemove', (e) => {
const wsRect = workspace.getBoundingClientRect();
if (isPanning) { panX = e.clientX - panStart.x; panY = e.clientY - panStart.y; updateViewport(); return; }
if (isDraggingNode) {
const node = nodes[isDraggingNode];
let newX = (e.clientX - wsRect.left - panX) / zoom - dragOffset.x;
let newY = (e.clientY - wsRect.top - panY) / zoom - dragOffset.y;
moveNodeRecursive(node.id, newX - node.x, newY - node.y);
updateNodePositions();
}
if (wiringStart) { tempWirePath = { x: (e.clientX - wsRect.left - panX) / zoom, y: (e.clientY - wsRect.top - panY) / zoom }; renderWires(); }
});
window.addEventListener('mouseup', (e) => {
if (isDraggingNode) {
const node = nodes[isDraggingNode];
let snapped = false;
// Check all other nodes for compatible slots
Object.values(nodes).forEach(target => {
if (target.slots && !snapped && target.id !== node.id) {
for(let slotKey in target.slots) {
let slotDef = PC_PARTS[target.type].slots[slotKey];
if(slotDef.accepts === node.type && target.slots[slotKey] === null) {
let tX = target.x + slotDef.x; let tY = target.y + slotDef.y;
if (Math.hypot(node.x - tX, node.y - tY) < 80) {
moveNodeRecursive(node.id, tX - node.x, tY - node.y);
node.snappedTo = { id: target.id, key: slotKey };
target.slots[slotKey] = node.id;
node.el.style.zIndex = PC_PARTS[target.type].z + 5; // Layer above parent
snapped = true; break;
}
}
}
}
});
isDraggingNode = null; updateNodePositions(); evaluateBuild();
}
if (wiringStart) {
const port = e.target.closest('.pb-port');
if (port) {
const targetNodeId = port.closest('.pb-node').dataset.id;
const targetPortId = port.dataset.port;
if (targetNodeId !== wiringStart.node) { connections.push({ id: `conn_${nextWireId++}`, fromNode: wiringStart.node, fromPort: wiringStart.port, toNode: targetNodeId, toPort: targetPortId }); }
}
wiringStart = null; tempWirePath = null; evaluateBuild(); renderWires();
}
isPanning = false;
});
/* --- Deletion (Recursive) --- */
function deleteNodeRecursive(id) {
const n = nodes[id]; if(!n) return;
if(n.slots) { Object.keys(n.slots).forEach(k => { if(typeof n.slots[k] === 'string') deleteNodeRecursive(n.slots[k]); }); }
if(n.snappedTo) { const p = nodes[n.snappedTo.id]; if(p) p.slots[n.snappedTo.key] = null; }
connections = connections.filter(c => c.fromNode !== id && c.toNode !== id);
viewport.removeChild(n.el); delete nodes[id];
}
window.addEventListener('keydown', (e) => {
if ((e.key === 'Delete' || e.key === 'Backspace') && selectedNodeId) {
deleteNodeRecursive(selectedNodeId); clearSelection(); evaluateBuild(); renderWires();
}
if ((e.key === 'Delete' || e.key === 'Backspace') && selectedWireId) {
connections = connections.filter(c => c.id !== selectedWireId); clearSelection(); evaluateBuild(); renderWires();
}
});
workspace.addEventListener('dragover', (e) => { e.preventDefault(); });
workspace.addEventListener('drop', (e) => {
e.preventDefault();
const type = e.dataTransfer.getData('spawnType');
if (type) {
const r = workspace.getBoundingClientRect();
spawnNode(type, (e.clientX - r.left - panX) / zoom - (PC_PARTS[type].w / 2), (e.clientY - r.top - panY) / zoom - (PC_PARTS[type].h / 2));
}
});
btnClearBoard?.addEventListener('click', () => {
viewport.querySelectorAll('.pb-node').forEach(el => el.remove());
nodes = {}; connections = []; evaluateBuild(); renderWires();
});
toolboxToggle?.addEventListener("click", () => {
const c = pcPage?.classList.contains("toolboxCollapsed");
pcPage.classList.toggle("toolboxCollapsed", !c);
toolboxToggle?.setAttribute("aria-expanded", c ? "true" : "false");
});
initToolbox(); evaluateBuild();
})();

View File

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

View File

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

View File

@@ -47,39 +47,9 @@ body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--
.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; }
/* --- SHARED UI COMPONENTS (Used by ALL Simulators) --- */
.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; }
@@ -92,99 +62,19 @@ body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--
.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)); } }
.toolboxToggle { position: fixed; top: var(--toolbox-toggle-top, calc(var(--nav-h) + 16px)); 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); }

View File

@@ -1,163 +1,165 @@
/* === LOGIC GATES CANVAS CSS === */
.lg-workspace {
position: relative;
flex: 1;
width: 100%;
min-height: 750px;
background-color: transparent;
background-image: radial-gradient(rgba(255,255,255,0.12) 1px, transparent 1px);
background-size: 24px 24px;
border: none;
border-radius: 0;
box-shadow: none;
overflow: hidden;
/* === FULL PAGE OVERRIDES FOR LOGIC GATES === */
body:has(#logicPage) { overflow: hidden; }
body:has(#logicPage) .pageWrap {
max-width: 100% !important;
padding: 0 !important;
margin: 0 !important;
height: calc(100vh - var(--nav-h));
display: flex;
flex-direction: column;
}
#logicPage { padding: 0 !important; margin: 0 !important; }
/* === MAIN CONTAINER === */
.lg-container {
flex: 1; display: flex; flex-direction: column; position: relative;
width: 100%; height: 100%; overflow: hidden;
}
.lg-svg-layer {
/* === FIXED HEADER === */
.lg-top-header {
width: 100%; text-align: center; padding: 8px 20px 8px;
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);
}
.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; 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 & CAMERA VIEWPORT === */
.lg-workspace {
flex: 1; position: relative; width: 100%;
background-color: transparent;
background-image: radial-gradient(rgba(255,255,255,0.15) 1px, transparent 1px);
background-size: 24px 24px; overflow: hidden;
cursor: grab; /* Indicates pannable area */
}
.lg-workspace:active { cursor: grabbing; }
.lg-viewport {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
transform-origin: 0 0;
pointer-events: none; /* Let events reach workspace, nodes will re-enable it */
}
/* Zoom UI Controls */
.lg-zoom-controls {
position: absolute; bottom: 20px; left: 20px; z-index: 100;
display: flex; gap: 8px;
}
.lg-zoom-btn {
width: 40px; height: 40px; border-radius: 8px; font-family: var(--ui-font);
font-size: 22px; font-weight: 800; display: flex; align-items: center; justify-content: center;
background: rgba(0,0,0,0.5); border: 1px solid rgba(255,255,255,0.15);
color: #e8e8ee; cursor: pointer; backdrop-filter: blur(8px); transition: all 0.2s;
}
.lg-zoom-btn:hover { background: rgba(255,255,255,0.1); border-color: #28f07a; color: #28f07a; }
.lg-svg-layer {
position: absolute; inset: 0; width: 100%; height: 100%; z-index: 1;
}
/* Wires */
.lg-wire {
stroke: rgba(255,255,255,0.25);
stroke-width: 6;
fill: none;
stroke-linecap: round;
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; /* Allows wires to be clicked */
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));
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;
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; }
.lg-wire-temp {
stroke: rgba(255,255,255,0.4);
stroke-dasharray: 8 8;
pointer-events: none;
}
/* Nodes (Borderless & Transparent) */
/* 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;
box-shadow: none;
backdrop-filter: none;
user-select: none;
transition: filter 0.2s;
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;
pointer-events: auto; /* Re-enables interaction inside the viewport */
}
.lg-node:active { cursor: grabbing; z-index: 20; }
.lg-node.selected {
filter: drop-shadow(0 0 10px rgba(255,85,85,0.8));
}
.lg-node.selected { filter: drop-shadow(0 0 10px rgba(255,85,85,0.8)); }
/* Node Labels (Seven-Segment, +2 Sizes Bigger) */
.lg-header {
font-size: 24px;
color: var(--muted);
font-family: var(--bit-font);
letter-spacing: 2px;
pointer-events: none;
margin-bottom: 6px;
font-size: 24px; color: var(--muted); font-family: var(--bit-font);
letter-spacing: 2px; pointer-events: none; margin-bottom: 6px;
}
/* Container mapping SVGs to absolutely positioned connection dots */
.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;
}
.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%); /* Centers the dot exactly over the coordinate */
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; }
/* Draggable Toolbox Visual Gates Grid */
.tb-icon-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
/* === FLOATING TOOLBOX === */
.toolboxToggle {
position: absolute; top: 10px; 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; right: 20px; bottom: 20px; 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;
}
.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-container.toolboxCollapsed .lg-toolbox { transform: translateX(calc(100% + 40px)); opacity: 0; pointer-events: none; }
.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;
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; }
/* Toolbox Scroll Fix */
.panelCol {
max-height: calc(100vh - var(--nav-h) - 30px) !important;
overflow-y: auto;
padding-bottom: 30px;
pointer-events: auto;
}
/* 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;
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: 300px; overflow-y: auto; overflow-x: auto;
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 {
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-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 { width: 100%; border-collapse: collapse; text-align: center; font-family: var(--num-font); font-size: 14px; color: #e8e8ee; }
.tt-table th { position: sticky; top: 0; background: rgba(31,32,39,0.95); padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.15); color: var(--muted); font-family: var(--bit-font); font-weight: normal; }
.tt-table td { padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.05); }
.tt-table .tt-on { color: #28f07a; text-shadow: 0 0 8px rgba(40,240,122,0.5); }

View File

@@ -1,43 +0,0 @@
/* src/styles/md3-tokens.css */
/* MD3-inspired tokens tuned for education: high readability, clear contrast, calm surfaces */
:root{
/* Typography */
--font-sans: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
/* Spacing + shape */
--radius-1: 10px;
--radius-2: 16px;
--radius-3: 22px;
--shadow-1: 0 1px 2px rgba(0,0,0,.08), 0 2px 8px rgba(0,0,0,.06);
/* Color roles (keep simple) */
--md-surface: #ffffff;
--md-surface-2: #f6f7fb;
--md-on-surface: #111318;
--md-primary: #2f6fed; /* calm blue */
--md-on-primary: #ffffff;
--md-secondary: #5a5f72; /* muted */
--md-on-secondary: #ffffff;
--md-tertiary: #0f766e; /* teal for "practical" tools */
--md-on-tertiary: #ffffff;
--md-outline: #d7dbe7;
--md-success: #1a7f37;
--md-warning: #b54708;
--md-danger: #b42318;
/* Focus ring for accessibility */
--md-focus: 0 0 0 3px rgba(47,111,237,.28);
}
@media (prefers-color-scheme: dark){
:root{
--md-surface: #0b0e14;
--md-surface-2: #121725;
--md-on-surface: #e8eaf2;
--md-primary: #9bb6ff;
--md-on-primary: #0b0e14;
--md-secondary: #b8bccd;
--md-on-secondary: #0b0e14;
--md-tertiary: #4fd1c5;
--md-on-tertiary: #0b0e14;
--md-outline: #2b3244;
--md-focus: 0 0 0 3px rgba(155,182,255,.25);
}
}

View File

@@ -0,0 +1,114 @@
/* --- 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; }
/* --- 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; }
/* --- 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 COMPONENTS FOR NUMBERS --- */
.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; }
.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; }
.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); }
/* === 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)); } }

120
src/styles/pc-builder.css Normal file
View File

@@ -0,0 +1,120 @@
/* === FULL PAGE OVERRIDES FOR PC BUILDER === */
body:has(#pcPage) { overflow: hidden; }
body:has(#pcPage) .pageWrap {
max-width: 100% !important; padding: 0 !important; margin: 0 !important;
height: calc(100vh - var(--nav-h)); display: flex; flex-direction: column;
}
#pcPage { padding: 0 !important; margin: 0 !important; }
/* === MAIN CONTAINER === */
.pb-container { flex: 1; display: flex; flex-direction: column; position: relative; width: 100%; height: 100%; overflow: hidden; }
/* === FIXED HEADER === */
.pb-top-header {
width: 100%; text-align: center; padding: 8px 20px 8px;
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);
}
.pb-title { font-family: var(--bit-font); font-size: 32px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text); margin: 0 0 2px 0; line-height: 1; }
.pb-subtitle { color: var(--muted); font-size: 14px; font-family: var(--ui-font); font-weight: 500; margin: 0; line-height: 1.2; }
.pb-subtitle kbd { background: rgba(255,255,255,0.1); padding: 2px 6px; border-radius: 4px; font-family: var(--ui-font); color: #e8e8ee; }
/* === DYNAMIC CANVAS === */
.pb-workspace {
flex: 1; position: relative; width: 100%; background-color: transparent;
background-image: radial-gradient(rgba(255,255,255,0.08) 2px, transparent 2px);
background-size: 32px 32px; overflow: hidden; cursor: grab;
}
.pb-workspace:active { cursor: grabbing; }
.pb-viewport { position: absolute; inset: 0; width: 100%; height: 100%; transform-origin: 0 0; pointer-events: none; }
/* Zoom UI Controls */
.pb-zoom-controls { position: absolute; bottom: 20px; left: 20px; z-index: 100; display: flex; gap: 8px; }
.pb-zoom-btn {
width: 40px; height: 40px; border-radius: 8px; font-family: var(--ui-font); font-size: 22px; font-weight: 800;
display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.5);
border: 1px solid rgba(255,255,255,0.15); color: #e8e8ee; cursor: pointer; backdrop-filter: blur(8px); transition: all 0.2s;
}
.pb-zoom-btn:hover { background: rgba(255,255,255,0.1); border-color: #55aaff; color: #55aaff; }
/* Wires sit at the VERY FRONT so they are never hidden in the case */
.pb-svg-layer { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 100; pointer-events: none; }
/* Cables */
.pb-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;
}
.pb-wire:hover { stroke: rgba(255,255,255,0.6); stroke-width: 10; }
.pb-wire.active { stroke: #55aaff; filter: drop-shadow(0 0 6px rgba(85,170,255,0.6)); }
.pb-wire.active:hover { stroke: #88ccff; }
.pb-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; } }
.pb-wire-temp { stroke: rgba(255,255,255,0.4); stroke-dasharray: 8 8; pointer-events: none; }
/* PC Parts */
.pb-node {
position: absolute; background: transparent; border: none; border-radius: 0; padding: 0;
display: flex; flex-direction: column; align-items: center; justify-content: center; cursor: grab;
user-select: none; transition: filter 0.2s; pointer-events: auto;
}
.pb-node:active { cursor: grabbing; }
.pb-node.selected { filter: drop-shadow(0 0 10px rgba(255,85,85,0.8)); }
.pb-part-svg { width: 100%; height: 100%; display: block; pointer-events: none; filter: drop-shadow(0 10px 15px rgba(0,0,0,0.5)); }
/* Connection Ports */
.pb-port {
width: 14px; height: 14px; background: #222; border-radius: 50%; cursor: crosshair;
border: 2px solid #55aaff; box-shadow: 0 0 0 1px rgba(255,255,255,0.2); transition: all 0.2s;
position: absolute; z-index: 200; transform: translate(-50%, -50%); pointer-events: auto;
}
.pb-port:hover { transform: translate(-50%, -50%) scale(1.4); background: #fff; }
.pb-port.active { background: #55aaff; box-shadow: 0 0 12px rgba(85,170,255,0.8); }
/* === ANIMATIONS (Triggered on Boot) === */
@keyframes spin { 100% { transform: rotate(360deg); } }
.system-running .fan-blades { animation: spin 0.4s linear infinite; }
.system-running .monitor-screen { opacity: 1 !important; }
/* === FLOATING TOOLBOX === */
.pb-toolbox {
position: absolute; top: 60px; right: 20px; bottom: 20px; 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;
}
.pb-toolbox::-webkit-scrollbar { width: 6px; }
.pb-toolbox::-webkit-scrollbar-track { background: transparent; }
.pb-toolbox::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.05); border-radius: 10px; transition: background 0.3s; }
.pb-toolbox:hover::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); }
.pb-container.toolboxCollapsed .pb-toolbox { transform: translateX(calc(100% + 40px)); opacity: 0; pointer-events: none; }
.tb-icon-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.tb-icon-box {
background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.15); border-radius: 8px; width: 100%; padding: 8px 0;
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; 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: 10px; font-weight: 800; color: var(--text); letter-spacing: 0px; text-transform: uppercase; text-align: center;}
/* Diagnostics Panel */
.specs-panel { width: 100%; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(0,0,0,0.4); padding: 12px; }
.diag-cat { font-family: var(--ui-font); font-size: 12px; font-weight: 800; color: #55aaff; letter-spacing: 1px; text-transform: uppercase; margin: 12px 0 4px 0; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 2px;}
.diag-cat:first-child { margin-top: 0; }
.diag-row {
display: flex; justify-content: space-between; font-family: var(--bit-font); font-size: 16px;
letter-spacing: 1px; text-transform: uppercase; margin-bottom: 4px;
}
/* === 3D INSPECT MODAL === */
.inspect-modal {
position: fixed; inset: 0; background: rgba(10,11,15,0.95); z-index: 1000;
display: flex; align-items: center; justify-content: center; flex-direction: column;
opacity: 0; pointer-events: none; transition: opacity 0.3s ease; backdrop-filter: blur(10px);
}
.inspect-modal.active { opacity: 1; pointer-events: auto; }
.inspect-close { position: absolute; top: 30px; right: 40px; font-size: 40px; color: var(--muted); cursor: pointer; transition: color 0.2s; }
.inspect-close:hover { color: #ff5555; }
.inspect-stage { width: 600px; height: 600px; perspective: 1200px; display: flex; align-items: center; justify-content: center; margin-top: 20px;}
.inspect-object { width: 100%; height: 100%; transform-style: preserve-3d; transition: transform 0.1s cubic-bezier(0.2, 0.8, 0.2, 1); display: flex; align-items: center; justify-content: center; }
.inspect-object svg { width: 100%; height: 100%; filter: drop-shadow(0 30px 40px rgba(0,0,0,0.8)); }

View File

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