← Commit history

Resistor flash cards: color code, SMD, formulas, calculations

Colby Knox ·7a14efd81b ·17d ago ·parent 7619031
2 files changed +377−1
README.md+19−1
@@ -1,3 +1,21 @@ # Resistor Flash Cards -Interactive flash cards for a circuits class: resistor color codes, SMD markings, formulas, and generated practice problems+Interactive flash cards for studying resistors in a circuits class. The page itself is the deck: open it, click a card to flip it, mark it "Got it" or "Again", and shuffle for a fresh set.++## Decks++- Color code: read 4-band and 5-band resistors, per-band digits and multipliers, mnemonics, the 0 ohm jumper, tempco bands+- Value to bands: given a value and tolerance, name the bands+- SMD codes: 3-digit, 4-digit, R-notation, and EIA-96 markings+- Formulas: Ohm's law, power, series and parallel, dividers, Kirchhoff, Wheatstone, tolerance, E-series, power ratings, tempco, resistor types+- Calculations: randomly generated series, parallel, divider, current, and power problems++The color-code, value-to-bands, and calculation cards are generated fresh on every shuffle, so the deck never repeats exactly.++## Keyboard++space flips, left and right arrows move, 1 marks Again, 2 marks Got it, s shuffles.++## Source++Everything lives in `readme.html`, a single self-contained file with no external assets.
readme.htmladded+358
@@ -0,0 +1,358 @@+<!doctype html>+<html lang="en">+<head>+<meta charset="utf-8">+<meta name="viewport" content="width=device-width, initial-scale=1">+<title>Resistor Flash Cards</title>+<style>+  :root {+    --bg: #0b0f14; --panel: #121821; --panel2: #18202b; --line: #243040;+    --text: #e6edf3; --muted: #93a1b3; --teal: #14b8a6; --blue: #3b82f6; --purple: #8b5cf6;+    --good: #22c55e; --bad: #f97316;+  }+  * { box-sizing: border-box; }+  html, body { margin: 0; background: var(--bg); color: var(--text);+    font: 15px/1.5 Inter, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }+  .wrap { max-width: 880px; margin: 0 auto; padding: 20px 16px 28px; }+  h1 { font-size: 22px; margin: 0 0 4px; letter-spacing: -0.01em; }+  .sub { color: var(--muted); margin: 0 0 14px; font-size: 14px; }+  .chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }+  .chip { border: 1px solid var(--line); background: var(--panel); color: var(--muted); border-radius: 999px;+    padding: 6px 12px; cursor: pointer; font-size: 13px; user-select: none; }+  .chip.on { color: #fff; border-color: transparent;+    background: linear-gradient(90deg, var(--teal), var(--blue) 60%, var(--purple)); }+  .stage { perspective: 1400px; }+  .card { position: relative; min-height: 340px; cursor: pointer; transform-style: preserve-3d;+    transition: transform .45s cubic-bezier(.2,.8,.2,1); }+  .card.flipped { transform: rotateY(180deg); }+  .face { position: absolute; inset: 0; backface-visibility: hidden; -webkit-backface-visibility: hidden;+    background: var(--panel); border: 1px solid var(--line); border-radius: 16px; padding: 22px 24px;+    display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; gap: 12px; }+  .face.back { transform: rotateY(180deg); background: var(--panel2); }+  .tag { position: absolute; top: 14px; left: 18px; font-size: 12px; color: var(--muted); letter-spacing: .04em; text-transform: uppercase; }+  .hint { position: absolute; bottom: 12px; right: 18px; font-size: 12px; color: var(--muted); }+  .q { font-size: 20px; font-weight: 600; max-width: 640px; }+  .a { font-size: 26px; font-weight: 700; max-width: 640px; }+  .a.small { font-size: 18px; font-weight: 500; }+  .note { color: var(--muted); font-size: 14px; max-width: 640px; }+  .big { font-size: 40px; font-weight: 800; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; letter-spacing: .04em; }+  .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }+  .bands { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; }+  .band { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 8px; background: #0f141b; border: 1px solid var(--line); font-size: 14px; }+  .sw { width: 14px; height: 14px; border-radius: 3px; border: 1px solid rgba(255,255,255,.15); }+  svg.res { width: 340px; max-width: 100%; height: auto; }+  .bar { display: flex; gap: 10px; align-items: center; margin-top: 14px; flex-wrap: wrap; }+  button { border: 1px solid var(--line); background: var(--panel); color: var(--text); border-radius: 10px;+    padding: 10px 16px; font-size: 14px; cursor: pointer; min-height: 44px; }+  button:hover { border-color: #3a4a60; }+  button.primary { background: linear-gradient(90deg, var(--teal), var(--blue)); border-color: transparent; color: #fff; font-weight: 600; }+  button.good { border-color: var(--good); color: var(--good); }+  button.bad { border-color: var(--bad); color: var(--bad); }+  .spacer { flex: 1; }+  .prog { color: var(--muted); font-size: 13px; }+  .meter { height: 6px; background: var(--panel); border: 1px solid var(--line); border-radius: 999px; overflow: hidden; margin-top: 10px; }+  .meter i { display: block; height: 100%; width: 0; background: linear-gradient(90deg, var(--teal), var(--blue), var(--purple)); transition: width .3s; }+  .keys { color: var(--muted); font-size: 12px; margin-top: 10px; }+  kbd { border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 4px; padding: 0 5px; font-size: 11px; background: var(--panel); }+  details { margin-top: 20px; border: 1px solid var(--line); border-radius: 12px; background: var(--panel); padding: 10px 16px; }+  summary { cursor: pointer; font-weight: 600; }+  table { border-collapse: collapse; width: 100%; margin-top: 10px; font-size: 14px; }+  th, td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--line); }+  th { color: var(--muted); font-weight: 500; }+  .done .q { color: var(--teal); }+</style>+</head>+<body>+<div class="wrap">+  <h1>Resistor Flash Cards</h1>+  <p class="sub">Color codes, SMD markings, formulas, and quick calculations. Click a card to flip it, then mark whether you got it.</p>++  <div class="chips" id="chips"></div>++  <div class="stage">+    <div class="card" id="card">+      <div class="face front"><span class="tag" id="tagF"></span><div id="front"></div><span class="hint">click or press space to flip</span></div>+      <div class="face back"><span class="tag" id="tagB"></span><div id="back"></div></div>+    </div>+  </div>++  <div class="bar">+    <button id="prev">Prev</button>+    <button id="flip" class="primary">Flip</button>+    <button id="next">Next</button>+    <span class="spacer"></span>+    <button id="again" class="bad">Again</button>+    <button id="got" class="good">Got it</button>+    <button id="shuffle">Shuffle</button>+  </div>+  <div class="meter"><i id="meter"></i></div>+  <div class="bar"><span class="prog" id="prog"></span></div>+  <div class="keys"><kbd>space</kbd> flip · <kbd>&larr;</kbd> <kbd>&rarr;</kbd> prev / next · <kbd>1</kbd> again · <kbd>2</kbd> got it · <kbd>s</kbd> shuffle</div>++  <details>+    <summary>Color code cheat sheet</summary>+    <table id="cheat"></table>+    <p class="note" style="margin-top:10px">Read with the tolerance band (gold, silver, or the band spaced farthest from the others) on the right. 4-band: digit, digit, multiplier, tolerance. 5-band: digit, digit, digit, multiplier, tolerance. 6-band adds a temperature coefficient band.</p>+  </details>+</div>++<script>+(function () {+  // ---------- reference data ----------+  var COLORS = [+    { n: 'Black',  hex: '#111111', d: 0, m: 1 },+    { n: 'Brown',  hex: '#8b4513', d: 1, m: 10,   t: 1 },+    { n: 'Red',    hex: '#e0201a', d: 2, m: 100,  t: 2 },+    { n: 'Orange', hex: '#f28c1a', d: 3, m: 1e3 },+    { n: 'Yellow', hex: '#f2d21a', d: 4, m: 1e4 },+    { n: 'Green',  hex: '#21a04b', d: 5, m: 1e5,  t: 0.5 },+    { n: 'Blue',   hex: '#2260d8', d: 6, m: 1e6,  t: 0.25 },+    { n: 'Violet', hex: '#8a3fd1', d: 7, m: 1e7,  t: 0.1 },+    { n: 'Gray',   hex: '#8f8f8f', d: 8, m: 1e8,  t: 0.05 },+    { n: 'White',  hex: '#f4f4f4', d: 9, m: 1e9 },+    { n: 'Gold',   hex: '#d4a017', m: 0.1,  t: 5 },+    { n: 'Silver', hex: '#c0c0c0', m: 0.01, t: 10 }+  ];+  var BY = {}; COLORS.forEach(function (c) { BY[c.n] = c; });+  var E12 = [10, 12, 15, 18, 22, 27, 33, 39, 47, 56, 68, 82];+  var E24 = [10, 11, 12, 13, 15, 16, 18, 20, 22, 24, 27, 30, 33, 36, 39, 43, 47, 51, 56, 62, 68, 75, 82, 91];+  var E96s = [100, 121, 150, 182, 221, 249, 274, 332, 402, 475, 549, 681, 825, 976];++  function fmt(ohms) {+    var u = 'Ω', v = ohms;+    if (ohms >= 1e6) { v = ohms / 1e6; u = 'MΩ'; }+    else if (ohms >= 1e3) { v = ohms / 1e3; u = 'kΩ'; }+    v = +v.toPrecision(3);+    return v + ' ' + u;+  }+  function esc(s) { return String(s).replace(/[&<>]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]; }); }+  function rnd(n) { return Math.floor(Math.random() * n); }+  function pick(a) { return a[rnd(a.length)]; }+  function shuffleArr(a) { for (var i = a.length - 1; i > 0; i--) { var j = rnd(i + 1); var t = a[i]; a[i] = a[j]; a[j] = t; } return a; }++  // ---------- resistor drawing ----------+  function resistorSVG(bandColors) {+    var n = bandColors.length;+    var xs = n === 4 ? [92, 116, 140, 196] : n === 5 ? [84, 106, 128, 150, 200] : [80, 100, 120, 140, 160, 205];+    var s = '<svg class="res" viewBox="0 0 340 90" xmlns="http://www.w3.org/2000/svg">';+    s += '<line x1="10" y1="45" x2="330" y2="45" stroke="#b8c0cc" stroke-width="5" stroke-linecap="round"/>';+    s += '<rect x="60" y="18" width="220" height="54" rx="22" fill="#d9c9a3" stroke="#6b5b3a" stroke-width="2"/>';+    s += '<rect x="66" y="22" width="208" height="14" rx="10" fill="rgba(255,255,255,.28)"/>';+    bandColors.forEach(function (c, i) {+      s += '<rect x="' + xs[i] + '" y="19" width="14" height="52" rx="2" fill="' + BY[c].hex + '" stroke="rgba(0,0,0,.25)"/>';+    });+    return s + '</svg>';+  }+  function bandList(names, roles) {+    return '<div class="bands">' + names.map(function (n, i) {+      return '<span class="band"><i class="sw" style="background:' + BY[n].hex + '"></i>' + n + (roles ? ' <span class="note">' + roles[i] + '</span>' : '') + '</span>';+    }).join('') + '</div>';+  }+  function bandsFor(sig, mult, tol) {+    var digits = String(sig).split('').map(function (d) { return COLORS[+d].n; });+    var mName = COLORS.filter(function (c) { return c.m === mult; })[0].n;+    var tName = COLORS.filter(function (c) { return c.t === tol; })[0].n;+    return digits.concat([mName, tName]);+  }+  function tolRange(ohms, tol) { return fmt(ohms * (1 - tol / 100)) + ' to ' + fmt(ohms * (1 + tol / 100)); }++  // ---------- generated decks ----------+  function colorCards(count) {+    var out = [];+    for (var i = 0; i < count; i++) {+      var five = i % 3 === 2;+      var sig = five ? pick(E96s) : pick(E24);+      var mExp = five ? rnd(5) : rnd(6);+      var mult = Math.pow(10, mExp);+      var tol = five ? 1 : pick([5, 10, 5, 1]);+      var ohms = sig * mult;+      var bands = bandsFor(sig, mult, tol);+      var roles = five ? ['1st digit', '2nd digit', '3rd digit', 'multiplier', 'tolerance'] : ['1st digit', '2nd digit', 'multiplier', 'tolerance'];+      out.push({+        deck: 'color',+        front: resistorSVG(bands) + '<div class="q">What is the value and tolerance?</div>',+        back: '<div class="a">' + fmt(ohms) + ' ±' + tol + '%</div>' + bandList(bands, roles) ++              '<div class="note">Acceptable range: ' + tolRange(ohms, tol) + '</div>'+      });+    }+    return out;+  }+  function reverseCards(count) {+    var out = [];+    for (var i = 0; i < count; i++) {+      var sig = pick(E12), mExp = rnd(6), mult = Math.pow(10, mExp), tol = pick([5, 10]);+      var ohms = sig * mult, bands = bandsFor(sig, mult, tol);+      out.push({+        deck: 'reverse',+        front: '<div class="big">' + fmt(ohms) + ' ±' + tol + '%</div><div class="q">Name the four color bands.</div>',+        back: resistorSVG(bands) + bandList(bands, ['1st digit', '2nd digit', 'multiplier', 'tolerance'])+      });+    }+    return out;+  }+  function digitCards() {+    var out = [];+    COLORS.slice(0, 10).forEach(function (c) {+      out.push({ deck: 'color',+        front: '<div class="bands"><span class="band" style="font-size:20px;padding:10px 18px"><i class="sw" style="width:22px;height:22px;background:' + c.hex + '"></i>' + c.n + '</span></div><div class="q">What digit is this band, and what multiplier?</div>',+        back: '<div class="a">Digit ' + c.d + ' · multiplier ×' + (c.m >= 1000 ? c.m.toLocaleString() : c.m) + '</div>' + (c.t ? '<div class="note">As a tolerance band: ±' + c.t + '%</div>' : '') });+    });+    out.push({ deck: 'color', front: '<div class="q">Gold as a multiplier band? As a tolerance band?</div>', back: '<div class="a">×0.1 · ±5%</div><div class="note">Silver is ×0.01 and ±10%. No tolerance band at all means ±20%.</div>' });+    out.push({ deck: 'color', front: '<div class="q">Mnemonic for Black through White?</div>', back: '<div class="a small">Better Be Right Or Your Great Big Venture Goes Wrong</div><div class="note">Black 0, Brown 1, Red 2, Orange 3, Yellow 4, Green 5, Blue 6, Violet 7, Gray 8, White 9</div>' });+    out.push({ deck: 'color', front: '<div class="q">Which end of the resistor do you read from?</div>', back: '<div class="a small">Tolerance band (gold or silver, or the band with the wider gap) goes on the RIGHT. Read left to right.</div>' });+    out.push({ deck: 'color', front: resistorSVG(['Black']) + '<div class="q">A resistor with a single black band?</div>', back: '<div class="a">0 Ω jumper</div><div class="note">Used as a configurable link on a PCB so the pick-and-place machine can place it like any other part.</div>' });+    out.push({ deck: 'color', front: '<div class="q">What does the sixth band on a 6-band resistor mean?</div>', back: '<div class="a small">Temperature coefficient (ppm/°C)</div><div class="note">Brown 100, Red 50, Orange 15, Yellow 25, Blue 10, Violet 5 ppm/°C</div>' });+    return out;+  }+  function smdCards() {+    var rows = [+      ['103', '10 kΩ', '3-digit: two digits then a power of ten. 10 × 10³'],+      ['472', '4.7 kΩ', '47 × 10²'],+      ['220', '22 Ω', '22 × 10⁰. Not 220 Ω, a common trap.'],+      ['221', '220 Ω', '22 × 10¹'],+      ['1R0', '1.0 Ω', 'R marks the decimal point'],+      ['4R7', '4.7 Ω', 'R marks the decimal point'],+      ['R47', '0.47 Ω', 'R marks the decimal point'],+      ['000', '0 Ω', 'Jumper'],+      ['1002', '10 kΩ', '4-digit (1% parts): three digits then a power of ten. 100 × 10²'],+      ['4701', '4.7 kΩ', '470 × 10¹'],+      ['3301', '3.3 kΩ', '330 × 10¹'],+      ['01C', '10 kΩ', 'EIA-96: code 01 = 100, letter C = ×100'],+      ['22A', '165 Ω', 'EIA-96: code 22 = 165, letter A = ×1'],+      ['01B', '1 kΩ', 'EIA-96: code 01 = 100, letter B = ×10']+    ];+    return rows.map(function (r) {+      return { deck: 'smd', front: '<div class="big">' + r[0] + '</div><div class="q">Value of this SMD marking?</div>', back: '<div class="a">' + r[1] + '</div><div class="note">' + esc(r[2]) + '</div>' };+    });+  }+  function formulaCards() {+    var rows = [+      ["Ohm's law", 'V = I·R, so I = V/R and R = V/I'],+      ['Power dissipated in a resistor', 'P = V·I = I²·R = V²/R'],+      ['Resistors in series', 'R<sub>total</sub> = R₁ + R₂ + … ; same current through each, voltages add'],+      ['Resistors in parallel', '1/R<sub>total</sub> = 1/R₁ + 1/R₂ + … ; same voltage across each, currents add'],+      ['Two resistors in parallel (product over sum)', 'R<sub>total</sub> = R₁·R₂ / (R₁ + R₂)'],+      ['N equal resistors R in parallel', 'R / N'],+      ['Voltage divider output', 'V<sub>out</sub> = V<sub>in</sub> · R₂ / (R₁ + R₂), with R₂ across the output'],+      ['Current divider (two branches)', 'I₁ = I<sub>total</sub> · R₂ / (R₁ + R₂). The current takes the smaller resistance more.'],+      ["Kirchhoff's voltage law", 'The sum of voltages around any closed loop is zero'],+      ["Kirchhoff's current law", 'The sum of currents entering a node equals the sum leaving it'],+      ['Wheatstone bridge balance condition', 'R₁/R₂ = R₃/R₄ (no current through the meter)'],+      ['Conductance', 'G = 1/R, measured in siemens (S). Parallel conductances add.'],+      ['Resistance of a wire', 'R = ρ·L / A (resistivity × length ÷ cross-section area)'],+      ['What does ±5% tolerance mean for 4.7 kΩ?', 'Actual value lies between 4.465 kΩ and 4.935 kΩ'],+      ['E12 series values', '10, 12, 15, 18, 22, 27, 33, 39, 47, 56, 68, 82 (each ~20% apart, made for ±10% parts)'],+      ['E24 series values', 'E12 plus 11, 13, 16, 20, 24, 30, 36, 43, 47…: 24 per decade, made for ±5% parts'],+      ['Why are standard values spaced logarithmically?', 'So adjacent values overlap at their tolerance and every resistance is covered with the fewest parts'],+      ['Typical power ratings', 'Through-hole: 1/8 W, 1/4 W, 1/2 W. SMD: 0402 ≈ 1/16 W, 0603 ≈ 1/10 W, 0805 ≈ 1/8 W, 1206 ≈ 1/4 W'],+      ['Derating', 'Above about 70 °C ambient the allowed power drops linearly toward zero at the max temperature. Run resistors at half their rating.'],+      ['Temperature coefficient, 100 ppm/°C', 'Resistance changes 0.01% per °C. A 10 kΩ part drifts 1 Ω per °C.'],+      ['Carbon film vs metal film', 'Carbon film: cheap, ±5%, noisier. Metal film: ±1% or better, low noise, low tempco. Default choice for signal work.'],+      ['Wirewound resistors', 'High power, very precise, but inductive: avoid in high-frequency paths'],+      ['Potentiometer vs rheostat', 'Potentiometer: three terminals, a variable voltage divider. Rheostat: two terminals, a variable resistance.'],+      ['Pull-up resistor', 'Ties a signal to V<sub>cc</sub> through a resistor (often 4.7 kΩ to 10 kΩ) so it reads high when nothing drives it'],+      ['LED current-limiting resistor', 'R = (V<sub>supply</sub> − V<sub>forward</sub>) / I<sub>LED</sub>'],+      ['Thermal noise (Johnson) of a resistor', 'v<sub>n</sub> = √(4·k·T·R·Δf). Bigger R means more noise.']+    ];+    return rows.map(function (r) { return { deck: 'formula', front: '<div class="q">' + r[0] + '</div>', back: '<div class="a small">' + r[1] + '</div>' }; });+  }+  function calcCards(count) {+    var out = [];+    for (var i = 0; i < count; i++) {+      var kind = i % 6, r1 = pick(E12) * Math.pow(10, 1 + rnd(3)), r2 = pick(E12) * Math.pow(10, 1 + rnd(3)), v = pick([3.3, 5, 9, 12, 24]);+      if (kind === 0) out.push({ deck: 'calc', front: '<div class="q">' + fmt(r1) + ' and ' + fmt(r2) + ' in series?</div>', back: '<div class="a">' + fmt(r1 + r2) + '</div><div class="note">Series resistances add.</div>' });+      else if (kind === 1) out.push({ deck: 'calc', front: '<div class="q">Two ' + fmt(r1) + ' resistors in parallel?</div>', back: '<div class="a">' + fmt(r1 / 2) + '</div><div class="note">Equal resistors in parallel: R / N.</div>' });+      else if (kind === 2) out.push({ deck: 'calc', front: '<div class="q">' + fmt(r1) + ' and ' + fmt(r2) + ' in parallel?</div>', back: '<div class="a">' + fmt(r1 * r2 / (r1 + r2)) + '</div><div class="note">Product over sum. The result is always smaller than the smaller resistor.</div>' });+      else if (kind === 3) out.push({ deck: 'calc', front: '<div class="q">Voltage divider: ' + v + ' V across ' + fmt(r1) + ' (top) and ' + fmt(r2) + ' (bottom). V<sub>out</sub> across the bottom?</div>', back: '<div class="a">' + (+(v * r2 / (r1 + r2)).toPrecision(3)) + ' V</div><div class="note">V<sub>out</sub> = V<sub>in</sub> · R₂ / (R₁ + R₂)</div>' });+      else if (kind === 4) { var iA = v / r1; out.push({ deck: 'calc', front: '<div class="q">' + v + ' V across ' + fmt(r1) + '. Current?</div>', back: '<div class="a">' + (iA >= 1e-3 ? +(iA * 1e3).toPrecision(3) + ' mA' : +(iA * 1e6).toPrecision(3) + ' µA') + '</div><div class="note">I = V / R</div>' }); }+      else { var p = v * v / r1; out.push({ deck: 'calc', front: '<div class="q">' + v + ' V across ' + fmt(r1) + '. Power dissipated? Is a 1/4 W part OK?</div>', back: '<div class="a">' + (p >= 1 ? +p.toPrecision(3) + ' W' : +(p * 1e3).toPrecision(3) + ' mW') + '</div><div class="note">P = V² / R. ' + (p <= 0.125 ? 'Yes, under half of 1/4 W.' : p <= 0.25 ? 'Marginal: within rating but over half of it.' : 'No, exceeds 1/4 W.') + '</div>' }); }+    }+    return out;+  }++  var DECKS = [+    { id: 'all', label: 'All' },+    { id: 'color', label: 'Color code' },+    { id: 'reverse', label: 'Value to bands' },+    { id: 'smd', label: 'SMD codes' },+    { id: 'formula', label: 'Formulas' },+    { id: 'calc', label: 'Calculations' }+  ];+  var ALL = [];+  function build() {+    ALL = shuffleArr(digitCards().concat(colorCards(18), reverseCards(12), smdCards(), formulaCards(), calcCards(18)));+  }++  // ---------- state ----------+  var deck = 'all', queue = [], idx = 0, flipped = false, done = 0, total = 0;+  var card = document.getElementById('card'), front = document.getElementById('front'), back = document.getElementById('back');+  var tagF = document.getElementById('tagF'), tagB = document.getElementById('tagB'), prog = document.getElementById('prog'), meter = document.getElementById('meter');+  function label(id) { return DECKS.filter(function (d) { return d.id === id; })[0].label; }++  function load() {+    queue = ALL.filter(function (c) { return deck === 'all' || c.deck === deck; });+    idx = 0; done = 0; total = queue.length; render();+  }+  function render() {+    flipped = false; card.classList.remove('flipped');+    if (!queue.length) {+      card.classList.add('done');+      front.innerHTML = '<div class="q">Deck complete. Shuffle to run it again.</div>'; back.innerHTML = '';+      tagF.textContent = label(deck); tagB.textContent = '';+    } else {+      card.classList.remove('done');+      var c = queue[idx];+      front.innerHTML = c.front; back.innerHTML = c.back;+      tagF.textContent = label(c.deck) + ' · ' + (idx + 1) + ' of ' + queue.length; tagB.textContent = tagF.textContent;+    }+    prog.textContent = done + ' of ' + total + ' marked "got it"' + (queue.length ? ', ' + queue.length + ' left in the queue' : '');+    meter.style.width = (total ? Math.round(done / total * 100) : 0) + '%';+    fit();+  }+  function flip() { if (!queue.length) return; flipped = !flipped; card.classList.toggle('flipped', flipped); }+  function next() { if (!queue.length) return; idx = (idx + 1) % queue.length; render(); }+  function prev() { if (!queue.length) return; idx = (idx - 1 + queue.length) % queue.length; render(); }+  function got() { if (!queue.length) return; queue.splice(idx, 1); done++; if (idx >= queue.length) idx = 0; render(); }+  function again() { if (queue.length < 2) { flipped = false; card.classList.remove('flipped'); return; } var c = queue.splice(idx, 1)[0]; queue.push(c); if (idx >= queue.length) idx = 0; render(); }+  function reshuffle() { build(); load(); }++  // ---------- UI wiring ----------+  var chips = document.getElementById('chips');+  DECKS.forEach(function (d) {+    var b = document.createElement('span'); b.className = 'chip' + (d.id === deck ? ' on' : ''); b.textContent = d.label;+    b.onclick = function () { deck = d.id; Array.prototype.forEach.call(chips.children, function (x) { x.classList.remove('on'); }); b.classList.add('on'); load(); };+    chips.appendChild(b);+  });+  card.onclick = flip;+  document.getElementById('flip').onclick = flip;+  document.getElementById('next').onclick = next;+  document.getElementById('prev').onclick = prev;+  document.getElementById('got').onclick = got;+  document.getElementById('again').onclick = again;+  document.getElementById('shuffle').onclick = reshuffle;+  document.addEventListener('keydown', function (e) {+    if (e.target && /input|textarea/i.test(e.target.tagName)) return;+    if (e.key === ' ') { e.preventDefault(); flip(); }+    else if (e.key === 'ArrowRight') next();+    else if (e.key === 'ArrowLeft') prev();+    else if (e.key === '1') again();+    else if (e.key === '2') got();+    else if (e.key === 's' || e.key === 'S') reshuffle();+  });++  // cheat sheet+  var cheat = document.getElementById('cheat');+  cheat.innerHTML = '<tr><th>Color</th><th>Digit</th><th>Multiplier</th><th>Tolerance</th></tr>' + COLORS.map(function (c) {+    return '<tr><td><span class="band"><i class="sw" style="background:' + c.hex + '"></i>' + c.n + '</span></td><td>' + (c.d === undefined ? '' : c.d) + '</td><td>×' + (c.m >= 1000 ? c.m.toLocaleString() : c.m) + '</td><td>' + (c.t ? '±' + c.t + '%' : '') + '</td></tr>';+  }).join('');++  // sandboxed-iframe height reporter+  function fit() { try { parent.postMessage({ type: 'readme-height', height: document.documentElement.scrollHeight }, '*'); } catch (e) {} }+  addEventListener('load', fit);+  if (window.ResizeObserver) new ResizeObserver(fit).observe(document.documentElement);++  build(); load();+})();+</script>+</body>+</html>