dotmote 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 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 ADDED
@@ -0,0 +1,178 @@
1
+ # dotmote
2
+
3
+ <p>
4
+ <a href="https://www.npmjs.com/package/dotmote"><img alt="npm" src="https://img.shields.io/npm/v/dotmote" /></a>
5
+ <img alt="license" src="https://img.shields.io/npm/l/dotmote" />
6
+ <img alt="bundle size" src="https://img.shields.io/bundlephobia/minzip/dotmote" />
7
+ <a href="https://stackblitz.com/github/rryanchiu/dotmote"><img alt="Open in StackBlitz" src="https://img.shields.io/badge/Open%20in-StackBlitz-1269D3" /></a>
8
+ </p>
9
+
10
+ **[English](README.md)** · [简体中文](README.zh-CN.md)
11
+
12
+ > **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.
13
+
14
+ A dotted-matrix glow background for React. 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.
15
+
16
+ - **React 18+** · TypeScript · Canvas 2D · **SSR-safe**
17
+ - **Zero runtime deps** — just `react` (peer)
18
+ - Pure Canvas 2D — no particle or dot-grid libraries
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install dotmote
24
+ ```
25
+
26
+ Requires `react >= 18`. Ships ESM + TypeScript types.
27
+
28
+ ## Quick start
29
+
30
+ ```tsx
31
+ import { Dotmote } from 'dotmote';
32
+
33
+ export function Page() {
34
+ return (
35
+ <div style={{ position: 'relative', minHeight: '100vh' }}>
36
+ <Dotmote theme="gradient" />
37
+
38
+ <main style={{ position: 'relative', zIndex: 1, padding: 24 }}>
39
+ <h1>My page</h1>
40
+ </main>
41
+ </div>
42
+ );
43
+ }
44
+ ```
45
+
46
+ The wrapper `<div>` is absolutely positioned behind everything — put your content in a sibling with a higher `z-index`.
47
+
48
+ ## Props
49
+
50
+ | Prop | Type | Default | Description |
51
+ | --- | --- | --- | --- |
52
+ | `values` | `string` | — | Content as a string — each character becomes one body (whitespace skipped). Shorthand for `items`. |
53
+ | `items` | `(ContentItem \| string)[]` | `['A','B','C','D','E']` | The bodies to drift. Strings become letters; see [Content](#content). |
54
+ | `theme` | `ThemePreset \| ThemeConfig` | `'auto'` | A preset name or an inline config (`dotColor`, `activeDotColor`, `glow`, `background`). `auto` picks `light`/`dark` from the OS. |
55
+ | `motion` | `MotionMode` | `'drift'` | How the bodies move — see [Motion](#motion). |
56
+ | `speed` | `number` | `1` | Global speed multiplier. |
57
+ | `glowStrength` | `number` | `1` | Glow strength: `<1` fades it, `>1` brightens. (`glowAlpha` is a deprecated alias.) |
58
+ | `dotRadius` | `number` | `spacing <= 9 ? 0.82 : 1` | Dot size in px. Bigger = chunkier dots. |
59
+ | `fontFamily` | `string` | `"Trebuchet MS", ui-rounded, sans-serif` | Font stack (weight/size are prefixed: `900 ${fontSize}px …`). |
60
+ | `fontSize` | `number` | — | Fixed character size in px. Overrides the auto size (and `fontSizeOverride`). |
61
+ | `fontSizeOverride` | `number \| ((w)=>number)` | — | Override the automatic font size. |
62
+ | `fontSizeMin` / `fontSizeMax` | `number` | `207` / `270` | Bounds of the automatic font clamp. |
63
+ | `breakpoints` | `Partial<Breakpoints>` | `{small:372, medium:640, …}` | Responsive geometry. |
64
+ | `spacingScale` | `number` | `1` | Lattice density — `<1` denser, `>1` sparser. |
65
+ | `introDurationMs` | `number` | `520` | Hold + fade-in period. |
66
+ | `className` / `class` / `style` | `string` / `string` / `CSSProperties` | — | Passed to the wrapper `<div>`. `class` is an alias for `className`. |
67
+ | `ariaHidden` | `boolean` | `true` | `aria-hidden` on the wrapper. |
68
+
69
+ ## Themes
70
+
71
+ Pick a neutral preset, or bring your own colors:
72
+
73
+ ```tsx
74
+ <Dotmote theme="mono" /> // default
75
+ <Dotmote theme="dark" />
76
+ <Dotmote theme="gradient" />
77
+
78
+ <Dotmote
79
+ theme={{
80
+ dotColor: 'rgba(96, 165, 250, 0.6)',
81
+ glow: ['rgba(56, 189, 248, 0.9)', 'rgba(99, 102, 241, 0.9)', 'rgba(236, 72, 153, 0.9)'],
82
+ background: '#0b1020',
83
+ }}
84
+ />
85
+ ```
86
+
87
+ Presets: `auto`, `light`, `dark`, `mono`, `gradient`. `auto` (the default) follows
88
+ the OS `prefers-color-scheme` — leave `theme` out to use it. The quick start uses
89
+ `theme="gradient"`; pass `theme="mono"` for no background + neutral gray dots.
90
+
91
+ ```ts
92
+ interface ThemeConfig {
93
+ dotColor?: string; // lattice dot color
94
+ activeDotColor?: string; // flat glow color (all 3 stops); omit to use `glow`
95
+ glow: [string, string, string];
96
+ background?: string;
97
+ }
98
+ ```
99
+
100
+ Changing the theme hot-updates in place — the bodies keep their positions and motion.
101
+
102
+ ## Content
103
+
104
+ `values` is the fastest way to fill the background — each character becomes a body:
105
+
106
+ ```tsx
107
+ <Dotmote values="dotmote" />
108
+ <Dotmote values="🌊🔥⭐🌙" />
109
+ ```
110
+
111
+ For per-body control, use `items`:
112
+
113
+ ```tsx
114
+ <Dotmote items={['A', 'B', 'C', 'D', 'E']} />
115
+ <Dotmote items={[{ kind: 'shape', value: 'circle', radius: 60 }, { kind: 'emoji', value: '🌊' }]} />
116
+ ```
117
+
118
+ ```ts
119
+ type ContentItem =
120
+ | { kind: 'text'; value: string }
121
+ | { kind: 'emoji'; value: string }
122
+ | { kind: 'shape'; value: 'circle' | 'square' | 'triangle' | 'star' | 'diamond'; radius?: number }
123
+ | { kind: 'path'; value: string }; // SVG path
124
+ ```
125
+
126
+ ## Motion
127
+
128
+ | Mode | Behavior |
129
+ | --- | --- |
130
+ | `drift` | drift + wall bounce + pairs collide |
131
+ | `roam` | drift + bounce, bodies pass through each other |
132
+ | `static` | frozen after the intro fade — laid out horizontally-aligned as a centered row |
133
+ | `ticker-left` / `ticker-right` | marquee: horizontally-aligned row at the center, scrolling and looping with even spacing |
134
+
135
+ ## Development
136
+
137
+ ```bash
138
+ npm install
139
+ npm run dev # playground at http://localhost:5199/
140
+ npm test # vitest (physics + item normalization)
141
+ npm run lint
142
+ npm run typecheck
143
+ npm run build # tsc → dist/
144
+ ```
145
+
146
+ 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.
147
+
148
+ ## Deploy the demo
149
+
150
+ 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).
151
+
152
+ ### GitHub Pages
153
+
154
+ 1. Push the repo, then in Settings → **Pages → Source = "GitHub Actions"**.
155
+ 2. The included `.github/workflows/pages.yml` builds the demo on every push and publishes it at `https://<user>.github.io/dotmote/` (base `/dotmote/`).
156
+
157
+ ### Cloudflare Pages
158
+
159
+ 1. Dashboard → **Workers & Pages → Create → Pages → Connect to Git** → pick the repo.
160
+ 2. Build settings:
161
+ - Build command: `npm run build:site`
162
+ - Build output directory: `site`
163
+ 3. Leave the base path empty — Cloudflare serves from the domain root, so no `--base` is needed.
164
+
165
+ ## Architecture
166
+
167
+ 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.
168
+
169
+ ## Notes / limitations
170
+
171
+ - **ESM only** — no CommonJS build; use a bundler that supports ESM.
172
+ - **Emoji keep their own colors** — they ignore the glow gradient (text and shapes recolor).
173
+ - **`path` is basic** — feed a path already centered near the origin and scaled to ~±100 units.
174
+ - Collision is a simplified momentum swap, not a full physics impulse.
175
+
176
+ ## License
177
+
178
+ MIT
@@ -0,0 +1,155 @@
1
+ # dotmote
2
+
3
+ <p>
4
+ <a href="https://www.npmjs.com/package/dotmote"><img alt="npm" src="https://img.shields.io/npm/v/dotmote" /></a>
5
+ <img alt="license" src="https://img.shields.io/npm/l/dotmote" />
6
+ <img alt="bundle size" src="https://img.shields.io/bundlephobia/minzip/dotmote" />
7
+ </p>
8
+
9
+ [English](README.md) · **简体中文**
10
+
11
+ 为 React 打造的点阵发光背景。把几个字母、emoji 或形状撒在一片无边的点阵上——它们四处漂移、彼此碰撞,然后从网格里透出光来,像一场小小的灯展。
12
+
13
+ - **React 18+** · TypeScript · Canvas 2D · **SSR 安全**
14
+ - **零运行时依赖** —— 只需 `react`(peer)
15
+ - 纯 Canvas 2D,不用任何粒子或点阵库
16
+
17
+ ## 安装
18
+
19
+ ```bash
20
+ npm install dotmote
21
+ ```
22
+
23
+ 需要 `react >= 18`。提供 ESM + TypeScript 类型声明。
24
+
25
+ ## 快速开始
26
+
27
+ ```tsx
28
+ import { Dotmote } from 'dotmote';
29
+
30
+ export function Page() {
31
+ return (
32
+ <div style={{ position: 'relative', minHeight: '100vh' }}>
33
+ <Dotmote theme="gradient" />
34
+
35
+ <main style={{ position: 'relative', zIndex: 1, padding: 24 }}>
36
+ <h1>My page</h1>
37
+ </main>
38
+ </div>
39
+ );
40
+ }
41
+ ```
42
+
43
+ 外层包裹的 `<div>` 是绝对定位、垫在所有内容后面——把你的内容放进一个 `z-index` 更高的兄弟节点即可。
44
+
45
+ ## 属性
46
+
47
+ | Prop | Type | Default | Description |
48
+ | --- | --- | --- | --- |
49
+ | `values` | `string` | — | 用一个字符串填充内容——每个字符成为一个主体(空白会被跳过)。`items` 的简写。 |
50
+ | `items` | `(ContentItem \| string)[]` | `['A','B','C','D','E']` | 要漂移的主体。字符串会变成字母;见 [内容](#内容)。 |
51
+ | `theme` | `ThemePreset \| ThemeConfig` | `'mono'` | 预设名,或内联配置(`dotColor`、`activeDotColor`、`glow`、`background`)。 |
52
+ | `motion` | `MotionMode` | `'drift'` | 主体的运动方式——见 [动效](#动效)。 |
53
+ | `speed` | `number` | `1` | 全局速度倍率。 |
54
+ | `glowStrength` | `number` | `1` | 发光强度:`<1` 变淡,`>1` 更亮。(`glowAlpha` 是已废弃的别名。) |
55
+ | `dotRadius` | `number` | `spacing <= 9 ? 0.82 : 1` | 点的大小(px)。越大越"颗粒感"。 |
56
+ | `fontFamily` | `string` | `"Trebuchet MS", ui-rounded, sans-serif` | 字体栈(字重/字号会自动前置:`900 ${fontSize}px …`)。 |
57
+ | `fontSizeOverride` | `number \| ((w)=>number)` | — | 覆盖自动字号。 |
58
+ | `fontSizeMin` / `fontSizeMax` | `number` | `207` / `270` | 自动字号钳制的上下界。 |
59
+ | `breakpoints` | `Partial<Breakpoints>` | `{small:372, medium:640, …}` | 响应式几何。 |
60
+ | `spacingScale` | `number` | `1` | 点阵密度——`<1` 更密,`>1` 更疏。 |
61
+ | `introDurationMs` | `number` | `520` | 停留 + 淡入时长。 |
62
+ | `className` / `style` | `string` / `CSSProperties` | — | 传给包裹 `<div>`。 |
63
+ | `ariaHidden` | `boolean` | `true` | 包裹 `<div>` 的 `aria-hidden`。 |
64
+
65
+ ## 主题
66
+
67
+ 选一个中性预设,或换成你自己的配色:
68
+
69
+ ```tsx
70
+ <Dotmote theme="mono" /> // 默认
71
+ <Dotmote theme="dark" />
72
+ <Dotmote theme="gradient" />
73
+
74
+ <Dotmote
75
+ theme={{
76
+ dotColor: 'rgba(96, 165, 250, 0.6)',
77
+ glow: ['rgba(56, 189, 248, 0.9)', 'rgba(99, 102, 241, 0.9)', 'rgba(236, 72, 153, 0.9)'],
78
+ background: '#0b1020',
79
+ }}
80
+ />
81
+ ```
82
+
83
+ 预设:`light`、`dark`、`mono`、`gradient`、`rainbow`。
84
+
85
+ ```ts
86
+ interface ThemeConfig {
87
+ dotColor?: string; // 点阵颜色
88
+ activeDotColor?: string; // 纯色发光(3 个 stop 同色);不填则用 `glow`
89
+ glow: [string, string, string];
90
+ background?: string;
91
+ }
92
+ ```
93
+
94
+ 切换主题会原地热更新——主体保持当前位置和运动不变。
95
+
96
+ ## 内容
97
+
98
+ `values` 是最快的填充方式——每个字符成为一个主体:
99
+
100
+ ```tsx
101
+ <Dotmote values="dotmote" />
102
+ <Dotmote values="🌊🔥⭐🌙" />
103
+ ```
104
+
105
+ 想逐个控制,就用 `items`:
106
+
107
+ ```tsx
108
+ <Dotmote items={['A', 'B', 'C', 'D', 'E']} />
109
+ <Dotmote items={[{ kind: 'shape', value: 'circle', radius: 60 }, { kind: 'emoji', value: '🌊' }]} />
110
+ ```
111
+
112
+ ```ts
113
+ type ContentItem =
114
+ | { kind: 'text'; value: string }
115
+ | { kind: 'emoji'; value: string }
116
+ | { kind: 'shape'; value: 'circle' | 'square' | 'triangle' | 'star' | 'diamond'; radius?: number }
117
+ | { kind: 'path'; value: string }; // SVG 路径
118
+ ```
119
+
120
+ ## 动效
121
+
122
+ | Mode | 行为 |
123
+ | --- | --- |
124
+ | `drift` | 漂移 + 碰壁反弹 + 两两碰撞 |
125
+ | `roam` | 漂移 + 反弹,主体彼此穿过 |
126
+ | `static` | 淡入后静止 |
127
+ | `ticker-left` / `ticker-right` | 跑马灯:主体排成一行循环滚动 |
128
+
129
+ ## 开发
130
+
131
+ ```bash
132
+ npm install
133
+ npm run dev # playground 在 http://localhost:5199/examples/index.html
134
+ npm test # vitest(物理 + 内容归一化)
135
+ npm run lint
136
+ npm run typecheck
137
+ npm run build # tsc → dist/
138
+ ```
139
+
140
+ playground 在任何现代浏览器都能跑——把窗口跨过 372 / 640px 看断点变化,并在 DevTools → Console 里确认刷新无报错。
141
+
142
+ ## 架构
143
+
144
+ 薄薄一层 React 包装,下面是框架无关的 Canvas 核心。四个画布:点阵、它的白色 alpha 遮罩、被渐变照亮的主体、以及合成层。点阵和遮罩只在尺寸变化时重绘;发光层每帧合成。
145
+
146
+ ## 说明 / 限制
147
+
148
+ - **仅 ESM** —— 没有 CommonJS 构建;请用支持 ESM 的打包器。
149
+ - **emoji 保持自身颜色** —— 它们不跟随发光渐变(文本和形状会变色)。
150
+ - **`path` 只支持基础用法** —— 传入一条已大致以原点为中心、缩放到约 ±100 单位的路径。
151
+ - 碰撞是简化的动量交换,不是完整的物理冲量。
152
+
153
+ ## 许可证
154
+
155
+ MIT
@@ -0,0 +1,16 @@
1
+ import type { DotmoteProps } from './types.js';
2
+ /**
3
+ * A brand-agnostic dotted-matrix glow background.
4
+ *
5
+ * Renders a full-size lattice of dots with a few drifting "bodies"
6
+ * (letters / emoji / shapes) revealed *through* the dots as glowing,
7
+ * dot-assembled light blobs. The colors and content are fully configurable and
8
+ * default to neutral, brand-free values.
9
+ *
10
+ * SSR-safe: the component renders an empty `<canvas>` during server / static
11
+ * rendering; every `window` / `document` / `ResizeObserver` / DPR touch happens
12
+ * inside a `useEffect` after mount.
13
+ */
14
+ export declare function Dotmote(props: DotmoteProps): JSX.Element;
15
+ export default Dotmote;
16
+ //# sourceMappingURL=Dotmote.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Dotmote.d.ts","sourceRoot":"","sources":["../src/Dotmote.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAIV,YAAY,EACb,MAAM,YAAY,CAAC;AAqEpB;;;;;;;;;;;GAWG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,GAAG,CAAC,OAAO,CAuFxD;AAED,eAAe,OAAO,CAAC"}
@@ -0,0 +1,142 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { resolveTheme } from './themes.js';
4
+ import { normalizeItems } from './core/physics.js';
5
+ import { createDotMatrixCore, DEFAULT_BREAKPOINTS, } from './core/createDotMatrixCore.js';
6
+ const DEFAULT_ITEMS_INPUT = ['A', 'B', 'C', 'D', 'E'];
7
+ const DEFAULT_FONT_FAMILY = '"Trebuchet MS", ui-rounded, sans-serif';
8
+ const DEFAULT_DOT = 'rgba(128, 128, 128, 0.5)';
9
+ const DEFAULT_GLOW = [
10
+ 'rgba(120, 120, 120, 0.9)',
11
+ 'rgba(170, 170, 170, 0.9)',
12
+ 'rgba(220, 220, 220, 0.9)',
13
+ ];
14
+ function resolveItemBodies(props) {
15
+ if (props.values !== undefined) {
16
+ const out = [];
17
+ for (const ch of Array.from(props.values)) {
18
+ if (ch.trim().length === 0)
19
+ continue;
20
+ out.push({ kind: 'text', value: ch });
21
+ }
22
+ return out;
23
+ }
24
+ return normalizeItems(props.items ?? DEFAULT_ITEMS_INPUT);
25
+ }
26
+ function buildOptions(props, dark = false) {
27
+ const theme = resolveTheme(props.theme, dark);
28
+ const glow = theme.activeDotColor
29
+ ? [theme.activeDotColor, theme.activeDotColor, theme.activeDotColor]
30
+ : theme.glow ?? DEFAULT_GLOW;
31
+ return {
32
+ items: resolveItemBodies(props),
33
+ dot: theme.dotColor ?? DEFAULT_DOT,
34
+ glow,
35
+ background: theme.background,
36
+ dotRadius: props.dotRadius,
37
+ glowStrength: props.glowStrength ?? props.glowAlpha ?? 1,
38
+ speed: props.speed ?? 1,
39
+ motion: props.motion ?? 'drift',
40
+ fontFamily: props.fontFamily ?? DEFAULT_FONT_FAMILY,
41
+ fontSize: props.fontSize,
42
+ fontSizeOverride: props.fontSizeOverride,
43
+ fontSizeMin: props.fontSizeMin ?? 207,
44
+ fontSizeMax: props.fontSizeMax ?? 270,
45
+ breakpoints: { ...DEFAULT_BREAKPOINTS, ...(props.breakpoints ?? {}) },
46
+ spacingScale: props.spacingScale ?? 1,
47
+ introDurationMs: props.introDurationMs ?? 520,
48
+ };
49
+ }
50
+ const containerBase = {
51
+ position: 'absolute',
52
+ inset: 0,
53
+ overflow: 'hidden',
54
+ pointerEvents: 'none',
55
+ };
56
+ const canvasStyle = {
57
+ width: '100%',
58
+ height: '100%',
59
+ display: 'block',
60
+ };
61
+ /**
62
+ * A brand-agnostic dotted-matrix glow background.
63
+ *
64
+ * Renders a full-size lattice of dots with a few drifting "bodies"
65
+ * (letters / emoji / shapes) revealed *through* the dots as glowing,
66
+ * dot-assembled light blobs. The colors and content are fully configurable and
67
+ * default to neutral, brand-free values.
68
+ *
69
+ * SSR-safe: the component renders an empty `<canvas>` during server / static
70
+ * rendering; every `window` / `document` / `ResizeObserver` / DPR touch happens
71
+ * inside a `useEffect` after mount.
72
+ */
73
+ export function Dotmote(props) {
74
+ const { className, class: cls, style, ariaHidden = true, } = props;
75
+ // `class` is an HTML-style alias for `className` (React uses `className`).
76
+ const wrapperClass = className ?? cls;
77
+ // OS color scheme, used only when `theme` resolves to `auto`. Defaults to
78
+ // light so server / static rendering stays SSR-safe (no `window`/`matchMedia`
79
+ // at render time); it syncs to the real value after mount.
80
+ const [dark, setDark] = useState(false);
81
+ useEffect(() => {
82
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
83
+ return;
84
+ }
85
+ const mq = window.matchMedia('(prefers-color-scheme: dark)');
86
+ const update = () => setDark(mq.matches);
87
+ update();
88
+ mq.addEventListener('change', update);
89
+ return () => mq.removeEventListener('change', update);
90
+ }, []);
91
+ const containerRef = useRef(null);
92
+ const canvasRef = useRef(null);
93
+ const coreRef = useRef(null);
94
+ // Deliberately enumerate the primitive props (rather than the whole `props`
95
+ // object) so inline `theme` / `breakpoints` literals don't rebuild options
96
+ // every render. buildOptions reads only the fields listed below.
97
+ // eslint-disable-next-line react-hooks/exhaustive-deps
98
+ const options = useMemo(() => buildOptions(props, dark), [
99
+ props.values,
100
+ props.items,
101
+ props.theme,
102
+ props.dotRadius,
103
+ props.glowStrength,
104
+ props.glowAlpha,
105
+ props.speed,
106
+ props.motion,
107
+ props.fontFamily,
108
+ props.fontSize,
109
+ props.fontSizeOverride,
110
+ props.fontSizeMin,
111
+ props.fontSizeMax,
112
+ props.breakpoints,
113
+ props.spacingScale,
114
+ props.introDurationMs,
115
+ dark,
116
+ ]);
117
+ // Keep the latest options available to the mount effect without re-running it.
118
+ const optionsRef = useRef(options);
119
+ optionsRef.current = options;
120
+ // Create / destroy the core exactly once per mounted instance.
121
+ useEffect(() => {
122
+ const canvas = canvasRef.current;
123
+ const container = containerRef.current;
124
+ if (!canvas || !container)
125
+ return;
126
+ const core = createDotMatrixCore(canvas, container, optionsRef.current);
127
+ coreRef.current = core;
128
+ core.start();
129
+ return () => {
130
+ coreRef.current = null;
131
+ core.destroy();
132
+ };
133
+ // eslint-disable-next-line react-hooks/exhaustive-deps
134
+ }, []);
135
+ // Hot-update on option change (theme colors, content, layout, ...).
136
+ useEffect(() => {
137
+ coreRef.current?.configure(optionsRef.current);
138
+ }, [options]);
139
+ return (_jsx("div", { ref: containerRef, className: wrapperClass, style: { ...containerBase, ...style }, "aria-hidden": ariaHidden, children: _jsx("canvas", { ref: canvasRef, style: canvasStyle }) }));
140
+ }
141
+ export default Dotmote;
142
+ //# sourceMappingURL=Dotmote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Dotmote.js","sourceRoot":"","sources":["../src/Dotmote.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { ContentItem, CoreOptions } from '../types.js';
2
+ /** Responsive defaults, mirrored from the React component's props. */
3
+ export declare const DEFAULT_BREAKPOINTS: {
4
+ readonly small: 372;
5
+ readonly medium: 640;
6
+ readonly smallSpacing: 8;
7
+ readonly mediumSpacing: 9;
8
+ readonly largeSpacing: 12;
9
+ };
10
+ /** Default content: five neutral letters. */
11
+ export declare const DEFAULT_ITEMS: ContentItem[];
12
+ export interface DotMatrixCoreConfig {
13
+ /** Create an offscreen canvas. Defaults to `document.createElement('canvas')`. */
14
+ canvasFactory?: () => HTMLCanvasElement;
15
+ /** Watch the container with `ResizeObserver`. Default `true`. */
16
+ observeResize?: boolean;
17
+ }
18
+ export interface DotMatrixCoreHandle {
19
+ /** Merge new resolved options. Re-renders static layers; rebuilds bodies only on items/geometry change. */
20
+ configure(options: CoreOptions): void;
21
+ /** Measure the container (unless width/height are passed) and re-render static layers. */
22
+ resize(cssWidth?: number, cssHeight?: number): void;
23
+ start(): void;
24
+ stop(): void;
25
+ destroy(): void;
26
+ }
27
+ /**
28
+ * Framework-agnostic canvas core for the dot-matrix glow background.
29
+ *
30
+ * Owns four canvases (three offscreen) and the full render/physic/lifecycle
31
+ * loop. The React component (and any vanilla / miniapp adapter) is a thin
32
+ * wrapper over this. All browser globals (`window`, `document`,
33
+ * `ResizeObserver`, `requestAnimationFrame`) are only touched once a handle is
34
+ * created at runtime — never at module scope — so import-time SSR is safe.
35
+ */
36
+ export declare function createDotMatrixCore(canvas: HTMLCanvasElement, container?: HTMLElement | null, initialOptions?: CoreOptions, config?: DotMatrixCoreConfig): DotMatrixCoreHandle;
37
+ //# sourceMappingURL=createDotMatrixCore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createDotMatrixCore.d.ts","sourceRoot":"","sources":["../../src/core/createDotMatrixCore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,WAAW,EAAE,WAAW,EAAc,MAAM,aAAa,CAAC;AAU9E,sEAAsE;AACtE,eAAO,MAAM,mBAAmB;;;;;;CAMtB,CAAC;AAEX,6CAA6C;AAC7C,eAAO,MAAM,aAAa,EAAE,WAAW,EAMtC,CAAC;AAwBF,MAAM,WAAW,mBAAmB;IAClC,kFAAkF;IAClF,aAAa,CAAC,EAAE,MAAM,iBAAiB,CAAC;IACxC,iEAAiE;IACjE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,2GAA2G;IAC3G,SAAS,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IACtC,0FAA0F;IAC1F,MAAM,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpD,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,IAAI,IAAI,CAAC;IACb,OAAO,IAAI,IAAI,CAAC;CACjB;AAiHD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,iBAAiB,EACzB,SAAS,CAAC,EAAE,WAAW,GAAG,IAAI,EAC9B,cAAc,CAAC,EAAE,WAAW,EAC5B,MAAM,GAAE,mBAAwB,GAC/B,mBAAmB,CAibrB"}