dividers
[github]rules and ornaments for between sections, plain to fancy
ui 63×25 still 2.9 kB
─────────────────────────────────────────────────────────── ═══════════════════════════════════════════════════════════ · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ━━━━━━━━━━━━━━━ · · ─ ─ ──────────━━━━━━━━━━━━━━━━━━━━━━━────────── ─ ─ · · ────────────────────────── · ° · ────────────────────────── * * * ╶──────────── ° ────────────╴ ══════════════════╡ ° ╞══════════════════ ─<>─<>─<>─<>─<>─<>─<>─<>─<>─<>─<>─<>─<>─<>─ ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ _.·°·._.·°·._.·°·._.·°·._.·°·._.·°·._.·°·._ ─────────────────────────────────────────────────────────── ═══════════════════════════════════════════════════════════ · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ━━━━━━━━━━━━━━━ · · ─ ─ ──────────━━━━━━━━━━━━━━━━━━━━━━━────────── ─ ─ · · ────────────────────────── · ° · ────────────────────────── * * * ╶──────────── ° ────────────╴ ══════════════════╡ ° ╞══════════════════ ─<>─<>─<>─<>─<>─<>─<>─<>─<>─<>─<>─<>─<>─<>─ ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ _.·°·._.·°·._.·°·._.·°·._.·°·._.·°·._.·°·._
use it
html
<script type="module" src="https://ascii.rest/ascii.js"></script>
<ascii-art piece="dividers" options='{"width":59}'></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 { dividers } from "ascii.rest/pieces";
<Ascii piece={dividers} options={{"width":59}} /> A client component, so it works in the Next.js app router as it is. Pass the name instead, piece="dividers", to fetch the piece only when it mounts.
astro
---
// npm install github:bas3line/ascii
import Ascii from "ascii.rest/astro";
---
<Ascii piece="dividers" options={{"width":59}} /> 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 { dividers } from "ascii.rest/pieces";
const el = document.querySelector<HTMLPreElement>("pre")!;
const stop = mount(el, dividers, {"width":59}); 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.
/*
* dividers: a sheet of rules to set between sections of a page, from a plain
* line through short centred rules down to ornaments, chains and waves.
*/
import type { Frame, Meta } from "../types.ts";
export interface DividersOptions {
[key: string]: unknown;
width: number;
}
export const meta = {
name: "dividers",
category: "ui",
note: "rules and ornaments for between sections, plain to fancy",
cols: 63,
rows: 25,
fps: 0,
options: { width: 59 },
} satisfies Meta<DividersOptions>;
// `unit` repeated to n characters.
const rep = (unit: string, n: number) => unit.repeat(Math.max(0, Math.ceil(n / unit.length))).slice(0, Math.max(0, n));
// The same string seen in a mirror, so its slanted and pointed glyphs turn too.
const FLIP: Record<string, string> = { "<": ">", ">": "<", "╱": "╲", "╲": "╱", "╶": "╴", "╴": "╶", "╡": "╞", "╞": "╡" };
const mirror = (s: string) => [...s].reverse().map((c) => FLIP[c] || c).join("");
// A left half, an optional middle cell and the half again in the mirror.
const sym = (half: string, mid = "") => half + mid + mirror(half);
// A centrepiece with a rule run out to either side, n wide.
const around = (mid: string, fill: string, n: number) => {
const side = Math.floor((n - [...mid].length) / 2);
return sym(rep(fill, side), [...mid].length + 2 * side < n ? fill + mid : mid);
};
// Thin at the ends, heavy in the middle: dots, then dashes, then rules.
const tapered = (n: number) => {
const h = Math.floor(n / 2);
if (h < 12) return rep("─", n);
const run = h - 8, thin = Math.ceil(run * 0.45);
return sym("· · ─ ─ " + rep("─", thin) + rep("━", run - thin), n % 2 ? "━" : "");
};
export default function dividers({ width = 59 }: Partial<DividersOptions> = {}): Frame {
const { cols, rows } = meta;
const W = Math.max(13, Math.min(cols, Math.floor(width) || 59));
// The ornaments run a fraction of the measure, so they shrink with it.
const part = (f: number) => Math.max(5, Math.round(W * f));
const odd = (n: number) => n - (n % 2 ? 0 : 1);
const SHEET = [
rep("─", W),
rep("═", W),
rep("· ", odd(W)),
rep("━", odd(part(0.25))), // a short rule, centred
tapered(W),
around(" · ° · ", "─", W),
"* * *",
sym("╶" + rep("─", (odd(part(0.5)) - 5) / 2) + " ", "°"),
around("╡ ° ╞", "═", odd(part(0.7))),
sym(rep("─<>", Math.floor((part(0.73) - 1) / 6) * 3), "─"),
rep("╱╲", part(0.55) - (part(0.55) % 2)), // even, so it ends on the half it began with, mirrored
rep("_.·°·.", Math.floor((part(0.73) - 1) / 6) * 6) + "_",
];
const lines: string[] = [];
for (let r = 0; r < rows; r++) {
const s = r % 2 === 1 ? SHEET[(r - 1) / 2] || "" : "";
const left = Math.floor((cols - [...s].length) / 2);
lines.push((" ".repeat(Math.max(0, left)) + s).padEnd(cols).slice(0, cols));
}
const picture = lines.join("\n");
return () => picture;
}