use it elsewhere

Complex addition

Put it on your own page with two lines, or fork it and make it yours. It is CC-BY-4.0.

Embed

The script finds its own origin, so nothing else is needed; the widget runs in a sandboxed frame and cannot touch your page.

<script src="https://learn.mimmsy.com/learn-widget.js"></script> <learn-widget name="complex-add"></learn-widget>

It is CC-BY-4.0: keep the credit line the frame shows.

Preview

Fork

A fork is a copy whose manifest names its parent and the parent's version; lineage is kept forever, and the copy is yours to change. The library is its own repository: clone it, copy complex-add/ to a new name (one DNS label), set forkedFrom to { "name": "complex-add", "version": 2 }, change what you want, run npm run check, and open a pull request. Passing the check is the whole gate. Without a checkout, submit the same files to POST /api/widgets and it is served instantly as an unreviewed draft.

Source

The whole widget is these files; the repository has their history.

widget.json

{
  "name": "complex-add",
  "title": "Complex addition",
  "version": 2,
  "claim": "Adding complex numbers adds their coordinates: z₁ + z₂ is the far corner of the parallelogram spanned by z₁ and z₂, the tip of z₂ placed at the tip of z₁.",
  "summary": "Drag two arrows; the sum is drawn tip-to-tail with the parallelogram completed and the three values read out.",
  "topics": [
    "math/complex-numbers"
  ],
  "aliases": [
    "vector addition",
    "parallelogram rule",
    "tip to tail"
  ],
  "check": [
    {
      "q": "(1 + 2i) + (3 − i) =",
      "options": [
        "4 + i",
        "4 + 3i",
        "2 + i",
        "3 + 2i"
      ],
      "answer": 0,
      "why": "Real parts add (1 + 3) and imaginary parts add (2 − 1)."
    }
  ],
  "height": 410,
  "requires": [
    "complex-point"
  ],
  "forkedFrom": null,
  "authors": []
}

index.html

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Complex addition</title>
<style>
  :root {
    color-scheme: light dark;
    --bg-card:#efe9da; --border:#d8cfba; --text:#211d14; --text-dim:#6e6553;
    --accent:#31597f; --accent2:#b04e1b; --hot:#a82433; --ok:#3d6b4f;
    --ink-rgb:33,29,20; --accent-rgb:49,89,127; --accent2-rgb:176,78,27;
    --serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;
    --mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  }
  @media (prefers-color-scheme: dark) {
    :root {
      --bg-card:#1e1b15; --border:#383225; --text:#e9e3d3; --text-dim:#9c917c;
      --accent:#8fb8e0; --accent2:#dd9355; --hot:#df7a88; --ok:#82bd97;
      --ink-rgb:233,227,211; --accent-rgb:143,184,224; --accent2-rgb:221,147,85;
    }
  }
  * { box-sizing: border-box; }
  html, body { margin: 0; }
  body { background: transparent; color: var(--text); font-family: var(--serif); }
  .widget { background: var(--bg-card); border: 1px solid var(--border); border-radius: 4px; padding: 22px; }
  .mono { font-family: var(--mono); }
  .plot { display: block; width: 100%; height: 220px; margin-top: 8px; }
  .plot.plane { height: 290px; }
  .plot.interactive { cursor: grab; touch-action: none; }
  .plot.interactive:active { cursor: grabbing; }
  .readout-row { display: flex; gap: 28px; flex-wrap: wrap; margin: 14px 0 0; }
  .readout { text-align: left; }
  .readout .big { display: block; font-size: 28px; font-weight: 600; color: var(--accent); line-height: 1.1; min-width: 5ch; }
  .readout.accent .big { color: var(--accent2); }
  .readout .big.hot { color: var(--hot); }
  .readout .big.ok { color: var(--ok); }
  .readout small { color: var(--text-dim); font-size: 12.5px; }
</style>
</head>
<body>
  <div class="widget">
    <canvas id="add-canvas" class="plot plane interactive"></canvas>
    <div class="readout-row">
      <div class="readout">
        <span class="big mono" id="add-z1">1.20 + 0.80i</span>
        <small>z&#8321;</small>
      </div>
      <div class="readout accent">
        <span class="big mono" id="add-z2">0.90 − 1.10i</span>
        <small>z&#8322;</small>
      </div>
      <div class="readout">
        <span class="big mono ok" id="add-sum">2.10 − 0.30i</span>
        <small>z&#8321; + z&#8322;</small>
      </div>
    </div>
  </div>
  <script src="/w/_sdk/host.js?v=1"></script>
  <script src="/w/_lib/complex-plane.js?v=1"></script>
  <script src="widget.js?v=1"></script>
</body>
</html>

widget.js

/* Complex addition: tip-to-tail with the parallelogram completed; both arrows draggable. */
(() => {
  'use strict';
  const $ = (id) => document.getElementById(id);
  const CP = window.ComplexPlane;
  const aZ1 = { re: 1.2, im: 0.8 };
  const aZ2 = { re: 0.9, im: -1.1 };

  function render() {
    const P = CP.planeBase($('add-canvas'), 3.2, false);
    const { ctx } = P;
    const sum = { re: aZ1.re + aZ2.re, im: aZ1.im + aZ2.im };

    ctx.setLineDash([4, 4]);
    ctx.strokeStyle = CP.accent2Wash(0.55);
    ctx.lineWidth = 1.6;
    ctx.beginPath();
    ctx.moveTo(P.X(aZ1), P.Y(aZ1)); ctx.lineTo(P.X(sum), P.Y(sum));
    ctx.stroke();
    ctx.strokeStyle = CP.accentWash(0.55);
    ctx.beginPath();
    ctx.moveTo(P.X(aZ2), P.Y(aZ2)); ctx.lineTo(P.X(sum), P.Y(sum));
    ctx.stroke();
    ctx.setLineDash([]);

    CP.drawArrow(ctx, P.cx, P.cy, P.X(aZ1), P.Y(aZ1), CP.T.ACCENT, 2.2);
    CP.drawArrow(ctx, P.cx, P.cy, P.X(aZ2), P.Y(aZ2), CP.T.ACCENT2, 2.2);
    CP.drawArrow(ctx, P.cx, P.cy, P.X(sum), P.Y(sum), CP.T.OK, 2.4);
    ctx.fillStyle = CP.T.INK;
    ctx.beginPath();
    ctx.arc(P.X(sum), P.Y(sum), 4.5, 0, Math.PI * 2);
    ctx.fill();

    CP.label(ctx, P, aZ1, 'z₁', CP.T.ACCENT, 12, -10);
    CP.label(ctx, P, aZ2, 'z₂', CP.T.ACCENT2, 12, -10);
    CP.label(ctx, P, sum, 'z₁ + z₂', CP.T.OK, 16, 16);

    $('add-z1').textContent = CP.fmtZ(aZ1);
    $('add-z2').textContent = CP.fmtZ(aZ2);
    $('add-sum').textContent = CP.fmtZ(sum);
  }

  CP.readTheme();
  CP.attachDrag($('add-canvas'), 3.2, [aZ1, aZ2], render);
  render();
  let t;
  window.addEventListener('resize', () => { clearTimeout(t); t = setTimeout(render, 120); });

  LearnWidget.connect().then((host) => {
    host.on('theme.changed', () => { CP.readTheme(); render(); });
    CP.readTheme();
    render();
  });
})();