Claude-code-minoan pretext
Create text effects impossible with CSS alone — kinetic typography, flowing text around animated obstacles, calligrams, shrinkwrap chat bubbles, typographic ASCII art, glyph path animation, variable font waves, glyph morphing, illuminated manuscripts. Single-file HTML output, zero build step. Powered by @chenglou/pretext + opentype.js. Triggers on text effects, kinetic typography, text animation, calligram, glyph art, typography art.
git clone https://github.com/tdimino/claude-code-minoan
T=$(mktemp -d) && git clone --depth=1 https://github.com/tdimino/claude-code-minoan "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/design-media/pretext" ~/.claude/skills/tdimino-claude-code-minoan-pretext && rm -rf "$T"
skills/design-media/pretext/SKILL.mdPretext Text Effects
Generate browser pages powered by Pretext (
@chenglou/pretext)—a pure-arithmetic text measurement library that bypasses DOM layout reflow entirely. Pretext measures text with proportional font precision using canvas measureText, enabling effects impossible with CSS alone.
Companion library: opentype.js (
opentype.js@1.3.4) — direct font binary parsing for per-glyph SVG path rendering, Bezier control point access, kerning tables, and variable font axes. Pretext handles line breaking; opentype.js handles what happens inside each glyph. Together they unlock effects neither can achieve alone.
Output is always a single self-contained HTML file. No build step, no framework, runs in any modern browser.
Quick Start
Describe a text effect. Claude picks the right Pretext pattern and generates a complete HTML file.
Examples:
/pretext fluid smoke ASCII art with gold characters on black/pretext chat bubbles that shrinkwrap tighter than CSS/pretext calligram — the word "ocean" shaped like a wave/pretext editorial layout with text flowing around draggable circles/pretext masonry grid of shower thoughts with instant height prediction/pretext glyph path art — SVG letterforms with stroke animation/pretext animated dragon cursor that pushes text aside as it moves/pretext illuminated manuscript with live vine growth reflow/pretext variable font wave — per-character weight animation/pretext glyph morph — letterform interpolation from A to Z/pretext letterbox gallery of "BEACON" in Playfair Display italic on dark background/pretext glyph-mask calligram — letter R filled with lorem ipsum in Georgia
Do NOT Use This Skill When
- Simple CSS
,text-shadow
, or gradient text effectstext-stroke - CSS Shapes level 1 (
on floated elements) for static layoutsshape-outside - Basic
text animation (fade, slide, typewriter)@keyframes - Monospace ASCII art (no proportional measurement needed)
- SVG
without per-glyph control<text> - Rich text editing (
, ProseMirror, TipTap)contenteditable - PDF generation or markdown rendering
These are all achievable without Pretext or opentype.js.
Effect Categories
Pretext-Only Effects
| Category | Pretext APIs Used | When to Use |
|---|---|---|
| Height Prediction | + | Accordion, masonry, virtualized lists — anywhere you need text height without DOM reads |
| Shrinkwrap | + binary search | Chat bubbles, tooltips, labels — finding the exact tightest width for multiline text |
| Obstacle Routing | (variable width) | Text flowing around images, logos, draggable orbs — editorial layouts |
| Animated Obstacles | + | Moving creatures/orbs that displace text at 60fps — slot-carving fills BOTH sides of obstacle |
| Typographic ASCII | (char measurement) | Fluid simulations, 3D wireframes, particle systems rendered as proportional characters |
| Calligrams | + SDF | Words rendered as shapes using their own letters — hearts, stars, spirals |
| Glyph-Mask Calligrams | Canvas + pixel mask | Any font glyph as calligram shape — fill a large letter with small text using pixel-mask technique (no SDF needed, no opentype.js) |
| Letterbox Gallery | Glyph-mask + per-letter grid | Each character in a string gets its own canvas with text fill, cursor displacement, and independent interaction |
| Multi-column Editorial | All rich APIs combined | Magazine-style layouts with headline fitting, pull quotes, drop caps, column flow |
Pretext + opentype.js Effects
| Category | APIs Used | When to Use |
|---|---|---|
| Glyph Path Art | opentype | SVG letterforms with fill/stroke/control point modes, stroke-draw animation |
| Text on Path | opentype + arc-length sampling | Per-glyph placement along Bezier curves with tangent rotation |
| Variable Font Animation | opentype + CSS | Per-character weight/width waves, breathe, ripple, cascade effects |
| Glyph Morphing | opentype paths + flubber | Letterform interpolation between glyphs with contour-aware morphing |
| Outline Calligrams | Pretext + opentype glyph mask | Text fills the interior of a large glyph's actual contour (not SDF approximation) |
| Illuminated Manuscript | All Pretext + opentype combined | Living medieval pages: wet ink, vine reflow, capital inflation, aging, erasure poetry |
Concept-to-Effect
Every design decision derives from the concept. Do not default—derive.
Choose the API tier from the effect complexity:
- Static height only →
+prepare()
(fastest, opaque)layout() - Need line text/positions →
+prepareWithSegments()layoutWithLines() - Need per-line width variation →
(iterator, variable width per line)layoutNextLine() - Need aggregate geometry without strings →
(no string materialization)walkLineRanges() - Need individual character widths →
on single charsprepareWithSegments()
Choose the rendering target from the output type:
- DOM elements (
) → editorial, accordion, masonry.line { position: absolute } - Canvas 2D (
) → calligrams, some ASCII artctx.fillText - HTML spans with inline styles → typographic ASCII (weight/style/opacity per character)
Architecture
Pretext CDN → prepare/prepareWithSegments → layout/layoutNextLine → line positions ↓ opentype.js CDN → font.parse(buffer) → glyph.getPath() ──────────> SVG <path> elements ↓ requestAnimationFrame (if animated) resize handler (always)
Two-library split: Pretext decides WHERE text goes (line breaking, obstacle routing). opentype.js decides HOW each glyph looks (SVG paths, Bezier curves, contour data). Use Pretext alone for DOM-positioned text. Add opentype.js when you need per-glyph SVG rendering, path animation, or glyph contour access.
CDN Imports
<!-- Pretext (ESM, required) --> <script type="module"> import { prepare, layout, prepareWithSegments, layoutWithLines, walkLineRanges, layoutNextLine, clearCache } from 'https://esm.sh/@chenglou/pretext@0.0.2' </script> <!-- opentype.js (UMD, optional — only for glyph path effects) --> <script src="https://cdn.jsdelivr.net/npm/opentype.js@1.3.4/dist/opentype.min.js"></script> <!-- Font binary for opentype.js (Inter .woff — confirmed working) --> <!-- const FONT_URL = 'https://cdn.jsdelivr.net/npm/@fontsource/inter@5.0.8/files/inter-latin-400-normal.woff' --> <!-- flubber (optional — only for glyph morphing) --> <!-- <script src="https://cdn.jsdelivr.net/npm/flubber@0.4.2/build/flubber.min.js"></script> -->
Import only the functions you need. Pin the version. opentype.js cannot parse
.woff2 — use .woff or .ttf only.
Composition Parameters
All Effects
| Parameter | Default | Notes |
|---|---|---|
| Font | | Never use — unreliable with Pretext |
| Line height | (px) | Must match CSS for height prediction |
| Background | or | Dark or light — never pure black |
Typographic ASCII
| Parameter | Range | Default |
|---|---|---|
| Font size | 10–18px | 14 |
| Font family | serif preferred | |
| Charset | printable ASCII | |
| Weights | 1–3 | |
| Styles | normal, italic | both |
| Opacity levels | 6–10 | 10 CSS classes – |
Calligrams
| Parameter | Range | Default |
|---|---|---|
| Canvas size | 200–800px | 400 |
| Char density | 6–24px | 14 |
| Shapes | heart, circle, star, wave, spiral | heart |
| Animation | spring entrance | |
Editorial / Obstacle Routing
| Parameter | Range | Default |
|---|---|---|
| Columns | 1–4 | 2 |
| Column gap | 20–60px | 40 |
| Gutter | 30–80px | 48 |
| Orb count | 1–6 | 5 |
| Orb radius | 30–120px | 65–110 |
| hPad (horizontal padding around obstacle) | 8–20px | 14 |
| vPad (vertical padding) | 2–8px | 4 |
| MIN_SLOT_WIDTH | 30–80px | 50 |
opentype.js — Glyph Path Effects
| Parameter | Range | Default |
|---|---|---|
| Font URL | or only | |
| Font size | 10–200px | 14 (body), 42 (display) |
| Stroke-dashoffset draw speed | 30–150ms per glyph | 80ms |
| Wet ink amplitude | 0.5–3px | 1.8 |
| Wet ink decay (tau) | 800–3000ms | 1500 |
| Glyph morph easing | linear, ease-in-out, spring | ease-in-out |
Glyph-Mask Calligrams / Letterbox Gallery
| Parameter | Range | Default |
|---|---|---|
| Canvas size (per letter) | 200–1000px | 500 |
| Fill font size | 8–24px | 11 |
| Fill model | , , , | |
| Fill case | , , | |
| Fill columns | 1–4 | 1 |
| Grid columns (gallery) | 2–6 | 3 |
| Cursor displacement radius | 50–250px | 100 |
| Cursor displacement force | 10–50 | 35 |
| Displacement damping | 0.85–0.98 | 0.94 |
opentype.js + flubber — Glyph Morphing
| Parameter | Range | Default |
|---|---|---|
| Morph duration | 300–2000ms | 800 |
| Flubber maxSegmentLength | 5–20 | 10 |
| Contour strategy | equal, 1-to-many, many-to-1 | auto-detect |
References
| Working on... | Load |
|---|---|
| Full API surface, types, caveats | |
| Typographic palette, brightness, ASCII grid | |
| Column flow, obstacles, headline fitting, slot-carving, animated physics | |
| SDF shapes, proportional spacing, animation | |
| Pixel-mask technique, glyph-mask calligrams, cursor displacement, letterbox gallery | (Pixel-Mask section) |
| opentype.js + Pretext integration patterns | |
| Working accordion template | |
| Working bubbles template | |
| Working editorial template | |
| Working ASCII art template | |
| Working calligram template | |
| Working letterbox gallery template | |
| GlyphKit demos (6 working demos, local) | (machine-specific) |
Anti-Patterns
Pretext
- Never use
as font — canvas and DOM can resolve different fonts on macOSsystem-ui - Never use React/Vue/framework — vanilla JS + HTML only
- Never omit
— proportional measurement depends on correct device pixels<meta name="viewport"> - Never use
for animation — alwayssetIntervalrequestAnimationFrame - Never skip the resize handler — text layout depends on container width
- Never call
inside the animation loop — it's the expensive one-time pass. Cache it.prepare() - Never omit
— measure after fonts load, not beforedocument.fonts.ready.then(...) - Never use pure black (
) — use rich off-blacks (#000000
,#06060a
,#08080e
)#0a0a0c - Never build line strings when you only need geometry — use
instead ofwalkLineRangeslayoutWithLines - Never position text with CSS flow — use
and place lines from Pretext coordinatesposition: absolute - Never pick one side of an obstacle when the obstacle is in the middle — use
to fill both sidescarveTextLineSlots - Never create/destroy DOM elements per frame — use element pooling (
)syncPool
opentype.js
- Never use
files — opentype.js cannot parse them. Use.woff2
or.woff
only.ttf - Never use
— useopentype.load()
for better error handlingfetch().then(r => r.arrayBuffer()).then(opentype.parse) - Never render glyphs at absolute positions then try to move them — render at
and position via(0,0)<g transform="translate(x,y)"> - Never forget
— the scale factor isfont.unitsPerEmfontSize / font.unitsPerEm - Never call
inside a tight loop without caching — cache the pathData string, regenerate only when position changesglyph.getPath() - Never skip kerning — always check
between adjacent glyphsfont.getKerningValue(glyph, nextGlyph) * scale - Never use Google Fonts TTF CDN URLs (they return 404 for programmatic access) — use
via jsdelivr@fontsource
Post-Generation QA
python3 ~/.claude/skills/pretext/scripts/validate_pretext.py output.html
Checklist:
- ESM import from
presentesm.sh/@chenglou/pretext
tag<script type="module">- Named font declaration (no
)system-ui
present<meta name="viewport">- Window resize handler
awaited before measurementdocument.fonts.ready
loop (for animated effects)requestAnimationFrame- Touch event handlers (for interactive effects)
- No
for animationsetInterval - No framework imports