pinch-type 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Lucas Crespo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # pinch-type
2
+
3
+ Three canvas-based text effects for mobile web, built on [`@chenglou/pretext`](https://github.com/chenglou/pretext).
4
+
5
+ - **Pinch Type** — intercepts pinch-to-zoom and scales text size instead of zooming the page
6
+ - **Scroll Morph** — fisheye effect: text near the viewport center is large and bright, edges are small and dim
7
+ - **Combined** — both effects together
8
+
9
+ [**Live Demo →**](https://pinch-type.surge.sh)
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install pinch-type @chenglou/pretext
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```ts
20
+ import { createPinchType, createScrollMorph, createPinchMorph } from 'pinch-type';
21
+
22
+ // 1. Pinch Type — uniform text, pinch gestures scale all text
23
+ const pt = createPinchType(document.getElementById('reader'));
24
+ pt.setText('Your long article text here…');
25
+
26
+ // 2. Scroll Morph — fisheye effect, no pinch zoom
27
+ const sm = createScrollMorph(document.getElementById('reader'));
28
+ sm.setText('Your long article text here…');
29
+
30
+ // 3. Combined — fisheye + pinch-to-zoom (the original behavior)
31
+ const pm = createPinchMorph(document.getElementById('reader'));
32
+ pm.setText('Your long article text here…');
33
+
34
+ // Clean up when done:
35
+ instance.destroy();
36
+ ```
37
+
38
+ The container element should have a defined width and height (e.g. `100vw × 100vh`). Each function creates a fullscreen `<canvas>` inside it.
39
+
40
+ ## API
41
+
42
+ ### `createPinchType(element, options?)`
43
+
44
+ Uniform text rendering with pinch-to-zoom scaling.
45
+
46
+ | Option | Type | Default | Description |
47
+ |---|---|---|---|
48
+ | `fontSize` | `number` | `18` | Base font size |
49
+ | `minFontSize` | `number` | `8` | Smallest size reachable via pinch |
50
+ | `maxFontSize` | `number` | `60` | Largest size reachable via pinch |
51
+ | `fontFamily` | `string` | `"Inter", system-ui, sans-serif` | CSS font-family |
52
+ | `lineHeight` | `number` | `1.57` | Line-height ratio |
53
+ | `padding` | `number` | `28` | Content padding (px) |
54
+ | `background` | `string` | `#0a0a0a` | Canvas background color |
55
+ | `friction` | `number` | `0.95` | Scroll momentum friction (0–1) |
56
+ | `onZoom` | `(fontSize) => void` | — | Callback after each pinch zoom |
57
+
58
+ ### `createScrollMorph(element, options?)`
59
+
60
+ Fisheye scroll effect. No pinch-to-zoom.
61
+
62
+ | Option | Type | Default | Description |
63
+ |---|---|---|---|
64
+ | `centerFontSize` | `number` | `26` | Font size at viewport center |
65
+ | `edgeFontSize` | `number` | `11` | Font size at viewport edges |
66
+ | `morphRadius` | `number` | `300` | Radius (px) of the center→edge gradient |
67
+ | `fontFamily` | `string` | `"Inter", system-ui, sans-serif` | CSS font-family |
68
+ | `lineHeight` | `number` | `1.57` | Line-height ratio |
69
+ | `padding` | `number` | `28` | Content padding (px) |
70
+ | `background` | `string` | `#0a0a0a` | Canvas background color |
71
+ | `friction` | `number` | `0.95` | Scroll momentum friction (0–1) |
72
+
73
+ ### `createPinchMorph(element, options?)`
74
+
75
+ Combined: fisheye scroll effect + pinch-to-zoom.
76
+
77
+ | Option | Type | Default | Description |
78
+ |---|---|---|---|
79
+ | `centerFontSize` | `number` | `26` | Font size at viewport center |
80
+ | `edgeFontSize` | `number` | `11` | Font size at viewport edges |
81
+ | `minFontSize` | `number` | `8` | Smallest size reachable via pinch |
82
+ | `maxFontSize` | `number` | `60` | Largest size reachable via pinch |
83
+ | `morphRadius` | `number` | `300` | Radius (px) of the center→edge gradient |
84
+ | `fontFamily` | `string` | `"Inter", system-ui, sans-serif` | CSS font-family |
85
+ | `lineHeight` | `number` | `1.57` | Line-height ratio |
86
+ | `padding` | `number` | `28` | Content padding (px) |
87
+ | `background` | `string` | `#0a0a0a` | Canvas background color |
88
+ | `friction` | `number` | `0.95` | Scroll momentum friction (0–1) |
89
+ | `onZoom` | `(center, edge) => void` | — | Callback after each pinch zoom |
90
+
91
+ ### Instance Methods (all three)
92
+
93
+ | Method | Description |
94
+ |---|---|
95
+ | `setText(text)` | Update displayed text and re-layout |
96
+ | `resize()` | Force re-layout (auto-called on window resize) |
97
+ | `destroy()` | Remove canvas, listeners, and animation loop |
98
+ | `canvas` | The underlying `<canvas>` element (read-only) |
99
+
100
+ ## How It Works
101
+
102
+ Text is measured and wrapped using [`@chenglou/pretext`](https://github.com/chenglou/pretext) for accurate segment-aware line breaking. Each frame, lines are drawn to a canvas. For scroll morph, font size and opacity are interpolated based on distance from the viewport center (ease-out cubic). Touch events drive momentum scrolling with configurable friction, and two-finger pinch gestures scale the font size range in real time.
103
+
104
+ ## License
105
+
106
+ [MIT](./LICENSE) — Lucas Crespo
package/dist/index.cjs ADDED
@@ -0,0 +1,19 @@
1
+ "use strict";var ne=Object.defineProperty;var oe=Object.getOwnPropertyDescriptor;var se=Object.getOwnPropertyNames;var ce=Object.prototype.hasOwnProperty;var re=(h,t)=>{for(var E in t)ne(h,E,{get:t[E],enumerable:!0})},ae=(h,t,E,D)=>{if(t&&typeof t=="object"||typeof t=="function")for(let b of se(t))!ce.call(h,b)&&b!==E&&ne(h,b,{get:()=>t[b],enumerable:!(D=oe(t,b))||D.enumerable});return h};var le=h=>ae(ne({},"__esModule",{value:!0}),h);var de={};re(de,{createPinchMorph:()=>fe,createPinchType:()=>he,createScrollMorph:()=>ue});module.exports=le(de);var j=require("@chenglou/pretext");function L(h,t,E){return Math.max(t,Math.min(E,h))}function ie(h){let t=document.createElement("canvas");return t.style.display="block",t.style.width="100%",t.style.height="100%",t.style.touchAction="none",h.appendChild(t),t}function he(h,t={}){let E=t.minFontSize??8,D=t.maxFontSize??60,b=t.fontFamily??'"Inter", system-ui, -apple-system, sans-serif',_=t.lineHeight??1.57,A=t.padding??28,ee=t.background??"#0a0a0a",G=t.friction??.95,Q=t.onZoom,o=t.fontSize??18,i=ie(h),u=i.getContext("2d"),T=Math.min(devicePixelRatio||1,3),s=0,p=0,H="",F=[],x=0,a=0,c=0,S=0,w=0,l=0,g=!1,P=!1,Z=0,O=0,C=0,X=!1;function k(){if(!H||s===0)return;let n=s-A*2,f=o*_,d=`400 ${o}px ${b}`,R=H.split(`
2
+
3
+ `);F=[];let e=A+10;for(let m of R){let v=m.trim();if(!v)continue;u.font=d;let $=(0,j.prepareWithSegments)(v,d),q=(0,j.layoutWithLines)($,n,f);for(let W=0;W<q.lines.length;W++)F.push({text:q.lines[W].text,y:e+W*f,baseSize:o,weight:400});e+=q.lines.length*f+f*.6}x=e+A,a=Math.max(0,x-p),c=L(c,0,a)}function J(){let n=T;u.fillStyle=ee,u.fillRect(0,0,s*n,p*n),u.textBaseline="top",u.fillStyle="#e5e5e5",u.font=`400 ${o*n}px ${b}`;for(let f of F){let d=f.y-c;d<-100||d>p+100||u.fillText(f.text,A*n,d*n)}}function K(){X||(g||(c+=S,S*=G,c<0?(c*=.85,S*=.5):c>a&&(c=a+(c-a)*.85,S*=.5),Math.abs(S)<.1&&(S=0)),J(),C=requestAnimationFrame(K))}function I(n){let f=n.touches[0].clientX-n.touches[1].clientX,d=n.touches[0].clientY-n.touches[1].clientY;return Math.hypot(f,d)}function r(n){n.touches.length===2?(P=!0,Z=I(n),O=o,S=0,g=!1):n.touches.length===1&&!P&&(g=!0,S=0,w=n.touches[0].clientY,l=performance.now()),n.preventDefault()}function Y(n){if(P&&n.touches.length===2){let m=I(n)/Z,v=L(Math.round(O*m),E,D);v!==o&&(o=v,k(),Q?.(o)),n.preventDefault();return}if(!g||n.touches.length!==1)return;let f=n.touches[0].clientY,d=w-f,R=performance.now(),e=R-l;c+=d,c=L(c,-50,a+50),e>0&&(S=d/e*16),w=f,l=R,n.preventDefault()}function y(n){n.touches.length<2&&(P=!1),n.touches.length===0&&(g=!1)}function z(n){if(n.preventDefault(),n.ctrlKey||n.metaKey){let f=n.deltaY>0?-1:1,d=L(o+f,E,D);d!==o&&(o=d,k(),Q?.(o))}else c+=n.deltaY,c=L(c,-50,a+50)}function M(){T=Math.min(devicePixelRatio||1,3),s=h.clientWidth,p=h.clientHeight,i.width=s*T,i.height=p*T,i.style.width=s+"px",i.style.height=p+"px",k()}return i.addEventListener("touchstart",r,{passive:!1}),i.addEventListener("touchmove",Y,{passive:!1}),i.addEventListener("touchend",y),i.addEventListener("wheel",z,{passive:!1}),window.addEventListener("resize",M),M(),C=requestAnimationFrame(K),{setText(n){H=n,c=0,S=0,k()},resize:M,destroy(){X=!0,cancelAnimationFrame(C),i.removeEventListener("touchstart",r),i.removeEventListener("touchmove",Y),i.removeEventListener("touchend",y),i.removeEventListener("wheel",z),window.removeEventListener("resize",M),i.remove()},get canvas(){return i}}}function ue(h,t={}){let E=t.fontFamily??'"Inter", system-ui, -apple-system, sans-serif',D=t.lineHeight??1.57,b=t.padding??28,_=t.background??"#0a0a0a",A=t.morphRadius??300,ee=t.friction??.95,G=t.centerFontSize??26,Q=t.edgeFontSize??11,o=ie(h),i=o.getContext("2d"),u=Math.min(devicePixelRatio||1,3),T=0,s=0,p="",H=[],F=0,x=0,a=0,c=0,S=0,w=0,l=!1,g=0,P=!1;function Z(){if(!p||T===0)return;let r=T-b*2,Y=G,y=Y*D,z=`400 ${Y}px ${E}`,M=p.split(`
4
+
5
+ `);H=[];let n=b+10;for(let f of M){let d=f.trim();if(!d)continue;i.font=z;let R=(0,j.prepareWithSegments)(d,z),e=(0,j.layoutWithLines)(R,r,y);for(let m=0;m<e.lines.length;m++)H.push({text:e.lines[m].text,y:n+m*y,baseSize:Y,weight:400});n+=e.lines.length*y+y*.6}F=n+b,x=Math.max(0,F-s),a=L(a,0,x)}function O(){let r=u;i.fillStyle=_,i.fillRect(0,0,T*r,s*r);let Y=s/2;i.textBaseline="top";for(let y of H){let z=y.y-a;if(z<-100||z>s+100)continue;let M=Math.abs(z-Y),f=1-(1-Math.min(M/A,1))**3,d=G+(Q-G)*f,R=1+(.25-1)*f,e=Math.round(255-153*f);i.save(),i.globalAlpha=R,i.fillStyle=`rgb(${e},${e},${e})`,i.font=`${y.weight} ${d*r}px ${E}`;let m=(d-y.baseSize)*.5;i.fillText(y.text,b*r,(z-m)*r),i.restore()}}function C(){P||(l||(a+=c,c*=ee,a<0?(a*=.85,c*=.5):a>x&&(a=x+(a-x)*.85,c*=.5),Math.abs(c)<.1&&(c=0)),O(),g=requestAnimationFrame(C))}function X(r){r.touches.length===1&&(l=!0,c=0,S=r.touches[0].clientY,w=performance.now()),r.preventDefault()}function k(r){if(!l||r.touches.length!==1)return;let Y=r.touches[0].clientY,y=S-Y,z=performance.now(),M=z-w;a+=y,a=L(a,-50,x+50),M>0&&(c=y/M*16),S=Y,w=z,r.preventDefault()}function J(r){r.touches.length===0&&(l=!1)}function K(r){a+=r.deltaY,a=L(a,-50,x+50),r.preventDefault()}function I(){u=Math.min(devicePixelRatio||1,3),T=h.clientWidth,s=h.clientHeight,o.width=T*u,o.height=s*u,o.style.width=T+"px",o.style.height=s+"px",Z()}return o.addEventListener("touchstart",X,{passive:!1}),o.addEventListener("touchmove",k,{passive:!1}),o.addEventListener("touchend",J),o.addEventListener("wheel",K,{passive:!1}),window.addEventListener("resize",I),I(),g=requestAnimationFrame(C),{setText(r){p=r,a=0,c=0,Z()},resize:I,destroy(){P=!0,cancelAnimationFrame(g),o.removeEventListener("touchstart",X),o.removeEventListener("touchmove",k),o.removeEventListener("touchend",J),o.removeEventListener("wheel",K),window.removeEventListener("resize",I),o.remove()},get canvas(){return o}}}function fe(h,t={}){let E=t.minFontSize??8,D=t.maxFontSize??60,b=t.fontFamily??'"Inter", system-ui, -apple-system, sans-serif',_=t.lineHeight??1.57,A=t.padding??28,ee=t.background??"#0a0a0a",G=t.morphRadius??300,Q=t.friction??.95,o=t.onZoom,i=t.centerFontSize??26,u=t.edgeFontSize??11,T=u/i,s=ie(h),p=s.getContext("2d"),H=Math.min(devicePixelRatio||1,3),F=0,x=0,a="",c=[],S=0,w=0,l=0,g=0,P=0,Z=0,O=!1,C=!1,X=0,k=0,J=0,K=0,I=!1;function r(){if(!a||F===0)return;let e=F-A*2,m=i,v=m*_,$=`400 ${m}px ${b}`,q=a.split(`
6
+
7
+ `);c=[];let W=A+10;for(let B of q){let V=B.trim();if(!V)continue;p.font=$;let te=(0,j.prepareWithSegments)(V,$),N=(0,j.layoutWithLines)(te,e,v);for(let U=0;U<N.lines.length;U++)c.push({text:N.lines[U].text,y:W+U*v,baseSize:m,weight:400});W+=N.lines.length*v+v*.6}S=W+A,w=Math.max(0,S-x),l=L(l,0,w)}function Y(){let e=H;p.fillStyle=ee,p.fillRect(0,0,F*e,x*e);let m=x/2;p.textBaseline="top";for(let v of c){let $=v.y-l;if($<-100||$>x+100)continue;let q=Math.abs($-m),B=1-(1-Math.min(q/G,1))**3,V=i+(u-i)*B,te=1+(.25-1)*B,N=Math.round(255-153*B);p.save(),p.globalAlpha=te,p.fillStyle=`rgb(${N},${N},${N})`,p.font=`${v.weight} ${V*e}px ${b}`;let U=(V-v.baseSize)*.5;p.fillText(v.text,A*e,($-U)*e),p.restore()}}function y(){I||(O||(l+=g,g*=Q,l<0?(l*=.85,g*=.5):l>w&&(l=w+(l-w)*.85,g*=.5),Math.abs(g)<.1&&(g=0)),Y(),K=requestAnimationFrame(y))}function z(e){let m=e.touches[0].clientX-e.touches[1].clientX,v=e.touches[0].clientY-e.touches[1].clientY;return Math.hypot(m,v)}function M(e){e.touches.length===2?(C=!0,X=z(e),k=i,J=u,g=0,O=!1):e.touches.length===1&&!C&&(O=!0,g=0,P=e.touches[0].clientY,Z=performance.now()),e.preventDefault()}function n(e){if(C&&e.touches.length===2){let W=z(e)/X,B=L(Math.round(k*W),E,D),V=L(Math.round(J*W),Math.max(E,6),Math.round(D*T));(B!==i||V!==u)&&(i=B,u=V,r(),o?.(i,u)),e.preventDefault();return}if(!O||e.touches.length!==1)return;let m=e.touches[0].clientY,v=P-m,$=performance.now(),q=$-Z;l+=v,l=L(l,-50,w+50),q>0&&(g=v/q*16),P=m,Z=$,e.preventDefault()}function f(e){e.touches.length<2&&(C=!1),e.touches.length===0&&(O=!1)}function d(e){if(e.preventDefault(),e.ctrlKey||e.metaKey){let m=e.deltaY>0?-1:1,v=L(i+m,E,D);v!==i&&(i=v,u=Math.round(i*T),u=L(u,4,i),r(),o?.(i,u))}else l+=e.deltaY,l=L(l,-50,w+50)}function R(){H=Math.min(devicePixelRatio||1,3),F=h.clientWidth,x=h.clientHeight,s.width=F*H,s.height=x*H,s.style.width=F+"px",s.style.height=x+"px",r()}return s.addEventListener("touchstart",M,{passive:!1}),s.addEventListener("touchmove",n,{passive:!1}),s.addEventListener("touchend",f),s.addEventListener("wheel",d,{passive:!1}),window.addEventListener("resize",R),R(),K=requestAnimationFrame(y),{setText(e){a=e,l=0,g=0,r()},resize:R,destroy(){I=!0,cancelAnimationFrame(K),s.removeEventListener("touchstart",M),s.removeEventListener("touchmove",n),s.removeEventListener("touchend",f),s.removeEventListener("wheel",d),window.removeEventListener("resize",R),s.remove()},get canvas(){return s}}}
8
+ /**
9
+ * pinch-type
10
+ *
11
+ * Three canvas-based text effects for mobile web:
12
+ * - **Pinch Type** — pinch-to-zoom scales text size uniformly
13
+ * - **Scroll Morph** — fisheye effect: center text is large/bright, edges small/dim
14
+ * - **Combined** — both effects together
15
+ *
16
+ * @license MIT
17
+ * @author Lucas Crespo
18
+ */
19
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": ["/**\n * pinch-type\n *\n * Three canvas-based text effects for mobile web:\n * - **Pinch Type** \u2014 pinch-to-zoom scales text size uniformly\n * - **Scroll Morph** \u2014 fisheye effect: center text is large/bright, edges small/dim\n * - **Combined** \u2014 both effects together\n *\n * @license MIT\n * @author Lucas Crespo\n */\n\nimport { prepareWithSegments, layoutWithLines } from '@chenglou/pretext';\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface PinchTypeOptions {\n /** Base font size. Default: `18` */\n fontSize?: number;\n /** Smallest font size reachable via pinch. Default: `8` */\n minFontSize?: number;\n /** Largest font size reachable via pinch. Default: `60` */\n maxFontSize?: number;\n /** CSS font-family string. Default: `\"Inter\", system-ui, sans-serif` */\n fontFamily?: string;\n /** Line-height ratio relative to font size. Default: `1.57` */\n lineHeight?: number;\n /** Content padding in CSS pixels. Default: `28` */\n padding?: number;\n /** Background color. Default: `#0a0a0a` */\n background?: string;\n /** Scroll friction (0\u20131). Higher = more momentum. Default: `0.95` */\n friction?: number;\n /** Called after every pinch-zoom with the new font size. */\n onZoom?: (fontSize: number) => void;\n}\n\nexport interface ScrollMorphOptions {\n /** Font size at the viewport center. Default: `26` */\n centerFontSize?: number;\n /** Font size at viewport edges. Default: `11` */\n edgeFontSize?: number;\n /** Smallest font size. Default: `8` */\n minFontSize?: number;\n /** Largest font size. Default: `60` */\n maxFontSize?: number;\n /** CSS font-family string. Default: `\"Inter\", system-ui, sans-serif` */\n fontFamily?: string;\n /** Line-height ratio relative to font size. Default: `1.57` */\n lineHeight?: number;\n /** Content padding in CSS pixels. Default: `28` */\n padding?: number;\n /** Background color. Default: `#0a0a0a` */\n background?: string;\n /** Radius (px) of the morph gradient from viewport center. Default: `300` */\n morphRadius?: number;\n /** Scroll friction (0\u20131). Default: `0.95` */\n friction?: number;\n}\n\nexport interface PinchMorphOptions {\n /** Font size at the viewport center. Default: `26` */\n centerFontSize?: number;\n /** Font size at viewport edges. Default: `11` */\n edgeFontSize?: number;\n /** Smallest font size reachable via pinch. Default: `8` */\n minFontSize?: number;\n /** Largest font size reachable via pinch. Default: `60` */\n maxFontSize?: number;\n /** CSS font-family string. Default: `\"Inter\", system-ui, sans-serif` */\n fontFamily?: string;\n /** Line-height ratio relative to font size. Default: `1.57` */\n lineHeight?: number;\n /** Content padding in CSS pixels. Default: `28` */\n padding?: number;\n /** Background color. Default: `#0a0a0a` */\n background?: string;\n /** Radius (px) of the morph gradient from viewport center. Default: `300` */\n morphRadius?: number;\n /** Scroll friction (0\u20131). Default: `0.95` */\n friction?: number;\n /** Called after every pinch-zoom with the new center and edge sizes. */\n onZoom?: (centerSize: number, edgeSize: number) => void;\n}\n\nexport interface EffectInstance {\n /** Update the displayed text and re-layout. */\n setText(text: string): void;\n /** Force a resize / re-layout (called automatically on window resize). */\n resize(): void;\n /** Remove all listeners and the canvas element. */\n destroy(): void;\n /** The canvas element created by the effect. */\n readonly canvas: HTMLCanvasElement;\n}\n\n// \u2500\u2500\u2500 Internals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface Line {\n text: string;\n y: number;\n baseSize: number;\n weight: number;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n}\n\nfunction createCanvas(container: HTMLElement) {\n const canvas = document.createElement('canvas');\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.touchAction = 'none';\n container.appendChild(canvas);\n return canvas;\n}\n\n// \u2500\u2500\u2500 Pinch Type \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Pinch-to-zoom text scaling. Text renders at a uniform size;\n * pinch gestures scale all text up or down.\n */\nexport function createPinchType(\n container: HTMLElement,\n options: PinchTypeOptions = {},\n): EffectInstance {\n const minFont = options.minFontSize ?? 8;\n const maxFont = options.maxFontSize ?? 60;\n const fontFamily = options.fontFamily ?? '\"Inter\", system-ui, -apple-system, sans-serif';\n const lhRatio = options.lineHeight ?? 1.57;\n const padding = options.padding ?? 28;\n const bg = options.background ?? '#0a0a0a';\n const friction = options.friction ?? 0.95;\n const onZoom = options.onZoom;\n\n let fontSize = options.fontSize ?? 18;\n\n const canvas = createCanvas(container);\n const ctx = canvas.getContext('2d')!;\n let dpr = Math.min(devicePixelRatio || 1, 3);\n let W = 0, H = 0;\n let rawText = '';\n let lines: Line[] = [];\n let totalHeight = 0, maxScroll = 0;\n let scrollY = 0, scrollVelocity = 0;\n let touchLastY = 0, touchLastTime = 0, isTouching = false;\n let pinchActive = false, pinchStartDist = 0, pinchStartSize = 0;\n let raf = 0, destroyed = false;\n\n function layout() {\n if (!rawText || W === 0) return;\n const maxW = W - padding * 2;\n const lh = fontSize * lhRatio;\n const font = `400 ${fontSize}px ${fontFamily}`;\n const paragraphs = rawText.split('\\n\\n');\n lines = [];\n let curY = padding + 10;\n for (const para of paragraphs) {\n const trimmed = para.trim();\n if (!trimmed) continue;\n ctx.font = font;\n const prepared = prepareWithSegments(trimmed, font);\n const result = layoutWithLines(prepared, maxW, lh);\n for (let li = 0; li < result.lines.length; li++) {\n lines.push({ text: result.lines[li].text, y: curY + li * lh, baseSize: fontSize, weight: 400 });\n }\n curY += result.lines.length * lh + lh * 0.6;\n }\n totalHeight = curY + padding;\n maxScroll = Math.max(0, totalHeight - H);\n scrollY = clamp(scrollY, 0, maxScroll);\n }\n\n function render() {\n const d = dpr;\n ctx.fillStyle = bg;\n ctx.fillRect(0, 0, W * d, H * d);\n ctx.textBaseline = 'top';\n ctx.fillStyle = '#e5e5e5';\n ctx.font = `400 ${fontSize * d}px ${fontFamily}`;\n for (const line of lines) {\n const screenY = line.y - scrollY;\n if (screenY < -100 || screenY > H + 100) continue;\n ctx.fillText(line.text, padding * d, screenY * d);\n }\n }\n\n function loop() {\n if (destroyed) return;\n if (!isTouching) {\n scrollY += scrollVelocity;\n scrollVelocity *= friction;\n if (scrollY < 0) { scrollY *= 0.85; scrollVelocity *= 0.5; }\n else if (scrollY > maxScroll) { scrollY = maxScroll + (scrollY - maxScroll) * 0.85; scrollVelocity *= 0.5; }\n if (Math.abs(scrollVelocity) < 0.1) scrollVelocity = 0;\n }\n render();\n raf = requestAnimationFrame(loop);\n }\n\n function pinchDist(e: TouchEvent) {\n const dx = e.touches[0].clientX - e.touches[1].clientX;\n const dy = e.touches[0].clientY - e.touches[1].clientY;\n return Math.hypot(dx, dy);\n }\n\n function onTouchStart(e: TouchEvent) {\n if (e.touches.length === 2) {\n pinchActive = true; pinchStartDist = pinchDist(e); pinchStartSize = fontSize;\n scrollVelocity = 0; isTouching = false;\n } else if (e.touches.length === 1 && !pinchActive) {\n isTouching = true; scrollVelocity = 0;\n touchLastY = e.touches[0].clientY; touchLastTime = performance.now();\n }\n e.preventDefault();\n }\n\n function onTouchMove(e: TouchEvent) {\n if (pinchActive && e.touches.length === 2) {\n const scale = pinchDist(e) / pinchStartDist;\n const newSize = clamp(Math.round(pinchStartSize * scale), minFont, maxFont);\n if (newSize !== fontSize) { fontSize = newSize; layout(); onZoom?.(fontSize); }\n e.preventDefault(); return;\n }\n if (!isTouching || e.touches.length !== 1) return;\n const y = e.touches[0].clientY;\n const dy = touchLastY - y;\n const now = performance.now();\n const dt = now - touchLastTime;\n scrollY += dy; scrollY = clamp(scrollY, -50, maxScroll + 50);\n if (dt > 0) scrollVelocity = (dy / dt) * 16;\n touchLastY = y; touchLastTime = now; e.preventDefault();\n }\n\n function onTouchEnd(e: TouchEvent) {\n if (e.touches.length < 2) pinchActive = false;\n if (e.touches.length === 0) isTouching = false;\n }\n\n function onWheel(e: WheelEvent) {\n e.preventDefault();\n if (e.ctrlKey || e.metaKey) {\n // Trackpad pinch-to-zoom\n const delta = e.deltaY > 0 ? -1 : 1;\n const newSize = clamp(fontSize + delta, minFont, maxFont);\n if (newSize !== fontSize) { fontSize = newSize; layout(); onZoom?.(fontSize); }\n } else {\n scrollY += e.deltaY; scrollY = clamp(scrollY, -50, maxScroll + 50);\n }\n }\n\n function handleResize() {\n dpr = Math.min(devicePixelRatio || 1, 3);\n W = container.clientWidth; H = container.clientHeight;\n canvas.width = W * dpr; canvas.height = H * dpr;\n canvas.style.width = W + 'px'; canvas.style.height = H + 'px';\n layout();\n }\n\n canvas.addEventListener('touchstart', onTouchStart, { passive: false });\n canvas.addEventListener('touchmove', onTouchMove, { passive: false });\n canvas.addEventListener('touchend', onTouchEnd);\n canvas.addEventListener('wheel', onWheel, { passive: false });\n window.addEventListener('resize', handleResize);\n handleResize();\n raf = requestAnimationFrame(loop);\n\n return {\n setText(text: string) { rawText = text; scrollY = 0; scrollVelocity = 0; layout(); },\n resize: handleResize,\n destroy() {\n destroyed = true; cancelAnimationFrame(raf);\n canvas.removeEventListener('touchstart', onTouchStart);\n canvas.removeEventListener('touchmove', onTouchMove);\n canvas.removeEventListener('touchend', onTouchEnd);\n canvas.removeEventListener('wheel', onWheel);\n window.removeEventListener('resize', handleResize);\n canvas.remove();\n },\n get canvas() { return canvas; },\n };\n}\n\n// \u2500\u2500\u2500 Scroll Morph \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Fisheye scroll effect. Text near the viewport center is large and bright;\n * text at the edges is small and dim. No pinch-to-zoom.\n */\nexport function createScrollMorph(\n container: HTMLElement,\n options: ScrollMorphOptions = {},\n): EffectInstance {\n const fontFamily = options.fontFamily ?? '\"Inter\", system-ui, -apple-system, sans-serif';\n const lhRatio = options.lineHeight ?? 1.57;\n const padding = options.padding ?? 28;\n const bg = options.background ?? '#0a0a0a';\n const morphRadius = options.morphRadius ?? 300;\n const friction = options.friction ?? 0.95;\n const centerSize = options.centerFontSize ?? 26;\n const edgeSize = options.edgeFontSize ?? 11;\n\n const canvas = createCanvas(container);\n const ctx = canvas.getContext('2d')!;\n let dpr = Math.min(devicePixelRatio || 1, 3);\n let W = 0, H = 0;\n let rawText = '';\n let lines: Line[] = [];\n let totalHeight = 0, maxScroll = 0;\n let scrollY = 0, scrollVelocity = 0;\n let touchLastY = 0, touchLastTime = 0, isTouching = false;\n let raf = 0, destroyed = false;\n\n function layout() {\n if (!rawText || W === 0) return;\n const maxW = W - padding * 2;\n const fs = centerSize;\n const lh = fs * lhRatio;\n const font = `400 ${fs}px ${fontFamily}`;\n const paragraphs = rawText.split('\\n\\n');\n lines = [];\n let curY = padding + 10;\n for (const para of paragraphs) {\n const trimmed = para.trim();\n if (!trimmed) continue;\n ctx.font = font;\n const prepared = prepareWithSegments(trimmed, font);\n const result = layoutWithLines(prepared, maxW, lh);\n for (let li = 0; li < result.lines.length; li++) {\n lines.push({ text: result.lines[li].text, y: curY + li * lh, baseSize: fs, weight: 400 });\n }\n curY += result.lines.length * lh + lh * 0.6;\n }\n totalHeight = curY + padding;\n maxScroll = Math.max(0, totalHeight - H);\n scrollY = clamp(scrollY, 0, maxScroll);\n }\n\n function render() {\n const d = dpr;\n ctx.fillStyle = bg;\n ctx.fillRect(0, 0, W * d, H * d);\n const viewCenter = H / 2;\n ctx.textBaseline = 'top';\n for (const line of lines) {\n const screenY = line.y - scrollY;\n if (screenY < -100 || screenY > H + 100) continue;\n const dist = Math.abs(screenY - viewCenter);\n const t = Math.min(dist / morphRadius, 1);\n const ease = 1 - (1 - t) ** 3;\n const fontSize = centerSize + (edgeSize - centerSize) * ease;\n const opacity = 1.0 + (0.25 - 1.0) * ease;\n const c = Math.round(255 - (255 - 102) * ease);\n ctx.save();\n ctx.globalAlpha = opacity;\n ctx.fillStyle = `rgb(${c},${c},${c})`;\n ctx.font = `${line.weight} ${fontSize * d}px ${fontFamily}`;\n const yOffset = (fontSize - line.baseSize) * 0.5;\n ctx.fillText(line.text, padding * d, (screenY - yOffset) * d);\n ctx.restore();\n }\n }\n\n function loop() {\n if (destroyed) return;\n if (!isTouching) {\n scrollY += scrollVelocity; scrollVelocity *= friction;\n if (scrollY < 0) { scrollY *= 0.85; scrollVelocity *= 0.5; }\n else if (scrollY > maxScroll) { scrollY = maxScroll + (scrollY - maxScroll) * 0.85; scrollVelocity *= 0.5; }\n if (Math.abs(scrollVelocity) < 0.1) scrollVelocity = 0;\n }\n render();\n raf = requestAnimationFrame(loop);\n }\n\n function onTouchStart(e: TouchEvent) {\n if (e.touches.length === 1) {\n isTouching = true; scrollVelocity = 0;\n touchLastY = e.touches[0].clientY; touchLastTime = performance.now();\n }\n e.preventDefault();\n }\n\n function onTouchMove(e: TouchEvent) {\n if (!isTouching || e.touches.length !== 1) return;\n const y = e.touches[0].clientY;\n const dy = touchLastY - y;\n const now = performance.now();\n const dt = now - touchLastTime;\n scrollY += dy; scrollY = clamp(scrollY, -50, maxScroll + 50);\n if (dt > 0) scrollVelocity = (dy / dt) * 16;\n touchLastY = y; touchLastTime = now; e.preventDefault();\n }\n\n function onTouchEnd(e: TouchEvent) {\n if (e.touches.length === 0) isTouching = false;\n }\n\n function onWheel(e: WheelEvent) {\n scrollY += e.deltaY; scrollY = clamp(scrollY, -50, maxScroll + 50); e.preventDefault();\n }\n\n function handleResize() {\n dpr = Math.min(devicePixelRatio || 1, 3);\n W = container.clientWidth; H = container.clientHeight;\n canvas.width = W * dpr; canvas.height = H * dpr;\n canvas.style.width = W + 'px'; canvas.style.height = H + 'px';\n layout();\n }\n\n canvas.addEventListener('touchstart', onTouchStart, { passive: false });\n canvas.addEventListener('touchmove', onTouchMove, { passive: false });\n canvas.addEventListener('touchend', onTouchEnd);\n canvas.addEventListener('wheel', onWheel, { passive: false });\n window.addEventListener('resize', handleResize);\n handleResize();\n raf = requestAnimationFrame(loop);\n\n return {\n setText(text: string) { rawText = text; scrollY = 0; scrollVelocity = 0; layout(); },\n resize: handleResize,\n destroy() {\n destroyed = true; cancelAnimationFrame(raf);\n canvas.removeEventListener('touchstart', onTouchStart);\n canvas.removeEventListener('touchmove', onTouchMove);\n canvas.removeEventListener('touchend', onTouchEnd);\n canvas.removeEventListener('wheel', onWheel);\n window.removeEventListener('resize', handleResize);\n canvas.remove();\n },\n get canvas() { return canvas; },\n };\n}\n\n// \u2500\u2500\u2500 Pinch Morph (Combined) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Combined: scroll-morph fisheye effect + pinch-to-zoom text scaling.\n * This is the original pinch-type behavior.\n */\nexport function createPinchMorph(\n container: HTMLElement,\n options: PinchMorphOptions = {},\n): EffectInstance {\n const minFont = options.minFontSize ?? 8;\n const maxFont = options.maxFontSize ?? 60;\n const fontFamily = options.fontFamily ?? '\"Inter\", system-ui, -apple-system, sans-serif';\n const lhRatio = options.lineHeight ?? 1.57;\n const padding = options.padding ?? 28;\n const bg = options.background ?? '#0a0a0a';\n const morphRadius = options.morphRadius ?? 300;\n const friction = options.friction ?? 0.95;\n const onZoom = options.onZoom;\n\n let centerSize = options.centerFontSize ?? 26;\n let edgeSize = options.edgeFontSize ?? 11;\n const initialRatio = edgeSize / centerSize;\n\n const canvas = createCanvas(container);\n const ctx = canvas.getContext('2d')!;\n let dpr = Math.min(devicePixelRatio || 1, 3);\n let W = 0, H = 0;\n let rawText = '';\n let lines: Line[] = [];\n let totalHeight = 0, maxScroll = 0;\n let scrollY = 0, scrollVelocity = 0;\n let touchLastY = 0, touchLastTime = 0, isTouching = false;\n let pinchActive = false, pinchStartDist = 0, pinchStartCenter = 0, pinchStartEdge = 0;\n let raf = 0, destroyed = false;\n\n function layout() {\n if (!rawText || W === 0) return;\n const maxW = W - padding * 2;\n const fs = centerSize;\n const lh = fs * lhRatio;\n const font = `400 ${fs}px ${fontFamily}`;\n const paragraphs = rawText.split('\\n\\n');\n lines = [];\n let curY = padding + 10;\n for (const para of paragraphs) {\n const trimmed = para.trim();\n if (!trimmed) continue;\n ctx.font = font;\n const prepared = prepareWithSegments(trimmed, font);\n const result = layoutWithLines(prepared, maxW, lh);\n for (let li = 0; li < result.lines.length; li++) {\n lines.push({ text: result.lines[li].text, y: curY + li * lh, baseSize: fs, weight: 400 });\n }\n curY += result.lines.length * lh + lh * 0.6;\n }\n totalHeight = curY + padding;\n maxScroll = Math.max(0, totalHeight - H);\n scrollY = clamp(scrollY, 0, maxScroll);\n }\n\n function render() {\n const d = dpr;\n ctx.fillStyle = bg;\n ctx.fillRect(0, 0, W * d, H * d);\n const viewCenter = H / 2;\n ctx.textBaseline = 'top';\n for (const line of lines) {\n const screenY = line.y - scrollY;\n if (screenY < -100 || screenY > H + 100) continue;\n const dist = Math.abs(screenY - viewCenter);\n const t = Math.min(dist / morphRadius, 1);\n const ease = 1 - (1 - t) ** 3;\n const fontSize = centerSize + (edgeSize - centerSize) * ease;\n const opacity = 1.0 + (0.25 - 1.0) * ease;\n const c = Math.round(255 - (255 - 102) * ease);\n ctx.save();\n ctx.globalAlpha = opacity;\n ctx.fillStyle = `rgb(${c},${c},${c})`;\n ctx.font = `${line.weight} ${fontSize * d}px ${fontFamily}`;\n const yOffset = (fontSize - line.baseSize) * 0.5;\n ctx.fillText(line.text, padding * d, (screenY - yOffset) * d);\n ctx.restore();\n }\n }\n\n function loop() {\n if (destroyed) return;\n if (!isTouching) {\n scrollY += scrollVelocity; scrollVelocity *= friction;\n if (scrollY < 0) { scrollY *= 0.85; scrollVelocity *= 0.5; }\n else if (scrollY > maxScroll) { scrollY = maxScroll + (scrollY - maxScroll) * 0.85; scrollVelocity *= 0.5; }\n if (Math.abs(scrollVelocity) < 0.1) scrollVelocity = 0;\n }\n render();\n raf = requestAnimationFrame(loop);\n }\n\n function pinchDist(e: TouchEvent) {\n const dx = e.touches[0].clientX - e.touches[1].clientX;\n const dy = e.touches[0].clientY - e.touches[1].clientY;\n return Math.hypot(dx, dy);\n }\n\n function onTouchStart(e: TouchEvent) {\n if (e.touches.length === 2) {\n pinchActive = true; pinchStartDist = pinchDist(e);\n pinchStartCenter = centerSize; pinchStartEdge = edgeSize;\n scrollVelocity = 0; isTouching = false;\n } else if (e.touches.length === 1 && !pinchActive) {\n isTouching = true; scrollVelocity = 0;\n touchLastY = e.touches[0].clientY; touchLastTime = performance.now();\n }\n e.preventDefault();\n }\n\n function onTouchMove(e: TouchEvent) {\n if (pinchActive && e.touches.length === 2) {\n const scale = pinchDist(e) / pinchStartDist;\n const newCenter = clamp(Math.round(pinchStartCenter * scale), minFont, maxFont);\n const newEdge = clamp(Math.round(pinchStartEdge * scale), Math.max(minFont, 6), Math.round(maxFont * initialRatio));\n if (newCenter !== centerSize || newEdge !== edgeSize) {\n centerSize = newCenter; edgeSize = newEdge; layout(); onZoom?.(centerSize, edgeSize);\n }\n e.preventDefault(); return;\n }\n if (!isTouching || e.touches.length !== 1) return;\n const y = e.touches[0].clientY;\n const dy = touchLastY - y;\n const now = performance.now();\n const dt = now - touchLastTime;\n scrollY += dy; scrollY = clamp(scrollY, -50, maxScroll + 50);\n if (dt > 0) scrollVelocity = (dy / dt) * 16;\n touchLastY = y; touchLastTime = now; e.preventDefault();\n }\n\n function onTouchEnd(e: TouchEvent) {\n if (e.touches.length < 2) pinchActive = false;\n if (e.touches.length === 0) isTouching = false;\n }\n\n function onWheel(e: WheelEvent) {\n e.preventDefault();\n if (e.ctrlKey || e.metaKey) {\n // Trackpad pinch-to-zoom\n const delta = e.deltaY > 0 ? -1 : 1;\n const newCenter = clamp(centerSize + delta, minFont, maxFont);\n if (newCenter !== centerSize) {\n centerSize = newCenter;\n edgeSize = Math.round(centerSize * initialRatio);\n edgeSize = clamp(edgeSize, 4, centerSize);\n layout();\n onZoom?.(centerSize, edgeSize);\n }\n } else {\n scrollY += e.deltaY; scrollY = clamp(scrollY, -50, maxScroll + 50);\n }\n }\n\n function handleResize() {\n dpr = Math.min(devicePixelRatio || 1, 3);\n W = container.clientWidth; H = container.clientHeight;\n canvas.width = W * dpr; canvas.height = H * dpr;\n canvas.style.width = W + 'px'; canvas.style.height = H + 'px';\n layout();\n }\n\n canvas.addEventListener('touchstart', onTouchStart, { passive: false });\n canvas.addEventListener('touchmove', onTouchMove, { passive: false });\n canvas.addEventListener('touchend', onTouchEnd);\n canvas.addEventListener('wheel', onWheel, { passive: false });\n window.addEventListener('resize', handleResize);\n handleResize();\n raf = requestAnimationFrame(loop);\n\n return {\n setText(text: string) { rawText = text; scrollY = 0; scrollVelocity = 0; layout(); },\n resize: handleResize,\n destroy() {\n destroyed = true; cancelAnimationFrame(raf);\n canvas.removeEventListener('touchstart', onTouchStart);\n canvas.removeEventListener('touchmove', onTouchMove);\n canvas.removeEventListener('touchend', onTouchEnd);\n canvas.removeEventListener('wheel', onWheel);\n window.removeEventListener('resize', handleResize);\n canvas.remove();\n },\n get canvas() { return canvas; },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["index_exports", "__export", "createPinchMorph", "createPinchType", "createScrollMorph", "__toCommonJS", "import_pretext", "clamp", "value", "min", "max", "createCanvas", "container", "canvas", "options", "minFont", "maxFont", "fontFamily", "lhRatio", "padding", "bg", "friction", "onZoom", "fontSize", "ctx", "dpr", "W", "H", "rawText", "lines", "totalHeight", "maxScroll", "scrollY", "scrollVelocity", "touchLastY", "touchLastTime", "isTouching", "pinchActive", "pinchStartDist", "pinchStartSize", "raf", "destroyed", "layout", "maxW", "lh", "font", "paragraphs", "curY", "para", "trimmed", "prepared", "result", "li", "render", "d", "line", "screenY", "loop", "pinchDist", "e", "dx", "dy", "onTouchStart", "onTouchMove", "scale", "newSize", "y", "now", "dt", "onTouchEnd", "onWheel", "delta", "handleResize", "text", "morphRadius", "centerSize", "edgeSize", "fs", "viewCenter", "dist", "ease", "opacity", "c", "yOffset", "initialRatio", "pinchStartCenter", "pinchStartEdge", "newCenter", "newEdge"]
7
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * pinch-type
3
+ *
4
+ * Three canvas-based text effects for mobile web:
5
+ * - **Pinch Type** — pinch-to-zoom scales text size uniformly
6
+ * - **Scroll Morph** — fisheye effect: center text is large/bright, edges small/dim
7
+ * - **Combined** — both effects together
8
+ *
9
+ * @license MIT
10
+ * @author Lucas Crespo
11
+ */
12
+ export interface PinchTypeOptions {
13
+ /** Base font size. Default: `18` */
14
+ fontSize?: number;
15
+ /** Smallest font size reachable via pinch. Default: `8` */
16
+ minFontSize?: number;
17
+ /** Largest font size reachable via pinch. Default: `60` */
18
+ maxFontSize?: number;
19
+ /** CSS font-family string. Default: `"Inter", system-ui, sans-serif` */
20
+ fontFamily?: string;
21
+ /** Line-height ratio relative to font size. Default: `1.57` */
22
+ lineHeight?: number;
23
+ /** Content padding in CSS pixels. Default: `28` */
24
+ padding?: number;
25
+ /** Background color. Default: `#0a0a0a` */
26
+ background?: string;
27
+ /** Scroll friction (0–1). Higher = more momentum. Default: `0.95` */
28
+ friction?: number;
29
+ /** Called after every pinch-zoom with the new font size. */
30
+ onZoom?: (fontSize: number) => void;
31
+ }
32
+ export interface ScrollMorphOptions {
33
+ /** Font size at the viewport center. Default: `26` */
34
+ centerFontSize?: number;
35
+ /** Font size at viewport edges. Default: `11` */
36
+ edgeFontSize?: number;
37
+ /** Smallest font size. Default: `8` */
38
+ minFontSize?: number;
39
+ /** Largest font size. Default: `60` */
40
+ maxFontSize?: number;
41
+ /** CSS font-family string. Default: `"Inter", system-ui, sans-serif` */
42
+ fontFamily?: string;
43
+ /** Line-height ratio relative to font size. Default: `1.57` */
44
+ lineHeight?: number;
45
+ /** Content padding in CSS pixels. Default: `28` */
46
+ padding?: number;
47
+ /** Background color. Default: `#0a0a0a` */
48
+ background?: string;
49
+ /** Radius (px) of the morph gradient from viewport center. Default: `300` */
50
+ morphRadius?: number;
51
+ /** Scroll friction (0–1). Default: `0.95` */
52
+ friction?: number;
53
+ }
54
+ export interface PinchMorphOptions {
55
+ /** Font size at the viewport center. Default: `26` */
56
+ centerFontSize?: number;
57
+ /** Font size at viewport edges. Default: `11` */
58
+ edgeFontSize?: number;
59
+ /** Smallest font size reachable via pinch. Default: `8` */
60
+ minFontSize?: number;
61
+ /** Largest font size reachable via pinch. Default: `60` */
62
+ maxFontSize?: number;
63
+ /** CSS font-family string. Default: `"Inter", system-ui, sans-serif` */
64
+ fontFamily?: string;
65
+ /** Line-height ratio relative to font size. Default: `1.57` */
66
+ lineHeight?: number;
67
+ /** Content padding in CSS pixels. Default: `28` */
68
+ padding?: number;
69
+ /** Background color. Default: `#0a0a0a` */
70
+ background?: string;
71
+ /** Radius (px) of the morph gradient from viewport center. Default: `300` */
72
+ morphRadius?: number;
73
+ /** Scroll friction (0–1). Default: `0.95` */
74
+ friction?: number;
75
+ /** Called after every pinch-zoom with the new center and edge sizes. */
76
+ onZoom?: (centerSize: number, edgeSize: number) => void;
77
+ }
78
+ export interface EffectInstance {
79
+ /** Update the displayed text and re-layout. */
80
+ setText(text: string): void;
81
+ /** Force a resize / re-layout (called automatically on window resize). */
82
+ resize(): void;
83
+ /** Remove all listeners and the canvas element. */
84
+ destroy(): void;
85
+ /** The canvas element created by the effect. */
86
+ readonly canvas: HTMLCanvasElement;
87
+ }
88
+ /**
89
+ * Pinch-to-zoom text scaling. Text renders at a uniform size;
90
+ * pinch gestures scale all text up or down.
91
+ */
92
+ export declare function createPinchType(container: HTMLElement, options?: PinchTypeOptions): EffectInstance;
93
+ /**
94
+ * Fisheye scroll effect. Text near the viewport center is large and bright;
95
+ * text at the edges is small and dim. No pinch-to-zoom.
96
+ */
97
+ export declare function createScrollMorph(container: HTMLElement, options?: ScrollMorphOptions): EffectInstance;
98
+ /**
99
+ * Combined: scroll-morph fisheye effect + pinch-to-zoom text scaling.
100
+ * This is the original pinch-type behavior.
101
+ */
102
+ export declare function createPinchMorph(container: HTMLElement, options?: PinchMorphOptions): EffectInstance;
package/dist/index.mjs ADDED
@@ -0,0 +1,19 @@
1
+ import{prepareWithSegments as te,layoutWithLines as ne}from"@chenglou/pretext";function z(E,i,$){return Math.max(i,Math.min($,E))}function ie(E){let i=document.createElement("canvas");return i.style.display="block",i.style.width="100%",i.style.height="100%",i.style.touchAction="none",E.appendChild(i),i}function se(E,i={}){let $=i.minFontSize??8,K=i.maxFontSize??60,P=i.fontFamily??'"Inter", system-ui, -apple-system, sans-serif',U=i.lineHeight??1.57,W=i.padding??28,_=i.background??"#0a0a0a",j=i.friction??.95,N=i.onZoom,o=i.fontSize??18,n=ie(E),h=n.getContext("2d"),w=Math.min(devicePixelRatio||1,3),s=0,v=0,Y="",T=[],g=0,a=0,c=0,y=0,b=0,l=0,p=!1,D=!1,q=0,C=0,A=0,X=!1;function O(){if(!Y||s===0)return;let t=s-W*2,u=o*U,f=`400 ${o}px ${P}`,F=Y.split(`
2
+
3
+ `);T=[];let e=W+10;for(let d of F){let m=d.trim();if(!m)continue;h.font=f;let R=te(m,f),I=ne(R,t,u);for(let H=0;H<I.lines.length;H++)T.push({text:I.lines[H].text,y:e+H*u,baseSize:o,weight:400});e+=I.lines.length*u+u*.6}g=e+W,a=Math.max(0,g-v),c=z(c,0,a)}function G(){let t=w;h.fillStyle=_,h.fillRect(0,0,s*t,v*t),h.textBaseline="top",h.fillStyle="#e5e5e5",h.font=`400 ${o*t}px ${P}`;for(let u of T){let f=u.y-c;f<-100||f>v+100||h.fillText(u.text,W*t,f*t)}}function Z(){X||(p||(c+=y,y*=j,c<0?(c*=.85,y*=.5):c>a&&(c=a+(c-a)*.85,y*=.5),Math.abs(y)<.1&&(y=0)),G(),A=requestAnimationFrame(Z))}function k(t){let u=t.touches[0].clientX-t.touches[1].clientX,f=t.touches[0].clientY-t.touches[1].clientY;return Math.hypot(u,f)}function r(t){t.touches.length===2?(D=!0,q=k(t),C=o,y=0,p=!1):t.touches.length===1&&!D&&(p=!0,y=0,b=t.touches[0].clientY,l=performance.now()),t.preventDefault()}function M(t){if(D&&t.touches.length===2){let d=k(t)/q,m=z(Math.round(C*d),$,K);m!==o&&(o=m,O(),N?.(o)),t.preventDefault();return}if(!p||t.touches.length!==1)return;let u=t.touches[0].clientY,f=b-u,F=performance.now(),e=F-l;c+=f,c=z(c,-50,a+50),e>0&&(y=f/e*16),b=u,l=F,t.preventDefault()}function x(t){t.touches.length<2&&(D=!1),t.touches.length===0&&(p=!1)}function S(t){if(t.preventDefault(),t.ctrlKey||t.metaKey){let u=t.deltaY>0?-1:1,f=z(o+u,$,K);f!==o&&(o=f,O(),N?.(o))}else c+=t.deltaY,c=z(c,-50,a+50)}function L(){w=Math.min(devicePixelRatio||1,3),s=E.clientWidth,v=E.clientHeight,n.width=s*w,n.height=v*w,n.style.width=s+"px",n.style.height=v+"px",O()}return n.addEventListener("touchstart",r,{passive:!1}),n.addEventListener("touchmove",M,{passive:!1}),n.addEventListener("touchend",x),n.addEventListener("wheel",S,{passive:!1}),window.addEventListener("resize",L),L(),A=requestAnimationFrame(Z),{setText(t){Y=t,c=0,y=0,O()},resize:L,destroy(){X=!0,cancelAnimationFrame(A),n.removeEventListener("touchstart",r),n.removeEventListener("touchmove",M),n.removeEventListener("touchend",x),n.removeEventListener("wheel",S),window.removeEventListener("resize",L),n.remove()},get canvas(){return n}}}function ce(E,i={}){let $=i.fontFamily??'"Inter", system-ui, -apple-system, sans-serif',K=i.lineHeight??1.57,P=i.padding??28,U=i.background??"#0a0a0a",W=i.morphRadius??300,_=i.friction??.95,j=i.centerFontSize??26,N=i.edgeFontSize??11,o=ie(E),n=o.getContext("2d"),h=Math.min(devicePixelRatio||1,3),w=0,s=0,v="",Y=[],T=0,g=0,a=0,c=0,y=0,b=0,l=!1,p=0,D=!1;function q(){if(!v||w===0)return;let r=w-P*2,M=j,x=M*K,S=`400 ${M}px ${$}`,L=v.split(`
4
+
5
+ `);Y=[];let t=P+10;for(let u of L){let f=u.trim();if(!f)continue;n.font=S;let F=te(f,S),e=ne(F,r,x);for(let d=0;d<e.lines.length;d++)Y.push({text:e.lines[d].text,y:t+d*x,baseSize:M,weight:400});t+=e.lines.length*x+x*.6}T=t+P,g=Math.max(0,T-s),a=z(a,0,g)}function C(){let r=h;n.fillStyle=U,n.fillRect(0,0,w*r,s*r);let M=s/2;n.textBaseline="top";for(let x of Y){let S=x.y-a;if(S<-100||S>s+100)continue;let L=Math.abs(S-M),u=1-(1-Math.min(L/W,1))**3,f=j+(N-j)*u,F=1+(.25-1)*u,e=Math.round(255-153*u);n.save(),n.globalAlpha=F,n.fillStyle=`rgb(${e},${e},${e})`,n.font=`${x.weight} ${f*r}px ${$}`;let d=(f-x.baseSize)*.5;n.fillText(x.text,P*r,(S-d)*r),n.restore()}}function A(){D||(l||(a+=c,c*=_,a<0?(a*=.85,c*=.5):a>g&&(a=g+(a-g)*.85,c*=.5),Math.abs(c)<.1&&(c=0)),C(),p=requestAnimationFrame(A))}function X(r){r.touches.length===1&&(l=!0,c=0,y=r.touches[0].clientY,b=performance.now()),r.preventDefault()}function O(r){if(!l||r.touches.length!==1)return;let M=r.touches[0].clientY,x=y-M,S=performance.now(),L=S-b;a+=x,a=z(a,-50,g+50),L>0&&(c=x/L*16),y=M,b=S,r.preventDefault()}function G(r){r.touches.length===0&&(l=!1)}function Z(r){a+=r.deltaY,a=z(a,-50,g+50),r.preventDefault()}function k(){h=Math.min(devicePixelRatio||1,3),w=E.clientWidth,s=E.clientHeight,o.width=w*h,o.height=s*h,o.style.width=w+"px",o.style.height=s+"px",q()}return o.addEventListener("touchstart",X,{passive:!1}),o.addEventListener("touchmove",O,{passive:!1}),o.addEventListener("touchend",G),o.addEventListener("wheel",Z,{passive:!1}),window.addEventListener("resize",k),k(),p=requestAnimationFrame(A),{setText(r){v=r,a=0,c=0,q()},resize:k,destroy(){D=!0,cancelAnimationFrame(p),o.removeEventListener("touchstart",X),o.removeEventListener("touchmove",O),o.removeEventListener("touchend",G),o.removeEventListener("wheel",Z),window.removeEventListener("resize",k),o.remove()},get canvas(){return o}}}function re(E,i={}){let $=i.minFontSize??8,K=i.maxFontSize??60,P=i.fontFamily??'"Inter", system-ui, -apple-system, sans-serif',U=i.lineHeight??1.57,W=i.padding??28,_=i.background??"#0a0a0a",j=i.morphRadius??300,N=i.friction??.95,o=i.onZoom,n=i.centerFontSize??26,h=i.edgeFontSize??11,w=h/n,s=ie(E),v=s.getContext("2d"),Y=Math.min(devicePixelRatio||1,3),T=0,g=0,a="",c=[],y=0,b=0,l=0,p=0,D=0,q=0,C=!1,A=!1,X=0,O=0,G=0,Z=0,k=!1;function r(){if(!a||T===0)return;let e=T-W*2,d=n,m=d*U,R=`400 ${d}px ${P}`,I=a.split(`
6
+
7
+ `);c=[];let H=W+10;for(let B of I){let V=B.trim();if(!V)continue;v.font=R;let ee=te(V,R),J=ne(ee,e,m);for(let Q=0;Q<J.lines.length;Q++)c.push({text:J.lines[Q].text,y:H+Q*m,baseSize:d,weight:400});H+=J.lines.length*m+m*.6}y=H+W,b=Math.max(0,y-g),l=z(l,0,b)}function M(){let e=Y;v.fillStyle=_,v.fillRect(0,0,T*e,g*e);let d=g/2;v.textBaseline="top";for(let m of c){let R=m.y-l;if(R<-100||R>g+100)continue;let I=Math.abs(R-d),B=1-(1-Math.min(I/j,1))**3,V=n+(h-n)*B,ee=1+(.25-1)*B,J=Math.round(255-153*B);v.save(),v.globalAlpha=ee,v.fillStyle=`rgb(${J},${J},${J})`,v.font=`${m.weight} ${V*e}px ${P}`;let Q=(V-m.baseSize)*.5;v.fillText(m.text,W*e,(R-Q)*e),v.restore()}}function x(){k||(C||(l+=p,p*=N,l<0?(l*=.85,p*=.5):l>b&&(l=b+(l-b)*.85,p*=.5),Math.abs(p)<.1&&(p=0)),M(),Z=requestAnimationFrame(x))}function S(e){let d=e.touches[0].clientX-e.touches[1].clientX,m=e.touches[0].clientY-e.touches[1].clientY;return Math.hypot(d,m)}function L(e){e.touches.length===2?(A=!0,X=S(e),O=n,G=h,p=0,C=!1):e.touches.length===1&&!A&&(C=!0,p=0,D=e.touches[0].clientY,q=performance.now()),e.preventDefault()}function t(e){if(A&&e.touches.length===2){let H=S(e)/X,B=z(Math.round(O*H),$,K),V=z(Math.round(G*H),Math.max($,6),Math.round(K*w));(B!==n||V!==h)&&(n=B,h=V,r(),o?.(n,h)),e.preventDefault();return}if(!C||e.touches.length!==1)return;let d=e.touches[0].clientY,m=D-d,R=performance.now(),I=R-q;l+=m,l=z(l,-50,b+50),I>0&&(p=m/I*16),D=d,q=R,e.preventDefault()}function u(e){e.touches.length<2&&(A=!1),e.touches.length===0&&(C=!1)}function f(e){if(e.preventDefault(),e.ctrlKey||e.metaKey){let d=e.deltaY>0?-1:1,m=z(n+d,$,K);m!==n&&(n=m,h=Math.round(n*w),h=z(h,4,n),r(),o?.(n,h))}else l+=e.deltaY,l=z(l,-50,b+50)}function F(){Y=Math.min(devicePixelRatio||1,3),T=E.clientWidth,g=E.clientHeight,s.width=T*Y,s.height=g*Y,s.style.width=T+"px",s.style.height=g+"px",r()}return s.addEventListener("touchstart",L,{passive:!1}),s.addEventListener("touchmove",t,{passive:!1}),s.addEventListener("touchend",u),s.addEventListener("wheel",f,{passive:!1}),window.addEventListener("resize",F),F(),Z=requestAnimationFrame(x),{setText(e){a=e,l=0,p=0,r()},resize:F,destroy(){k=!0,cancelAnimationFrame(Z),s.removeEventListener("touchstart",L),s.removeEventListener("touchmove",t),s.removeEventListener("touchend",u),s.removeEventListener("wheel",f),window.removeEventListener("resize",F),s.remove()},get canvas(){return s}}}export{re as createPinchMorph,se as createPinchType,ce as createScrollMorph};
8
+ /**
9
+ * pinch-type
10
+ *
11
+ * Three canvas-based text effects for mobile web:
12
+ * - **Pinch Type** — pinch-to-zoom scales text size uniformly
13
+ * - **Scroll Morph** — fisheye effect: center text is large/bright, edges small/dim
14
+ * - **Combined** — both effects together
15
+ *
16
+ * @license MIT
17
+ * @author Lucas Crespo
18
+ */
19
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": ["/**\n * pinch-type\n *\n * Three canvas-based text effects for mobile web:\n * - **Pinch Type** \u2014 pinch-to-zoom scales text size uniformly\n * - **Scroll Morph** \u2014 fisheye effect: center text is large/bright, edges small/dim\n * - **Combined** \u2014 both effects together\n *\n * @license MIT\n * @author Lucas Crespo\n */\n\nimport { prepareWithSegments, layoutWithLines } from '@chenglou/pretext';\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface PinchTypeOptions {\n /** Base font size. Default: `18` */\n fontSize?: number;\n /** Smallest font size reachable via pinch. Default: `8` */\n minFontSize?: number;\n /** Largest font size reachable via pinch. Default: `60` */\n maxFontSize?: number;\n /** CSS font-family string. Default: `\"Inter\", system-ui, sans-serif` */\n fontFamily?: string;\n /** Line-height ratio relative to font size. Default: `1.57` */\n lineHeight?: number;\n /** Content padding in CSS pixels. Default: `28` */\n padding?: number;\n /** Background color. Default: `#0a0a0a` */\n background?: string;\n /** Scroll friction (0\u20131). Higher = more momentum. Default: `0.95` */\n friction?: number;\n /** Called after every pinch-zoom with the new font size. */\n onZoom?: (fontSize: number) => void;\n}\n\nexport interface ScrollMorphOptions {\n /** Font size at the viewport center. Default: `26` */\n centerFontSize?: number;\n /** Font size at viewport edges. Default: `11` */\n edgeFontSize?: number;\n /** Smallest font size. Default: `8` */\n minFontSize?: number;\n /** Largest font size. Default: `60` */\n maxFontSize?: number;\n /** CSS font-family string. Default: `\"Inter\", system-ui, sans-serif` */\n fontFamily?: string;\n /** Line-height ratio relative to font size. Default: `1.57` */\n lineHeight?: number;\n /** Content padding in CSS pixels. Default: `28` */\n padding?: number;\n /** Background color. Default: `#0a0a0a` */\n background?: string;\n /** Radius (px) of the morph gradient from viewport center. Default: `300` */\n morphRadius?: number;\n /** Scroll friction (0\u20131). Default: `0.95` */\n friction?: number;\n}\n\nexport interface PinchMorphOptions {\n /** Font size at the viewport center. Default: `26` */\n centerFontSize?: number;\n /** Font size at viewport edges. Default: `11` */\n edgeFontSize?: number;\n /** Smallest font size reachable via pinch. Default: `8` */\n minFontSize?: number;\n /** Largest font size reachable via pinch. Default: `60` */\n maxFontSize?: number;\n /** CSS font-family string. Default: `\"Inter\", system-ui, sans-serif` */\n fontFamily?: string;\n /** Line-height ratio relative to font size. Default: `1.57` */\n lineHeight?: number;\n /** Content padding in CSS pixels. Default: `28` */\n padding?: number;\n /** Background color. Default: `#0a0a0a` */\n background?: string;\n /** Radius (px) of the morph gradient from viewport center. Default: `300` */\n morphRadius?: number;\n /** Scroll friction (0\u20131). Default: `0.95` */\n friction?: number;\n /** Called after every pinch-zoom with the new center and edge sizes. */\n onZoom?: (centerSize: number, edgeSize: number) => void;\n}\n\nexport interface EffectInstance {\n /** Update the displayed text and re-layout. */\n setText(text: string): void;\n /** Force a resize / re-layout (called automatically on window resize). */\n resize(): void;\n /** Remove all listeners and the canvas element. */\n destroy(): void;\n /** The canvas element created by the effect. */\n readonly canvas: HTMLCanvasElement;\n}\n\n// \u2500\u2500\u2500 Internals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface Line {\n text: string;\n y: number;\n baseSize: number;\n weight: number;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n}\n\nfunction createCanvas(container: HTMLElement) {\n const canvas = document.createElement('canvas');\n canvas.style.display = 'block';\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.touchAction = 'none';\n container.appendChild(canvas);\n return canvas;\n}\n\n// \u2500\u2500\u2500 Pinch Type \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Pinch-to-zoom text scaling. Text renders at a uniform size;\n * pinch gestures scale all text up or down.\n */\nexport function createPinchType(\n container: HTMLElement,\n options: PinchTypeOptions = {},\n): EffectInstance {\n const minFont = options.minFontSize ?? 8;\n const maxFont = options.maxFontSize ?? 60;\n const fontFamily = options.fontFamily ?? '\"Inter\", system-ui, -apple-system, sans-serif';\n const lhRatio = options.lineHeight ?? 1.57;\n const padding = options.padding ?? 28;\n const bg = options.background ?? '#0a0a0a';\n const friction = options.friction ?? 0.95;\n const onZoom = options.onZoom;\n\n let fontSize = options.fontSize ?? 18;\n\n const canvas = createCanvas(container);\n const ctx = canvas.getContext('2d')!;\n let dpr = Math.min(devicePixelRatio || 1, 3);\n let W = 0, H = 0;\n let rawText = '';\n let lines: Line[] = [];\n let totalHeight = 0, maxScroll = 0;\n let scrollY = 0, scrollVelocity = 0;\n let touchLastY = 0, touchLastTime = 0, isTouching = false;\n let pinchActive = false, pinchStartDist = 0, pinchStartSize = 0;\n let raf = 0, destroyed = false;\n\n function layout() {\n if (!rawText || W === 0) return;\n const maxW = W - padding * 2;\n const lh = fontSize * lhRatio;\n const font = `400 ${fontSize}px ${fontFamily}`;\n const paragraphs = rawText.split('\\n\\n');\n lines = [];\n let curY = padding + 10;\n for (const para of paragraphs) {\n const trimmed = para.trim();\n if (!trimmed) continue;\n ctx.font = font;\n const prepared = prepareWithSegments(trimmed, font);\n const result = layoutWithLines(prepared, maxW, lh);\n for (let li = 0; li < result.lines.length; li++) {\n lines.push({ text: result.lines[li].text, y: curY + li * lh, baseSize: fontSize, weight: 400 });\n }\n curY += result.lines.length * lh + lh * 0.6;\n }\n totalHeight = curY + padding;\n maxScroll = Math.max(0, totalHeight - H);\n scrollY = clamp(scrollY, 0, maxScroll);\n }\n\n function render() {\n const d = dpr;\n ctx.fillStyle = bg;\n ctx.fillRect(0, 0, W * d, H * d);\n ctx.textBaseline = 'top';\n ctx.fillStyle = '#e5e5e5';\n ctx.font = `400 ${fontSize * d}px ${fontFamily}`;\n for (const line of lines) {\n const screenY = line.y - scrollY;\n if (screenY < -100 || screenY > H + 100) continue;\n ctx.fillText(line.text, padding * d, screenY * d);\n }\n }\n\n function loop() {\n if (destroyed) return;\n if (!isTouching) {\n scrollY += scrollVelocity;\n scrollVelocity *= friction;\n if (scrollY < 0) { scrollY *= 0.85; scrollVelocity *= 0.5; }\n else if (scrollY > maxScroll) { scrollY = maxScroll + (scrollY - maxScroll) * 0.85; scrollVelocity *= 0.5; }\n if (Math.abs(scrollVelocity) < 0.1) scrollVelocity = 0;\n }\n render();\n raf = requestAnimationFrame(loop);\n }\n\n function pinchDist(e: TouchEvent) {\n const dx = e.touches[0].clientX - e.touches[1].clientX;\n const dy = e.touches[0].clientY - e.touches[1].clientY;\n return Math.hypot(dx, dy);\n }\n\n function onTouchStart(e: TouchEvent) {\n if (e.touches.length === 2) {\n pinchActive = true; pinchStartDist = pinchDist(e); pinchStartSize = fontSize;\n scrollVelocity = 0; isTouching = false;\n } else if (e.touches.length === 1 && !pinchActive) {\n isTouching = true; scrollVelocity = 0;\n touchLastY = e.touches[0].clientY; touchLastTime = performance.now();\n }\n e.preventDefault();\n }\n\n function onTouchMove(e: TouchEvent) {\n if (pinchActive && e.touches.length === 2) {\n const scale = pinchDist(e) / pinchStartDist;\n const newSize = clamp(Math.round(pinchStartSize * scale), minFont, maxFont);\n if (newSize !== fontSize) { fontSize = newSize; layout(); onZoom?.(fontSize); }\n e.preventDefault(); return;\n }\n if (!isTouching || e.touches.length !== 1) return;\n const y = e.touches[0].clientY;\n const dy = touchLastY - y;\n const now = performance.now();\n const dt = now - touchLastTime;\n scrollY += dy; scrollY = clamp(scrollY, -50, maxScroll + 50);\n if (dt > 0) scrollVelocity = (dy / dt) * 16;\n touchLastY = y; touchLastTime = now; e.preventDefault();\n }\n\n function onTouchEnd(e: TouchEvent) {\n if (e.touches.length < 2) pinchActive = false;\n if (e.touches.length === 0) isTouching = false;\n }\n\n function onWheel(e: WheelEvent) {\n e.preventDefault();\n if (e.ctrlKey || e.metaKey) {\n // Trackpad pinch-to-zoom\n const delta = e.deltaY > 0 ? -1 : 1;\n const newSize = clamp(fontSize + delta, minFont, maxFont);\n if (newSize !== fontSize) { fontSize = newSize; layout(); onZoom?.(fontSize); }\n } else {\n scrollY += e.deltaY; scrollY = clamp(scrollY, -50, maxScroll + 50);\n }\n }\n\n function handleResize() {\n dpr = Math.min(devicePixelRatio || 1, 3);\n W = container.clientWidth; H = container.clientHeight;\n canvas.width = W * dpr; canvas.height = H * dpr;\n canvas.style.width = W + 'px'; canvas.style.height = H + 'px';\n layout();\n }\n\n canvas.addEventListener('touchstart', onTouchStart, { passive: false });\n canvas.addEventListener('touchmove', onTouchMove, { passive: false });\n canvas.addEventListener('touchend', onTouchEnd);\n canvas.addEventListener('wheel', onWheel, { passive: false });\n window.addEventListener('resize', handleResize);\n handleResize();\n raf = requestAnimationFrame(loop);\n\n return {\n setText(text: string) { rawText = text; scrollY = 0; scrollVelocity = 0; layout(); },\n resize: handleResize,\n destroy() {\n destroyed = true; cancelAnimationFrame(raf);\n canvas.removeEventListener('touchstart', onTouchStart);\n canvas.removeEventListener('touchmove', onTouchMove);\n canvas.removeEventListener('touchend', onTouchEnd);\n canvas.removeEventListener('wheel', onWheel);\n window.removeEventListener('resize', handleResize);\n canvas.remove();\n },\n get canvas() { return canvas; },\n };\n}\n\n// \u2500\u2500\u2500 Scroll Morph \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Fisheye scroll effect. Text near the viewport center is large and bright;\n * text at the edges is small and dim. No pinch-to-zoom.\n */\nexport function createScrollMorph(\n container: HTMLElement,\n options: ScrollMorphOptions = {},\n): EffectInstance {\n const fontFamily = options.fontFamily ?? '\"Inter\", system-ui, -apple-system, sans-serif';\n const lhRatio = options.lineHeight ?? 1.57;\n const padding = options.padding ?? 28;\n const bg = options.background ?? '#0a0a0a';\n const morphRadius = options.morphRadius ?? 300;\n const friction = options.friction ?? 0.95;\n const centerSize = options.centerFontSize ?? 26;\n const edgeSize = options.edgeFontSize ?? 11;\n\n const canvas = createCanvas(container);\n const ctx = canvas.getContext('2d')!;\n let dpr = Math.min(devicePixelRatio || 1, 3);\n let W = 0, H = 0;\n let rawText = '';\n let lines: Line[] = [];\n let totalHeight = 0, maxScroll = 0;\n let scrollY = 0, scrollVelocity = 0;\n let touchLastY = 0, touchLastTime = 0, isTouching = false;\n let raf = 0, destroyed = false;\n\n function layout() {\n if (!rawText || W === 0) return;\n const maxW = W - padding * 2;\n const fs = centerSize;\n const lh = fs * lhRatio;\n const font = `400 ${fs}px ${fontFamily}`;\n const paragraphs = rawText.split('\\n\\n');\n lines = [];\n let curY = padding + 10;\n for (const para of paragraphs) {\n const trimmed = para.trim();\n if (!trimmed) continue;\n ctx.font = font;\n const prepared = prepareWithSegments(trimmed, font);\n const result = layoutWithLines(prepared, maxW, lh);\n for (let li = 0; li < result.lines.length; li++) {\n lines.push({ text: result.lines[li].text, y: curY + li * lh, baseSize: fs, weight: 400 });\n }\n curY += result.lines.length * lh + lh * 0.6;\n }\n totalHeight = curY + padding;\n maxScroll = Math.max(0, totalHeight - H);\n scrollY = clamp(scrollY, 0, maxScroll);\n }\n\n function render() {\n const d = dpr;\n ctx.fillStyle = bg;\n ctx.fillRect(0, 0, W * d, H * d);\n const viewCenter = H / 2;\n ctx.textBaseline = 'top';\n for (const line of lines) {\n const screenY = line.y - scrollY;\n if (screenY < -100 || screenY > H + 100) continue;\n const dist = Math.abs(screenY - viewCenter);\n const t = Math.min(dist / morphRadius, 1);\n const ease = 1 - (1 - t) ** 3;\n const fontSize = centerSize + (edgeSize - centerSize) * ease;\n const opacity = 1.0 + (0.25 - 1.0) * ease;\n const c = Math.round(255 - (255 - 102) * ease);\n ctx.save();\n ctx.globalAlpha = opacity;\n ctx.fillStyle = `rgb(${c},${c},${c})`;\n ctx.font = `${line.weight} ${fontSize * d}px ${fontFamily}`;\n const yOffset = (fontSize - line.baseSize) * 0.5;\n ctx.fillText(line.text, padding * d, (screenY - yOffset) * d);\n ctx.restore();\n }\n }\n\n function loop() {\n if (destroyed) return;\n if (!isTouching) {\n scrollY += scrollVelocity; scrollVelocity *= friction;\n if (scrollY < 0) { scrollY *= 0.85; scrollVelocity *= 0.5; }\n else if (scrollY > maxScroll) { scrollY = maxScroll + (scrollY - maxScroll) * 0.85; scrollVelocity *= 0.5; }\n if (Math.abs(scrollVelocity) < 0.1) scrollVelocity = 0;\n }\n render();\n raf = requestAnimationFrame(loop);\n }\n\n function onTouchStart(e: TouchEvent) {\n if (e.touches.length === 1) {\n isTouching = true; scrollVelocity = 0;\n touchLastY = e.touches[0].clientY; touchLastTime = performance.now();\n }\n e.preventDefault();\n }\n\n function onTouchMove(e: TouchEvent) {\n if (!isTouching || e.touches.length !== 1) return;\n const y = e.touches[0].clientY;\n const dy = touchLastY - y;\n const now = performance.now();\n const dt = now - touchLastTime;\n scrollY += dy; scrollY = clamp(scrollY, -50, maxScroll + 50);\n if (dt > 0) scrollVelocity = (dy / dt) * 16;\n touchLastY = y; touchLastTime = now; e.preventDefault();\n }\n\n function onTouchEnd(e: TouchEvent) {\n if (e.touches.length === 0) isTouching = false;\n }\n\n function onWheel(e: WheelEvent) {\n scrollY += e.deltaY; scrollY = clamp(scrollY, -50, maxScroll + 50); e.preventDefault();\n }\n\n function handleResize() {\n dpr = Math.min(devicePixelRatio || 1, 3);\n W = container.clientWidth; H = container.clientHeight;\n canvas.width = W * dpr; canvas.height = H * dpr;\n canvas.style.width = W + 'px'; canvas.style.height = H + 'px';\n layout();\n }\n\n canvas.addEventListener('touchstart', onTouchStart, { passive: false });\n canvas.addEventListener('touchmove', onTouchMove, { passive: false });\n canvas.addEventListener('touchend', onTouchEnd);\n canvas.addEventListener('wheel', onWheel, { passive: false });\n window.addEventListener('resize', handleResize);\n handleResize();\n raf = requestAnimationFrame(loop);\n\n return {\n setText(text: string) { rawText = text; scrollY = 0; scrollVelocity = 0; layout(); },\n resize: handleResize,\n destroy() {\n destroyed = true; cancelAnimationFrame(raf);\n canvas.removeEventListener('touchstart', onTouchStart);\n canvas.removeEventListener('touchmove', onTouchMove);\n canvas.removeEventListener('touchend', onTouchEnd);\n canvas.removeEventListener('wheel', onWheel);\n window.removeEventListener('resize', handleResize);\n canvas.remove();\n },\n get canvas() { return canvas; },\n };\n}\n\n// \u2500\u2500\u2500 Pinch Morph (Combined) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Combined: scroll-morph fisheye effect + pinch-to-zoom text scaling.\n * This is the original pinch-type behavior.\n */\nexport function createPinchMorph(\n container: HTMLElement,\n options: PinchMorphOptions = {},\n): EffectInstance {\n const minFont = options.minFontSize ?? 8;\n const maxFont = options.maxFontSize ?? 60;\n const fontFamily = options.fontFamily ?? '\"Inter\", system-ui, -apple-system, sans-serif';\n const lhRatio = options.lineHeight ?? 1.57;\n const padding = options.padding ?? 28;\n const bg = options.background ?? '#0a0a0a';\n const morphRadius = options.morphRadius ?? 300;\n const friction = options.friction ?? 0.95;\n const onZoom = options.onZoom;\n\n let centerSize = options.centerFontSize ?? 26;\n let edgeSize = options.edgeFontSize ?? 11;\n const initialRatio = edgeSize / centerSize;\n\n const canvas = createCanvas(container);\n const ctx = canvas.getContext('2d')!;\n let dpr = Math.min(devicePixelRatio || 1, 3);\n let W = 0, H = 0;\n let rawText = '';\n let lines: Line[] = [];\n let totalHeight = 0, maxScroll = 0;\n let scrollY = 0, scrollVelocity = 0;\n let touchLastY = 0, touchLastTime = 0, isTouching = false;\n let pinchActive = false, pinchStartDist = 0, pinchStartCenter = 0, pinchStartEdge = 0;\n let raf = 0, destroyed = false;\n\n function layout() {\n if (!rawText || W === 0) return;\n const maxW = W - padding * 2;\n const fs = centerSize;\n const lh = fs * lhRatio;\n const font = `400 ${fs}px ${fontFamily}`;\n const paragraphs = rawText.split('\\n\\n');\n lines = [];\n let curY = padding + 10;\n for (const para of paragraphs) {\n const trimmed = para.trim();\n if (!trimmed) continue;\n ctx.font = font;\n const prepared = prepareWithSegments(trimmed, font);\n const result = layoutWithLines(prepared, maxW, lh);\n for (let li = 0; li < result.lines.length; li++) {\n lines.push({ text: result.lines[li].text, y: curY + li * lh, baseSize: fs, weight: 400 });\n }\n curY += result.lines.length * lh + lh * 0.6;\n }\n totalHeight = curY + padding;\n maxScroll = Math.max(0, totalHeight - H);\n scrollY = clamp(scrollY, 0, maxScroll);\n }\n\n function render() {\n const d = dpr;\n ctx.fillStyle = bg;\n ctx.fillRect(0, 0, W * d, H * d);\n const viewCenter = H / 2;\n ctx.textBaseline = 'top';\n for (const line of lines) {\n const screenY = line.y - scrollY;\n if (screenY < -100 || screenY > H + 100) continue;\n const dist = Math.abs(screenY - viewCenter);\n const t = Math.min(dist / morphRadius, 1);\n const ease = 1 - (1 - t) ** 3;\n const fontSize = centerSize + (edgeSize - centerSize) * ease;\n const opacity = 1.0 + (0.25 - 1.0) * ease;\n const c = Math.round(255 - (255 - 102) * ease);\n ctx.save();\n ctx.globalAlpha = opacity;\n ctx.fillStyle = `rgb(${c},${c},${c})`;\n ctx.font = `${line.weight} ${fontSize * d}px ${fontFamily}`;\n const yOffset = (fontSize - line.baseSize) * 0.5;\n ctx.fillText(line.text, padding * d, (screenY - yOffset) * d);\n ctx.restore();\n }\n }\n\n function loop() {\n if (destroyed) return;\n if (!isTouching) {\n scrollY += scrollVelocity; scrollVelocity *= friction;\n if (scrollY < 0) { scrollY *= 0.85; scrollVelocity *= 0.5; }\n else if (scrollY > maxScroll) { scrollY = maxScroll + (scrollY - maxScroll) * 0.85; scrollVelocity *= 0.5; }\n if (Math.abs(scrollVelocity) < 0.1) scrollVelocity = 0;\n }\n render();\n raf = requestAnimationFrame(loop);\n }\n\n function pinchDist(e: TouchEvent) {\n const dx = e.touches[0].clientX - e.touches[1].clientX;\n const dy = e.touches[0].clientY - e.touches[1].clientY;\n return Math.hypot(dx, dy);\n }\n\n function onTouchStart(e: TouchEvent) {\n if (e.touches.length === 2) {\n pinchActive = true; pinchStartDist = pinchDist(e);\n pinchStartCenter = centerSize; pinchStartEdge = edgeSize;\n scrollVelocity = 0; isTouching = false;\n } else if (e.touches.length === 1 && !pinchActive) {\n isTouching = true; scrollVelocity = 0;\n touchLastY = e.touches[0].clientY; touchLastTime = performance.now();\n }\n e.preventDefault();\n }\n\n function onTouchMove(e: TouchEvent) {\n if (pinchActive && e.touches.length === 2) {\n const scale = pinchDist(e) / pinchStartDist;\n const newCenter = clamp(Math.round(pinchStartCenter * scale), minFont, maxFont);\n const newEdge = clamp(Math.round(pinchStartEdge * scale), Math.max(minFont, 6), Math.round(maxFont * initialRatio));\n if (newCenter !== centerSize || newEdge !== edgeSize) {\n centerSize = newCenter; edgeSize = newEdge; layout(); onZoom?.(centerSize, edgeSize);\n }\n e.preventDefault(); return;\n }\n if (!isTouching || e.touches.length !== 1) return;\n const y = e.touches[0].clientY;\n const dy = touchLastY - y;\n const now = performance.now();\n const dt = now - touchLastTime;\n scrollY += dy; scrollY = clamp(scrollY, -50, maxScroll + 50);\n if (dt > 0) scrollVelocity = (dy / dt) * 16;\n touchLastY = y; touchLastTime = now; e.preventDefault();\n }\n\n function onTouchEnd(e: TouchEvent) {\n if (e.touches.length < 2) pinchActive = false;\n if (e.touches.length === 0) isTouching = false;\n }\n\n function onWheel(e: WheelEvent) {\n e.preventDefault();\n if (e.ctrlKey || e.metaKey) {\n // Trackpad pinch-to-zoom\n const delta = e.deltaY > 0 ? -1 : 1;\n const newCenter = clamp(centerSize + delta, minFont, maxFont);\n if (newCenter !== centerSize) {\n centerSize = newCenter;\n edgeSize = Math.round(centerSize * initialRatio);\n edgeSize = clamp(edgeSize, 4, centerSize);\n layout();\n onZoom?.(centerSize, edgeSize);\n }\n } else {\n scrollY += e.deltaY; scrollY = clamp(scrollY, -50, maxScroll + 50);\n }\n }\n\n function handleResize() {\n dpr = Math.min(devicePixelRatio || 1, 3);\n W = container.clientWidth; H = container.clientHeight;\n canvas.width = W * dpr; canvas.height = H * dpr;\n canvas.style.width = W + 'px'; canvas.style.height = H + 'px';\n layout();\n }\n\n canvas.addEventListener('touchstart', onTouchStart, { passive: false });\n canvas.addEventListener('touchmove', onTouchMove, { passive: false });\n canvas.addEventListener('touchend', onTouchEnd);\n canvas.addEventListener('wheel', onWheel, { passive: false });\n window.addEventListener('resize', handleResize);\n handleResize();\n raf = requestAnimationFrame(loop);\n\n return {\n setText(text: string) { rawText = text; scrollY = 0; scrollVelocity = 0; layout(); },\n resize: handleResize,\n destroy() {\n destroyed = true; cancelAnimationFrame(raf);\n canvas.removeEventListener('touchstart', onTouchStart);\n canvas.removeEventListener('touchmove', onTouchMove);\n canvas.removeEventListener('touchend', onTouchEnd);\n canvas.removeEventListener('wheel', onWheel);\n window.removeEventListener('resize', handleResize);\n canvas.remove();\n },\n get canvas() { return canvas; },\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["prepareWithSegments", "layoutWithLines", "clamp", "value", "min", "max", "createCanvas", "container", "canvas", "createPinchType", "options", "minFont", "maxFont", "fontFamily", "lhRatio", "padding", "bg", "friction", "onZoom", "fontSize", "ctx", "dpr", "W", "H", "rawText", "lines", "totalHeight", "maxScroll", "scrollY", "scrollVelocity", "touchLastY", "touchLastTime", "isTouching", "pinchActive", "pinchStartDist", "pinchStartSize", "raf", "destroyed", "layout", "maxW", "lh", "font", "paragraphs", "curY", "para", "trimmed", "prepared", "result", "li", "render", "d", "line", "screenY", "loop", "pinchDist", "e", "dx", "dy", "onTouchStart", "onTouchMove", "scale", "newSize", "y", "now", "dt", "onTouchEnd", "onWheel", "delta", "handleResize", "text", "createScrollMorph", "morphRadius", "centerSize", "edgeSize", "fs", "viewCenter", "dist", "ease", "opacity", "c", "yOffset", "createPinchMorph", "initialRatio", "pinchStartCenter", "pinchStartEdge", "newCenter", "newEdge"]
7
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "pinch-type",
3
+ "version": "0.1.0",
4
+ "description": "Pinch to zoom text, not the page. Canvas-based text scaling for mobile web.",
5
+ "author": "Lucas Crespo",
6
+ "license": "MIT",
7
+ "main": "dist/index.cjs",
8
+ "module": "dist/index.mjs",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "sideEffects": false,
23
+ "keywords": [
24
+ "pinch-to-zoom",
25
+ "text-scaling",
26
+ "typography",
27
+ "mobile",
28
+ "canvas",
29
+ "touch",
30
+ "zoom",
31
+ "pretext"
32
+ ],
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/lucascrespo/pinch-type"
36
+ },
37
+ "homepage": "https://github.com/lucascrespo/pinch-type#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/lucascrespo/pinch-type/issues"
40
+ },
41
+ "peerDependencies": {
42
+ "@chenglou/pretext": ">=0.0.2"
43
+ },
44
+ "devDependencies": {
45
+ "@chenglou/pretext": "^0.0.2",
46
+ "esbuild": "^0.27.4",
47
+ "typescript": "^5.7.0"
48
+ },
49
+ "scripts": {
50
+ "build": "node scripts/build.mjs",
51
+ "dev": "node scripts/dev.mjs",
52
+ "typecheck": "tsc --noEmit",
53
+ "prepublishOnly": "npm run build"
54
+ }
55
+ }