weasel-icons 0.1.0 → 0.2.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.
Files changed (104) hide show
  1. package/README.md +13 -4
  2. package/THIRD_PARTY_NOTICES.md +14 -4
  3. package/dist/icons/alert/alert-icon.d.ts +6 -0
  4. package/dist/icons/alert/alert-icon.js +22 -0
  5. package/dist/icons/alert/alert-rig.d.ts +12 -0
  6. package/dist/icons/alert/alert-rig.js +25 -0
  7. package/dist/icons/badge-check/badge-check-icon.d.ts +5 -0
  8. package/dist/icons/badge-check/badge-check-icon.js +33 -0
  9. package/dist/icons/badge-check/badge-check-rig.d.ts +14 -0
  10. package/dist/icons/badge-check/badge-check-rig.js +36 -0
  11. package/dist/icons/bell/bell-icon.js +1 -1
  12. package/dist/icons/bulb/bulb-icon.d.ts +5 -0
  13. package/dist/icons/bulb/bulb-icon.js +25 -0
  14. package/dist/icons/bulb/bulb-rig.d.ts +13 -0
  15. package/dist/icons/bulb/bulb-rig.js +37 -0
  16. package/dist/icons/check/check-icon.d.ts +5 -0
  17. package/dist/icons/check/check-icon.js +26 -0
  18. package/dist/icons/check/check-rig.d.ts +6 -0
  19. package/dist/icons/check/check-rig.js +38 -0
  20. package/dist/icons/clapperboard/clapperboard-icon.d.ts +5 -0
  21. package/dist/icons/clapperboard/clapperboard-icon.js +18 -0
  22. package/dist/icons/clapperboard/clapperboard-rig.d.ts +12 -0
  23. package/dist/icons/clapperboard/clapperboard-rig.js +25 -0
  24. package/dist/icons/close/close-icon.d.ts +5 -0
  25. package/dist/icons/close/close-icon.js +27 -0
  26. package/dist/icons/close/close-rig.d.ts +10 -0
  27. package/dist/icons/close/close-rig.js +31 -0
  28. package/dist/icons/copy/copy-icon.d.ts +5 -0
  29. package/dist/icons/copy/copy-icon.js +31 -0
  30. package/dist/icons/copy/copy-rig.d.ts +13 -0
  31. package/dist/icons/copy/copy-rig.js +44 -0
  32. package/dist/icons/cursor/cursor-icon.d.ts +5 -0
  33. package/dist/icons/cursor/cursor-icon.js +32 -0
  34. package/dist/icons/cursor/cursor-rig.d.ts +21 -0
  35. package/dist/icons/cursor/cursor-rig.js +63 -0
  36. package/dist/icons/delete/delete-icon.d.ts +5 -0
  37. package/dist/icons/delete/delete-icon.js +25 -0
  38. package/dist/icons/delete/delete-rig.d.ts +15 -0
  39. package/dist/icons/delete/delete-rig.js +33 -0
  40. package/dist/icons/dog/dog-icon.js +1 -1
  41. package/dist/icons/download/download-icon.js +1 -1
  42. package/dist/icons/eye/eye-icon.d.ts +5 -0
  43. package/dist/icons/eye/eye-icon.js +27 -0
  44. package/dist/icons/eye/eye-rig.d.ts +9 -0
  45. package/dist/icons/eye/eye-rig.js +37 -0
  46. package/dist/icons/heart/heart-icon.js +1 -1
  47. package/dist/icons/heart/heart-rig.js +1 -1
  48. package/dist/icons/link/link-icon.d.ts +5 -0
  49. package/dist/icons/link/link-icon.js +30 -0
  50. package/dist/icons/link/link-rig.d.ts +13 -0
  51. package/dist/icons/link/link-rig.js +37 -0
  52. package/dist/icons/monkey/monkey-icon.js +1 -1
  53. package/dist/icons/plug/plug-icon.d.ts +5 -0
  54. package/dist/icons/plug/plug-icon.js +21 -0
  55. package/dist/icons/plug/plug-rig.d.ts +16 -0
  56. package/dist/icons/plug/plug-rig.js +29 -0
  57. package/dist/icons/refresh/refresh-icon.d.ts +5 -0
  58. package/dist/icons/refresh/refresh-icon.js +40 -0
  59. package/dist/icons/refresh/refresh-rig.d.ts +11 -0
  60. package/dist/icons/refresh/refresh-rig.js +30 -0
  61. package/dist/icons/replace/replace-icon.d.ts +5 -0
  62. package/dist/icons/replace/replace-icon.js +25 -0
  63. package/dist/icons/replace/replace-rig.d.ts +14 -0
  64. package/dist/icons/replace/replace-rig.js +24 -0
  65. package/dist/icons/rocket/rocket-icon.js +1 -1
  66. package/dist/icons/search/search-icon.js +1 -1
  67. package/dist/icons/shield/shield-icon.d.ts +5 -0
  68. package/dist/icons/shield/shield-icon.js +25 -0
  69. package/dist/icons/shield/shield-rig.d.ts +23 -0
  70. package/dist/icons/shield/shield-rig.js +42 -0
  71. package/dist/icons/ship/ship-icon.js +1 -1
  72. package/dist/icons/sparkle/sparkle-icon.d.ts +5 -0
  73. package/dist/icons/sparkle/sparkle-icon.js +27 -0
  74. package/dist/icons/sparkle/sparkle-rig.d.ts +8 -0
  75. package/dist/icons/sparkle/sparkle-rig.js +6 -0
  76. package/dist/icons/sparkles/sparkles-icon.d.ts +5 -0
  77. package/dist/icons/sparkles/sparkles-icon.js +27 -0
  78. package/dist/icons/sparkles/sparkles-rig.d.ts +14 -0
  79. package/dist/icons/sparkles/sparkles-rig.js +24 -0
  80. package/dist/icons/star/star-icon.d.ts +5 -0
  81. package/dist/icons/star/star-icon.js +38 -0
  82. package/dist/icons/star/star-rig.d.ts +20 -0
  83. package/dist/icons/star/star-rig.js +61 -0
  84. package/dist/icons/swap/swap-icon.d.ts +5 -0
  85. package/dist/icons/swap/swap-icon.js +26 -0
  86. package/dist/icons/swap/swap-rig.d.ts +10 -0
  87. package/dist/icons/swap/swap-rig.js +24 -0
  88. package/dist/icons/wave/wave-icon.d.ts +5 -0
  89. package/dist/icons/wave/wave-icon.js +18 -0
  90. package/dist/icons/wave/wave-rig.d.ts +8 -0
  91. package/dist/icons/wave/wave-rig.js +39 -0
  92. package/dist/index.d.ts +21 -2
  93. package/dist/index.js +20 -1
  94. package/dist/shared/icon-colors.d.ts +23 -4
  95. package/dist/shared/icon-types.d.ts +1 -0
  96. package/dist/shared/sparkle-motion.d.ts +12 -0
  97. package/dist/shared/sparkle-motion.js +38 -0
  98. package/dist/shared/use-icon-motion.d.ts +4 -0
  99. package/dist/shared/use-icon-motion.js +26 -0
  100. package/package.json +104 -9
  101. package/dist/icons/leaf/leaf-icon.d.ts +0 -6
  102. package/dist/icons/leaf/leaf-icon.js +0 -29
  103. package/dist/icons/leaf/leaf-rig.d.ts +0 -25
  104. package/dist/icons/leaf/leaf-rig.js +0 -201
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { memo, useRef, useCallback } from 'react';
4
+ import { motion, useTransform } from 'motion/react';
5
+ import { usePoseWeight, useBehaviorClock, useActionProgress } from "../../shared/use-icon-motion.js";
6
+ import { useMotionPreference } from "../../shared/use-motion-preference.js";
7
+ import { COPY_BACK, COPY_FRONT, sampleCopy } from "./copy-rig.js";
8
+ export { copyStates } from "./copy-rig.js";
9
+ const ORIGIN = { originX: '0px', originY: '0px', transformBox: 'view-box' };
10
+ export const CopyIcon = memo(function CopyIcon({ state = 'ready', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, }) {
11
+ const reduced = useMotionPreference(), element = useRef(null);
12
+ const pace = Number.isFinite(speed) ? Math.min(4, Math.max(0.1, speed)) : 1;
13
+ const w0 = usePoseWeight(state === 'ready', pace, reduced);
14
+ const w1 = usePoseWeight(state === 'copying', pace, reduced);
15
+ const w2 = usePoseWeight(state === 'copied', pace, reduced);
16
+ const w3 = usePoseWeight(state === 'failed', pace, reduced);
17
+ const performs = alive && !reduced && (state === 'copying' || state === 'failed');
18
+ const life = usePoseWeight(performs, pace, reduced), time = useBehaviorClock(element, performs, pace, life);
19
+ const can0 = useCallback(() => w1.get() < 0.001, [w1]);
20
+ const e0 = useActionProgress(state === 'copying', pace, reduced, 1.05, can0);
21
+ const can1 = useCallback(() => w2.get() < 0.001, [w2]);
22
+ const e1 = useActionProgress(state === 'copied', pace, reduced, 0.8, can1);
23
+ const can2 = useCallback(() => w3.get() < 0.001, [w3]);
24
+ const e2 = useActionProgress(state === 'failed', pace, reduced, 0.9, can2);
25
+ const rig = useTransform(() => sampleCopy([w0.get(), w1.get(), w2.get(), w3.get()], time.get(), life.get(), e0.get(), e1.get(), e2.get()));
26
+ const front = useTransform(rig, (r) => r.front);
27
+ const check = useTransform(rig, (r) => r.check);
28
+ const checkOffset = useTransform(rig, (r) => r.checkOffset);
29
+ const failure = useTransform(rig, (r) => r.failure);
30
+ return (_jsx("svg", { ref: element, className: className, width: size, height: size, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", fill: "none", stroke: color, strokeWidth: strokeWidth, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", pointerEvents: "none", style: { display: 'block' }, children: _jsxs("g", { "data-layer": "copy-body", transform: "translate(12 12) scale(.84) translate(-12 -12)", children: [_jsx("path", { "data-layer": "copy-back", "data-part": "back", d: COPY_BACK }), _jsxs(motion.g, { "data-layer": "copy-front", transform: front, style: ORIGIN, children: [_jsx("path", { "data-part": "front", d: COPY_FRONT }), _jsx(motion.path, { "data-layer": "copy-check", d: "M11.5 15L14 17.5L18.5 13", opacity: check, stroke: accentColor, strokeWidth: Math.min(strokeWidth * 0.8, 1.5), strokeDasharray: "9.91", strokeDashoffset: checkOffset }), _jsx(motion.path, { "data-layer": "copy-failure", d: "M12.5 12.5L17.5 17.5M17.5 12.5L12.5 17.5", opacity: failure, strokeWidth: Math.min(strokeWidth * 0.8, 1.5) })] })] }) }));
31
+ });
@@ -0,0 +1,13 @@
1
+ export declare const copyStates: readonly ["ready", "copying", "copied", "failed"];
2
+ export type CopyState = (typeof copyStates)[number];
3
+ export declare const COPY_BACK = "M4 16Q2 16 2 14V4Q2 2 4 2H14Q16 2 16 4";
4
+ export declare const COPY_FRONT = "M10 8H20Q22 8 22 10V20Q22 22 20 22H10Q8 22 8 20V10Q8 8 10 8Z";
5
+ export declare function sampleCopy(weights: readonly number[], time?: number, alive?: number, entry?: number, successEntry?: number, failureEntry?: number): {
6
+ front: string;
7
+ scanY: number;
8
+ scanOpacity: number;
9
+ check: number;
10
+ checkOffset: number;
11
+ failure: number;
12
+ failureOffset: number;
13
+ };
@@ -0,0 +1,44 @@
1
+ import { behaviorBeat } from "../../shared/behavior-curves.js";
2
+ const clamp = (n) => Math.max(0, Math.min(1, Number.isFinite(n) ? n : 0));
3
+ const smooth = (n) => {
4
+ const q = clamp(n);
5
+ return q * q * q * (q * (q * 6 - 15) + 10);
6
+ };
7
+ const normalized = (values) => {
8
+ const w = values.map(clamp), sum = w.reduce((a, b) => a + b, 0) || 1;
9
+ return w.map((v) => v / sum);
10
+ };
11
+ export const copyStates = ['ready', 'copying', 'copied', 'failed'];
12
+ // Matching quadratic corners keep the two outlines registered at contact.
13
+ export const COPY_BACK = 'M4 16Q2 16 2 14V4Q2 2 4 2H14Q16 2 16 4';
14
+ export const COPY_FRONT = 'M10 8H20Q22 8 22 10V20Q22 22 20 22H10Q8 22 8 20V10Q8 8 10 8Z';
15
+ export function sampleCopy(weights, time = 0, alive = 0, entry = 1, successEntry = 1, failureEntry = 1) {
16
+ const [, copying, copied, failed] = normalized(weights), life = clamp(alive);
17
+ const register = (s) => behaviorBeat(s, 0.04, 0.24, 0.1, 0.48);
18
+ const press = copying * ((1 - life) * register(clamp(entry) * 1.25) + life * register(time % 2.6));
19
+ const attempt = (s) => behaviorBeat(s, 0.03, 0.15, 0.04, 0.15);
20
+ const reject = (s) => behaviorBeat(s, 0.23, 0.1, 0.01, 0.42);
21
+ const f = clamp(failureEntry) * 1.15, loop = time % 3.1;
22
+ const jam = failed * ((1 - life) * attempt(f) + life * attempt(loop));
23
+ const kick = failed * ((1 - life) * reject(f) + life * reject(loop));
24
+ const dx = -copying * 0.35 - press * 5.65 - jam * 3.6 + kick * 1.4;
25
+ const dy = -copying * 0.35 - press * 5.65 - jam * 3.6 - kick * 0.7;
26
+ // Scan is an entry-only transfer cue. Its travel never depends on alive;
27
+ // repeated registration may continue, but toggles cannot rewind a visible scan.
28
+ const scanTime = clamp(entry) * 1.25;
29
+ return {
30
+ front: 'translate(' +
31
+ dx +
32
+ 'px, ' +
33
+ dy +
34
+ 'px) translate(15px, 15px) rotate(' +
35
+ kick * 6.5 +
36
+ 'deg) translate(-15px, -15px)',
37
+ scanY: 8 * smooth((scanTime - 0.18) / 0.44),
38
+ scanOpacity: copying * behaviorBeat(scanTime, 0.18, 0.08, 0.26, 0.1),
39
+ check: copied,
40
+ checkOffset: 9.91 * (1 - smooth((successEntry - 0.08) / 0.65)),
41
+ failure: failed,
42
+ failureOffset: Math.SQRT2 * 2.5 * (1 - smooth((failureEntry - 0.3) / 0.42)),
43
+ };
44
+ }
@@ -0,0 +1,5 @@
1
+ import type { StatefulIconProps } from "../../shared/icon-types.js";
2
+ import { type CursorState } from "./cursor-rig.js";
3
+ export { cursorStates, type CursorState } from "./cursor-rig.js";
4
+ export type CursorIconProps = StatefulIconProps<CursorState>;
5
+ export declare const CursorIcon: import("react").NamedExoticComponent<CursorIconProps>;
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { memo, useCallback, useEffect, useId, useRef } from 'react';
4
+ import { motion, useMotionValue, useTransform } from 'motion/react';
5
+ import { usePoseWeight, useBehaviorClock, useActionProgress } from "../../shared/use-icon-motion.js";
6
+ import { useMotionPreference } from "../../shared/use-motion-preference.js";
7
+ import { CURSOR_POINTER, cursorClickAction, sampleCursor } from "./cursor-rig.js";
8
+ export { cursorStates } from "./cursor-rig.js";
9
+ const ORIGIN = { originX: '0px', originY: '0px', transformBox: 'view-box' };
10
+ export const CursorIcon = memo(function CursorIcon({ state = 'idle', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, }) {
11
+ const reduced = useMotionPreference(), element = useRef(null), id = useId();
12
+ const pace = Number.isFinite(speed) ? Math.min(4, Math.max(0.1, speed)) : 1;
13
+ const idle = usePoseWeight(state === 'idle', pace, reduced), hovering = usePoseWeight(state === 'hovering', pace, reduced), clicking = usePoseWeight(state === 'clicking', pace, reduced), blocked = usePoseWeight(state === 'blocked', pace, reduced);
14
+ const performs = alive && !reduced && state !== 'idle', life = usePoseWeight(performs, pace, reduced), time = useBehaviorClock(element, performs, pace, life);
15
+ const clickEpoch = useMotionValue(0);
16
+ const canRestart = useCallback(() => {
17
+ const phase = (time.get() - clickEpoch.get()) % 6.8;
18
+ const repeat = phase < 3.1 ? 1 : Math.min(1, (phase - 3.1) / 0.9);
19
+ const reaction = cursorClickAction(repeat);
20
+ return clicking.get() * life.get() * (reaction.press + reaction.opacity) === 0;
21
+ }, [time, clickEpoch, clicking, life]);
22
+ const entry = useActionProgress(state === 'clicking', pace, reduced, 0.9, canRestart);
23
+ useEffect(() => {
24
+ // Give a fresh entry its own quiet interval without resetting the shared clock
25
+ // or restarting an already visible, interrupted click.
26
+ if (state === 'clicking' && entry.get() === 0)
27
+ clickEpoch.set(time.get());
28
+ }, [state, entry, clickEpoch, time]);
29
+ const rig = useTransform(() => sampleCursor([idle.get(), hovering.get(), clicking.get(), blocked.get()], time.get(), life.get(), entry.get(), time.get() - clickEpoch.get(), strokeWidth));
30
+ const pointer = useTransform(rig, (r) => r.pointer), click = useTransform(rig, (r) => r.click), blockedOpacity = useTransform(rig, (r) => r.blocked), tipCut = useTransform(rig, (r) => r.tipCut), badgeRadius = useTransform(rig, (r) => r.badgeRadius), badgeStroke = useTransform(rig, (r) => r.badgeStroke), blockedSlash = useTransform(rig, (r) => r.blockedSlash), rays = useTransform(rig, (r) => r.rays);
31
+ return (_jsxs("svg", { ref: element, className: className, width: size, height: size, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", pointerEvents: "none", style: { display: 'block', overflow: 'visible' }, children: [_jsx("defs", { children: _jsx("clipPath", { id: `${id}-tip-cut`, children: _jsx(motion.path, { "data-part": "cursor-tip-cut", d: tipCut, clipRule: "evenodd" }) }) }), _jsx(motion.path, { "data-layer": "cursor-click", d: rays, stroke: accentColor, strokeWidth: strokeWidth * 0.45, opacity: click }), _jsx(motion.path, { "data-layer": "cursor-pointer", "data-part": "pointer", d: CURSOR_POINTER, stroke: color, strokeWidth: strokeWidth, transform: pointer, style: ORIGIN, clipPath: `url(#${id}-tip-cut)` }), _jsxs(motion.g, { "data-layer": "cursor-blocked", transform: pointer, style: ORIGIN, opacity: blockedOpacity, stroke: accentColor, strokeWidth: badgeStroke, children: [_jsx(motion.circle, { "data-part": "blocked-ring", cx: "4.2", cy: "4.2", r: badgeRadius }), _jsx(motion.path, { "data-part": "blocked-slash", d: blockedSlash })] })] }));
32
+ });
@@ -0,0 +1,21 @@
1
+ export declare const cursorStates: readonly ["idle", "hovering", "clicking", "blocked"];
2
+ export type CursorState = (typeof cursorStates)[number];
3
+ export declare const CURSOR_POINTER = "M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z";
4
+ export declare const CURSOR_RAY_ANGLES: readonly [180, 225, 270];
5
+ /** One press, followed by an outward burst. Reset distance only while invisible. */
6
+ export declare function cursorClickAction(progress: number): {
7
+ press: number;
8
+ opacity: number;
9
+ distance: number;
10
+ length: number;
11
+ };
12
+ export declare function sampleCursor(weights: readonly number[], time?: number, alive?: number, entry?: number, clickTime?: number, strokeWidth?: number): {
13
+ pointer: string;
14
+ rays: string;
15
+ click: number;
16
+ blocked: number;
17
+ badgeRadius: number;
18
+ badgeStroke: number;
19
+ blockedSlash: string;
20
+ tipCut: string;
21
+ };
@@ -0,0 +1,63 @@
1
+ import { behaviorBeat } from "../../shared/behavior-curves.js";
2
+ export const cursorStates = ['idle', 'hovering', 'clicking', 'blocked'];
3
+ export const CURSOR_POINTER = 'M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z';
4
+ export const CURSOR_RAY_ANGLES = [180, 225, 270];
5
+ const clamp = (n) => Math.max(0, Math.min(1, n));
6
+ const smooth = (n) => {
7
+ const x = clamp(n);
8
+ return x * x * x * (x * (x * 6 - 15) + 10);
9
+ };
10
+ /** One press, followed by an outward burst. Reset distance only while invisible. */
11
+ export function cursorClickAction(progress) {
12
+ const t = clamp(progress) * 0.9;
13
+ const travel = smooth((t - 0.22) / 0.5);
14
+ return {
15
+ press: behaviorBeat(t, 0, 0.18, 0.04, 0.3),
16
+ opacity: behaviorBeat(t, 0.22, 0.09, 0.02, 0.39),
17
+ distance: 2.7 + 0.5 * travel,
18
+ length: 1.05,
19
+ };
20
+ }
21
+ export function sampleCursor(weights, time = 0, alive = 0, entry = 1, clickTime = time, strokeWidth = 1.65) {
22
+ const [, h, c, b] = weights.map(clamp), life = clamp(alive), t = time % 6.8;
23
+ // Two continuous figure-eights per phrase: the whole pointer explores, never
24
+ // stops at a keyframe, and keeps the current phase when a state is interrupted.
25
+ const orbit = (t * Math.PI * 2) / 3.4;
26
+ const approach = behaviorBeat(t, 0.2, 0.38, 0.08, 0.28) + 0.65 * behaviorBeat(t, 3.65, 0.44, 0.06, 0.3);
27
+ const recoil = behaviorBeat(t, 0.63, 0.22, 0.04, 0.66) + 0.65 * behaviorBeat(t, 4.12, 0.24, 0.04, 0.7);
28
+ // Entry owns the first click; living repeats use the existing uninterrupted clock.
29
+ const clickPhase = clickTime % 6.8;
30
+ const repeat = clickPhase < 3.1 ? 1 : clamp((clickPhase - 3.1) / 0.9);
31
+ const action = cursorClickAction(entry < 1 ? entry : repeat);
32
+ const amount = entry < 1 ? 1 : life;
33
+ const refusal = life * (-0.5 * approach + 0.42 * recoil);
34
+ // A brief entry hesitation also works with alive off, then becomes static.
35
+ const entryHesitation = 0.24 * 4 * b * (1 - b);
36
+ const dx = h * life * 1.25 * Math.sin(orbit) + b * (0.3 + refusal) + entryHesitation;
37
+ const dy = h * life * 0.75 * Math.sin(orbit * 2) + b * (0.3 + refusal) + entryHesitation;
38
+ const tipX = 6 + dx, tipY = 6 + dy;
39
+ const scale = 0.84 + h * 0.015 - b * 0.06 - c * (0.012 + amount * 0.065 * action.press);
40
+ const rays = CURSOR_RAY_ANGLES.map((angle) => {
41
+ const radians = (angle * Math.PI) / 180, x = Math.cos(radians), y = Math.sin(radians);
42
+ // Fade paint, not emitted travel: turning alive off must not pull rays back.
43
+ const distance = amount > 0 ? action.distance : 2.7;
44
+ const point = (r) => `${tipX + x * r} ${tipY + y * r}`;
45
+ return `M${point(distance)}L${point(distance + action.length)}`;
46
+ }).join('');
47
+ // The blocked symbol replaces the pointed apex. Clearance includes both
48
+ // painted half-strokes, so primary paint never shows through the badge.
49
+ const badgeRadius = b * 2.8;
50
+ const badgeStroke = b * strokeWidth * 0.75;
51
+ const cutRadius = badgeRadius + badgeStroke / 2 + b * 0.5 + strokeWidth * 0.5 * smooth(b / 0.08);
52
+ const diagonal = badgeRadius / Math.SQRT2;
53
+ return {
54
+ pointer: `translate(${tipX}px, ${tipY}px) scale(${scale}) translate(-4.2px, -4.2px)`,
55
+ rays,
56
+ click: c * amount * action.opacity,
57
+ blocked: b,
58
+ badgeRadius,
59
+ badgeStroke,
60
+ blockedSlash: `M${4.2 - diagonal} ${4.2 - diagonal}L${4.2 + diagonal} ${4.2 + diagonal}`,
61
+ tipCut: `M-24 -24H48V48H-24Z M${4.2 - cutRadius} 4.2a${cutRadius} ${cutRadius} 0 1 0 ${2 * cutRadius} 0a${cutRadius} ${cutRadius} 0 1 0 ${-2 * cutRadius} 0Z`,
62
+ };
63
+ }
@@ -0,0 +1,5 @@
1
+ import type { StatefulIconProps } from "../../shared/icon-types.js";
2
+ import { type DeleteState } from "./delete-rig.js";
3
+ export { deleteStates, type DeleteState } from "./delete-rig.js";
4
+ export type DeleteIconProps = StatefulIconProps<DeleteState>;
5
+ export declare const DeleteIcon: import("react").NamedExoticComponent<DeleteIconProps>;
@@ -0,0 +1,25 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { memo, useCallback, useRef, useId } from 'react';
4
+ import { motion, useTransform } from 'motion/react';
5
+ import { usePoseWeight, useBehaviorClock, useActionProgress } from "../../shared/use-icon-motion.js";
6
+ import { useMotionPreference } from "../../shared/use-motion-preference.js";
7
+ import { DELETE_SHELL, sampleDelete } from "./delete-rig.js";
8
+ export { deleteStates } from "./delete-rig.js";
9
+ const ORIGIN = { originX: '0px', originY: '0px', transformBox: 'view-box' };
10
+ export const DeleteIcon = memo(function DeleteIcon({ state = 'idle', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, }) {
11
+ const reduced = useMotionPreference(), element = useRef(null), id = useId();
12
+ const pace = Number.isFinite(speed) ? Math.min(4, Math.max(0.1, speed)) : 1;
13
+ const idle = usePoseWeight(state === 'idle', pace, reduced), deleting = usePoseWeight(state === 'deleting', pace, reduced), deleted = usePoseWeight(state === 'deleted', pace, reduced), error = usePoseWeight(state === 'error', pace, reduced);
14
+ const performs = alive && !reduced && state !== 'idle', life = usePoseWeight(performs, pace, reduced), time = useBehaviorClock(element, performs, pace, life);
15
+ const first = useRef(state !== 'deleting');
16
+ const canRestart = useCallback(() => {
17
+ const invisible = first.current || deleting.get() < 0.001;
18
+ first.current = false;
19
+ return invisible;
20
+ }, [deleting]);
21
+ const entry = useActionProgress(state === 'deleting', pace, reduced, 1.15, canRestart);
22
+ const rig = useTransform(() => sampleDelete([idle.get(), deleting.get(), deleted.get(), error.get()], time.get(), life.get(), entry.get()));
23
+ const body = useTransform(rig, (r) => r.body), mark = useTransform(rig, (r) => r.mark), reverse = useTransform(rig, (r) => r.reverse), offset = useTransform(rig, (r) => r.offset), markOpacity = useTransform(rig, (r) => r.markOpacity), cutX = useTransform(rig, (r) => r.cutX), erase = useTransform(rig, (r) => r.erase), eraseX = useTransform(rig, (r) => r.eraseX), echo = useTransform(rig, (r) => r.echo), echoOffset = useTransform(rig, (r) => r.echoOffset);
24
+ return (_jsxs("svg", { ref: element, className: className, width: size, height: size, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", pointerEvents: "none", style: { display: 'block', overflow: 'visible' }, children: [_jsxs("defs", { children: [_jsx("clipPath", { id: `${id}-inside`, children: _jsx("path", { d: DELETE_SHELL }) }), _jsx("clipPath", { id: `${id}-erase`, children: _jsx(motion.rect, { "data-part": "erase-window", x: "0", y: "6", width: cutX, height: "12" }) })] }), _jsxs(motion.g, { "data-layer": "delete-body", transform: body, style: ORIGIN, children: [_jsx("path", { "data-part": "shell", d: DELETE_SHELL, stroke: color, strokeWidth: strokeWidth }), _jsxs("g", { clipPath: `url(#${id}-inside)`, children: [_jsxs(motion.g, { "data-layer": "delete-symbol", style: { x: offset }, opacity: markOpacity, clipPath: `url(#${id}-erase)`, children: [_jsx(motion.path, { "data-layer": "delete-mark", d: mark, stroke: color, strokeWidth: Math.min(strokeWidth, 2.5) }), _jsx(motion.path, { "data-layer": "delete-reverse", d: "m18 9-6 6", opacity: reverse, stroke: color, strokeWidth: Math.min(strokeWidth, 2.5) })] }), _jsx(motion.path, { "data-layer": "delete-erase", d: "M0 8v8", style: { x: eraseX }, opacity: erase, stroke: accentColor, strokeWidth: Math.min(strokeWidth * 0.75, 1.4) })] }), _jsx(motion.path, { "data-layer": "delete-echo", d: "M0 10h-1.2M0 14h-1.2", style: { x: echoOffset }, opacity: echo, stroke: accentColor, strokeWidth: Math.min(strokeWidth * 0.65, 1.1) })] })] }));
25
+ });
@@ -0,0 +1,15 @@
1
+ export declare const deleteStates: readonly ["idle", "deleting", "deleted", "error"];
2
+ export type DeleteState = (typeof deleteStates)[number];
3
+ export declare const DELETE_SHELL = "M10 5a2 2 0 0 0-1.344.519l-6.328 5.74a1 1 0 0 0 0 1.481l6.328 5.741A2 2 0 0 0 10 19h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2z";
4
+ export declare function sampleDelete(weights: readonly number[], time?: number, alive?: number, entry?: number): {
5
+ body: string;
6
+ mark: string;
7
+ reverse: number;
8
+ markOpacity: number;
9
+ cutX: number;
10
+ offset: number;
11
+ erase: number;
12
+ eraseX: number;
13
+ echo: number;
14
+ echoOffset: number;
15
+ };
@@ -0,0 +1,33 @@
1
+ import { behaviorBeat } from "../../shared/behavior-curves.js";
2
+ export const deleteStates = ['idle', 'deleting', 'deleted', 'error'];
3
+ export const DELETE_SHELL = 'M10 5a2 2 0 0 0-1.344.519l-6.328 5.74a1 1 0 0 0 0 1.481l6.328 5.741A2 2 0 0 0 10 19h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2z';
4
+ export function sampleDelete(weights, time = 0, alive = 0, entry = 1) {
5
+ const clamp = (n) => Math.max(0, Math.min(1, n));
6
+ const smooth = (n) => {
7
+ const t = clamp(n);
8
+ return t * t * t * (t * (t * 6 - 15) + 10);
9
+ };
10
+ const [, deleting, deleted, error] = weights.map(clamp), life = clamp(alive);
11
+ const t = time % 6;
12
+ const action = clamp(entry) * 1.15;
13
+ const sweep = smooth((action - 0.22) / 0.68);
14
+ const repeating = smooth((t - 0.55) / 0.8) * (1 - smooth((t - 2.5) / 0.65));
15
+ const eraseProgress = sweep * (1 - life) + repeating * life;
16
+ const contact = Math.max(behaviorBeat(action, 0, 0.16, 0.05, 0.32), life * behaviorBeat(t, 0.25, 0.16, 0.05, 0.35));
17
+ const refusal = error * life * behaviorBeat(t, 0.4, 0.22, 0.06, 0.6);
18
+ const erased = smooth((t - 1.1) / 0.3) * (1 - smooth((t - 3.2) / 0.55));
19
+ const effect = deleting *
20
+ Math.max(behaviorBeat(action, 0.22, 0.1, 0.45, 0.22), life * behaviorBeat(t, 0.55, 0.1, 0.55, 0.25));
21
+ return {
22
+ body: `translate(${12 - deleting * contact * 0.7 + refusal * 0.6}px, 12px) scale(${0.82 - deleting * contact * 0.025}) translate(-12px, -12px)`,
23
+ mark: `M${12 - 0.5 * deleted} ${9 + 3 * deleted}L${15 - 1.5 * deleted} ${12 + 2 * deleted}L18 ${15 - 5 * deleted}`,
24
+ reverse: 1 - deleted,
25
+ markOpacity: 1 - deleting * life * erased,
26
+ cutX: 24 - deleting * (4 + eraseProgress * 10) - effect * 0.6,
27
+ offset: -deleting * eraseProgress * 0.4,
28
+ erase: effect,
29
+ eraseX: 20 - eraseProgress * 10,
30
+ echo: deleting * contact * 0.5,
31
+ echoOffset: -contact * 0.2,
32
+ };
33
+ }
@@ -9,7 +9,7 @@ export { dogStates } from "./dog-rig.js";
9
9
  // Motion emits CSS transforms. The -3/-3 viewBox origin is offset back to
10
10
  // SVG coordinate (0,0), so authored pivot coordinates remain exact.
11
11
  const SVG_ORIGIN = { originX: '3px', originY: '3px', transformBox: 'view-box' };
12
- export const DogIcon = memo(function DogIcon({ state = 'waiting', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.8, speed = 1, alive = false, className, colors, }) {
12
+ export const DogIcon = memo(function DogIcon({ state = 'waiting', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, colors, }) {
13
13
  const reduced = useMotionPreference();
14
14
  const element = useRef(null);
15
15
  const mouthId = `puppy-mouth-${useId().replace(/:/g, '')}`;
@@ -7,7 +7,7 @@ import { usePoseWeight, useBehaviorClock } from "../../shared/use-icon-motion.js
7
7
  import { sampleDownload, DOWNLOAD_TRAY } from "./download-rig.js";
8
8
  export { downloadStates } from "./download-rig.js";
9
9
  const ORIGIN = { originX: 0, originY: 0, transformBox: 'view-box' };
10
- export const DownloadIcon = memo(function DownloadIcon({ state = 'idle', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.8, speed = 1, alive = false, className, }) {
10
+ export const DownloadIcon = memo(function DownloadIcon({ state = 'idle', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, }) {
11
11
  const reduced = useMotionPreference(), element = useRef(null);
12
12
  const pace = Number.isFinite(speed) ? Math.min(4, Math.max(0.1, speed)) : 1;
13
13
  const idle = usePoseWeight(state === 'idle', pace, reduced), downloading = usePoseWeight(state === 'downloading', pace, reduced);
@@ -0,0 +1,5 @@
1
+ import type { StatefulIconProps } from "../../shared/icon-types.js";
2
+ import { type EyeState } from "./eye-rig.js";
3
+ export { eyeStates, type EyeState } from "./eye-rig.js";
4
+ export type EyeIconProps = StatefulIconProps<EyeState>;
5
+ export declare const EyeIcon: import("react").NamedExoticComponent<EyeIconProps>;
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { memo, useRef, useCallback, useId } from 'react';
4
+ import { motion, useTransform } from 'motion/react';
5
+ import { usePoseWeight, useBehaviorClock, useActionProgress } from "../../shared/use-icon-motion.js";
6
+ import { useMotionPreference } from "../../shared/use-motion-preference.js";
7
+ import { sampleEye } from "./eye-rig.js";
8
+ export { eyeStates } from "./eye-rig.js";
9
+ export const EyeIcon = memo(function EyeIcon({ state = 'visible', size = 24, color = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, }) {
10
+ const reduced = useMotionPreference(), element = useRef(null);
11
+ const clipId = 'eye-' + useId().replace(/:/g, '');
12
+ const pace = Number.isFinite(speed) ? Math.min(4, Math.max(0.1, speed)) : 1;
13
+ const w0 = usePoseWeight(state === 'visible', pace, reduced);
14
+ const w1 = usePoseWeight(state === 'looking', pace, reduced);
15
+ const w2 = usePoseWeight(state === 'hidden', pace, reduced);
16
+ const performs = alive && !reduced && state === 'looking';
17
+ const life = usePoseWeight(performs, pace, reduced), time = useBehaviorClock(element, performs, pace, life);
18
+ const can0 = useCallback(() => w1.get() < 0.001, [w1]);
19
+ const e0 = useActionProgress(state === 'looking', pace, reduced, 1.4, can0);
20
+ const rig = useTransform(() => sampleEye([w0.get(), w1.get(), w2.get()], time.get(), life.get(), e0.get()));
21
+ const lid = useTransform(rig, (r) => r.lid);
22
+ const irisX = useTransform(rig, (r) => r.irisX);
23
+ const irisY = useTransform(rig, (r) => r.irisY);
24
+ const lashes = useTransform(rig, (r) => r.lashes);
25
+ const lashOpacity = useTransform(rig, (r) => r.lashOpacity);
26
+ return (_jsx("svg", { ref: element, className: className, width: size, height: size, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", fill: "none", stroke: color, strokeWidth: strokeWidth, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", pointerEvents: "none", style: { display: 'block' }, children: _jsxs("g", { "data-layer": "eye-body", transform: "translate(12 12) scale(.84) translate(-12 -12)", children: [_jsx("defs", { children: _jsx("clipPath", { id: clipId, children: _jsx(motion.path, { "data-part": "eye-clip", d: lid }) }) }), _jsx(motion.path, { "data-layer": "eye-lid", d: lid }), _jsx("g", { "data-layer": "eye-iris", clipPath: 'url(#' + clipId + ')', children: _jsx(motion.circle, { "data-part": "iris", cx: irisX, cy: irisY, r: "2.7" }) }), _jsx(motion.path, { "data-layer": "eye-lashes", d: lashes, opacity: lashOpacity })] }) }));
27
+ });
@@ -0,0 +1,9 @@
1
+ export declare const eyeStates: readonly ["visible", "looking", "hidden"];
2
+ export type EyeState = (typeof eyeStates)[number];
3
+ export declare function sampleEye(weights: readonly number[], time?: number, alive?: number, entry?: number): {
4
+ lid: string;
5
+ irisX: number;
6
+ irisY: number;
7
+ lashes: string;
8
+ lashOpacity: number;
9
+ };
@@ -0,0 +1,37 @@
1
+ import { behaviorBeat } from "../../shared/behavior-curves.js";
2
+ const clamp = (n) => Math.max(0, Math.min(1, Number.isFinite(n) ? n : 0));
3
+ const normalized = (values) => {
4
+ const w = values.map(clamp), sum = w.reduce((a, b) => a + b, 0) || 1;
5
+ return w.map((v) => v / sum);
6
+ };
7
+ export const eyeStates = ['visible', 'looking', 'hidden'];
8
+ function lidPath(hidden) {
9
+ const end = 12 - hidden * 1.5, top = 3.4 + hidden * 14.1, bottom = 20.6 - hidden * 3.1;
10
+ return `M2.5 ${end}C6.5 ${top} 17.5 ${top} 21.5 ${end}C17.5 ${bottom} 6.5 ${bottom} 2.5 ${end}Z`;
11
+ }
12
+ function lashesPath(hidden) {
13
+ // Lower cubic landmarks at t=.2/.5/.8. Lashes stay attached.
14
+ const end = 12 - hidden * 1.5, bottom = 20.6 - hidden * 3.1;
15
+ const y = end * 0.52 + bottom * 0.48, middle = end * 0.25 + bottom * 0.75;
16
+ return `M5.62 ${y}l-1.3 1.7M12 ${middle}v2.1M18.38 ${y}l1.3 1.7`;
17
+ }
18
+ export function sampleEye(weights, time = 0, alive = 0, entry = 1) {
19
+ const [, looking, hidden] = normalized(weights), life = clamp(alive), t = time % 7.2;
20
+ const glance = (s) => behaviorBeat(s, 0.08, 0.25, 0.12, 0.4) - 0.75 * behaviorBeat(s, 0.9, 0.17, 0.02, 0.27);
21
+ const x = looking *
22
+ (1 +
23
+ (1 - life) * glance(clamp(entry) * 1.4) +
24
+ life *
25
+ (1.15 * behaviorBeat(t, 0.5, 0.45, 0.3, 0.6) - 2 * behaviorBeat(t, 2.4, 0.5, 0.3, 0.6)));
26
+ const y = looking * life * (-0.6 * behaviorBeat(t, 4.3, 0.45, 0.3, 0.7));
27
+ const blink = looking *
28
+ Math.max((1 - life) * behaviorBeat(clamp(entry) * 1.4, 0.76, 0.08, 0.01, 0.13), life * behaviorBeat(t, 5.7, 0.1, 0.02, 0.16));
29
+ const closed = hidden + (1 - hidden) * blink;
30
+ return {
31
+ lid: lidPath(closed),
32
+ irisX: 12 + x * (1 - hidden),
33
+ irisY: 12 + y * (1 - hidden),
34
+ lashes: lashesPath(closed),
35
+ lashOpacity: hidden,
36
+ };
37
+ }
@@ -8,7 +8,7 @@ import { HEART_PATH, HEART_LEFT_CLIP, HEART_RIGHT_CLIP, HEART_CRACK } from "./he
8
8
  import { sampleHeart } from "./heart-rig.js";
9
9
  export { heartStates } from "./heart-rig.js";
10
10
  const SVG_ORIGIN = { originX: 0, originY: 0, transformBox: 'view-box' };
11
- export const HeartIcon = memo(function HeartIcon({ state = 'unliked', size = 24, color = 'currentColor', accentColor = 'currentColor', colors, strokeWidth = 1.8, speed = 1, alive = false, className, }) {
11
+ export const HeartIcon = memo(function HeartIcon({ state = 'unliked', size = 24, color = 'currentColor', accentColor = 'currentColor', colors, strokeWidth = 1.65, speed = 1, alive = false, className, }) {
12
12
  const reduced = useMotionPreference();
13
13
  const element = useRef(null);
14
14
  const id = useId().replace(/:/g, '');
@@ -12,7 +12,7 @@ const smooth = (value) => {
12
12
  const t = Math.max(0, Math.min(1, value));
13
13
  return t * t * t * (t * (t * 6 - 15) + 10);
14
14
  };
15
- export function sampleHeart(weights, time = 0, alive = 0, strokeWidth = 1.8) {
15
+ export function sampleHeart(weights, time = 0, alive = 0, strokeWidth = 1.65) {
16
16
  const [unliked, loading, liked, error] = weights;
17
17
  const life = Math.max(0, Math.min(1, alive));
18
18
  const clock = Math.max(0, time);
@@ -0,0 +1,5 @@
1
+ import type { StatefulIconProps } from "../../shared/icon-types.js";
2
+ import { type LinkState } from "./link-rig.js";
3
+ export { linkStates, type LinkState } from "./link-rig.js";
4
+ export type LinkIconProps = StatefulIconProps<LinkState>;
5
+ export declare const LinkIcon: import("react").NamedExoticComponent<LinkIconProps>;
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { memo, useRef, useCallback } from 'react';
4
+ import { motion, useTransform } from 'motion/react';
5
+ import { usePoseWeight, useBehaviorClock, useActionProgress } from "../../shared/use-icon-motion.js";
6
+ import { useMotionPreference } from "../../shared/use-motion-preference.js";
7
+ import { LINK_LEFT, LINK_RIGHT, LINK_LEFT_JOIN, LINK_RIGHT_JOIN, LINK_JOIN_LENGTH, sampleLink, } from "./link-rig.js";
8
+ export { linkStates } from "./link-rig.js";
9
+ export const LinkIcon = memo(function LinkIcon({ state = 'disconnected', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, }) {
10
+ const reduced = useMotionPreference(), element = useRef(null);
11
+ const pace = Number.isFinite(speed) ? Math.min(4, Math.max(0.1, speed)) : 1;
12
+ const w0 = usePoseWeight(state === 'disconnected', pace, reduced);
13
+ const w1 = usePoseWeight(state === 'connecting', pace, reduced);
14
+ const w2 = usePoseWeight(state === 'connected', pace, reduced);
15
+ const w3 = usePoseWeight(state === 'failed', pace, reduced);
16
+ const performs = alive && !reduced && state !== 'disconnected';
17
+ const life = usePoseWeight(performs, pace, reduced), time = useBehaviorClock(element, performs, pace, life);
18
+ const can0 = useCallback(() => w1.get() < 0.001, [w1]);
19
+ const e0 = useActionProgress(state === 'connecting', pace, reduced, 1.1, can0);
20
+ const can1 = useCallback(() => w2.get() < 0.001, [w2]);
21
+ const e1 = useActionProgress(state === 'connected', pace, reduced, 0.9, can1);
22
+ const can2 = useCallback(() => w3.get() < 0.001, [w3]);
23
+ const e2 = useActionProgress(state === 'failed', pace, reduced, 1.05, can2);
24
+ const rig = useTransform(() => sampleLink([w0.get(), w1.get(), w2.get(), w3.get()], time.get(), life.get(), e0.get(), e1.get(), e2.get()));
25
+ const leftOffset = useTransform(rig, (r) => r.leftOffset);
26
+ const rightOffset = useTransform(rig, (r) => r.rightOffset);
27
+ const signalOffset = useTransform(rig, (r) => r.signalOffset);
28
+ const signalOpacity = useTransform(rig, (r) => r.signalOpacity);
29
+ return (_jsx("svg", { ref: element, className: className, width: size, height: size, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", fill: "none", stroke: color, strokeWidth: strokeWidth, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", pointerEvents: "none", style: { display: 'block' }, children: _jsxs("g", { "data-layer": "link-body", transform: "translate(12 12) scale(.84) translate(-12 -12)", children: [_jsx("path", { "data-layer": "link-left", "data-part": "left", d: LINK_LEFT }), _jsx("path", { "data-layer": "link-right", "data-part": "right", d: LINK_RIGHT, stroke: accentColor }), _jsx(motion.path, { "data-layer": "link-left-join", d: LINK_LEFT_JOIN, strokeDasharray: LINK_JOIN_LENGTH + ' ' + LINK_JOIN_LENGTH, strokeDashoffset: leftOffset }), _jsx(motion.path, { "data-layer": "link-right-join", d: LINK_RIGHT_JOIN, stroke: accentColor, strokeDasharray: LINK_JOIN_LENGTH + ' ' + LINK_JOIN_LENGTH, strokeDashoffset: rightOffset }), _jsx(motion.path, { "data-layer": "link-signal", d: LINK_LEFT_JOIN, stroke: accentColor, strokeWidth: Math.min(strokeWidth * 0.65, 1.3), strokeDasharray: "1.4 30", strokeDashoffset: signalOffset, opacity: signalOpacity })] }) }));
30
+ });
@@ -0,0 +1,13 @@
1
+ export declare const linkStates: readonly ["disconnected", "connecting", "connected", "failed"];
2
+ export type LinkState = (typeof linkStates)[number];
3
+ export declare const LINK_LEFT = "M6.46 10.46l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71";
4
+ export declare const LINK_RIGHT = "M17.54 13.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71";
5
+ export declare const LINK_LEFT_JOIN = "M6.46 10.46a5 5 0 0 1 7.54.54";
6
+ export declare const LINK_RIGHT_JOIN = "M17.54 13.54a5 5 0 0 1-7.54-.54";
7
+ export declare const LINK_JOIN_LENGTH: number;
8
+ export declare function sampleLink(weights: readonly number[], time?: number, alive?: number, entry?: number, successEntry?: number, failureEntry?: number): {
9
+ leftOffset: number;
10
+ rightOffset: number;
11
+ signalOffset: number;
12
+ signalOpacity: number;
13
+ };
@@ -0,0 +1,37 @@
1
+ import { behaviorBeat } from "../../shared/behavior-curves.js";
2
+ const clamp = (n) => Math.max(0, Math.min(1, Number.isFinite(n) ? n : 0));
3
+ const smooth = (n) => {
4
+ const q = clamp(n);
5
+ return q * q * q * (q * (q * 6 - 15) + 10);
6
+ };
7
+ const normalized = (values) => {
8
+ const w = values.map(clamp), sum = w.reduce((a, b) => a + b, 0) || 1;
9
+ return w.map((v) => v / sum);
10
+ };
11
+ export const linkStates = ['disconnected', 'connecting', 'connected', 'failed'];
12
+ // Lucide link contours, split at their inner joins. The outer silhouette is fixed.
13
+ export const LINK_LEFT = 'M6.46 10.46l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71';
14
+ export const LINK_RIGHT = 'M17.54 13.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71';
15
+ export const LINK_LEFT_JOIN = 'M6.46 10.46a5 5 0 0 1 7.54.54';
16
+ export const LINK_RIGHT_JOIN = 'M17.54 13.54a5 5 0 0 1-7.54-.54';
17
+ export const LINK_JOIN_LENGTH = 10 * Math.asin(Math.hypot(7.54, 0.54) / 10);
18
+ export function sampleLink(weights, time = 0, alive = 0, entry = 1, successEntry = 1, failureEntry = 1) {
19
+ const [, connecting, connected, failed] = normalized(weights), life = clamp(alive);
20
+ const attempt = (s, delay) => behaviorBeat(s, 0.03 + delay, 0.27, 0.06, 0.35);
21
+ const reach = (delay) => connecting *
22
+ ((1 - life) * attempt(clamp(entry) * 1.1, delay) + life * attempt(time % 2.6, delay));
23
+ const snap = (s) => behaviorBeat(s, 0.03, 0.14, 0.03, 0.4);
24
+ const strain = failed * ((1 - life) * snap(clamp(failureEntry) * 1.05) + life * 0.12 * snap(time % 3.1));
25
+ const locked = connected * smooth((successEntry - 0.02) / 0.5);
26
+ const leftAmount = clamp(locked + reach(0) * 0.68 + failed * 0.22 + strain * 0.58);
27
+ const rightAmount = clamp(locked + reach(0.19) * 0.62 + failed * 0.16 + strain * 0.5);
28
+ // A small data glint belongs to the lower join only. Position is independent
29
+ // of alive's fade, so it never reverses while disappearing.
30
+ const local = time % 3.2, progress = clamp((local - 0.4) / 0.8);
31
+ return {
32
+ leftOffset: LINK_JOIN_LENGTH * (1 - leftAmount),
33
+ rightOffset: LINK_JOIN_LENGTH * (1 - rightAmount),
34
+ signalOffset: 1.4 - progress * (LINK_JOIN_LENGTH + 2.8),
35
+ signalOpacity: clamp(connected * life * behaviorBeat(local, 0.4, 0.12, 0.5, 0.18)),
36
+ };
37
+ }
@@ -6,7 +6,7 @@ import { MONKEY_HEAD, MONKEY_MUZZLE, sampleMonkey } from "./monkey-rig.js";
6
6
  import { useMotionPreference } from "../../shared/use-motion-preference.js";
7
7
  import { useFollowPose, useBehaviorClock, usePoseWeight } from "../../shared/use-icon-motion.js";
8
8
  export { monkeyStates } from "./monkey-rig.js";
9
- export const MonkeyIcon = memo(function MonkeyIcon({ state = 'idle', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.8, speed = 1, alive = false, className, colors, }) {
9
+ export const MonkeyIcon = memo(function MonkeyIcon({ state = 'idle', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, colors, }) {
10
10
  const reduced = useMotionPreference();
11
11
  const element = useRef(null);
12
12
  const pace = Number.isFinite(speed) ? Math.min(4, Math.max(0.1, speed)) : 1;
@@ -0,0 +1,5 @@
1
+ import type { StatefulIconProps } from "../../shared/icon-types.js";
2
+ import { type PlugState } from "./plug-rig.js";
3
+ export { plugStates, type PlugState } from "./plug-rig.js";
4
+ export type PlugIconProps = StatefulIconProps<PlugState>;
5
+ export declare const PlugIcon: import("react").NamedExoticComponent<PlugIconProps>;
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { memo, useRef } from 'react';
4
+ import { motion, useTransform } from 'motion/react';
5
+ import { usePoseWeight, useBehaviorClock } from "../../shared/use-icon-motion.js";
6
+ import { useMotionPreference } from "../../shared/use-motion-preference.js";
7
+ import { PLUG_MALE, PLUG_SOCKET, samplePlug } from "./plug-rig.js";
8
+ export { plugStates } from "./plug-rig.js";
9
+ const ORIGIN = { originX: '0px', originY: '0px', transformBox: 'view-box' };
10
+ export const PlugIcon = memo(function PlugIcon({ state = 'disconnected', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, }) {
11
+ const reduced = useMotionPreference(), element = useRef(null);
12
+ const pace = Number.isFinite(speed) ? Math.min(4, Math.max(0.1, speed)) : 1;
13
+ const disconnected = usePoseWeight(state === 'disconnected', pace, reduced), connecting = usePoseWeight(state === 'connecting', pace, reduced), connected = usePoseWeight(state === 'connected', pace, reduced), error = usePoseWeight(state === 'error', pace, reduced);
14
+ const performs = alive && !reduced && state !== 'disconnected', life = usePoseWeight(performs, pace, reduced), time = useBehaviorClock(element, performs, pace, life);
15
+ const rig = useTransform(() => samplePlug([disconnected.get(), connecting.get(), connected.get(), error.get()], time.get(), life.get()));
16
+ const male = useTransform(rig, (r) => r.male), socket = useTransform(rig, (r) => r.socket), cables = useTransform(rig, (r) => r.cables), pins = useTransform(rig, (r) => r.pins), pinOpacity = useTransform(rig, (r) => r.pinOpacity), signal = useTransform(rig, (r) => r.signal), warning = useTransform(rig, (r) => r.error), signalScale = useTransform(rig, (r) => r.signalScale);
17
+ return (_jsx("svg", { ref: element, className: className, width: size, height: size, viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", pointerEvents: "none", style: { display: 'block', overflow: 'visible' }, children: _jsxs("g", { style: {
18
+ ...ORIGIN,
19
+ transform: 'translate(12px, 12px) scale(.78) rotate(-45deg) translate(-12px, -12px)',
20
+ }, children: [_jsx(motion.path, { "data-layer": "plug-cables", d: cables, stroke: color, strokeWidth: strokeWidth }), _jsx(motion.path, { "data-layer": "plug-signal", d: "M10 6l2-1 2 1M10 18l2 1 2-1", stroke: accentColor, strokeWidth: strokeWidth * 0.7, opacity: signal, style: { ...ORIGIN, scale: signalScale, originX: '12px', originY: '12px' } }), _jsxs(motion.g, { "data-layer": "plug-male", transform: male, style: ORIGIN, stroke: color, strokeWidth: strokeWidth, children: [_jsx("path", { "data-part": "male", d: PLUG_MALE }), _jsx(motion.path, { "data-layer": "plug-pins", d: pins, opacity: pinOpacity })] }), _jsx(motion.g, { "data-layer": "plug-socket", transform: socket, style: ORIGIN, stroke: color, strokeWidth: strokeWidth, children: _jsx("path", { "data-part": "socket", d: PLUG_SOCKET }) }), _jsx(motion.path, { "data-layer": "plug-error", d: "M12 3v2M12 7v.1", stroke: accentColor, strokeWidth: strokeWidth * 0.8, opacity: warning })] }) }));
21
+ });
@@ -0,0 +1,16 @@
1
+ export declare const plugStates: readonly ["disconnected", "connecting", "connected", "error"];
2
+ export type PlugState = (typeof plugStates)[number];
3
+ export declare const PLUG_MALE = "M8 8H6a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h2Z";
4
+ export declare const PLUG_SOCKET = "M16 8h2a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-2Z";
5
+ export declare function samplePlug(weights: readonly number[], time?: number, alive?: number): {
6
+ male: string;
7
+ socket: string;
8
+ cables: string;
9
+ pins: string;
10
+ pinOpacity: number;
11
+ signal: number;
12
+ signalScale: number;
13
+ error: number;
14
+ gap: number;
15
+ contact: number;
16
+ };