react-marketing-ui 0.0.2 → 0.0.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/dist/accordion.js +1 -1
- package/dist/accordion.js.map +1 -1
- package/dist/accordion.mjs +222 -2
- package/dist/accordion.mjs.map +1 -1
- package/dist/action-swap.js +1 -0
- package/dist/action-swap.mjs +77 -3
- package/dist/action-swap.mjs.map +1 -1
- package/dist/animated-beam.js +1 -0
- package/dist/animated-beam.mjs +127 -2
- package/dist/animated-beam.mjs.map +1 -1
- package/dist/button.js +1 -1
- package/dist/button.js.map +1 -1
- package/dist/button.mjs +115 -2
- package/dist/button.mjs.map +1 -1
- package/dist/card.js +1 -2
- package/dist/card.js.map +1 -1
- package/dist/card.mjs +135 -3
- package/dist/card.mjs.map +1 -1
- package/dist/dia-text-reveal.js +1 -0
- package/dist/dia-text-reveal.mjs +331 -2
- package/dist/dia-text-reveal.mjs.map +1 -1
- package/dist/elevated-input.js +1 -1
- package/dist/elevated-input.js.map +1 -1
- package/dist/elevated-input.mjs +48 -2
- package/dist/elevated-input.mjs.map +1 -1
- package/dist/fade-in.js +1 -0
- package/dist/fade-in.mjs +84 -2
- package/dist/fade-in.mjs.map +1 -1
- package/dist/feature-card.js +1 -1
- package/dist/feature-card.js.map +1 -1
- package/dist/feature-card.mjs +34 -2
- package/dist/feature-card.mjs.map +1 -1
- package/dist/flowchart-parts.js +1 -1
- package/dist/flowchart-parts.js.map +1 -1
- package/dist/flowchart-parts.mjs +91 -2
- package/dist/flowchart-parts.mjs.map +1 -1
- package/dist/glossy-button.js +1 -1
- package/dist/glossy-button.js.map +1 -1
- package/dist/glossy-button.mjs +82 -2
- package/dist/glossy-button.mjs.map +1 -1
- package/dist/grainient.js +1 -0
- package/dist/grainient.mjs +327 -2
- package/dist/grainient.mjs.map +1 -1
- package/dist/highlighter.js +1 -0
- package/dist/highlighter.mjs +122 -3
- package/dist/highlighter.mjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2642 -32
- package/dist/index.mjs.map +1 -1
- package/dist/input.js +1 -0
- package/dist/input.mjs +107 -2
- package/dist/input.mjs.map +1 -1
- package/dist/lib/breakpoints.js +1 -1
- package/dist/lib/breakpoints.js.map +1 -1
- package/dist/lib/breakpoints.mjs +34 -1
- package/dist/lib/breakpoints.mjs.map +1 -1
- package/dist/lib/cn.js +1 -1
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/cn.mjs +17 -1
- package/dist/lib/cn.mjs.map +1 -1
- package/dist/lib/ease.js +1 -0
- package/dist/lib/ease.mjs +9 -1
- package/dist/lib/ease.mjs.map +1 -1
- package/dist/lib/surface.js +1 -0
- package/dist/lib/surface.mjs +37 -1
- package/dist/lib/surface.mjs.map +1 -1
- package/dist/lib/use-media-query.js +1 -0
- package/dist/lib/use-media-query.mjs +16 -1
- package/dist/lib/use-media-query.mjs.map +1 -1
- package/dist/liquid-metal.js +1 -0
- package/dist/liquid-metal.mjs +40 -2
- package/dist/liquid-metal.mjs.map +1 -1
- package/dist/navigation-menu-icons.js +1 -1
- package/dist/navigation-menu-icons.js.map +1 -1
- package/dist/navigation-menu-icons.mjs +36 -1
- package/dist/navigation-menu-icons.mjs.map +1 -1
- package/dist/navigation-menu-styles.js +1 -1
- package/dist/navigation-menu-styles.js.map +1 -1
- package/dist/navigation-menu-styles.mjs +11 -1
- package/dist/navigation-menu-styles.mjs.map +1 -1
- package/dist/navigation-menu.js +1 -2
- package/dist/navigation-menu.js.map +1 -1
- package/dist/navigation-menu.mjs +350 -5
- package/dist/navigation-menu.mjs.map +1 -1
- package/dist/password-input.js +1 -0
- package/dist/password-input.mjs +171 -3
- package/dist/password-input.mjs.map +1 -1
- package/dist/rainbow-button.js +1 -0
- package/dist/rainbow-button.mjs +45 -2
- package/dist/rainbow-button.mjs.map +1 -1
- package/dist/range-slider.js +1 -0
- package/dist/range-slider.mjs +82 -1
- package/dist/range-slider.mjs.map +1 -1
- package/dist/rolling-badge.js +1 -0
- package/dist/rolling-badge.mjs +68 -2
- package/dist/rolling-badge.mjs.map +1 -1
- package/dist/savings-badge.js +1 -1
- package/dist/savings-badge.js.map +1 -1
- package/dist/savings-badge.mjs +22 -2
- package/dist/savings-badge.mjs.map +1 -1
- package/dist/shine-border.js +1 -0
- package/dist/shine-border.mjs +44 -2
- package/dist/shine-border.mjs.map +1 -1
- package/dist/sparkles-text.js +1 -0
- package/dist/sparkles-text.mjs +84 -3
- package/dist/sparkles-text.mjs.map +1 -1
- package/dist/tabs.js +1 -0
- package/dist/tabs.mjs +118 -2
- package/dist/tabs.mjs.map +1 -1
- package/dist/theme-toggle.js +1 -0
- package/dist/theme-toggle.mjs +248 -5
- package/dist/theme-toggle.mjs.map +1 -1
- package/package.json +68 -68
- package/dist/chunk-3SB6HUBA.mjs +0 -31
- package/dist/chunk-3SB6HUBA.mjs.map +0 -1
- package/dist/chunk-4PBVBXGY.mjs +0 -84
- package/dist/chunk-4PBVBXGY.mjs.map +0 -1
- package/dist/chunk-4VCFWVSJ.mjs +0 -42
- package/dist/chunk-4VCFWVSJ.mjs.map +0 -1
- package/dist/chunk-5QQAPGR2.mjs +0 -109
- package/dist/chunk-5QQAPGR2.mjs.map +0 -1
- package/dist/chunk-6MB4UKRX.mjs +0 -19
- package/dist/chunk-6MB4UKRX.mjs.map +0 -1
- package/dist/chunk-6T5LR34O.mjs +0 -45
- package/dist/chunk-6T5LR34O.mjs.map +0 -1
- package/dist/chunk-AYLCYDUM.mjs +0 -275
- package/dist/chunk-AYLCYDUM.mjs.map +0 -1
- package/dist/chunk-AYY7D2RJ.mjs +0 -112
- package/dist/chunk-AYY7D2RJ.mjs.map +0 -1
- package/dist/chunk-CTZEI2YS.mjs +0 -324
- package/dist/chunk-CTZEI2YS.mjs.map +0 -1
- package/dist/chunk-DA3I3Y5O.mjs +0 -71
- package/dist/chunk-DA3I3Y5O.mjs.map +0 -1
- package/dist/chunk-EYHT2EWJ.mjs +0 -18
- package/dist/chunk-EYHT2EWJ.mjs.map +0 -1
- package/dist/chunk-IHLGCS63.mjs +0 -36
- package/dist/chunk-IHLGCS63.mjs.map +0 -1
- package/dist/chunk-JVXJ6SFX.mjs +0 -38
- package/dist/chunk-JVXJ6SFX.mjs.map +0 -1
- package/dist/chunk-KBHYXLRG.mjs +0 -13
- package/dist/chunk-KBHYXLRG.mjs.map +0 -1
- package/dist/chunk-KMXUQYR3.mjs +0 -74
- package/dist/chunk-KMXUQYR3.mjs.map +0 -1
- package/dist/chunk-KNPOZ34H.mjs +0 -88
- package/dist/chunk-KNPOZ34H.mjs.map +0 -1
- package/dist/chunk-KRNWCJ4D.mjs +0 -219
- package/dist/chunk-KRNWCJ4D.mjs.map +0 -1
- package/dist/chunk-M5MESV26.mjs +0 -124
- package/dist/chunk-M5MESV26.mjs.map +0 -1
- package/dist/chunk-MJYBPJBA.mjs +0 -115
- package/dist/chunk-MJYBPJBA.mjs.map +0 -1
- package/dist/chunk-NNHPQ67J.mjs +0 -10
- package/dist/chunk-NNHPQ67J.mjs.map +0 -1
- package/dist/chunk-OCZFHVVD.mjs +0 -71
- package/dist/chunk-OCZFHVVD.mjs.map +0 -1
- package/dist/chunk-QAWSH567.mjs +0 -19
- package/dist/chunk-QAWSH567.mjs.map +0 -1
- package/dist/chunk-R2E2J42H.mjs +0 -38
- package/dist/chunk-R2E2J42H.mjs.map +0 -1
- package/dist/chunk-TPGLLSYV.mjs +0 -65
- package/dist/chunk-TPGLLSYV.mjs.map +0 -1
- package/dist/chunk-VJENJHXJ.mjs +0 -72
- package/dist/chunk-VJENJHXJ.mjs.map +0 -1
- package/dist/chunk-W22G7XHR.mjs +0 -104
- package/dist/chunk-W22G7XHR.mjs.map +0 -1
- package/dist/chunk-WDR5VPZJ.mjs +0 -79
- package/dist/chunk-WDR5VPZJ.mjs.map +0 -1
- package/dist/chunk-WLUGKBH7.mjs +0 -37
- package/dist/chunk-WLUGKBH7.mjs.map +0 -1
- package/dist/chunk-WYJYLDUA.mjs +0 -84
- package/dist/chunk-WYJYLDUA.mjs.map +0 -1
- package/dist/chunk-XZEDZVXT.mjs +0 -38
- package/dist/chunk-XZEDZVXT.mjs.map +0 -1
- package/dist/chunk-YKGP5JIL.mjs +0 -328
- package/dist/chunk-YKGP5JIL.mjs.map +0 -1
- package/dist/chunk-YM3U2OBE.mjs +0 -99
- package/dist/chunk-YM3U2OBE.mjs.map +0 -1
package/dist/grainient.mjs
CHANGED
|
@@ -1,4 +1,329 @@
|
|
|
1
|
-
|
|
2
|
-
import '
|
|
1
|
+
"use client";
|
|
2
|
+
import { Renderer, Triangle, Mesh, Program } from 'ogl';
|
|
3
|
+
import { useRef, useEffect } from 'react';
|
|
4
|
+
import { clsx } from 'clsx';
|
|
5
|
+
import { twMerge } from 'tailwind-merge';
|
|
6
|
+
import { jsx } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
function cn(...inputs) {
|
|
9
|
+
return twMerge(clsx(inputs));
|
|
10
|
+
}
|
|
11
|
+
var vertex = `#version 300 es
|
|
12
|
+
in vec2 position;
|
|
13
|
+
void main() {
|
|
14
|
+
gl_Position = vec4(position, 0.0, 1.0);
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
var fragment = `#version 300 es
|
|
18
|
+
precision highp float;
|
|
19
|
+
uniform vec2 iResolution;
|
|
20
|
+
uniform float iTime;
|
|
21
|
+
uniform float uTimeSpeed;
|
|
22
|
+
uniform float uColorBalance;
|
|
23
|
+
uniform float uWarpStrength;
|
|
24
|
+
uniform float uWarpFrequency;
|
|
25
|
+
uniform float uWarpSpeed;
|
|
26
|
+
uniform float uWarpAmplitude;
|
|
27
|
+
uniform float uBlendAngle;
|
|
28
|
+
uniform float uBlendSoftness;
|
|
29
|
+
uniform float uRotationAmount;
|
|
30
|
+
uniform float uNoiseScale;
|
|
31
|
+
uniform float uGrainAmount;
|
|
32
|
+
uniform float uGrainScale;
|
|
33
|
+
uniform float uGrainAnimated;
|
|
34
|
+
uniform float uContrast;
|
|
35
|
+
uniform float uGamma;
|
|
36
|
+
uniform float uSaturation;
|
|
37
|
+
uniform vec2 uCenterOffset;
|
|
38
|
+
uniform float uZoom;
|
|
39
|
+
uniform vec3 uColor1;
|
|
40
|
+
uniform vec3 uColor2;
|
|
41
|
+
uniform vec3 uColor3;
|
|
42
|
+
out vec4 fragColor;
|
|
43
|
+
#define S(a,b,t) smoothstep(a,b,t)
|
|
44
|
+
mat2 Rot(float a){float s=sin(a),c=cos(a);return mat2(c,-s,s,c);}
|
|
45
|
+
vec2 hash(vec2 p){p=vec2(dot(p,vec2(2127.1,81.17)),dot(p,vec2(1269.5,283.37)));return fract(sin(p)*43758.5453);}
|
|
46
|
+
float noise(vec2 p){vec2 i=floor(p),f=fract(p),u=f*f*(3.0-2.0*f);float n=mix(mix(dot(-1.0+2.0*hash(i+vec2(0.0,0.0)),f-vec2(0.0,0.0)),dot(-1.0+2.0*hash(i+vec2(1.0,0.0)),f-vec2(1.0,0.0)),u.x),mix(dot(-1.0+2.0*hash(i+vec2(0.0,1.0)),f-vec2(0.0,1.0)),dot(-1.0+2.0*hash(i+vec2(1.0,1.0)),f-vec2(1.0,1.0)),u.x),u.y);return 0.5+0.5*n;}
|
|
47
|
+
void mainImage(out vec4 o, vec2 C){
|
|
48
|
+
float t=iTime*uTimeSpeed;
|
|
49
|
+
vec2 uv=C/iResolution.xy;
|
|
50
|
+
float ratio=iResolution.x/iResolution.y;
|
|
51
|
+
vec2 tuv=uv-0.5+uCenterOffset;
|
|
52
|
+
tuv/=max(uZoom,0.001);
|
|
53
|
+
float degree=noise(vec2(t*0.1,tuv.x*tuv.y)*uNoiseScale);
|
|
54
|
+
tuv.y*=1.0/ratio;
|
|
55
|
+
tuv*=Rot(radians((degree-0.5)*uRotationAmount+180.0));
|
|
56
|
+
tuv.y*=ratio;
|
|
57
|
+
float frequency=uWarpFrequency;
|
|
58
|
+
float ws=max(uWarpStrength,0.001);
|
|
59
|
+
float amplitude=uWarpAmplitude/ws;
|
|
60
|
+
float warpTime=t*uWarpSpeed;
|
|
61
|
+
tuv.x+=sin(tuv.y*frequency+warpTime)/amplitude;
|
|
62
|
+
tuv.y+=sin(tuv.x*(frequency*1.5)+warpTime)/(amplitude*0.5);
|
|
63
|
+
vec3 colLav=uColor1;
|
|
64
|
+
vec3 colOrg=uColor2;
|
|
65
|
+
vec3 colDark=uColor3;
|
|
66
|
+
float b=uColorBalance;
|
|
67
|
+
float s=max(uBlendSoftness,0.0);
|
|
68
|
+
mat2 blendRot=Rot(radians(uBlendAngle));
|
|
69
|
+
float blendX=(tuv*blendRot).x;
|
|
70
|
+
float edge0=-0.3-b-s;
|
|
71
|
+
float edge1=0.2-b+s;
|
|
72
|
+
float v0=0.5-b+s;
|
|
73
|
+
float v1=-0.3-b-s;
|
|
74
|
+
vec3 layer1=mix(colDark,colOrg,S(edge0,edge1,blendX));
|
|
75
|
+
vec3 layer2=mix(colOrg,colLav,S(edge0,edge1,blendX));
|
|
76
|
+
vec3 col=mix(layer1,layer2,S(v0,v1,tuv.y));
|
|
77
|
+
vec2 grainUv=uv*max(uGrainScale,0.001);
|
|
78
|
+
if(uGrainAnimated>0.5){grainUv+=vec2(iTime*0.05);}
|
|
79
|
+
float grain=fract(sin(dot(grainUv,vec2(12.9898,78.233)))*43758.5453);
|
|
80
|
+
col+=(grain-0.5)*uGrainAmount;
|
|
81
|
+
col=(col-0.5)*uContrast+0.5;
|
|
82
|
+
float luma=dot(col,vec3(0.2126,0.7152,0.0722));
|
|
83
|
+
col=mix(vec3(luma),col,uSaturation);
|
|
84
|
+
col=pow(max(col,0.0),vec3(1.0/max(uGamma,0.001)));
|
|
85
|
+
col=clamp(col,0.0,1.0);
|
|
86
|
+
o=vec4(col,1.0);
|
|
87
|
+
}
|
|
88
|
+
void main(){
|
|
89
|
+
vec4 o=vec4(0.0);
|
|
90
|
+
mainImage(o,gl_FragCoord.xy);
|
|
91
|
+
fragColor=o;
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
function createGrainientProgram(gl) {
|
|
95
|
+
return new Program(gl, {
|
|
96
|
+
vertex,
|
|
97
|
+
fragment,
|
|
98
|
+
uniforms: {
|
|
99
|
+
iTime: { value: 0 },
|
|
100
|
+
iResolution: { value: new Float32Array([1, 1]) },
|
|
101
|
+
uTimeSpeed: { value: 0.25 },
|
|
102
|
+
uColorBalance: { value: 0 },
|
|
103
|
+
uWarpStrength: { value: 1 },
|
|
104
|
+
uWarpFrequency: { value: 5 },
|
|
105
|
+
uWarpSpeed: { value: 2 },
|
|
106
|
+
uWarpAmplitude: { value: 50 },
|
|
107
|
+
uBlendAngle: { value: 0 },
|
|
108
|
+
uBlendSoftness: { value: 0.05 },
|
|
109
|
+
uRotationAmount: { value: 500 },
|
|
110
|
+
uNoiseScale: { value: 2 },
|
|
111
|
+
uGrainAmount: { value: 0.1 },
|
|
112
|
+
uGrainScale: { value: 2 },
|
|
113
|
+
uGrainAnimated: { value: 0 },
|
|
114
|
+
uContrast: { value: 1.5 },
|
|
115
|
+
uGamma: { value: 1 },
|
|
116
|
+
uSaturation: { value: 1 },
|
|
117
|
+
uCenterOffset: { value: new Float32Array([0, 0]) },
|
|
118
|
+
uZoom: { value: 0.9 },
|
|
119
|
+
uColor1: { value: new Float32Array([1, 1, 1]) },
|
|
120
|
+
uColor2: { value: new Float32Array([1, 1, 1]) },
|
|
121
|
+
uColor3: { value: new Float32Array([1, 1, 1]) }
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
var ctxMap = /* @__PURE__ */ new WeakMap();
|
|
126
|
+
var HEX_COLOR_RE = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;
|
|
127
|
+
var hexToRgb = (hex) => {
|
|
128
|
+
const result = HEX_COLOR_RE.exec(hex);
|
|
129
|
+
if (!result) return [1, 1, 1];
|
|
130
|
+
const [, r = "ff", g = "ff", b = "ff"] = result;
|
|
131
|
+
return [Number.parseInt(r, 16) / 255, Number.parseInt(g, 16) / 255, Number.parseInt(b, 16) / 255];
|
|
132
|
+
};
|
|
133
|
+
var prefersReducedMotion = () => typeof globalThis !== "undefined" && globalThis.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
|
|
134
|
+
var isLowPowerClient = () => {
|
|
135
|
+
if (typeof globalThis === "undefined") return false;
|
|
136
|
+
const narrow = globalThis.matchMedia?.("(max-width: 1023px)").matches === true;
|
|
137
|
+
const fewCores = typeof navigator !== "undefined" && typeof navigator.hardwareConcurrency === "number" && navigator.hardwareConcurrency <= 4;
|
|
138
|
+
return narrow || fewCores;
|
|
139
|
+
};
|
|
140
|
+
var GRAIN_LAYER = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.12'/%3E%3C/svg%3E")`;
|
|
141
|
+
var fallbackGradient = (c1, c2, c3) => `${GRAIN_LAYER}, linear-gradient(135deg, ${c1} 0%, ${c2} 55%, ${c3} 100%)`;
|
|
142
|
+
var applyStaticBg = (el, src) => {
|
|
143
|
+
el.style.backgroundImage = `url(${src})`;
|
|
144
|
+
el.style.backgroundSize = "cover";
|
|
145
|
+
el.style.backgroundPosition = "center";
|
|
146
|
+
};
|
|
147
|
+
function initGrainient(container) {
|
|
148
|
+
const renderer = new Renderer({
|
|
149
|
+
webgl: 2,
|
|
150
|
+
alpha: true,
|
|
151
|
+
antialias: false,
|
|
152
|
+
dpr: Math.min(globalThis.devicePixelRatio || 1, 2)
|
|
153
|
+
});
|
|
154
|
+
const gl = renderer.gl;
|
|
155
|
+
const canvas = gl.canvas;
|
|
156
|
+
canvas.style.width = "100%";
|
|
157
|
+
canvas.style.height = "100%";
|
|
158
|
+
canvas.style.display = "block";
|
|
159
|
+
container.appendChild(canvas);
|
|
160
|
+
const geometry = new Triangle(gl);
|
|
161
|
+
const program = createGrainientProgram(gl);
|
|
162
|
+
const mesh = new Mesh(gl, { geometry, program });
|
|
163
|
+
ctxMap.set(container, { renderer, program, mesh });
|
|
164
|
+
const setSize = () => {
|
|
165
|
+
const rect = container.getBoundingClientRect();
|
|
166
|
+
renderer.setSize(Math.max(1, Math.floor(rect.width)), Math.max(1, Math.floor(rect.height)));
|
|
167
|
+
const res = program.uniforms.iResolution.value;
|
|
168
|
+
res[0] = gl.drawingBufferWidth;
|
|
169
|
+
res[1] = gl.drawingBufferHeight;
|
|
170
|
+
renderer.render({ scene: mesh });
|
|
171
|
+
};
|
|
172
|
+
const ro = new ResizeObserver(setSize);
|
|
173
|
+
ro.observe(container);
|
|
174
|
+
setSize();
|
|
175
|
+
let raf = 0;
|
|
176
|
+
let isVisible = true;
|
|
177
|
+
let isPageVisible = !document.hidden;
|
|
178
|
+
const t0 = performance.now();
|
|
179
|
+
const loop = (t) => {
|
|
180
|
+
program.uniforms.iTime.value = (t - t0) * 1e-3;
|
|
181
|
+
renderer.render({ scene: mesh });
|
|
182
|
+
raf = requestAnimationFrame(loop);
|
|
183
|
+
};
|
|
184
|
+
const tryStart = () => {
|
|
185
|
+
if (isVisible && isPageVisible && raf === 0) raf = requestAnimationFrame(loop);
|
|
186
|
+
};
|
|
187
|
+
const tryStop = () => {
|
|
188
|
+
if (raf !== 0) {
|
|
189
|
+
cancelAnimationFrame(raf);
|
|
190
|
+
raf = 0;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const reduce = prefersReducedMotion();
|
|
194
|
+
const io = new IntersectionObserver(
|
|
195
|
+
([entry]) => {
|
|
196
|
+
if (!entry) return;
|
|
197
|
+
isVisible = entry.isIntersecting;
|
|
198
|
+
if (isVisible) tryStart();
|
|
199
|
+
else tryStop();
|
|
200
|
+
},
|
|
201
|
+
{ threshold: 0 }
|
|
202
|
+
);
|
|
203
|
+
if (!reduce) io.observe(container);
|
|
204
|
+
const onVisibility = () => {
|
|
205
|
+
isPageVisible = !document.hidden;
|
|
206
|
+
if (isPageVisible) tryStart();
|
|
207
|
+
else tryStop();
|
|
208
|
+
};
|
|
209
|
+
if (!reduce) document.addEventListener("visibilitychange", onVisibility);
|
|
210
|
+
if (!reduce) tryStart();
|
|
211
|
+
return () => {
|
|
212
|
+
tryStop();
|
|
213
|
+
ro.disconnect();
|
|
214
|
+
io.disconnect();
|
|
215
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
216
|
+
ctxMap.delete(container);
|
|
217
|
+
try {
|
|
218
|
+
container.removeChild(canvas);
|
|
219
|
+
} catch {
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
var Grainient = (props) => {
|
|
224
|
+
const {
|
|
225
|
+
timeSpeed = 0.25,
|
|
226
|
+
colorBalance = 0,
|
|
227
|
+
warpStrength = 1,
|
|
228
|
+
warpFrequency = 5,
|
|
229
|
+
warpSpeed = 2,
|
|
230
|
+
warpAmplitude = 50,
|
|
231
|
+
blendAngle = 0,
|
|
232
|
+
blendSoftness = 0.05,
|
|
233
|
+
rotationAmount = 500,
|
|
234
|
+
noiseScale = 2,
|
|
235
|
+
grainAmount = 0.1,
|
|
236
|
+
grainScale = 2,
|
|
237
|
+
grainAnimated = false,
|
|
238
|
+
contrast = 1.5,
|
|
239
|
+
gamma = 1,
|
|
240
|
+
saturation = 1,
|
|
241
|
+
centerX = 0,
|
|
242
|
+
centerY = 0,
|
|
243
|
+
zoom = 0.9,
|
|
244
|
+
color1 = "#FF9FFC",
|
|
245
|
+
color2 = "#5227FF",
|
|
246
|
+
color3 = "#B497CF",
|
|
247
|
+
staticSrc = "",
|
|
248
|
+
className = ""
|
|
249
|
+
} = props;
|
|
250
|
+
const containerRef = useRef(null);
|
|
251
|
+
const fallbackRef = useRef(false);
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
const container = containerRef.current;
|
|
254
|
+
if (!container) return;
|
|
255
|
+
if (isLowPowerClient() || prefersReducedMotion()) {
|
|
256
|
+
fallbackRef.current = true;
|
|
257
|
+
container.dataset.fallback = "true";
|
|
258
|
+
if (staticSrc) applyStaticBg(container, staticSrc);
|
|
259
|
+
else container.style.background = fallbackGradient(color1, color2, color3);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
return initGrainient(container);
|
|
263
|
+
}, []);
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
const container = containerRef.current;
|
|
266
|
+
if (!container) return;
|
|
267
|
+
if (fallbackRef.current) {
|
|
268
|
+
if (staticSrc) applyStaticBg(container, staticSrc);
|
|
269
|
+
else container.style.background = fallbackGradient(color1, color2, color3);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const ctx = ctxMap.get(container);
|
|
273
|
+
if (!ctx) return;
|
|
274
|
+
const { program, renderer, mesh } = ctx;
|
|
275
|
+
const u = program.uniforms;
|
|
276
|
+
u.uTimeSpeed.value = timeSpeed;
|
|
277
|
+
u.uColorBalance.value = colorBalance;
|
|
278
|
+
u.uWarpStrength.value = warpStrength;
|
|
279
|
+
u.uWarpFrequency.value = warpFrequency;
|
|
280
|
+
u.uWarpSpeed.value = warpSpeed;
|
|
281
|
+
u.uWarpAmplitude.value = warpAmplitude;
|
|
282
|
+
u.uBlendAngle.value = blendAngle;
|
|
283
|
+
u.uBlendSoftness.value = blendSoftness;
|
|
284
|
+
u.uRotationAmount.value = rotationAmount;
|
|
285
|
+
u.uNoiseScale.value = noiseScale;
|
|
286
|
+
u.uGrainAmount.value = grainAmount;
|
|
287
|
+
u.uGrainScale.value = grainScale;
|
|
288
|
+
u.uGrainAnimated.value = grainAnimated ? 1 : 0;
|
|
289
|
+
u.uContrast.value = contrast;
|
|
290
|
+
u.uGamma.value = gamma;
|
|
291
|
+
u.uSaturation.value = saturation;
|
|
292
|
+
u.uCenterOffset.value = new Float32Array([centerX, centerY]);
|
|
293
|
+
u.uZoom.value = zoom;
|
|
294
|
+
u.uColor1.value = new Float32Array(hexToRgb(color1));
|
|
295
|
+
u.uColor2.value = new Float32Array(hexToRgb(color2));
|
|
296
|
+
u.uColor3.value = new Float32Array(hexToRgb(color3));
|
|
297
|
+
if (prefersReducedMotion()) renderer.render({ scene: mesh });
|
|
298
|
+
}, [
|
|
299
|
+
timeSpeed,
|
|
300
|
+
colorBalance,
|
|
301
|
+
warpStrength,
|
|
302
|
+
warpFrequency,
|
|
303
|
+
warpSpeed,
|
|
304
|
+
warpAmplitude,
|
|
305
|
+
blendAngle,
|
|
306
|
+
blendSoftness,
|
|
307
|
+
rotationAmount,
|
|
308
|
+
noiseScale,
|
|
309
|
+
grainAmount,
|
|
310
|
+
grainScale,
|
|
311
|
+
grainAnimated,
|
|
312
|
+
contrast,
|
|
313
|
+
gamma,
|
|
314
|
+
saturation,
|
|
315
|
+
centerX,
|
|
316
|
+
centerY,
|
|
317
|
+
zoom,
|
|
318
|
+
color1,
|
|
319
|
+
color2,
|
|
320
|
+
color3,
|
|
321
|
+
staticSrc
|
|
322
|
+
]);
|
|
323
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className: cn("grainient-container", className) });
|
|
324
|
+
};
|
|
325
|
+
var grainient_default = Grainient;
|
|
326
|
+
|
|
327
|
+
export { grainient_default as default };
|
|
3
328
|
//# sourceMappingURL=grainient.mjs.map
|
|
4
329
|
//# sourceMappingURL=grainient.mjs.map
|
package/dist/grainient.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"grainient.mjs"}
|
|
1
|
+
{"version":3,"sources":["../src/lib/utils.ts","../src/components/grainient.shader.ts","../src/components/grainient.tsx"],"names":[],"mappings":"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","file":"grainient.mjs","sourcesContent":["import { type ClassValue, clsx } from 'clsx';\nimport type { CSSProperties } from 'react';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n/**\n * A style object that may carry CSS custom properties (CSS variables)\n * such as `--duration`. React's `CSSProperties` intentionally drops\n * its index signature, so custom properties are not accepted without help.\n */\nexport type CSSPropertiesWithVars = CSSProperties & Record<`--${string}`, string | number>;\n\n/**\n * Builds a {@link CSSProperties} value that also allows CSS custom properties,\n * so `style` props can be written without an `as React.CSSProperties` cast while\n * still type-checking both the known style keys and the custom-property values.\n *\n * @example\n * <div style={validCssVars({ '--duration': '0.35s', color: 'red' })} />\n */\nexport function validCssVars(style: CSSPropertiesWithVars): CSSProperties {\n return style;\n}\n\n/**\n * Narrows a union to the member(s) that declare `key` — a type-guard\n * replacement for the `in` operator's discriminated-union narrowing.\n */\nexport function hasKey<K extends PropertyKey>(key: K, obj: unknown): obj is Record<K, unknown> {\n return obj !== null && typeof obj === 'object' && Object.hasOwn(obj, key);\n}\n\nexport function noop(): void {\n // intentional no-op\n}\n","import { type Mesh, Program, type Renderer } from 'ogl';\n\n// Keep renderer/program alive across re-renders so the uniform-sync effect can\n// update uniforms without ever rebuilding the WebGL context.\nconst vertex = `#version 300 es\nin vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`;\n\nconst fragment = `#version 300 es\nprecision highp float;\nuniform vec2 iResolution;\nuniform float iTime;\nuniform float uTimeSpeed;\nuniform float uColorBalance;\nuniform float uWarpStrength;\nuniform float uWarpFrequency;\nuniform float uWarpSpeed;\nuniform float uWarpAmplitude;\nuniform float uBlendAngle;\nuniform float uBlendSoftness;\nuniform float uRotationAmount;\nuniform float uNoiseScale;\nuniform float uGrainAmount;\nuniform float uGrainScale;\nuniform float uGrainAnimated;\nuniform float uContrast;\nuniform float uGamma;\nuniform float uSaturation;\nuniform vec2 uCenterOffset;\nuniform float uZoom;\nuniform vec3 uColor1;\nuniform vec3 uColor2;\nuniform vec3 uColor3;\nout vec4 fragColor;\n#define S(a,b,t) smoothstep(a,b,t)\nmat2 Rot(float a){float s=sin(a),c=cos(a);return mat2(c,-s,s,c);}\nvec2 hash(vec2 p){p=vec2(dot(p,vec2(2127.1,81.17)),dot(p,vec2(1269.5,283.37)));return fract(sin(p)*43758.5453);}\nfloat noise(vec2 p){vec2 i=floor(p),f=fract(p),u=f*f*(3.0-2.0*f);float n=mix(mix(dot(-1.0+2.0*hash(i+vec2(0.0,0.0)),f-vec2(0.0,0.0)),dot(-1.0+2.0*hash(i+vec2(1.0,0.0)),f-vec2(1.0,0.0)),u.x),mix(dot(-1.0+2.0*hash(i+vec2(0.0,1.0)),f-vec2(0.0,1.0)),dot(-1.0+2.0*hash(i+vec2(1.0,1.0)),f-vec2(1.0,1.0)),u.x),u.y);return 0.5+0.5*n;}\nvoid mainImage(out vec4 o, vec2 C){\n float t=iTime*uTimeSpeed;\n vec2 uv=C/iResolution.xy;\n float ratio=iResolution.x/iResolution.y;\n vec2 tuv=uv-0.5+uCenterOffset;\n tuv/=max(uZoom,0.001);\n float degree=noise(vec2(t*0.1,tuv.x*tuv.y)*uNoiseScale);\n tuv.y*=1.0/ratio;\n tuv*=Rot(radians((degree-0.5)*uRotationAmount+180.0));\n tuv.y*=ratio;\n float frequency=uWarpFrequency;\n float ws=max(uWarpStrength,0.001);\n float amplitude=uWarpAmplitude/ws;\n float warpTime=t*uWarpSpeed;\n tuv.x+=sin(tuv.y*frequency+warpTime)/amplitude;\n tuv.y+=sin(tuv.x*(frequency*1.5)+warpTime)/(amplitude*0.5);\n vec3 colLav=uColor1;\n vec3 colOrg=uColor2;\n vec3 colDark=uColor3;\n float b=uColorBalance;\n float s=max(uBlendSoftness,0.0);\n mat2 blendRot=Rot(radians(uBlendAngle));\n float blendX=(tuv*blendRot).x;\n float edge0=-0.3-b-s;\n float edge1=0.2-b+s;\n float v0=0.5-b+s;\n float v1=-0.3-b-s;\n vec3 layer1=mix(colDark,colOrg,S(edge0,edge1,blendX));\n vec3 layer2=mix(colOrg,colLav,S(edge0,edge1,blendX));\n vec3 col=mix(layer1,layer2,S(v0,v1,tuv.y));\n vec2 grainUv=uv*max(uGrainScale,0.001);\n if(uGrainAnimated>0.5){grainUv+=vec2(iTime*0.05);}\n float grain=fract(sin(dot(grainUv,vec2(12.9898,78.233)))*43758.5453);\n col+=(grain-0.5)*uGrainAmount;\n col=(col-0.5)*uContrast+0.5;\n float luma=dot(col,vec3(0.2126,0.7152,0.0722));\n col=mix(vec3(luma),col,uSaturation);\n col=pow(max(col,0.0),vec3(1.0/max(uGamma,0.001)));\n col=clamp(col,0.0,1.0);\n o=vec4(col,1.0);\n}\nvoid main(){\n vec4 o=vec4(0.0);\n mainImage(o,gl_FragCoord.xy);\n fragColor=o;\n}\n`;\n\nexport type GrainientContext = { renderer: Renderer; program: Program; mesh: Mesh };\n\nexport function createGrainientProgram(gl: Renderer['gl']): Program {\n return new Program(gl, {\n vertex,\n fragment,\n uniforms: {\n iTime: { value: 0 },\n iResolution: { value: new Float32Array([1, 1]) },\n uTimeSpeed: { value: 0.25 },\n uColorBalance: { value: 0.0 },\n uWarpStrength: { value: 1.0 },\n uWarpFrequency: { value: 5.0 },\n uWarpSpeed: { value: 2.0 },\n uWarpAmplitude: { value: 50.0 },\n uBlendAngle: { value: 0.0 },\n uBlendSoftness: { value: 0.05 },\n uRotationAmount: { value: 500.0 },\n uNoiseScale: { value: 2.0 },\n uGrainAmount: { value: 0.1 },\n uGrainScale: { value: 2.0 },\n uGrainAnimated: { value: 0.0 },\n uContrast: { value: 1.5 },\n uGamma: { value: 1.0 },\n uSaturation: { value: 1.0 },\n uCenterOffset: { value: new Float32Array([0, 0]) },\n uZoom: { value: 0.9 },\n uColor1: { value: new Float32Array([1, 1, 1]) },\n uColor2: { value: new Float32Array([1, 1, 1]) },\n uColor3: { value: new Float32Array([1, 1, 1]) },\n },\n });\n}\n\nexport const ctxMap = new WeakMap<HTMLElement, GrainientContext>();\n","'use client';\n\nimport { Mesh, Renderer, Triangle } from 'ogl';\nimport { useEffect, useRef } from 'react';\nimport { cn } from '../lib/utils.js';\nimport { createGrainientProgram, ctxMap } from './grainient.shader.js';\nimport type { GrainientProps } from './grainient.types.js';\nimport './grainient.css';\n\nconst HEX_COLOR_RE = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i;\n\nconst hexToRgb = (hex: string): [number, number, number] => {\n const result = HEX_COLOR_RE.exec(hex);\n if (!result) return [1, 1, 1];\n // The regex guarantees all three capture groups when it matches; the defaults\n // only silence noUncheckedIndexedAccess.\n const [, r = 'ff', g = 'ff', b = 'ff'] = result;\n return [Number.parseInt(r, 16) / 255, Number.parseInt(g, 16) / 255, Number.parseInt(b, 16) / 255];\n};\n\n// framer-motion's MotionConfig only governs declarative motion.* components, so\n// this raw-WebGL canvas carries its own prefers-reduced-motion check.\nconst prefersReducedMotion = () =>\n typeof globalThis !== 'undefined' && globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches === true;\n\n// The home page mounts four Grainient canvases at once; browsers cap live WebGL\n// contexts (~8-16) and each costs GPU/CPU. On low-power clients — narrow viewports\n// or few CPU cores — skip WebGL and let the static background stand in.\nconst isLowPowerClient = () => {\n if (typeof globalThis === 'undefined') return false;\n const narrow = globalThis.matchMedia?.('(max-width: 1023px)').matches === true;\n const fewCores =\n typeof navigator !== 'undefined' && typeof navigator.hardwareConcurrency === 'number' && navigator.hardwareConcurrency <= 4;\n return narrow || fewCores;\n};\n\n// A tiling SVG feTurbulence tile that stands in for the shader's film grain,\n// inlined so it needs no network request.\nconst GRAIN_LAYER = `url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.12'/%3E%3C/svg%3E\")`;\n\n// CSS gradient fallback when no pre-rendered image is available.\nconst fallbackGradient = (c1: string, c2: string, c3: string) =>\n `${GRAIN_LAYER}, linear-gradient(135deg, ${c1} 0%, ${c2} 55%, ${c3} 100%)`;\n\n// Apply a pre-rendered static image as a cover background.\nconst applyStaticBg = (el: HTMLDivElement, src: string) => {\n el.style.backgroundImage = `url(${src})`;\n el.style.backgroundSize = 'cover';\n el.style.backgroundPosition = 'center';\n};\n\n// Build the WebGL context, wire up resize/visibility observers, and run the\n// render loop. Returns a cleanup that tears everything back down.\nfunction initGrainient(container: HTMLDivElement): () => void {\n const renderer = new Renderer({\n webgl: 2,\n alpha: true,\n antialias: false,\n dpr: Math.min(globalThis.devicePixelRatio || 1, 2),\n });\n\n const gl = renderer.gl;\n const canvas = gl.canvas;\n canvas.style.width = '100%';\n canvas.style.height = '100%';\n canvas.style.display = 'block';\n container.appendChild(canvas);\n\n const geometry = new Triangle(gl);\n const program = createGrainientProgram(gl);\n const mesh = new Mesh(gl, { geometry, program });\n ctxMap.set(container, { renderer, program, mesh });\n\n const setSize = () => {\n const rect = container.getBoundingClientRect();\n renderer.setSize(Math.max(1, Math.floor(rect.width)), Math.max(1, Math.floor(rect.height)));\n const res: Float32Array = program.uniforms.iResolution.value;\n res[0] = gl.drawingBufferWidth;\n res[1] = gl.drawingBufferHeight;\n renderer.render({ scene: mesh });\n };\n\n const ro = new ResizeObserver(setSize);\n ro.observe(container);\n setSize();\n\n let raf = 0;\n let isVisible = true;\n let isPageVisible = !document.hidden;\n const t0 = performance.now();\n\n const loop = (t: number) => {\n program.uniforms.iTime.value = (t - t0) * 0.001;\n renderer.render({ scene: mesh });\n raf = requestAnimationFrame(loop);\n };\n\n const tryStart = () => {\n if (isVisible && isPageVisible && raf === 0) raf = requestAnimationFrame(loop);\n };\n const tryStop = () => {\n if (raf !== 0) {\n cancelAnimationFrame(raf);\n raf = 0;\n }\n };\n\n // Reduced motion: render a single static frame and never start the RAF loop.\n const reduce = prefersReducedMotion();\n\n const io = new IntersectionObserver(\n ([entry]) => {\n if (!entry) return;\n isVisible = entry.isIntersecting;\n if (isVisible) tryStart();\n else tryStop();\n },\n { threshold: 0 },\n );\n if (!reduce) io.observe(container);\n\n const onVisibility = () => {\n isPageVisible = !document.hidden;\n if (isPageVisible) tryStart();\n else tryStop();\n };\n if (!reduce) document.addEventListener('visibilitychange', onVisibility);\n if (!reduce) tryStart();\n\n return () => {\n tryStop();\n ro.disconnect();\n io.disconnect();\n document.removeEventListener('visibilitychange', onVisibility);\n ctxMap.delete(container);\n try {\n container.removeChild(canvas);\n } catch {\n /* ignore */\n }\n };\n}\n\nconst Grainient = (props: GrainientProps) => {\n const {\n timeSpeed = 0.25,\n colorBalance = 0.0,\n warpStrength = 1.0,\n warpFrequency = 5.0,\n warpSpeed = 2.0,\n warpAmplitude = 50.0,\n blendAngle = 0.0,\n blendSoftness = 0.05,\n rotationAmount = 500.0,\n noiseScale = 2.0,\n grainAmount = 0.1,\n grainScale = 2.0,\n grainAnimated = false,\n contrast = 1.5,\n gamma = 1.0,\n saturation = 1.0,\n centerX = 0.0,\n centerY = 0.0,\n zoom = 0.9,\n color1 = '#FF9FFC',\n color2 = '#5227FF',\n color3 = '#B497CF',\n staticSrc = '',\n className = '',\n } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n // Set once on mount: when true, WebGL is skipped and a static background stands in.\n const fallbackRef = useRef(false);\n\n // Effect 1: build WebGL context once, pause when offscreen / tab hidden.\n // When a pre-rendered image is available, low-power clients and\n // prefers-reduced-motion users get it as a zero-GPU-cost background.\n // The renderer is created once on mount; color changes are handled in Effect 2.\n // biome-ignore lint/plugin: creates the WebGL renderer once on mount and tears it down on unmount — imperative GPU setup with no derived-state form.\n // biome-ignore lint/correctness/useExhaustiveDependencies: mount-only WebGL setup; color/staticSrc changes are handled in Effect 2.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n if (isLowPowerClient() || prefersReducedMotion()) {\n fallbackRef.current = true;\n container.dataset.fallback = 'true';\n if (staticSrc) applyStaticBg(container, staticSrc);\n else container.style.background = fallbackGradient(color1, color2, color3);\n return;\n }\n return initGrainient(container);\n }, []); // renderer created once\n\n // Effect 2: sync props to uniforms — zero GPU cost, no teardown.\n // biome-ignore lint/plugin: imperatively writes props into mutable WebGL uniform objects (an external system); there is no render-time equivalent.\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n // Fallback clients have no WebGL context; re-apply the background so a\n // later color/src change still reaches the static background.\n if (fallbackRef.current) {\n if (staticSrc) applyStaticBg(container, staticSrc);\n else container.style.background = fallbackGradient(color1, color2, color3);\n return;\n }\n\n const ctx = ctxMap.get(container);\n if (!ctx) return;\n\n const { program, renderer, mesh } = ctx;\n const u = program.uniforms;\n\n u.uTimeSpeed.value = timeSpeed;\n u.uColorBalance.value = colorBalance;\n u.uWarpStrength.value = warpStrength;\n u.uWarpFrequency.value = warpFrequency;\n u.uWarpSpeed.value = warpSpeed;\n u.uWarpAmplitude.value = warpAmplitude;\n u.uBlendAngle.value = blendAngle;\n u.uBlendSoftness.value = blendSoftness;\n u.uRotationAmount.value = rotationAmount;\n u.uNoiseScale.value = noiseScale;\n u.uGrainAmount.value = grainAmount;\n u.uGrainScale.value = grainScale;\n u.uGrainAnimated.value = grainAnimated ? 1.0 : 0.0;\n u.uContrast.value = contrast;\n u.uGamma.value = gamma;\n u.uSaturation.value = saturation;\n u.uCenterOffset.value = new Float32Array([centerX, centerY]);\n u.uZoom.value = zoom;\n u.uColor1.value = new Float32Array(hexToRgb(color1));\n u.uColor2.value = new Float32Array(hexToRgb(color2));\n u.uColor3.value = new Float32Array(hexToRgb(color3));\n\n // Under reduced motion the RAF loop never starts; draw one frame here so\n // the real colors appear (the first frame from setSize() is all-white).\n if (prefersReducedMotion()) renderer.render({ scene: mesh });\n }, [\n timeSpeed,\n colorBalance,\n warpStrength,\n warpFrequency,\n warpSpeed,\n warpAmplitude,\n blendAngle,\n blendSoftness,\n rotationAmount,\n noiseScale,\n grainAmount,\n grainScale,\n grainAnimated,\n contrast,\n gamma,\n saturation,\n centerX,\n centerY,\n zoom,\n color1,\n color2,\n color3,\n staticSrc,\n ]);\n\n return <div ref={containerRef} className={cn('grainient-container', className)} />;\n};\n\nexport default Grainient;\n"]}
|
package/dist/highlighter.js
CHANGED
package/dist/highlighter.mjs
CHANGED
|
@@ -1,5 +1,124 @@
|
|
|
1
|
-
|
|
2
|
-
import '
|
|
3
|
-
import '
|
|
1
|
+
"use client";
|
|
2
|
+
import { useInView } from 'framer-motion';
|
|
3
|
+
import { useRef, useState, useLayoutEffect, useEffect } from 'react';
|
|
4
|
+
import { annotate } from 'rough-notation';
|
|
5
|
+
import { clsx } from 'clsx';
|
|
6
|
+
import { twMerge } from 'tailwind-merge';
|
|
7
|
+
import { jsx } from 'react/jsx-runtime';
|
|
8
|
+
|
|
9
|
+
function useMediaQuery(query, defaultState = false) {
|
|
10
|
+
const [matches, setMatches] = useState(defaultState);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const mql = globalThis.matchMedia(query);
|
|
13
|
+
setMatches(mql.matches);
|
|
14
|
+
const onChange = (e) => setMatches(e.matches);
|
|
15
|
+
mql.addEventListener("change", onChange);
|
|
16
|
+
return () => mql.removeEventListener("change", onChange);
|
|
17
|
+
}, [query]);
|
|
18
|
+
return matches;
|
|
19
|
+
}
|
|
20
|
+
function cn(...inputs) {
|
|
21
|
+
return twMerge(clsx(inputs));
|
|
22
|
+
}
|
|
23
|
+
function resolveColor(element, color) {
|
|
24
|
+
if (!color.startsWith("var(")) return color;
|
|
25
|
+
const token = color.slice(4, -1).trim();
|
|
26
|
+
return globalThis.getComputedStyle(element).getPropertyValue(token).trim() || color;
|
|
27
|
+
}
|
|
28
|
+
var BREAKPOINTS = [
|
|
29
|
+
["xs", 30],
|
|
30
|
+
["sm", 40],
|
|
31
|
+
["md", 50],
|
|
32
|
+
["lg", 64],
|
|
33
|
+
["xl", 80],
|
|
34
|
+
["2xl", 96]
|
|
35
|
+
];
|
|
36
|
+
function getBreakpoint() {
|
|
37
|
+
let current = "base";
|
|
38
|
+
for (const [name, rem] of BREAKPOINTS) {
|
|
39
|
+
if (globalThis.matchMedia(`(min-width: ${rem}rem)`).matches) current = name;
|
|
40
|
+
}
|
|
41
|
+
return current;
|
|
42
|
+
}
|
|
43
|
+
function onBreakpointChange(listener) {
|
|
44
|
+
const queries = BREAKPOINTS.map(([, rem]) => globalThis.matchMedia(`(min-width: ${rem}rem)`));
|
|
45
|
+
for (const query of queries) query.addEventListener("change", listener);
|
|
46
|
+
return () => {
|
|
47
|
+
for (const query of queries) query.removeEventListener("change", listener);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function Highlighter({
|
|
51
|
+
children,
|
|
52
|
+
className,
|
|
53
|
+
action = "highlight",
|
|
54
|
+
color = "var(--marker-highlight)",
|
|
55
|
+
strokeWidth = 1.5,
|
|
56
|
+
animationDuration = 600,
|
|
57
|
+
delay = 0,
|
|
58
|
+
iterations = 2,
|
|
59
|
+
padding = 2,
|
|
60
|
+
multiline = true,
|
|
61
|
+
isView = false
|
|
62
|
+
}) {
|
|
63
|
+
const elementRef = useRef(null);
|
|
64
|
+
const isInView = useInView(elementRef, { once: true, margin: "-10%" });
|
|
65
|
+
const reducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
|
|
66
|
+
const [themeVersion, setThemeVersion] = useState(0);
|
|
67
|
+
const shouldShow = !isView || isInView;
|
|
68
|
+
useLayoutEffect(() => {
|
|
69
|
+
const observer = new MutationObserver(() => setThemeVersion((v) => v + 1));
|
|
70
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
|
|
71
|
+
return () => observer.disconnect();
|
|
72
|
+
}, []);
|
|
73
|
+
useLayoutEffect(() => {
|
|
74
|
+
const element = elementRef.current;
|
|
75
|
+
if (!(shouldShow && element)) return;
|
|
76
|
+
let annotation;
|
|
77
|
+
let untrack;
|
|
78
|
+
const draw = () => {
|
|
79
|
+
annotation = annotate(element, {
|
|
80
|
+
type: action,
|
|
81
|
+
color: resolveColor(element, color),
|
|
82
|
+
strokeWidth,
|
|
83
|
+
animationDuration,
|
|
84
|
+
animate: !reducedMotion,
|
|
85
|
+
iterations,
|
|
86
|
+
padding,
|
|
87
|
+
multiline
|
|
88
|
+
});
|
|
89
|
+
annotation.show();
|
|
90
|
+
let drawnAt = getBreakpoint();
|
|
91
|
+
untrack = onBreakpointChange(() => {
|
|
92
|
+
const current = getBreakpoint();
|
|
93
|
+
if (current === drawnAt) return;
|
|
94
|
+
drawnAt = current;
|
|
95
|
+
annotation?.show();
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
const startDelay = reducedMotion ? 0 : delay;
|
|
99
|
+
const timer = startDelay > 0 ? globalThis.setTimeout(draw, startDelay) : void 0;
|
|
100
|
+
if (timer === void 0) draw();
|
|
101
|
+
return () => {
|
|
102
|
+
globalThis.clearTimeout(timer);
|
|
103
|
+
untrack?.();
|
|
104
|
+
annotation?.remove();
|
|
105
|
+
};
|
|
106
|
+
}, [
|
|
107
|
+
shouldShow,
|
|
108
|
+
action,
|
|
109
|
+
color,
|
|
110
|
+
strokeWidth,
|
|
111
|
+
animationDuration,
|
|
112
|
+
delay,
|
|
113
|
+
iterations,
|
|
114
|
+
padding,
|
|
115
|
+
multiline,
|
|
116
|
+
reducedMotion,
|
|
117
|
+
themeVersion
|
|
118
|
+
]);
|
|
119
|
+
return /* @__PURE__ */ jsx("span", { className: cn("relative inline-block bg-transparent", className), children: /* @__PURE__ */ jsx("span", { ref: elementRef, children }) });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export { Highlighter };
|
|
4
123
|
//# sourceMappingURL=highlighter.mjs.map
|
|
5
124
|
//# sourceMappingURL=highlighter.mjs.map
|
package/dist/highlighter.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"highlighter.mjs"}
|
|
1
|
+
{"version":3,"sources":["../src/lib/use-media-query.ts","../src/lib/utils.ts","../src/components/highlighter.tsx"],"names":["useState"],"mappings":"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","file":"highlighter.mjs","sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\n\n/**\n * Tracks a CSS media query, returning `true` when it matches.\n *\n * The default return before hydration is `false`; pass `defaultState`\n * to set a different initial value (e.g. `true` for dark-mode queries\n * whose SSR default is dark).\n */\nexport function useMediaQuery(query: string, defaultState = false): boolean {\n const [matches, setMatches] = useState(defaultState);\n\n // biome-ignore lint/plugin: subscribes to a matchMedia listener — an imperative side effect with no derived-state equivalent.\n useEffect(() => {\n const mql = globalThis.matchMedia(query);\n setMatches(mql.matches);\n\n const onChange = (e: MediaQueryListEvent) => setMatches(e.matches);\n mql.addEventListener('change', onChange);\n return () => mql.removeEventListener('change', onChange);\n }, [query]);\n\n return matches;\n}\n","import { type ClassValue, clsx } from 'clsx';\nimport type { CSSProperties } from 'react';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\n/**\n * A style object that may carry CSS custom properties (CSS variables)\n * such as `--duration`. React's `CSSProperties` intentionally drops\n * its index signature, so custom properties are not accepted without help.\n */\nexport type CSSPropertiesWithVars = CSSProperties & Record<`--${string}`, string | number>;\n\n/**\n * Builds a {@link CSSProperties} value that also allows CSS custom properties,\n * so `style` props can be written without an `as React.CSSProperties` cast while\n * still type-checking both the known style keys and the custom-property values.\n *\n * @example\n * <div style={validCssVars({ '--duration': '0.35s', color: 'red' })} />\n */\nexport function validCssVars(style: CSSPropertiesWithVars): CSSProperties {\n return style;\n}\n\n/**\n * Narrows a union to the member(s) that declare `key` — a type-guard\n * replacement for the `in` operator's discriminated-union narrowing.\n */\nexport function hasKey<K extends PropertyKey>(key: K, obj: unknown): obj is Record<K, unknown> {\n return obj !== null && typeof obj === 'object' && Object.hasOwn(obj, key);\n}\n\nexport function noop(): void {\n // intentional no-op\n}\n","'use client';\n\nimport { useInView } from 'framer-motion';\nimport { type ReactNode, useLayoutEffect, useRef, useState } from 'react';\nimport { annotate } from 'rough-notation';\nimport type { RoughAnnotation } from 'rough-notation/lib/model';\nimport { useMediaQuery } from '../lib/use-media-query.js';\nimport { cn } from '../lib/utils.js';\n\ntype AnnotationAction = 'highlight' | 'underline' | 'box' | 'circle' | 'strike-through' | 'crossed-off' | 'bracket';\n\n/**\n * rough-notation writes the colour straight into the SVG's `stroke` presentation\n * attribute, where `var()` is never substituted — so a design token has to be\n * resolved against the live element before it is handed over.\n */\nfunction resolveColor(element: Element, color: string): string {\n if (!color.startsWith('var(')) return color;\n const token = color.slice(4, -1).trim();\n return globalThis.getComputedStyle(element).getPropertyValue(token).trim() || color;\n}\n\n/**\n * The site's breakpoints, ascending, in the `rem` units Tailwind emits them in —\n * so they keep tracking the root font size the way the CSS does. Mirrored from\n * the library's `@theme` block (`xs` and `md` are custom, the rest are defaults).\n */\nconst BREAKPOINTS = [\n ['xs', 30],\n ['sm', 40],\n ['md', 50],\n ['lg', 64],\n ['xl', 80],\n ['2xl', 96],\n] as const;\n\ntype Breakpoint = 'base' | (typeof BREAKPOINTS)[number][0];\n\n/** `'base'` is the implicit below-`xs` band — the widths no `min-width` query claims. */\nfunction getBreakpoint(): Breakpoint {\n let current: Breakpoint = 'base';\n for (const [name, rem] of BREAKPOINTS) {\n if (globalThis.matchMedia(`(min-width: ${rem}rem)`).matches) current = name;\n }\n return current;\n}\n\n/**\n * Call `listener` whenever the viewport crosses a breakpoint. Returns the\n * unsubscribe.\n *\n * The imperative counterpart to a `useBreakpoint` hook, for work that has to land\n * in the *same frame* as the layout change. Media-query change events fire during\n * the rendering steps, before `requestAnimationFrame` and before paint, so a\n * listener that writes to the DOM here is painted with the new layout rather than\n * one frame behind it.\n */\nfunction onBreakpointChange(listener: () => void): () => void {\n const queries = BREAKPOINTS.map(([, rem]) => globalThis.matchMedia(`(min-width: ${rem}rem)`));\n for (const query of queries) query.addEventListener('change', listener);\n return () => {\n for (const query of queries) query.removeEventListener('change', listener);\n };\n}\n\ntype HighlighterProps = {\n /** The words to annotate. Rendered inline, so they inherit the surrounding type. */\n children: ReactNode;\n className?: string;\n /**\n * Which marker stroke to draw.\n * @default 'highlight'\n */\n action?: AnnotationAction;\n /**\n * Stroke colour. Accepts a `var(--token)` string, which is resolved against\n * the live element and re-resolved whenever the theme flips.\n * @default 'var(--marker-highlight)'\n */\n color?: string;\n /** @default 1.5 */\n strokeWidth?: number;\n /** Milliseconds the stroke takes to draw itself on. @default 600 */\n animationDuration?: number;\n /**\n * Milliseconds to hold the stroke back before it starts drawing. Ignored under\n * reduced motion, where there is no draw-on to stagger.\n * @default 0\n */\n delay?: number;\n /** Passes over the same stroke, which is what makes it read as hand-drawn. @default 2 */\n iterations?: number;\n /** Pixels of slack between the text box and the stroke. @default 2 */\n padding?: number;\n /** Annotate every line the text wraps onto, rather than one box around all of them. @default true */\n multiline?: boolean;\n /** Hold the stroke back until the text scrolls into view. @default false */\n isView?: boolean;\n};\n\n/**\n * Draws a hand-drawn marker stroke over a word or phrase.\n *\n * The stroke is an SVG that rough-notation injects on the client, so the\n * exported HTML ships the bare text and nothing about the layout depends on JS\n * having run. Readers who ask for reduced motion get the stroke without the\n * draw-on animation.\n */\nexport function Highlighter({\n children,\n className,\n action = 'highlight',\n color = 'var(--marker-highlight)',\n strokeWidth = 1.5,\n animationDuration = 600,\n delay = 0,\n iterations = 2,\n padding = 2,\n multiline = true,\n isView = false,\n}: HighlighterProps) {\n const elementRef = useRef<HTMLSpanElement>(null);\n const isInView = useInView(elementRef, { once: true, margin: '-10%' });\n const reducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');\n // Only a re-run trigger: the colour itself comes from the computed style, which\n // is already correct on first paint because the theme class is set before\n // hydration. The observer below bumps this whenever the class attribute flips.\n const [themeVersion, setThemeVersion] = useState(0);\n\n const shouldShow = !isView || isInView;\n\n // Theme flips land as a class swap on <html> (the `.dark` class toggled by the\n // theme provider), and rough-notation bakes the *resolved* token into the\n // SVG's stroke attribute — which does not track var(). Re-running the draw\n // effect with a freshly resolved colour is what repaints the stroke.\n useLayoutEffect(() => {\n const observer = new MutationObserver(() => setThemeVersion((v) => v + 1));\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });\n return () => observer.disconnect();\n }, []);\n\n // Imperatively injects and tears down a rough-notation SVG measured off live\n // DOM layout — a post-render side effect with no render-time equivalent.\n // biome-ignore lint/correctness/useExhaustiveDependencies: `themeVersion` is a redraw trigger, not a read value — the token resolves to a different colour per theme, so a flip has to repaint the stroke.\n useLayoutEffect(() => {\n const element = elementRef.current;\n if (!(shouldShow && element)) return;\n\n let annotation: RoughAnnotation | undefined;\n let untrack: (() => void) | undefined;\n\n const draw = () => {\n annotation = annotate(element, {\n type: action,\n color: resolveColor(element, color),\n strokeWidth,\n animationDuration,\n animate: !reducedMotion,\n iterations,\n padding,\n multiline,\n });\n annotation.show();\n\n // The wrapper below already carries the stroke wherever the page moves the\n // text, so the only thing left that can stale it is a change in the text's\n // own shape — which on a breakpoint-driven layout means a breakpoint. This\n // is subscribed directly rather than through a `useBreakpoint` hook because\n // a React state round-trip commits a frame or more after the browser has\n // painted the new layout, which is exactly the lag being chased out.\n // `show()` on a still-showing annotation re-renders un-animated, so the\n // stroke resizes without replaying the draw-on.\n let drawnAt = getBreakpoint();\n untrack = onBreakpointChange(() => {\n const current = getBreakpoint();\n if (current === drawnAt) return;\n drawnAt = current;\n annotation?.show();\n });\n };\n\n const startDelay = reducedMotion ? 0 : delay;\n const timer = startDelay > 0 ? globalThis.setTimeout(draw, startDelay) : undefined;\n if (timer === undefined) draw();\n\n return () => {\n globalThis.clearTimeout(timer);\n untrack?.();\n annotation?.remove();\n };\n }, [\n shouldShow,\n action,\n color,\n strokeWidth,\n animationDuration,\n delay,\n iterations,\n padding,\n multiline,\n reducedMotion,\n themeVersion,\n ]);\n\n // Two spans, and both earn their place.\n //\n // rough-notation injects its SVG as a *sibling* of the annotated element, laid\n // out `position: absolute; top: 0; left: 0`, with the stroke's coordinates baked\n // as offsets from wherever that SVG lands. Left alone it resolves against\n // whatever positioned ancestor it happens to find — for a centred, shrink-wrapped\n // page section, effectively the viewport — so every reflow that slid the text\n // sideways slid it out from under its own stroke, and only a redraw put it back.\n // The `relative` wrapper makes the SVG resolve against a box the text sits\n // inside instead, so the two now move as one and no listener is involved.\n //\n // The inner span stays plain `inline`, which is what makes `multiline` work:\n // `getClientRects()` on an inline reports one box per line, so wrapped text gets\n // a stroke per line rather than one slab drawn around the whole paragraph.\n return (\n <span className={cn('relative inline-block bg-transparent', className)}>\n <span ref={elementRef}>{children}</span>\n </span>\n );\n}\n\nexport type { AnnotationAction, HighlighterProps };\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
'use strict';
|
|
2
3
|
|
|
3
4
|
var accordion = require('@base-ui/react/accordion');
|
|
@@ -13,7 +14,6 @@ var shadersReact = require('@paper-design/shaders-react');
|
|
|
13
14
|
var navigationMenu = require('@base-ui/react/navigation-menu');
|
|
14
15
|
var reactSlot = require('@radix-ui/react-slot');
|
|
15
16
|
|
|
16
|
-
// src/components/accordion.tsx
|
|
17
17
|
function cn(...inputs) {
|
|
18
18
|
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
19
19
|
}
|