argentui 0.3.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/dist/index.js ADDED
@@ -0,0 +1,794 @@
1
+ // src/surfaces.tsx
2
+ import { forwardRef } from "react";
3
+
4
+ // src/metal.tsx
5
+ import { useEffect, useRef, useState } from "react";
6
+ import { LiquidMetal } from "@paper-design/shaders-react";
7
+
8
+ // src/engine.ts
9
+ var VERT = `#version 300 es
10
+ void main() {
11
+ vec2 pos = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
12
+ gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0);
13
+ }`;
14
+ var FRAG = `#version 300 es
15
+ precision highp float;
16
+ uniform vec2 u_res;
17
+ uniform float u_time;
18
+ uniform vec3 u_light;
19
+ uniform vec3 u_dark;
20
+ uniform float u_rep;
21
+ uniform float u_angle;
22
+ uniform float u_soft;
23
+ uniform float u_disp;
24
+ uniform float u_dist;
25
+ uniform float u_scale;
26
+ out vec4 outColor;
27
+
28
+ // 2D simplex noise \u2014 Ashima Arts / Stefan Gustavson (webgl-noise, MIT).
29
+ vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
30
+ vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
31
+ vec3 permute(vec3 x) { return mod289(((x * 34.0) + 1.0) * x); }
32
+ float snoise(vec2 v) {
33
+ const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);
34
+ vec2 i = floor(v + dot(v, C.yy));
35
+ vec2 x0 = v - i + dot(i, C.xx);
36
+ vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
37
+ vec4 x12 = x0.xyxy + C.xxzz;
38
+ x12.xy -= i1;
39
+ i = mod289(i);
40
+ vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
41
+ vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
42
+ m = m * m;
43
+ m = m * m;
44
+ vec3 x = 2.0 * fract(p * C.www) - 1.0;
45
+ vec3 h = abs(x) - 0.5;
46
+ vec3 ox = floor(x + 0.5);
47
+ vec3 a0 = x - ox;
48
+ m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h);
49
+ vec3 g;
50
+ g.x = a0.x * x0.x + h.x * x0.y;
51
+ g.yz = a0.yz * x12.xz + h.yz * x12.yw;
52
+ return 130.0 * dot(m, g);
53
+ }
54
+
55
+ // One reflection band: a soft triangle wave over the warped coordinate.
56
+ float stripe(float coord) {
57
+ float f = fract(coord);
58
+ float tri = abs(f * 2.0 - 1.0);
59
+ return smoothstep(0.5 - u_soft, 0.5 + u_soft, tri);
60
+ }
61
+
62
+ void main() {
63
+ vec2 uv = (gl_FragCoord.xy / u_res - 0.5);
64
+ uv.x *= u_res.x / u_res.y;
65
+ uv /= max(u_scale, 0.001);
66
+
67
+ float t = u_time;
68
+
69
+ // curvature: bands bend as if wrapping a bulged surface
70
+ float d = length(uv);
71
+ float bulge = 1.0 - 0.45 * d * d;
72
+
73
+ // flowing warp \u2014 two noise octaves drifting at different rates
74
+ float n = snoise(uv * 0.9 + vec2(0.0, -t * 0.28));
75
+ n += 0.35 * snoise(uv * 1.8 + vec2(t * 0.1, -t * 0.45));
76
+
77
+ // rotate so bands run along u_angle
78
+ float c = cos(u_angle), s = sin(u_angle);
79
+ vec2 ruv = mat2(c, -s, s, c) * uv;
80
+
81
+ float coord = ruv.y * bulge * u_rep + n * u_dist * 2.2 - t * 0.22;
82
+
83
+ // per-channel dispersion \u2014 the chrome fringing
84
+ float r = stripe(coord + u_disp);
85
+ float g = stripe(coord);
86
+ float b = stripe(coord - u_disp);
87
+
88
+ vec3 col = vec3(
89
+ mix(u_dark.r, u_light.r, r),
90
+ mix(u_dark.g, u_light.g, g),
91
+ mix(u_dark.b, u_light.b, b)
92
+ );
93
+
94
+ // rim lift toward the edges
95
+ col *= 0.94 + 0.14 * smoothstep(0.25, 0.85, d);
96
+
97
+ outColor = vec4(col, 1.0);
98
+ }`;
99
+ function hexToRgb(hex) {
100
+ const h = hex.replace("#", "");
101
+ const v = h.length === 3 ? h.split("").map((ch) => ch + ch).join("") : h;
102
+ const n = parseInt(v, 16);
103
+ return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
104
+ }
105
+ var NATIVE_TONES = {
106
+ silver: { light: "#f8fafc", dark: "#23262b", repetition: 1.8, angle: 68, softness: 0.34, dispersion: 0.03, distortion: 0.38 },
107
+ gold: { light: "#ffe9a8", dark: "#6e5408", repetition: 1.8, angle: 68, softness: 0.34, dispersion: 0.028, distortion: 0.36 },
108
+ gunmetal: { light: "#b6bec9", dark: "#14161a", repetition: 1.6, angle: 80, softness: 0.36, dispersion: 0.025, distortion: 0.34 },
109
+ obsidian: { light: "#787c88", dark: "#000000", repetition: 1.4, angle: 92, softness: 0.42, dispersion: 0.02, distortion: 0.28 }
110
+ };
111
+ function compile(gl, type, src) {
112
+ const sh = gl.createShader(type);
113
+ if (!sh) return null;
114
+ gl.shaderSource(sh, src);
115
+ gl.compileShader(sh);
116
+ if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
117
+ console.error("argent shader:", gl.getShaderInfoLog(sh));
118
+ gl.deleteShader(sh);
119
+ return null;
120
+ }
121
+ return sh;
122
+ }
123
+ function mountMetal(canvas, params) {
124
+ const gl = canvas.getContext("webgl2", { antialias: true });
125
+ if (!gl) return null;
126
+ const vs = compile(gl, gl.VERTEX_SHADER, VERT);
127
+ const fs = compile(gl, gl.FRAGMENT_SHADER, FRAG);
128
+ if (!vs || !fs) return null;
129
+ const prog = gl.createProgram();
130
+ gl.attachShader(prog, vs);
131
+ gl.attachShader(prog, fs);
132
+ gl.linkProgram(prog);
133
+ if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
134
+ console.error("argent shader link:", gl.getProgramInfoLog(prog));
135
+ return null;
136
+ }
137
+ gl.useProgram(prog);
138
+ const U = (name) => gl.getUniformLocation(prog, name);
139
+ const uRes = U("u_res"), uTime = U("u_time"), uLight = U("u_light"), uDark = U("u_dark");
140
+ const uRep = U("u_rep"), uAngle = U("u_angle"), uSoft = U("u_soft");
141
+ const uDisp = U("u_disp"), uDist = U("u_dist"), uScale = U("u_scale");
142
+ let p = { ...params };
143
+ let raf = 0;
144
+ let tAcc = Math.random() * 40;
145
+ let last = performance.now();
146
+ let dead = false;
147
+ function draw() {
148
+ raf = 0;
149
+ if (dead) return;
150
+ const dpr = Math.min(window.devicePixelRatio || 1, 2);
151
+ const w = Math.max(1, Math.round(canvas.clientWidth * dpr));
152
+ const h = Math.max(1, Math.round(canvas.clientHeight * dpr));
153
+ if (canvas.width !== w || canvas.height !== h) {
154
+ canvas.width = w;
155
+ canvas.height = h;
156
+ gl.viewport(0, 0, w, h);
157
+ }
158
+ const now = performance.now();
159
+ tAcc += (now - last) / 1e3 * p.speed;
160
+ last = now;
161
+ gl.uniform2f(uRes, w, h);
162
+ gl.uniform1f(uTime, tAcc);
163
+ gl.uniform3fv(uLight, hexToRgb(p.light));
164
+ gl.uniform3fv(uDark, hexToRgb(p.dark));
165
+ gl.uniform1f(uRep, p.repetition);
166
+ gl.uniform1f(uAngle, p.angle * Math.PI / 180);
167
+ gl.uniform1f(uSoft, Math.max(0.02, p.softness));
168
+ gl.uniform1f(uDisp, p.dispersion);
169
+ gl.uniform1f(uDist, p.distortion);
170
+ gl.uniform1f(uScale, p.scale);
171
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
172
+ if (p.speed > 0) raf = requestAnimationFrame(draw);
173
+ }
174
+ draw();
175
+ return {
176
+ update(np) {
177
+ p = { ...p, ...np };
178
+ last = performance.now();
179
+ if (!raf) raf = requestAnimationFrame(draw);
180
+ },
181
+ destroy() {
182
+ dead = true;
183
+ if (raf) cancelAnimationFrame(raf);
184
+ gl.getExtension("WEBGL_lose_context")?.loseContext();
185
+ }
186
+ };
187
+ }
188
+
189
+ // src/metal.tsx
190
+ import { jsx } from "react/jsx-runtime";
191
+ var TONE_PARAMS = {
192
+ silver: {
193
+ colorBack: "#a7abb1",
194
+ colorTint: "#ffffff",
195
+ repetition: 3,
196
+ softness: 0.18,
197
+ shiftRed: 0.32,
198
+ shiftBlue: 0.32,
199
+ distortion: 0.14,
200
+ contour: 0.55,
201
+ angle: 68
202
+ },
203
+ gold: {
204
+ colorBack: "#94700e",
205
+ colorTint: "#ffedb0",
206
+ repetition: 3,
207
+ softness: 0.2,
208
+ shiftRed: 0.3,
209
+ shiftBlue: 0.12,
210
+ distortion: 0.13,
211
+ contour: 0.5,
212
+ angle: 68
213
+ },
214
+ gunmetal: {
215
+ colorBack: "#33373d",
216
+ colorTint: "#b2bac4",
217
+ repetition: 2.6,
218
+ softness: 0.26,
219
+ shiftRed: 0.22,
220
+ shiftBlue: 0.32,
221
+ distortion: 0.1,
222
+ contour: 0.45,
223
+ angle: 80
224
+ },
225
+ obsidian: {
226
+ colorBack: "#000000",
227
+ colorTint: "#9498a6",
228
+ repetition: 2,
229
+ softness: 0.4,
230
+ shiftRed: 0.14,
231
+ shiftBlue: 0.24,
232
+ distortion: 0.07,
233
+ contour: 0.32,
234
+ angle: 92
235
+ }
236
+ };
237
+ var imageMountQueue = Promise.resolve();
238
+ function useStaggeredMount() {
239
+ const [go, setGo] = useState(false);
240
+ useEffect(() => {
241
+ let alive = true;
242
+ imageMountQueue = imageMountQueue.then(async () => {
243
+ if (!alive) return;
244
+ setGo(true);
245
+ await new Promise((r) => setTimeout(r, 300));
246
+ });
247
+ return () => {
248
+ alive = false;
249
+ };
250
+ }, []);
251
+ return go;
252
+ }
253
+ function useMounted() {
254
+ const [m, setM] = useState(false);
255
+ useEffect(() => setM(true), []);
256
+ return m;
257
+ }
258
+ function useReducedMotion() {
259
+ const [reduced, setReduced] = useState(false);
260
+ useEffect(() => {
261
+ const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
262
+ setReduced(mq.matches);
263
+ const onChange = (e) => setReduced(e.matches);
264
+ mq.addEventListener("change", onChange);
265
+ return () => mq.removeEventListener("change", onChange);
266
+ }, []);
267
+ return reduced;
268
+ }
269
+ function useInView(ref, margin = "250px") {
270
+ const [inView, setInView] = useState(false);
271
+ useEffect(() => {
272
+ const el = ref.current;
273
+ if (!el) return;
274
+ const io = new IntersectionObserver(([entry]) => setInView(entry.isIntersecting), { rootMargin: margin });
275
+ io.observe(el);
276
+ return () => io.disconnect();
277
+ }, [ref, margin]);
278
+ return inView;
279
+ }
280
+ var FINISHES = {
281
+ /** Cards, nav, panels — broad flowing reflection bands (the tone defaults). */
282
+ surface: { scale: 1.1 },
283
+ /** Buttons — slightly spread, calmer warp so labels stay readable. */
284
+ button: { scale: 1.4, distortion: 0.1 },
285
+ /** Thin horizontal strips (progress) — near-vertical stripes crossing the bar. */
286
+ bar: { angle: 14, repetition: 5, softness: 0.3, distortion: 0.06, shift: 0.5, scale: 0.8, speed: 0.75 },
287
+ /** Small round things (toggle thumbs) — one soft highlight, like a polished sphere. */
288
+ orb: { angle: 112, angleJitter: 20, repetition: 1.7, softness: 0.5, distortion: 0.05, shift: 0.4, scale: 1.5 },
289
+ /** Hairline edges (badges, thin borders) — dense bands so any slice sparkles. */
290
+ rim: { angle: 40, repetition: 4.5, softness: 0.3, distortion: 0.08, shift: 0.5, scale: 0.85, speed: 0.85 }
291
+ };
292
+ var EFFECTS = {
293
+ /** The default — steady flowing reflection bands. */
294
+ flow: {},
295
+ /** Slow, heavy, half-melted — soft wide bands churning lazily. */
296
+ molten: { softness: 0.5, distortion: 0.3, speed: 0.45, shift: 0.6 },
297
+ /** Agitated surface — tighter bands, hard noise, quick motion. */
298
+ ripple: { repetition: 4, softness: 0.24, distortion: 0.5, speed: 1.4 },
299
+ /** Mirror-polished — crisp hard bands, strong chromatic fringe, calm. */
300
+ chrome: { softness: 0.05, distortion: 0.05, shift: 1.6, speed: 0.8 },
301
+ /** Horizontal swells rolling through the surface. */
302
+ wave: { angle: 0, repetition: 5, softness: 0.36, distortion: 0.18, speed: 0.7 }
303
+ };
304
+ var FILL_STYLE = { position: "absolute", inset: 0, width: "100%", height: "100%" };
305
+ function NativeCanvas({ params }) {
306
+ const canvasRef = useRef(null);
307
+ const mountRef = useRef(null);
308
+ const initial = useRef(params);
309
+ initial.current = params;
310
+ useEffect(() => {
311
+ const canvas = canvasRef.current;
312
+ if (!canvas) return;
313
+ mountRef.current = mountMetal(canvas, initial.current);
314
+ return () => {
315
+ mountRef.current?.destroy();
316
+ mountRef.current = null;
317
+ };
318
+ }, []);
319
+ useEffect(() => {
320
+ mountRef.current?.update(params);
321
+ }, [params]);
322
+ return /* @__PURE__ */ jsx("canvas", { ref: canvasRef, style: { ...FILL_STYLE, display: "block" } });
323
+ }
324
+ function MetalFill({ tone, speed = 1, scale, engine = "paper", finish = "surface", effect = "flow", angle }) {
325
+ const ref = useRef(null);
326
+ const mounted = useMounted();
327
+ const inView = useInView(ref);
328
+ const reduced = useReducedMotion();
329
+ const f = FINISHES[finish];
330
+ const e = EFFECTS[effect];
331
+ const [jitter] = useState(() => f.angleJitter ? (Math.random() * 2 - 1) * f.angleJitter : 0);
332
+ const base = TONE_PARAMS[tone];
333
+ const effAngle = (angle ?? e.angle ?? f.angle ?? base.angle ?? 70) + jitter;
334
+ const effScale = scale ?? f.scale ?? 1.1;
335
+ const effSpeed = (reduced ? 0 : speed) * (f.speed ?? 1) * (e.speed ?? 1);
336
+ const shift = (f.shift ?? 1) * (e.shift ?? 1);
337
+ const repetition = e.repetition ?? f.repetition ?? base.repetition;
338
+ const softness = e.softness ?? f.softness ?? base.softness;
339
+ const distortion = e.distortion ?? f.distortion ?? base.distortion;
340
+ const native = NATIVE_TONES[tone];
341
+ const nativeParams = {
342
+ ...native,
343
+ angle: effAngle,
344
+ repetition: repetition ?? native.repetition,
345
+ softness: softness ?? native.softness,
346
+ // the native warp amount runs ~3× paper's distortion scale
347
+ distortion: distortion !== void 0 ? distortion * 3 : native.distortion,
348
+ dispersion: native.dispersion * shift,
349
+ speed: effSpeed,
350
+ scale: effScale
351
+ };
352
+ return /* @__PURE__ */ jsx("span", { ref, "aria-hidden": "true", style: { position: "absolute", inset: 0 }, children: mounted && inView && (engine === "native" ? /* @__PURE__ */ jsx(NativeCanvas, { params: nativeParams }) : /* @__PURE__ */ jsx(
353
+ LiquidMetal,
354
+ {
355
+ shape: "none",
356
+ fit: "cover",
357
+ scale: effScale,
358
+ speed: effSpeed,
359
+ ...base,
360
+ angle: effAngle,
361
+ repetition,
362
+ softness,
363
+ distortion,
364
+ shiftRed: (base.shiftRed ?? 0.3) * shift,
365
+ shiftBlue: (base.shiftBlue ?? 0.3) * shift,
366
+ style: FILL_STYLE
367
+ }
368
+ )) });
369
+ }
370
+
371
+ // src/haptics.ts
372
+ var enabled = true;
373
+ function setHaptics(on) {
374
+ enabled = on;
375
+ }
376
+ function vibrate(pattern) {
377
+ if (!enabled || typeof navigator === "undefined" || !("vibrate" in navigator)) return;
378
+ try {
379
+ navigator.vibrate(pattern);
380
+ } catch {
381
+ }
382
+ }
383
+ var PRESS_PATTERN = 8;
384
+
385
+ // src/surfaces.tsx
386
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
387
+ function cx(...parts) {
388
+ return parts.filter(Boolean).join(" ");
389
+ }
390
+ function assignRef(ref, node) {
391
+ if (typeof ref === "function") ref(node);
392
+ else if (ref) ref.current = node;
393
+ }
394
+ function buildVars(radius, borderWidth, halo, style) {
395
+ const vars = {
396
+ borderRadius: radius,
397
+ "--argent-bw": `${borderWidth}px`,
398
+ "--argent-radius": `${radius}px`,
399
+ ...style
400
+ };
401
+ if (halo) vars["--argent-halo"] = `${halo === true ? 8 : halo}px`;
402
+ return vars;
403
+ }
404
+ var Metal = forwardRef(function Metal2({
405
+ as,
406
+ tone = "silver",
407
+ variant = "border",
408
+ frame = "single",
409
+ tint = false,
410
+ borderWidth = 1.5,
411
+ revealOnHover = false,
412
+ radius = 14,
413
+ speed,
414
+ metalScale,
415
+ engine,
416
+ finish,
417
+ effect,
418
+ angle,
419
+ sheen = false,
420
+ halo = false,
421
+ className,
422
+ style,
423
+ children,
424
+ ...rest
425
+ }, ref) {
426
+ const Tag = as ?? "div";
427
+ const border = variant === "border";
428
+ return /* @__PURE__ */ jsxs(
429
+ Tag,
430
+ {
431
+ ref: (node) => assignRef(ref, node),
432
+ className: cx(
433
+ "argent",
434
+ border ? "argent--border" : "argent--fill",
435
+ border && frame === "double" && "argent--double",
436
+ border && tint && "argent--tint",
437
+ revealOnHover && "argent--reveal",
438
+ sheen && "argent--sheen",
439
+ !!halo && "argent--halo",
440
+ className
441
+ ),
442
+ "data-tone": tone,
443
+ style: buildVars(radius, borderWidth, halo, style),
444
+ ...rest,
445
+ children: [
446
+ /* @__PURE__ */ jsx2("span", { className: "argent-fill", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(MetalFill, { tone, speed, scale: metalScale, engine, finish, effect, angle }) }),
447
+ border && /* @__PURE__ */ jsx2("span", { className: "argent-core", "aria-hidden": "true" }),
448
+ sheen && /* @__PURE__ */ jsx2("span", { className: "argent-sheen", "aria-hidden": "true" }),
449
+ /* @__PURE__ */ jsx2("span", { className: "argent-content", children })
450
+ ]
451
+ }
452
+ );
453
+ });
454
+ var MetalCard = forwardRef(function MetalCard2({ className, radius = 18, ...rest }, ref) {
455
+ return /* @__PURE__ */ jsx2(Metal, { ref, radius, className: cx("argent-card", className), ...rest });
456
+ });
457
+ var SIZE_RADIUS = { sm: 11, md: 13, lg: 15 };
458
+ var MetalButton = forwardRef(function MetalButton2({ tone = "silver", size = "md", variant = "border", radius, borderWidth = 1.5, revealOnHover = true, haptics = true, speed, engine, finish = "button", effect, angle, halo = false, type = "button", className, children, style, onPointerDown, ...rest }, ref) {
459
+ const border = variant === "border";
460
+ return /* @__PURE__ */ jsxs(
461
+ "button",
462
+ {
463
+ ref,
464
+ type,
465
+ onPointerDown: (e) => {
466
+ if (haptics) vibrate(PRESS_PATTERN);
467
+ onPointerDown?.(e);
468
+ },
469
+ className: cx("argent", border ? "argent--border" : "argent--fill", border && revealOnHover && "argent--reveal", "argent--sheen", !!halo && "argent--halo", "argent-btn", `argent-btn--${size}`, className),
470
+ "data-tone": tone,
471
+ style: buildVars(radius ?? SIZE_RADIUS[size], borderWidth, halo, style),
472
+ ...rest,
473
+ children: [
474
+ /* @__PURE__ */ jsx2("span", { className: "argent-fill", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(MetalFill, { tone, speed, engine, finish, effect, angle }) }),
475
+ border && /* @__PURE__ */ jsx2("span", { className: "argent-core", "aria-hidden": "true" }),
476
+ /* @__PURE__ */ jsx2("span", { className: "argent-sheen", "aria-hidden": "true" }),
477
+ /* @__PURE__ */ jsx2("span", { className: "argent-content argent-btn-label", children })
478
+ ]
479
+ }
480
+ );
481
+ });
482
+
483
+ // src/controls.tsx
484
+ import { forwardRef as forwardRef2, useState as useState2 } from "react";
485
+ import { jsx as jsx3 } from "react/jsx-runtime";
486
+ var MetalToggle = forwardRef2(function MetalToggle2({ tone = "silver", checked, defaultChecked = false, onCheckedChange, haptics = true, className, onClick, ...rest }, ref) {
487
+ const [internal, setInternal] = useState2(defaultChecked);
488
+ const isOn = checked ?? internal;
489
+ return /* @__PURE__ */ jsx3(
490
+ "button",
491
+ {
492
+ ref,
493
+ type: "button",
494
+ role: "switch",
495
+ "aria-checked": isOn,
496
+ "data-tone": tone,
497
+ "data-checked": isOn || void 0,
498
+ className: ["argent-toggle", className].filter(Boolean).join(" "),
499
+ onClick: (e) => {
500
+ if (haptics) vibrate(PRESS_PATTERN);
501
+ if (checked === void 0) setInternal(!isOn);
502
+ onCheckedChange?.(!isOn);
503
+ onClick?.(e);
504
+ },
505
+ ...rest,
506
+ children: /* @__PURE__ */ jsx3("span", { className: "argent-toggle-thumb", "aria-hidden": "true", children: /* @__PURE__ */ jsx3(MetalFill, { tone, finish: "orb" }) })
507
+ }
508
+ );
509
+ });
510
+ var MetalProgress = forwardRef2(function MetalProgress2({ tone = "silver", value, height = 10, className, style, ...rest }, ref) {
511
+ const indeterminate = value === void 0;
512
+ const clamped = indeterminate ? 0 : Math.max(0, Math.min(100, value));
513
+ return /* @__PURE__ */ jsx3(
514
+ "div",
515
+ {
516
+ ref,
517
+ role: "progressbar",
518
+ "aria-valuemin": 0,
519
+ "aria-valuemax": 100,
520
+ "aria-valuenow": indeterminate ? void 0 : clamped,
521
+ "data-tone": tone,
522
+ className: ["argent-progress", indeterminate && "argent-progress--indeterminate", className].filter(Boolean).join(" "),
523
+ style: { height, ...style },
524
+ ...rest,
525
+ children: /* @__PURE__ */ jsx3(
526
+ "span",
527
+ {
528
+ className: "argent-progress-fill",
529
+ style: indeterminate ? void 0 : { width: `${clamped}%` },
530
+ "aria-hidden": "true",
531
+ children: /* @__PURE__ */ jsx3(MetalFill, { tone, finish: "bar" })
532
+ }
533
+ )
534
+ }
535
+ );
536
+ });
537
+ var MetalBadge = forwardRef2(function MetalBadge2({ className, radius = 999, borderWidth = 1, finish = "rim", ...rest }, ref) {
538
+ return /* @__PURE__ */ jsx3(
539
+ Metal,
540
+ {
541
+ ref,
542
+ as: "span",
543
+ variant: "border",
544
+ radius,
545
+ borderWidth,
546
+ finish,
547
+ className: ["argent-badge", className].filter(Boolean).join(" "),
548
+ ...rest
549
+ }
550
+ );
551
+ });
552
+
553
+ // src/text.tsx
554
+ import { forwardRef as forwardRef3, useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
555
+ import { LiquidMetal as LiquidMetal2 } from "@paper-design/shaders-react";
556
+ import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
557
+ var DEFAULT_STACK = "-apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif";
558
+ function svgOpen(g) {
559
+ const style = g.fontCss ? `<style>${g.fontCss}</style>` : "";
560
+ return `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 ${g.w} ${g.h}' width='${g.w}' height='${g.h}'>${style}`;
561
+ }
562
+ function svgText(g, attrs) {
563
+ const safe = g.text.replace(/&/g, "&amp;").replace(/</g, "&lt;");
564
+ return `<text x='50%' y='${Math.round(g.fontSize * 1)}' font-size='${g.fontSize}' font-family="${g.fontFamily.replace(/"/g, "'")}" font-weight='${g.fontWeight}' text-anchor='middle' ${attrs}>${safe}</text>`;
565
+ }
566
+ function encode(svg) {
567
+ return `data:image/svg+xml,${encodeURIComponent(svg)}`;
568
+ }
569
+ function fillSilhouette(g) {
570
+ return encode(`${svgOpen(g)}${svgText(g, "fill='#000'")}</svg>`);
571
+ }
572
+ function outlineSilhouette(g, ow) {
573
+ return encode(`${svgOpen(g)}${svgText(g, `fill='none' stroke='#000' stroke-width='${ow}' stroke-linejoin='round'`)}</svg>`);
574
+ }
575
+ function interior(g, fill, gradient) {
576
+ if (!gradient) return encode(`${svgOpen(g)}${svgText(g, `fill='${fill}'`)}</svg>`);
577
+ const defs = `<defs><linearGradient id='g' x1='0' y1='0' x2='0' y2='1'><stop offset='0' stop-color='${gradient[0]}'/><stop offset='1' stop-color='${gradient[1]}'/></linearGradient></defs>`;
578
+ return encode(`${svgOpen(g)}${defs}${svgText(g, "fill='url(#g)'")}</svg>`);
579
+ }
580
+ var LAYER = { position: "absolute", inset: 0, width: "100%", height: "100%" };
581
+ function ShaderText({
582
+ text,
583
+ tone,
584
+ variant,
585
+ fill,
586
+ fillGradient,
587
+ outlineWidth,
588
+ fontSize,
589
+ fontWeight,
590
+ fontFamily,
591
+ fontCss,
592
+ speed,
593
+ shimmer,
594
+ className,
595
+ style,
596
+ ...rest
597
+ }) {
598
+ const ref = useRef2(null);
599
+ const mounted = useMounted();
600
+ const inView = useInView(ref);
601
+ const reduced = useReducedMotion();
602
+ const turn = useStaggeredMount();
603
+ const [geom, setGeom] = useState3(null);
604
+ const outlined = variant === "outline";
605
+ const ow = outlineWidth ?? Math.max(2, Math.round(fontSize * 0.05));
606
+ useEffect2(() => {
607
+ let alive = true;
608
+ const fontSpec = `${fontWeight} ${fontSize}px ${fontFamily}`;
609
+ const measure = () => {
610
+ if (!alive) return;
611
+ const ctx = document.createElement("canvas").getContext("2d");
612
+ if (!ctx) return;
613
+ ctx.font = fontSpec;
614
+ const m = ctx.measureText(text);
615
+ setGeom({
616
+ text,
617
+ w: Math.ceil(m.width + fontSize * 0.24 + ow * 2),
618
+ h: Math.ceil(fontSize * 1.3 + ow),
619
+ fontSize,
620
+ fontWeight,
621
+ fontFamily,
622
+ fontCss
623
+ });
624
+ };
625
+ if (document.fonts?.load) document.fonts.load(fontSpec, text).then(measure, measure);
626
+ else measure();
627
+ return () => {
628
+ alive = false;
629
+ };
630
+ }, [text, fontSize, fontWeight, fontFamily, fontCss, ow]);
631
+ const ready = mounted && inView && turn && geom;
632
+ const { colorBack: _drop, ...params } = TONE_PARAMS[tone];
633
+ const shaderParams = outlined ? { ...params, contour: 0.2, distortion: Math.min(params.distortion ?? 0.1, 0.08) } : params;
634
+ return /* @__PURE__ */ jsx4(
635
+ "span",
636
+ {
637
+ ref,
638
+ role: "img",
639
+ "aria-label": text,
640
+ className,
641
+ style: {
642
+ position: "relative",
643
+ display: "inline-block",
644
+ verticalAlign: "middle",
645
+ width: geom?.w,
646
+ height: geom?.h,
647
+ ...style
648
+ },
649
+ ...rest,
650
+ children: ready ? /* @__PURE__ */ jsxs2(Fragment, { children: [
651
+ outlined && /* @__PURE__ */ jsx4("img", { src: interior(geom, fill, fillGradient), alt: "", "aria-hidden": "true", style: LAYER }),
652
+ /* @__PURE__ */ jsx4(
653
+ LiquidMetal2,
654
+ {
655
+ image: outlined ? outlineSilhouette(geom, ow) : fillSilhouette(geom),
656
+ suspendWhenProcessingImage: false,
657
+ colorBack: "#00000000",
658
+ fit: "contain",
659
+ scale: 0.97,
660
+ speed: reduced ? 0 : speed,
661
+ ...shaderParams,
662
+ style: LAYER
663
+ },
664
+ `${geom.text}|${geom.fontFamily}|${geom.w}|${outlined ? "o" : "f"}`
665
+ )
666
+ ] }) : (
667
+ // CSS chrome stands in until the shader is ready (and during SSR)
668
+ /* @__PURE__ */ jsx4(
669
+ "span",
670
+ {
671
+ "aria-hidden": "true",
672
+ className: ["argent-text", shimmer && !outlined && "argent-text--shimmer"].filter(Boolean).join(" "),
673
+ "data-tone": tone,
674
+ style: {
675
+ fontSize,
676
+ fontWeight,
677
+ fontFamily,
678
+ lineHeight: 1.3,
679
+ whiteSpace: "nowrap",
680
+ ...outlined && {
681
+ background: "none",
682
+ WebkitTextFillColor: fillGradient?.[0] ?? fill,
683
+ color: fillGradient?.[0] ?? fill,
684
+ WebkitTextStroke: "1px rgba(220, 224, 230, 0.55)"
685
+ }
686
+ },
687
+ children: text
688
+ }
689
+ )
690
+ )
691
+ }
692
+ );
693
+ }
694
+ var MetalText = forwardRef3(function MetalText2({
695
+ as,
696
+ tone = "silver",
697
+ shimmer = true,
698
+ shader = false,
699
+ variant = "fill",
700
+ fill = "#101114",
701
+ fillGradient,
702
+ outlineWidth,
703
+ fontSize = 64,
704
+ fontWeight = 800,
705
+ fontFamily = DEFAULT_STACK,
706
+ fontCss,
707
+ speed = 1,
708
+ className,
709
+ children,
710
+ ...rest
711
+ }, ref) {
712
+ if (shader) {
713
+ return /* @__PURE__ */ jsx4(
714
+ ShaderText,
715
+ {
716
+ text: String(children),
717
+ tone,
718
+ variant,
719
+ fill,
720
+ fillGradient,
721
+ outlineWidth,
722
+ fontSize,
723
+ fontWeight,
724
+ fontFamily,
725
+ fontCss,
726
+ speed,
727
+ shimmer,
728
+ className,
729
+ ...rest
730
+ }
731
+ );
732
+ }
733
+ const Tag = as ?? "span";
734
+ return /* @__PURE__ */ jsx4(
735
+ Tag,
736
+ {
737
+ ref,
738
+ className: ["argent-text", shimmer && "argent-text--shimmer", className].filter(Boolean).join(" "),
739
+ "data-tone": tone,
740
+ ...rest,
741
+ children
742
+ }
743
+ );
744
+ });
745
+
746
+ // src/logo.tsx
747
+ import { useRef as useRef3 } from "react";
748
+ import { LiquidMetal as LiquidMetal3 } from "@paper-design/shaders-react";
749
+ import { jsx as jsx5 } from "react/jsx-runtime";
750
+ function MetalLogo({ src, tone = "silver", size = 160, width, height, speed = 1, style, ...rest }) {
751
+ const ref = useRef3(null);
752
+ const mounted = useMounted();
753
+ const inView = useInView(ref);
754
+ const reduced = useReducedMotion();
755
+ const turn = useStaggeredMount();
756
+ const w = width ?? size;
757
+ const h = height ?? size;
758
+ const { colorBack: _drop, ...params } = TONE_PARAMS[tone];
759
+ return /* @__PURE__ */ jsx5("div", { ref, style: { position: "relative", width: w, height: h, ...style }, ...rest, children: mounted && inView && turn && /* @__PURE__ */ jsx5(
760
+ LiquidMetal3,
761
+ {
762
+ image: src,
763
+ suspendWhenProcessingImage: false,
764
+ colorBack: "#00000000",
765
+ fit: "contain",
766
+ scale: 0.92,
767
+ speed: reduced ? 0 : speed,
768
+ ...params,
769
+ style: { position: "absolute", inset: 0, width: "100%", height: "100%" }
770
+ },
771
+ src
772
+ ) });
773
+ }
774
+ export {
775
+ EFFECTS,
776
+ FINISHES,
777
+ Metal,
778
+ MetalBadge,
779
+ MetalButton,
780
+ MetalCard,
781
+ MetalFill,
782
+ MetalLogo,
783
+ MetalProgress,
784
+ MetalText,
785
+ MetalToggle,
786
+ NATIVE_TONES,
787
+ TONE_PARAMS,
788
+ mountMetal,
789
+ setHaptics,
790
+ useInView,
791
+ useMounted,
792
+ useReducedMotion
793
+ };
794
+ //# sourceMappingURL=index.js.map