Skip to content
Development
Skill

/skeleton-dither

Use when building a loading skeleton or a content/image reveal that should feel ASCII or dithered - a shimmering glyph skeleton (░▒▓█) that decodes into the real text and images, for cards, profiles, dashboards and heroes. Based on ascii-reveal by Quan Hoang (MIT).

BOOST
From plugin
julianromli-ai-skills
20830 skills
Install
$ npx -y skills add julianromli/ai-skills --skill skeleton-dither --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/skeleton-dither

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when building a loading skeleton or a content/image reveal that should feel ASCII or dithered - a shimmering glyph skeleton (░▒▓█) that decodes into the real text and images, for cards, profiles, dashboards and heroes. Based on ascii-reveal by Quan Hoang (MIT).

SKILL.md

skeleton-dither.SKILL.md
name: skeleton-dither
description: Use when building a loading skeleton or a content/image reveal that should feel ASCII or dithered - a shimmering glyph skeleton (░▒▓█) that decodes into the real text and images, for cards, profiles, dashboards and heroes. Based on ascii-reveal by Quan Hoang (MIT).

skeleton-dither

An ASCII loading state in two parts. First, a **dither skeleton**: shaded glyph bars (`░▒▓█`) with a denser band sweeping across them. Then a **decode reveal**: when data lands, every character scrambles into place, and images resolve from coarse glyph cells into real pixels. Both move as one radial wave.

Source: [ascii-reveal](https://quanhoangindex.github.io/ascii-reveal/) by Quan Hoang, from the repo [quanhoangindex/ascii-reveal](https://github.com/quanhoangindex/ascii-reveal) (v0.3.0). It's **MIT licensed**, so copying or adapting it is allowed if you keep the copyright notice. You can also install the original with `npm i github:quanhoangindex/ascii-reveal`; it includes `ascii-reveal/core` for non-React use. The reference file `skeleton-dither.js` in this skill's folder is a clean, smaller rewrite. It credits the original in its header. The folder also has `demo/` (working demo plus `verify.py`) and `shots/` (demo vs original screenshots).

When to use

  • Skeleton or loading states for cards, profiles, dashboards and stats that should feel crafted, not gray.
  • Revealing a hero, image or card on mount, on route change, or when it scrolls into view.
  • Brands that are techy, editorial or terminal-like. Skip it for dense forms, for anything that needs a calm feel (medical, finance errors), and for content that has to be readable at 0 ms.

How it works

1. **Skeleton: pure HTML and CSS, painted on the first frame, no JS at runtime.**

  • Each line is 3 stacked `white-space:pre` spans:
  • a faint **base** row (opacity .22),
  • two **wave** rows that use denser glyphs and alternate every 120 ms (`steps(1)` visibility flip), so the glyphs inside the band look like they scramble.
  • A `mask-image` gradient band (55% wide) sweeps from `-120%` to `220%` over 1.8 s with `cubic-bezier(.45,.05,.55,.95)`.
  • Each line is delayed 0.09 s more than the one above, so the sweep reads as one diagonal band.
  • Glyph rows come from a deterministic integer hash of (index, seed), so SSR and client output match.

2. **Reveal: one `<canvas>` overlay above the real DOM.**

  • The real content stays in the DOM at its real position. Nothing shifts, and screen readers read it the whole time.
  • Text is hidden with `-webkit-text-fill-color:transparent`, so `currentColor` icons stay visible.
  • Images, and fades marked with `[data-sd="fade"]`, are hidden through `opacity: var(--sd-o)`.
  • **The wave:**
  • Each target starts at `offset = min(distance(target centre, origin) / 3.2 px/ms, 220 ms)`.
  • The default origin is the centre. It can also be `top-left` or `{x,y}` in viewport px; pass `{x:e.clientX,y:e.clientY}` to start from the pointer.
  • **Text:**
  • Each grapheme is measured with a `Range`.
  • Character k starts at `s0 = k/n·spread·0.72 + rand·spread·0.28`, with `spread = min(200, 40 + 4n)` ms.
  • It scrambles for `90 + rand·110` ms.
  • For the first 70% of that time it **condenses through the ramp** (light to dense, ±1 jitter 35% of the time). After that it picks random glyphs from the scramble set. Glyphs change every `tick` ms and are drawn in the mono font at 50% alpha.
  • Then it **locks**: the real character is drawn in the element's own font. When everything has landed, the overlay is removed and the DOM text is unhidden.
  • Numeric text scrambles through digits only.
  • **Images (450 ms):**
  • The pixels are drawn to an offscreen canvas.
  • The image is split into grids of mono cells (cell width = 0.62 × height), stepping coarse to fine: `[24,16,11,8,6]` px for decode and `[20,14,10,7,5]` for dither. Cells scale down on images smaller than 230 px.
  • Each cell takes the strongest of 5 samples. Its ink is `|lum − backdropLum| / max(bgLum, 1−bgLum)`, and that picks the glyph from the ramp.
  • Each cell appears when `gate < q·2.3`, with `gate = 0.5·noise + 0.5·distance from origin`. This gives an organic radial fill.
  • On each tick, 16% of the cells flicker by ±0.175 ink.
  • The real image cross-fades in from q = 0.68 (easeInOutCubic) while the glyphs fade out from q = 0.74.
  • **Fades (220 ms):** easeOutCubic opacity on the wave.

Tuned parameters (from the original)

| Preset | ramp | scramble | image color | cells | tick | |---|---|---|---|---|---| | decode | ` .:-=+*#%@` | `A–Z 0–9 #%&*+=?/` | text color | 24,16,11,8,6 | 60 ms | | dither | ` ░▒▓█` | `░▒▓█` | source pixels | 20,14,10,7,5 | 85 ms | | terminal | ` .-:=+*#` | `01<>/\{}[]\|_=+-;:` | text color, `█` cursor leads | 22,15,10,7,6 | 40 ms | | blueprint | ` ·-+=x#` | `+-/\|x·` | accent | 26,18,13,9,7 | 95 ms |

| Timing | vel | maxWave | spread | per char | media | fade | |---|---|---|---|---|---|---| | fast (data, readable ~400 ms) | 3.2 px/ms | 220 ms | min(200, 40+4n) | 90+110·r | 450 | 220 | | cinematic (hero) | 1.15 px/ms | ∞ | min(720, 90+13n) | 200+240·r | 1150 | 420 |

Skeleton variants:

| Variant | base | wave | base opacity | wave opacity | band | notes | |---|---|---|---|---|---|---| | **dither** | `░░░░▒` | `▒▓▓█▓▒` | .22 | .75 | 55% | `line-height:1`, `letter-spacing:-.02em` | | scan | `··:·:-` | `+=*#%@&$` | .28 | .95 | 38% | 8% gaps | | braille | `⠢⠔⠑…` | `⣿⣷⣯…` | .35 | 1 | 40% | | | terminal | `01` | `01` | .16 | 1 | 35% | 1.5 s sweep, accent color |

Rows are 160 glyphs long and clipped by `overflow:hidden`. Block skeletons (images, avatars) get `rows` lines and a 6% `currentColor` tint background.

Fonts: Geist for UI and Geist Mono for glyphs (`--sd-font`). The demo page background is `#f6f6f7` and the text is `#141416`.

Implementation steps

1. Copy `skeleton-dither.js` from this skill's folder (about 300 l

Read more
Ships withjulianromli-ai-skills

A curated collection of AI agent skills for coding, frontend, debugging, product thinking, and workflow automation.

Get the whole plugin

Other skills on julianromli-ai-skills.