You've already forked computing-box
feat(binary): add full binary simulator with unsigned and two’s complement modes
- Introduce new Binary Simulator page with adjustable bit width (4–16 bits) - Support unsigned and two’s complement representations with live conversion - Add left/right shift operations and custom binary/denary input - Implement accessible bulb-and-switch UI with MD3-inspired styling - Add seven-segment display font assets for realistic number output - Establish shared base layout, styles, and tooling for future simulators Signed-off-by: Alexander Lyall <alex@adcm.uk>
This commit is contained in:
1
src/assets/astro.svg
Normal file
1
src/assets/astro.svg
Normal file
@@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
1
src/assets/background.svg
Normal file
1
src/assets/background.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="1024" fill="none"><path fill="url(#a)" fill-rule="evenodd" d="M-217.58 475.75c91.82-72.02 225.52-29.38 341.2-44.74C240 415.56 372.33 315.14 466.77 384.9c102.9 76.02 44.74 246.76 90.31 366.31 29.83 78.24 90.48 136.14 129.48 210.23 57.92 109.99 169.67 208.23 155.9 331.77-13.52 121.26-103.42 264.33-224.23 281.37-141.96 20.03-232.72-220.96-374.06-196.99-151.7 25.73-172.68 330.24-325.85 315.72-128.6-12.2-110.9-230.73-128.15-358.76-12.16-90.14 65.87-176.25 44.1-264.57-26.42-107.2-167.12-163.46-176.72-273.45-10.15-116.29 33.01-248.75 124.87-320.79Z" clip-rule="evenodd" style="opacity:.154"/><path fill="url(#b)" fill-rule="evenodd" d="M1103.43 115.43c146.42-19.45 275.33-155.84 413.5-103.59 188.09 71.13 409 212.64 407.06 413.88-1.94 201.25-259.28 278.6-414.96 405.96-130 106.35-240.24 294.39-405.6 265.3-163.7-28.8-161.93-274.12-284.34-386.66-134.95-124.06-436-101.46-445.82-284.6-9.68-180.38 247.41-246.3 413.54-316.9 101.01-42.93 207.83 21.06 316.62 6.61Z" clip-rule="evenodd" style="opacity:.154"/><defs><linearGradient id="b" x1="373" x2="1995.44" y1="1100" y2="118.03" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient><linearGradient id="a" x1="107.37" x2="1130.66" y1="1993.35" y2="1026.31" gradientUnits="userSpaceOnUse"><stop stop-color="#3245FF"/><stop offset="1" stop-color="#BC52EE"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
210
src/components/Welcome.astro
Normal file
210
src/components/Welcome.astro
Normal file
@@ -0,0 +1,210 @@
|
||||
---
|
||||
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>
|
||||
381
src/components/tools/BinarySimulator.astro
Normal file
381
src/components/tools/BinarySimulator.astro
Normal file
@@ -0,0 +1,381 @@
|
||||
---
|
||||
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>
|
||||
17
src/layouts/BaseLayout.astro
Normal file
17
src/layouts/BaseLayout.astro
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
// src/layouts/BaseLayout.astro
|
||||
const { title = "Computing:Box" } = Astro.props;
|
||||
---
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{title}</title>
|
||||
<link rel="stylesheet" href="/styles.css" />
|
||||
<link rel="stylesheet" href="/fonts/DSEG7Classic-Regular.css">
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
22
src/layouts/Layout.astro
Normal file
22
src/layouts/Layout.astro
Normal file
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>Astro Basics</title>
|
||||
</head>
|
||||
<body>
|
||||
<slot />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
293
src/pages/binary.astro
Normal file
293
src/pages/binary.astro
Normal file
@@ -0,0 +1,293 @@
|
||||
---
|
||||
let initialBits = 8;
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Binary | Computing:Box</title>
|
||||
|
||||
<style>
|
||||
:root{
|
||||
--bg:#1f2027;
|
||||
--text:#e8e8ee;
|
||||
--muted:#a9acb8;
|
||||
--accent:#33ff7a;
|
||||
--accent-dim:rgba(51,255,122,.2);
|
||||
--line:rgba(255,255,255,.12);
|
||||
}
|
||||
|
||||
@font-face{
|
||||
font-family:"DSEG7";
|
||||
src:url("/fonts/DSEG7Classic-Regular.woff") format("woff"),
|
||||
url("/fonts/DSEG7Classic-Regular.ttf") format("truetype");
|
||||
}
|
||||
|
||||
body{
|
||||
margin:0;
|
||||
background:var(--bg);
|
||||
color:var(--text);
|
||||
font-family:system-ui,Segoe UI,Roboto,Arial;
|
||||
}
|
||||
|
||||
.wrap{
|
||||
max-width:1200px;
|
||||
margin:auto;
|
||||
padding:32px 20px 60px;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.label{
|
||||
letter-spacing:.18em;
|
||||
color:var(--muted);
|
||||
font-weight:700;
|
||||
}
|
||||
|
||||
.num{
|
||||
font-family:"DSEG7",monospace;
|
||||
color:var(--accent);
|
||||
text-shadow:0 0 18px var(--accent-dim);
|
||||
}
|
||||
|
||||
.den{
|
||||
font-size:64px;
|
||||
}
|
||||
|
||||
.bin{
|
||||
font-size:44px;
|
||||
letter-spacing:.12em;
|
||||
margin-bottom:12px;
|
||||
}
|
||||
|
||||
.controls{
|
||||
display:flex;
|
||||
gap:10px;
|
||||
justify-content:center;
|
||||
flex-wrap:wrap;
|
||||
margin:16px 0;
|
||||
}
|
||||
|
||||
.btn{
|
||||
background:rgba(255,255,255,.08);
|
||||
border:1px solid rgba(255,255,255,.15);
|
||||
color:#fff;
|
||||
padding:10px 14px;
|
||||
border-radius:12px;
|
||||
cursor:pointer;
|
||||
font-weight:700;
|
||||
}
|
||||
|
||||
.panel{
|
||||
margin-top:20px;
|
||||
display:flex;
|
||||
gap:24px;
|
||||
justify-content:center;
|
||||
flex-wrap:wrap;
|
||||
}
|
||||
|
||||
.card{
|
||||
background:rgba(255,255,255,.04);
|
||||
border:1px solid var(--line);
|
||||
border-radius:14px;
|
||||
padding:14px 16px;
|
||||
min-width:220px;
|
||||
text-align:left;
|
||||
}
|
||||
|
||||
.card h4{
|
||||
margin:0 0 8px;
|
||||
font-size:13px;
|
||||
letter-spacing:.14em;
|
||||
color:var(--muted);
|
||||
}
|
||||
|
||||
.bits{
|
||||
margin-top:28px;
|
||||
padding-top:24px;
|
||||
border-top:1px solid var(--line);
|
||||
display:flex;
|
||||
justify-content:center;
|
||||
gap:20px;
|
||||
flex-wrap:wrap;
|
||||
}
|
||||
|
||||
.bit{
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
align-items:center;
|
||||
gap:8px;
|
||||
}
|
||||
|
||||
/* bulb */
|
||||
.bulb{
|
||||
width:22px;
|
||||
height:22px;
|
||||
border-radius:50%;
|
||||
background:rgba(255,255,255,.08);
|
||||
border:1px solid rgba(255,255,255,.12);
|
||||
}
|
||||
.bulb.on{
|
||||
background:#ffd86b;
|
||||
box-shadow:0 0 18px rgba(255,216,107,.7);
|
||||
}
|
||||
|
||||
/* SAME SWITCH USED EVERYWHERE */
|
||||
.switch{
|
||||
position:relative;
|
||||
width:56px;
|
||||
height:32px;
|
||||
}
|
||||
.switch input{opacity:0;width:0;height:0;}
|
||||
.slider{
|
||||
position:absolute;
|
||||
inset:0;
|
||||
background:rgba(255,255,255,.15);
|
||||
border-radius:999px;
|
||||
transition:.2s;
|
||||
}
|
||||
.slider::before{
|
||||
content:"";
|
||||
position:absolute;
|
||||
width:26px;
|
||||
height:26px;
|
||||
left:3px;
|
||||
top:3px;
|
||||
background:white;
|
||||
border-radius:50%;
|
||||
transition:.2s;
|
||||
}
|
||||
.switch input:checked + .slider{
|
||||
background:rgba(51,255,122,.4);
|
||||
}
|
||||
.switch input:checked + .slider::before{
|
||||
transform:translateX(24px);
|
||||
background:var(--accent);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="wrap">
|
||||
|
||||
<div class="label">DENARY</div>
|
||||
<div id="denary" class="num den">0</div>
|
||||
|
||||
<div class="label">BINARY</div>
|
||||
<div id="binary" class="num bin">00000000</div>
|
||||
|
||||
<div class="controls">
|
||||
<button class="btn" id="customBinary">Custom Binary</button>
|
||||
<button class="btn" id="customDenary">Custom Denary</button>
|
||||
<button class="btn" id="shiftL">Left Shift</button>
|
||||
<button class="btn" id="shiftR">Right Shift</button>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="card">
|
||||
<h4>MODE</h4>
|
||||
Unsigned
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="mode">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
Two’s complement
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h4>BIT WIDTH</h4>
|
||||
<input id="bitCount" type="number" min="4" max="64" value="8"
|
||||
style="width:100%;padding:6px;border-radius:8px;border:none;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="bits" id="bits"></section>
|
||||
|
||||
</main>
|
||||
|
||||
<script type="module">
|
||||
let bits = 8;
|
||||
let twos = false;
|
||||
let state = [];
|
||||
|
||||
const bitsEl = document.getElementById("bits");
|
||||
|
||||
function build(){
|
||||
state = Array(bits).fill(false);
|
||||
bitsEl.innerHTML = "";
|
||||
for(let i=bits-1;i>=0;i--){
|
||||
const val = 2**i;
|
||||
const div = document.createElement("div");
|
||||
div.className="bit";
|
||||
div.innerHTML=`
|
||||
<div class="bulb" id="b${i}"></div>
|
||||
<div class="num">${val}</div>
|
||||
<label class="switch">
|
||||
<input type="checkbox" data-i="${i}">
|
||||
<span class="slider"></span>
|
||||
</label>`;
|
||||
bitsEl.appendChild(div);
|
||||
}
|
||||
document.querySelectorAll('[data-i]').forEach(sw=>{
|
||||
sw.onchange=()=>{state[sw.dataset.i]=sw.checked;update();}
|
||||
});
|
||||
update();
|
||||
}
|
||||
|
||||
function update(){
|
||||
let n=0;
|
||||
state.forEach((on,i)=>{
|
||||
if(on) n+=2**i;
|
||||
document.getElementById(`b${i}`).classList.toggle("on",on);
|
||||
});
|
||||
|
||||
if(twos && state[bits-1]) n-=2**bits;
|
||||
|
||||
document.getElementById("denary").textContent=n;
|
||||
document.getElementById("binary").textContent=
|
||||
state.slice().reverse().map(b=>b?1:0).join("");
|
||||
}
|
||||
|
||||
document.getElementById("mode").onchange=e=>{
|
||||
twos=e.target.checked;
|
||||
update();
|
||||
};
|
||||
|
||||
document.getElementById("bitCount").onchange=e=>{
|
||||
const v=Math.max(4,Math.min(64,Number(e.target.value)));
|
||||
bits=v;
|
||||
build();
|
||||
};
|
||||
|
||||
document.getElementById("shiftL").onclick=()=>{
|
||||
state.shift();state.push(false);update();
|
||||
};
|
||||
document.getElementById("shiftR").onclick=()=>{
|
||||
state.pop();state.unshift(false);update();
|
||||
};
|
||||
|
||||
document.getElementById("customBinary").onclick=()=>{
|
||||
const v=prompt(`Enter ${bits}-bit binary`);
|
||||
if(!v||!/^[01]+$/.test(v))return;
|
||||
const p=v.padStart(bits,"0").slice(-bits);
|
||||
state=p.split("").reverse().map(b=>b==="1");
|
||||
build();
|
||||
};
|
||||
|
||||
document.getElementById("customDenary").onclick=()=>{
|
||||
const max=twos?(2**(bits-1)-1):(2**bits-1);
|
||||
const min=twos?-(2**(bits-1)):0;
|
||||
const n=Number(prompt(`Enter ${min} to ${max}`));
|
||||
if(isNaN(n)||n<min||n>max)return;
|
||||
let val=n<0?n+2**bits:n;
|
||||
state=Array(bits).fill(false);
|
||||
for(let i=0;i<bits;i++){
|
||||
if(val>=2**i){state[i]=true;val-=2**i;}
|
||||
}
|
||||
build();
|
||||
};
|
||||
|
||||
build();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
11
src/pages/index.astro
Normal file
11
src/pages/index.astro
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
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>
|
||||
54
src/scripts/unsignedBinary.js
Normal file
54
src/scripts/unsignedBinary.js
Normal file
@@ -0,0 +1,54 @@
|
||||
let bits = [128,64,32,16,8,4,2,1];
|
||||
let state = Array(8).fill(0);
|
||||
|
||||
const denaryEl = document.getElementById("denaryNumber");
|
||||
const binaryEl = document.getElementById("binaryNumber");
|
||||
const bitEls = document.querySelectorAll(".bit");
|
||||
|
||||
bitEls.forEach((el, i) => {
|
||||
el.addEventListener("click", () => {
|
||||
state[i] = state[i] ? 0 : 1;
|
||||
el.classList.toggle("on");
|
||||
update();
|
||||
});
|
||||
});
|
||||
|
||||
function update() {
|
||||
const denary = state.reduce((sum, bit, i) => sum + bit * bits[i], 0);
|
||||
denaryEl.textContent = denary;
|
||||
binaryEl.textContent = state.join("");
|
||||
}
|
||||
|
||||
function requestBinary() {
|
||||
const input = prompt("Enter 8-bit binary:");
|
||||
if (!/^[01]{8}$/.test(input)) return;
|
||||
state = input.split("").map(Number);
|
||||
bitEls.forEach((el,i)=>el.classList.toggle("on",state[i]));
|
||||
update();
|
||||
}
|
||||
|
||||
function requestDenary() {
|
||||
const input = parseInt(prompt("Enter denary (0–255)"),10);
|
||||
if (isNaN(input) || input < 0 || input > 255) return;
|
||||
|
||||
let value = input;
|
||||
state = bits.map(b => {
|
||||
if (value >= b) {
|
||||
value -= b;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
bitEls.forEach((el,i)=>el.classList.toggle("on",state[i]));
|
||||
update();
|
||||
}
|
||||
|
||||
function shiftBinary(dir) {
|
||||
if (dir === "left") state.shift(), state.push(0);
|
||||
if (dir === "right") state.pop(), state.unshift(0);
|
||||
bitEls.forEach((el,i)=>el.classList.toggle("on",state[i]));
|
||||
update();
|
||||
}
|
||||
|
||||
update();
|
||||
52
src/styles/base.css
Normal file
52
src/styles/base.css
Normal file
@@ -0,0 +1,52 @@
|
||||
/* 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); }
|
||||
68
src/styles/binary.css
Normal file
68
src/styles/binary.css
Normal file
@@ -0,0 +1,68 @@
|
||||
.binary-container {
|
||||
max-width: 1100px;
|
||||
margin: auto;
|
||||
padding: 2rem;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.display {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #00ff66;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.controls button {
|
||||
margin: 0.25rem;
|
||||
padding: 0.6rem 1rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.bits {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
gap: 1rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.bit {
|
||||
width: 40px;
|
||||
height: 80px;
|
||||
background: #333;
|
||||
border-radius: 20px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bit::after {
|
||||
content: "";
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: #555;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
bottom: 6px;
|
||||
left: 4px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.bit.on {
|
||||
background: #00c853;
|
||||
}
|
||||
|
||||
.bit.on::after {
|
||||
bottom: 42px;
|
||||
background: #eaffea;
|
||||
}
|
||||
11
src/styles/fonts.css
Normal file
11
src/styles/fonts.css
Normal file
@@ -0,0 +1,11 @@
|
||||
@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;
|
||||
}
|
||||
43
src/styles/md3-tokens.css
Normal file
43
src/styles/md3-tokens.css
Normal file
@@ -0,0 +1,43 @@
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user