@liiift-studio/magnettype 1.1.3 → 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 client";"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,7 +1,7 @@
1
1
  "use client";
2
- import J, { useRef as j, useCallback as X, useLayoutEffect as et, useEffect as Q, forwardRef as Z, useMemo as nt } from "react";
3
- import { jsx as D } from "react/jsx-runtime";
4
- const R = {
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 = {
5
5
  i: 3,
6
6
  l: 3,
7
7
  1: 3,
@@ -23,14 +23,14 @@ const R = {
23
23
  c: 1,
24
24
  e: 1
25
25
  // similar bowls
26
- }, P = {
26
+ }, G = {
27
27
  /** Applied to each word span in field mode */
28
28
  word: "mt-word",
29
29
  /** Applied to each character span in legibility mode */
30
30
  char: "mt-char",
31
31
  /** Applied to measurement probe spans (never in final output) */
32
32
  probe: "mt-probe"
33
- }, Y = {
33
+ }, j = {
34
34
  axes: { wght: [300, 500] },
35
35
  radius: 120,
36
36
  falloff: "quadratic",
@@ -38,329 +38,389 @@ const R = {
38
38
  wdthBoost: 6,
39
39
  scope: "document"
40
40
  };
41
- function K(t, n = []) {
42
- 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;
43
43
  }
44
- function I(t, n, o) {
45
- if (!t || t === "normal") return `"${n}" ${o}`;
46
- const u = new RegExp(`(["'])${n}\\1\\s+[\\d.eE+-]+`), c = `"${n}" ${o}`;
47
- 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}`;
48
48
  }
49
- function z(t, n) {
50
- let o = t;
51
- for (const [u, c] of Object.entries(n))
52
- 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);
53
53
  return o;
54
54
  }
55
- function W(t, n, o) {
56
- if (n.opacity !== void 0) {
57
- const [u, c] = n.opacity;
58
- 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);
59
59
  }
60
- n.italic === !0 && (t.style.fontStyle = o > 0.5 ? "italic" : "");
60
+ r.italic === !0 && (e.style.fontStyle = o > 0.5 ? "italic" : "");
61
61
  }
62
- function _(t, n) {
63
- 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 = "");
64
64
  }
65
- function ot(t) {
66
- const n = t.cloneNode(!0), o = n.querySelectorAll(
67
- `.${P.word}, .${P.char}`
65
+ function lt(e) {
66
+ const r = e.cloneNode(!0), o = r.querySelectorAll(
67
+ `.${G.word}, .${G.char}`
68
68
  );
69
- return Array.from(o).reverse().forEach((c) => {
70
- const a = c.parentNode;
71
- if (a) {
72
- for (; c.firstChild; ) a.insertBefore(c.firstChild, c);
73
- 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);
74
74
  }
75
- }), n.innerHTML;
75
+ }), r.innerHTML;
76
76
  }
77
- function lt(t, n) {
78
- t.innerHTML = n;
77
+ function gt(e, r) {
78
+ e.innerHTML = r;
79
79
  }
80
- function rt(t, n, o = {}) {
80
+ function ft(e, r, o = {}) {
81
81
  if (typeof window > "u") return () => {
82
82
  };
83
83
  if (window.matchMedia("(prefers-reduced-motion: reduce)").matches)
84
- return t.innerHTML = n, () => {
84
+ return e.innerHTML = r, () => {
85
85
  };
86
- 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;
87
- t.innerHTML = n;
88
- const N = getComputedStyle(t).fontVariationSettings, F = N.match(/"wdth"\s+([\d.eE+-]+)/), y = F ? parseFloat(F[1]) : 100, q = K(t), w = [];
89
- for (const r of q) {
90
- const e = r.textContent ?? "";
91
- if (!e || !e.split("").some((m) => m in R)) continue;
92
- const C = document.createDocumentFragment();
93
- for (const m of e) {
94
- const V = R[m];
95
- if (V === void 0) {
96
- const S = C.lastChild;
97
- 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));
98
98
  } else {
99
- const S = document.createElement("span");
100
- 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 });
101
101
  }
102
102
  }
103
- r.parentNode.replaceChild(C, r);
103
+ c.parentNode.replaceChild(s, c);
104
104
  }
105
105
  if (requestAnimationFrame(() => {
106
- typeof window < "u" && Math.abs(window.scrollY - x) > 2 && window.scrollTo({ top: x, behavior: "instant" });
107
- }), 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 () => {
108
108
  };
109
- v && w.forEach(({ span: r }) => _(r, v));
110
- let p = -9999, b = -9999, E = !1, i = 0, B = !0, T = null;
111
- function s() {
112
- if (!B) return;
113
- if (!E) {
114
- w.forEach(({ span: e }) => {
115
- f > 0 && (e.style.transition = `font-variation-settings ${f}ms ease`), e.style.fontVariationSettings = I(N, "wdth", y), v && _(e, v);
116
- }), f > 0 && (T !== null && clearTimeout(T), T = setTimeout(() => {
117
- w.forEach(({ span: e }) => {
118
- e.style.transition = "";
119
- }), T = null;
120
- }, 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;
121
121
  return;
122
122
  }
123
- const r = w.map(({ span: e }) => e.getBoundingClientRect());
124
- w.forEach(({ span: e, riskLevel: L }, C) => {
125
- e.style.transition = "";
126
- 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) * $;
127
- e.style.fontVariationSettings = I(N, "wdth", y + H), v && W(e, v, $);
128
- }), 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);
129
129
  }
130
- function g(r) {
131
- 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));
132
132
  }
133
- function h() {
134
- E = !1, i === 0 && (i = requestAnimationFrame(s));
133
+ function $() {
134
+ T = !1, u === 0 && (u = requestAnimationFrame(h));
135
135
  }
136
- function l(r) {
137
- 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)));
138
138
  }
139
- function A() {
140
- E = !1, i === 0 && (i = requestAnimationFrame(s));
139
+ function k() {
140
+ T = !1, u === 0 && (u = requestAnimationFrame(h));
141
141
  }
142
- const d = M === "document" ? document : t;
143
- return d.addEventListener("mousemove", g), d.addEventListener("mouseleave", h), d.addEventListener("touchmove", l, { passive: !0 }), d.addEventListener("touchend", A), () => {
144
- 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;
145
145
  };
146
146
  }
147
- function st(t, n, o = {}) {
147
+ function dt(e, r, o = {}) {
148
148
  if (typeof window > "u") return () => {
149
149
  };
150
150
  if (window.matchMedia("(prefers-reduced-motion: reduce)").matches)
151
- return t.innerHTML = n, () => {
151
+ return e.innerHTML = r, () => {
152
152
  };
153
- 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;
154
- t.innerHTML = n;
155
- const F = K(t), y = [];
156
- for (const r of F) {
157
- const e = r.textContent ?? "";
158
- if (!e.trim()) continue;
159
- const L = e.split(/(\S+)/), C = document.createDocumentFragment();
160
- for (let m = 0; m < L.length; m += 2) {
161
- const V = L[m], S = L[m + 1];
162
- if (!S) continue;
163
- const O = L[m + 3] === void 0 ? L[m + 2] ?? "" : "", $ = document.createElement("span");
164
- $.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);
165
165
  }
166
- r.parentNode.replaceChild(C, r);
166
+ c.parentNode.replaceChild(s, c);
167
167
  }
168
168
  if (requestAnimationFrame(() => {
169
- typeof window < "u" && Math.abs(window.scrollY - N) > 2 && window.scrollTo({ top: N, behavior: "instant" });
170
- }), 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 () => {
171
171
  };
172
- const q = getComputedStyle(t).fontVariationSettings, w = z(
172
+ const q = getComputedStyle(e).fontVariationSettings, m = ot(
173
173
  q,
174
- Object.fromEntries(Object.entries(u).map(([r, [e]]) => [r, e]))
174
+ Object.fromEntries(Object.entries(p).map(([c, [t]]) => [c, t]))
175
175
  );
176
- y.forEach((r) => {
177
- r.style.fontVariationSettings = w, f && _(r, f);
176
+ E.forEach((c) => {
177
+ c.style.fontVariationSettings = m, f && Q(c, f);
178
178
  });
179
- let p = -9999, b = -9999, E = !1, i = 0, B = !0, T = null;
180
- function s() {
181
- if (!B) return;
182
- if (!E) {
183
- y.forEach((e) => {
184
- x > 0 && (e.style.transition = `font-variation-settings ${x}ms ease`), e.style.fontVariationSettings = w, f && _(e, f);
185
- }), x > 0 && (T !== null && clearTimeout(T), T = setTimeout(() => {
186
- y.forEach((e) => {
187
- e.style.transition = "";
188
- }), T = null;
189
- }, 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;
190
190
  return;
191
191
  }
192
- const r = y.map((e) => e.getBoundingClientRect());
193
- y.forEach((e, L) => {
194
- e.style.transition = "";
195
- 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 = {};
196
- for (const U of Object.keys(u)) {
197
- const [G, tt] = u[U] ?? [300, 500];
198
- 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;
199
199
  }
200
- e.style.fontVariationSettings = z(q, H), f && W(e, f, O);
201
- }), i = requestAnimationFrame(s);
200
+ t.style.fontVariationSettings = ot(q, I), f && st(t, f, b);
201
+ }), u = requestAnimationFrame(h);
202
202
  }
203
- function g(r) {
204
- 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));
205
205
  }
206
- function h() {
207
- E = !1, i === 0 && (i = requestAnimationFrame(s));
206
+ function $() {
207
+ T = !1, u === 0 && (u = requestAnimationFrame(h));
208
208
  }
209
- function l(r) {
210
- 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)));
211
211
  }
212
- function A() {
213
- E = !1, i === 0 && (i = requestAnimationFrame(s));
212
+ function k() {
213
+ T = !1, u === 0 && (u = requestAnimationFrame(h));
214
214
  }
215
- const d = v === "document" ? document : t;
216
- return d.addEventListener("mousemove", g), d.addEventListener("mouseleave", h), d.addEventListener("touchmove", l, { passive: !0 }), d.addEventListener("touchend", A), () => {
217
- 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;
218
218
  };
219
219
  }
220
- function ct(t) {
221
- const n = j(null), o = j(null), u = j(t);
222
- u.current = t;
223
- 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(() => {
224
- const p = n.current;
225
- if (!p) return;
226
- 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);
227
- }, [a, y, v, f, x, N, F, q]);
228
- return et(() => (w(), () => {
229
- c.current && (c.current(), c.current = null);
230
- }), [w]), Q(() => {
231
- document.fonts.ready.then(w);
232
- }, [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;
233
233
  }
234
- const it = Z(
235
- function({ children: n, as: o = "p", className: u, style: c, ...a }, M) {
236
- const v = ct(a), f = X(
237
- (x) => {
238
- 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);
239
239
  },
240
240
  // eslint-disable-next-line react-hooks/exhaustive-deps
241
- [M]
241
+ [S]
242
242
  );
243
- 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 });
244
244
  }
245
245
  );
246
- it.displayName = "MagnetTypeText";
247
- const at = Z(
246
+ pt.displayName = "MagnetTypeText";
247
+ const ht = ct(
248
248
  function({
249
- children: n,
249
+ children: r,
250
250
  as: o = "p",
251
- className: u,
252
- style: c,
253
- minWeight: a = 300,
254
- maxWeight: M = 600,
255
- proximityRadius: v,
251
+ className: p,
252
+ style: a,
253
+ minWeight: l = 300,
254
+ maxWeight: S = 600,
255
+ proximityRadius: w,
256
256
  spreadRadius: f,
257
- fixedAxes: x = {}
258
- }, N) {
259
- const F = j(null), y = j(null), q = j([]), w = X(
260
- (s) => {
261
- 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);
262
264
  },
263
265
  // eslint-disable-next-line react-hooks/exhaustive-deps
264
- [N]
266
+ [E]
265
267
  );
266
- function p(s) {
267
- const g = [`'wght' ${s.toFixed(0)}`];
268
- for (const [h, l] of Object.entries(x)) g.push(`'${h}' ${l}`);
269
- 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(", ");
270
272
  }
271
- const b = nt(() => {
272
- if (!f) return n;
273
- q.current = [];
274
- let s = 0;
275
- function g(h) {
276
- if (typeof h == "string")
277
- return [...h].map((l) => {
278
- if (/\s/.test(l)) return l;
279
- const A = s++;
280
- return /* @__PURE__ */ D(
281
- "span",
282
- {
283
- ref: (d) => {
284
- q.current[A] = d;
285
- },
286
- style: { fontVariationSettings: p(a) },
287
- children: l
288
- },
289
- A
290
- );
291
- });
292
- if (Array.isArray(h)) return h.map((l, A) => /* @__PURE__ */ D(J.Fragment, { children: g(l) }, A));
293
- if (J.isValidElement(h)) {
294
- const l = h;
295
- if (l.props.children !== void 0)
296
- return J.cloneElement(l, {}, g(l.props.children));
297
- }
298
- 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;
299
301
  }
300
- return g(n);
301
- }, [n, f, a, JSON.stringify(x)]);
302
- function E(s, g) {
303
- const h = F.current;
304
- if (!h) return;
305
- 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);
306
- if (v !== void 0 && !f) {
307
- const L = 1 - (1 - Math.max(0, 1 - r / v)) ** 2;
308
- 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);
309
306
  return;
310
307
  }
311
308
  if (f) {
312
- if (v !== void 0 && r > v) {
313
- h.style.fontVariationSettings = p(a);
314
- 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));
315
312
  return;
316
313
  }
317
- for (const e of q.current) {
318
- if (!e) continue;
319
- 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;
320
- 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;
321
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
+ }
322
332
  }
323
333
  }
324
- const i = X(
325
- (s) => {
326
- 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);
327
339
  },
328
- [a, M, v, f]
329
- ), B = X(
340
+ // eslint-disable-next-line react-hooks/exhaustive-deps
341
+ [l, S, w, f, L, Y, JSON.stringify(M)]
342
+ ), k = R(
330
343
  () => {
331
- y.current && E(y.current.x, y.current.y);
344
+ m.current && $(m.current.x, m.current.y);
332
345
  },
333
- [a, M, v, f]
334
- ), T = X(
346
+ // eslint-disable-next-line react-hooks/exhaustive-deps
347
+ [l, S, w, f, L, JSON.stringify(M)]
348
+ ), v = R(
335
349
  () => {
336
- y.current = null;
337
- const s = F.current;
338
- s && (s.style.fontVariationSettings = p(a));
339
- 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));
340
354
  },
341
- [a]
355
+ // eslint-disable-next-line react-hooks/exhaustive-deps
356
+ [l, JSON.stringify(M)]
342
357
  );
343
- return Q(() => (window.addEventListener("mousemove", i, { passive: !0 }), window.addEventListener("scroll", B, { passive: !0, capture: !0 }), document.documentElement.addEventListener("mouseleave", T), () => {
344
- window.removeEventListener("mousemove", i), window.removeEventListener("scroll", B, { capture: !0 }), document.documentElement.removeEventListener("mouseleave", T);
345
- }), [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(
346
406
  o,
347
407
  {
348
- ref: w,
349
- className: u,
350
- style: { fontVariationSettings: p(a), ...c },
351
- children: b
408
+ ref: N,
409
+ className: p,
410
+ style: { fontVariationSettings: h(l), ...a },
411
+ children: c
352
412
  }
353
413
  );
354
414
  }
355
415
  );
356
- at.displayName = "MagnetBlock";
416
+ ht.displayName = "MagnetBlock";
357
417
  export {
358
- P as MAGNET_TYPE_CLASSES,
359
- at as MagnetBlock,
360
- it as MagnetTypeText,
361
- rt as applyMagnetType,
362
- ot as getCleanHTML,
363
- lt as removeMagnetType,
364
- st as startMagnetType,
365
- 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
366
426
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liiift-studio/magnettype",
3
- "version": "1.1.3",
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",