@liiift-studio/magnettype 1.1.2 → 1.1.4

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/%40liiift-studio%2Fmagnettype.svg)](https://www.npmjs.com/package/@liiift-studio/magnettype) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![part of liiift type-tools](https://img.shields.io/badge/liiift-type--tools-blueviolet)](https://github.com/Liiift-Studio/type-tools)
4
4
 
5
- CSS `font-variation-settings` applies a single value to the whole element — there is no native way to drive axis values per word from cursor proximity, or to selectively widen visually confusable characters for legibility. magnetType adds both.
5
+ CSS `font-variation-settings` applies a single value to the whole element — there is no native way to drive axis values per word from cursor proximity, to selectively widen visually confusable characters for legibility, or to vary weight per-character across a block element. magnetType adds all three.
6
6
 
7
7
  **[magnettype.com](https://magnettype.com)** · [npm](https://www.npmjs.com/package/@liiift-studio/magnettype) · [GitHub](https://github.com/Liiift-Studio/MagnetType)
8
8
 
@@ -16,15 +16,15 @@ TypeScript · Zero dependencies · React + Vanilla JS
16
16
  npm install @liiift-studio/magnettype
17
17
  ```
18
18
 
19
+ > **Variable font required:** magnetType sets `font-variation-settings` per word or per character. The target font must support the axes you specify (e.g. a font with a `wght` axis for weight-based effects, or a `wdth` axis for legibility mode). The effect is invisible with non-variable fonts.
20
+
19
21
  ---
20
22
 
21
23
  ## Usage
22
24
 
23
- > **Next.js App Router:** this library uses browser APIs. Add `"use client"` to any component file that imports from it.
24
-
25
- > **Variable font required:** magnetType sets `font-variation-settings` per word or per character. The target font must support the axes you specify (e.g. a font with a `wght` axis for weight-based field effects, or a `wdth` axis for legibility mode). The effect is invisible with fonts that do not have variable axis support.
25
+ ### React field mode (`MagnetTypeText`)
26
26
 
27
- ### React component field mode
27
+ Per-word cursor-proximity weight variation driven by a continuous rAF loop.
28
28
 
29
29
  ```tsx
30
30
  import { MagnetTypeText } from '@liiift-studio/magnettype'
@@ -40,19 +40,67 @@ import { MagnetTypeText } from '@liiift-studio/magnettype'
40
40
  </MagnetTypeText>
41
41
  ```
42
42
 
43
+ ### React — block mode (`MagnetBlock`)
44
+
45
+ Per-character cursor-proximity weight variation. Works with mixed content (inline elements, links, `<code>`, etc.) inside any block element. Characters are wrapped as React elements — no DOM mutation.
46
+
47
+ ```tsx
48
+ import { MagnetBlock } from '@liiift-studio/magnettype'
49
+
50
+ // Per-character spread — each character responds to cursor distance
51
+ <MagnetBlock
52
+ spreadRadius={200}
53
+ minWeight={300}
54
+ maxWeight={700}
55
+ >
56
+ Typography that responds to presence.
57
+ </MagnetBlock>
58
+
59
+ // Whole-element gate — the effect only activates when the cursor is within proximityRadius of the element edge
60
+ <MagnetBlock
61
+ proximityRadius={120}
62
+ minWeight={300}
63
+ maxWeight={700}
64
+ >
65
+ Weight rises when the cursor enters.
66
+ </MagnetBlock>
67
+
68
+ // Both combined — proximity gates the spread effect
69
+ <MagnetBlock
70
+ proximityRadius={200}
71
+ spreadRadius={120}
72
+ minWeight={300}
73
+ maxWeight={700}
74
+ >
75
+ Only spreads when the cursor is close.
76
+ </MagnetBlock>
77
+ ```
78
+
79
+ **`MagnetBlock` props:**
80
+
81
+ | Prop | Default | Description |
82
+ |------|---------|-------------|
83
+ | `as` | `'p'` | HTML element to render — `'h1'`, `'div'`, `'span'`, etc. |
84
+ | `minWeight` | `300` | `wght` axis value at rest (cursor beyond any radius) |
85
+ | `maxWeight` | `600` | `wght` axis value at peak (cursor directly over the character) |
86
+ | `spreadRadius` | — | Pixel distance from the cursor within which each **character's** weight rises to `maxWeight`. When omitted, per-character splitting is skipped |
87
+ | `proximityRadius` | — | Pixel distance from the element **edge** that gates the effect. Without `spreadRadius`, drives a whole-element weight ramp. With `spreadRadius`, acts as an outer gate — the spread only fires while the cursor is within this distance |
88
+ | `fixedAxes` | `{}` | Additional axis values to hold constant in every `font-variation-settings` string (e.g. `{ opsz: 144 }`) |
89
+ | `className` | — | Forwarded to the root element |
90
+ | `style` | — | Merged with the root element's style; `fontVariationSettings` at `minWeight` is set as the base |
91
+
43
92
  ### React hook — field mode
44
93
 
45
94
  ```tsx
46
95
  import { useMagnetType } from '@liiift-studio/magnettype'
47
96
 
48
- // Inside a React component:
49
97
  const ref = useMagnetType({ mode: 'field', axes: { wght: [300, 700] }, radius: 150 })
50
98
  return <p ref={ref}>{children}</p>
51
99
  ```
52
100
 
53
- The hook starts the cursor-proximity rAF loop on mount and tears it down cleanly on unmount. In field mode, no `ResizeObserver` is needed — the loop reads live `getBoundingClientRect` positions on every frame. After fonts load (`document.fonts.ready`), the hook re-runs to ensure measurements are taken on the loaded font.
101
+ The hook starts the cursor-proximity rAF loop on mount and tears it down cleanly on unmount. After fonts load (`document.fonts.ready`), the hook re-runs to ensure measurements are taken on the loaded font.
54
102
 
55
- ### React component — legibility mode
103
+ ### React — legibility mode
56
104
 
57
105
  ```tsx
58
106
  import { MagnetTypeText } from '@liiift-studio/magnettype'
@@ -112,7 +160,7 @@ ro.observe(el)
112
160
  ### TypeScript
113
161
 
114
162
  ```ts
115
- import type { MagnetTypeOptions, FalloffType, MagnetModeType } from '@liiift-studio/magnettype'
163
+ import type { MagnetTypeOptions, FalloffType, MagnetModeType, MagnetBlockProps } from '@liiift-studio/magnettype'
116
164
 
117
165
  const fieldOpts: MagnetTypeOptions = {
118
166
  mode: 'field',
@@ -130,18 +178,18 @@ const legibilityOpts: MagnetTypeOptions = {
130
178
 
131
179
  ---
132
180
 
133
- ## Options
181
+ ## Field mode options (`MagnetTypeText` / `useMagnetType` / vanilla JS)
134
182
 
135
183
  | Option | Default | Description |
136
184
  |--------|---------|-------------|
137
- | `mode` | `'field'` | `'field'` — cursor proximity drives per-word `font-variation-settings` via a continuous rAF loop. `'legibility'` — static per-character `wdth` boost applied to visually confusable characters; no cursor interaction needed |
138
- | `axes` | `{ wght: [300, 500] }` | *(field mode only)* Map of axis tag → `[restValue, peakValue]`. `restValue` is applied when the cursor is beyond the radius; `peakValue` when the cursor is directly over the word. Multiple axes are supported simultaneously |
139
- | `radius` | `120` | *(field mode only)* Pixel radius over which the field effect fades. Words with their centre beyond this distance from the cursor receive `restValue` |
140
- | `falloff` | `'quadratic'` | *(field mode only)* Falloff curve. `'linear'` strength decreases linearly with distance. `'quadratic'` strength decreases as distance², giving a tighter hot zone and a sharper peak feel |
141
- | `magnetMode` | `'attract'` | *(field mode only)* `'attract'` — words near the cursor approach `peakValue`. `'repel'` — words near the cursor stay at `restValue`; words farther away approach `peakValue` |
142
- | `wdthBoost` | `6` | *(legibility mode only)* `wdth` axis units added to confusable characters, scaled by risk level. Risk 3 characters (`i l 1 I`) receive the full boost; risk 2 characters (`r 0 O`) receive ⅔; risk 1 characters (`n m o b d p q c e`) receive ⅓ |
143
- | `transitionMs` | `0` | Duration in milliseconds for the CSS transition back to rest values when the cursor leaves (or a touch ends). `0` preserves the existing instant snap. When > 0, `font-variation-settings` animates back over the given duration using `ease` easing. The transition is cleared immediately on the next `mousemove`/`touchmove` so live tracking is not delayed |
144
- | `as` | `'p'` | HTML element to render, e.g. `'h1'`, `'div'`, `'span'`. Accepts any valid React element type. *(React component only)* |
185
+ | `mode` | `'field'` | `'field'` — cursor proximity drives per-word `font-variation-settings` via a continuous rAF loop. `'legibility'` — static per-character `wdth` boost on visually confusable characters |
186
+ | `axes` | `{ wght: [300, 500] }` | *(field mode)* Map of axis tag → `[restValue, peakValue]` |
187
+ | `radius` | `120` | *(field mode)* Pixel radius over which the field effect fades from each word's centre |
188
+ | `falloff` | `'quadratic'` | *(field mode)* `'linear'` or `'quadratic'` falloff curve |
189
+ | `magnetMode` | `'attract'` | *(field mode)* `'attract'` — near words approach `peakValue`. `'repel'` — near words stay at `restValue`, far words approach `peakValue` |
190
+ | `wdthBoost` | `6` | *(legibility mode)* `wdth` units added to confusable characters, scaled by risk: `il1I` (risk 3) get the full boost; `r 0 O` (risk 2) get ⅔; `n m o b d p q c e` (risk 1) get ⅓ |
191
+ | `transitionMs` | `0` | Duration in ms for CSS transition back to rest when cursor leaves. `0` = instant snap. Cleared on the next `mousemove` so live tracking is not delayed |
192
+ | `as` | `'p'` | HTML element to render. *(React component only)* |
145
193
 
146
194
  ---
147
195
 
@@ -149,30 +197,32 @@ const legibilityOpts: MagnetTypeOptions = {
149
197
 
150
198
  ### Field mode
151
199
 
152
- On activation, magnetType wraps each word in the element in an `mt-word` span. A `mousemove` listener records the cursor's `clientX`/`clientY` coordinates, and a `requestAnimationFrame` loop runs continuously while the cursor is inside the element. Each frame, the loop batch-reads every word span's `getBoundingClientRect`, computes the Euclidean distance from the cursor to each word's centre, and maps that distance through the falloff formula to a normalised strength value in `[0, 1]`:
200
+ On activation, magnetType wraps each word in an `mt-word` span. A `mousemove` listener records cursor coordinates, and a `requestAnimationFrame` loop runs while the cursor is inside the element. Each frame, the loop batch-reads every word span's `getBoundingClientRect`, computes Euclidean distance from cursor to word centre, and maps it through the falloff formula:
153
201
 
154
202
  ```
155
203
  normalised = max(0, 1 − distance / radius)
156
204
  strength = normalised² (quadratic) or normalised (linear)
157
205
  ```
158
206
 
159
- Each word's `font-variation-settings` is then set to the interpolated axis value between `restValue` and `peakValue`, with `attract` mode mapping `strength=1` to `peakValue` and `repel` mode inverting that relationship. Reads are batched before writes on every frame to avoid layout thrashing. When the cursor leaves the element, the loop fires one final frame to reset all words to `restValue`, then idles.
207
+ Each word's `font-variation-settings` interpolates between `restValue` and `peakValue` at that strength. Reads are batched before writes on every frame to avoid layout thrashing. When the cursor leaves, one final frame resets all words to `restValue`.
208
+
209
+ ### Block mode (`MagnetBlock`)
160
210
 
161
- The base `fontVariationSettings` string is read from the computed style of the element once at startup, and each per-word override patches only the affected axes all parent-defined axes are preserved.
211
+ `MagnetBlock` splits string children into per-character `<span>` elements during the React render pass using `useMemo` no DOM mutation. Callback refs collect each span element. On `mousemove` (and on `scroll`, using the stored last position), the component reads each span's `getBoundingClientRect`, computes cursor-to-character-centre distance, and sets `font-variation-settings` directly on the span's style. This is passive and batched per frame via the event handler.
212
+
213
+ `proximityRadius` measures cursor distance to the element **edge** (not its centre) — useful as an outer gate so the effect only fires when the cursor is actually near the block. `spreadRadius` measures cursor distance to each **character centre** — controls how wide the weight gradient spreads around the cursor within the block. Both are independent and combinable.
162
214
 
163
215
  ### Legibility mode
164
216
 
165
- magnetType scans all text nodes in the element using recursive `childNodes` traversal and checks each character against a built-in confusable character table. Confusable characters are grouped into three risk levels: `il1I` (risk 3, high confusion), `r 0 O` and related pairs (risk 2), and `n m o b d p q c e` (risk 1, low confusion). Each confusable character is wrapped in an `mt-char` span with a `wdth` axis boost proportional to its risk level — making similar-looking characters slightly wider and more distinct. Non-confusable characters pass through as plain text nodes, with adjacent non-confusable characters consolidated into single text nodes to keep the DOM lean.
217
+ magnetType scans text nodes recursively and checks each character against a built-in confusable character table. Confusable characters are wrapped in `mt-char` spans with a `wdth` boost proportional to risk level. Non-confusable characters pass through as plain text nodes.
166
218
 
167
219
  ### No layout shift
168
220
 
169
- In field mode, the rAF loop drives only `font-variation-settings` values on per-word spans it does not change element widths, margins, padding, or position. If you use only a `wght` axis, advance widths are not affected and no reflow occurs. If you include a `wdth` axis, character advance widths will change, which may cause lines to reflow. To prevent this, consider constraining axis ranges or combining with a `scaleX` transform on the container.
170
-
171
- In legibility mode, the `wdth` axis boost widens individual confusable characters, which shifts surrounding characters slightly. This is intentional — the point is to make the characters physically wider and more distinct. The shift is small by default (`wdthBoost: 6`) and does not cause line breaks to change.
221
+ Field mode and block mode drive only `font-variation-settings` on per-word or per-character spans. If you use only a `wght` axis, advance widths are unaffected and no reflow occurs. If you include a `wdth` axis, character advance widths change and lines may reflow consider constraining axis ranges or pairing with a `scaleX` transform.
172
222
 
173
223
  ### `prefers-reduced-motion`
174
224
 
175
- Field mode respects `prefers-reduced-motion: reduce`. If the media query matches at the time `startMagnetType` is called, the function returns immediately without wrapping words or starting the rAF loop, and returns a no-op stop function. Legibility mode is a static DOM transformation and is not affected by this preference.
225
+ Field mode respects `prefers-reduced-motion: reduce`. If the media query matches at activation time, the function returns immediately without wrapping words or starting the rAF loop. Legibility mode and block mode are not affected.
176
226
 
177
227
  ---
178
228
 
@@ -188,10 +238,10 @@ The package itself has zero runtime dependencies. Do not remove this entry.
188
238
 
189
239
  ## Future improvements
190
240
 
191
- - **Custom confusable table** — allow callers to pass their own `Record<string, number>` to override or extend the built-in character risk map for language- or font-specific tuning
192
- - **Axis clamping** — optional per-axis min/max clamp to prevent axis values from exceeding a font's supported range, avoiding undefined browser rendering behaviour
193
- - **SSR hydration** — pre-render legibility mode markup on the server so boosted characters are present from first paint without a client-side flash
241
+ - **Custom confusable table** — allow callers to pass their own `Record<string, number>` to override or extend the built-in character risk map
242
+ - **Axis clamping** — optional per-axis min/max clamp to prevent values from exceeding a font's supported range
243
+ - **SSR hydration** — pre-render legibility mode markup on the server so boosted characters are present from first paint
194
244
 
195
245
  ---
196
246
 
197
- Current version: 0.1.3
247
+ Current version: 1.1.3
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("react"),H=require("react/jsx-runtime"),G={i:3,l:3,1:3,I:3,r:2,n:1,m:1,0:2,O:2,o:1,b:1,d:1,p:1,q:1,c:1,e:1},R={word:"mt-word",char:"mt-char",probe:"mt-probe"},$={axes:{wght:[300,500]},radius:120,falloff:"quadratic",magnetMode:"attract",wdthBoost:6,scope:"document"};function D(t,n=[]){return t.nodeType===Node.TEXT_NODE?n.push(t):t.childNodes.forEach(o=>D(o,n)),n}function P(t,n,o){if(!t||t==="normal")return`"${n}" ${o}`;const u=new RegExp(`(["'])${n}\\1\\s+[\\d.eE+-]+`),c=`"${n}" ${o}`;return u.test(t)?t.replace(u,c):`${t}, ${c}`}function K(t,n){let o=t;for(const[u,c]of Object.entries(n))o=P(o,u,c);return o}function U(t,n,o){if(n.opacity!==void 0){const[u,c]=n.opacity;t.style.opacity=String(u+(c-u)*o)}n.italic===!0&&(t.style.fontStyle=o>.5?"italic":"")}function _(t,n){n.opacity!==void 0&&(t.style.opacity=String(n.opacity[0])),n.italic===!0&&(t.style.fontStyle="")}function z(t){const n=t.cloneNode(!0),o=n.querySelectorAll(`.${R.word}, .${R.char}`);return Array.from(o).reverse().forEach(c=>{const a=c.parentNode;if(a){for(;c.firstChild;)a.insertBefore(c.firstChild,c);a.removeChild(c)}}),n.innerHTML}function ot(t,n){t.innerHTML=n}function Q(t,n,o={}){if(typeof window>"u")return()=>{};if(window.matchMedia("(prefers-reduced-motion: reduce)").matches)return t.innerHTML=n,()=>{};const u=o.wdthBoost??$.wdthBoost,c=o.radius??$.radius,a=o.falloff??$.falloff,T=o.scope??$.scope,y=o.props,l=o.transitionMs??0,C=window.scrollY;t.innerHTML=n;const N=getComputedStyle(t).fontVariationSettings,F=N.match(/"wdth"\s+([\d.eE+-]+)/),g=F?parseFloat(F[1]):100,q=D(t),w=[];for(const r of q){const e=r.textContent??"";if(!e||!e.split("").some(m=>m in G))continue;const A=document.createDocumentFragment();for(const m of e){const k=G[m];if(k===void 0){const L=A.lastChild;L&&L.nodeType===Node.TEXT_NODE?L.textContent+=m:A.appendChild(document.createTextNode(m))}else{const L=document.createElement("span");L.className=R.char,L.style.fontVariationSettings=P(N,"wdth",g),L.textContent=m,A.appendChild(L),w.push({span:L,riskLevel:k})}}r.parentNode.replaceChild(A,r)}if(requestAnimationFrame(()=>{typeof window<"u"&&Math.abs(window.scrollY-C)>2&&window.scrollTo({top:C,behavior:"instant"})}),w.length===0)return()=>{};y&&w.forEach(({span:r})=>_(r,y));let h=-9999,V=-9999,E=!1,i=0,B=!0,S=null;function s(){if(!B)return;if(!E){w.forEach(({span:e})=>{l>0&&(e.style.transition=`font-variation-settings ${l}ms ease`),e.style.fontVariationSettings=P(N,"wdth",g),y&&_(e,y)}),l>0&&(S!==null&&clearTimeout(S),S=setTimeout(()=>{w.forEach(({span:e})=>{e.style.transition=""}),S=null},l)),i=0;return}const r=w.map(({span:e})=>e.getBoundingClientRect());w.forEach(({span:e,riskLevel:x},A)=>{e.style.transition="";const m=r[A],k=m.left+m.width/2,L=m.top+m.height/2,j=Math.sqrt((h-k)**2+(V-L)**2),O=Math.max(0,1-j/c),Y=a==="quadratic"?O*O:O,X=u*(x/3)*Y;e.style.fontVariationSettings=P(N,"wdth",g+X),y&&U(e,y,Y)}),i=requestAnimationFrame(s)}function M(r){h=r.clientX,V=r.clientY,E||(E=!0),i===0&&(i=requestAnimationFrame(s))}function v(){E=!1,i===0&&(i=requestAnimationFrame(s))}function f(r){r.touches.length!==0&&(h=r.touches[0].clientX,V=r.touches[0].clientY,E||(E=!0),i===0&&(i=requestAnimationFrame(s)))}function b(){E=!1,i===0&&(i=requestAnimationFrame(s))}const d=T==="document"?document:t;return d.addEventListener("mousemove",M),d.addEventListener("mouseleave",v),d.addEventListener("touchmove",f,{passive:!0}),d.addEventListener("touchend",b),()=>{B=!1,cancelAnimationFrame(i),S!==null&&clearTimeout(S),d.removeEventListener("mousemove",M),d.removeEventListener("mouseleave",v),d.removeEventListener("touchmove",f),d.removeEventListener("touchend",b),t.innerHTML=n}}function Z(t,n,o={}){if(typeof window>"u")return()=>{};if(window.matchMedia("(prefers-reduced-motion: reduce)").matches)return t.innerHTML=n,()=>{};const u=o.axes??$.axes,c=o.radius??$.radius,a=o.falloff??$.falloff,T=o.magnetMode??$.magnetMode,y=o.scope??$.scope,l=o.props,C=o.transitionMs??0,N=window.scrollY;t.innerHTML=n;const F=D(t),g=[];for(const r of F){const e=r.textContent??"";if(!e.trim())continue;const x=e.split(/(\S+)/),A=document.createDocumentFragment();for(let m=0;m<x.length;m+=2){const k=x[m],L=x[m+1];if(!L)continue;const O=x[m+3]===void 0?x[m+2]??"":"",Y=document.createElement("span");Y.className=R.word,Y.textContent=k+L+O,A.appendChild(Y),g.push(Y)}r.parentNode.replaceChild(A,r)}if(requestAnimationFrame(()=>{typeof window<"u"&&Math.abs(window.scrollY-N)>2&&window.scrollTo({top:N,behavior:"instant"})}),g.length===0)return()=>{};const q=getComputedStyle(t).fontVariationSettings,w=K(q,Object.fromEntries(Object.entries(u).map(([r,[e]])=>[r,e])));g.forEach(r=>{r.style.fontVariationSettings=w,l&&_(r,l)});let h=-9999,V=-9999,E=!1,i=0,B=!0,S=null;function s(){if(!B)return;if(!E){g.forEach(e=>{C>0&&(e.style.transition=`font-variation-settings ${C}ms ease`),e.style.fontVariationSettings=w,l&&_(e,l)}),C>0&&(S!==null&&clearTimeout(S),S=setTimeout(()=>{g.forEach(e=>{e.style.transition=""}),S=null},C)),i=0;return}const r=g.map(e=>e.getBoundingClientRect());g.forEach((e,x)=>{e.style.transition="";const A=r[x],m=A.left+A.width/2,k=A.top+A.height/2,L=Math.sqrt((h-m)**2+(V-k)**2),j=Math.max(0,1-L/c),O=a==="quadratic"?j*j:j,Y=T==="repel"?1-O:O,X={};for(const I of Object.keys(u)){const[J,nt]=u[I]??[300,500];X[I]=J+(nt-J)*Y}e.style.fontVariationSettings=K(q,X),l&&U(e,l,O)}),i=requestAnimationFrame(s)}function M(r){h=r.clientX,V=r.clientY,E||(E=!0),i===0&&(i=requestAnimationFrame(s))}function v(){E=!1,i===0&&(i=requestAnimationFrame(s))}function f(r){r.touches.length!==0&&(h=r.touches[0].clientX,V=r.touches[0].clientY,E||(E=!0),i===0&&(i=requestAnimationFrame(s)))}function b(){E=!1,i===0&&(i=requestAnimationFrame(s))}const d=y==="document"?document:t;return d.addEventListener("mousemove",M),d.addEventListener("mouseleave",v),d.addEventListener("touchmove",f,{passive:!0}),d.addEventListener("touchend",b),()=>{B=!1,cancelAnimationFrame(i),S!==null&&clearTimeout(S),d.removeEventListener("mousemove",M),d.removeEventListener("mouseleave",v),d.removeEventListener("touchmove",f),d.removeEventListener("touchend",b),t.innerHTML=n}}function W(t){const n=p.useRef(null),o=p.useRef(null),u=p.useRef(t);u.current=t;const c=p.useRef(null),a=t.mode??"field",{axes:T,radius:y,falloff:l,magnetMode:C,wdthBoost:N,scope:F}=t,g=T?JSON.stringify(T):void 0,q=t.props?JSON.stringify(t.props):void 0,w=p.useCallback(()=>{const h=n.current;if(!h)return;o.current===null&&(o.current=z(h)),c.current&&(c.current(),c.current=null),(u.current.mode??"field")==="field"?c.current=Z(h,o.current,u.current):c.current=Q(h,o.current,u.current)},[a,g,y,l,C,N,F,q]);return p.useLayoutEffect(()=>(w(),()=>{c.current&&(c.current(),c.current=null)}),[w]),p.useEffect(()=>{document.fonts.ready.then(w)},[w]),n}const tt=p.forwardRef(function({children:n,as:o="p",className:u,style:c,...a},T){const y=W(a),l=p.useCallback(C=>{y.current=C,typeof T=="function"?T(C):T&&(T.current=C)},[T]);return H.jsx(o,{ref:l,className:u,style:c,children:n})});tt.displayName="MagnetTypeText";const et=p.forwardRef(function({children:n,as:o="p",className:u,style:c,minWeight:a=300,maxWeight:T=600,proximityRadius:y,spreadRadius:l,fixedAxes:C={}},N){const F=p.useRef(null),g=p.useRef(null),q=p.useRef([]),w=p.useCallback(s=>{F.current=s,typeof N=="function"?N(s):N&&(N.current=s)},[N]);function h(s){const M=[`'wght' ${s.toFixed(0)}`];for(const[v,f]of Object.entries(C))M.push(`'${v}' ${f}`);return M.join(", ")}const V=p.useMemo(()=>{if(!l)return n;q.current=[];let s=0;function M(v){if(typeof v=="string")return[...v].map(f=>{if(/\s/.test(f))return f;const b=s++;return H.jsx("span",{ref:d=>{q.current[b]=d},style:{fontVariationSettings:h(a)},children:f},b)});if(Array.isArray(v))return v.map((f,b)=>H.jsx(p.Fragment,{children:M(f)},b));if(p.isValidElement(v)){const f=v;if(f.props.children!==void 0)return p.cloneElement(f,{},M(f.props.children))}return v}return M(n)},[n,l,a,JSON.stringify(C)]);function E(s,M){const v=F.current;if(!v)return;const f=v.getBoundingClientRect(),b=Math.max(f.left-s,0,s-f.right),d=Math.max(f.top-M,0,M-f.bottom),r=Math.sqrt(b*b+d*d);if(y!==void 0&&!l){const x=1-(1-Math.max(0,1-r/y))**2;v.style.fontVariationSettings=h(a+(T-a)*x);return}if(l){if(y!==void 0&&r>y){v.style.fontVariationSettings=h(a);for(const e of q.current)e&&(e.style.fontVariationSettings=h(a));return}for(const e of q.current){if(!e)continue;const x=e.getBoundingClientRect(),A=(x.left+x.right)/2,m=(x.top+x.bottom)/2,k=Math.sqrt((s-A)**2+(M-m)**2),j=1-(1-Math.max(0,1-k/l))**2;e.style.fontVariationSettings=h(a+(T-a)*j)}}}const i=p.useCallback(s=>{g.current={x:s.clientX,y:s.clientY},E(s.clientX,s.clientY)},[a,T,y,l]),B=p.useCallback(()=>{g.current&&E(g.current.x,g.current.y)},[a,T,y,l]),S=p.useCallback(()=>{g.current=null;const s=F.current;s&&(s.style.fontVariationSettings=h(a));for(const M of q.current)M&&(M.style.fontVariationSettings=h(a))},[a]);return p.useEffect(()=>(window.addEventListener("mousemove",i,{passive:!0}),window.addEventListener("scroll",B,{passive:!0,capture:!0}),document.documentElement.addEventListener("mouseleave",S),()=>{window.removeEventListener("mousemove",i),window.removeEventListener("scroll",B,{capture:!0}),document.documentElement.removeEventListener("mouseleave",S)}),[i,B,S]),H.jsx(o,{ref:w,className:u,style:{fontVariationSettings:h(a),...c},children:V})});et.displayName="MagnetBlock";exports.MAGNET_TYPE_CLASSES=R;exports.MagnetBlock=et;exports.MagnetTypeText=tt;exports.applyMagnetType=Q;exports.getCleanHTML=z;exports.removeMagnetType=ot;exports.startMagnetType=Z;exports.useMagnetType=W;
1
+ "use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const d=require("react"),G=require("react/jsx-runtime"),tt={i:3,l:3,1:3,I:3,r:2,n:1,m:1,0:2,O:2,o:1,b:1,d:1,p:1,q:1,c:1,e:1},D={word:"mt-word",char:"mt-char",probe:"mt-probe"},j={axes:{wght:[300,500]},radius:120,falloff:"quadratic",magnetMode:"attract",wdthBoost:6,scope:"document"};function W(e,r=[]){return e.nodeType===Node.TEXT_NODE?r.push(e):e.childNodes.forEach(o=>W(o,r)),r}function K(e,r,o){if(!e||e==="normal")return`"${r}" ${o}`;const y=new RegExp(`(["'])${r}\\1\\s+[\\d.eE+-]+`),a=`"${r}" ${o}`;return y.test(e)?e.replace(y,a):`${e}, ${a}`}function et(e,r){let o=e;for(const[y,a]of Object.entries(r))o=K(o,y,a);return o}function nt(e,r,o){if(r.opacity!==void 0){const[y,a]=r.opacity;e.style.opacity=String(y+(a-y)*o)}r.italic===!0&&(e.style.fontStyle=o>.5?"italic":"")}function U(e,r){r.opacity!==void 0&&(e.style.opacity=String(r.opacity[0])),r.italic===!0&&(e.style.fontStyle="")}function rt(e){const r=e.cloneNode(!0),o=r.querySelectorAll(`.${D.word}, .${D.char}`);return Array.from(o).reverse().forEach(a=>{const l=a.parentNode;if(l){for(;a.firstChild;)l.insertBefore(a.firstChild,a);l.removeChild(a)}}),r.innerHTML}function lt(e,r){e.innerHTML=r}function ot(e,r,o={}){if(typeof window>"u")return()=>{};if(window.matchMedia("(prefers-reduced-motion: reduce)").matches)return e.innerHTML=r,()=>{};const y=o.wdthBoost??j.wdthBoost,a=o.radius??j.radius,l=o.falloff??j.falloff,x=o.scope??j.scope,E=o.props,f=o.transitionMs??0,w=window.scrollY;e.innerHTML=r;const L=getComputedStyle(e).fontVariationSettings,Y=L.match(/"wdth"\s+([\d.eE+-]+)/),T=Y?parseFloat(Y[1]):100,F=W(e),p=[];for(const s of F){const t=s.textContent??"";if(!t||!t.split("").some(n=>n in tt))continue;const c=document.createDocumentFragment();for(const n of t){const M=tt[n];if(M===void 0){const m=c.lastChild;m&&m.nodeType===Node.TEXT_NODE?m.textContent+=n:c.appendChild(document.createTextNode(n))}else{const m=document.createElement("span");m.className=D.char,m.style.fontVariationSettings=K(L,"wdth",T),m.textContent=n,c.appendChild(m),p.push({span:m,riskLevel:M})}}s.parentNode.replaceChild(c,s)}if(requestAnimationFrame(()=>{typeof window<"u"&&Math.abs(window.scrollY-w)>2&&window.scrollTo({top:w,behavior:"instant"})}),p.length===0)return()=>{};E&&p.forEach(({span:s})=>U(s,E));let g=-9999,V=-9999,S=!1,u=0,b=!0,N=null;function h(){if(!b)return;if(!S){p.forEach(({span:t})=>{f>0&&(t.style.transition=`font-variation-settings ${f}ms ease`),t.style.fontVariationSettings=K(L,"wdth",T),E&&U(t,E)}),f>0&&(N!==null&&clearTimeout(N),N=setTimeout(()=>{p.forEach(({span:t})=>{t.style.transition=""}),N=null},f)),u=0;return}const s=p.map(({span:t})=>t.getBoundingClientRect());p.forEach(({span:t,riskLevel:i},c)=>{t.style.transition="";const n=s[c],M=n.left+n.width/2,m=n.top+n.height/2,B=Math.sqrt((g-M)**2+(V-m)**2),A=Math.max(0,1-B/a),q=l==="quadratic"?A*A:A,H=y*(i/3)*q;t.style.fontVariationSettings=K(L,"wdth",T+H),E&&nt(t,E,q)}),u=requestAnimationFrame(h)}function X(s){g=s.clientX,V=s.clientY,S||(S=!0),u===0&&(u=requestAnimationFrame(h))}function R(){S=!1,u===0&&(u=requestAnimationFrame(h))}function k(s){s.touches.length!==0&&(g=s.touches[0].clientX,V=s.touches[0].clientY,S||(S=!0),u===0&&(u=requestAnimationFrame(h)))}function $(){S=!1,u===0&&(u=requestAnimationFrame(h))}const v=x==="document"?document:e;return v.addEventListener("mousemove",X),v.addEventListener("mouseleave",R),v.addEventListener("touchmove",k,{passive:!0}),v.addEventListener("touchend",$),()=>{b=!1,cancelAnimationFrame(u),N!==null&&clearTimeout(N),v.removeEventListener("mousemove",X),v.removeEventListener("mouseleave",R),v.removeEventListener("touchmove",k),v.removeEventListener("touchend",$),e.innerHTML=r}}function st(e,r,o={}){if(typeof window>"u")return()=>{};if(window.matchMedia("(prefers-reduced-motion: reduce)").matches)return e.innerHTML=r,()=>{};const y=o.axes??j.axes,a=o.radius??j.radius,l=o.falloff??j.falloff,x=o.magnetMode??j.magnetMode,E=o.scope??j.scope,f=o.props,w=o.transitionMs??0,L=window.scrollY;e.innerHTML=r;const Y=W(e),T=[];for(const s of Y){const t=s.textContent??"";if(!t.trim())continue;const i=t.split(/(\S+)/),c=document.createDocumentFragment();for(let n=0;n<i.length;n+=2){const M=i[n],m=i[n+1];if(!m)continue;const A=i[n+3]===void 0?i[n+2]??"":"",q=document.createElement("span");q.className=D.word,q.textContent=M+m+A,c.appendChild(q),T.push(q)}s.parentNode.replaceChild(c,s)}if(requestAnimationFrame(()=>{typeof window<"u"&&Math.abs(window.scrollY-L)>2&&window.scrollTo({top:L,behavior:"instant"})}),T.length===0)return()=>{};const F=getComputedStyle(e).fontVariationSettings,p=et(F,Object.fromEntries(Object.entries(y).map(([s,[t]])=>[s,t])));T.forEach(s=>{s.style.fontVariationSettings=p,f&&U(s,f)});let g=-9999,V=-9999,S=!1,u=0,b=!0,N=null;function h(){if(!b)return;if(!S){T.forEach(t=>{w>0&&(t.style.transition=`font-variation-settings ${w}ms ease`),t.style.fontVariationSettings=p,f&&U(t,f)}),w>0&&(N!==null&&clearTimeout(N),N=setTimeout(()=>{T.forEach(t=>{t.style.transition=""}),N=null},w)),u=0;return}const s=T.map(t=>t.getBoundingClientRect());T.forEach((t,i)=>{t.style.transition="";const c=s[i],n=c.left+c.width/2,M=c.top+c.height/2,m=Math.sqrt((g-n)**2+(V-M)**2),B=Math.max(0,1-m/a),A=l==="quadratic"?B*B:B,q=x==="repel"?1-A:A,H={};for(const _ of Object.keys(y)){const[J,O]=y[_]??[300,500];H[_]=J+(O-J)*q}t.style.fontVariationSettings=et(F,H),f&&nt(t,f,A)}),u=requestAnimationFrame(h)}function X(s){g=s.clientX,V=s.clientY,S||(S=!0),u===0&&(u=requestAnimationFrame(h))}function R(){S=!1,u===0&&(u=requestAnimationFrame(h))}function k(s){s.touches.length!==0&&(g=s.touches[0].clientX,V=s.touches[0].clientY,S||(S=!0),u===0&&(u=requestAnimationFrame(h)))}function $(){S=!1,u===0&&(u=requestAnimationFrame(h))}const v=E==="document"?document:e;return v.addEventListener("mousemove",X),v.addEventListener("mouseleave",R),v.addEventListener("touchmove",k,{passive:!0}),v.addEventListener("touchend",$),()=>{b=!1,cancelAnimationFrame(u),N!==null&&clearTimeout(N),v.removeEventListener("mousemove",X),v.removeEventListener("mouseleave",R),v.removeEventListener("touchmove",k),v.removeEventListener("touchend",$),e.innerHTML=r}}function ct(e){const r=d.useRef(null),o=d.useRef(null),y=d.useRef(e);y.current=e;const a=d.useRef(null),l=e.mode??"field",{axes:x,radius:E,falloff:f,magnetMode:w,wdthBoost:L,scope:Y}=e,T=x?JSON.stringify(x):void 0,F=e.props?JSON.stringify(e.props):void 0,p=d.useCallback(()=>{const g=r.current;if(!g)return;o.current===null&&(o.current=rt(g)),a.current&&(a.current(),a.current=null),(y.current.mode??"field")==="field"?a.current=st(g,o.current,y.current):a.current=ot(g,o.current,y.current)},[l,T,E,f,w,L,Y,F]);return d.useLayoutEffect(()=>(p(),()=>{a.current&&(a.current(),a.current=null)}),[p]),d.useEffect(()=>{document.fonts.ready.then(p)},[p]),r}const it=d.forwardRef(function({children:r,as:o="p",className:y,style:a,...l},x){const E=ct(l),f=d.useCallback(w=>{E.current=w,typeof x=="function"?x(w):x&&(x.current=w)},[x]);return G.jsx(o,{ref:f,className:y,style:a,children:r})});it.displayName="MagnetTypeText";const ut=d.forwardRef(function({children:r,as:o="p",className:y,style:a,minWeight:l=300,maxWeight:x=600,proximityRadius:E,spreadRadius:f,fixedAxes:w={},cachePositions:L=!0,rafThrottle:Y=!0},T){const F=d.useRef(null),p=d.useRef(null),g=d.useRef([]),V=d.useRef([]),S=d.useRef(null),u=d.useRef(!1),b=d.useRef(null),N=d.useCallback(t=>{F.current=t,typeof T=="function"?T(t):T&&(T.current=t)},[T]);function h(t){const i=[`'wght' ${t.toFixed(0)}`];for(const[c,n]of Object.entries(w))i.push(`'${c}' ${n}`);return i.join(", ")}function X(){const t=F.current;if(!t)return;const i=window.scrollX,c=window.scrollY,n=t.getBoundingClientRect();S.current={top:n.top+c,left:n.left+i,right:n.right+i,bottom:n.bottom+c},V.current=g.current.map(M=>{if(!M)return{cx:0,cy:0};const m=M.getBoundingClientRect();return{cx:(m.left+m.right)/2+i,cy:(m.top+m.bottom)/2+c}}),u.current=!0}function R(t,i){const c=F.current;if(!c)return;L&&!u.current&&X();let n,M,m,B,A,q;if(L&&S.current)A=t+window.scrollX,q=i+window.scrollY,{top:n,left:M,right:m,bottom:B}=S.current;else{const O=c.getBoundingClientRect();A=t,q=i,n=O.top,M=O.left,m=O.right,B=O.bottom}const H=Math.max(M-A,0,A-m),_=Math.max(n-q,0,q-B),J=Math.sqrt(H*H+_*_);if(E!==void 0&&!f){const C=1-(1-Math.max(0,1-J/E))**2;c.style.fontVariationSettings=h(l+(x-l)*C);return}if(f){if(E!==void 0&&J>E){c.style.fontVariationSettings=h(l);for(const C of g.current)C&&(C.style.fontVariationSettings=h(l));return}if(J>f){for(const C of g.current)C&&(C.style.fontVariationSettings=h(l));return}const O=g.current;if(L&&V.current.length===O.length)for(let C=0;C<O.length;C++){const I=O[C];if(!I)continue;const{cx:z,cy:P}=V.current[C],Q=Math.sqrt((A-z)**2+(q-P)**2),Z=1-(1-Math.max(0,1-Q/f))**2;I.style.fontVariationSettings=h(l+(x-l)*Z)}else for(const C of O){if(!C)continue;const I=C.getBoundingClientRect(),z=(I.left+I.right)/2,P=(I.top+I.bottom)/2,Q=Math.sqrt((t-z)**2+(i-P)**2),Z=1-(1-Math.max(0,1-Q/f))**2;C.style.fontVariationSettings=h(l+(x-l)*Z)}}}const k=d.useCallback(t=>{p.current={x:t.clientX,y:t.clientY},Y?b.current===null&&(b.current=requestAnimationFrame(()=>{b.current=null,p.current&&R(p.current.x,p.current.y)})):R(t.clientX,t.clientY)},[l,x,E,f,L,Y,JSON.stringify(w)]),$=d.useCallback(()=>{p.current&&R(p.current.x,p.current.y)},[l,x,E,f,L,JSON.stringify(w)]),v=d.useCallback(()=>{p.current=null,b.current!==null&&(cancelAnimationFrame(b.current),b.current=null);const t=F.current;t&&(t.style.fontVariationSettings=h(l));for(const i of g.current)i&&(i.style.fontVariationSettings=h(l))},[l,JSON.stringify(w)]);d.useEffect(()=>(window.addEventListener("mousemove",k,{passive:!0}),window.addEventListener("scroll",$,{passive:!0,capture:!0}),document.documentElement.addEventListener("mouseleave",v),()=>{window.removeEventListener("mousemove",k),window.removeEventListener("scroll",$,{capture:!0}),document.documentElement.removeEventListener("mouseleave",v),b.current!==null&&(cancelAnimationFrame(b.current),b.current=null)}),[k,$,v]),d.useEffect(()=>{if(!L||!f)return;u.current=!1;const t=F.current;if(!t)return;const i=new ResizeObserver(()=>{u.current=!1});return i.observe(t),document.fonts.ready.then(()=>{u.current=!1}),()=>i.disconnect()},[L,f]),d.useEffect(()=>{u.current=!1},[r,f]);const s=d.useMemo(()=>{if(!f)return r;g.current=[];let t=0;function i(c){if(typeof c=="string")return[...c].map(n=>{if(/\s/.test(n))return n;const M=t++;return G.jsx("span",{ref:m=>{g.current[M]=m},style:{fontVariationSettings:h(l)},children:n},M)});if(Array.isArray(c))return c.map((n,M)=>G.jsx(d.Fragment,{children:i(n)},M));if(d.isValidElement(c)){const n=c;if(n.props.children!==void 0)return d.cloneElement(n,{},i(n.props.children))}return c}return i(r)},[r,f,l,JSON.stringify(w)]);return G.jsx(o,{ref:N,className:y,style:{fontVariationSettings:h(l),...a},children:s})});ut.displayName="MagnetBlock";exports.MAGNET_TYPE_CLASSES=D;exports.MagnetBlock=ut;exports.MagnetTypeText=it;exports.applyMagnetType=ot;exports.getCleanHTML=rt;exports.removeMagnetType=lt;exports.startMagnetType=st;exports.useMagnetType=ct;
package/dist/index.d.ts CHANGED
@@ -38,12 +38,6 @@ export declare const MAGNET_TYPE_CLASSES: {
38
38
  readonly probe: "mt-probe";
39
39
  };
40
40
 
41
- /**
42
- * Drop-in block element with cursor-proximity variable font weight variation.
43
- * Accepts any ReactNode. When spreadRadius is set, string children are split into
44
- * per-character spans rendered as React elements — no DOM manipulation required.
45
- * Use proximityRadius for a whole-element gate, or combine both.
46
- */
47
41
  export declare const MagnetBlock: default_2.ForwardRefExoticComponent<MagnetBlockProps & default_2.RefAttributes<HTMLElement>>;
48
42
 
49
43
  export declare interface MagnetBlockProps {
@@ -59,6 +53,22 @@ export declare interface MagnetBlockProps {
59
53
  /** Pixel distance from the cursor within which each character's weight rises to max */
60
54
  spreadRadius?: number;
61
55
  fixedAxes?: Record<string, number>;
56
+ /**
57
+ * Cache character centre positions in page-relative coordinates after first render,
58
+ * eliminating getBoundingClientRect calls on every mousemove. The cache is rebuilt on
59
+ * resize and after fonts load. Disable if the block lives inside a custom scroll
60
+ * container (overflow: scroll on a non-window element) — page coordinates are derived
61
+ * from window.scrollX/Y and will be incorrect when a nested element is the scroll parent.
62
+ * @default true
63
+ */
64
+ cachePositions?: boolean;
65
+ /**
66
+ * Throttle proximity updates to one per animation frame via requestAnimationFrame,
67
+ * capping the update rate at the display refresh rate. Disable if the ~16 ms rAF delay
68
+ * is perceptible — for example on 120 Hz displays or very tight, fast-moving effects.
69
+ * @default true
70
+ */
71
+ rafThrottle?: boolean;
62
72
  }
63
73
 
64
74
  /** Whether cursor proximity attracts toward peak or repels toward rest */
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
- import J, { useRef as j, useCallback as X, useLayoutEffect as et, useEffect as Q, forwardRef as Z, useMemo as nt } from "react";
2
- import { jsx as D } from "react/jsx-runtime";
3
- const R = {
1
+ "use client";
2
+ import et, { useRef as O, useCallback as R, useLayoutEffect as ut, useEffect as K, forwardRef as ct, useMemo as at } from "react";
3
+ import { jsx as U } from "react/jsx-runtime";
4
+ const rt = {
4
5
  i: 3,
5
6
  l: 3,
6
7
  1: 3,
@@ -22,14 +23,14 @@ const R = {
22
23
  c: 1,
23
24
  e: 1
24
25
  // similar bowls
25
- }, P = {
26
+ }, G = {
26
27
  /** Applied to each word span in field mode */
27
28
  word: "mt-word",
28
29
  /** Applied to each character span in legibility mode */
29
30
  char: "mt-char",
30
31
  /** Applied to measurement probe spans (never in final output) */
31
32
  probe: "mt-probe"
32
- }, Y = {
33
+ }, j = {
33
34
  axes: { wght: [300, 500] },
34
35
  radius: 120,
35
36
  falloff: "quadratic",
@@ -37,329 +38,389 @@ const R = {
37
38
  wdthBoost: 6,
38
39
  scope: "document"
39
40
  };
40
- function K(t, n = []) {
41
- return t.nodeType === Node.TEXT_NODE ? n.push(t) : t.childNodes.forEach((o) => K(o, n)), n;
41
+ function nt(e, r = []) {
42
+ return e.nodeType === Node.TEXT_NODE ? r.push(e) : e.childNodes.forEach((o) => nt(o, r)), r;
42
43
  }
43
- function I(t, n, o) {
44
- if (!t || t === "normal") return `"${n}" ${o}`;
45
- const u = new RegExp(`(["'])${n}\\1\\s+[\\d.eE+-]+`), c = `"${n}" ${o}`;
46
- return u.test(t) ? t.replace(u, c) : `${t}, ${c}`;
44
+ function z(e, r, o) {
45
+ if (!e || e === "normal") return `"${r}" ${o}`;
46
+ const p = new RegExp(`(["'])${r}\\1\\s+[\\d.eE+-]+`), a = `"${r}" ${o}`;
47
+ return p.test(e) ? e.replace(p, a) : `${e}, ${a}`;
47
48
  }
48
- function z(t, n) {
49
- let o = t;
50
- for (const [u, c] of Object.entries(n))
51
- o = I(o, u, c);
49
+ function ot(e, r) {
50
+ let o = e;
51
+ for (const [p, a] of Object.entries(r))
52
+ o = z(o, p, a);
52
53
  return o;
53
54
  }
54
- function W(t, n, o) {
55
- if (n.opacity !== void 0) {
56
- const [u, c] = n.opacity;
57
- t.style.opacity = String(u + (c - u) * o);
55
+ function st(e, r, o) {
56
+ if (r.opacity !== void 0) {
57
+ const [p, a] = r.opacity;
58
+ e.style.opacity = String(p + (a - p) * o);
58
59
  }
59
- n.italic === !0 && (t.style.fontStyle = o > 0.5 ? "italic" : "");
60
+ r.italic === !0 && (e.style.fontStyle = o > 0.5 ? "italic" : "");
60
61
  }
61
- function _(t, n) {
62
- n.opacity !== void 0 && (t.style.opacity = String(n.opacity[0])), n.italic === !0 && (t.style.fontStyle = "");
62
+ function Q(e, r) {
63
+ r.opacity !== void 0 && (e.style.opacity = String(r.opacity[0])), r.italic === !0 && (e.style.fontStyle = "");
63
64
  }
64
- function ot(t) {
65
- const n = t.cloneNode(!0), o = n.querySelectorAll(
66
- `.${P.word}, .${P.char}`
65
+ function lt(e) {
66
+ const r = e.cloneNode(!0), o = r.querySelectorAll(
67
+ `.${G.word}, .${G.char}`
67
68
  );
68
- return Array.from(o).reverse().forEach((c) => {
69
- const a = c.parentNode;
70
- if (a) {
71
- for (; c.firstChild; ) a.insertBefore(c.firstChild, c);
72
- a.removeChild(c);
69
+ return Array.from(o).reverse().forEach((a) => {
70
+ const l = a.parentNode;
71
+ if (l) {
72
+ for (; a.firstChild; ) l.insertBefore(a.firstChild, a);
73
+ l.removeChild(a);
73
74
  }
74
- }), n.innerHTML;
75
+ }), r.innerHTML;
75
76
  }
76
- function lt(t, n) {
77
- t.innerHTML = n;
77
+ function gt(e, r) {
78
+ e.innerHTML = r;
78
79
  }
79
- function rt(t, n, o = {}) {
80
+ function ft(e, r, o = {}) {
80
81
  if (typeof window > "u") return () => {
81
82
  };
82
83
  if (window.matchMedia("(prefers-reduced-motion: reduce)").matches)
83
- return t.innerHTML = n, () => {
84
+ return e.innerHTML = r, () => {
84
85
  };
85
- const u = o.wdthBoost ?? Y.wdthBoost, c = o.radius ?? Y.radius, a = o.falloff ?? Y.falloff, M = o.scope ?? Y.scope, v = o.props, f = o.transitionMs ?? 0, x = window.scrollY;
86
- t.innerHTML = n;
87
- const N = getComputedStyle(t).fontVariationSettings, F = N.match(/"wdth"\s+([\d.eE+-]+)/), y = F ? parseFloat(F[1]) : 100, q = K(t), w = [];
88
- for (const r of q) {
89
- const e = r.textContent ?? "";
90
- if (!e || !e.split("").some((m) => m in R)) continue;
91
- const C = document.createDocumentFragment();
92
- for (const m of e) {
93
- const V = R[m];
94
- if (V === void 0) {
95
- const S = C.lastChild;
96
- S && S.nodeType === Node.TEXT_NODE ? S.textContent += m : C.appendChild(document.createTextNode(m));
86
+ const p = o.wdthBoost ?? j.wdthBoost, a = o.radius ?? j.radius, l = o.falloff ?? j.falloff, S = o.scope ?? j.scope, w = o.props, f = o.transitionMs ?? 0, M = window.scrollY;
87
+ e.innerHTML = r;
88
+ const L = getComputedStyle(e).fontVariationSettings, Y = L.match(/"wdth"\s+([\d.eE+-]+)/), E = Y ? parseFloat(Y[1]) : 100, q = nt(e), m = [];
89
+ for (const c of q) {
90
+ const t = c.textContent ?? "";
91
+ if (!t || !t.split("").some((n) => n in rt)) continue;
92
+ const s = document.createDocumentFragment();
93
+ for (const n of t) {
94
+ const g = rt[n];
95
+ if (g === void 0) {
96
+ const d = s.lastChild;
97
+ d && d.nodeType === Node.TEXT_NODE ? d.textContent += n : s.appendChild(document.createTextNode(n));
97
98
  } else {
98
- const S = document.createElement("span");
99
- S.className = P.char, S.style.fontVariationSettings = I(N, "wdth", y), S.textContent = m, C.appendChild(S), w.push({ span: S, riskLevel: V });
99
+ const d = document.createElement("span");
100
+ d.className = G.char, d.style.fontVariationSettings = z(L, "wdth", E), d.textContent = n, s.appendChild(d), m.push({ span: d, riskLevel: g });
100
101
  }
101
102
  }
102
- r.parentNode.replaceChild(C, r);
103
+ c.parentNode.replaceChild(s, c);
103
104
  }
104
105
  if (requestAnimationFrame(() => {
105
- typeof window < "u" && Math.abs(window.scrollY - x) > 2 && window.scrollTo({ top: x, behavior: "instant" });
106
- }), w.length === 0) return () => {
106
+ typeof window < "u" && Math.abs(window.scrollY - M) > 2 && window.scrollTo({ top: M, behavior: "instant" });
107
+ }), m.length === 0) return () => {
107
108
  };
108
- v && w.forEach(({ span: r }) => _(r, v));
109
- let p = -9999, b = -9999, E = !1, i = 0, B = !0, T = null;
110
- function s() {
111
- if (!B) return;
112
- if (!E) {
113
- w.forEach(({ span: e }) => {
114
- f > 0 && (e.style.transition = `font-variation-settings ${f}ms ease`), e.style.fontVariationSettings = I(N, "wdth", y), v && _(e, v);
115
- }), f > 0 && (T !== null && clearTimeout(T), T = setTimeout(() => {
116
- w.forEach(({ span: e }) => {
117
- e.style.transition = "";
118
- }), T = null;
119
- }, f)), i = 0;
109
+ w && m.forEach(({ span: c }) => Q(c, w));
110
+ let y = -9999, F = -9999, T = !1, u = 0, C = !0, N = null;
111
+ function h() {
112
+ if (!C) return;
113
+ if (!T) {
114
+ m.forEach(({ span: t }) => {
115
+ f > 0 && (t.style.transition = `font-variation-settings ${f}ms ease`), t.style.fontVariationSettings = z(L, "wdth", E), w && Q(t, w);
116
+ }), f > 0 && (N !== null && clearTimeout(N), N = setTimeout(() => {
117
+ m.forEach(({ span: t }) => {
118
+ t.style.transition = "";
119
+ }), N = null;
120
+ }, f)), u = 0;
120
121
  return;
121
122
  }
122
- const r = w.map(({ span: e }) => e.getBoundingClientRect());
123
- w.forEach(({ span: e, riskLevel: L }, C) => {
124
- e.style.transition = "";
125
- const m = r[C], V = m.left + m.width / 2, S = m.top + m.height / 2, k = Math.sqrt((p - V) ** 2 + (b - S) ** 2), O = Math.max(0, 1 - k / c), $ = a === "quadratic" ? O * O : O, H = u * (L / 3) * $;
126
- e.style.fontVariationSettings = I(N, "wdth", y + H), v && W(e, v, $);
127
- }), i = requestAnimationFrame(s);
123
+ const c = m.map(({ span: t }) => t.getBoundingClientRect());
124
+ m.forEach(({ span: t, riskLevel: i }, s) => {
125
+ t.style.transition = "";
126
+ const n = c[s], g = n.left + n.width / 2, d = n.top + n.height / 2, B = Math.sqrt((y - g) ** 2 + (F - d) ** 2), b = Math.max(0, 1 - B / a), A = l === "quadratic" ? b * b : b, I = p * (i / 3) * A;
127
+ t.style.fontVariationSettings = z(L, "wdth", E + I), w && st(t, w, A);
128
+ }), u = requestAnimationFrame(h);
128
129
  }
129
- function g(r) {
130
- p = r.clientX, b = r.clientY, E || (E = !0), i === 0 && (i = requestAnimationFrame(s));
130
+ function H(c) {
131
+ y = c.clientX, F = c.clientY, T || (T = !0), u === 0 && (u = requestAnimationFrame(h));
131
132
  }
132
- function h() {
133
- E = !1, i === 0 && (i = requestAnimationFrame(s));
133
+ function $() {
134
+ T = !1, u === 0 && (u = requestAnimationFrame(h));
134
135
  }
135
- function l(r) {
136
- r.touches.length !== 0 && (p = r.touches[0].clientX, b = r.touches[0].clientY, E || (E = !0), i === 0 && (i = requestAnimationFrame(s)));
136
+ function X(c) {
137
+ c.touches.length !== 0 && (y = c.touches[0].clientX, F = c.touches[0].clientY, T || (T = !0), u === 0 && (u = requestAnimationFrame(h)));
137
138
  }
138
- function A() {
139
- E = !1, i === 0 && (i = requestAnimationFrame(s));
139
+ function k() {
140
+ T = !1, u === 0 && (u = requestAnimationFrame(h));
140
141
  }
141
- const d = M === "document" ? document : t;
142
- return d.addEventListener("mousemove", g), d.addEventListener("mouseleave", h), d.addEventListener("touchmove", l, { passive: !0 }), d.addEventListener("touchend", A), () => {
143
- B = !1, cancelAnimationFrame(i), T !== null && clearTimeout(T), d.removeEventListener("mousemove", g), d.removeEventListener("mouseleave", h), d.removeEventListener("touchmove", l), d.removeEventListener("touchend", A), t.innerHTML = n;
142
+ const v = S === "document" ? document : e;
143
+ return v.addEventListener("mousemove", H), v.addEventListener("mouseleave", $), v.addEventListener("touchmove", X, { passive: !0 }), v.addEventListener("touchend", k), () => {
144
+ C = !1, cancelAnimationFrame(u), N !== null && clearTimeout(N), v.removeEventListener("mousemove", H), v.removeEventListener("mouseleave", $), v.removeEventListener("touchmove", X), v.removeEventListener("touchend", k), e.innerHTML = r;
144
145
  };
145
146
  }
146
- function st(t, n, o = {}) {
147
+ function dt(e, r, o = {}) {
147
148
  if (typeof window > "u") return () => {
148
149
  };
149
150
  if (window.matchMedia("(prefers-reduced-motion: reduce)").matches)
150
- return t.innerHTML = n, () => {
151
+ return e.innerHTML = r, () => {
151
152
  };
152
- const u = o.axes ?? Y.axes, c = o.radius ?? Y.radius, a = o.falloff ?? Y.falloff, M = o.magnetMode ?? Y.magnetMode, v = o.scope ?? Y.scope, f = o.props, x = o.transitionMs ?? 0, N = window.scrollY;
153
- t.innerHTML = n;
154
- const F = K(t), y = [];
155
- for (const r of F) {
156
- const e = r.textContent ?? "";
157
- if (!e.trim()) continue;
158
- const L = e.split(/(\S+)/), C = document.createDocumentFragment();
159
- for (let m = 0; m < L.length; m += 2) {
160
- const V = L[m], S = L[m + 1];
161
- if (!S) continue;
162
- const O = L[m + 3] === void 0 ? L[m + 2] ?? "" : "", $ = document.createElement("span");
163
- $.className = P.word, $.textContent = V + S + O, C.appendChild($), y.push($);
153
+ const p = o.axes ?? j.axes, a = o.radius ?? j.radius, l = o.falloff ?? j.falloff, S = o.magnetMode ?? j.magnetMode, w = o.scope ?? j.scope, f = o.props, M = o.transitionMs ?? 0, L = window.scrollY;
154
+ e.innerHTML = r;
155
+ const Y = nt(e), E = [];
156
+ for (const c of Y) {
157
+ const t = c.textContent ?? "";
158
+ if (!t.trim()) continue;
159
+ const i = t.split(/(\S+)/), s = document.createDocumentFragment();
160
+ for (let n = 0; n < i.length; n += 2) {
161
+ const g = i[n], d = i[n + 1];
162
+ if (!d) continue;
163
+ const b = i[n + 3] === void 0 ? i[n + 2] ?? "" : "", A = document.createElement("span");
164
+ A.className = G.word, A.textContent = g + d + b, s.appendChild(A), E.push(A);
164
165
  }
165
- r.parentNode.replaceChild(C, r);
166
+ c.parentNode.replaceChild(s, c);
166
167
  }
167
168
  if (requestAnimationFrame(() => {
168
- typeof window < "u" && Math.abs(window.scrollY - N) > 2 && window.scrollTo({ top: N, behavior: "instant" });
169
- }), y.length === 0) return () => {
169
+ typeof window < "u" && Math.abs(window.scrollY - L) > 2 && window.scrollTo({ top: L, behavior: "instant" });
170
+ }), E.length === 0) return () => {
170
171
  };
171
- const q = getComputedStyle(t).fontVariationSettings, w = z(
172
+ const q = getComputedStyle(e).fontVariationSettings, m = ot(
172
173
  q,
173
- Object.fromEntries(Object.entries(u).map(([r, [e]]) => [r, e]))
174
+ Object.fromEntries(Object.entries(p).map(([c, [t]]) => [c, t]))
174
175
  );
175
- y.forEach((r) => {
176
- r.style.fontVariationSettings = w, f && _(r, f);
176
+ E.forEach((c) => {
177
+ c.style.fontVariationSettings = m, f && Q(c, f);
177
178
  });
178
- let p = -9999, b = -9999, E = !1, i = 0, B = !0, T = null;
179
- function s() {
180
- if (!B) return;
181
- if (!E) {
182
- y.forEach((e) => {
183
- x > 0 && (e.style.transition = `font-variation-settings ${x}ms ease`), e.style.fontVariationSettings = w, f && _(e, f);
184
- }), x > 0 && (T !== null && clearTimeout(T), T = setTimeout(() => {
185
- y.forEach((e) => {
186
- e.style.transition = "";
187
- }), T = null;
188
- }, x)), i = 0;
179
+ let y = -9999, F = -9999, T = !1, u = 0, C = !0, N = null;
180
+ function h() {
181
+ if (!C) return;
182
+ if (!T) {
183
+ E.forEach((t) => {
184
+ M > 0 && (t.style.transition = `font-variation-settings ${M}ms ease`), t.style.fontVariationSettings = m, f && Q(t, f);
185
+ }), M > 0 && (N !== null && clearTimeout(N), N = setTimeout(() => {
186
+ E.forEach((t) => {
187
+ t.style.transition = "";
188
+ }), N = null;
189
+ }, M)), u = 0;
189
190
  return;
190
191
  }
191
- const r = y.map((e) => e.getBoundingClientRect());
192
- y.forEach((e, L) => {
193
- e.style.transition = "";
194
- const C = r[L], m = C.left + C.width / 2, V = C.top + C.height / 2, S = Math.sqrt((p - m) ** 2 + (b - V) ** 2), k = Math.max(0, 1 - S / c), O = a === "quadratic" ? k * k : k, $ = M === "repel" ? 1 - O : O, H = {};
195
- for (const U of Object.keys(u)) {
196
- const [G, tt] = u[U] ?? [300, 500];
197
- H[U] = G + (tt - G) * $;
192
+ const c = E.map((t) => t.getBoundingClientRect());
193
+ E.forEach((t, i) => {
194
+ t.style.transition = "";
195
+ const s = c[i], n = s.left + s.width / 2, g = s.top + s.height / 2, d = Math.sqrt((y - n) ** 2 + (F - g) ** 2), B = Math.max(0, 1 - d / a), b = l === "quadratic" ? B * B : B, A = S === "repel" ? 1 - b : b, I = {};
196
+ for (const _ of Object.keys(p)) {
197
+ const [D, V] = p[_] ?? [300, 500];
198
+ I[_] = D + (V - D) * A;
198
199
  }
199
- e.style.fontVariationSettings = z(q, H), f && W(e, f, O);
200
- }), i = requestAnimationFrame(s);
200
+ t.style.fontVariationSettings = ot(q, I), f && st(t, f, b);
201
+ }), u = requestAnimationFrame(h);
201
202
  }
202
- function g(r) {
203
- p = r.clientX, b = r.clientY, E || (E = !0), i === 0 && (i = requestAnimationFrame(s));
203
+ function H(c) {
204
+ y = c.clientX, F = c.clientY, T || (T = !0), u === 0 && (u = requestAnimationFrame(h));
204
205
  }
205
- function h() {
206
- E = !1, i === 0 && (i = requestAnimationFrame(s));
206
+ function $() {
207
+ T = !1, u === 0 && (u = requestAnimationFrame(h));
207
208
  }
208
- function l(r) {
209
- r.touches.length !== 0 && (p = r.touches[0].clientX, b = r.touches[0].clientY, E || (E = !0), i === 0 && (i = requestAnimationFrame(s)));
209
+ function X(c) {
210
+ c.touches.length !== 0 && (y = c.touches[0].clientX, F = c.touches[0].clientY, T || (T = !0), u === 0 && (u = requestAnimationFrame(h)));
210
211
  }
211
- function A() {
212
- E = !1, i === 0 && (i = requestAnimationFrame(s));
212
+ function k() {
213
+ T = !1, u === 0 && (u = requestAnimationFrame(h));
213
214
  }
214
- const d = v === "document" ? document : t;
215
- return d.addEventListener("mousemove", g), d.addEventListener("mouseleave", h), d.addEventListener("touchmove", l, { passive: !0 }), d.addEventListener("touchend", A), () => {
216
- B = !1, cancelAnimationFrame(i), T !== null && clearTimeout(T), d.removeEventListener("mousemove", g), d.removeEventListener("mouseleave", h), d.removeEventListener("touchmove", l), d.removeEventListener("touchend", A), t.innerHTML = n;
215
+ const v = w === "document" ? document : e;
216
+ return v.addEventListener("mousemove", H), v.addEventListener("mouseleave", $), v.addEventListener("touchmove", X, { passive: !0 }), v.addEventListener("touchend", k), () => {
217
+ C = !1, cancelAnimationFrame(u), N !== null && clearTimeout(N), v.removeEventListener("mousemove", H), v.removeEventListener("mouseleave", $), v.removeEventListener("touchmove", X), v.removeEventListener("touchend", k), e.innerHTML = r;
217
218
  };
218
219
  }
219
- function ct(t) {
220
- const n = j(null), o = j(null), u = j(t);
221
- u.current = t;
222
- const c = j(null), a = t.mode ?? "field", { axes: M, radius: v, falloff: f, magnetMode: x, wdthBoost: N, scope: F } = t, y = M ? JSON.stringify(M) : void 0, q = t.props ? JSON.stringify(t.props) : void 0, w = X(() => {
223
- const p = n.current;
224
- if (!p) return;
225
- o.current === null && (o.current = ot(p)), c.current && (c.current(), c.current = null), (u.current.mode ?? "field") === "field" ? c.current = st(p, o.current, u.current) : c.current = rt(p, o.current, u.current);
226
- }, [a, y, v, f, x, N, F, q]);
227
- return et(() => (w(), () => {
228
- c.current && (c.current(), c.current = null);
229
- }), [w]), Q(() => {
230
- document.fonts.ready.then(w);
231
- }, [w]), n;
220
+ function mt(e) {
221
+ const r = O(null), o = O(null), p = O(e);
222
+ p.current = e;
223
+ const a = O(null), l = e.mode ?? "field", { axes: S, radius: w, falloff: f, magnetMode: M, wdthBoost: L, scope: Y } = e, E = S ? JSON.stringify(S) : void 0, q = e.props ? JSON.stringify(e.props) : void 0, m = R(() => {
224
+ const y = r.current;
225
+ if (!y) return;
226
+ o.current === null && (o.current = lt(y)), a.current && (a.current(), a.current = null), (p.current.mode ?? "field") === "field" ? a.current = dt(y, o.current, p.current) : a.current = ft(y, o.current, p.current);
227
+ }, [l, E, w, f, M, L, Y, q]);
228
+ return ut(() => (m(), () => {
229
+ a.current && (a.current(), a.current = null);
230
+ }), [m]), K(() => {
231
+ document.fonts.ready.then(m);
232
+ }, [m]), r;
232
233
  }
233
- const it = Z(
234
- function({ children: n, as: o = "p", className: u, style: c, ...a }, M) {
235
- const v = ct(a), f = X(
236
- (x) => {
237
- v.current = x, typeof M == "function" ? M(x) : M && (M.current = x);
234
+ const pt = ct(
235
+ function({ children: r, as: o = "p", className: p, style: a, ...l }, S) {
236
+ const w = mt(l), f = R(
237
+ (M) => {
238
+ w.current = M, typeof S == "function" ? S(M) : S && (S.current = M);
238
239
  },
239
240
  // eslint-disable-next-line react-hooks/exhaustive-deps
240
- [M]
241
+ [S]
241
242
  );
242
- return /* @__PURE__ */ D(o, { ref: f, className: u, style: c, children: n });
243
+ return /* @__PURE__ */ U(o, { ref: f, className: p, style: a, children: r });
243
244
  }
244
245
  );
245
- it.displayName = "MagnetTypeText";
246
- const at = Z(
246
+ pt.displayName = "MagnetTypeText";
247
+ const ht = ct(
247
248
  function({
248
- children: n,
249
+ children: r,
249
250
  as: o = "p",
250
- className: u,
251
- style: c,
252
- minWeight: a = 300,
253
- maxWeight: M = 600,
254
- proximityRadius: v,
251
+ className: p,
252
+ style: a,
253
+ minWeight: l = 300,
254
+ maxWeight: S = 600,
255
+ proximityRadius: w,
255
256
  spreadRadius: f,
256
- fixedAxes: x = {}
257
- }, N) {
258
- const F = j(null), y = j(null), q = j([]), w = X(
259
- (s) => {
260
- F.current = s, typeof N == "function" ? N(s) : N && (N.current = s);
257
+ fixedAxes: M = {},
258
+ cachePositions: L = !0,
259
+ rafThrottle: Y = !0
260
+ }, E) {
261
+ const q = O(null), m = O(null), y = O([]), F = O([]), T = O(null), u = O(!1), C = O(null), N = R(
262
+ (t) => {
263
+ q.current = t, typeof E == "function" ? E(t) : E && (E.current = t);
261
264
  },
262
265
  // eslint-disable-next-line react-hooks/exhaustive-deps
263
- [N]
266
+ [E]
264
267
  );
265
- function p(s) {
266
- const g = [`'wght' ${s.toFixed(0)}`];
267
- for (const [h, l] of Object.entries(x)) g.push(`'${h}' ${l}`);
268
- return g.join(", ");
268
+ function h(t) {
269
+ const i = [`'wght' ${t.toFixed(0)}`];
270
+ for (const [s, n] of Object.entries(M)) i.push(`'${s}' ${n}`);
271
+ return i.join(", ");
269
272
  }
270
- const b = nt(() => {
271
- if (!f) return n;
272
- q.current = [];
273
- let s = 0;
274
- function g(h) {
275
- if (typeof h == "string")
276
- return [...h].map((l) => {
277
- if (/\s/.test(l)) return l;
278
- const A = s++;
279
- return /* @__PURE__ */ D(
280
- "span",
281
- {
282
- ref: (d) => {
283
- q.current[A] = d;
284
- },
285
- style: { fontVariationSettings: p(a) },
286
- children: l
287
- },
288
- A
289
- );
290
- });
291
- if (Array.isArray(h)) return h.map((l, A) => /* @__PURE__ */ D(J.Fragment, { children: g(l) }, A));
292
- if (J.isValidElement(h)) {
293
- const l = h;
294
- if (l.props.children !== void 0)
295
- return J.cloneElement(l, {}, g(l.props.children));
296
- }
297
- return h;
273
+ function H() {
274
+ const t = q.current;
275
+ if (!t) return;
276
+ const i = window.scrollX, s = window.scrollY, n = t.getBoundingClientRect();
277
+ T.current = {
278
+ top: n.top + s,
279
+ left: n.left + i,
280
+ right: n.right + i,
281
+ bottom: n.bottom + s
282
+ }, F.current = y.current.map((g) => {
283
+ if (!g) return { cx: 0, cy: 0 };
284
+ const d = g.getBoundingClientRect();
285
+ return {
286
+ cx: (d.left + d.right) / 2 + i,
287
+ cy: (d.top + d.bottom) / 2 + s
288
+ };
289
+ }), u.current = !0;
290
+ }
291
+ function $(t, i) {
292
+ const s = q.current;
293
+ if (!s) return;
294
+ L && !u.current && H();
295
+ let n, g, d, B, b, A;
296
+ if (L && T.current)
297
+ b = t + window.scrollX, A = i + window.scrollY, { top: n, left: g, right: d, bottom: B } = T.current;
298
+ else {
299
+ const V = s.getBoundingClientRect();
300
+ b = t, A = i, n = V.top, g = V.left, d = V.right, B = V.bottom;
298
301
  }
299
- return g(n);
300
- }, [n, f, a, JSON.stringify(x)]);
301
- function E(s, g) {
302
- const h = F.current;
303
- if (!h) return;
304
- const l = h.getBoundingClientRect(), A = Math.max(l.left - s, 0, s - l.right), d = Math.max(l.top - g, 0, g - l.bottom), r = Math.sqrt(A * A + d * d);
305
- if (v !== void 0 && !f) {
306
- const L = 1 - (1 - Math.max(0, 1 - r / v)) ** 2;
307
- h.style.fontVariationSettings = p(a + (M - a) * L);
302
+ const I = Math.max(g - b, 0, b - d), _ = Math.max(n - A, 0, A - B), D = Math.sqrt(I * I + _ * _);
303
+ if (w !== void 0 && !f) {
304
+ const x = 1 - (1 - Math.max(0, 1 - D / w)) ** 2;
305
+ s.style.fontVariationSettings = h(l + (S - l) * x);
308
306
  return;
309
307
  }
310
308
  if (f) {
311
- if (v !== void 0 && r > v) {
312
- h.style.fontVariationSettings = p(a);
313
- for (const e of q.current) e && (e.style.fontVariationSettings = p(a));
309
+ if (w !== void 0 && D > w) {
310
+ s.style.fontVariationSettings = h(l);
311
+ for (const x of y.current) x && (x.style.fontVariationSettings = h(l));
314
312
  return;
315
313
  }
316
- for (const e of q.current) {
317
- if (!e) continue;
318
- const L = e.getBoundingClientRect(), C = (L.left + L.right) / 2, m = (L.top + L.bottom) / 2, V = Math.sqrt((s - C) ** 2 + (g - m) ** 2), k = 1 - (1 - Math.max(0, 1 - V / f)) ** 2;
319
- e.style.fontVariationSettings = p(a + (M - a) * k);
314
+ if (D > f) {
315
+ for (const x of y.current) x && (x.style.fontVariationSettings = h(l));
316
+ return;
320
317
  }
318
+ const V = y.current;
319
+ if (L && F.current.length === V.length)
320
+ for (let x = 0; x < V.length; x++) {
321
+ const J = V[x];
322
+ if (!J) continue;
323
+ const { cx: Z, cy: P } = F.current[x], W = Math.sqrt((b - Z) ** 2 + (A - P) ** 2), tt = 1 - (1 - Math.max(0, 1 - W / f)) ** 2;
324
+ J.style.fontVariationSettings = h(l + (S - l) * tt);
325
+ }
326
+ else
327
+ for (const x of V) {
328
+ if (!x) continue;
329
+ const J = x.getBoundingClientRect(), Z = (J.left + J.right) / 2, P = (J.top + J.bottom) / 2, W = Math.sqrt((t - Z) ** 2 + (i - P) ** 2), tt = 1 - (1 - Math.max(0, 1 - W / f)) ** 2;
330
+ x.style.fontVariationSettings = h(l + (S - l) * tt);
331
+ }
321
332
  }
322
333
  }
323
- const i = X(
324
- (s) => {
325
- y.current = { x: s.clientX, y: s.clientY }, E(s.clientX, s.clientY);
334
+ const X = R(
335
+ (t) => {
336
+ m.current = { x: t.clientX, y: t.clientY }, Y ? C.current === null && (C.current = requestAnimationFrame(() => {
337
+ C.current = null, m.current && $(m.current.x, m.current.y);
338
+ })) : $(t.clientX, t.clientY);
326
339
  },
327
- [a, M, v, f]
328
- ), B = X(
340
+ // eslint-disable-next-line react-hooks/exhaustive-deps
341
+ [l, S, w, f, L, Y, JSON.stringify(M)]
342
+ ), k = R(
329
343
  () => {
330
- y.current && E(y.current.x, y.current.y);
344
+ m.current && $(m.current.x, m.current.y);
331
345
  },
332
- [a, M, v, f]
333
- ), T = X(
346
+ // eslint-disable-next-line react-hooks/exhaustive-deps
347
+ [l, S, w, f, L, JSON.stringify(M)]
348
+ ), v = R(
334
349
  () => {
335
- y.current = null;
336
- const s = F.current;
337
- s && (s.style.fontVariationSettings = p(a));
338
- for (const g of q.current) g && (g.style.fontVariationSettings = p(a));
350
+ m.current = null, C.current !== null && (cancelAnimationFrame(C.current), C.current = null);
351
+ const t = q.current;
352
+ t && (t.style.fontVariationSettings = h(l));
353
+ for (const i of y.current) i && (i.style.fontVariationSettings = h(l));
339
354
  },
340
- [a]
355
+ // eslint-disable-next-line react-hooks/exhaustive-deps
356
+ [l, JSON.stringify(M)]
341
357
  );
342
- return Q(() => (window.addEventListener("mousemove", i, { passive: !0 }), window.addEventListener("scroll", B, { passive: !0, capture: !0 }), document.documentElement.addEventListener("mouseleave", T), () => {
343
- window.removeEventListener("mousemove", i), window.removeEventListener("scroll", B, { capture: !0 }), document.documentElement.removeEventListener("mouseleave", T);
344
- }), [i, B, T]), /* @__PURE__ */ D(
358
+ K(() => (window.addEventListener("mousemove", X, { passive: !0 }), window.addEventListener("scroll", k, { passive: !0, capture: !0 }), document.documentElement.addEventListener("mouseleave", v), () => {
359
+ window.removeEventListener("mousemove", X), window.removeEventListener("scroll", k, { capture: !0 }), document.documentElement.removeEventListener("mouseleave", v), C.current !== null && (cancelAnimationFrame(C.current), C.current = null);
360
+ }), [X, k, v]), K(() => {
361
+ if (!L || !f) return;
362
+ u.current = !1;
363
+ const t = q.current;
364
+ if (!t) return;
365
+ const i = new ResizeObserver(() => {
366
+ u.current = !1;
367
+ });
368
+ return i.observe(t), document.fonts.ready.then(() => {
369
+ u.current = !1;
370
+ }), () => i.disconnect();
371
+ }, [L, f]), K(() => {
372
+ u.current = !1;
373
+ }, [r, f]);
374
+ const c = at(() => {
375
+ if (!f) return r;
376
+ y.current = [];
377
+ let t = 0;
378
+ function i(s) {
379
+ if (typeof s == "string")
380
+ return [...s].map((n) => {
381
+ if (/\s/.test(n)) return n;
382
+ const g = t++;
383
+ return /* @__PURE__ */ U(
384
+ "span",
385
+ {
386
+ ref: (d) => {
387
+ y.current[g] = d;
388
+ },
389
+ style: { fontVariationSettings: h(l) },
390
+ children: n
391
+ },
392
+ g
393
+ );
394
+ });
395
+ if (Array.isArray(s)) return s.map((n, g) => /* @__PURE__ */ U(et.Fragment, { children: i(n) }, g));
396
+ if (et.isValidElement(s)) {
397
+ const n = s;
398
+ if (n.props.children !== void 0)
399
+ return et.cloneElement(n, {}, i(n.props.children));
400
+ }
401
+ return s;
402
+ }
403
+ return i(r);
404
+ }, [r, f, l, JSON.stringify(M)]);
405
+ return /* @__PURE__ */ U(
345
406
  o,
346
407
  {
347
- ref: w,
348
- className: u,
349
- style: { fontVariationSettings: p(a), ...c },
350
- children: b
408
+ ref: N,
409
+ className: p,
410
+ style: { fontVariationSettings: h(l), ...a },
411
+ children: c
351
412
  }
352
413
  );
353
414
  }
354
415
  );
355
- at.displayName = "MagnetBlock";
416
+ ht.displayName = "MagnetBlock";
356
417
  export {
357
- P as MAGNET_TYPE_CLASSES,
358
- at as MagnetBlock,
359
- it as MagnetTypeText,
360
- rt as applyMagnetType,
361
- ot as getCleanHTML,
362
- lt as removeMagnetType,
363
- st as startMagnetType,
364
- ct as useMagnetType
418
+ G as MAGNET_TYPE_CLASSES,
419
+ ht as MagnetBlock,
420
+ pt as MagnetTypeText,
421
+ ft as applyMagnetType,
422
+ lt as getCleanHTML,
423
+ gt as removeMagnetType,
424
+ dt as startMagnetType,
425
+ mt as useMagnetType
365
426
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liiift-studio/magnettype",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "Cursor-field per-word variable font axis variation and per-character legibility mode",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",