dotmote 0.3.2 → 0.3.3

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.
Files changed (4) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +205 -201
  3. package/README.zh-CN.md +183 -179
  4. package/package.json +86 -77
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Ryan
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.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ryan
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 CHANGED
@@ -1,201 +1,205 @@
1
- # dotmote
2
-
3
- <img src="https://raw.githubusercontent.com/rryanchiu/dotmote/main/docs/example.png" alt="dotmote — LED dot-matrix glow component" width="100%" />
4
-
5
- <p align="center">
6
- <a href="https://www.npmjs.com/package/dotmote"><img alt="npm" src="https://img.shields.io/npm/v/dotmote" /></a>
7
- <img alt="license" src="https://img.shields.io/npm/l/dotmote" />
8
- <img alt="bundle size" src="https://img.shields.io/bundlephobia/minzip/dotmote" />
9
- <a href="https://dotmote.imryan.dev"><img alt="Live demo" src="https://img.shields.io/badge/Live%20demo-dotmote.imryan.dev-2ea44f" /></a>
10
- <a href="https://stackblitz.com/github/rryanchiu/dotmote"><img alt="Open in StackBlitz" src="https://img.shields.io/badge/Open%20in-StackBlitz-1269D3" /></a>
11
- </p>
12
-
13
- <p align="center">[English](https://github.com/rryanchiu/dotmote/blob/main/README.md) · [简体中文](https://github.com/rryanchiu/dotmote/blob/main/README.zh-CN.md)</p>
14
-
15
- > **Live demo** — [dotmote.imryan.dev](https://dotmote.imryan.dev) · **Try it live**: [Open in StackBlitz](https://stackblitz.com/github/rryanchiu/dotmote) to run the playground in your browser. You can also import this GitHub repo into CodeSandbox.
16
-
17
- A framework-agnostic dotted-matrix glow background for React and Vue. Scatter a few letters, emoji, or shapes over an endless grid of dots — they drift, bump into each other, and glow through the lattice like a little light show.
18
-
19
- - **React 18+** · TypeScript · Canvas 2D · **SSR-safe**
20
- - **Zero runtime deps** — just `react` (peer)
21
- - Pure Canvas 2D — no particle or dot-grid libraries
22
-
23
- ## Install
24
-
25
- ```bash
26
- npm install dotmote
27
- ```
28
-
29
- Requires `react >= 18`. Ships ESM + TypeScript types.
30
-
31
- ## Quick start
32
-
33
- ```tsx
34
- import { Dotmote } from 'dotmote';
35
-
36
- export function Page() {
37
- return (
38
- <div style={{ position: 'relative', minHeight: '100vh' }}>
39
- <Dotmote theme="gradient" />
40
-
41
- <main style={{ position: 'relative', zIndex: 1, padding: 24 }}>
42
- <h1>My page</h1>
43
- </main>
44
- </div>
45
- );
46
- }
47
- ```
48
-
49
- The wrapper `<div>` is absolutely positioned behind everything — put your content in a sibling with a higher `z-index`.
50
-
51
- ## Vue
52
-
53
- The Canvas core is framework-agnostic — React and Vue are both thin wrappers over
54
- the same `createDotMatrixCore`. For Vue 3 use the `dotmote/vue` entry:
55
-
56
- ```vue
57
- <script setup>
58
- import { Dotmote } from 'dotmote/vue';
59
- </script>
60
-
61
- <template>
62
- <div style="position: relative; min-height: 40vh">
63
- <Dotmote values="👋dotmote!" theme="auto" motion="ticker-right" />
64
- </div>
65
- </template>
66
- ```
67
-
68
- `class` / `style` fall through to the wrapper `<div>`.
69
-
70
- ## Props
71
-
72
- | Prop | Type | Default | Description |
73
- | --- | --- | --- | --- |
74
- | `values` | `string` | — | Content as a string — each character becomes one body (whitespace skipped). Shorthand for `items`. |
75
- | `items` | `(ContentItem \| string)[]` | `['A','B','C','D','E']` | The bodies to drift. Strings become letters; see [Content](#content). |
76
- | `theme` | `ThemePreset \| ThemeConfig` | `'auto'` | A preset name or an inline config (`dotColor`, `activeDotColor`, `glow`, `background`). `auto` picks `light`/`dark` from the OS. |
77
- | `motion` | `MotionMode` | `'drift'` | How the bodies move — see [Motion](#motion). |
78
- | `speed` | `number` | `1` | Global speed multiplier. |
79
- | `glowStrength` | `number` | `1` | Glow strength: `<1` fades it, `>1` brightens. (`glowAlpha` is a deprecated alias.) |
80
- | `dotRadius` | `number` | `spacing <= 9 ? 0.82 : 1` | Dot size in px. Bigger = chunkier dots. |
81
- | `fontFamily` | `string` | `"Trebuchet MS", ui-rounded, sans-serif` | Font stack (weight/size are prefixed: `900 ${fontSize}px …`). |
82
- | `fontSize` | `number` | — | Fixed character size in px. Overrides the auto size (and `fontSizeOverride`). |
83
- | `fontSizeOverride` | `number \| ((w)=>number)` | — | Override the automatic font size. |
84
- | `fontSizeMin` / `fontSizeMax` | `number` | `207` / `270` | Bounds of the automatic font clamp. |
85
- | `breakpoints` | `Partial<Breakpoints>` | `{small:372, medium:640, …}` | Responsive geometry. |
86
- | `spacingScale` | `number` | `1` | Lattice density — `<1` denser, `>1` sparser. |
87
- | `charGap` | `number` | `auto` | Gap between characters in `ticker` / `static` rows (px). Omit for an auto compact spacing. |
88
- | `introDurationMs` | `number` | `520` | Hold + fade-in period. |
89
- | `className` / `class` / `style` | `string` / `string` / `CSSProperties` | — | Passed to the wrapper `<div>`. `class` is an alias for `className`. |
90
- | `ariaHidden` | `boolean` | `true` | `aria-hidden` on the wrapper. |
91
-
92
- ## Themes
93
-
94
- Pick a neutral preset, or bring your own colors:
95
-
96
- ```tsx
97
- <Dotmote theme="mono" /> // default
98
- <Dotmote theme="dark" />
99
- <Dotmote theme="gradient" />
100
-
101
- <Dotmote
102
- theme={{
103
- dotColor: 'rgba(96, 165, 250, 0.6)',
104
- glow: ['rgba(56, 189, 248, 0.9)', 'rgba(99, 102, 241, 0.9)', 'rgba(236, 72, 153, 0.9)'],
105
- background: '#0b1020',
106
- }}
107
- />
108
- ```
109
-
110
- Presets: `auto`, `light`, `dark`, `mono`, `gradient`. `auto` (the default) follows
111
- the OS `prefers-color-scheme` — leave `theme` out to use it. The quick start uses
112
- `theme="gradient"`; pass `theme="mono"` for no background + neutral gray dots.
113
-
114
- ```ts
115
- interface ThemeConfig {
116
- dotColor?: string; // lattice dot color
117
- activeDotColor?: string; // flat glow color (all 3 stops); omit to use `glow`
118
- glow: [string, string, string];
119
- background?: string;
120
- }
121
- ```
122
-
123
- Changing the theme hot-updates in place — the bodies keep their positions and motion.
124
-
125
- ## Content
126
-
127
- `values` is the fastest way to fill the background — each character becomes a body:
128
-
129
- ```tsx
130
- <Dotmote values="dotmote" />
131
- <Dotmote values="🌊🔥⭐🌙" />
132
- ```
133
-
134
- For per-body control, use `items`:
135
-
136
- ```tsx
137
- <Dotmote items={['A', 'B', 'C', 'D', 'E']} />
138
- <Dotmote items={[{ kind: 'shape', value: 'circle', radius: 60 }, { kind: 'emoji', value: '🌊' }]} />
139
- ```
140
-
141
- ```ts
142
- type ContentItem =
143
- | { kind: 'text'; value: string }
144
- | { kind: 'emoji'; value: string }
145
- | { kind: 'shape'; value: 'circle' | 'square' | 'triangle' | 'star' | 'diamond'; radius?: number }
146
- | { kind: 'path'; value: string }; // SVG path
147
- ```
148
-
149
- ## Motion
150
-
151
- | Mode | Behavior |
152
- | --- | --- |
153
- | `drift` | drift + wall bounce + pairs collide |
154
- | `roam` | drift + bounce, bodies pass through each other |
155
- | `static` | frozen after the intro fade — laid out horizontally-aligned as a centered row |
156
- | `ticker-left` / `ticker-right` | marquee: horizontally-aligned row at the center, scrolling and looping with even spacing |
157
-
158
- ## Development
159
-
160
- ```bash
161
- npm install
162
- npm run dev # playground at http://localhost:5199/
163
- npm test # vitest (physics + item normalization)
164
- npm run lint
165
- npm run typecheck
166
- npm run build # tsc → dist/
167
- ```
168
-
169
- The playground runs on any modern browser — resize across 372 / 640px to see the breakpoints, and open DevTools → Console to confirm the reload is clean.
170
-
171
- ## Deploy the demo
172
-
173
- The playground (`examples/`) is a static Vite site; `npm run build:site` emits it to `site/`. The npm **library** is built separately with `npm run build` → `dist/` (that's what gets published to npm, not the site).
174
-
175
- ### GitHub Pages
176
-
177
- 1. Push the repo, then in Settings → **Pages → Source = "GitHub Actions"**.
178
- 2. The included `.github/workflows/pages.yml` builds the demo on every push and publishes it at `https://<user>.github.io/dotmote/` (base `/dotmote/`).
179
-
180
- ### Cloudflare Pages
181
-
182
- 1. Dashboard → **Workers & Pages → Create → Pages → Connect to Git** → pick the repo.
183
- 2. Build settings:
184
- - Build command: `npm run build:site`
185
- - Build output directory: `site`
186
- 3. Leave the base path empty — Cloudflare serves from the domain root, so no `--base` is needed.
187
-
188
- ## Architecture
189
-
190
- A thin React wrapper over a framework-agnostic Canvas core. Four canvases: a dot lattice, its white alpha mask, the gradient-illuminated bodies, and the composite. The lattice and mask redraw only on resize; the glow composites every frame.
191
-
192
- ## Notes / limitations
193
-
194
- - **ESM only** — no CommonJS build; use a bundler that supports ESM.
195
- - **Emoji keep their own colors** — they ignore the glow gradient (text and shapes recolor).
196
- - **`path` is basic** — feed a path already centered near the origin and scaled to ~±100 units.
197
- - Collision is a simplified momentum swap, not a full physics impulse.
198
-
199
- ## License
200
-
201
- MIT
1
+ # dotmote
2
+
3
+ <img src="https://raw.githubusercontent.com/rryanchiu/dotmote/main/docs/example.png" alt="dotmote — LED dot-matrix glow component" width="100%" />
4
+
5
+ <p align="center">
6
+ <a href="https://www.npmjs.com/package/dotmote"><img alt="npm" src="https://img.shields.io/npm/v/dotmote" /></a>
7
+ <img alt="license" src="https://img.shields.io/npm/l/dotmote" />
8
+ <img alt="bundle size" src="https://img.shields.io/bundlephobia/minzip/dotmote" />
9
+ <a href="https://dotmote.imryan.dev"><img alt="Live demo" src="https://img.shields.io/badge/Live%20demo-dotmote.imryan.dev-2ea44f" /></a>
10
+ <a href="https://stackblitz.com/github/rryanchiu/dotmote"><img alt="Open in StackBlitz" src="https://img.shields.io/badge/Open%20in-StackBlitz-1269D3" /></a>
11
+ </p>
12
+
13
+ <p align="center">
14
+ <img src="https://raw.githubusercontent.com/rryanchiu/dotmote/main/docs/dotmote.gif" alt="dotmote demo" width="200" />
15
+ </p>
16
+
17
+ <p align="center"><a href="https://github.com/rryanchiu/dotmote/blob/main/README.md">English</a> · <a href="https://github.com/rryanchiu/dotmote/blob/main/README.zh-CN.md">简体中文</a></p>
18
+
19
+ > **Live demo** — [dotmote.imryan.dev](https://dotmote.imryan.dev) · **Try it live**: [Open in StackBlitz](https://stackblitz.com/github/rryanchiu/dotmote) to run the playground in your browser. You can also import this GitHub repo into CodeSandbox.
20
+
21
+ A framework-agnostic dotted-matrix glow background for React and Vue. Scatter a few letters, emoji, or shapes over an endless grid of dots — they drift, bump into each other, and glow through the lattice like a little light show.
22
+
23
+ - **React 18+** · TypeScript · Canvas 2D · **SSR-safe**
24
+ - **Zero runtime deps** — just `react` (peer)
25
+ - Pure Canvas 2D — no particle or dot-grid libraries
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ npm install dotmote
31
+ ```
32
+
33
+ Requires `react >= 18`. Ships ESM + TypeScript types.
34
+
35
+ ## Quick start
36
+
37
+ ```tsx
38
+ import { Dotmote } from 'dotmote';
39
+
40
+ export function Page() {
41
+ return (
42
+ <div style={{ position: 'relative', minHeight: '100vh' }}>
43
+ <Dotmote theme="gradient" />
44
+
45
+ <main style={{ position: 'relative', zIndex: 1, padding: 24 }}>
46
+ <h1>My page</h1>
47
+ </main>
48
+ </div>
49
+ );
50
+ }
51
+ ```
52
+
53
+ The wrapper `<div>` is absolutely positioned behind everything — put your content in a sibling with a higher `z-index`.
54
+
55
+ ## Vue
56
+
57
+ The Canvas core is framework-agnostic — React and Vue are both thin wrappers over
58
+ the same `createDotMatrixCore`. For Vue 3 use the `dotmote/vue` entry:
59
+
60
+ ```vue
61
+ <script setup>
62
+ import { Dotmote } from 'dotmote/vue';
63
+ </script>
64
+
65
+ <template>
66
+ <div style="position: relative; min-height: 40vh">
67
+ <Dotmote values="👋dotmote!" theme="auto" motion="ticker-right" />
68
+ </div>
69
+ </template>
70
+ ```
71
+
72
+ `class` / `style` fall through to the wrapper `<div>`.
73
+
74
+ ## Props
75
+
76
+ | Prop | Type | Default | Description |
77
+ | --- | --- | --- | --- |
78
+ | `values` | `string` | — | Content as a string — each character becomes one body (whitespace skipped). Shorthand for `items`. |
79
+ | `items` | `(ContentItem \| string)[]` | `['A','B','C','D','E']` | The bodies to drift. Strings become letters; see [Content](#content). |
80
+ | `theme` | `ThemePreset \| ThemeConfig` | `'auto'` | A preset name or an inline config (`dotColor`, `activeDotColor`, `glow`, `background`). `auto` picks `light`/`dark` from the OS. |
81
+ | `motion` | `MotionMode` | `'drift'` | How the bodies move — see [Motion](#motion). |
82
+ | `speed` | `number` | `1` | Global speed multiplier. |
83
+ | `glowStrength` | `number` | `1` | Glow strength: `<1` fades it, `>1` brightens. (`glowAlpha` is a deprecated alias.) |
84
+ | `dotRadius` | `number` | `spacing <= 9 ? 0.82 : 1` | Dot size in px. Bigger = chunkier dots. |
85
+ | `fontFamily` | `string` | `"Trebuchet MS", ui-rounded, sans-serif` | Font stack (weight/size are prefixed: `900 ${fontSize}px …`). |
86
+ | `fontSize` | `number` | — | Fixed character size in px. Overrides the auto size (and `fontSizeOverride`). |
87
+ | `fontSizeOverride` | `number \| ((w)=>number)` | — | Override the automatic font size. |
88
+ | `fontSizeMin` / `fontSizeMax` | `number` | `207` / `270` | Bounds of the automatic font clamp. |
89
+ | `breakpoints` | `Partial<Breakpoints>` | `{small:372, medium:640, …}` | Responsive geometry. |
90
+ | `spacingScale` | `number` | `1` | Lattice density — `<1` denser, `>1` sparser. |
91
+ | `charGap` | `number` | `auto` | Gap between characters in `ticker` / `static` rows (px). Omit for an auto compact spacing. |
92
+ | `introDurationMs` | `number` | `520` | Hold + fade-in period. |
93
+ | `className` / `class` / `style` | `string` / `string` / `CSSProperties` | — | Passed to the wrapper `<div>`. `class` is an alias for `className`. |
94
+ | `ariaHidden` | `boolean` | `true` | `aria-hidden` on the wrapper. |
95
+
96
+ ## Themes
97
+
98
+ Pick a neutral preset, or bring your own colors:
99
+
100
+ ```tsx
101
+ <Dotmote theme="mono" /> // default
102
+ <Dotmote theme="dark" />
103
+ <Dotmote theme="gradient" />
104
+
105
+ <Dotmote
106
+ theme={{
107
+ dotColor: 'rgba(96, 165, 250, 0.6)',
108
+ glow: ['rgba(56, 189, 248, 0.9)', 'rgba(99, 102, 241, 0.9)', 'rgba(236, 72, 153, 0.9)'],
109
+ background: '#0b1020',
110
+ }}
111
+ />
112
+ ```
113
+
114
+ Presets: `auto`, `light`, `dark`, `mono`, `gradient`. `auto` (the default) follows
115
+ the OS `prefers-color-scheme` — leave `theme` out to use it. The quick start uses
116
+ `theme="gradient"`; pass `theme="mono"` for no background + neutral gray dots.
117
+
118
+ ```ts
119
+ interface ThemeConfig {
120
+ dotColor?: string; // lattice dot color
121
+ activeDotColor?: string; // flat glow color (all 3 stops); omit to use `glow`
122
+ glow: [string, string, string];
123
+ background?: string;
124
+ }
125
+ ```
126
+
127
+ Changing the theme hot-updates in place — the bodies keep their positions and motion.
128
+
129
+ ## Content
130
+
131
+ `values` is the fastest way to fill the background — each character becomes a body:
132
+
133
+ ```tsx
134
+ <Dotmote values="dotmote" />
135
+ <Dotmote values="🌊🔥⭐🌙" />
136
+ ```
137
+
138
+ For per-body control, use `items`:
139
+
140
+ ```tsx
141
+ <Dotmote items={['A', 'B', 'C', 'D', 'E']} />
142
+ <Dotmote items={[{ kind: 'shape', value: 'circle', radius: 60 }, { kind: 'emoji', value: '🌊' }]} />
143
+ ```
144
+
145
+ ```ts
146
+ type ContentItem =
147
+ | { kind: 'text'; value: string }
148
+ | { kind: 'emoji'; value: string }
149
+ | { kind: 'shape'; value: 'circle' | 'square' | 'triangle' | 'star' | 'diamond'; radius?: number }
150
+ | { kind: 'path'; value: string }; // SVG path
151
+ ```
152
+
153
+ ## Motion
154
+
155
+ | Mode | Behavior |
156
+ | --- | --- |
157
+ | `drift` | drift + wall bounce + pairs collide |
158
+ | `roam` | drift + bounce, bodies pass through each other |
159
+ | `static` | frozen after the intro fade — laid out horizontally-aligned as a centered row |
160
+ | `ticker-left` / `ticker-right` | marquee: horizontally-aligned row at the center, scrolling and looping with even spacing |
161
+
162
+ ## Development
163
+
164
+ ```bash
165
+ npm install
166
+ npm run dev # playground at http://localhost:5199/
167
+ npm test # vitest (physics + item normalization)
168
+ npm run lint
169
+ npm run typecheck
170
+ npm run build # tsc → dist/
171
+ ```
172
+
173
+ The playground runs on any modern browser — resize across 372 / 640px to see the breakpoints, and open DevTools → Console to confirm the reload is clean.
174
+
175
+ ## Deploy the demo
176
+
177
+ The playground (`examples/`) is a static Vite site; `npm run build:site` emits it to `site/`. The npm **library** is built separately with `npm run build` → `dist/` (that's what gets published to npm, not the site).
178
+
179
+ ### GitHub Pages
180
+
181
+ 1. Push the repo, then in Settings → **Pages → Source = "GitHub Actions"**.
182
+ 2. The included `.github/workflows/pages.yml` builds the demo on every push and publishes it at `https://<user>.github.io/dotmote/` (base `/dotmote/`).
183
+
184
+ ### Cloudflare Pages
185
+
186
+ 1. Dashboard → **Workers & Pages → Create → Pages → Connect to Git** → pick the repo.
187
+ 2. Build settings:
188
+ - Build command: `npm run build:site`
189
+ - Build output directory: `site`
190
+ 3. Leave the base path empty — Cloudflare serves from the domain root, so no `--base` is needed.
191
+
192
+ ## Architecture
193
+
194
+ A thin React wrapper over a framework-agnostic Canvas core. Four canvases: a dot lattice, its white alpha mask, the gradient-illuminated bodies, and the composite. The lattice and mask redraw only on resize; the glow composites every frame.
195
+
196
+ ## Notes / limitations
197
+
198
+ - **ESM only** — no CommonJS build; use a bundler that supports ESM.
199
+ - **Emoji keep their own colors** — they ignore the glow gradient (text and shapes recolor).
200
+ - **`path` is basic** — feed a path already centered near the origin and scaled to ~±100 units.
201
+ - Collision is a simplified momentum swap, not a full physics impulse.
202
+
203
+ ## License
204
+
205
+ MIT
package/README.zh-CN.md CHANGED
@@ -1,179 +1,183 @@
1
- # dotmote
2
-
3
- <img src="https://raw.githubusercontent.com/rryanchiu/dotmote/main/docs/example.png" alt="dotmote — LED 点阵流光组件" width="100%" />
4
-
5
- <p align="center">
6
- <a href="https://www.npmjs.com/package/dotmote"><img alt="npm" src="https://img.shields.io/npm/v/dotmote" /></a>
7
- <img alt="license" src="https://img.shields.io/npm/l/dotmote" />
8
- <img alt="bundle size" src="https://img.shields.io/bundlephobia/minzip/dotmote" />
9
- <a href="https://dotmote.imryan.dev"><img alt="在线演示" src="https://img.shields.io/badge/Live%20demo-dotmote.imryan.dev-2ea44f" /></a>
10
- <a href="https://stackblitz.com/github/rryanchiu/dotmote"><img alt="Open in StackBlitz" src="https://img.shields.io/badge/Open%20in-StackBlitz-1269D3" /></a>
11
- </p>
12
-
13
- <p align="center">[English](https://github.com/rryanchiu/dotmote/blob/main/README.md) · [简体中文](https://github.com/rryanchiu/dotmote/blob/main/README.zh-CN.md)</p>
14
-
15
- > **在线演示** — [dotmote.imryan.dev](https://dotmote.imryan.dev)。
16
-
17
- 框架无关的点阵发光背景,支持 React 与 Vue。把几个字母、emoji 或形状撒在一片无边的点阵上——它们四处漂移、彼此碰撞,然后从网格里透出光来,像一场小小的灯展。
18
-
19
- - **React 18+** · TypeScript · Canvas 2D · **SSR 安全**
20
- - **零运行时依赖** —— 只需 `react`(peer)
21
- - 纯 Canvas 2D,不用任何粒子或点阵库
22
-
23
- ## 安装
24
-
25
- ```bash
26
- npm install dotmote
27
- ```
28
-
29
- 需要 `react >= 18`。提供 ESM + TypeScript 类型声明。
30
-
31
- ## 快速开始
32
-
33
- ```tsx
34
- import { Dotmote } from 'dotmote';
35
-
36
- export function Page() {
37
- return (
38
- <div style={{ position: 'relative', minHeight: '100vh' }}>
39
- <Dotmote theme="gradient" />
40
-
41
- <main style={{ position: 'relative', zIndex: 1, padding: 24 }}>
42
- <h1>My page</h1>
43
- </main>
44
- </div>
45
- );
46
- }
47
- ```
48
-
49
- 外层包裹的 `<div>` 是绝对定位、垫在所有内容后面——把你的内容放进一个 `z-index` 更高的兄弟节点即可。
50
-
51
- ## Vue
52
-
53
- Canvas 核心与框架无关——React 与 Vue 都只是同一套 `createDotMatrixCore` 的薄封装。Vue 3 用 `dotmote/vue` 入口:
54
-
55
- ```vue
56
- <script setup>
57
- import { Dotmote } from 'dotmote/vue';
58
- </script>
59
-
60
- <template>
61
- <div style="position: relative; min-height: 40vh">
62
- <Dotmote values="👋dotmote!" theme="auto" motion="ticker-right" />
63
- </div>
64
- </template>
65
- ```
66
-
67
- `class` / `style` 会透传到外层 `<div>`。
68
-
69
- ## 属性
70
-
71
- | Prop | Type | Default | Description |
72
- | --- | --- | --- | --- |
73
- | `values` | `string` | — | 用一个字符串填充内容——每个字符成为一个主体(空白会被跳过)。`items` 的简写。 |
74
- | `items` | `(ContentItem \| string)[]` | `['A','B','C','D','E']` | 要漂移的主体。字符串会变成字母;见 [内容](#内容)。 |
75
- | `theme` | `ThemePreset \| ThemeConfig` | `'mono'` | 预设名,或内联配置(`dotColor`、`activeDotColor`、`glow`、`background`)。 |
76
- | `motion` | `MotionMode` | `'drift'` | 主体的运动方式——见 [动效](#动效)。 |
77
- | `speed` | `number` | `1` | 全局速度倍率。 |
78
- | `glowStrength` | `number` | `1` | 发光强度:`<1` 变淡,`>1` 更亮。(`glowAlpha` 是已废弃的别名。) |
79
- | `dotRadius` | `number` | `spacing <= 9 ? 0.82 : 1` | 点的大小(px)。越大越"颗粒感"。 |
80
- | `fontFamily` | `string` | `"Trebuchet MS", ui-rounded, sans-serif` | 字体栈(字重/字号会自动前置:`900 ${fontSize}px …`)。 |
81
- | `fontSizeOverride` | `number \| ((w)=>number)` | — | 覆盖自动字号。 |
82
- | `fontSizeMin` / `fontSizeMax` | `number` | `207` / `270` | 自动字号钳制的上下界。 |
83
- | `breakpoints` | `Partial<Breakpoints>` | `{small:372, medium:640, …}` | 响应式几何。 |
84
- | `spacingScale` | `number` | `1` | 点阵密度——`<1` 更密,`>1` 更疏。 |
85
- | `introDurationMs` | `number` | `520` | 停留 + 淡入时长。 |
86
- | `className` / `style` | `string` / `CSSProperties` | — | 传给包裹 `<div>`。 |
87
- | `ariaHidden` | `boolean` | `true` | 包裹 `<div>` 的 `aria-hidden`。 |
88
-
89
- ## 主题
90
-
91
- 选一个中性预设,或换成你自己的配色:
92
-
93
- ```tsx
94
- <Dotmote theme="mono" /> // 默认
95
- <Dotmote theme="dark" />
96
- <Dotmote theme="gradient" />
97
-
98
- <Dotmote
99
- theme={{
100
- dotColor: 'rgba(96, 165, 250, 0.6)',
101
- glow: ['rgba(56, 189, 248, 0.9)', 'rgba(99, 102, 241, 0.9)', 'rgba(236, 72, 153, 0.9)'],
102
- background: '#0b1020',
103
- }}
104
- />
105
- ```
106
-
107
- 预设:`light`、`dark`、`mono`、`gradient`、`rainbow`。
108
-
109
- ```ts
110
- interface ThemeConfig {
111
- dotColor?: string; // 点阵颜色
112
- activeDotColor?: string; // 纯色发光(3 个 stop 同色);不填则用 `glow`
113
- glow: [string, string, string];
114
- background?: string;
115
- }
116
- ```
117
-
118
- 切换主题会原地热更新——主体保持当前位置和运动不变。
119
-
120
- ## 内容
121
-
122
- `values` 是最快的填充方式——每个字符成为一个主体:
123
-
124
- ```tsx
125
- <Dotmote values="dotmote" />
126
- <Dotmote values="🌊🔥⭐🌙" />
127
- ```
128
-
129
- 想逐个控制,就用 `items`:
130
-
131
- ```tsx
132
- <Dotmote items={['A', 'B', 'C', 'D', 'E']} />
133
- <Dotmote items={[{ kind: 'shape', value: 'circle', radius: 60 }, { kind: 'emoji', value: '🌊' }]} />
134
- ```
135
-
136
- ```ts
137
- type ContentItem =
138
- | { kind: 'text'; value: string }
139
- | { kind: 'emoji'; value: string }
140
- | { kind: 'shape'; value: 'circle' | 'square' | 'triangle' | 'star' | 'diamond'; radius?: number }
141
- | { kind: 'path'; value: string }; // SVG 路径
142
- ```
143
-
144
- ## 动效
145
-
146
- | Mode | 行为 |
147
- | --- | --- |
148
- | `drift` | 漂移 + 碰壁反弹 + 两两碰撞 |
149
- | `roam` | 漂移 + 反弹,主体彼此穿过 |
150
- | `static` | 淡入后静止 |
151
- | `ticker-left` / `ticker-right` | 跑马灯:主体排成一行循环滚动 |
152
-
153
- ## 开发
154
-
155
- ```bash
156
- npm install
157
- npm run dev # playground 在 http://localhost:5199/examples/index.html
158
- npm test # vitest(物理 + 内容归一化)
159
- npm run lint
160
- npm run typecheck
161
- npm run build # tsc → dist/
162
- ```
163
-
164
- playground 在任何现代浏览器都能跑——把窗口跨过 372 / 640px 看断点变化,并在 DevTools → Console 里确认刷新无报错。
165
-
166
- ## 架构
167
-
168
- 薄薄一层 React 包装,下面是框架无关的 Canvas 核心。四个画布:点阵、它的白色 alpha 遮罩、被渐变照亮的主体、以及合成层。点阵和遮罩只在尺寸变化时重绘;发光层每帧合成。
169
-
170
- ## 说明 / 限制
171
-
172
- - **仅 ESM** —— 没有 CommonJS 构建;请用支持 ESM 的打包器。
173
- - **emoji 保持自身颜色** —— 它们不跟随发光渐变(文本和形状会变色)。
174
- - **`path` 只支持基础用法** —— 传入一条已大致以原点为中心、缩放到约 ±100 单位的路径。
175
- - 碰撞是简化的动量交换,不是完整的物理冲量。
176
-
177
- ## 许可证
178
-
179
- MIT
1
+ # dotmote
2
+
3
+ <img src="https://raw.githubusercontent.com/rryanchiu/dotmote/main/docs/example.png" alt="dotmote — LED 点阵流光组件" width="100%" />
4
+
5
+ <p align="center">
6
+ <a href="https://www.npmjs.com/package/dotmote"><img alt="npm" src="https://img.shields.io/npm/v/dotmote" /></a>
7
+ <img alt="license" src="https://img.shields.io/npm/l/dotmote" />
8
+ <img alt="bundle size" src="https://img.shields.io/bundlephobia/minzip/dotmote" />
9
+ <a href="https://dotmote.imryan.dev"><img alt="在线演示" src="https://img.shields.io/badge/Live%20demo-dotmote.imryan.dev-2ea44f" /></a>
10
+ <a href="https://stackblitz.com/github/rryanchiu/dotmote"><img alt="Open in StackBlitz" src="https://img.shields.io/badge/Open%20in-StackBlitz-1269D3" /></a>
11
+ </p>
12
+
13
+ <p align="center">
14
+ <img src="https://raw.githubusercontent.com/rryanchiu/dotmote/main/docs/dotmote.gif" alt="dotmote 演示" width="200" />
15
+ </p>
16
+
17
+ <p align="center"><a href="https://github.com/rryanchiu/dotmote/blob/main/README.md">English</a> · <a href="https://github.com/rryanchiu/dotmote/blob/main/README.zh-CN.md">简体中文</a></p>
18
+
19
+ > **在线演示** — [dotmote.imryan.dev](https://dotmote.imryan.dev)。
20
+
21
+ 框架无关的点阵发光背景,支持 React 与 Vue。把几个字母、emoji 或形状撒在一片无边的点阵上——它们四处漂移、彼此碰撞,然后从网格里透出光来,像一场小小的灯展。
22
+
23
+ - **React 18+** · TypeScript · Canvas 2D · **SSR 安全**
24
+ - **零运行时依赖** —— 只需 `react`(peer)
25
+ - 纯 Canvas 2D,不用任何粒子或点阵库
26
+
27
+ ## 安装
28
+
29
+ ```bash
30
+ npm install dotmote
31
+ ```
32
+
33
+ 需要 `react >= 18`。提供 ESM + TypeScript 类型声明。
34
+
35
+ ## 快速开始
36
+
37
+ ```tsx
38
+ import { Dotmote } from 'dotmote';
39
+
40
+ export function Page() {
41
+ return (
42
+ <div style={{ position: 'relative', minHeight: '100vh' }}>
43
+ <Dotmote theme="gradient" />
44
+
45
+ <main style={{ position: 'relative', zIndex: 1, padding: 24 }}>
46
+ <h1>My page</h1>
47
+ </main>
48
+ </div>
49
+ );
50
+ }
51
+ ```
52
+
53
+ 外层包裹的 `<div>` 是绝对定位、垫在所有内容后面——把你的内容放进一个 `z-index` 更高的兄弟节点即可。
54
+
55
+ ## Vue
56
+
57
+ Canvas 核心与框架无关——React 与 Vue 都只是同一套 `createDotMatrixCore` 的薄封装。Vue 3 用 `dotmote/vue` 入口:
58
+
59
+ ```vue
60
+ <script setup>
61
+ import { Dotmote } from 'dotmote/vue';
62
+ </script>
63
+
64
+ <template>
65
+ <div style="position: relative; min-height: 40vh">
66
+ <Dotmote values="👋dotmote!" theme="auto" motion="ticker-right" />
67
+ </div>
68
+ </template>
69
+ ```
70
+
71
+ `class` / `style` 会透传到外层 `<div>`。
72
+
73
+ ## 属性
74
+
75
+ | Prop | Type | Default | Description |
76
+ | --- | --- | --- | --- |
77
+ | `values` | `string` | — | 用一个字符串填充内容——每个字符成为一个主体(空白会被跳过)。`items` 的简写。 |
78
+ | `items` | `(ContentItem \| string)[]` | `['A','B','C','D','E']` | 要漂移的主体。字符串会变成字母;见 [内容](#内容)。 |
79
+ | `theme` | `ThemePreset \| ThemeConfig` | `'mono'` | 预设名,或内联配置(`dotColor`、`activeDotColor`、`glow`、`background`)。 |
80
+ | `motion` | `MotionMode` | `'drift'` | 主体的运动方式——见 [动效](#动效)。 |
81
+ | `speed` | `number` | `1` | 全局速度倍率。 |
82
+ | `glowStrength` | `number` | `1` | 发光强度:`<1` 变淡,`>1` 更亮。(`glowAlpha` 是已废弃的别名。) |
83
+ | `dotRadius` | `number` | `spacing <= 9 ? 0.82 : 1` | 点的大小(px)。越大越"颗粒感"。 |
84
+ | `fontFamily` | `string` | `"Trebuchet MS", ui-rounded, sans-serif` | 字体栈(字重/字号会自动前置:`900 ${fontSize}px …`)。 |
85
+ | `fontSizeOverride` | `number \| ((w)=>number)` | — | 覆盖自动字号。 |
86
+ | `fontSizeMin` / `fontSizeMax` | `number` | `207` / `270` | 自动字号钳制的上下界。 |
87
+ | `breakpoints` | `Partial<Breakpoints>` | `{small:372, medium:640, …}` | 响应式几何。 |
88
+ | `spacingScale` | `number` | `1` | 点阵密度——`<1` 更密,`>1` 更疏。 |
89
+ | `introDurationMs` | `number` | `520` | 停留 + 淡入时长。 |
90
+ | `className` / `style` | `string` / `CSSProperties` | — | 传给包裹 `<div>`。 |
91
+ | `ariaHidden` | `boolean` | `true` | 包裹 `<div>` 的 `aria-hidden`。 |
92
+
93
+ ## 主题
94
+
95
+ 选一个中性预设,或换成你自己的配色:
96
+
97
+ ```tsx
98
+ <Dotmote theme="mono" /> // 默认
99
+ <Dotmote theme="dark" />
100
+ <Dotmote theme="gradient" />
101
+
102
+ <Dotmote
103
+ theme={{
104
+ dotColor: 'rgba(96, 165, 250, 0.6)',
105
+ glow: ['rgba(56, 189, 248, 0.9)', 'rgba(99, 102, 241, 0.9)', 'rgba(236, 72, 153, 0.9)'],
106
+ background: '#0b1020',
107
+ }}
108
+ />
109
+ ```
110
+
111
+ 预设:`light`、`dark`、`mono`、`gradient`、`rainbow`。
112
+
113
+ ```ts
114
+ interface ThemeConfig {
115
+ dotColor?: string; // 点阵颜色
116
+ activeDotColor?: string; // 纯色发光(3 个 stop 同色);不填则用 `glow`
117
+ glow: [string, string, string];
118
+ background?: string;
119
+ }
120
+ ```
121
+
122
+ 切换主题会原地热更新——主体保持当前位置和运动不变。
123
+
124
+ ## 内容
125
+
126
+ `values` 是最快的填充方式——每个字符成为一个主体:
127
+
128
+ ```tsx
129
+ <Dotmote values="dotmote" />
130
+ <Dotmote values="🌊🔥⭐🌙" />
131
+ ```
132
+
133
+ 想逐个控制,就用 `items`:
134
+
135
+ ```tsx
136
+ <Dotmote items={['A', 'B', 'C', 'D', 'E']} />
137
+ <Dotmote items={[{ kind: 'shape', value: 'circle', radius: 60 }, { kind: 'emoji', value: '🌊' }]} />
138
+ ```
139
+
140
+ ```ts
141
+ type ContentItem =
142
+ | { kind: 'text'; value: string }
143
+ | { kind: 'emoji'; value: string }
144
+ | { kind: 'shape'; value: 'circle' | 'square' | 'triangle' | 'star' | 'diamond'; radius?: number }
145
+ | { kind: 'path'; value: string }; // SVG 路径
146
+ ```
147
+
148
+ ## 动效
149
+
150
+ | Mode | 行为 |
151
+ | --- | --- |
152
+ | `drift` | 漂移 + 碰壁反弹 + 两两碰撞 |
153
+ | `roam` | 漂移 + 反弹,主体彼此穿过 |
154
+ | `static` | 淡入后静止 |
155
+ | `ticker-left` / `ticker-right` | 跑马灯:主体排成一行循环滚动 |
156
+
157
+ ## 开发
158
+
159
+ ```bash
160
+ npm install
161
+ npm run dev # playground 在 http://localhost:5199/examples/index.html
162
+ npm test # vitest(物理 + 内容归一化)
163
+ npm run lint
164
+ npm run typecheck
165
+ npm run build # tsc → dist/
166
+ ```
167
+
168
+ playground 在任何现代浏览器都能跑——把窗口跨过 372 / 640px 看断点变化,并在 DevTools → Console 里确认刷新无报错。
169
+
170
+ ## 架构
171
+
172
+ 薄薄一层 React 包装,下面是框架无关的 Canvas 核心。四个画布:点阵、它的白色 alpha 遮罩、被渐变照亮的主体、以及合成层。点阵和遮罩只在尺寸变化时重绘;发光层每帧合成。
173
+
174
+ ## 说明 / 限制
175
+
176
+ - **仅 ESM** —— 没有 CommonJS 构建;请用支持 ESM 的打包器。
177
+ - **emoji 保持自身颜色** —— 它们不跟随发光渐变(文本和形状会变色)。
178
+ - **`path` 只支持基础用法** —— 传入一条已大致以原点为中心、缩放到约 ±100 单位的路径。
179
+ - 碰撞是简化的动量交换,不是完整的物理冲量。
180
+
181
+ ## 许可证
182
+
183
+ MIT
package/package.json CHANGED
@@ -1,77 +1,86 @@
1
- {
2
- "name": "dotmote",
3
- "version": "0.3.2",
4
- "description": "Brand-agnostic dotted-matrix glow background. Framework-agnostic Canvas core with thin React and Vue wrappers.",
5
- "keywords": [
6
- "react",
7
- "canvas",
8
- "background",
9
- "dot-matrix",
10
- "dotted-grid",
11
- "particles",
12
- "glow",
13
- "typescript"
14
- ],
15
- "license": "MIT",
16
- "type": "module",
17
- "sideEffects": false,
18
- "main": "./dist/index.js",
19
- "module": "./dist/index.js",
20
- "types": "./dist/index.d.ts",
21
- "exports": {
22
- ".": {
23
- "types": "./dist/index.d.ts",
24
- "import": "./dist/index.js"
25
- },
26
- "./themes": {
27
- "types": "./dist/themes.d.ts",
28
- "import": "./dist/themes.js"
29
- },
30
- "./core": {
31
- "types": "./dist/core/index.d.ts",
32
- "import": "./dist/core/index.js"
33
- },
34
- "./vue": {
35
- "types": "./dist/vue/index.d.ts",
36
- "import": "./dist/vue/index.js"
37
- }
38
- },
39
- "files": [
40
- "dist",
41
- "README.md",
42
- "README.zh-CN.md",
43
- "LICENSE"
44
- ],
45
- "scripts": {
46
- "typecheck": "tsc --noEmit",
47
- "build": "tsc -p tsconfig.build.json",
48
- "build:site": "vite build",
49
- "prepublishOnly": "npm run build",
50
- "lint": "eslint . --ext .ts,.tsx",
51
- "test": "vitest run",
52
- "dev": "vite",
53
- "clean": "rm -rf dist"
54
- },
55
- "peerDependencies": {
56
- "react": ">=18",
57
- "vue": "^3.3"
58
- },
59
- "devDependencies": {
60
- "@types/react": "^18.3.3",
61
- "@types/react-dom": "^18.3.0",
62
- "@typescript-eslint/eslint-plugin": "^7.16.1",
63
- "@typescript-eslint/parser": "^7.16.1",
64
- "@vitejs/plugin-react": "^4.3.1",
65
- "eslint": "^8.57.0",
66
- "eslint-plugin-react-hooks": "^4.6.2",
67
- "react": "^18.3.1",
68
- "react-dom": "^18.3.1",
69
- "typescript": "^5.5.3",
70
- "vite": "^5.4.0",
71
- "vitest": "^2.0.3",
72
- "vue": "^3.4.2"
73
- },
74
- "engines": {
75
- "node": ">=18"
76
- }
77
- }
1
+ {
2
+ "name": "dotmote",
3
+ "version": "0.3.3",
4
+ "description": "Brand-agnostic dotted-matrix glow background. Framework-agnostic Canvas core with thin React and Vue wrappers.",
5
+ "keywords": [
6
+ "react",
7
+ "canvas",
8
+ "background",
9
+ "dot-matrix",
10
+ "dotted-grid",
11
+ "particles",
12
+ "glow",
13
+ "typescript"
14
+ ],
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/rryanchiu/dotmote.git"
19
+ },
20
+ "homepage": "https://dotmote.imryan.dev",
21
+ "bugs": {
22
+ "url": "https://github.com/rryanchiu/dotmote/issues"
23
+ },
24
+ "author": "Ryan <xiaolg.qiu@gmail.com>",
25
+ "type": "module",
26
+ "sideEffects": false,
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js"
34
+ },
35
+ "./themes": {
36
+ "types": "./dist/themes.d.ts",
37
+ "import": "./dist/themes.js"
38
+ },
39
+ "./core": {
40
+ "types": "./dist/core/index.d.ts",
41
+ "import": "./dist/core/index.js"
42
+ },
43
+ "./vue": {
44
+ "types": "./dist/vue/index.d.ts",
45
+ "import": "./dist/vue/index.js"
46
+ }
47
+ },
48
+ "files": [
49
+ "dist",
50
+ "README.md",
51
+ "README.zh-CN.md",
52
+ "LICENSE"
53
+ ],
54
+ "scripts": {
55
+ "typecheck": "tsc --noEmit",
56
+ "build": "tsc -p tsconfig.build.json",
57
+ "build:site": "vite build",
58
+ "prepublishOnly": "npm run build",
59
+ "lint": "eslint . --ext .ts,.tsx",
60
+ "test": "vitest run",
61
+ "dev": "vite",
62
+ "clean": "rm -rf dist"
63
+ },
64
+ "peerDependencies": {
65
+ "react": ">=18",
66
+ "vue": "^3.3"
67
+ },
68
+ "devDependencies": {
69
+ "@types/react": "^18.3.3",
70
+ "@types/react-dom": "^18.3.0",
71
+ "@typescript-eslint/eslint-plugin": "^7.16.1",
72
+ "@typescript-eslint/parser": "^7.16.1",
73
+ "@vitejs/plugin-react": "^4.3.1",
74
+ "eslint": "^8.57.0",
75
+ "eslint-plugin-react-hooks": "^4.6.2",
76
+ "react": "^18.3.1",
77
+ "react-dom": "^18.3.1",
78
+ "typescript": "^5.5.3",
79
+ "vite": "^5.4.0",
80
+ "vitest": "^2.0.3",
81
+ "vue": "^3.4.2"
82
+ },
83
+ "engines": {
84
+ "node": ">=18"
85
+ }
86
+ }