You've already forked computing-box
Fully functional logic gates page
All checks were successful
Pre-release on non-main branches / prerelease (push) Successful in 28s
All checks were successful
Pre-release on non-main branches / prerelease (push) Successful in 28s
Signed-off-by: Alexander Lyall <alex@adcm.uk>
This commit is contained in:
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" width="115" height="48"><path fill="#17191E" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="url(#a)" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="#17191E" d="M.02 30.31s4.02-1.95 8.05-1.95l3.04-9.4c.11-.45.44-.76.82-.76.37 0 .7.31.82.76l3.04 9.4c4.77 0 8.05 1.95 8.05 1.95L17 11.71c-.2-.56-.53-.91-.98-.91H7.83c-.44 0-.76.35-.97.9L.02 30.31Zm42.37-5.97c0 1.64-2.05 2.62-4.88 2.62-1.85 0-2.5-.45-2.5-1.41 0-1 .8-1.49 2.65-1.49 1.67 0 3.09.03 4.73.23v.05Zm.03-2.04a21.37 21.37 0 0 0-4.37-.36c-5.32 0-7.82 1.25-7.82 4.18 0 3.04 1.71 4.2 5.68 4.2 3.35 0 5.63-.84 6.46-2.92h.14c-.03.5-.05 1-.05 1.4 0 1.07.18 1.16 1.06 1.16h4.15a16.9 16.9 0 0 1-.36-4c0-1.67.06-2.93.06-4.62 0-3.45-2.07-5.64-8.56-5.64-2.8 0-5.9.48-8.26 1.19.22.93.54 2.83.7 4.06 2.04-.96 4.95-1.37 7.2-1.37 3.11 0 3.97.71 3.97 2.15v.57Zm11.37 3c-.56.07-1.33.07-2.12.07-.83 0-1.6-.03-2.12-.1l-.02.58c0 2.85 1.87 4.52 8.45 4.52 6.2 0 8.2-1.64 8.2-4.55 0-2.74-1.33-4.09-7.2-4.39-4.58-.2-4.99-.7-4.99-1.28 0-.66.59-1 3.65-1 3.18 0 4.03.43 4.03 1.35v.2a46.13 46.13 0 0 1 4.24.03l.02-.55c0-3.36-2.8-4.46-8.2-4.46-6.08 0-8.13 1.49-8.13 4.39 0 2.6 1.64 4.23 7.48 4.48 4.3.14 4.77.62 4.77 1.28 0 .7-.7 1.03-3.71 1.03-3.47 0-4.35-.48-4.35-1.47v-.13Zm19.82-12.05a17.5 17.5 0 0 1-6.24 3.48c.03.84.03 2.4.03 3.24l1.5.02c-.02 1.63-.04 3.6-.04 4.9 0 3.04 1.6 5.32 6.58 5.32 2.1 0 3.5-.23 5.23-.6a43.77 43.77 0 0 1-.46-4.13c-1.03.34-2.34.53-3.78.53-2 0-2.82-.55-2.82-2.13 0-1.37 0-2.65.03-3.84 2.57.02 5.13.07 6.64.11-.02-1.18.03-2.9.1-4.04-2.2.04-4.65.07-6.68.07l.07-2.93h-.16Zm13.46 6.04a767.33 767.33 0 0 1 .07-3.18H82.6c.07 1.96.07 3.98.07 6.92 0 2.95-.03 4.99-.07 6.93h5.18c-.09-1.37-.11-3.68-.11-5.65 0-3.1 1.26-4 4.12-4 1.33 0 2.28.16 3.1.46.03-1.16.26-3.43.4-4.43-.86-.25-1.81-.41-2.96-.41-2.46-.03-4.26.98-5.1 3.38l-.17-.02Zm22.55 3.65c0 2.5-1.8 3.66-4.64 3.66-2.81 0-4.61-1.1-4.61-3.66s1.82-3.52 4.61-3.52c2.82 0 4.64 1.03 4.64 3.52Zm4.71-.11c0-4.96-3.87-7.18-9.35-7.18-5.5 0-9.23 2.22-9.23 7.18 0 4.94 3.49 7.59 9.21 7.59 5.77 0 9.37-2.65 9.37-7.6Z"/><defs><linearGradient id="a" x1="6.33" x2="19.43" y1="40.8" y2="34.6" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient></defs></svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,210 +0,0 @@
|
|||||||
---
|
|
||||||
import astroLogo from '../assets/astro.svg';
|
|
||||||
import background from '../assets/background.svg';
|
|
||||||
---
|
|
||||||
|
|
||||||
<div id="container">
|
|
||||||
<img id="background" src={background.src} alt="" fetchpriority="high" />
|
|
||||||
<main>
|
|
||||||
<section id="hero">
|
|
||||||
<a href="https://astro.build"
|
|
||||||
><img src={astroLogo.src} width="115" height="48" alt="Astro Homepage" /></a
|
|
||||||
>
|
|
||||||
<h1>
|
|
||||||
To get started, open the <code><pre>src/pages</pre></code> directory in your project.
|
|
||||||
</h1>
|
|
||||||
<section id="links">
|
|
||||||
<a class="button" href="https://docs.astro.build">Read our docs</a>
|
|
||||||
<a href="https://astro.build/chat"
|
|
||||||
>Join our Discord <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"
|
|
||||||
><path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M107.7 8.07A105.15 105.15 0 0 0 81.47 0a72.06 72.06 0 0 0-3.36 6.83 97.68 97.68 0 0 0-29.11 0A72.37 72.37 0 0 0 45.64 0a105.89 105.89 0 0 0-26.25 8.09C2.79 32.65-1.71 56.6.54 80.21a105.73 105.73 0 0 0 32.17 16.15 77.7 77.7 0 0 0 6.89-11.11 68.42 68.42 0 0 1-10.85-5.18c.91-.66 1.8-1.34 2.66-2a75.57 75.57 0 0 0 64.32 0c.87.71 1.76 1.39 2.66 2a68.68 68.68 0 0 1-10.87 5.19 77 77 0 0 0 6.89 11.1 105.25 105.25 0 0 0 32.19-16.14c2.64-27.38-4.51-51.11-18.9-72.15ZM42.45 65.69C36.18 65.69 31 60 31 53s5-12.74 11.43-12.74S54 46 53.89 53s-5.05 12.69-11.44 12.69Zm42.24 0C78.41 65.69 73.25 60 73.25 53s5-12.74 11.44-12.74S96.23 46 96.12 53s-5.04 12.69-11.43 12.69Z"
|
|
||||||
></path></svg
|
|
||||||
>
|
|
||||||
</a>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<a href="https://astro.build/blog/astro-5/" id="news" class="box">
|
|
||||||
<svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg"
|
|
||||||
><path
|
|
||||||
d="M24.667 12c1.333 1.414 2 3.192 2 5.334 0 4.62-4.934 5.7-7.334 12C18.444 28.567 18 27.456 18 26c0-4.642 6.667-7.053 6.667-14Zm-5.334-5.333c1.6 1.65 2.4 3.43 2.4 5.333 0 6.602-8.06 7.59-6.4 17.334C13.111 27.787 12 25.564 12 22.666c0-4.434 7.333-8 7.333-16Zm-6-5.333C15.111 3.555 16 5.556 16 7.333c0 8.333-11.333 10.962-5.333 22-3.488-.774-6-4-6-8 0-8.667 8.666-10 8.666-20Z"
|
|
||||||
fill="#111827"></path></svg
|
|
||||||
>
|
|
||||||
<h2>What's New in Astro 5.0?</h2>
|
|
||||||
<p>
|
|
||||||
From content layers to server islands, click to learn more about the new features and
|
|
||||||
improvements in Astro 5.0
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#background {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: -1;
|
|
||||||
filter: blur(100px);
|
|
||||||
}
|
|
||||||
|
|
||||||
#container {
|
|
||||||
font-family: Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hero {
|
|
||||||
display: flex;
|
|
||||||
align-items: start;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 22px;
|
|
||||||
margin-top: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#links {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#links a {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px 12px;
|
|
||||||
color: #111827;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#links a:hover {
|
|
||||||
color: rgb(78, 80, 86);
|
|
||||||
}
|
|
||||||
|
|
||||||
#links a svg {
|
|
||||||
height: 1em;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#links a.button {
|
|
||||||
color: white;
|
|
||||||
background: linear-gradient(83.21deg, #3245ff 0%, #bc52ee 100%);
|
|
||||||
box-shadow:
|
|
||||||
inset 0 0 0 1px rgba(255, 255, 255, 0.12),
|
|
||||||
inset 0 -2px 0 rgba(0, 0, 0, 0.24);
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#links a.button:hover {
|
|
||||||
color: rgb(230, 230, 230);
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
font-family:
|
|
||||||
ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono',
|
|
||||||
monospace;
|
|
||||||
font-weight: normal;
|
|
||||||
background: linear-gradient(14deg, #d83333 0%, #f041ff 100%);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin: 0 0 1em;
|
|
||||||
font-weight: normal;
|
|
||||||
color: #111827;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: #4b5563;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 24px;
|
|
||||||
letter-spacing: -0.006em;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
display: inline-block;
|
|
||||||
background:
|
|
||||||
linear-gradient(66.77deg, #f3cddd 0%, #f5cee7 100%) padding-box,
|
|
||||||
linear-gradient(155deg, #d83333 0%, #f041ff 18%, #f5cee7 45%) border-box;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
padding: 6px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box {
|
|
||||||
padding: 16px;
|
|
||||||
background: rgba(255, 255, 255, 1);
|
|
||||||
border-radius: 16px;
|
|
||||||
border: 1px solid white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#news {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 16px;
|
|
||||||
right: 16px;
|
|
||||||
max-width: 300px;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: background 0.2s;
|
|
||||||
backdrop-filter: blur(50px);
|
|
||||||
}
|
|
||||||
|
|
||||||
#news:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.55);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-height: 368px) {
|
|
||||||
#news {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
|
||||||
#container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
#hero {
|
|
||||||
display: block;
|
|
||||||
padding-top: 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#links {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
#links a.button {
|
|
||||||
padding: 14px 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#news {
|
|
||||||
right: 16px;
|
|
||||||
left: 16px;
|
|
||||||
bottom: 2.5rem;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,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>
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
@@ -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) (0–9, A–F).`;
|
|
||||||
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 0–9 and A–F 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();
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -1,381 +0,0 @@
|
|||||||
---
|
|
||||||
const { mode = "unsigned", defaultBits = 8 } = Astro.props;
|
|
||||||
|
|
||||||
// For unsigned: min 1 bit, max 16 bits (tweak if you want)
|
|
||||||
const minBits = 4;
|
|
||||||
const maxBits = 16;
|
|
||||||
const initialBits = Math.min(Math.max(defaultBits, minBits), maxBits);
|
|
||||||
---
|
|
||||||
|
|
||||||
<section class="binary-sim" data-mode={mode} data-bits={initialBits}>
|
|
||||||
<div class="top">
|
|
||||||
<div class="left-brand">
|
|
||||||
<!-- Replace with your logo/img -->
|
|
||||||
<div class="brand-box" aria-hidden="true">Computing:Box</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="display" aria-live="polite">
|
|
||||||
<div class="label">DENARY</div>
|
|
||||||
<div id="denaryNumber" class="value">0</div>
|
|
||||||
|
|
||||||
<div class="label">BINARY</div>
|
|
||||||
<div id="binaryNumber" class="value mono">00000000</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="actions">
|
|
||||||
<button class="btn" type="button" data-action="custom-binary">Custom Binary</button>
|
|
||||||
<button class="btn" type="button" data-action="custom-denary">Custom Denary</button>
|
|
||||||
<button class="btn" type="button" data-action="shift-left">Left Shift</button>
|
|
||||||
<button class="btn" type="button" data-action="shift-right">Right Shift</button>
|
|
||||||
|
|
||||||
<div class="bits-control">
|
|
||||||
<div class="bits-title">Bits</div>
|
|
||||||
<div class="bits-buttons">
|
|
||||||
<button class="btn small" type="button" data-action="bits-minus" aria-label="Decrease bits">−</button>
|
|
||||||
<span id="bitsCount" class="bits-count">{initialBits}</span>
|
|
||||||
<button class="btn small" type="button" data-action="bits-plus" aria-label="Increase bits">+</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="bitsRow" class="bits-row" aria-label="Binary bit switches"></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<script is:inline>
|
|
||||||
(() => {
|
|
||||||
const root = document.currentScript.closest(".binary-sim");
|
|
||||||
const bitsRow = root.querySelector("#bitsRow");
|
|
||||||
const binaryEl = root.querySelector("#binaryNumber");
|
|
||||||
const denaryEl = root.querySelector("#denaryNumber");
|
|
||||||
const bitsCountEl = root.querySelector("#bitsCount");
|
|
||||||
|
|
||||||
let bitCount = parseInt(root.dataset.bits || "8", 10);
|
|
||||||
const minBits = 4;
|
|
||||||
const maxBits = 16;
|
|
||||||
|
|
||||||
// state: array of 0/1, MSB at index 0
|
|
||||||
let bits = new Array(bitCount).fill(0);
|
|
||||||
|
|
||||||
function placeValues(n) {
|
|
||||||
// unsigned place values: [2^(n-1), ..., 2^0]
|
|
||||||
return Array.from({ length: n }, (_, i) => 2 ** (n - 1 - i));
|
|
||||||
}
|
|
||||||
|
|
||||||
function bitsToDenary(bitsArr) {
|
|
||||||
const pv = placeValues(bitsArr.length);
|
|
||||||
return bitsArr.reduce((acc, b, i) => acc + (b ? pv[i] : 0), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function render() {
|
|
||||||
const binaryStr = bits.join("");
|
|
||||||
binaryEl.textContent = binaryStr;
|
|
||||||
denaryEl.textContent = String(bitsToDenary(bits));
|
|
||||||
bitsCountEl.textContent = String(bitCount);
|
|
||||||
|
|
||||||
const pv = placeValues(bitCount);
|
|
||||||
|
|
||||||
bitsRow.innerHTML = pv.map((value, i) => {
|
|
||||||
const id = `bit_${bitCount}_${i}`;
|
|
||||||
const checked = bits[i] === 1 ? "checked" : "";
|
|
||||||
return `
|
|
||||||
<div class="bit-col">
|
|
||||||
<div class="bulb ${bits[i] ? "on" : "off"}" aria-hidden="true"></div>
|
|
||||||
<div class="place">${value}</div>
|
|
||||||
|
|
||||||
<label class="switch" for="${id}">
|
|
||||||
<input id="${id}" type="checkbox" ${checked} data-index="${i}">
|
|
||||||
<span class="slider" aria-hidden="true"></span>
|
|
||||||
<span class="sr-only">Toggle bit value ${value}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}).join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
function setBitsFromBinaryString(str) {
|
|
||||||
// allow shorter input; pad left
|
|
||||||
const clean = (str || "").trim();
|
|
||||||
if (!/^[01]+$/.test(clean) || clean.length > bitCount) return false;
|
|
||||||
const padded = clean.padStart(bitCount, "0");
|
|
||||||
bits = padded.split("").map(c => c === "1" ? 1 : 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setBitsFromDenary(num) {
|
|
||||||
if (!Number.isInteger(num)) return false;
|
|
||||||
const max = (2 ** bitCount) - 1;
|
|
||||||
if (num < 0 || num > max) return false;
|
|
||||||
|
|
||||||
const bin = num.toString(2).padStart(bitCount, "0");
|
|
||||||
bits = bin.split("").map(c => c === "1" ? 1 : 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function shiftLeft() {
|
|
||||||
bits = bits.slice(1).concat(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function shiftRight() {
|
|
||||||
bits = [0].concat(bits.slice(0, -1));
|
|
||||||
}
|
|
||||||
|
|
||||||
function resizeBits(newCount) {
|
|
||||||
const clamped = Math.max(minBits, Math.min(maxBits, newCount));
|
|
||||||
if (clamped === bitCount) return;
|
|
||||||
|
|
||||||
// keep value as best as possible: preserve LSBs when resizing
|
|
||||||
const currentBinary = bits.join("");
|
|
||||||
bitCount = clamped;
|
|
||||||
bits = new Array(bitCount).fill(0);
|
|
||||||
|
|
||||||
const take = currentBinary.slice(-bitCount);
|
|
||||||
setBitsFromBinaryString(take);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch input handler
|
|
||||||
bitsRow.addEventListener("change", (e) => {
|
|
||||||
const input = e.target;
|
|
||||||
if (!(input instanceof HTMLInputElement)) return;
|
|
||||||
const idx = parseInt(input.dataset.index || "-1", 10);
|
|
||||||
if (idx < 0) return;
|
|
||||||
bits[idx] = input.checked ? 1 : 0;
|
|
||||||
render();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Button actions
|
|
||||||
root.querySelector(".actions").addEventListener("click", (e) => {
|
|
||||||
const btn = e.target.closest("button[data-action]");
|
|
||||||
if (!btn) return;
|
|
||||||
|
|
||||||
const action = btn.dataset.action;
|
|
||||||
|
|
||||||
if (action === "custom-binary") {
|
|
||||||
const entered = prompt(`Enter a ${bitCount}-bit binary value (0s and 1s):`, bits.join(""));
|
|
||||||
if (entered === null) return;
|
|
||||||
if (!setBitsFromBinaryString(entered)) {
|
|
||||||
alert(`Invalid binary. Use only 0 and 1, up to ${bitCount} digits.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
render();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === "custom-denary") {
|
|
||||||
const max = (2 ** bitCount) - 1;
|
|
||||||
const entered = prompt(`Enter a denary value (0 to ${max}):`, String(bitsToDenary(bits)));
|
|
||||||
if (entered === null) return;
|
|
||||||
const num = Number.parseInt(entered, 10);
|
|
||||||
if (!setBitsFromDenary(num)) {
|
|
||||||
alert(`Invalid denary. Enter a whole number from 0 to ${max}.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
render();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (action === "shift-left") { shiftLeft(); render(); }
|
|
||||||
if (action === "shift-right") { shiftRight(); render(); }
|
|
||||||
|
|
||||||
if (action === "bits-minus") { resizeBits(bitCount - 1); render(); }
|
|
||||||
if (action === "bits-plus") { resizeBits(bitCount + 1); render(); }
|
|
||||||
});
|
|
||||||
|
|
||||||
// initial
|
|
||||||
render();
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Layout */
|
|
||||||
.binary-sim {
|
|
||||||
padding: 2rem 1rem 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 260px 1fr 220px;
|
|
||||||
gap: 1.5rem;
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 980px) {
|
|
||||||
.top {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
.actions {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Brand placeholder */
|
|
||||||
.brand-box {
|
|
||||||
width: 180px;
|
|
||||||
height: 180px;
|
|
||||||
border-radius: 12px;
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
font-weight: 700;
|
|
||||||
opacity: 0.9;
|
|
||||||
border: 1px solid rgba(255,255,255,0.08);
|
|
||||||
background: rgba(255,255,255,0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Display */
|
|
||||||
.display {
|
|
||||||
text-align: center;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
opacity: 0.85;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.value {
|
|
||||||
font-size: 3rem;
|
|
||||||
line-height: 1.1;
|
|
||||||
margin: 0.25rem 0 0.75rem;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mono {
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Buttons */
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
border: 0;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
background: rgba(255,255,255,0.08);
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover { background: rgba(255,255,255,0.12); }
|
|
||||||
|
|
||||||
.btn.small {
|
|
||||||
padding: 0.45rem 0.75rem;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bits-control {
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
border-top: 1px solid rgba(255,255,255,0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bits-title { opacity: 0.8; margin-bottom: 0.35rem; }
|
|
||||||
|
|
||||||
.bits-buttons {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bits-count {
|
|
||||||
min-width: 2ch;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Bits row */
|
|
||||||
.bits-row {
|
|
||||||
margin-top: 2rem;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
|
|
||||||
gap: 1.25rem;
|
|
||||||
align-items: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bit-col {
|
|
||||||
text-align: center;
|
|
||||||
padding: 0.5rem 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.place {
|
|
||||||
margin: 0.5rem 0 0.5rem;
|
|
||||||
font-size: 2rem;
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Bulb */
|
|
||||||
.bulb {
|
|
||||||
width: 26px;
|
|
||||||
height: 26px;
|
|
||||||
margin: 0 auto 0.25rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 1px solid rgba(255,255,255,0.15);
|
|
||||||
}
|
|
||||||
.bulb.off { opacity: 0.15; }
|
|
||||||
.bulb.on { opacity: 1; }
|
|
||||||
|
|
||||||
/* Light switch (accessible checkbox) */
|
|
||||||
.switch {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 64px;
|
|
||||||
height: 36px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch input {
|
|
||||||
position: absolute;
|
|
||||||
opacity: 0;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider {
|
|
||||||
width: 64px;
|
|
||||||
height: 36px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: rgba(255,255,255,0.10);
|
|
||||||
border: 1px solid rgba(255,255,255,0.14);
|
|
||||||
position: relative;
|
|
||||||
transition: transform 120ms ease, background 120ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 4px;
|
|
||||||
left: 4px;
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: rgba(255,255,255,0.85);
|
|
||||||
transition: transform 120ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch input:checked + .slider {
|
|
||||||
background: rgba(255,255,255,0.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch input:checked + .slider::after {
|
|
||||||
transform: translateX(28px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* focus */
|
|
||||||
.switch input:focus-visible + .slider {
|
|
||||||
outline: 3px solid rgba(255,255,255,0.35);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px; height: 1px;
|
|
||||||
padding: 0; margin: -1px;
|
|
||||||
overflow: hidden; clip: rect(0,0,0,0);
|
|
||||||
white-space: nowrap; border: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
---
|
|
||||||
import Welcome from '../components/Welcome.astro';
|
|
||||||
import Layout from '../layouts/Layout.astro';
|
|
||||||
|
|
||||||
// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
|
|
||||||
// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
|
|
||||||
---
|
|
||||||
|
|
||||||
<Layout>
|
|
||||||
<Welcome />
|
|
||||||
</Layout>
|
|
||||||
@@ -4,41 +4,31 @@ import "../styles/logic-gates.css";
|
|||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title="Logic Gate Builder | Computing:Box">
|
<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">
|
<button id="toolboxToggle" class="toolboxToggle" type="button" aria-expanded="true">
|
||||||
<span class="toolboxIcon" aria-hidden="true">🧰</span>
|
<span class="toolboxIcon" aria-hidden="true">🧰</span>
|
||||||
<span class="toolboxText">TOOLBOX</span>
|
<span class="toolboxText">TOOLBOX</span>
|
||||||
</button>
|
</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 to the board. Drag from output ports to input ports to wire. Click a wire or node and press <kbd>Delete</kbd> to remove it.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="readout">
|
<div class="lg-workspace" id="workspace">
|
||||||
<div class="readoutBlock">
|
<svg class="lg-svg-layer" id="wireLayer"></svg>
|
||||||
<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>
|
</div>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<aside id="toolboxPanel" class="lg-toolbox" aria-label="Toolbox">
|
||||||
|
|
||||||
<div class="lg-workspace" id="workspace">
|
|
||||||
<svg class="lg-svg-layer" id="wireLayer"></svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<aside id="toolboxPanel" class="panelCol" aria-label="Toolbox">
|
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="cardTitle">Components</div>
|
<div class="cardTitle">Components</div>
|
||||||
|
|
||||||
<div class="tb-icon-grid" id="toolboxGrid">
|
<div class="tb-icon-grid" id="toolboxGrid">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|||||||
@@ -32,12 +32,10 @@
|
|||||||
|
|
||||||
let nextNodeId = 1;
|
let nextNodeId = 1;
|
||||||
let nextWireId = 1;
|
let nextWireId = 1;
|
||||||
let inputCount = 0;
|
|
||||||
let outputCount = 0;
|
|
||||||
|
|
||||||
let isDraggingNode = null;
|
let isDraggingNode = null;
|
||||||
let dragOffset = { x: 0, y: 0 };
|
let dragOffset = { x: 0, y: 0 };
|
||||||
let clickStartX = 0, clickStartY = 0; // Fixes switch drag conflict
|
let clickStartX = 0, clickStartY = 0;
|
||||||
|
|
||||||
let wiringStart = null;
|
let wiringStart = null;
|
||||||
let tempWirePath = null;
|
let tempWirePath = null;
|
||||||
@@ -252,6 +250,23 @@
|
|||||||
generateTruthTable();
|
generateTruthTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Smart Label Generation --- */
|
||||||
|
function getNextInputLabel() {
|
||||||
|
let charCode = 65; // Starts at 'A'
|
||||||
|
while (Object.values(nodes).some(n => n.type === 'INPUT' && n.label === String.fromCharCode(charCode))) {
|
||||||
|
charCode++;
|
||||||
|
}
|
||||||
|
return String.fromCharCode(charCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextOutputLabel() {
|
||||||
|
let idx = 1;
|
||||||
|
while (Object.values(nodes).some(n => n.type === 'OUTPUT' && n.label === ('Q' + idx))) {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
return 'Q' + idx;
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Node Creation --- */
|
/* --- Node Creation --- */
|
||||||
function createNodeElement(node) {
|
function createNodeElement(node) {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
@@ -292,7 +307,6 @@
|
|||||||
node.el = el;
|
node.el = el;
|
||||||
|
|
||||||
if (node.type === 'INPUT') {
|
if (node.type === 'INPUT') {
|
||||||
// Custom click handler to prevent dragging from toggling the switch
|
|
||||||
el.querySelector('.switch').addEventListener('click', (e) => {
|
el.querySelector('.switch').addEventListener('click', (e) => {
|
||||||
const dist = Math.hypot(e.clientX - clickStartX, e.clientY - clickStartY);
|
const dist = Math.hypot(e.clientX - clickStartX, e.clientY - clickStartY);
|
||||||
if (isDraggingNode || dist > 3) {
|
if (isDraggingNode || dist > 3) {
|
||||||
@@ -313,9 +327,9 @@
|
|||||||
|
|
||||||
function spawnNode(type, gateType = null, dropX = null, dropY = null) {
|
function spawnNode(type, gateType = null, dropX = null, dropY = null) {
|
||||||
let label = '';
|
let label = '';
|
||||||
if (type === 'INPUT') { inputCount++; label = String.fromCharCode(64 + inputCount); }
|
if (type === 'INPUT') label = getNextInputLabel();
|
||||||
if (type === 'OUTPUT') { outputCount++; label = `Q${outputCount}`; }
|
if (type === 'OUTPUT') label = getNextOutputLabel();
|
||||||
if (type === 'GATE') { label = gateType; }
|
if (type === 'GATE') label = gateType;
|
||||||
|
|
||||||
const id = `node_${nextNodeId++}`;
|
const id = `node_${nextNodeId++}`;
|
||||||
|
|
||||||
@@ -468,8 +482,6 @@
|
|||||||
workspace.querySelectorAll('.lg-node').forEach(el => el.remove());
|
workspace.querySelectorAll('.lg-node').forEach(el => el.remove());
|
||||||
nodes = {};
|
nodes = {};
|
||||||
connections = [];
|
connections = [];
|
||||||
inputCount = 0;
|
|
||||||
outputCount = 0;
|
|
||||||
runSimulation();
|
runSimulation();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -481,9 +493,5 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
initToolbox();
|
initToolbox();
|
||||||
spawnNode('INPUT', null, 80, 150);
|
// Starts completely blank as requested!
|
||||||
spawnNode('INPUT', null, 80, 250);
|
|
||||||
spawnNode('GATE', 'AND', 320, 200);
|
|
||||||
spawnNode('OUTPUT', null, 600, 200);
|
|
||||||
|
|
||||||
})();
|
})();
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/* src/styles/base.css */
|
|
||||||
@import "./md3-tokens.css";
|
|
||||||
html, body{ height:100%; }
|
|
||||||
body{
|
|
||||||
margin:0;
|
|
||||||
font-family: var(--font-sans);
|
|
||||||
background: var(--md-surface-2);
|
|
||||||
color: var(--md-on-surface);
|
|
||||||
}
|
|
||||||
a{ color: var(--md-primary); text-decoration: none; }
|
|
||||||
a:hover{ text-decoration: underline; }
|
|
||||||
.container{
|
|
||||||
max-width: 1100px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
.card{
|
|
||||||
background: var(--md-surface);
|
|
||||||
border: 1px solid var(--md-outline);
|
|
||||||
border-radius: var(--radius-2);
|
|
||||||
box-shadow: var(--shadow-1);
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
.btn{
|
|
||||||
display:inline-flex;
|
|
||||||
gap:8px;
|
|
||||||
align-items:center;
|
|
||||||
justify-content:center;
|
|
||||||
border-radius: 999px;
|
|
||||||
border: 1px solid var(--md-outline);
|
|
||||||
background: var(--md-surface);
|
|
||||||
color: var(--md-on-surface);
|
|
||||||
padding: 10px 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.btn:hover{ filter: brightness(0.98); }
|
|
||||||
.btn:focus{ outline:none; box-shadow: var(--md-focus); }
|
|
||||||
.btn-primary{
|
|
||||||
background: var(--md-primary);
|
|
||||||
color: var(--md-on-primary);
|
|
||||||
border-color: transparent;
|
|
||||||
}
|
|
||||||
.badge{
|
|
||||||
display:inline-block;
|
|
||||||
padding: 2px 10px;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-size: 12px;
|
|
||||||
border: 1px solid var(--md-outline);
|
|
||||||
background: var(--md-surface-2);
|
|
||||||
}
|
|
||||||
code, pre{ font-family: var(--font-mono); }
|
|
||||||
@@ -1,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;
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,83 @@
|
|||||||
/* === LOGIC GATES CANVAS CSS === */
|
/* === FULL PAGE OVERRIDES FOR LOGIC GATES === */
|
||||||
.lg-workspace {
|
body:has(#logicPage) {
|
||||||
position: relative;
|
overflow: hidden; /* Prevents the entire page from scrolling */
|
||||||
|
}
|
||||||
|
|
||||||
|
body:has(#logicPage) .pageWrap {
|
||||||
|
max-width: 100% !important; /* Forces edge-to-edge canvas */
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
height: calc(100vh - var(--nav-h));
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logicPage {
|
||||||
|
padding: 0 !important; /* CRITICAL: Stops the page/header from shifting when toolbox opens */
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === MAIN CONTAINER === */
|
||||||
|
.lg-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === FIXED HEADER (Ultra Compact) === */
|
||||||
|
.lg-top-header {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 8px 20px 8px; /* Extremely tight padding to maximize canvas */
|
||||||
|
background: var(--bg);
|
||||||
|
z-index: 10;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.08); /* Clean separation line */
|
||||||
|
}
|
||||||
|
|
||||||
|
.lg-title {
|
||||||
|
font-family: var(--bit-font);
|
||||||
|
font-size: 32px;
|
||||||
|
letter-spacing: 0.12em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--text);
|
||||||
|
margin: 0 0 2px 0; /* Minimal gap between title and subtitle */
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lg-subtitle {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: var(--ui-font);
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lg-subtitle kbd {
|
||||||
|
background: rgba(255,255,255,0.1);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: var(--ui-font);
|
||||||
|
color: #e8e8ee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === DYNAMIC CANVAS === */
|
||||||
|
.lg-workspace {
|
||||||
|
flex: 1; /* Automatically fills all remaining vertical space */
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 750px;
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
background-image: radial-gradient(rgba(255,255,255,0.12) 1px, transparent 1px);
|
background-image: radial-gradient(rgba(255,255,255,0.12) 1px, transparent 1px);
|
||||||
background-size: 24px 24px;
|
background-size: 24px 24px;
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +97,7 @@
|
|||||||
fill: none;
|
fill: none;
|
||||||
stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
transition: stroke 0.1s ease, filter 0.1s ease, stroke-width 0.1s ease;
|
transition: stroke 0.1s ease, filter 0.1s ease, stroke-width 0.1s ease;
|
||||||
pointer-events: stroke; /* Allows wires to be clicked */
|
pointer-events: stroke;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.lg-wire:hover {
|
.lg-wire:hover {
|
||||||
@@ -56,7 +124,7 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Nodes (Borderless & Transparent) */
|
/* Nodes */
|
||||||
.lg-node {
|
.lg-node {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -68,17 +136,12 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
box-shadow: none;
|
|
||||||
backdrop-filter: none;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transition: filter 0.2s;
|
transition: filter 0.2s;
|
||||||
}
|
}
|
||||||
.lg-node:active { cursor: grabbing; z-index: 20; }
|
.lg-node:active { cursor: grabbing; z-index: 20; }
|
||||||
.lg-node.selected {
|
.lg-node.selected { filter: drop-shadow(0 0 10px rgba(255,85,85,0.8)); }
|
||||||
filter: drop-shadow(0 0 10px rgba(255,85,85,0.8));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Node Labels (Seven-Segment, +2 Sizes Bigger) */
|
|
||||||
.lg-header {
|
.lg-header {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@@ -88,40 +151,71 @@
|
|||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Container mapping SVGs to absolutely positioned connection dots */
|
|
||||||
.lg-gate-container {
|
.lg-gate-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.lg-gate-svg { width: 100px; height: 50px; display: block; }
|
||||||
.lg-gate-svg {
|
.lg-line-svg { width: 30px; height: 50px; display: block; }
|
||||||
width: 100px;
|
|
||||||
height: 50px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lg-line-svg {
|
|
||||||
width: 30px;
|
|
||||||
height: 50px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Connection Ports */
|
/* Connection Ports */
|
||||||
.lg-port {
|
.lg-port {
|
||||||
width: 16px; height: 16px; background: #a9acb8; border-radius: 50%; cursor: crosshair;
|
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;
|
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;
|
position: absolute; z-index: 5;
|
||||||
transform: translate(-50%, -50%); /* Centers the dot exactly over the coordinate */
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
.lg-port:hover { transform: translate(-50%, -50%) scale(1.3); background: #fff; }
|
.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; }
|
.lg-port.active { background: #28f07a; box-shadow: 0 0 12px rgba(40,240,122,0.8); border-color: #1f2027; }
|
||||||
|
|
||||||
/* Draggable Toolbox Visual Gates Grid */
|
/* === FLOATING TOOLBOX === */
|
||||||
|
.toolboxToggle {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px; /* Snug inside the thinner header */
|
||||||
|
right: 20px;
|
||||||
|
z-index: 90;
|
||||||
|
display: flex; align-items: center; gap: 10px; padding: 8px 14px;
|
||||||
|
border-radius: 12px; border: 1px solid rgba(255,255,255,.12); background: rgba(0,0,0,.15);
|
||||||
|
backdrop-filter: blur(8px); color: rgba(232,232,238,.95); font-family: var(--bit-font);
|
||||||
|
font-weight: 800; font-size: 16px; letter-spacing: .12em; text-transform: uppercase; cursor: pointer;
|
||||||
|
}
|
||||||
|
.toolboxIcon { font-size: 20px; filter: drop-shadow(0 0 8px rgba(255,105,180,.35)); }
|
||||||
|
.toolboxToggle:hover { border-color: rgba(255,255,255,.22); }
|
||||||
|
|
||||||
|
.lg-toolbox {
|
||||||
|
position: absolute;
|
||||||
|
top: 60px; /* Sits right under the new thin header */
|
||||||
|
right: 20px;
|
||||||
|
bottom: 20px; /* Constrains the height so it scrolls internally */
|
||||||
|
width: var(--toolbox-w, 360px);
|
||||||
|
z-index: 80;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
transform: translateX(0);
|
||||||
|
transition: transform 420ms cubic-bezier(.2,.9,.2,1), opacity 220ms ease;
|
||||||
|
overflow-y: auto;
|
||||||
|
pointer-events: auto;
|
||||||
|
padding-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Faded Subdued Scrollbars */
|
||||||
|
.lg-toolbox::-webkit-scrollbar { width: 6px; }
|
||||||
|
.lg-toolbox::-webkit-scrollbar-track { background: transparent; }
|
||||||
|
.lg-toolbox::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.05); border-radius: 10px; transition: background 0.3s; }
|
||||||
|
.lg-toolbox:hover::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); }
|
||||||
|
.lg-toolbox::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.4); }
|
||||||
|
|
||||||
|
.lg-container.toolboxCollapsed .lg-toolbox {
|
||||||
|
transform: translateX(calc(100% + 40px));
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Toolbox Grid */
|
||||||
.tb-icon-grid {
|
.tb-icon-grid {
|
||||||
display: grid;
|
display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
}
|
||||||
.tb-icon-box {
|
.tb-icon-box {
|
||||||
background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.15);
|
background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.15);
|
||||||
@@ -132,14 +226,6 @@
|
|||||||
.tb-icon-box:hover { background: rgba(255,255,255,0.1); border-color: rgba(255,255,255,0.3); }
|
.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; }
|
.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 */
|
/* Truth Table */
|
||||||
.tt-summary {
|
.tt-summary {
|
||||||
font-family: var(--ui-font); font-size: 14px; font-weight: 800;
|
font-family: var(--ui-font); font-size: 14px; font-weight: 800;
|
||||||
@@ -147,17 +233,15 @@
|
|||||||
outline: none; margin-bottom: 10px; text-transform: uppercase;
|
outline: none; margin-bottom: 10px; text-transform: uppercase;
|
||||||
}
|
}
|
||||||
.tt-table-wrap {
|
.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);
|
border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); background: rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
.tt-table {
|
.tt-table-wrap::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||||
width: 100%; border-collapse: collapse; text-align: center;
|
.tt-table-wrap::-webkit-scrollbar-track { background: transparent; }
|
||||||
font-family: var(--num-font); font-size: 14px; color: #e8e8ee;
|
.tt-table-wrap::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
|
||||||
}
|
.tt-table-wrap:hover::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.3); }
|
||||||
.tt-table th {
|
|
||||||
position: sticky; top: 0; background: rgba(31,32,39,0.95);
|
.tt-table { width: 100%; border-collapse: collapse; text-align: center; font-family: var(--num-font); font-size: 14px; color: #e8e8ee; }
|
||||||
padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.15);
|
.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; }
|
||||||
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 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); }
|
.tt-table .tt-on { color: #28f07a; text-shadow: 0 0 8px rgba(40,240,122,0.5); }
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user