fox

[github]

a fox asleep on its tail, breathing, peeking now and then

creatures 47×16 10 fps 3.2 kB

use it

html

<script type="module" src="https://ascii.rest/ascii.js"></script>

<ascii-art piece="fox"></ascii-art>

No install and no build step. ascii.js defines the <ascii-art> tag, loads the piece from ascii.rest, plays it while it is on screen, and holds the first frame for anyone who prefers reduced motion.

react

// npm install github:bas3line/ascii
import { Ascii } from "ascii.rest/react";
import { fox } from "ascii.rest/pieces";

<Ascii piece={fox} />

A client component, so it works in the Next.js app router as it is. Pass the name instead, piece="fox", to fetch the piece only when it mounts.

astro

---
// npm install github:bas3line/ascii
import Ascii from "ascii.rest/astro";
---

<Ascii piece="fox" />

Renders the first frame on the server, so the page is whole before any script runs, then plays the piece once it loads.

typescript

// npm install github:bas3line/ascii
import { mount } from "ascii.rest";
import { fox } from "ascii.rest/pieces";

const el = document.querySelector<HTMLPreElement>("pre")!;
const stop = mount(el, fox);

mount plays a piece in a <pre>, or on a <canvas> for the coloured ones, and returns a function that stops it. Every piece, option and frame is typed.

fox.ts

[js] [github]
/*
 * fox: a fox asleep, curled up with its chin on its tail. Its flank rises and
 * falls; now and then it opens one eye and looks about, or flicks an ear.
 */
import type { Frame, Meta } from "../types.ts";

export const meta = {
  name: "fox",
  category: "creatures",
  note: "a fox asleep on its tail, breathing, peeking now and then",
  cols: 47,
  rows: 16,
  fps: 10,
} satisfies Meta;

const FOX = [
  "           /\\          /\\",
  "          /  \\        /  \\",
  "         /    `.____.'    \\",
  "        /                  \\",
  "       ;    `-'      `-'    ;",
  "        `.     \\    /     .'`-.._",
  "     _.-' `-.   \\../   .-'       ``-._",
  "  .-'        `-. \\/ .-'               `.",
  " /   _..--''``-.`--'.-''``--..__        \\",
  "|  .'   ,                       ``-.     |",
  "| (    (                            `.   |",
  "|  `.   `.                            \\  |",
  " \\   `-..__                            \\ |",
  "  `-..__   ``---...______________...----'",
];
const OX = 2, OY = 1; // where the drawing sits in the frame

const LOOP = 12; // seconds
const BREATH = 4; // seconds a breath; the flank is out for the middle of it
// Breathing in, the flank swells out a column: [row, col, glyphs] over the art.
const SWELL: [number, number, string][] = [[7, 38, "`-."], [8, 40, " \\"], [9, 41, " |"], [10, 41, " |"], [11, 41, " |"], [12, 41, " |"], [13, 40, "-'"]];
const EYE: [number, number] = [4, 21];
// One eye opens, looks one way and the other, and shuts: [from, to, glyphs, shift].
const PEEK: [number, number, string, number][] = [
  [2.2, 2.5, "`o'", 0], [2.5, 3.0, "(o)", 0], [3.0, 3.5, "(o)", -1],
  [3.5, 4.0, "(o)", 1], [4.0, 4.3, "(o)", 0], [4.3, 4.6, "`o'", 0],
];
const EAR: [number, number][] = [[7.0, 7.2], [7.45, 7.65]]; // the right ear's tip folds over
// Each z drifts up and away from the head a cell at a time, growing as it goes.
const Z_EVERY = 2, Z_LIFE = 4;
const Z_PATH: [number, number][] = [[4, 31], [3, 32], [3, 33], [3, 34], [2, 35], [2, 36], [2, 37], [1, 38], [1, 39], [1, 40]];

export default function fox(): Frame {
  const { cols, rows } = meta;
  const awake = (u: number) => u >= PEEK[0][0] - 0.3 && u < PEEK[PEEK.length - 1][1] + 0.6;

  return (t) => {
    const u = ((t % LOOP) + LOOP) % LOOP;
    const grid = Array.from({ length: rows }, () => Array<string>(cols).fill(" "));
    const put = (r: number, c: number, s: string) => [...s].forEach((ch, i) => (grid[OY + r][OX + c + i] = ch));
    FOX.forEach((line, r) => put(r, 0, line));

    const b = u % BREATH;
    if (b >= 0.6 && b < 2.4) for (const [r, c, s] of SWELL) put(r, c, s);

    const peek = PEEK.find(([a, z]) => u >= a && u < z);
    if (peek) put(EYE[0], EYE[1] - 1, " ".repeat(5)), put(EYE[0], EYE[1] + peek[3], peek[2]);
    if (EAR.some(([a, z]) => u >= a && u < z)) put(0, 23, "/-");

    // A z is only let out while both eyes are shut.
    for (let k = -2; k <= Math.ceil(LOOP / Z_EVERY); k++) {
      const born = k * Z_EVERY, age = u - born;
      if (age < 0 || age >= Z_LIFE || awake(((born % LOOP) + LOOP) % LOOP)) continue;
      const i = Math.floor((age / Z_LIFE) * Z_PATH.length);
      const [r, c] = Z_PATH[i];
      grid[r][c] = i < 6 ? "z" : "Z";
    }
    return grid.map((row) => row.join("")).join("\n");
  };
}