glyphdust 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LINNO / NOGUCHILin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,180 @@
1
+ # glyphdust
2
+
3
+ > Scroll-driven **text → particles → glyph → real-text resolve**, in one declarative [react-three-fiber](https://r3f.docs.pmnd.rs/) component.
4
+
5
+ Pass any text and glyphdust dissolves it into thousands of GPU particles, scatters them into a cloud, reforms them into the next glyph, and finally **resolves into crisp, real DOM text** — all driven by a single scroll progress `0 → 1`.
6
+
7
+ <p align="center">
8
+ <img src="https://raw.githubusercontent.com/linno-inc/glyphdust/main/docs/demo.gif" alt="glyphdust: text dissolving into particles and resolving into real text" width="720" />
9
+ </p>
10
+
11
+ ```tsx
12
+ <GlyphDust
13
+ keyframes={[
14
+ { type: "text", text: "Hello", domSelector: "#headline" },
15
+ { type: "scatter", spread: 1 },
16
+ { type: "text", text: "WORLD", dense: true, resolveToDom: true },
17
+ ]}
18
+ />
19
+ ```
20
+
21
+ That's the whole thing. Scroll, and it animates.
22
+
23
+ ---
24
+
25
+ ## Why glyphdust?
26
+
27
+ Existing tools each cover a slice — but none give you *"text → particles → another glyph → resolve back to selectable real text"* declaratively from one scroll driver.
28
+
29
+ | | particles | text → particles | particle → **another glyph** | resolve to **real DOM text** | scroll-driven, declarative |
30
+ |---|:---:|:---:|:---:|:---:|:---:|
31
+ | tsParticles | ✅ | partial | — | — | — |
32
+ | GSAP + custom | ✅ | manual | manual | manual | manual |
33
+ | three.js shape-morph demos | ✅ | manual | ✅ | — | — |
34
+ | **glyphdust** | ✅ | ✅ | ✅ | ✅ | ✅ |
35
+
36
+ - **Resolves to real text.** The finale isn't a picture of letters — particles cross-fade into actual, selectable, accessible DOM text, pixel-aligned to the glyph they formed (`resolveToDom` + `domSelector`).
37
+ - **One driver, any number of keyframes.** `text → scatter → text → scatter → text …`; timing is auto-distributed (override with `timing`).
38
+ - **Never blanks.** `prefers-reduced-motion` or no-WebGL falls back to your own static markup.
39
+
40
+ ---
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ npm i glyphdust three @react-three/fiber
46
+ # or: pnpm add glyphdust three @react-three/fiber
47
+ ```
48
+
49
+ `three`, `@react-three/fiber`, `react`, and `react-dom` are **peer dependencies** (React 18+, three 0.160+).
50
+
51
+ ---
52
+
53
+ ## Quick start (5 minutes)
54
+
55
+ A scroll hero that reads a real headline, dissolves it into particles, and resolves into a wordmark:
56
+
57
+ ```tsx
58
+ import { GlyphDust } from "glyphdust";
59
+
60
+ export function Hero() {
61
+ return (
62
+ <main>
63
+ <GlyphDust
64
+ keyframes={[
65
+ // 1. Read an existing DOM heading, pixel-aligned (particles overlap it exactly).
66
+ { type: "text", text: "Next user\nisn't human.", domSelector: "#headline" },
67
+ // 2. Scatter into a cloud.
68
+ { type: "scatter", spread: 1 },
69
+ // 3. Reform as a dense wordmark, then cross-fade to real text.
70
+ { type: "text", text: "LINNO", dense: true, resolveToDom: true },
71
+ ]}
72
+ driver={{ type: "scroll", triggerHeight: 2.4 }}
73
+ colors={{ ink: "#1b2330", accent: "#0055ff", accentRatio: 0.18 }}
74
+ fallback={<h1>Next user isn't human.</h1>}
75
+ />
76
+
77
+ {/* The heading the first keyframe aligns to. */}
78
+ <h1 id="headline">Next user isn't human.</h1>
79
+ </main>
80
+ );
81
+ }
82
+ ```
83
+
84
+ - The `<canvas>` is a `position: fixed` full-viewport layer; `triggerHeight` (×100vh) controls how much scroll the animation spans.
85
+ - `domSelector` makes particles land exactly on an existing element's box and font — no jump on cross-fade.
86
+ - `resolveToDom` on the final keyframe hands off from particles to crisp real text.
87
+
88
+ ### Drive it yourself (no scroll)
89
+
90
+ ```tsx
91
+ const [p, setP] = useState(0); // 0 → 1 from time, GSAP, a slider, anything
92
+
93
+ <GlyphDust
94
+ keyframes={[/* … */]}
95
+ driver={{ type: "manual", progress: p }}
96
+ />
97
+ ```
98
+
99
+ ---
100
+
101
+ ## API
102
+
103
+ ### `<GlyphDust>` props
104
+
105
+ | prop | type | default | description |
106
+ |---|---|---|---|
107
+ | `keyframes` | `Keyframe[]` | — (required) | The animation timeline. Minimum 1; typically `text → scatter → text`. |
108
+ | `driver` | `DriverConfig` | `{ type: "scroll" }` | Progress source: `scroll` or `manual`. |
109
+ | `colors` | `GlyphColors` | see below | Particle ink / accent colors. |
110
+ | `count` | `GlyphCount` | `{ desktop: 11000, mobile: 5200 }` | Particle count per device class. |
111
+ | `timing` | `number[]` | even spacing | Normalized time `0..1` per keyframe (interpolation boundaries). Length must match `keyframes`. |
112
+ | `interaction` | `GlyphInteraction` | `{ pointer: true, drag: true }` | Pointer repulsion / drag-to-rotate (with inertia). |
113
+ | `camera` | `GlyphCamera` | `{ z: 7, fov: 42 }` | Camera position / vertical FOV. |
114
+ | `dpr` | `[number, number]` | `[1, 1.75]` | r3f Canvas device-pixel-ratio range. |
115
+ | `fallback` | `ReactNode` | — | Rendered on reduced-motion / no-WebGL (prevents a blank screen). |
116
+ | `className` | `string` | — | Class on the wrapper element. |
117
+
118
+ ### Keyframes
119
+
120
+ ```ts
121
+ type Keyframe = TextKeyframe | ScatterKeyframe;
122
+ ```
123
+
124
+ **`TextKeyframe`** (`type: "text"`) — turns text into a particle glyph:
125
+
126
+ | field | type | description |
127
+ |---|---|---|
128
+ | `text` | `string` | Text to render. Use `\n` for line breaks. |
129
+ | `domSelector` | `string?` | Selector of a real element; particles align pixel-perfect to its rect & font. |
130
+ | `resolveToDom` | `boolean?` | At the finale, cross-fade particles → real DOM text (usually the last keyframe). |
131
+ | `dense` | `boolean?` | High-density, uniform sampling (best for solid wordmarks). |
132
+ | `font` | `string?` | Canvas2D `font` string. Defaults to a density-appropriate value. |
133
+ | `worldW` | `number?` | Visible world width to fit the glyph into. |
134
+ | `offsetX` / `offsetY` | `number?` | World-space offset (right / up are positive). |
135
+
136
+ **`ScatterKeyframe`** (`type: "scatter"`) — scatters particles into a random cloud:
137
+
138
+ | field | type | description |
139
+ |---|---|---|
140
+ | `spread` | `number?` | Scatter-radius multiplier. Default `1`. |
141
+
142
+ ### Drivers
143
+
144
+ ```ts
145
+ { type: "scroll", triggerHeight?: number } // default triggerHeight: 2 (×100vh)
146
+ { type: "manual", progress: number } // you supply 0..1
147
+ ```
148
+
149
+ ### Colors
150
+
151
+ ```ts
152
+ { ink?: "#1b2330", accent?: "#0055ff", accentRatio?: 0.18 }
153
+ ```
154
+
155
+ `accentRatio` (`0..1`) is the fraction of particles drawn in `accent`.
156
+
157
+ ### Low-level helpers
158
+
159
+ For custom rigs, the building blocks are exported too: `buildTextTargets`, `buildDenseTextTargets`, `buildVertexShader`, `FRAGMENT_SHADER`, `createScrollProgress`, `useScrollProgress`, `useReducedMotion`, `prefersReducedMotion`, `viewSizeAtZ0`, `buildGlyphFromDOM`, `computeScreenRect`.
160
+
161
+ ---
162
+
163
+ ## Accessibility & resilience
164
+
165
+ - **`prefers-reduced-motion`** → renders `fallback`, no animation.
166
+ - **No WebGL** → renders `fallback`, never a blank canvas.
167
+ - **SSR-safe** — guards `window`; server render yields static markup.
168
+ - **Real text finale** — `resolveToDom` output is selectable, copyable, and screen-reader friendly.
169
+
170
+ ---
171
+
172
+ ## Status
173
+
174
+ `0.1.0` — the component and API above are implemented and demoed (see [`examples/`](./examples)). Published from [LINNO](https://linno.co.jp). Semantic-versioned; expect minor API polish before `1.0`.
175
+
176
+ ## License
177
+
178
+ [MIT](./LICENSE) © [LINNO](https://linno.co.jp) (NOGUCHILin)
179
+
180
+ > glyphdust is the first open-source product from **LINNO** — built in the open, for the joy of the challenge.