Skip to main content

ascii-motion

Make original animated ASCII art as a self-contained web page, in the ascii.rest style — halftone colour scenes, shaded mono 3D/fields, line-drawn sprites, particle sims, type/UI. Use for ASCII art, character art, halftone or dot-matrix animation, text-mode animation, or "make something like ascii.rest". Requests may come in any language. Always ask which style first.

ソース情報

リポジトリ
zgyk4167/ascii-motion
ソースの最終更新活動
2026年10月8日 07:59
検出された SKILL.md の言語
英語
スター
0
フォーク
0

ascii-motion: create animated character art

Make original ASCII animation on a character grid and package it as a self-contained HTML page, using color halftone scenes, shaded monochrome forms, line sprites or particles.

Examples

The creator's release post includes a scene preview with colorful flowers rendered as character dots.

Uses

Choose a visual family, subject and mood before building. The documented delivery options include a standalone page and a snippet for an existing site.

Prerequisites

An agent that can write files and run Python 3. A headless Chromium browser is optional for the verification step.

How to use

Supply the style, subject, mood and preferred delivery. The workflow designs the lighting and motion, writes a new scene for the supplied runtime, then bundles the runtime and scene into one HTML file.

Limitations

The technique and portions of the runtime are adapted from ascii.rest under MIT. Scenes should be newly composed rather than copied from its reference pieces. The documented verification checks rendering and motion; this page does not certify a generated animation.

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
ascii-motion
description
Make original animated ASCII art as a self-contained web page, in the ascii.rest style — halftone colour scenes, shaded mono 3D/fields, line-drawn sprites, particle sims, type/UI. Use for ASCII art, character art, halftone or dot-matrix animation, text-mode animation, or "make something like ascii.rest". Requests may come in any language. Always ask which style first.
license
MIT (portions adapted from ascii.rest, MIT; see THIRD_PARTY_NOTICES.md)
compatibility
Any agent that can write files and run python3. Headless Chromium (Playwright or Puppeteer) is optional, for the verify step.
metadata
{"version":"1.0.0"}
# ASCII Motion Original, animated ASCII pieces drawn on a character grid, rendered to a `<canvas>` (colour) or `<pre>` (one ink). The craft follows ascii.rest by @bas3line (MIT): every piece is a pure function `frame(t) → text`, the colour is a palette index per cell, and a tiny runtime plays it. You write new pieces with this technique. You do not copy theirs. ## Step 1 — Ask before building (mandatory) Before writing any code, ask the user these questions, in the user's language, all in one turn. If the agent has a question tool (for example AskUserQuestion in Claude Code), use it. Otherwise, ask in chat as a short numbered list with the options, and wait for the reply. Skip a question only if the user's message already answers it. **Q1 Style (header "Style"):** offer the four main families. Mention in the question text that type/UI/data and logo glint are also available (through "Other" in a question tool, or as extra choices in chat). | Option | Look | Reference pieces on ascii.rest | |---|---|---| | Halftone scene | full-colour landscape in dots ` ·•●`, 200×100, cinematic light | night-coast, ocean-sunset, kyoto-dusk, deep-reef | | Shaded mono | one ink, brightness ramp `.,-~:;=!*#$@`, lit 3D forms or maths fields | donut, plasma, black-hole, mandelbrot | | Line sprite | hand-drawn characters `/\_()'` with timed poses: creatures, objects | owl, cat, whale, train, windmill | | Particles & sim | many small marks moving by simple physics | fireworks, rain, falling-sand, flow-field | **Q2 Subject & mood (header "Subject"):** offer three or four concrete subjects that fit the chosen style. If the style is still unknown, offer neutral ones: night sea, mountain dawn, a creature, an abstract form. Each option description names the light source and the one motion, e.g. "moonlight; slow swell + drifting cloud". **Q3 Delivery (header "Delivery"):** a standalone HTML file (default), a published Artifact page (only where the agent can publish one), or a drop-in `<script>`+element snippet for the user's own site. If the session is unattended, choose the style that best fits the request, state it in one line, and proceed. ## Step 2 — Design on paper (one short paragraph, internal) Decide these before coding. They are what taste comes from: 1. **One light story.** Name the key light (moon, sun, lamp, lantern) and where it sits. Everything else is lit *from* it: halos, rim light on the facing edge, reflections, a road of glints on water. 2. **Two or three silhouettes** big enough to read as a thumbnail. Silhouettes are *absence* of dots (low `floor`), not dark dots. 3. **One primary motion, one or two secondary ones.** Examples: a beam turns, clouds drift, water swells. Never animate everything. 4. **A loop.** Make periods whole divisors of one loop (e.g. 8 s, 12 s, 30 s) so nothing jumps. 5. **The palette:** 25–46 hand-picked hexes in ramps (sky blues dark→light, warm lamp 3–4 steps, land greens/greys, one accent). Ground colour ≈ the darkest sky, not pure black. ## Step 3 — Build with the kit Copy `scripts/ascii-kit.js` from this skill (the same code is below; write it out verbatim if the file is not reachable) and write one piece file into the working directory, then bundle them into a single self-contained HTML file with `bundle.py`. Keep the piece contract exact; it is also compatible with ascii.rest's own `mount`. **Piece contract** ```js export const meta = { name, note, cols, rows, fps, cell /* 1 = square cells (halftone), 2 = text cells */, palette /* colour pieces only */, ground, options }; export default function make(options) { /* build static layers once */ return (t, { paper, color }) => text; } ``` - `t` is seconds. `paper` is true on a light page: flip ramps so dense ink means dark. `color` is a `Uint8Array(cols*rows)`; write palette indices into it. - Precompute everything static (terrain masks, sprite strings, cloud fields that wrap with `noise(..., period)`) in `make`, outside the frame function. Frames must be cheap: 200×100 at 15 fps. ### ascii-kit.js ```js // ascii-kit: runtime + helpers for animated ASCII pieces. // Part of ascii-motion (MIT License, Copyright (c) 2026 ascii-motion contributors). // mount(), halftone(), hash() and noise() are adapted from ascii.rest by bas3line // (https://github.com/bas3line/ascii), used under this license: // // MIT License // // Copyright (c) 2026 bas3line (https://github.com/bas3line) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Piece contract (compatible with ascii.rest, MIT): // export const meta = { name, note, cols, rows, fps, cell?, palette?, ground?, options? } // export default (options) => (t, { paper, color }) => string // rows joined by "\n" // Coloured pieces write a palette index per cell into color[y*cols+x]. // ---------- math ---------- export const clamp = (v) => (v < 0 ? 0 : v > 1 ? 1 : v); export const smooth = (a, b, v) => { const k = clamp((v - a) / (b - a)); return k * k * (3 - 2 * k); }; export const mix = (a, b, k) => a + (b - a) * k; export const ease = (x) => (x <= 0 ? 0 : x >= 1 ? 1 : x * x * (3 - 2 * x)); export function hash(x, y) { let h = Math.imul(x | 0, 374761393) + Math.imul(y | 0, 668265263); h = Math.imul(h ^ (h >>> 13), 1274126177); return ((h ^ (h >>> 16)) >>> 0) / 4294967296; } // value noise; wraps every `period` lattice cells in x when period > 0 (for endless drift) export function noise(x, y, period = 0) { const xi = Math.floor(x), yi = Math.floor(y), fx = x - xi, fy = y - yi; const u = fx * fx * (3 - 2 * fx), v = fy * fy * (3 - 2 * fy); let x0 = xi, x1 = xi + 1; if (period) { x0 = ((xi % period) + period) % period; x1 = (x0 + 1) % period; } const a = hash(x0, yi), b = hash(x1, yi), c = hash(x0, yi + 1), d = hash(x1, yi + 1); return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v; } export function fbm(x, y, oct = 4, period = 0) { let s = 0, n = 0, amp = 0.5, f = 1; for (let i = 0; i < oct; i++) { s += amp * noise(x * f, y * f, period * f); n += amp; amp *= 0.5; f *= 2; } return s / n; } // ---------- halftone scenes (cell: 1, ~200×100, " ·•●") ---------- const DOTS = " ·•●", COVER = [0, 0.3, 0.6, 1]; const BAYER = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5].map((v) => v / 16 - 0.47); const hex = (s) => [1, 3, 5].map((i) => parseInt(s.slice(i, i + 2), 16) / 255); // shade(s, x, y, t, col, row): set s.r s.g s.b (linear-ish 0..1+), optionally s.floor (min dot level), s.fade. export function halftone(meta, shade) { const W = meta.cols, H = meta.rows, P = meta.palette.map(hex); const lut = new Uint8Array(32768).fill(255); const nearest = (r, g, b) => { const k = (Math.min(31, (r * 31.99) | 0) << 10) | (Math.min(31, (g * 31.99) | 0) << 5) | Math.min(31, (b * 31.99) | 0); if (lut[k] !== 255) return lut[k]; let best = 0, bd = 1e9; for (let i = 0; i < P.length; i++) { const dr = P[i][0] - r, dg = P[i][1] - g, db = P[i][2] - b, d = 0.3 * dr * dr + 0.5 * dg * dg + 0.2 * db * db; if (d < bd) (bd = d), (best = i); } return (lut[k] = best); }; const out = new Array(W), lines = new Array(H), s = { r: 0, g: 0, b: 0, floor: 0.3, fade: 1 }; return (t, { color } = {}) => { for (let r = 0; r < H; r++) { for (let x = 0; x < W; x++) { s.r = s.g = s.b = 0; s.floor = 0.3; s.fade = 1; shade(s, x + 0.5, r + 0.5, t, x, r); // dot size from brightness, dithered; colour makes up what dot size could not const peak = Math.max(s.r, s.g, s.b, 1e-4); const level = clamp(s.floor + (1 - s.floor) * Math.pow(peak, 0.85) * 0.95) * s.fade; const step = Math.max(0, Math.min(3, Math.round(level * 3 + BAYER[(r & 3) * 4 + (x & 3)]))); out[x] = DOTS[step]; if (color) { const want = step ? Math.min(1, (level + 0.06) / COVER[step]) : 0, k = (0.3 + 0.7 * want) / peak; color[r * W + x] = nearest(clamp(s.r * k), clamp(s.g * k), clamp(s.b * k)); } } lines[r] = out.join(""); } return lines.join("\n"); }; } // ---------- mono ramp fields (cell: 2, text cells are ~2× taller than wide) ---------- // field(x, y, t) → 0..1 brightness, or -1 for empty. x,y in cell widths from centre (y already ×2). export function ramp(meta, field, RAMP = ".,-~:;=!*#$@") { const { cols, rows } = meta, N = RAMP.length, out = new Array(cols), lines = new Array(rows); return (t, { paper = false } = {}) => { for (let r = 0; r < rows; r++) { const y = (r + 0.5 - rows / 2) * 2; for (let c = 0; c < cols; c++) { const v = field(c + 0.5 - cols / 2, y, t); if (v < 0) { out[c] = " "; continue; } const i = Math.min(N - 1, Math.floor(clamp(v) * N)); out[c] = RAMP[paper ? N - 1 - i : i]; // light page: dense ink = dark, so flip } lines[r] = out.join(""); } return lines.join("\n"); }; } // ---------- sprites ---------- export const FLIP = { "/": "\\", "\\": "/", "(": ")", ")": "(", "<": ">", ">": "<", "[": "]", "]": "[", "{": "}", "}": "{", "`": "'", "'": "`" }; export const mirror = (half, w = Math.max(...half.map((l) => l.length))) => half.map((l) => { l = l.padEnd(w); return l + [...l.slice(0, w - 1)].reverse().map((c) => FLIP[c] || c).join(""); }); export function grid(cols, rows) { const g = Array.from({ length: rows }, () => new Array(cols).fill(" ")); return { g,
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る