Recho

DocsExamplesSketches
Bairui Su
Bairui Su / Word Count
Created 2025-09-13
//โžœ      dog-|๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ
//โžœ      cat-|๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ
//โžœ    mouse-|๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ
//โžœ  playing-|๐ŸŸฉ
//โžœ     yard-|๐ŸŸฉ
//โžœ   barked-|๐ŸŸฉ
//โžœ   loudly-|๐ŸŸฉ
//โžœ      ran-|๐ŸŸฉ
//โžœ  quickly-|๐ŸŸฉ
//โžœ      hid-|๐ŸŸฉ
//โžœ    under-|๐ŸŸฉ
//โžœ    bench-|๐ŸŸฉ
//โžœ     kept-|๐ŸŸฉ
//โžœ  looking-|๐ŸŸฉ
//โžœ   jumped-|๐ŸŸฉ
//โžœ    small-|๐ŸŸฉ
//โžœ    fence-|๐ŸŸฉ
//โžœ followed-|๐ŸŸฉ
//โžœ     bird-|๐ŸŸฉ
//โžœ  watched-|๐ŸŸฉ
//โžœ silently-|๐ŸŸฉ
//โžœ    moved-|๐ŸŸฉ
echo(rows.join("\n"));

/**
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                              References
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * Here are the links to the JavaScript APIs we used:
 *
 * - `string.split(splitter)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split
 * - `array.filter(callback)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
 * - `array.includes(value)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
 * - `array.reduce(callback, initialValue)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
 * - `Object.keys(object)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
 * - `Object.entries(object)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
 * - `Math.max(...array)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max
 * - `array.map(callback)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
 * - `string.padStart(length, padString)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
 *    the padString. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
 * - `string.repeat(value)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
 * - `array.join(separator)`: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join
 */
Bairui Su
Created 2025-09-12
//โžœ ๐ŸŒ˜๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜
//โžœ ๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ˜๐ŸŒ˜
//โžœ ๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ˜
//โžœ ๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ˜
//โžœ ๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•๐ŸŒ•
//โžœ ๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•
//โžœ ๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•
//โžœ ๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•
//โžœ ๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ˜๐ŸŒ˜๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•
//โžœ ๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ˜๐ŸŒ˜๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•
//โžœ ๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ˜๐ŸŒ˜๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•
//โžœ ๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•
//โžœ ๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•๐ŸŒ•
//โžœ ๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•๐ŸŒ•
//โžœ ๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ˜
//โžœ ๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ˜๐ŸŒ˜
//โžœ ๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜
//โžœ ๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜
//โžœ ๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ—
//โžœ ๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ—๐ŸŒ—๐ŸŒ—
{
  let output = "";
  for (let i = 0; i < size; i++) {
    for (let j = 0; j < size; j++) {
      const [x, y] = [pos(j), pos(i)];
      const r = Math.hypot(x, y);
      const theta = Math.atan2(y, x) / (Math.PI * 2);
      const phase = now / 3000;
      const l = ((r + theta - phase) % 1) * -360;
      const index = ~~rotate(l);
      output += moons[index];
    }
    output += i == size - 1 ? "" : "\n";
  }
  echo(output);
}

const d3 = recho.require("d3");

/**
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                             References
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * [1] https://observablehq.com/d/a741f9a27e8c0e73
 */
Luyu Cheng
Luyu Cheng / Phases of The Moon
Created 2025-09-12
//โžœ โ•           โ•Ž โ“‰โ“Œโ“‡โ’ปโ“ˆโ“Šโ“‚โ“‰โ“Œโ“‡โ’ปโ“ˆโ“Šโ“‚โ“‰โ“Œโ“‡โ’ปโ“ˆโ“Šโ“‚โ“‰โ“Œโ“‡โ’ปโ“ˆโ“Šโ“‚โ“‰โ“Œโ“‡โ’ปโ“ˆโ“Šโ“‚โ“‰โ“Œ โ•
//โžœ โ•   January โ•Ž ใ€€ใ€€ใ€€๐ŸŒ‘๐ŸŒ‘๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ“๐ŸŒ“๐ŸŒ“๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘ใ€€ใ€€ใ€€ โ•
//โžœ โ•  February โ•Ž ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ“๐ŸŒ“๐ŸŒ“๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ‘๐ŸŒ‘ใ€€ใ€€ใ€€ โ•
//โžœ โ•     March โ•Ž ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€๐ŸŒ‘๐ŸŒ‘๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ“๐ŸŒ“๐ŸŒ“๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘ โ•
//โžœ โ•     April โ•Ž ใ€€ใ€€๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ“๐ŸŒ“๐ŸŒ“๐ŸŒ“๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ’ใ€€ใ€€ใ€€ใ€€ใ€€ โ•
//โžœ โ•       May โ•Ž ใ€€ใ€€ใ€€ใ€€๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ“๐ŸŒ“๐ŸŒ“๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ’๐ŸŒ’ใ€€ใ€€ โ•
//โžœ โ•      June โ•Ž ๐ŸŒ’๐ŸŒ“๐ŸŒ“๐ŸŒ“๐ŸŒ“๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ’๐ŸŒ’๐ŸŒ’ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ โ•
//โžœ โ•      July โ•Ž ใ€€ใ€€๐ŸŒ’๐ŸŒ“๐ŸŒ“๐ŸŒ“๐ŸŒ“๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ“ใ€€ใ€€ใ€€ใ€€ โ•
//โžœ โ•    August โ•Ž ใ€€ใ€€ใ€€ใ€€ใ€€๐ŸŒ“๐ŸŒ“๐ŸŒ“๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ“๐ŸŒ“ใ€€ โ•
//โžœ โ• September โ•Ž ใ€€๐ŸŒ“๐ŸŒ“๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ“๐ŸŒ“ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ โ•
//โžœ โ•   October โ•Ž ใ€€ใ€€ใ€€๐ŸŒ“๐ŸŒ“๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ“๐ŸŒ“๐ŸŒ“๐ŸŒ“ใ€€ใ€€ใ€€ โ•
//โžœ โ•  November โ•Ž ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ“๐ŸŒ“๐ŸŒ“๐ŸŒ“ใ€€ โ•
//โžœ โ•  December โ•Ž ใ€€๐ŸŒ”๐ŸŒ”๐ŸŒ”๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ—๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ˜๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ’๐ŸŒ“๐ŸŒ“๐ŸŒ“๐ŸŒ”๐ŸŒ”ใ€€ใ€€ใ€€ใ€€ใ€€ โ•
{
  const matrix = _.times(12, () => _.times(6 * 7, () => SPACE));
  for (const day of days) {
    const row = day.getMonth();
    const column = day.getDate() + months[row].getDay();
    matrix[row][column] = getMoonEmoji(day);
  }
  // Remove the leading and trailing spaces from the header and each row.
  const head = Math.min(...matrix.map((xs) => xs.findIndex((x) => x !== SPACE)));
  const tail = Math.max(...matrix.map((xs) => xs.findLastIndex((x) => x !== SPACE))) + 1;
  echo(line(" ".repeat(longestLength), header.slice(head, tail)), {quote: false});
  echo(matrix.map((x, i) => line(alignedMonthNames[i], x.slice(head, tail).join(""))).join("\n"));
}

function line(...items) {
  return "โ• " + items.join(" โ•Ž ") + " โ•";
}

const header = "โ“‚โ“‰โ“Œโ“‡โ’ปโ“ˆโ“Š".repeat(7);
const months = d3.timeMonths(theFirstDay, theLastDay);
const longestLength = monthNames.reduce((x, y) => Math.max(x, y.length), 0);
const monthNames = months.map(d3.timeFormat("%B"));
const alignedMonthNames = monthNames.map((n) => n.padStart(longestLength, " "));

const theFirstDay = d3.timeYear(new Date(year, 0, 1));
const theLastDay = d3.timeYear.offset(theFirstDay, 1);
const days = d3.timeDays(theFirstDay, theLastDay);

const SPACE = "\u3000"; // Full-width space

function getMoonEmoji(date) {
  const index = Math.round(suncalc.getMoonIllumination(date).phase * 8);
  return String.fromCodePoint(0x1f311 + (index === 8 ? 0 : index));
}

const suncalc = recho.require("suncalc");
const d3 = recho.require("d3");
const _ = recho.require("lodash");
Luyu Cheng
Luyu Cheng / Maze
Created 2025-09-11
//โžœ โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
//โžœ   โ”‚       โ”‚     โ”‚                   โ”‚
//โžœ โ”‚ โ”‚ โ”€โ”€โ”ฌโ”€โ” โ”‚ โ”Œโ”€โ”€ โ”œโ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€ โ”Œโ”€โ”€ โ”‚
//โžœ โ”‚ โ”‚   โ”‚ โ”‚   โ”‚   โ”‚     โ”‚ โ”‚       โ”‚   โ”‚
//โžœ โ”‚ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”ค โ”Œโ”€โ”ค โ”€โ”€โ” โ””โ”€โ”˜ โ”Œโ”€โ”€โ”€โ”€โ”€โ”ค โ”€โ”€โ”ค
//โžœ โ”‚ โ”‚ โ”‚ โ”‚     โ”‚ โ”‚ โ”‚   โ”‚     โ”‚     โ”‚   โ”‚
//โžœ โ”‚ โ””โ”€โ”˜ โ”‚ โ”‚ โ”Œโ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€โ”€โ”ฌโ”€โ”˜ โ”Œโ”€โ”€โ”€โ”ดโ”€โ”€ โ”‚
//โžœ โ”‚     โ”‚ โ”‚ โ”‚   โ”‚   โ”‚ โ”‚   โ”‚   โ”‚       โ”‚
//โžœ โ”œโ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ โ”‚ โ”Œโ”€โ”˜ โ”Œโ”€โ”ค โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”€โ”€โ”ฌโ”€โ”€โ”€โ”ค
//โžœ โ”‚     โ”‚ โ”‚ โ”‚ โ”‚   โ”‚ โ”‚  โ—โ”‚ โ”‚ โ”‚ โ”‚   โ”‚   โ”‚
//โžœ โ”‚ โ”Œโ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”€โ”€โ”˜ โ”œโ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ””โ”€โ” โ”œโ”€โ”€ โ”‚
//โžœ โ”‚ โ”‚     โ”‚   โ”‚     โ”‚     โ”‚ โ”‚   โ”‚ โ”‚   โ”‚
//โžœ โ”‚ โ”‚ โ”€โ”€โ”ฌโ”€โ”ดโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ” โ”‚ โ”€โ”€โ”€โ”€โ”ดโ”€โ”ค โ”€โ”€โ”ค โ”‚ โ”€โ”€โ”ค
//โžœ โ”‚ โ”‚   โ”‚         โ”‚ โ”‚       โ”‚   โ”‚ โ”‚   โ”‚
//โžœ โ”‚ โ””โ”€โ” โ””โ”€โ” โ”€โ”€โ”ฌโ”€โ” โ””โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ โ”‚
//โžœ โ”‚   โ”‚   โ”‚   โ”‚ โ”‚   โ”‚       โ”‚ โ”‚ โ”‚     โ”‚
//โžœ โ”‚ โ”€โ”€โ”ดโ”€โ” โ”œโ”€โ”€ โ”‚ โ””โ”€โ” โ”‚ โ”Œโ”€โ”€โ”€โ”ฌโ”€โ”ดโ”€โ”˜ โ””โ”€โ”ฌโ”€โ”€ โ”‚
//โžœ โ”‚     โ”‚ โ”‚   โ”‚   โ”‚   โ”‚   โ”‚       โ”‚   โ”‚
//โžœ โ”‚ โ”€โ”€โ” โ”‚ โ”‚ โ”Œโ”€โ”˜ โ”€โ”€โ”ดโ”€โ”€โ”€โ”˜ โ”€โ”€โ”˜ โ”‚ โ”€โ”€โ”€โ”€โ”˜ โ”€โ”€โ”˜
//โžœ โ”‚   โ”‚     โ”‚               โ”‚
//โžœ โ””โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
{
  frame;

  if (!maze.walk()) maze.regenerate();
  echo(maze.render());
}

const width = 18;
const height = 10;

const maze = new Maze(width, height);

const frame = recho.interval(100);

const cell = () => ({top: true, right: true, bottom: true, left: true});

class Maze {
  constructor(width, height) {
    this.width = width;
    this.height = height;
    this.initialize();
    this.generate();
  }

  initialize() {
    this.grid = _.times(this.height, () => _.times(this.width, cell));
    this.visited = _.times(this.height, () => _.times(this.width, _.constant(false)));
    this.path = null;
    this.step = 0;
  }

  regenerate() {
    this.initialize();
    this.generate();
  }

  generate() {
    this.carvePassage(0, 0);
    this.grid[0][0].left = false;
    this.grid[this.height - 1][this.width - 1].right = false;
  }

  walk() {
    if (this.path === null) {
      this.path = this.findPath();
      this.step = 0;
      return true;
    } else if (this.step + 1 < this.path.length) {
      this.step += 1;
      return true;
    } else {
      return false;
    }
  }

  carvePassage(x, y) {
    this.visited[y][x] = true;
    for (const neighbor of _.shuffle(this.getUnvisitedNeighbors(x, y))) {
      if (!this.visited[neighbor.y][neighbor.x]) {
        // Remove walls between current cell and neighbor
        this.removeWall(x, y, neighbor.x, neighbor.y);
        // Recursively visit neighbor
        this.carvePassage(neighbor.x, neighbor.y);
      }
    }
  }

  getUnvisitedNeighbors(x, y) {
    const neighbors = [];
    if (y > 0 && !this.visited[y - 1][x]) neighbors.push({x: x, y: y - 1}); // up
    if (x < this.width - 1 && !this.visited[y][x + 1]) neighbors.push({x: x + 1, y: y}); // right
    if (y < this.height - 1 && !this.visited[y + 1][x]) neighbors.push({x: x, y: y + 1}); // down
    if (x > 0 && !this.visited[y][x - 1]) neighbors.push({x: x - 1, y: y}); // left
    return neighbors;
  }

  removeWall(x1, y1, x2, y2) {
    const dx = x2 - x1;
    const dy = y2 - y1;
    if (dx === 1) {
      // Moving right
      this.grid[y1][x1].right = false;
      this.grid[y2][x2].left = false;
    } else if (dx === -1) {
      // Moving left
      this.grid[y1][x1].left = false;
      this.grid[y2][x2].right = false;
    } else if (dy === 1) {
      // Moving down
      this.grid[y1][x1].bottom = false;
      this.grid[y2][x2].top = false;
    } else if (dy === -1) {
      // Moving up
      this.grid[y1][x1].top = false;
      this.grid[y2][x2].bottom = false;
    }
  }

  findPath() {
    const queue = [[0, 0, [{x: 0, y: 0}]]];
    const visited = new Set([`${0},${0}`]);
    while (queue.length > 0) {
      const [x, y, path] = queue.shift();
      if (x === this.width - 1 && y === this.height - 1) return path;
      for (const {dx, dy, direction} of directions) {
        const nx = x + dx;
        const ny = y + dy;
        const key = `${nx},${ny}`;
        if (
          nx >= 0 &&
          nx < this.width &&
          ny >= 0 &&
          ny < this.height &&
          !visited.has(key) &&
          !this.grid[y][x][direction]
        ) {
          visited.add(key);
          queue.push([nx, ny, [...path, {x: nx, y: ny}]]);
        }
      }
    }
    return null;
  }

  render() {
    const pathSet = new Set();
    if (this.path !== null && this.step < this.path.length) {
      pathSet.add(`${this.path[this.step].x},${this.path[this.step].y}`);
    }

    // Create output grid (2x dimensions + 1 for walls)
    const outputHeight = this.height * 2 + 1;
    const outputWidth = this.width * 2 + 1;
    const output = _.times(outputHeight, () => _.times(outputWidth, _.constant(chars.space)));

    for (let y = 0; y < this.height; y++) {
      for (let x = 0; x < this.width; x++) {
        const cell = this.grid[y][x];
        const ox = x * 2;
        const oy = y * 2;
        if (cell.top) output[oy][ox + 1] = chars.horizontal;
        if (cell.bottom) output[oy + 2][ox + 1] = chars.horizontal;
        if (cell.left) output[oy + 1][ox] = chars.vertical;
        if (cell.right) output[oy + 1][ox + 2] = chars.vertical;
        if (pathSet.has(`${x},${y}`)) output[oy + 1][ox + 1] = chars.ghost;
      }
    }

    // Draw corners and junctions
    for (let y = 0; y < outputHeight; y += 2) {
      for (let x = 0; x < outputWidth; x += 2) {
        const top = y > 0 && output[y - 1][x] === chars.vertical;
        const bottom = y < outputHeight - 1 && output[y + 1][x] === chars.vertical;
        const left = x > 0 && output[y][x - 1] === chars.horizontal;
        const right = x < outputWidth - 1 && output[y][x + 1] === chars.horizontal;
        const connections = (top ? 1 : 0) + (right ? 1 : 0) + (bottom ? 1 : 0) + (left ? 1 : 0);
        if (connections === 0) {
          output[y][x] = chars.space;
        } else if (connections === 1) {
          if (top || bottom) output[y][x] = chars.vertical;
          else output[y][x] = chars.horizontal;
        } else if (connections === 2) {
          if (top && bottom) output[y][x] = chars.vertical;
          else if (left && right) output[y][x] = chars.horizontal;
          else if (top && right) output[y][x] = chars.bottomLeft;
          else if (top && left) output[y][x] = chars.bottomRight;
          else if (bottom && right) output[y][x] = chars.topLeft;
          else if (bottom && left) output[y][x] = chars.topRight;
        } else if (connections === 3) {
          if (!top) output[y][x] = chars.teeDown;
          else if (!right) output[y][x] = chars.teeLeft;
          else if (!bottom) output[y][x] = chars.teeUp;
          else if (!left) output[y][x] = chars.teeRight;
        } else {
          output[y][x] = chars.cross;
        }
      }
    }
    output[1][0] = chars.startMarker;
    output[outputHeight - 2][outputWidth - 1] = chars.endMarker;
    return output.map((row) => row.join("")).join("\n");
  }
}

const directions = [
  {dx: 0, dy: -1, direction: "top"},
  {dx: 1, dy: 0, direction: "right"},
  {dx: 0, dy: 1, direction: "bottom"},
  {dx: -1, dy: 0, direction: "left"},
];

const chars = {
  horizontal: "โ”€",
  vertical: "โ”‚",
  topLeft: "โ”Œ",
  topRight: "โ”",
  bottomLeft: "โ””",
  bottomRight: "โ”˜",
  teeUp: "โ”ด",
  teeDown: "โ”ฌ",
  teeLeft: "โ”ค",
  teeRight: "โ”œ",
  cross: "โ”ผ",
  space: " ",
  ghost: "โ—",
  startMarker: " ",
  endMarker: " ",
};

const _ = recho.require("lodash");
Bairui Su
Bairui Su / Running Race
Created 2025-09-09
//โžœ                                    ๐ŸŒ๐Ÿ’จ
//โžœ                                   ๐Ÿข๐Ÿ’จ
//โžœ                              ๐Ÿšถโ€โ™‚๏ธ๐Ÿ’จ
//โžœ                     ๐Ÿš—๐Ÿ’จ
//โžœ           ๐Ÿš€๐Ÿ’จ
{
  const x = (count) => 40 - (count % 40);
  echo("๐ŸŒ๐Ÿ’จ".padStart(x(snail)), {quote: false});
  echo("๐Ÿข๐Ÿ’จ".padStart(x(turtle)), {quote: false});
  echo("๐Ÿšถโ€โ™‚๏ธ๐Ÿ’จ".padStart(x(human)), {quote: false});
  echo("๐Ÿš—๐Ÿ’จ".padStart(x(car)), {quote: false});
  echo("๐Ÿš€๐Ÿ’จ".padStart(x(rocket)), {quote: false});
}
Bairui Su
Created 2025-09-03
//โžœ
//โžœ             Great Britain
//โžœ     pigs -| ๐Ÿ–
//โžœ   cattle -| ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„
//โžœ    sheep -| ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘
//โžœ
//โžœ             United States
//โžœ     pigs -| ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ–
//โžœ   cattle -| ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„
//โžœ    sheep -| ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘
//โžœ
//โžœ Live stock (millions)
//โžœ
echo(output);

/**
 * Next, let's dive into how `output` is generated.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                          Preparing the data
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * First, we need to prepare the data. We're going to use the following dataset
 * to create the chart. It's a tiny tubular dataset, with each row representing
 * a animal in a country and the count of the animal.
 */

const data = [
  {animal: "pigs", country: "Great Britain", count: 1354979},
  {animal: "cattle", country: "Great Britain", count: 3962921},
  {animal: "sheep", country: "Great Britain", count: 10931215},
  {animal: "pigs", country: "United States", count: 6281935},
  {animal: "cattle", country: "United States", count: 9917873},
  {animal: "sheep", country: "United States", count: 7084151},
];

/**
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                            Importing D3
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * Then we import D3 to help us with the data processing. In Recho, you can
 * typically use `recho.require(name)` to import an external library.
 *
 * > Ref. https://recho.dev/docs/libraries-imports
 * > Ref. https://d3js.org/
 */

const d3 = recho.require("d3");

/**
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                          Generating the bars
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * We'll get started with generating the bars. There are three main tasks here:
 *
 * 1. Mapping animals types to their corresponding emojis.
 * 2. Mapping the counts to the number of emojis.
 * 3. Generating the bars based on the emojis and the number.
 *
 * Here is the implementation:
 */

//โžœ [ 0, 1, 2, 3, 4, 5 ]
const I = echo(d3.range(data.length));

//โžœ { cattle: "๐Ÿ„", sheep: "๐Ÿ‘", pigs: "๐Ÿ–" }
const emoji = echo({cattle: "๐Ÿ„", sheep: "๐Ÿ‘", pigs: "๐Ÿ–"});

//โžœ [ "๐Ÿ–", "๐Ÿ„", "๐Ÿ‘", "๐Ÿ–", "๐Ÿ„", "๐Ÿ‘" ]
const E = echo(data.map((d) => emoji[d.animal]));

//โžœ [ 1, 4, 11, 6, 10, 7 ]
const V = echo(data.map((d) => Math.round(d.count / 1e6)));

//โžœ [ "๐Ÿ– ", "๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ", "๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ", "๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ– ", "๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ", "๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ " ]
const bars = echo(I.map((i) => `${E[i]} `.repeat(V[i])));

/** This is the chart we got so far. */

//โžœ ๐Ÿ–
//โžœ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„
//โžœ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘
//โžœ ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ–
//โžœ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„
//โžœ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘
echo(bars.join("\n"));

/**
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                          Adding the labels
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * Next step is to add the labels to the bars. We need to collect all the
 * animal types by a set, and compute a margin left to make sure the labels
 * are aligned. Then concatenate the labels to the bars with a separator: `-|`.
 */

//โžœ [ "pigs", "cattle", "sheep" ]
const L = echo(Array.from(new Set(data.map((d) => d.animal))));

//โžœ 6
const marginLeft = echo(d3.max(L, (d) => d.length));

//โžœ [ "  pigs", "cattle", " sheep", "  pigs", "cattle", " sheep" ]
const labels = echo(data.map((d) => d.animal.padStart(marginLeft, " ")));

//โžœ [ "    pigs -| ๐Ÿ– ", "  cattle -| ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ", "   sheep -| ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ", "    pigs -| ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ– ", "  cattle -| ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ", "   sheep -| ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ โ€ฆ
const rows = echo(I.map((i) => "  " + labels[i] + " -| " + bars[i]));

/** Now the chart looks like this. */

//โžœ     pigs -| ๐Ÿ–
//โžœ   cattle -| ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„
//โžœ    sheep -| ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘
//โžœ     pigs -| ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ–
//โžœ   cattle -| ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„
//โžœ    sheep -| ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘
echo(rows.join("\n"));

/**
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                         Generating the titles
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * Technically speaking, the chart is a facet chart, which means it contains
 * multiple charts. The first one is for Great Britain, and the second one is
 * for United States. In order to differentiate the two charts, we need to add
 * the titles and some spacing.
 */

//โžœ 45
const width = echo(d3.max(rows, (d) => d.length));

//โžœ [ "Great Britain", "United States" ]
const T = echo(Array.from(new Set(data.map((d) => d.country))));

//โžœ [ "            Great Britain", "            United States" ]
const titles = echo(T.map((t) => t.padStart(Math.ceil(width / 2 + 2), " ")));

/**
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                             Final output
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * Finally, we can concatenate the titles, the rows, and the live stock caption
 * to get the final output!
 */

//โžœ
//โžœ             Great Britain
//โžœ     pigs -| ๐Ÿ–
//โžœ   cattle -| ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„
//โžœ    sheep -| ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘
//โžœ
//โžœ             United States
//โžœ     pigs -| ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ– ๐Ÿ–
//โžœ   cattle -| ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„ ๐Ÿ„
//โžœ    sheep -| ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘ ๐Ÿ‘
//โžœ
//โžœ Live stock (millions)
//โžœ
const output = echo(
  [
    " ",
    titles[0], // Great Britain
    ...rows.slice(0, 3),
    " ",
    titles[1], // United States
    ...rows.slice(3),
    " ",
    "Live stock (millions)", // Add a caption
    " ",
  ].join("\n"),
);
Bairui Su
Created 2025-09-03
//โžœ   ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€็‡š
//โžœ   ็‡š็‡šใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€็‡š็‡šใ€€ใ€€็‡šใ€€ใ€€ใ€€ใ€€ใ€€็‡š็‡šใ€€ใ€€็‡š
//โžœ   ใ€€ใ€€็‡š็‡šใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€็‡šใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€ใ€€็‡š
//โžœ   ใ€€ใ€€ใ€€ใ€€็‡š็‡š็‡šใ€€ใ€€็‡š็‡šใ€€็‡šใ€€ใ€€ใ€€ใ€€็‡š็‡š็‡š็‡šใ€€ใ€€ใ€€็‡š็‡šใ€€ใ€€็‡š็‡šใ€€็‡š็„ฑ็‡š็‡š็‡š
//โžœ   ใ€€ใ€€ใ€€็‡š็„ฑใ€€ใ€€ใ€€ใ€€็‡š็‡š็‡šใ€€็‡š็‡šใ€€ใ€€ใ€€็‡š็‡š็‡š็‡š็‡šใ€€ใ€€ใ€€ใ€€็‡š็‡šใ€€ใ€€็‡š็‡š็„ฑ็‡š็‡š
//โžœ   ็„ฑ็‡šใ€€ใ€€ใ€€็‡šใ€€ใ€€ใ€€ใ€€็‡š็‡š็‡š็‡š็‡šใ€€ใ€€็„ฑ็„ฑ็„ฑ็‡š็‡š
//โžœ   ใ€€ใ€€ใ€€็„ฑ็„ฑ็„ฑ็‡š็‡š็‡š็‡š็‡š็‡š็„ฑ็„ฑ็„ฑ็‡š็‡š็‡šใ€€็„ฑ็‡š็„ฑใ€€ใ€€ใ€€ใ€€ใ€€ใ€€็‡š็„ฑ็„ฑ
//โžœ   ็‡š็‡š็‡š็‡š็‡š็‡š็‡šใ€€ใ€€ใ€€ใ€€ใ€€็‡š็‡š็‡š็„ฑ็„ฑ็‡š
//โžœ   ็„ฑ็„ฑใ€€ใ€€็„ฑ็‡š็‡š็‡š็‡š็‡š็‡š็„ฑ็„ฑ็‡š็„ฑ็„ฑ็„ฑใ€€ใ€€ใ€€ใ€€็‡š็„ฑ็„ฑ็‡š็‡š็‡š็‡š็„ฑ็„ฑ็„ฑ็‡š็‡š็‡š็‡š็„ฑ
//โžœ   ็‡š็‡š็‚Ž็‚Ž็‡š็„ฑ็„ฑ็‚Ž็„ฑ็‡š็‡š็‡š็‡š็‡šใ€€ใ€€็‡š็‡š็‡š็‡š็„ฑ็„ฑ็‡š็‚Ž็‚Ž็‚Ž็‡š็„ฑ็‡šใ€€็‡š็‡šใ€€็„ฑ็„ฑ็‚Ž
//โžœ   ็‡š็‡š็‡šใ€€็‚Ž็‚Ž็‚Ž็‚Ž็‚Ž็‚Ž็‚Ž็‚Ž็‚Ž็‚Ž็‚Ž็„ฑ็„ฑ็„ฑ็‡šใ€€็‚Ž็‚Žใ€€็„ฑ็„ฑ็‡š็‚Ž็„ฑ็‚Ž็‡š็„ฑ็„ฑ็„ฑ็„ฑ็‚Ž็‚Ž
//โžœ   ็„ฑ็„ฑ็„ฑ็„ฑ็„ฑ็„ฑ็„ฑ็„ฑ็‚Ž็‚Ž็‚Ž็‚Ž็„ฑ็„ฑ็„ฑ็„ฑ็„ฑ็ซ็„ฑ็‡š็‡š็„ฑใ€€็„ฑ็‡š็„ฑ็‡š็„ฑ็‚Ž็‚Ž็‚Ž็‚Ž็‡š็‚Ž็„ฑ็„ฑ
//โžœ   ็„ฑ็‚Ž็‡š็‡š็‡š็„ฑ็‡š็‚Ž็‚Ž็‚Ž็ซ็ซ็„ฑ็„ฑ็ซ็ซ็ซ็ซ็ซ็ซ็„ฑ็‚Ž็‚Ž็‚Žใ€€็‡š็‚Ž็‚Žใ€€ใ€€็„ฑ็‡š็‡š็‡š็‚Ž็ซ
//โžœ   ็‡š็‚Ž็„ฑ็„ฑ็‡š็‡š็‚Ž็„ฑ็‚Ž็ซ็„ฑ็‚Ž็‚Ž็„ฑ็„ฑ็‡š็„ฑ็‚Ž็‚Ž็„ฑ็‚Ž็ซ็„ฑ็‚Ž็„ฑ็„ฑ็„ฑ็‚Ž็ซ็„ฑ็‚Ž็‚Ž็„ฑ็„ฑ็„ฑ็‚Ž
//โžœ   ็ซ็‚Ž็„ฑ็„ฑ็ซ็‚Ž็‚Ž็„ฑ็‚Ž็„ฑ็ซ็‚Ž็‚Ž็‚Ž็‚Ž็‚Ž็‚Ž็‚Ž็‚Ž็„ฑ็‚Ž็„ฑ็ซ็„ฑ็ซ็‡š็„ฑ็„ฑ็‚Ž็‚Ž็„ฑ็„ฑ็„ฑ็„ฑ็‚Ž็ซ
{
  frame;

  for (let y = 0; y < rows - 1; y++) {
    for (let x = 0; x < cols; x++) {
      const decay = d3.randomInt(0, 3)();
      const spread = d3.randomInt(-1, 1)();
      const i = Math.min(Math.max(0, x - spread), cols - 1);
      const target = fire[index(i, y + 1)];
      fire[index(x, y)] = Math.max(0, target - decay);
    }
  }

  const noise = new Noise();
  const linear = d3.scaleLinear([0, 1], [0, rows]);
  const source = (i) => linear(noise.get(i, 0, 0));

  for (let x = 0; x < cols; x++) {
    fire[index(x, rows - 1)] = ~~source(x);
  }

  const quantile = d3.scaleQuantile(d3.extent(fire), chs);
  const ch = (d) => (d ? quantile(d) : "ใ€€");

  let output = "";
  for (let i = 0; i < rows; ++i) {
    for (let j = 0; j < cols; ++j) output += ch(fire[index(j, i)]);
    output += i === rows - 1 ? "" : "\n";
  }
  output = output.split("\n").map((d) => "  " + d).join("\n");

  echo(output);
}

const d3 = recho.require("d3");

/**
 * I like this example also because I found one importable noise library:
 * `perlin-noise-3d`:
 *
 * - https://www.npmjs.com/package/perlin-noise-3d
 */

const Noise = recho.require("perlin-noise-3d");

/**
 * Perlin noise is so useful in creative coding, so it's great that we can use
 * it in Recho before making it a built-in function.
 *
 * As you can see, the implementation replies on noise function to generate
 * natural source of fire, which is the key to create such effect!
 */
Bairui Su
Bairui Su / Matrix Rain
Created 2025-08-22
//โžœ              w       $  R        |       @      2         !
//โžœ        j     M          &        C              m        ,
//โžœ         _#   D          9 z      L         ?    Z=
//โžœ         !d   `          [        *         g    g     3
//โžœ         t|   [      -  BS  E     k e      u<    J :  o6      j
//โžœ         I*   !      j  R$ p7     p I&D    k,      j  ?1      M
//โžœ      k  j#   W O D  (      ,   > a s#     XY   9' .  XK y    )
//โžœ      a  $\  *n [B   W  W   F    6[ w>  R  AOL   p j  _B U    Z
//โžœ      X  /1     NX9  K      ~     7  Z /u  b_    o H ,:u=$    6
//โžœ    Btb* rE a    yD  i    n ^     ks , S!  |?      &  ?$ l    a
//โžœ    $ 3  @  y    ro  9       b    Oh ! I-  ^7      x  =t     X
//โžœ    8 n  <  F    8ZG -       +    8i 2 .j  j       bx ,tf     G
//โžœ    _ 8  t  3    GH& T u           S K y   M   !   Ln 45Y   K L
//โžœ    }    d  ca   @ h   4           T 7 6   T   (   !y_hx7   P r
//โžœ    u    i  /    FH0   =      K    v P     `   e   <[ XjD  <6 u
//โžœ   M%    V  {    vg    |      V    4 q     7 [ R   ;   [@  =; \
//โžœ    R       y    [:    }      ) `v S       [ %     /   g-  u= 3
//โžœ    g       $    UH    g      & rD @       b H     =    o  4+ R
//โžœ    9       g    96           W c  ~         t          *  mq
//โžœ            ;    vI           )    p          \             M
//โžœ            )    wc           A                 ~
//โžœ                  d           _          =      D
//โžœ                  `           ~          q      #
//โžœ                  |           m                 e
//โžœ                  e           W                 ^
{
  frame;

  // Create a new buffer.
  const buffer = d3.range(width * height).map(() => " ");

  // Update all columns.
  for (let i = columns.length - 1; i >= 0; --i) {
    const column = columns[i];
    const {lifespan, length, chars} = column;
    const n = chars.length;
    if (lifespan < 0) columns[i] = createColumn(height);
    else if (lifespan <= n) chars[n - lifespan] = " ";
    else {
      for (let j = length - 1; j < n; ++j) chars[j] = randomChar();
      chars.push(randomChar());
    }
    column.lifespan -= 1;
  }

  // Update the buffer.
  for (let i = 0; i < columns.length; ++i) {
    const column = columns[i];
    const {y, chars} = column;
    for (let j = 0; j < chars.length; ++j) buffer[(y + j) * width + i] = chars[j];
  }

  // Render the buffer.
  let output = "";
  for (let i = 0; i < height; ++i) {
    for (let j = 0; j < width; ++j) output += buffer[i * width + j];
    output += i === height - 1 ? "" : "\n";
  }
  output = output.split("\n").map((d) => "  " + d).join("\n");

  echo(output);
}

function createColumn(height) {
  const lifespan = d3.randomInt(height)();
  const length = d3.randomInt(lifespan)();
  const chars = d3.range(length).map(randomChar);
  const y = d3.randomInt(0, 10)();
  return {lifespan, chars, y};
}

function randomChar() {
  return String.fromCharCode(d3.randomInt(32, 127)());
}

const frame = recho.interval(1000 / 15);

const d3 = recho.require("d3");
Bairui Su
Bairui Su / Random Histogram
Created 2025-08-22
//โžœ          โ–ˆ
//โžœ       โ–ˆ  โ–ˆ          โ–ˆ  โ–ˆ                    โ–ˆโ–ˆ
//โžœ       โ–ˆ  โ–ˆ  โ–ˆ โ–ˆ     โ–ˆ  โ–ˆ          โ–ˆ         โ–ˆโ–ˆ
//โžœ   โ–ˆ โ–ˆ โ–ˆโ–ˆ โ–ˆ  โ–ˆ โ–ˆโ–ˆ    โ–ˆ  โ–ˆ  โ–ˆโ–ˆโ–ˆ     โ–ˆ       โ–ˆ โ–ˆโ–ˆ
//โžœ  โ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ  โ–ˆ โ–ˆโ–ˆ    โ–ˆโ–ˆ โ–ˆ โ–ˆโ–ˆโ–ˆโ–ˆ    โ–ˆโ–ˆ  โ–ˆโ–ˆโ–ˆโ–ˆ โ–ˆ โ–ˆโ–ˆ  โ–ˆโ–ˆ
//โžœ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ  โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆโ–ˆ    โ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆ
//โžœ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ โ–ˆโ–ˆโ–ˆ
//โžœ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ
{
  let output = "";
  for (let i = 0; i < height; i++) {
    for (let j = 0; j < width; j++) {
      const bin = bins[j];
      const h = bin ? (bin * height) / d3.max(bins) : 0;
      output += h >= height - i ? "โ–ˆ" : " ";
    }
    output += i === height - 1 ? "" : "\n";
  }
  echo(output);
}
Bairui Su
Bairui Su / Mandelbrot Set
Created 2025-08-21
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*OoO@@@@@@@@@@@@@@@@@@@*ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@@@@@@@@@@@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@@@@@@@@@@@@@@@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@ยทO@**ยทยทยทยท*@@@@@@@@@@@@@@@@@@@@@@@@Oยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*@@@@@@@@ยทยท@@@@@@@@@@@@@@@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*@@@@@@@@@@*@@@@@@@@@@@@@@@@@@@@@@@@@oยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*o*@@@@@@@@@@@o@@@@@@@@@@@@@@@@@@@@@@@@oยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยท@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*o*@@@@@@@@@@@o@@@@@@@@@@@@@@@@@@@@@@@@oยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*@@@@@@@@@@*@@@@@@@@@@@@@@@@@@@@@@@@@oยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*@@@@@@@@ยทยท@@@@@@@@@@@@@@@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@ยทO@**ยทยทยทยท*@@@@@@@@@@@@@@@@@@@@@@@@Oยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@@@@@@@@@@@@@@@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@@@@@@@@@@@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท*OoO@@@@@@@@@@@@@@@@@@@*ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@@@@@@@@@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@@ยท*@@@@@@@@@@@@ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท@ยทยทยทยทยทยทยท@@@*ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทO@@@@*ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทo@@@@*ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทO@ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
//โžœ ยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยทยท
{
  let output = "";
  for (let y = 0; y < rows; y++) {
    for (let x = 0; x < cols; x++) {
      const re = map(x, 0, cols, -2.5, 1);
      const im = map(y, 0, rows, -1, 1);
      let [a, b, i] = [0, 0, 0];
      while (i < maxIter) {
        [a, b] = [a * a - b * b + re, 2 * a * b + im];
        if (a * a + b * b > 4) break;
        i++;
      }
      const index = ~~((i / maxIter) * (colors.length - 1));
      output += colors[index];
    }
    output += y === rows - 1 ? "" : "\n";
  }
  echo(output);
}

function map(x, d0, d1, r0, r1) {
  return r0 + ((r1 - r0) * (x - d0)) / (d1 - d0);
}

/**
 * Again, you don't need to completely understand the code above for now. If
 * you find textual outputs can be interesting and creative by this example,
 * that's the point!
 *
 * If you're really curious about Mandelbrot set, here are some examples that
 * I made with Charming.js[2] you may find interesting:
 *
 * - Multibrot Set: https://observablehq.com/d/fc2cfd9ae9e7524c
 * - Multibrot Set Table: https://observablehq.com/d/3028c0d5655345e3
 * - Multibrot Set Transition: https://observablehq.com/d/c040d3db33c0033e
 * - Zoomable Mandelbrot Set (Canvas): https://observablehq.com/d/2e5bdd2365236c2d
 * - Zoomable Mandelbrot Set (WebGL): https://observablehq.com/d/cfe263c1213334e3
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *                              References
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * [1] https://en.wikipedia.org/wiki/Mandelbrot_set
 * [2] https://charmingjs.org/
 */