# glyphdust > Scroll/agent-driven **text → particles → glyph → real-text resolve** for the web (WebGL/three.js). > You animate everything from a single progress `0 → 1`. Two entry points: a **React-free one call > `glyphText()`**, or a **``** react-three-fiber component. MIT. npm package: `glyphdust`. Designed for AI agents / codegen: minimal required config, safe defaults, and one `progress 0→1` you can drive from anything — scroll, a timer, an AI agent, audio, sensor data. Copy an example below and change the text; it works as-is. Current version: 0.8.4. ## Install - **npm / bundler (vanilla `glyphText`)**: `npm i glyphdust three` - **npm / React (``)**: `npm i glyphdust three @react-three/fiber react react-dom` (`three`, `@react-three/fiber`, `react`, `react-dom` are peerDependencies.) - **CDN, zero install (vanilla only)**: one script tag exposes a global `glyphdust` with `three` bundled in — no `npm install`, no bundler: `` Pin a version (`@0.8.4`) in production. ## `glyphText(target, text, options?) → handle` (React-free, one call) Creates a `` inside `target`, boots three.js, and animates particles into `text`. - `target`: CSS selector or `HTMLElement`. The canvas fills it — give the box a size. - `text`: word/phrase; `\n` = line break. Drives the auto sequence + the reduced-motion fallback. - returns a **handle**: `{ canvas, play(), pause(), restart(), setProgress(p /*0..1*/), morphTo(text, opts?) → Promise, scatter(opts?) → Promise, destroy() }`. Minimal — autoplays (scatter → text, then holds), needs only `three`: ```js import { glyphText } from "glyphdust"; glyphText("#hero", "LINNO"); ``` Zero-install via CDN: ```html
``` ### Options (all optional) `preset`: `"default"|"minimal"|"lively"|"glow"` · `style` · `colors` `{ink,accent,accentRatio}` · `count` (desktop 11000 / mobile 5200) · `spread` (1.3) · `duration` seconds (3.6) · `delay` · `loop` · `pingpong` · `playOnView` (true) · `autoplay` (true) · `resolveToDom` (false) · `maxDpr` (1.75) · `cameraZ` (7) · `cameraFov` (42) · `keyframes` (override the sequence) · `fallback` (true) · `morphDuration` (1.6, default seconds per `morphTo`/`scatter`) · `resolve` (true — the initial text also condenses into real crisp DOM text at the end, same finish as `morphTo`; auto-disabled for custom `keyframes`/`loop`/`pingpong`/multi-line). ### Streaming — say new words at runtime (`morphTo` / `scatter`) For agents that decide their words on the fly. `morphTo(text)` re-converges the particles from wherever they are **right now** into the new text — same instance, same canvas, same WebGL context; never destroy/re-create per word. Each morph finishes by cross-fading into **real crisp DOM text** automatically (pass `{resolve:false}` to keep the particle finish). ```js const h = glyphdust.glyphText("#hero", "HELLO"); await h.morphTo("THINKING…"); // true when settled await h.morphTo("答えは 42"); await h.scatter(); // no words → melt into a cloud // Streaming without waiting — latest wins; superseded promises resolve false: h.morphTo("こん"); h.morphTo("こんにち"); h.morphTo("こんにちは"); ``` - Interrupting mid-morph retargets from current particle positions (no jump). - Promise: `true` = settled, `false` = superseded or destroyed. - Per-call opts: `{ duration, resolve, font, dense, worldW, offsetX, offsetY }`. - Long text auto-shrinks to fit; multi-line (`\n`) keeps the particle finish (no DOM resolve). - Reduced-motion / no-WebGL: `morphTo` updates the static fallback text (stays accessible). ### Drive progress yourself — scroll / agent / timeline (`autoplay:false` + `setProgress`) ```js const h = glyphText("#hero", "LINNO", { autoplay: false }); addEventListener("scroll", () => { const p = scrollY / (document.body.scrollHeight - innerHeight); h.setProgress(p); // 0 → start, 1 → end. Call every frame from any source. }); // An AI agent / audio / timeline can call h.setProgress(x) the same way. ``` ### Resolve into real, selectable DOM text (`resolveToDom:true` + keyframe `domSelector`) Put the real text in the DOM. Particles are sampled from those elements (pixel-aligned) and cross-fade into the real text at the ends — the finale is actual, selectable, accessible text. In the **vanilla** API, `resolveToDom` is a **top-level option** and each text keyframe carries a `domSelector`. The DOM elements may be flex-centered/padded — alignment uses the text's *actual painted box*, so it won't drift. ```html
LINNO
WORLD
``` ```js const h = glyphText("#hero", "LINNO / WORLD", { autoplay: false, resolveToDom: true, keyframes: [ { type: "text", text: "LINNO", domSelector: "#a" }, { type: "scatter", spread: 0.4 }, { type: "text", text: "WORLD", domSelector: "#b" }, ], }); addEventListener("scroll", () => h.setProgress(scrollY / (document.body.scrollHeight - innerHeight))); ``` ## `` — React (react-three-fiber) component For React apps. Render it in your layout and pass `keyframes`. Scroll-driven by default. Here `resolveToDom` is a **per-keyframe** flag (on the text keyframe), alongside `domSelector`. ```tsx import { GlyphDust } from "glyphdust"; ``` `resolveToDom` works on **any** text keyframe with a `domSelector`, not just the last one: while the journey stays on that keyframe, particles melt into the real DOM text (crossfade + blur→focus), and melt back into particles when the journey moves on. Consecutive keyframes with the same `domSelector` (form → hold) count as one stop. The first stop starts as real text (visible from progress 0); the final stop stays as real text. Do NOT drive those elements' opacity yourself — the library owns it. ## Keyframe types - `{ type: "text", text, dense?, domSelector?, resolveToDom?, font?, segments?, worldW?, offsetX?, offsetY? }` - `{ type: "scatter", spread? }` Progress `0→1` auto-distributes across keyframes; a final text keyframe forms by ~0.85 and holds. ## Gotchas - **three.js is required** — peer dependency for the npm builds; bundled into the CDN build. - **CDN build is vanilla-only** — the `