arch linux

[github]

the blue arch, scanned now and then

distros 59×28 30 fps in colour or one ink 6.7 kB

use it

html

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

<ascii-art piece="arch-linux" options='{"scan":5}'></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. Add mono, <ascii-art piece="arch-linux" mono>, for one ink.

react

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

<Ascii piece={archLinux} options={{"scan":5}} />

A client component, so it works in the Next.js app router as it is. Pass the name instead, piece="arch-linux", to fetch the piece only when it mounts. Add mono for one ink.

astro

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

<Ascii piece="arch-linux" options={{"scan":5}} />

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

typescript

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

const el = document.querySelector<HTMLCanvasElement>("canvas")!;
const stop = mount(el, archLinux, {"scan":5});

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. Mount it in a <pre> for one ink.

arch-linux.ts

[js] [github]
/*
 * arch linux: the Arch Linux logo, the blue arch, with a scan line running
 * down it every few seconds that scrambles what it passes.
 *
 * Drawn from devicon's archlinux-original.svg (MIT): each cell holds the
 * character whose shape best matches the logo's edge through it, and 8 where
 * the logo is solid. On a canvas it takes the logo's colours, lifted on a dark
 * page where they would sink into it; in a <pre> it is one ink, from the same
 * drawing. The logo is a trademark of its owner, shown here to name the
 * distribution.
 */
import type { Frame, Meta } from "../types.ts";

export interface ArchLinuxOptions {
  [key: string]: unknown;
  /** Seconds between scans; 0 keeps the logo still. */
  scan: number;
}

export const meta = {
  name: "arch linux",
  category: "distros",
  note: "the blue arch, scanned now and then",
  cols: 59,
  rows: 28,
  fps: 30,
  options: { scan: 5 },
  // The logo's colour for a light page, then for a dark one; each as drawn, then twice lighter for the scan.
  palette: [
    "#1894d4", "#69b9e3", "#badff2", "#1894d4", "#69b9e3", "#badff2",
  ],
} satisfies Meta<ArchLinuxOptions>;

// In a <pre>, in the page's own colour.
const MONO = String.raw`

                           \p
                          _88(
                         .8888,
                         p88888,
                        q8888888,
                       q88888888q,
                      )8888888888q
                     '888888888888q
                    _q_"88888888888p
                   _8888q_8888888888p
                  _888888888888888888p
                 _88888888888888888888q
                _8888888888888888888888q
               )888888888888888888888888q,
              q88888888888888888888888888q,
             q88888888888P"''"Y88888888888q,
            q88888888888"      '888888888888,
           q88888888888'         888888888888,
          p88888888888P          "888888888888,
        .p888888888888|           88888888q("Y8,
       _p8888888888888|           88888888888q_,
      _88888888888888P\          '88888888888888q_
     _888888888P""'                  '""Y888888888p   __.
    _88888P""                              ""888888p  '"|
   )88P""                                      '"888p
  )P"                                              ""b

`;

// On a canvas, and the colour of each cell: an index into the logo's colours.
const ART = String.raw`

                           \p
                          _88(
                         .8888,
                         p88888,
                        q8888888,
                       q88888888q,
                      )8888888888q
                     '888888888888q
                    _q_"88888888888p
                   _8888q_8888888888p
                  _888888888888888888p
                 _88888888888888888888q
                _8888888888888888888888q
               )888888888888888888888888q,
              q88888888888888888888888888q,
             q88888888888P"''"Y88888888888q,
            q88888888888"      '888888888888,
           q88888888888'         888888888888,
          p88888888888P          "888888888888,
        .p888888888888|           88888888q("Y8,
       _p8888888888888|           88888888888q_,
      _88888888888888P\          '88888888888888q_
     _888888888P""'                  '""Y888888888p   __.
    _88888P""                              ""888888p  '"|
   )88P""                                      '"888p
  )P"                                              ""b

`;
const INK = String.raw`

                           00
                          0000
                         000000
                         0000000
                        000000000
                       00000000000
                      000000000000
                     00000000000000
                    0000000000000000
                   000000000000000000
                  00000000000000000000
                 0000000000000000000000
                000000000000000000000000
               000000000000000000000000000
              00000000000000000000000000000
             0000000000000000000000000000000
            0000000000000      00000000000000
           0000000000000         0000000000000
          0000000000000          00000000000000
        000000000000000           00000000000000
       0000000000000000           00000000000000
      00000000000000000          00000000000000000
     00000000000000                  00000000000000   000
    000000000                              000000000  000
   000000                                      000000
  000                                              000

`;

const START = 0.5; // seconds before the first scan
const PASS = 2; // seconds a scan takes, top to bottom
const HEAD = 2; // rows the line scrambles
const TRAIL = 5; // rows of afterglow behind it
const NOISE = "#$%&*+<=>?@[]{}"; // what the line scrambles the logo into

const lines = (art: string) => art.slice(1, -1).split("\n").map((line) => line.padEnd(meta.cols));

function hash(x: number, y: number, k: number) {
  let h = Math.imul(x, 73856093) ^ Math.imul(y, 19349663) ^ Math.imul(k, 83492791);
  h = Math.imul(h ^ (h >>> 13), 1274126177);
  return (h ^ (h >>> 16)) >>> 0;
}

export default function archLinux({ scan = meta.options.scan }: Partial<ArchLinuxOptions> = {}): Frame {
  const { cols, rows, fps } = meta;
  const mono = lines(MONO), art = lines(ART), ink = lines(INK);
  const n = meta.palette.length / 6;
  const every = scan > 0 ? Math.max(scan, PASS + 0.5) : 0;
  // The line runs from the logo's first row to past its last, rather than the whole frame.
  let first = rows, last = -1;
  for (const pic of [mono, art])
    pic.forEach((line, y) => {
      if (line.trim()) (first = Math.min(first, y)), (last = Math.max(last, y));
    });
  const span = last - first + 1 + HEAD + TRAIL;

  return (t, { paper = false, color } = {}) => {
    const pic = color ? art : mono;
    const since = t - START;
    const at = every && since >= 0 ? first + (span * (since % every)) / PASS : -Infinity;
    const tick = Math.floor((t * fps) / 2); // the noise changes every other frame
    const out: string[] = [];
    for (let y = 0; y < rows; y++) {
      const d = at - y; // rows since the line reached this one
      const level = d >= 0 && d < HEAD ? 2 : d >= HEAD && d < HEAD + TRAIL ? 1 : 0;
      let line = "";
      for (let x = 0; x < cols; x++) {
        let ch = pic[y][x];
        if (ch !== " ") {
          if (level === 2) {
            const h = hash(x, y, tick);
            if (h % 4) ch = NOISE[(h >>> 2) % NOISE.length];
          }
          if (color) color[y * cols + x] = (paper ? 0 : 3 * n) + level * n + parseInt(ink[y][x], 36);
        }
        line += ch;
      }
      out.push(line);
    }
    return out.join("\n");
  };
}