diff --git a/css/style.css b/css/style.css
index d1e0087..5a30f1d 100644
--- a/css/style.css
+++ b/css/style.css
@@ -217,6 +217,47 @@ body {
background: #252540;
}
+/* Example buttons */
+.example-btn {
+ width: 100%;
+ text-align: left;
+ padding: 8px 12px;
+ background: transparent;
+ border: 1px solid transparent;
+ border-radius: 5px;
+ color: #ccc;
+ cursor: pointer;
+ transition: all 0.15s;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.example-btn:hover {
+ background: #1a1a3a;
+ border-color: #00e599;
+}
+
+.example-name {
+ font-size: 12px;
+ font-weight: 600;
+ color: #00e599;
+}
+
+.example-btn:hover .example-name {
+ color: #00ff99;
+}
+
+.example-desc {
+ font-size: 10px;
+ color: #666;
+ line-height: 1.3;
+}
+
+#examples-menu {
+ min-width: 260px;
+}
+
/* ==================== Canvas ==================== */
#canvas {
position: fixed;
diff --git a/index.html b/index.html
index 178b10a..f4a3e00 100644
--- a/index.html
+++ b/index.html
@@ -42,6 +42,12 @@
+
+
+
+
+
+
diff --git a/js/events.js b/js/events.js
index a4ca266..ca2eef1 100644
--- a/js/events.js
+++ b/js/events.js
@@ -9,6 +9,7 @@ import { puzzleMode, currentLevel, showLevelPanel } from './puzzleUI.js';
import { getLevel } from './levels.js';
import { saveState, loadState, exportAsFile, importFromFile } from './saveLoad.js';
import { enterComponentEditor, exitComponentEditor, updateComponentButtons, setResizeCallback } from './components.js';
+import { getExampleList, loadExample } from './examples.js';
const PAN_SPEED = 40;
@@ -255,6 +256,34 @@ export function initEvents() {
});
});
+ // ==================== EXAMPLES ====================
+ const examplesMenu = document.getElementById('examples-menu');
+ getExampleList().forEach((ex, i) => {
+ const btn = document.createElement('button');
+ btn.className = 'example-btn';
+ btn.innerHTML = `${ex.name}${ex.description}`;
+ btn.addEventListener('click', (e) => {
+ e.stopPropagation();
+ const hasGates = state.gates.length > 0;
+ if (hasGates && !confirm('Load example? This will replace your current circuit.')) return;
+ const data = loadExample(i);
+ if (data) {
+ state.gates = data.circuit.gates;
+ state.connections = data.circuit.connections;
+ state.nextId = data.circuit.nextId;
+ if (data.camera) {
+ state.camX = data.camera.camX;
+ state.camY = data.camera.camY;
+ state.zoom = data.camera.zoom;
+ }
+ clearWaveData();
+ evaluateAll();
+ }
+ closeAllDropdowns();
+ });
+ examplesMenu.appendChild(btn);
+ });
+
document.getElementById('clear-btn').addEventListener('click', () => {
if (state.gates.length === 0 || confirm('Clear all gates and connections?')) {
state.gates = [];
diff --git a/js/examples.js b/js/examples.js
new file mode 100644
index 0000000..f2e5dec
--- /dev/null
+++ b/js/examples.js
@@ -0,0 +1,300 @@
+// Pre-built example circuits
+// Each example is a { gates, connections, nextId, camera } object
+
+const W = 100; // GATE_W
+const H = 60; // GATE_H
+const GAP_X = 160;
+const GAP_Y = 100;
+
+/**
+ * SR Flip-Flop using cross-coupled NOR gates
+ *
+ * S ──┐
+ * NOR─── Q
+ * ┌──┘ │
+ * │ │
+ * └──┐ │
+ * NOR─── Q̅
+ * R ──┘
+ */
+function srFlipFlop() {
+ const gates = [
+ // Inputs
+ { id: 1, type: 'INPUT', x: 50, y: 80, value: 0 }, // S (Set)
+ { id: 2, type: 'INPUT', x: 50, y: 280, value: 0 }, // R (Reset)
+
+ // Cross-coupled NOR gates
+ { id: 3, type: 'NOR', x: 300, y: 80, value: 0 }, // Top NOR → Q
+ { id: 4, type: 'NOR', x: 300, y: 280, value: 0 }, // Bottom NOR → Q̅
+
+ // Outputs
+ { id: 5, type: 'OUTPUT', x: 550, y: 80, value: 0 }, // Q
+ { id: 6, type: 'OUTPUT', x: 550, y: 280, value: 0 } // Q̅
+ ];
+
+ const connections = [
+ // S → Top NOR input 0
+ { from: 1, fromPort: 0, to: 3, toPort: 0 },
+ // R → Bottom NOR input 1
+ { from: 2, fromPort: 0, to: 4, toPort: 1 },
+ // Top NOR (Q) → Bottom NOR input 0 (cross-couple)
+ { from: 3, fromPort: 0, to: 4, toPort: 0 },
+ // Bottom NOR (Q̅) → Top NOR input 1 (cross-couple)
+ { from: 4, fromPort: 0, to: 3, toPort: 1 },
+ // NOR outputs → visible outputs
+ { from: 3, fromPort: 0, to: 5, toPort: 0 },
+ { from: 4, fromPort: 0, to: 6, toPort: 0 }
+ ];
+
+ return {
+ name: 'SR Flip-Flop (NOR)',
+ description: 'Set-Reset latch using cross-coupled NOR gates. Toggle S to set Q=1, toggle R to reset Q=0.',
+ gates,
+ connections,
+ nextId: 7,
+ camera: { camX: 0, camY: 0, zoom: 1 }
+ };
+}
+
+/**
+ * SR Flip-Flop using cross-coupled NAND gates
+ *
+ * S̅ ──┐
+ * NAND── Q
+ * ┌───┘ │
+ * │ │
+ * └───┐ │
+ * NAND── Q̅
+ * R̅ ──┘
+ */
+function srFlipFlopNand() {
+ const gates = [
+ // Inputs (active low for NAND SR)
+ { id: 1, type: 'INPUT', x: 50, y: 80, value: 1 }, // S̅
+ { id: 2, type: 'INPUT', x: 50, y: 280, value: 1 }, // R̅
+
+ // Cross-coupled NAND gates
+ { id: 3, type: 'NAND', x: 300, y: 80, value: 0 }, // Top NAND → Q
+ { id: 4, type: 'NAND', x: 300, y: 280, value: 0 }, // Bottom NAND → Q̅
+
+ // Outputs
+ { id: 5, type: 'OUTPUT', x: 550, y: 80, value: 0 }, // Q
+ { id: 6, type: 'OUTPUT', x: 550, y: 280, value: 0 } // Q̅
+ ];
+
+ const connections = [
+ // S̅ → Top NAND input 0
+ { from: 1, fromPort: 0, to: 3, toPort: 0 },
+ // R̅ → Bottom NAND input 1
+ { from: 2, fromPort: 0, to: 4, toPort: 1 },
+ // Top NAND (Q) → Bottom NAND input 0 (cross-couple)
+ { from: 3, fromPort: 0, to: 4, toPort: 0 },
+ // Bottom NAND (Q̅) → Top NAND input 1 (cross-couple)
+ { from: 4, fromPort: 0, to: 3, toPort: 1 },
+ // Outputs
+ { from: 3, fromPort: 0, to: 5, toPort: 0 },
+ { from: 4, fromPort: 0, to: 6, toPort: 0 }
+ ];
+
+ return {
+ name: 'SR Flip-Flop (NAND)',
+ description: 'Set-Reset latch using cross-coupled NAND gates. Inputs are active-low: set S̅=0 to set, R̅=0 to reset.',
+ gates,
+ connections,
+ nextId: 7,
+ camera: { camX: 0, camY: 0, zoom: 1 }
+ };
+}
+
+/**
+ * Gated D Latch (1-bit memory)
+ *
+ * Uses an SR flip-flop with gating logic:
+ * D ──AND──┐
+ * E ──┤ NOR── Q
+ * │ │ ┌──┘│
+ * │ │ │ │
+ * │ │ └──┐│
+ * E ──┤ NOR── Q̅
+ * D─NOT─AND┘
+ *
+ * When Enable=1, Q follows D.
+ * When Enable=0, Q holds its value.
+ */
+function dLatch() {
+ const gates = [
+ // Inputs
+ { id: 1, type: 'INPUT', x: 50, y: 100, value: 0 }, // D (Data)
+ { id: 2, type: 'INPUT', x: 50, y: 280, value: 0 }, // E (Enable)
+
+ // NOT gate to invert D
+ { id: 3, type: 'NOT', x: 200, y: 340, value: 0 },
+
+ // AND gates for gating
+ { id: 4, type: 'AND', x: 350, y: 60, value: 0 }, // D AND E → S
+ { id: 5, type: 'AND', x: 350, y: 340, value: 0 }, // NOT(D) AND E → R
+
+ // Cross-coupled NOR gates (SR latch core)
+ { id: 6, type: 'NOR', x: 550, y: 60, value: 0 }, // → Q
+ { id: 7, type: 'NOR', x: 550, y: 340, value: 0 }, // → Q̅
+
+ // Outputs
+ { id: 8, type: 'OUTPUT', x: 750, y: 60, value: 0 }, // Q
+ { id: 9, type: 'OUTPUT', x: 750, y: 340, value: 0 } // Q̅
+ ];
+
+ const connections = [
+ // D → AND top input, and D → NOT
+ { from: 1, fromPort: 0, to: 4, toPort: 0 },
+ { from: 1, fromPort: 0, to: 3, toPort: 0 },
+
+ // E → both AND gates
+ { from: 2, fromPort: 0, to: 4, toPort: 1 },
+ { from: 2, fromPort: 0, to: 5, toPort: 1 },
+
+ // NOT(D) → bottom AND
+ { from: 3, fromPort: 0, to: 5, toPort: 0 },
+
+ // AND outputs → NOR inputs (S and R)
+ { from: 4, fromPort: 0, to: 6, toPort: 0 },
+ { from: 5, fromPort: 0, to: 7, toPort: 1 },
+
+ // Cross-coupling
+ { from: 6, fromPort: 0, to: 7, toPort: 0 },
+ { from: 7, fromPort: 0, to: 6, toPort: 1 },
+
+ // Outputs
+ { from: 6, fromPort: 0, to: 8, toPort: 0 },
+ { from: 7, fromPort: 0, to: 9, toPort: 0 }
+ ];
+
+ return {
+ name: 'D Latch (1-bit Memory)',
+ description: 'Gated D latch — a 1-bit memory cell. When Enable=1, output Q follows input D. When Enable=0, Q holds its last value.',
+ gates,
+ connections,
+ nextId: 10,
+ camera: { camX: 0, camY: 0, zoom: 1 }
+ };
+}
+
+/**
+ * D Flip-Flop (edge-triggered, master-slave)
+ *
+ * Two D latches in series with inverted enable:
+ * - Master latch captures D when CLK=0
+ * - Slave latch outputs when CLK=1
+ * This creates rising-edge triggered behavior.
+ */
+function dFlipFlop() {
+ const gates = [
+ // Inputs
+ { id: 1, type: 'INPUT', x: 30, y: 100, value: 0 }, // D
+ { id: 2, type: 'CLOCK', x: 30, y: 350, value: 0 }, // CLK
+
+ // CLK inverter (for master latch)
+ { id: 3, type: 'NOT', x: 170, y: 350, value: 0 },
+
+ // === MASTER LATCH (enabled when CLK=0, i.e. NOT CLK=1) ===
+ { id: 4, type: 'NOT', x: 170, y: 200, value: 0 }, // NOT D for master
+ { id: 5, type: 'AND', x: 300, y: 60, value: 0 }, // D AND !CLK
+ { id: 6, type: 'AND', x: 300, y: 240, value: 0 }, // !D AND !CLK
+ { id: 7, type: 'NOR', x: 450, y: 60, value: 0 }, // Master Q
+ { id: 8, type: 'NOR', x: 450, y: 240, value: 0 }, // Master Q̅
+
+ // === SLAVE LATCH (enabled when CLK=1) ===
+ { id: 9, type: 'NOT', x: 570, y: 200, value: 0 }, // NOT master Q for slave
+ { id: 10, type: 'AND', x: 680, y: 60, value: 0 }, // masterQ AND CLK
+ { id: 11, type: 'AND', x: 680, y: 240, value: 0 }, // !masterQ AND CLK
+ { id: 12, type: 'NOR', x: 830, y: 60, value: 0 }, // Slave Q
+ { id: 13, type: 'NOR', x: 830, y: 240, value: 0 }, // Slave Q̅
+
+ // Outputs
+ { id: 14, type: 'OUTPUT', x: 1010, y: 60, value: 0 }, // Q
+ { id: 15, type: 'OUTPUT', x: 1010, y: 240, value: 0 } // Q̅
+ ];
+
+ const connections = [
+ // D → master AND, D → master NOT
+ { from: 1, fromPort: 0, to: 5, toPort: 0 },
+ { from: 1, fromPort: 0, to: 4, toPort: 0 },
+
+ // CLK → NOT (invert for master)
+ { from: 2, fromPort: 0, to: 3, toPort: 0 },
+
+ // !CLK → master AND gates (enable)
+ { from: 3, fromPort: 0, to: 5, toPort: 1 },
+ { from: 3, fromPort: 0, to: 6, toPort: 1 },
+
+ // !D → master bottom AND
+ { from: 4, fromPort: 0, to: 6, toPort: 0 },
+
+ // Master AND outputs → Master NOR (SR latch)
+ { from: 5, fromPort: 0, to: 7, toPort: 0 },
+ { from: 6, fromPort: 0, to: 8, toPort: 1 },
+
+ // Master cross-coupling
+ { from: 7, fromPort: 0, to: 8, toPort: 0 },
+ { from: 8, fromPort: 0, to: 7, toPort: 1 },
+
+ // Master Q → slave AND, Master Q → slave NOT
+ { from: 7, fromPort: 0, to: 10, toPort: 0 },
+ { from: 7, fromPort: 0, to: 9, toPort: 0 },
+
+ // CLK → slave AND gates (enable, direct CLK)
+ { from: 2, fromPort: 0, to: 10, toPort: 1 },
+ { from: 2, fromPort: 0, to: 11, toPort: 1 },
+
+ // !masterQ → slave bottom AND
+ { from: 9, fromPort: 0, to: 11, toPort: 0 },
+
+ // Slave AND outputs → Slave NOR (SR latch)
+ { from: 10, fromPort: 0, to: 12, toPort: 0 },
+ { from: 11, fromPort: 0, to: 13, toPort: 1 },
+
+ // Slave cross-coupling
+ { from: 12, fromPort: 0, to: 13, toPort: 0 },
+ { from: 13, fromPort: 0, to: 12, toPort: 1 },
+
+ // Outputs
+ { from: 12, fromPort: 0, to: 14, toPort: 0 },
+ { from: 13, fromPort: 0, to: 15, toPort: 0 }
+ ];
+
+ return {
+ name: 'D Flip-Flop (Master-Slave)',
+ description: 'Edge-triggered D flip-flop built from two D latches. Captures D on the rising edge of CLK. Use with the clock simulation to see it in action.',
+ gates,
+ connections,
+ nextId: 16,
+ camera: { camX: 0, camY: -50, zoom: 0.9 }
+ };
+}
+
+// Export all examples as a list
+export const examples = [
+ srFlipFlop,
+ srFlipFlopNand,
+ dLatch,
+ dFlipFlop
+];
+
+export function getExampleList() {
+ return examples.map((fn, i) => {
+ const ex = fn();
+ return { id: i, name: ex.name, description: ex.description };
+ });
+}
+
+export function loadExample(index) {
+ if (index < 0 || index >= examples.length) return null;
+ const ex = examples[index]();
+ return {
+ circuit: {
+ gates: JSON.parse(JSON.stringify(ex.gates)),
+ connections: JSON.parse(JSON.stringify(ex.connections)),
+ nextId: ex.nextId
+ },
+ camera: ex.camera
+ };
+}