Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/scripts/assign-or-comment.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#!/bin/bash

const { Octokit } = require("@octokit/rest");

const octokit = new Octokit({
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1
with:
Expand All @@ -24,7 +24,7 @@ jobs:
run: bundle exec jekyll serve --port=4000 --detach

- name: API Generation
run: sudo python utils/api_generator.py
run: python utils/api_generator.py

- name: Kill Temporary Server
run: pkill -f jekyll
Expand Down
6 changes: 3 additions & 3 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ aux_links:

# Footer content
# appears at the bottom of every page's main content
footer_content: "Copyright © 2026 Contributors to CircuitVerse. Distributed under a [CC-by-sa] license."
footer_content: "Copyright © {% assign current_year = 'now' | date: '%Y' %}{{ current_year }} Contributors to CircuitVerse. Distributed under a [CC-by-sa] license."

# Footer last edited timestamp
last_edit_timestamp: false # show or hide edit time - page must have `last_modified_date` defined in the frontmatter
Expand All @@ -99,8 +99,8 @@ gh_edit_view_mode: "tree" # "tree" or "edit" if you want the user to jump into t
color_scheme: circuitverse

# Google Analytics Tracking (optional)
# e.g, UA-1234567-89
ga_tracking: UA-112678513-3
# e.g, G-XXXXXXXXXX (GA4 format)
# ga_tracking: G-XXXXXXXXXX

jekyll-spaceship:
# default enabled processors
Expand Down
204 changes: 204 additions & 0 deletions _includes/gate_animation.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
{% comment %}
Usage: {% include gate_animation.html gate="AND" %}
Supported: AND, OR, NOT, NAND, NOR, XOR, XNOR
{% endcomment %}

<div class="gate-anim-wrapper" data-gate="{{ include.gate }}">
<canvas class="gate-anim-canvas" width="340" height="120" aria-label="{{ include.gate }} gate animation"></canvas>
<div class="gate-anim-controls">
{% if include.gate == "NOT" %}
<label>A: <input type="checkbox" class="gate-anim-input" data-gate="{{ include.gate }}" data-input="0"> <span class="bit-label">0</span></label>
{% else %}
<label>A: <input type="checkbox" class="gate-anim-input" data-gate="{{ include.gate }}" data-input="0"> <span class="bit-label">0</span></label>
<label>B: <input type="checkbox" class="gate-anim-input" data-gate="{{ include.gate }}" data-input="1"> <span class="bit-label">0</span></label>
{% endif %}
<span class="gate-anim-output">Output: <strong class="output-val">β€”</strong></span>
</div>
</div>

<script>
(function() {
var wrapper = document.currentScript && document.currentScript.previousElementSibling;
if (!wrapper || !wrapper.classList.contains("gate-anim-wrapper")) return;

var gateId = wrapper.dataset.gate;
var canvas = wrapper.querySelector(".gate-anim-canvas");
if (!canvas) return;

var ctx = canvas.getContext("2d");
var inputs = [false, false];
var animProgress = 0;
var animFrame = null;

function computeOutput(gate, a, b) {
switch(gate) {
case "AND": return a && b;
case "OR": return a || b;
case "NOT": return !a;
case "NAND": return !(a && b);
case "NOR": return !(a || b);
case "XOR": return a !== b;
case "XNOR": return a === b;
}
}

function drawGateShape(gate, cx, cy, active) {
ctx.strokeStyle = "#333";
ctx.lineWidth = 2.5;
ctx.fillStyle = active ? "#d4edda" : "#f8f9fa";

if (gate === "NOT") {
// Triangle
ctx.beginPath();
ctx.moveTo(cx - 22, cy - 20);
ctx.lineTo(cx + 18, cy);
ctx.lineTo(cx - 22, cy + 20);
ctx.closePath();
ctx.fill(); ctx.stroke();
// Bubble
ctx.beginPath();
ctx.arc(cx + 24, cy, 6, 0, Math.PI * 2);
ctx.fillStyle = active ? "#28a745" : "#fff";
ctx.fill(); ctx.stroke();
} else if (gate === "AND" || gate === "NAND") {
ctx.beginPath();
ctx.moveTo(cx - 22, cy - 22);
ctx.lineTo(cx, cy - 22);
ctx.arc(cx, cy, 22, -Math.PI / 2, Math.PI / 2);
ctx.lineTo(cx - 22, cy + 22);
ctx.closePath();
ctx.fill(); ctx.stroke();
if (gate === "NAND") {
ctx.beginPath();
ctx.arc(cx + 28, cy, 6, 0, Math.PI * 2);
ctx.fillStyle = active ? "#28a745" : "#fff";
ctx.fill(); ctx.stroke();
}
} else {
// OR / NOR / XOR / XNOR β€” curved body
ctx.beginPath();
ctx.moveTo(cx - 22, cy - 22);
ctx.quadraticCurveTo(cx - 5, cy, cx - 22, cy + 22);
ctx.quadraticCurveTo(cx + 5, cy + 22, cx + 22, cy);
ctx.quadraticCurveTo(cx + 5, cy - 22, cx - 22, cy - 22);
ctx.fill(); ctx.stroke();
if (gate === "XOR" || gate === "XNOR") {
ctx.beginPath();
ctx.moveTo(cx - 28, cy - 22);
ctx.quadraticCurveTo(cx - 11, cy, cx - 28, cy + 22);
ctx.stroke();
}
if (gate === "NOR" || gate === "XNOR") {
ctx.beginPath();
ctx.arc(cx + 28, cy, 6, 0, Math.PI * 2);
ctx.fillStyle = active ? "#28a745" : "#fff";
ctx.fill(); ctx.stroke();
}
}
}

function signalColor(on, alpha) {
return on
? "rgba(40,167,69," + alpha + ")"
: "rgba(180,180,180," + alpha + ")";
}

function drawWire(x1, y1, x2, y2, on, progress) {
var len = Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1));
var ex = x1 + (x2 - x1) * progress;
var ey = y1 + (y2 - y1) * progress;

// Base wire (grey)
ctx.beginPath();
ctx.moveTo(x1, y1); ctx.lineTo(x2, y2);
ctx.strokeStyle = "#ccc"; ctx.lineWidth = 3;
ctx.stroke();

// Animated signal
ctx.beginPath();
ctx.moveTo(x1, y1); ctx.lineTo(ex, ey);
ctx.strokeStyle = signalColor(on, 1);
ctx.lineWidth = 3;
ctx.stroke();

// Signal dot
ctx.beginPath();
ctx.arc(ex, ey, 5, 0, Math.PI * 2);
ctx.fillStyle = signalColor(on, 0.9);
ctx.fill();
}

function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
var isNot = (gateId === "NOT");
var a = inputs[0], b = inputs[1];
var out = computeOutput(gateId, a, b);
var cx = 185, cy = 60;
var p = animProgress;

// Input wires
if (isNot) {
drawWire(30, cy, cx - 22, cy, a, Math.min(p * 2, 1));
} else {
drawWire(30, 35, cx - 22, cy - 14, a, Math.min(p * 2, 1));
drawWire(30, 85, cx - 22, cy + 14, b, Math.min(p * 2, 1));
}

// Gate body
var bodyAlpha = Math.max(0, p * 2 - 1);
ctx.globalAlpha = 0.3 + bodyAlpha * 0.7;
drawGateShape(gateId, cx, cy, out);
ctx.globalAlpha = 1;

// Output wire
var outX = (gateId === "NOT" || gateId === "NAND" || gateId === "NOR" || gateId === "XNOR") ? cx + 30 : cx + 22;
var outProgress = Math.max(0, p * 2 - 1);
drawWire(outX, cy, 310, cy, out, outProgress);

// Labels
ctx.fillStyle = "#333";
ctx.font = "bold 13px monospace";
ctx.fillText("A=" + (a ? "1" : "0"), 5, 39);
if (!isNot) ctx.fillText("B=" + (b ? "1" : "0"), 5, 89);
ctx.fillText("Out=" + (out ? "1" : "0"), 315, 64);

// Update output display
var outEl = wrapper.querySelector(".output-val");
if (outEl) {
outEl.textContent = out ? "1 (HIGH)" : "0 (LOW)";
outEl.style.color = out ? "#28a745" : "#dc3545";
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function animate() {
animProgress += 0.03;
draw();
if (animProgress < 1) {
animFrame = requestAnimationFrame(animate);
} else {
animProgress = 1;
draw();
}
}

function startAnim() {
if (animFrame) cancelAnimationFrame(animFrame);
animProgress = 0;
animate();
}

// Init
animProgress = 1;
draw();

// Wire up checkboxes
wrapper.querySelectorAll(".gate-anim-input").forEach(function(cb) {
cb.addEventListener("change", function() {
var idx = parseInt(this.dataset.input);
inputs[idx] = this.checked;
this.nextElementSibling.textContent = this.checked ? "1" : "0";
startAnim();
});
});
})();
</script>
56 changes: 47 additions & 9 deletions _sass/custom/custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -279,19 +279,13 @@ $text-color: #111111;
bottom: 15px;
}

// Added transitions
a,
h1,
h2,
h3,
h4,
h5,
button,
// Added transitions β€” only structural layout elements, not links/buttons
// (animating a/button causes a blue color flash during theme switches)
.main-content,
.side-bar,
.site-nav,
.search {
transition: linear 0.3s;
transition: background-color 0.3s linear, color 0.3s linear;
}

.search-active .search-input,
Expand All @@ -309,3 +303,47 @@ button,
width: 0%;
z-index: 10000;
}

// Gate animation widget
.gate-anim-wrapper {
background: $body-background-color;
border: 1px solid $border-color;
border-radius: 6px;
display: inline-block;
margin: 1rem 0;
max-width: 420px;
padding: 0.75rem 1rem;
width: 100%;
}

.gate-anim-canvas {
display: block;
max-width: 340px;
width: 100%;
}

.gate-anim-controls {
align-items: center;
display: flex;
flex-wrap: wrap;
font-size: 0.9rem;
margin-top: 0.5rem;

label {
align-items: center;
cursor: pointer;
display: flex;
margin-right: 1.2rem;
}
}

.bit-label {
font-family: monospace;
font-weight: bold;
margin-left: 0.3rem;
min-width: 0.8rem;
}

.gate-anim-output {
margin-left: auto;
}
38 changes: 20 additions & 18 deletions assets/js/global_scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,46 @@
* Global Scripts for Interactive Book
*/

// Switch Color Scheme as soon as possible
var searchText = "mode";
var storageItem = "colorMode";
var isDarkMode = localStorage.getItem(storageItem);

if (isDarkMode == 0 || isDarkMode == null) {
isDarkMode = 0;
localStorage.setItem(storageItem, isDarkMode);
} else if (isDarkMode == 1) {
jtd.setTheme('circuitversedark');

var isDarkMode = localStorage.getItem(storageItem) === "1";

if (localStorage.getItem(storageItem) === null) {
isDarkMode = false;
localStorage.setItem(storageItem, "0");
}

// Apply saved theme only after jtd is ready to avoid race condition
document.addEventListener('DOMContentLoaded', function () {
if (isDarkMode) {
jtd.setTheme('circuitversedark');
}
});

$(document).ready(function () {

//dark mode functionality
var a = $('a.site-button:contains("mode")');

if (isDarkMode == 1) {
if (isDarkMode) {
a.text("Light mode");
}

a.click(function () {

if (isDarkMode == 0 || isDarkMode == null) {
if (!isDarkMode) {
jtd.setTheme('circuitversedark');
a.text("Light mode");
isDarkMode = 1;
localStorage.setItem(storageItem, isDarkMode);
isDarkMode = true;
} else {
jtd.setTheme('circuitverse');
a.text("Dark mode");
isDarkMode = 0;
localStorage.setItem(storageItem, isDarkMode);
isDarkMode = false;
}
localStorage.setItem(storageItem, isDarkMode ? "1" : "0");

// Reset Disqus thread to reload with matching color scheme
setTimeout(function(){ DISQUS.reset({reload: true}); }, 500);
if (typeof DISQUS !== 'undefined') {
setTimeout(function () { DISQUS.reset({ reload: true }); }, 500);
}
return false;
});

Expand Down
Loading