A star history chart draws itself week by week, flagging each milestone as the line climbs to your repo's star count.
* kailashsr38/ascii-magic-art 94 stars │ │ 100 │┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ │ ● │ ••│ │ •│·· │ ••│··· 75 │┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄••│·····┄┄ │ •│······· │ ••│········ │ 50 ••••│·········· │ ╲ •••••│·············· 50 │┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄•••••••│···················┄┄ │ ••••••│·························· │ 25 •••••••│································ │ ╲ •••••••••│······································· 25 │┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄••│················································┄┄ │ •│·················································· │ •│··················································· │ •••••│···················································· │ •••••••••••••│░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 0 │••••••••••│░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░┄┄ └─────────────────────────────────────────────────────────────────────────────────── 2y ago 1y ago now star history, weekly
Make it yours
Paste two lines into any page, use it from React, play it in a terminal, put a GIF in a README, or hand the Agent prompt to your coding agent. Every tab follows the look you pick above.
<script type="module" src="https://www.ascii-magic.com/ascii/ascii.js"></script>
<ascii-art piece="star-history-climb"></ascii-art>// npm install ascii-magic-art
import { useEffect, useRef } from "react";
import { play } from "ascii-magic-art/runtime/player.js";
import * as piece from "ascii-magic-art/pieces/star-history-climb.js";
export function StarHistoryClimb() {
const ref = useRef(null);
// play() draws the piece on a canvas in your look and returns stop(), which React runs on unmount
useEffect(() => play(ref.current, piece), []);
return <pre ref={ref} style={{ fontSize: 10, fontFamily: "Menlo, Consolas, monospace" }} />;
}npx ascii-magic-art star-history-climb<!-- Export the GIF below in the look you picked, commit it, then: -->
<p align="center">
<img src="assets/star-history-climb.gif" alt="Star History Climb, animated ASCII art" width="720">
</p>Add the animated ASCII art piece "star-history-climb" (Star History Climb) from ASCII Magic to this project.
1. Load the script once, in the browser only (in SSR frameworks such as Next.js, load it on the client):
<script type="module" src="https://www.ascii-magic.com/ascii/ascii.js"></script>
2. Put the piece where it fits best, such as the hero, a loading state, an empty state or the 404 page:
<ascii-art piece="star-history-climb"></ascii-art>
3. Keep a real heading or text next to it (the art is decoration), and size it with the size attribute or CSS.
Preview and options: https://www.ascii-magic.com/ascii/star-history-climb
Rules for coding agents: https://www.ascii-magic.com/ascii/use/ai-coding-agentsPause on the frame you like, pick a look, then download it at an exact size.
MIT licensed and open source on GitHub. It imports one small helper file, kit.js. This is the animation itself: what gets drawn in every frame. The look you pick is applied on top by the player, like a filter, so this file stays the same in every look while the snippets above change with it. Edit it to change the animation; see the docs to make your own.
/* star-history-climb: a repo's star history drawing itself, climbing past milestones to your count. */
import { Grid, palette, clamp } from "../lib/kit.js";
export const meta = {
slug: "star-history-climb",
name: "Star History Climb",
category: "live-data",
note: "A star history chart draws itself week by week, flagging each milestone as the line climbs to your repo's star count.",
tags: ["github", "stars", "chart", "growth", "readme", "open-source", "dashboard", "milestone"],
cols: 96, rows: 30, fps: 24,
loop: 12,
params: {
stars: { label: "Stars", type: "number", default: 100, min: 10, max: 999999 },
repo: { label: "Repo", default: "kailashsr38/ascii-magic-art", max: 40 },
},
palette: palette(["#e6edf3", "#8b949e", "#30363d", "#ffd75e", "#e8b423", "#7ee787", "#79c0ff", "#21262d", "#f0883e"]),
};
const fmt = (n) => (n >= 10000 ? (n / 1000).toFixed(n >= 100000 ? 0 : 1).replace(/\.0$/, "") + "k" : String(n));
const ease = (x) => 1 - (1 - clamp(x)) ** 2;
const LOOP = 12, DRAW = [0.6, 8.0];
export default function create(p = {}) {
const P = meta.params;
const total = Math.max(10, Math.round(p.stars ?? P.stars.default));
const repo = String(p.repo ?? P.repo.default);
const g = new Grid(meta.cols, meta.rows), W = meta.cols, H = meta.rows;
const X0 = 9, X1 = W - 3, Y0 = 4, Y1 = H - 5; // plot box
const N = X1 - X0 - 1; // one column a week
// a believable history: a slow start, a launch spike, steady growth, a late burst
const raw = [];
let acc = 0;
for (let i = 0; i < N; i++) {
const u = i / (N - 1);
const rate = 0.25 + (u > 0.32 && u < 0.38 ? 5 : 0) + u * 1.6 + (u > 0.85 ? 3 : 0) + Math.sin(i * 1.7) * 0.15;
acc += Math.max(0.05, rate);
raw.push(acc);
}
const series = raw.map((v) => Math.round((v / acc) * total));
// milestones worth a flag: round numbers under the total
const marks = [10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000, 250000, 500000].filter((m) => m <= total && m >= total / 20).slice(-3);
const yOf = (v) => Y1 - 1 - (v / total) * (Y1 - Y0 - 2);
return (t) => {
g.clear();
const ph = ((t % LOOP) + LOOP) % LOOP;
const shown = Math.floor(ease((ph - DRAW[0]) / (DRAW[1] - DRAW[0])) * N);
// title row
const title = repo.length > W - 22 ? repo.slice(0, W - 24) + ".." : repo;
g.text(2, 1, "*", "3"); g.text(4, 1, title, "0");
const now = shown > 0 ? series[Math.min(N - 1, shown - 1)] : 0;
const cnt = fmt(now) + " stars";
g.text(W - 2 - cnt.length, 1, cnt, "3");
// axes and grid
for (let k = 0; k <= 4; k++) {
const y = Math.round(yOf(total * (1 - k / 4)));
g.hline(X0 + 1, X1 - 1, y, "┄", "7");
const lab = fmt(Math.round(total * (1 - k / 4)));
g.text(X0 - 1 - lab.length, y, lab, "1");
}
g.vline(X0, Y0 - 1, Y1 - 1, "│", "2"); g.hline(X0, X1 - 1, Y1, "─", "2"); g.set(X0, Y1, "└", "2");
["2y ago", "1y ago", "now"].forEach((s, i) => {
const x = Math.round(X0 + 1 + (N - 1) * (i / 2)) - (i === 2 ? s.length - 1 : i === 1 ? 3 : 0);
g.text(x, Y1 + 1, s, "1");
});
// the line, filled underneath with a soft shade
for (let i = 0; i < shown; i++) {
const x = X0 + 1 + i, y = yOf(series[i]), yi = Math.round(y);
for (let yy = yi + 1; yy < Y1; yy++) g.set(x, yy, yy > Y1 - 3 ? "░" : "·", "4");
const prev = i ? Math.round(yOf(series[i - 1])) : yi;
for (let yy = Math.min(prev, yi); yy <= Math.max(prev, yi); yy++) g.set(x, yy, yy === yi ? "•" : "│", "3");
}
// milestone flags as the line passes them
for (const m of marks) {
const i = series.findIndex((v) => v >= m);
if (i < 0 || i >= shown) continue;
const x = X0 + 1 + i, y = Math.round(yOf(series[i]));
// label up and to the left of the point, joined by a short leader
const lab = `${fmt(m)}`, ly = Math.max(Y0, y - 2), lx = clamp(x - lab.length - 1, X0 + 2, X1 - lab.length - 2);
g.text(lx, ly, lab, "8");
if (y - 1 > ly) g.set(lx + lab.length, y - 1, "╲", "8");
}
// the head of the line, and a caption once it lands
if (shown > 0 && shown < N) g.set(X0 + shown, Math.round(yOf(series[shown - 1])), "●", "5");
if (ph > DRAW[1] + 0.3) {
const msg = ` ${fmt(total)} stars, thank you `;
g.text(Math.round((W - msg.length) / 2), H - 2, msg, "5");
} else g.text(2, H - 2, "star history, weekly", "1");
return g.frame();
};
}
/YOUR OWN
The pieces here are hand-built animations. To turn your own photo, logo or video into animated ASCII, use the ASCII Magic editor.