# A chess opening repertoire — lines, plans, traps and the move orders to drill

Recipe No. 58, Hobbies and play. From The know.sh Cookbook: https://know.sh/cookbook/chess-repertoire

You knew 7.Bd2 was the move, but what comes after ...d5? Give each line a section: its ideas, the plans for move 13, your own games with the losses first, and the traps both ways. Flashcards drill the move orders you mix up, and a small board steps through each main line with a comment on every move.

- For: a club player building an opening repertoire
- You get: a section per line, flashcards, and a board that plays each line
- Time: ten minutes after each game
- Made with: Shelf, Document, Your AI assistant, Elements, Highlights, Look up

## How it works

1. **Give your assistant your games.** Give it your last fifty games and ask for a document per opening and a section per line you actually meet, each with *Ideas*, *Typical plans*, *Model games* and *Traps*.
2. **Play every move through on a board.** Set up a board beside your book and play each line from the first move. Where the section and the book disagree, the book wins; then write the plans in your own words.
3. **Drill the move orders with flashcards.** Ask for a flashcards element at the head of each opening, a card for each move order you confuse, and check every card on the board before you trust it.
4. **Ask for a board that plays the lines.** Ask your assistant for a widget that steps through the main lines with **Next** and **Back**, a comment per move, and the board turned for the openings you play as Black.

## Try this prompt

Your assistant, connected to know.sh (Claude, ChatGPT or a local model):

> Using know.sh, read the PGN file I have attached and sort my games by opening. Make a shelf called Repertoire with a document per opening I play, and a section per line I actually met, titled with the move that defines it, with the parts Ideas, Typical plans, Model games and Traps. List games by date and result, and give the move where each left my line. Invent no moves.

Your assistant, connected to know.sh (Claude, ChatGPT or a local model):

> Using know.sh, add a flashcards element to the top of the overview of “Italian Game — Giuoco Piano (White)” on my Repertoire shelf: four cards on the move orders in the 7.Bd2 main line, each question giving the moves that reach the position, each answer giving the move and one reason. Use only moves from the document’s sections, and change nothing else.

## Elements in the specimens

Written as your assistant writes them through the know.sh MCP server. Each reads as plain words until you turn elements on (Account → Elements).

Widget, specimen 1:

```element widget
{
  "title": "Three opening lines, move by move",
  "caption": "Step through three standard lines — the Giuoco Piano with 7.Bd2, the Orthodox Queen’s Gambit Declined with Capablanca’s freeing manoeuvre, and the Classical Caro-Kann — with a comment on each move. The board reads each move in algebraic notation and refuses an illegal one. It holds only the main line: no side variations, no engine and no evaluation.",
  "height": 450,
  "html": "<div class=\"chess\" id=\"chess\">\n  <div class=\"top\">\n    <label for=\"line\">Opening</label>\n    <select id=\"line\">\n      <option value=\"0\">Giuoco Piano, 7.Bd2 (C54)</option>\n      <option value=\"1\">Queen’s Gambit Declined (D67)</option>\n      <option value=\"2\">Caro-Kann, Classical (B19)</option>\n    </select>\n  </div>\n  <div class=\"main\">\n    <div class=\"boardcol\">\n      <svg id=\"board\" viewBox=\"0 0 320 320\" role=\"img\" aria-labelledby=\"boardDesc\"><desc id=\"boardDesc\"></desc><g id=\"sq\"></g><g id=\"hl\"></g><g id=\"co\"></g><g id=\"pc\"></g></svg>\n      <div class=\"nav\">\n        <button type=\"button\" id=\"first\" class=\"quiet\" aria-label=\"Start position\">Start</button>\n        <button type=\"button\" id=\"prev\" class=\"quiet\">Back</button>\n        <button type=\"button\" id=\"next\">Next</button>\n        <button type=\"button\" id=\"last\" class=\"quiet\" aria-label=\"End of the line\">End</button>\n        <button type=\"button\" id=\"flip\" class=\"quiet\" aria-pressed=\"false\" aria-label=\"Turn the board\">Turn</button>\n      </div>\n    </div>\n    <div class=\"side\">\n      <p class=\"where\" id=\"where\"></p>\n      <p class=\"say\" id=\"say\" aria-live=\"polite\"></p>\n      <div class=\"moves\" id=\"moves\" aria-label=\"Moves\"></div>\n    </div>\n  </div>\n</div>",
  "css": ".chess { outline: none; }\n.top { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; }\n.top select { flex: 1 1 0; min-width: 0; min-height: 38px; }\n.main { display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr); gap: 18px; margin-top: 14px; }\n.chess { --sq-l: var(--wash); --sq-d: var(--rule); --pc-l: var(--paper); --pc-d: var(--ink); }\n.chess.inv { --sq-l: var(--rule); --sq-d: var(--wash); --pc-l: var(--ink); --pc-d: var(--paper); }\n#board { display: block; width: 100%; height: auto; border: 1px solid var(--ink); box-sizing: border-box; }\n.l { fill: var(--sq-l); }\n.d { fill: var(--sq-d); }\n.hl { fill: none; stroke: var(--ink-2); stroke-width: 2.5; }\n.co { font: 500 8px var(--sans); fill: var(--ink-3); }\n.pc { font-size: 33px; font-family: 'Apple Symbols', 'Segoe UI Symbol', 'Noto Sans Symbols 2', 'DejaVu Sans', sans-serif; text-anchor: middle; dominant-baseline: central; paint-order: stroke fill; stroke-linejoin: round; }\n.pc.w { fill: var(--pc-l); stroke: var(--pc-d); stroke-width: 1.6; }\n.pc.b { fill: var(--pc-d); stroke: var(--pc-l); stroke-width: .9; }\n.nav { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 6px; margin-top: 10px; }\n.nav button { min-height: 40px; padding: 6px 4px; }\n.quiet { background: var(--paper); color: var(--ink); border: 1px solid var(--ink); }\n.where { margin: 0; font: 400 13px/1.4 var(--sans); color: var(--ink-3); }\n.say { min-height: 4.4em; margin: 4px 0 10px; font: 400 16px/1.45 var(--serif); }\n.say b { font-weight: 600; }\n.moves { padding-top: 8px; border-top: 1px solid var(--ink); font: 400 14px/1.9 var(--sans); font-variant-numeric: tabular-nums; }\n.moves .pair { white-space: nowrap; margin-right: 8px; }\n.moves .n { color: var(--ink-3); }\n.mv { background: none; color: var(--ink); border: 0; padding: 1px 4px; font: inherit; cursor: pointer; }\n.mv[aria-current='true'] { background: var(--ink); color: var(--paper); }\n.nav .quiet[aria-pressed='true'] { background: var(--ink); color: var(--paper); }\nbutton:focus-visible, select:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }\n@media (max-width: 540px) {\n  .main { grid-template-columns: minmax(0, 1fr); }\n  .top label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }\n  .say { min-height: 0; margin-bottom: 8px; }\n  .moves { line-height: 1.75; }\n}",
  "js": "(function () {\n  var LINES = [\n    { name: 'Giuoco Piano, 7.Bd2', black: false,\n      intro: 'The Italian Game in its quiet form: both bishops aim at the other side’s weakest square, f7 or f2, and White builds a centre with c3 and d4.',\n      moves: [\n        ['e4', 'White takes the centre and opens lines for the queen and the king’s bishop.'],\n        ['e5', 'Black claims an equal share of the centre.'],\n        ['Nf3', 'Develops with a threat: the knight attacks e5.'],\n        ['Nc6', 'Defends e5 and develops.'],\n        ['Bc4', 'The Italian bishop aims at f7, which only Black’s king defends.'],\n        ['Bc5', 'The Giuoco Piano, the “quiet game”: Black’s bishop mirrors White’s and eyes f2.'],\n        ['c3', 'Prepares d4, to build a full pawn centre and hit the bishop on c5.'],\n        ['Nf6', 'Counter-attacks e4 before White can play d4.'],\n        ['d4', 'The central break: the pawn attacks e5 and the bishop at once.'],\n        ['exd4', 'Black exchanges rather than let White’s centre roll forward.'],\n        ['cxd4', 'White has the pawn centre it wanted, on d4 and e4.'],\n        ['Bb4+', 'The check gains time and asks White how to block it.'],\n        ['Bd2', 'The quiet block. 7.Nc3 is the sharp alternative, giving up the e-pawn for an attack.'],\n        ['Bxd2+', 'Black exchanges the bishops.'],\n        ['Nbxd2', 'The knight recaptures and now guards e4.'],\n        ['d5', 'The key move: Black strikes at the centre before White can play e5.'],\n        ['exd5', 'White takes, and the centre opens.'],\n        ['Nxd5', 'The knight recaptures on a strong central square.'],\n        ['Qb3', 'The queen lines up behind the bishop against d5, and looks at b7.'],\n        ['Nce7', 'Guards the knight on d5 a second time; the bishop on c8 still covers b7.'],\n        ['O-O', 'White castles. The d4 pawn is isolated now: a strength in the middle, a weakness in an ending.'],\n        ['O-O', 'Black castles too.'],\n        ['Rfe1', 'The rook takes the open e-file and looks at the knight on e7.'],\n        ['c6', 'Holds d5 for the knight for good. The position is about level: active White pieces against a weak d4 pawn.']\n      ] },\n    { name: 'Queen’s Gambit Declined, Orthodox', black: false,\n      intro: 'Black holds the centre with pawns on d5 and e6, accepts a cramped game, and frees it with exchanges and a late ...e5.',\n      moves: [\n        ['d4', 'White takes the centre with the queen’s pawn.'],\n        ['d5', 'Black meets it head on.'],\n        ['c4', 'The Queen’s Gambit: White offers the c-pawn to draw Black’s d-pawn away from the centre.'],\n        ['e6', 'Declined: d5 is held by a pawn, at the price of shutting in the bishop on c8.'],\n        ['Nc3', 'A third attacker on d5.'],\n        ['Nf6', 'A third defender.'],\n        ['Bg5', 'Pins the knight to the queen, so f6 no longer fully guards d5.'],\n        ['Be7', 'Breaks the pin and prepares to castle.'],\n        ['e3', 'Opens the way for the king’s bishop and supports d4.'],\n        ['O-O', 'Black castles.'],\n        ['Nf3', 'Develops towards the kingside.'],\n        ['Nbd7', 'The Orthodox set-up: the knight backs up f6 and waits for ...c5 or ...e5.'],\n        ['Rc1', 'The rook takes the c-file, which will open when the c-pawns come off.'],\n        ['c6', 'Firms up d5 and prepares the freeing manoeuvre.'],\n        ['Bd3', 'White develops the last minor piece.'],\n        ['dxc4', 'Black gives up the centre on purpose: the bishop has moved once and must move again to recapture.'],\n        ['Bxc4', 'The bishop recaptures.'],\n        ['Nd5', 'Capablanca’s freeing manoeuvre: offer exchanges to ease a cramped position.'],\n        ['Bxe7', 'White exchanges the dark-squared bishops.'],\n        ['Qxe7', 'The queen recaptures.'],\n        ['O-O', 'White castles.'],\n        ['Nxc3', 'One more pair of pieces comes off.'],\n        ['Rxc3', 'The rook recaptures, active on the third rank.'],\n        ['e5', 'The freeing break at last: the bishop on c8 can come out, and Black is close to level.']\n      ] },\n    { name: 'Caro-Kann, Classical', black: true,\n      intro: 'A defence for Black against 1.e4: support ...d5 with the c-pawn, bring the light-squared bishop out before ...e6, and accept less space for a sound position.',\n      moves: [\n        ['e4', 'White takes the centre.'],\n        ['c6', 'The Caro-Kann: Black prepares ...d5 with a pawn behind it.'],\n        ['d4', 'White builds the full centre.'],\n        ['d5', 'Black challenges e4 at once.'],\n        ['Nc3', 'Defends e4 with a piece.'],\n        ['dxe4', 'Black gives up the centre for quick development.'],\n        ['Nxe4', 'The knight recaptures on a central square.'],\n        ['Bf5', 'The Classical Variation: the bishop comes out before ...e6 shuts it in.'],\n        ['Ng3', 'Hits the bishop and gains time.'],\n        ['Bg6', 'Steps back, still on its good diagonal.'],\n        ['h4', 'Threatens h5, to chase the bishop or trap it.'],\n        ['h6', 'Makes a retreat square on h7.'],\n        ['Nf3', 'Develops; the knight may jump to e5 later.'],\n        ['Nd7', 'Stops Ne5 and prepares ...Ngf6.'],\n        ['h5', 'Gains space on the kingside and drives the bishop back.'],\n        ['Bh7', 'The bishop retreats to the square it made for itself.'],\n        ['Bd3', 'Offers to trade off Black’s best bishop.'],\n        ['Bxd3', 'Black takes: the bishop’s work is done.'],\n        ['Qxd3', 'The queen recaptures, centralised.'],\n        ['e6', 'With the bishop gone, ...e6 costs Black nothing.'],\n        ['Bd2', 'Clears the back rank to castle long.'],\n        ['Ngf6', 'The other knight develops; the one on d7 could have gone to f6 too.'],\n        ['O-O-O', 'White castles long, with the h5 pawn holding space on the kingside.'],\n        ['Be7', 'Black gets ready to castle. Solid, with less space: the Caro-Kann bargain.']\n      ] }\n  ];\n\n  var FILES = 'abcdefgh';\n  var NAMES = { k: 'king', q: 'queen', r: 'rook', b: 'bishop', n: 'knight', p: 'pawn' };\n  var GLYPH = { k: '♚', q: '♛', r: '♜', b: '♝', n: '♞', p: '♟︎' };\n  var sqName = function (s) { return FILES[s % 8] + (Math.floor(s / 8) + 1); };\n  var parseSq = function (t) { return (Number(t[1]) - 1) * 8 + FILES.indexOf(t[0]); };\n  var isW = function (p) { return p && p === p.toUpperCase(); };\n  var at = function (r, f) { return r >= 0 && r < 8 && f >= 0 && f < 8 ? r * 8 + f : -1; };\n\n  function start() {\n    var b = [], back = 'RNBQKBNR';\n    for (var i = 0; i < 64; i++) b.push('');\n    for (var f = 0; f < 8; f++) { b[f] = back[f]; b[8 + f] = 'P'; b[48 + f] = 'p'; b[56 + f] = back[f].toLowerCase(); }\n    return { b: b, w: true, cr: 'KQkq', ep: -1 };\n  }\n\n  var KN = [[1, 2], [2, 1], [2, -1], [1, -2], [-1, -2], [-2, -1], [-2, 1], [-1, 2]];\n  var KG = [[1, 0], [1, 1], [0, 1], [-1, 1], [-1, 0], [-1, -1], [0, -1], [1, -1]];\n  var ROOK = [[1, 0], [-1, 0], [0, 1], [0, -1]], BISHOP = [[1, 1], [1, -1], [-1, 1], [-1, -1]];\n\n  function attacked(b, s, byWhite) {\n    var r = Math.floor(s / 8), f = s % 8, i, t, p;\n    var own = function (x) { return byWhite ? x.toUpperCase() : x.toLowerCase(); };\n    var pr = byWhite ? r - 1 : r + 1;\n    for (i = -1; i <= 1; i += 2) { t = at(pr, f + i); if (t >= 0 && b[t] === own('p')) return true; }\n    for (i = 0; i < 8; i++) { t = at(r + KN[i][0], f + KN[i][1]); if (t >= 0 && b[t] === own('n')) return true; }\n    for (i = 0; i < 8; i++) { t = at(r + KG[i][0], f + KG[i][1]); if (t >= 0 && b[t] === own('k')) return true; }\n    var rays = function (dirs, a, c) {\n      for (var d = 0; d < dirs.length; d++) {\n        for (var k = 1; k < 8; k++) {\n          var u = at(r + dirs[d][0] * k, f + dirs[d][1] * k);\n          if (u < 0) break;\n          p = b[u];\n          if (p) { if (p = own(a) || p = own(c)) return true; break; }\n        }\n      }\n      return false;\n    };\n    return rays(ROOK, 'r', 'q') || rays(BISHOP, 'b', 'q');\n  }\n\n  function kingSq(b, white) { return b.indexOf(white ? 'K' : 'k'); }\n\n  function apply(pos, m) {\n    var b = pos.b.slice(), p = b[m.from], cr = pos.cr, ep = -1;\n    b[m.to] = m.promo ? (pos.w ? m.promo : m.promo.toLowerCase()) : p;\n    b[m.from] = '';\n    if (m.epCap) b[m.to + (pos.w ? -8 : 8)] = '';\n    if (m.rook) { b[m.rook[1]] = b[m.rook[0]]; b[m.rook[0]] = ''; }\n    if (p.toLowerCase() = 'p' && Math.abs(m.to - m.from) = 16) ep = (m.to + m.from) / 2;\n    if (p === 'K') cr = cr.replace('K', '').replace('Q', '');\n    if (p === 'k') cr = cr.replace('k', '').replace('q', '');\n    [[0, 'Q'], [7, 'K'], [56, 'q'], [63, 'k']].forEach(function (c) { if (m.from = c[0] || m.to = c[0]) cr = cr.replace(c[1], ''); });\n    return { b: b, w: !pos.w, cr: cr, ep: ep };\n  }\n\n  // Reads one move in standard algebraic notation, refusing anything illegal or badly written.\n  function play(pos, san) {\n    var w = pos.w, b = pos.b, clean = san.replace(/[+#]$/, ''), check = /[+#]$/.test(san), m;\n    if (clean = 'O-O' || clean = 'O-O-O') {\n      var base = w ? 0 : 56, long = clean === 'O-O-O', right = long ? (w ? 'Q' : 'q') : (w ? 'K' : 'k');\n      var between = long ? [1, 2, 3] : [5, 6], safe = long ? [4, 3, 2] : [4, 5, 6];\n      if (pos.cr.indexOf(right) < 0) throw new Error(san + ': no castling right');\n      between.forEach(function (x) { if (b[base + x]) throw new Error(san + ': path blocked'); });\n      safe.forEach(function (x) { if (attacked(b, base + x, !w)) throw new Error(san + ': king passes through check'); });\n      m = { from: base + 4, to: base + (long ? 2 : 6), rook: long ? [base, base + 3] : [base + 7, base + 5] };\n    } else {\n      var x = /^([NBRQK])?([a-h])?([1-8])?(x)?([a-h][1-8])(=([NBRQ]))?$/.exec(clean);\n      if (!x) throw new Error(san + ': cannot read');\n      var type = (x[1] || 'P').toLowerCase(), to = parseSq(x[5]), cap = !!x[4];\n      var target = b[to], epCap = type = 'p' && cap && to = pos.ep && !target;\n      if (cap && !epCap && (!target || isW(target) === w)) throw new Error(san + ': nothing to take');\n      if (!cap && target) throw new Error(san + ': square occupied');\n      var cands = [], tr = Math.floor(to / 8), tf = to % 8, mine = w ? type.toUpperCase() : type, i, s;\n      if (type === 'p') {\n        var dir = w ? 1 : -1;\n        if (cap) { s = at(tr - dir, FILES.indexOf(x[2])); if (s >= 0 && b[s] = mine && Math.abs(s % 8 - tf) = 1) cands.push(s); }\n        else {\n          s = at(tr - dir, tf);\n          if (s >= 0 && b[s] === mine) cands.push(s);\n          else if (s >= 0 && !b[s] && tr = (w ? 3 : 4) && b[at(tr - 2 * dir, tf)] = mine) cands.push(at(tr - 2 * dir, tf));\n        }\n        if ((tr = 7 || tr = 0) ! !!x[7]) throw new Error(san + ': promotion wrong');\n      } else if (type = 'n' || type === 'k') {\n        var jumps = type === 'n' ? KN : KG;\n        for (i = 0; i < 8; i++) { s = at(tr + jumps[i][0], tf + jumps[i][1]); if (s >= 0 && b[s] === mine) cands.push(s); }\n      } else {\n        var dirs = type = 'r' ? ROOK : type = 'b' ? BISHOP : ROOK.concat(BISHOP);\n        for (i = 0; i < dirs.length; i++) {\n          for (var k = 1; k < 8; k++) {\n            s = at(tr + dirs[i][0] * k, tf + dirs[i][1] * k);\n            if (s < 0) break;\n            if (b[s]) { if (b[s] === mine) cands.push(s); break; }\n          }\n        }\n      }\n      var legal = cands.filter(function (from) {\n        var after = apply(pos, { from: from, to: to, epCap: epCap, promo: x[7] });\n        return !attacked(after.b, kingSq(after.b, w), !w);\n      });\n      var chosen = legal.filter(function (from) {\n        return (type = 'p' || !x[2] || from % 8 = FILES.indexOf(x[2])) && (!x[3] || Math.floor(from / 8) = Number(x[3]) - 1);\n      });\n      if (chosen.length ! 1) throw new Error(san + ': ' + chosen.length + ' pieces can make it');\n      if (type !== 'p' && (x[2] || x[3]) && legal.length < 2) throw new Error(san + ': needless disambiguation');\n      m = { from: chosen[0], to: to, epCap: epCap, promo: x[7] };\n    }\n    var next = apply(pos, m);\n    if (attacked(next.b, kingSq(next.b, w), !w)) throw new Error(san + ': leaves the king in check');\n    if (attacked(next.b, kingSq(next.b, !w), w) !== check) throw new Error(san + ': check sign wrong');\n    return { pos: next, from: m.from, to: m.to };\n  }\n\n  var $ = function (id) { return document.getElementById(id); };\n  var root = $('chess'), svgNS = 'http://www.w3.org/2000/svg';\n  var line = 0, ply = 0, flipped = false, plies = [];\n\n  function el(tag, attrs, text) {\n    var e = document.createElementNS(svgNS, tag);\n    for (var k in attrs) e.setAttribute(k, attrs[k]);\n    if (text != null) e.textContent = text;\n    return e;\n  }\n  function xy(s) {\n    var r = Math.floor(s / 8), f = s % 8;\n    return flipped ? [(7 - f) * 40, r * 40] : [f * 40, (7 - r) * 40];\n  }\n\n  function load(n) {\n    line = n;\n    var pos = start();\n    plies = [{ pos: pos }];\n    try {\n      LINES[n].moves.forEach(function (mv) { var r = play(pos, mv[0]); pos = r.pos; plies.push({ pos: pos, from: r.from, to: r.to, san: mv[0], note: mv[1] }); });\n    } catch (e) {\n      plies = plies.slice(0, 1);\n      $('say').textContent = 'This line has an error: ' + e.message;\n    }\n    flipped = LINES[n].black;\n    $('flip').setAttribute('aria-pressed', String(flipped));\n    ply = 0;\n    listMoves();\n    render();\n  }\n\n  function label(i) {\n    var n = Math.ceil(i / 2);\n    return i % 2 ? n + '.' + plies[i].san : n + '...' + plies[i].san;\n  }\n\n  function listMoves() {\n    var box = $('moves');\n    box.textContent = '';\n    var pair;\n    for (var i = 1; i < plies.length; i++) {\n      if (i % 2) {\n        pair = document.createElement('span'); pair.className = 'pair'; box.appendChild(pair);\n        var num = document.createElement('span'); num.className = 'n'; num.textContent = Math.ceil(i / 2) + '.'; pair.appendChild(num);\n      }\n      var bt = document.createElement('button');\n      bt.type = 'button'; bt.className = 'mv'; bt.textContent = plies[i].san;\n      bt.setAttribute('aria-label', 'Move ' + label(i));\n      bt.setAttribute('data-ply', String(i));\n      bt.addEventListener('click', function (e) { go(Number(e.currentTarget.getAttribute('data-ply'))); });\n      pair.appendChild(bt);\n    }\n  }\n\n  function describe(b) {\n    var sides = [[], []];\n    b.forEach(function (p, s) { if (p) sides[isW(p) ? 0 : 1].push(NAMES[p.toLowerCase()] + ' ' + sqName(s)); });\n    return 'White: ' + sides[0].join(', ') + '. Black: ' + sides[1].join(', ') + '.';\n  }\n\n  function render() {\n    var cur = plies[ply], b = cur.pos.b, i;\n    var gs = $('sq'), gh = $('hl'), gc = $('co'), gp = $('pc');\n    [gs, gh, gc, gp].forEach(function (g) { g.textContent = ''; });\n    for (i = 0; i < 64; i++) {\n      var p = xy(i), light = (Math.floor(i / 8) + i % 8) % 2 = 1;\n      gs.appendChild(el('rect', { x: p[0], y: p[1], width: 40, height: 40, 'class': light ? 'l' : 'd' }));\n      if (p[0] = 0) gc.appendChild(el('text', { x: 2.5, y: p[1] + 9, 'class': 'co' }, String(Math.floor(i / 8) + 1)));\n      if (p[1] === 280) gc.appendChild(el('text', { x: p[0] + 34, y: 317, 'class': 'co' }, FILES[i % 8]));\n      if (b[i]) gp.appendChild(el('text', { x: p[0] + 20, y: p[1] + 21.5, 'class': 'pc ' + (isW(b[i]) ? 'w' : 'b') }, GLYPH[b[i].toLowerCase()]));\n    }\n    if (ply > 0) [cur.from, cur.to].forEach(function (s) { var q = xy(s); gh.appendChild(el('rect', { x: q[0] + 1.5, y: q[1] + 1.5, width: 37, height: 37, 'class': 'hl' })); });\n    var L = LINES[line];\n    $('where').textContent = ply ? 'Move ' + Math.ceil(ply / 2) + ' of ' + Math.ceil((plies.length - 1) / 2) + (ply % 2 ? ', White' : ', Black') : 'Before the first move. Arrow keys step through the line.';\n    var say = $('say');\n    if (ply) { say.textContent = ''; var bb = document.createElement('b'); bb.textContent = label(ply) + ' '; say.appendChild(bb); say.appendChild(document.createTextNode(cur.note)); }\n    else if (plies.length > 1) say.textContent = L.intro;\n    $('boardDesc').textContent = (ply ? 'Position after ' + label(ply) + '. ' : 'Starting position. ') + describe(b);\n    root.querySelectorAll('.mv').forEach(function (m) { m.setAttribute('aria-current', String(Number(m.getAttribute('data-ply')) === ply)); });\n    $('first').disabled = $('prev').disabled = ply === 0;\n    $('next').disabled = $('last').disabled = ply === plies.length - 1;\n  }\n\n  function go(n) { ply = Math.max(0, Math.min(plies.length - 1, n)); render(); }\n\n  // Pieces keep their meaning in dark mode: white pieces take the lighter token, whichever that is.\n  function tone() {\n    var c = document.createElement('canvas'); c.width = 2; c.height = 1;\n    var x = c.getContext('2d'), cs = getComputedStyle(document.documentElement);\n    x.fillStyle = cs.getPropertyValue('--ink').trim() || '#000'; x.fillRect(0, 0, 1, 1);\n    x.fillStyle = cs.getPropertyValue('--paper').trim() || '#fff'; x.fillRect(1, 0, 1, 1);\n    var d = x.getImageData(0, 0, 2, 1).data;\n    var lum = function (o) { return 0.2126 * d[o] + 0.7152 * d[o + 1] + 0.0722 * d[o + 2]; };\n    root.classList.toggle('inv', lum(0) > lum(4));\n  }\n\n  $('line').addEventListener('change', function (e) { load(Number(e.target.value)); });\n  $('first').addEventListener('click', function () { go(0); });\n  $('prev').addEventListener('click', function () { go(ply - 1); });\n  $('next').addEventListener('click', function () { go(ply + 1); });\n  $('last').addEventListener('click', function () { go(plies.length - 1); });\n  $('flip').addEventListener('click', function () { flipped = !flipped; $('flip').setAttribute('aria-pressed', String(flipped)); render(); });\n  root.addEventListener('keydown', function (e) {\n    if (e.target.tagName === 'SELECT') return;\n    var k = { ArrowLeft: ply - 1, ArrowRight: ply + 1, Home: 0, End: plies.length - 1 }[e.key];\n    if (k != null) { e.preventDefault(); go(k); }\n  });\n  document.addEventListener('know-tokens', tone);\n  tone();\n  load(0);\n})();"
}
```

Flashcards, specimen 2:

```element flashcards
{
  "title": "Giuoco Piano move orders",
  "cards": [
    {
      "question": "After 4.c3 Nf6 5.d4 exd4 6.cxd4, Black checks. What is the check, and how do you block it in this repertoire?",
      "answer": "6...Bb4+, and **7.Bd2**, the quiet block. 7.Nc3 is the sharper gambit, giving up the e-pawn after 7...Nxe4."
    },
    {
      "question": "After 7.Bd2 Bxd2+, which piece recaptures, and why?",
      "hint": "Think about d4.",
      "answer": "**8.Nbxd2**: the b1 knight comes to d2 and guards e4, and the knight on f3 stays where it defends d4."
    },
    {
      "question": "After 8.Nbxd2, what must you expect from Black, and what is your answer?",
      "answer": "8...d5, striking at e4 before you can play e5. Take: **9.exd5 Nxd5 10.Qb3**, the queen behind the bishop against d5."
    },
    {
      "question": "Black answers 3.Bc4 with 3...Nd4. Do you take on e5?",
      "answer": "**No.** 4.Nxe5? Qg5 hits the knight and g2, and 5.Nxf7? Qxg2 6.Rf1 Qxe4+ 7.Be2 Nf3 is mate. Play 4.Nxd4 exd4 and castle."
    }
  ],
  "shuffle": false
}
```
