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.
- package/README.md +13 -4
- package/THIRD_PARTY_NOTICES.md +14 -4
- package/dist/icons/alert/alert-icon.d.ts +6 -0
- package/dist/icons/alert/alert-icon.js +22 -0
- package/dist/icons/alert/alert-rig.d.ts +12 -0
- package/dist/icons/alert/alert-rig.js +25 -0
- package/dist/icons/badge-check/badge-check-icon.d.ts +5 -0
- package/dist/icons/badge-check/badge-check-icon.js +33 -0
- package/dist/icons/badge-check/badge-check-rig.d.ts +14 -0
- package/dist/icons/badge-check/badge-check-rig.js +36 -0
- package/dist/icons/bell/bell-icon.js +1 -1
- package/dist/icons/bulb/bulb-icon.d.ts +5 -0
- package/dist/icons/bulb/bulb-icon.js +25 -0
- package/dist/icons/bulb/bulb-rig.d.ts +13 -0
- package/dist/icons/bulb/bulb-rig.js +37 -0
- package/dist/icons/check/check-icon.d.ts +5 -0
- package/dist/icons/check/check-icon.js +26 -0
- package/dist/icons/check/check-rig.d.ts +6 -0
- package/dist/icons/check/check-rig.js +38 -0
- package/dist/icons/clapperboard/clapperboard-icon.d.ts +5 -0
- package/dist/icons/clapperboard/clapperboard-icon.js +18 -0
- package/dist/icons/clapperboard/clapperboard-rig.d.ts +12 -0
- package/dist/icons/clapperboard/clapperboard-rig.js +25 -0
- package/dist/icons/close/close-icon.d.ts +5 -0
- package/dist/icons/close/close-icon.js +27 -0
- package/dist/icons/close/close-rig.d.ts +10 -0
- package/dist/icons/close/close-rig.js +31 -0
- package/dist/icons/copy/copy-icon.d.ts +5 -0
- package/dist/icons/copy/copy-icon.js +31 -0
- package/dist/icons/copy/copy-rig.d.ts +13 -0
- package/dist/icons/copy/copy-rig.js +44 -0
- package/dist/icons/cursor/cursor-icon.d.ts +5 -0
- package/dist/icons/cursor/cursor-icon.js +32 -0
- package/dist/icons/cursor/cursor-rig.d.ts +21 -0
- package/dist/icons/cursor/cursor-rig.js +63 -0
- package/dist/icons/delete/delete-icon.d.ts +5 -0
- package/dist/icons/delete/delete-icon.js +25 -0
- package/dist/icons/delete/delete-rig.d.ts +15 -0
- package/dist/icons/delete/delete-rig.js +33 -0
- package/dist/icons/dog/dog-icon.js +1 -1
- package/dist/icons/download/download-icon.js +1 -1
- package/dist/icons/eye/eye-icon.d.ts +5 -0
- package/dist/icons/eye/eye-icon.js +27 -0
- package/dist/icons/eye/eye-rig.d.ts +9 -0
- package/dist/icons/eye/eye-rig.js +37 -0
- package/dist/icons/heart/heart-icon.js +1 -1
- package/dist/icons/heart/heart-rig.js +1 -1
- package/dist/icons/link/link-icon.d.ts +5 -0
- package/dist/icons/link/link-icon.js +30 -0
- package/dist/icons/link/link-rig.d.ts +13 -0
- package/dist/icons/link/link-rig.js +37 -0
- package/dist/icons/monkey/monkey-icon.js +1 -1
- package/dist/icons/plug/plug-icon.d.ts +5 -0
- package/dist/icons/plug/plug-icon.js +21 -0
- package/dist/icons/plug/plug-rig.d.ts +16 -0
- package/dist/icons/plug/plug-rig.js +29 -0
- package/dist/icons/refresh/refresh-icon.d.ts +5 -0
- package/dist/icons/refresh/refresh-icon.js +40 -0
- package/dist/icons/refresh/refresh-rig.d.ts +11 -0
- package/dist/icons/refresh/refresh-rig.js +30 -0
- package/dist/icons/replace/replace-icon.d.ts +5 -0
- package/dist/icons/replace/replace-icon.js +25 -0
- package/dist/icons/replace/replace-rig.d.ts +14 -0
- package/dist/icons/replace/replace-rig.js +24 -0
- package/dist/icons/rocket/rocket-icon.js +1 -1
- package/dist/icons/search/search-icon.js +1 -1
- package/dist/icons/shield/shield-icon.d.ts +5 -0
- package/dist/icons/shield/shield-icon.js +25 -0
- package/dist/icons/shield/shield-rig.d.ts +23 -0
- package/dist/icons/shield/shield-rig.js +42 -0
- package/dist/icons/ship/ship-icon.js +1 -1
- package/dist/icons/sparkle/sparkle-icon.d.ts +5 -0
- package/dist/icons/sparkle/sparkle-icon.js +27 -0
- package/dist/icons/sparkle/sparkle-rig.d.ts +8 -0
- package/dist/icons/sparkle/sparkle-rig.js +6 -0
- package/dist/icons/sparkles/sparkles-icon.d.ts +5 -0
- package/dist/icons/sparkles/sparkles-icon.js +27 -0
- package/dist/icons/sparkles/sparkles-rig.d.ts +14 -0
- package/dist/icons/sparkles/sparkles-rig.js +24 -0
- package/dist/icons/star/star-icon.d.ts +5 -0
- package/dist/icons/star/star-icon.js +38 -0
- package/dist/icons/star/star-rig.d.ts +20 -0
- package/dist/icons/star/star-rig.js +61 -0
- package/dist/icons/swap/swap-icon.d.ts +5 -0
- package/dist/icons/swap/swap-icon.js +26 -0
- package/dist/icons/swap/swap-rig.d.ts +10 -0
- package/dist/icons/swap/swap-rig.js +24 -0
- package/dist/icons/wave/wave-icon.d.ts +5 -0
- package/dist/icons/wave/wave-icon.js +18 -0
- package/dist/icons/wave/wave-rig.d.ts +8 -0
- package/dist/icons/wave/wave-rig.js +39 -0
- package/dist/index.d.ts +21 -2
- package/dist/index.js +20 -1
- package/dist/shared/icon-colors.d.ts +23 -4
- package/dist/shared/icon-types.d.ts +1 -0
- package/dist/shared/sparkle-motion.d.ts +12 -0
- package/dist/shared/sparkle-motion.js +38 -0
- package/dist/shared/use-icon-motion.d.ts +4 -0
- package/dist/shared/use-icon-motion.js +26 -0
- package/package.json +104 -9
- package/dist/icons/leaf/leaf-icon.d.ts +0 -6
- package/dist/icons/leaf/leaf-icon.js +0 -29
- package/dist/icons/leaf/leaf-rig.d.ts +0 -25
- package/dist/icons/leaf/leaf-rig.js +0 -201
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { behaviorBeat } from "../../shared/behavior-curves.js";
|
|
2
|
+
export const plugStates = ['disconnected', 'connecting', 'connected', 'error'];
|
|
3
|
+
// A straightened Lucide Unplug pair, rotated together onto its original diagonal.
|
|
4
|
+
export const PLUG_MALE = 'M8 8H6a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h2Z';
|
|
5
|
+
export const PLUG_SOCKET = 'M16 8h2a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-2Z';
|
|
6
|
+
const clamp = (n) => Math.max(0, Math.min(1, n));
|
|
7
|
+
export function samplePlug(weights, time = 0, alive = 0) {
|
|
8
|
+
const [, c, on, e] = weights.map(clamp), life = clamp(alive), t = time % 8.2;
|
|
9
|
+
const approach = behaviorBeat(t, 0.15, 0.85, 0.3, 1.1) + 0.65 * behaviorBeat(t, 4.4, 0.65, 0.15, 0.9);
|
|
10
|
+
const latch = behaviorBeat(t, 0.98, 0.16, 0.08, 0.5);
|
|
11
|
+
const retry = behaviorBeat(t, 0.2, 0.55, 0.1, 0.85) + 0.6 * behaviorBeat(t, 4.6, 0.45, 0.1, 0.75);
|
|
12
|
+
// Connecting approaches but never declares success. Error attempts cannot mate.
|
|
13
|
+
const contact = clamp(on + c * (0.18 + life * 0.3 * approach) + e * life * 0.12 * retry);
|
|
14
|
+
const shift = 4 * contact, gap = 8 - 2 * shift;
|
|
15
|
+
const pulse = on * life * (behaviorBeat(t, 0.2, 0.8, 0.2, 1) + 0.7 * behaviorBeat(t, 4.5, 0.7, 0.2, 0.9));
|
|
16
|
+
const pins = Math.min(3, gap);
|
|
17
|
+
return {
|
|
18
|
+
male: `translate(${shift}px, 0px)`,
|
|
19
|
+
socket: `translate(${-shift}px, 0px)`,
|
|
20
|
+
cables: `M2 12H${4 + shift}M${20 - shift} 12H22`,
|
|
21
|
+
pins: `M8 10H${8 + pins}M8 14H${8 + pins}`,
|
|
22
|
+
pinOpacity: clamp(gap / 1.8),
|
|
23
|
+
signal: c * (0.35 + life * 0.45 * approach) + pulse * 0.4,
|
|
24
|
+
signalScale: 1 + c * life * 0.18 * latch,
|
|
25
|
+
error: e,
|
|
26
|
+
gap,
|
|
27
|
+
contact,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { StatefulIconProps } from "../../shared/icon-types.js";
|
|
2
|
+
import { type RefreshState } from "./refresh-rig.js";
|
|
3
|
+
export { refreshStates, type RefreshState } from "./refresh-rig.js";
|
|
4
|
+
export type RefreshIconProps = StatefulIconProps<RefreshState>;
|
|
5
|
+
export declare const RefreshIcon: import("react").NamedExoticComponent<RefreshIconProps>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { memo, useRef, useCallback, useEffect } from 'react';
|
|
4
|
+
import { motion, useTransform, useMotionValue } 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 { REFRESH_UPPER, REFRESH_LOWER, REFRESH_HEADS, sampleRefresh, } from "./refresh-rig.js";
|
|
8
|
+
export { refreshStates } from "./refresh-rig.js";
|
|
9
|
+
const ORIGIN = { originX: '0px', originY: '0px', transformBox: 'view-box' };
|
|
10
|
+
export const RefreshIcon = memo(function RefreshIcon({ 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 === 'refreshing', pace, reduced);
|
|
15
|
+
const w2 = usePoseWeight(state === 'complete', pace, reduced);
|
|
16
|
+
const w3 = usePoseWeight(state === 'failed', pace, reduced);
|
|
17
|
+
const performs = alive && !reduced && state === 'refreshing';
|
|
18
|
+
const life = usePoseWeight(performs, pace, reduced), time = useBehaviorClock(element, performs, pace, life);
|
|
19
|
+
const phase = useMotionValue(0);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
let previous = time.get();
|
|
22
|
+
return time.on('change', (now) => {
|
|
23
|
+
const delta = Math.max(0, now - previous);
|
|
24
|
+
previous = now;
|
|
25
|
+
phase.set(phase.get() + delta * 180 * life.get() * w1.get());
|
|
26
|
+
});
|
|
27
|
+
}, [time, phase, life, w1]);
|
|
28
|
+
const can0 = useCallback(() => w1.get() < 0.001, [w1]);
|
|
29
|
+
const e0 = useActionProgress(state === 'refreshing', pace, reduced, 1.1, can0);
|
|
30
|
+
const can1 = useCallback(() => w2.get() < 0.001, [w2]);
|
|
31
|
+
const e1 = useActionProgress(state === 'complete', pace, reduced, 0.85, can1);
|
|
32
|
+
const can2 = useCallback(() => w3.get() < 0.001, [w3]);
|
|
33
|
+
const e2 = useActionProgress(state === 'failed', pace, reduced, 0.95, can2);
|
|
34
|
+
const rig = useTransform(() => sampleRefresh([w0.get(), w1.get(), w2.get(), w3.get()], time.get(), life.get(), e0.get(), e1.get(), e2.get(), phase.get()));
|
|
35
|
+
const wheel = useTransform(rig, (r) => r.wheel);
|
|
36
|
+
const check = useTransform(rig, (r) => r.check);
|
|
37
|
+
const checkOffset = useTransform(rig, (r) => r.checkOffset);
|
|
38
|
+
const failure = useTransform(rig, (r) => r.failure);
|
|
39
|
+
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": "refresh-body", transform: "translate(12 12) scale(.8) translate(-12 -12)", children: [_jsxs(motion.g, { "data-layer": "refresh-wheel", transform: wheel, style: ORIGIN, children: [_jsx("path", { "data-part": "upper-arc", d: REFRESH_UPPER }), _jsx("path", { "data-part": "lower-arc", d: REFRESH_LOWER }), _jsx("path", { "data-part": "heads", d: REFRESH_HEADS })] }), _jsx(motion.path, { "data-layer": "refresh-check", d: "M8.8 12L11.2 14.4L15.2 10.4", opacity: check, stroke: accentColor, strokeWidth: Math.min(strokeWidth * 0.8, 1.5), strokeDasharray: "9.06", strokeDashoffset: checkOffset }), _jsx(motion.g, { "data-layer": "refresh-failure", opacity: failure, children: _jsx("path", { d: "M9.5 9.5L14.5 14.5M14.5 9.5L9.5 14.5", strokeWidth: Math.min(strokeWidth * 0.8, 1.5) }) })] }) }));
|
|
40
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const refreshStates: readonly ["ready", "refreshing", "complete", "failed"];
|
|
2
|
+
export type RefreshState = (typeof refreshStates)[number];
|
|
3
|
+
export declare const REFRESH_UPPER = "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8";
|
|
4
|
+
export declare const REFRESH_LOWER = "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16";
|
|
5
|
+
export declare const REFRESH_HEADS = "M21 3v5h-5M8 16H3v5";
|
|
6
|
+
export declare function sampleRefresh(weights: readonly number[], _time?: number, _alive?: number, entry?: number, successEntry?: number, _failureEntry?: number, phase?: number): {
|
|
7
|
+
wheel: string;
|
|
8
|
+
check: number;
|
|
9
|
+
checkOffset: number;
|
|
10
|
+
failure: number;
|
|
11
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const clamp = (n) => Math.max(0, Math.min(1, Number.isFinite(n) ? n : 0));
|
|
2
|
+
const smooth = (n) => {
|
|
3
|
+
const q = clamp(n);
|
|
4
|
+
return q * q * q * (q * (q * 6 - 15) + 10);
|
|
5
|
+
};
|
|
6
|
+
const normalized = (values) => {
|
|
7
|
+
const w = values.map(clamp), sum = w.reduce((a, b) => a + b, 0) || 1;
|
|
8
|
+
return w.map((v) => v / sum);
|
|
9
|
+
};
|
|
10
|
+
export const refreshStates = ['ready', 'refreshing', 'complete', 'failed'];
|
|
11
|
+
export const REFRESH_UPPER = 'M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8';
|
|
12
|
+
export const REFRESH_LOWER = 'M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16';
|
|
13
|
+
export const REFRESH_HEADS = 'M21 3v5h-5M8 16H3v5';
|
|
14
|
+
export function sampleRefresh(weights, _time = 0, _alive = 0, entry = 1, successEntry = 1, _failureEntry = 1, phase = 0) {
|
|
15
|
+
// Retain the common rig-sampler positions; this rig consumes integrated
|
|
16
|
+
// phase rather than the raw behavior clock or a refusal playhead.
|
|
17
|
+
void _time;
|
|
18
|
+
void _alive;
|
|
19
|
+
void _failureEntry;
|
|
20
|
+
const w = normalized(weights);
|
|
21
|
+
// Opposed arrows return to an equivalent outline after a finite half-turn.
|
|
22
|
+
// Living phase accumulates in the renderer; a fade never rewinds this angle.
|
|
23
|
+
const turn = phase + 180 * smooth(entry);
|
|
24
|
+
return {
|
|
25
|
+
wheel: `translate(12px, 12px) rotate(${turn}deg) translate(-12px, -12px)`,
|
|
26
|
+
check: w[2],
|
|
27
|
+
checkOffset: 9.06 * (1 - smooth((successEntry - 0.08) / 0.62)),
|
|
28
|
+
failure: w[3],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { StatefulIconProps } from "../../shared/icon-types.js";
|
|
2
|
+
import { type ReplaceState } from "./replace-rig.js";
|
|
3
|
+
export { replaceStates, type ReplaceState } from "./replace-rig.js";
|
|
4
|
+
export type ReplaceIconProps = StatefulIconProps<ReplaceState>;
|
|
5
|
+
export declare const ReplaceIcon: import("react").NamedExoticComponent<ReplaceIconProps>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { memo, useCallback, useRef } 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 { REPLACE_SOURCE, REPLACE_TARGET, sampleReplace } from "./replace-rig.js";
|
|
8
|
+
export { replaceStates } from "./replace-rig.js";
|
|
9
|
+
const ORIGIN = { originX: '0px', originY: '0px', transformBox: 'view-box' };
|
|
10
|
+
export const ReplaceIcon = memo(function ReplaceIcon({ state = 'idle', 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 idle = usePoseWeight(state === 'idle', pace, reduced), replacing = usePoseWeight(state === 'replacing', pace, reduced), replaced = usePoseWeight(state === 'replaced', 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 !== 'replacing');
|
|
16
|
+
const canRestart = useCallback(() => {
|
|
17
|
+
const invisible = first.current || replacing.get() < 0.001;
|
|
18
|
+
first.current = false;
|
|
19
|
+
return invisible;
|
|
20
|
+
}, [replacing]);
|
|
21
|
+
const entry = useActionProgress(state === 'replacing', pace, reduced, 1.4, canRestart);
|
|
22
|
+
const rig = useTransform(() => sampleReplace([idle.get(), replacing.get(), replaced.get(), error.get()], time.get(), life.get(), entry.get()));
|
|
23
|
+
const body = useTransform(rig, (r) => r.body), source = useTransform(rig, (r) => r.source), target = useTransform(rig, (r) => r.target), arrow = useTransform(rig, (r) => r.arrow), head = useTransform(rig, (r) => r.head), check = useTransform(rig, (r) => r.check), failure = useTransform(rig, (r) => r.error), status = useTransform(rig, (r) => r.status);
|
|
24
|
+
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(motion.g, { "data-layer": "replace-body", transform: body, style: ORIGIN, children: [_jsx(motion.g, { "data-layer": "replace-source", transform: source, style: ORIGIN, children: _jsx("path", { "data-part": "source", d: REPLACE_SOURCE, stroke: color, strokeWidth: strokeWidth }) }), _jsxs(motion.g, { "data-layer": "replace-target", transform: target, style: ORIGIN, children: [_jsx("path", { "data-part": "target", d: REPLACE_TARGET, stroke: color, strokeWidth: strokeWidth }), _jsx(motion.path, { "data-layer": "replace-check", d: status, opacity: check, stroke: accentColor, strokeWidth: Math.min(strokeWidth * 0.65, 0.8) }), _jsx(motion.path, { "data-layer": "replace-error", d: "M6.5 16.4v1.1M6.5 18.5h.01", opacity: failure, stroke: color, strokeWidth: Math.min(strokeWidth * 0.65, 0.8) })] }), _jsx(motion.path, { "data-layer": "replace-arrow", d: arrow, stroke: color, strokeWidth: strokeWidth }), _jsx(motion.path, { "data-layer": "replace-head", d: head, stroke: color, strokeWidth: strokeWidth })] }) }));
|
|
25
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const replaceStates: readonly ["idle", "replacing", "replaced", "error"];
|
|
2
|
+
export type ReplaceState = (typeof replaceStates)[number];
|
|
3
|
+
export declare const REPLACE_SOURCE = "M14 4a1 1 0 0 1 1-1M15 10a1 1 0 0 1-1-1M21 4a1 1 0 0 0-1-1M21 9a1 1 0 0 1-1 1";
|
|
4
|
+
export declare const REPLACE_TARGET = "M4 14h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1Z";
|
|
5
|
+
export declare function sampleReplace(weights: readonly number[], time?: number, alive?: number, entry?: number): {
|
|
6
|
+
body: string;
|
|
7
|
+
source: string;
|
|
8
|
+
target: string;
|
|
9
|
+
arrow: string;
|
|
10
|
+
head: string;
|
|
11
|
+
check: number;
|
|
12
|
+
error: number;
|
|
13
|
+
status: string;
|
|
14
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { behaviorBeat } from "../../shared/behavior-curves.js";
|
|
2
|
+
export const replaceStates = ['idle', 'replacing', 'replaced', 'error'];
|
|
3
|
+
export const REPLACE_SOURCE = 'M14 4a1 1 0 0 1 1-1M15 10a1 1 0 0 1-1-1M21 4a1 1 0 0 0-1-1M21 9a1 1 0 0 1-1 1';
|
|
4
|
+
export const REPLACE_TARGET = 'M4 14h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1Z';
|
|
5
|
+
export function sampleReplace(weights, time = 0, alive = 0, entry = 1) {
|
|
6
|
+
const clamp = (n) => Math.max(0, Math.min(1, n));
|
|
7
|
+
const [, replacing, replaced, error] = weights.map(clamp), life = clamp(alive);
|
|
8
|
+
const t = time % 6;
|
|
9
|
+
const launch = Math.max(behaviorBeat(entry * 1.4, 0.05, 0.25, 0.12, 0.7), life * behaviorBeat(t, 0.25, 0.3, 0.1, 0.75));
|
|
10
|
+
const receive = Math.max(behaviorBeat(entry * 1.4, 0.48, 0.18, 0.12, 0.45), life * behaviorBeat(t, 0.75, 0.18, 0.12, 0.5));
|
|
11
|
+
const retry = life * behaviorBeat(t, 3.1, 0.4, 0.12, 0.65);
|
|
12
|
+
const travel = replacing * launch * 1.15;
|
|
13
|
+
const compress = replacing * receive * 0.045;
|
|
14
|
+
return {
|
|
15
|
+
body: 'translate(12px, 12px) scale(0.84) translate(-12px, -12px)',
|
|
16
|
+
source: `translate(${-replacing * launch * 0.7}px, ${replacing * launch * 0.5}px)`,
|
|
17
|
+
target: `translate(6.5px, 17.5px) scale(${1 + compress + replaced * life * retry * 0.025}, ${1 - compress}) translate(-6.5px, -17.5px)`,
|
|
18
|
+
arrow: `M6 ${10 + travel - error * retry * 0.7}V5a2 2 0 0 1 2-2h2`,
|
|
19
|
+
head: `M3 ${7 + travel - error * retry * 0.7}L6 ${10 + travel - error * retry * 0.7}L9 ${7 + travel - error * retry * 0.7}`,
|
|
20
|
+
check: replaced,
|
|
21
|
+
error,
|
|
22
|
+
status: 'M5.5 17.4L6.25 18.15L7.5 16.7',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -19,7 +19,7 @@ const RocketSmoke = memo(function RocketSmoke({ rig, index, color, }) {
|
|
|
19
19
|
const transform = useTransform(rig, (r) => r.smoke[index].transform);
|
|
20
20
|
return (_jsx(motion.circle, { "data-part": `smoke-${index}`, cx: "8.9", cy: "15.1", r: 0.3 + index * 0.08, fill: color, opacity: opacity, transform: transform, style: ORIGIN }));
|
|
21
21
|
});
|
|
22
|
-
export const RocketIcon = memo(function RocketIcon({ state = 'parked', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.
|
|
22
|
+
export const RocketIcon = memo(function RocketIcon({ state = 'parked', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, }) {
|
|
23
23
|
const reduced = useMotionPreference(), element = useRef(null);
|
|
24
24
|
const pace = Number.isFinite(speed) ? Math.min(4, Math.max(0.1, speed)) : 1;
|
|
25
25
|
const parked = usePoseWeight(state === 'parked', pace, reduced);
|
|
@@ -6,7 +6,7 @@ import { useMotionPreference } from "../../shared/use-motion-preference.js";
|
|
|
6
6
|
import { usePoseWeight, useBehaviorClock } from "../../shared/use-icon-motion.js";
|
|
7
7
|
import { sampleSearch, SEARCH_HANDLE, SEARCH_SHINE } from "./search-rig.js";
|
|
8
8
|
export { searchStates } from "./search-rig.js";
|
|
9
|
-
export const SearchIcon = memo(function SearchIcon({ state = 'idle', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.
|
|
9
|
+
export const SearchIcon = memo(function SearchIcon({ state = 'idle', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, }) {
|
|
10
10
|
const reduced = useMotionPreference(), element = useRef(null);
|
|
11
11
|
const pace = Number.isFinite(speed) ? Math.min(4, Math.max(0.1, speed)) : 1;
|
|
12
12
|
const idle = usePoseWeight(state === 'idle', pace, reduced), searching = usePoseWeight(state === 'searching', pace, reduced);
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { StatefulIconProps } from "../../shared/icon-types.js";
|
|
2
|
+
import { type ShieldState } from "./shield-rig.js";
|
|
3
|
+
export { shieldStates, type ShieldState } from "./shield-rig.js";
|
|
4
|
+
export type ShieldIconProps = StatefulIconProps<ShieldState>;
|
|
5
|
+
export declare const ShieldIcon: import("react").NamedExoticComponent<ShieldIconProps>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { memo, useCallback, useId, useRef } 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 { SHIELD_SHELL, SHIELD_RING_LENGTH, SHIELD_CHECK_LENGTH, SHIELD_CONFIRM_SECONDS, sampleShield, } from "./shield-rig.js";
|
|
8
|
+
export { shieldStates } from "./shield-rig.js";
|
|
9
|
+
const ORIGIN = { originX: '0px', originY: '0px', transformBox: 'view-box' };
|
|
10
|
+
export const ShieldIcon = memo(function ShieldIcon({ 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), scanning = usePoseWeight(state === 'scanning', pace, reduced), verified = usePoseWeight(state === 'verified', 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 firstConfirmation = useRef(true);
|
|
16
|
+
const canRestart = useCallback(() => {
|
|
17
|
+
const invisible = firstConfirmation.current || verified.get() < 0.001;
|
|
18
|
+
firstConfirmation.current = false;
|
|
19
|
+
return invisible;
|
|
20
|
+
}, [verified]);
|
|
21
|
+
const entry = useActionProgress(state === 'verified', pace, reduced, SHIELD_CONFIRM_SECONDS, canRestart);
|
|
22
|
+
const rig = useTransform(() => sampleShield([idle.get(), scanning.get(), verified.get(), blocked.get()], time.get(), life.get(), entry.get()));
|
|
23
|
+
const body = useTransform(rig, (r) => r.body), cut = useTransform(rig, (r) => r.cut), scanY = useTransform(rig, (r) => r.scanY), scan = useTransform(rig, (r) => r.scan), check = useTransform(rig, (r) => r.check), checkOffset = useTransform(rig, (r) => (1 - r.checkDraw) * SHIELD_CHECK_LENGTH), circle = useTransform(rig, (r) => r.circle), circleOffset = useTransform(rig, (r) => (1 - r.circleDraw) * SHIELD_RING_LENGTH), slash = useTransform(rig, (r) => r.blocked), shine = useTransform(rig, (r) => r.shine);
|
|
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: SHIELD_SHELL }) }), _jsx("clipPath", { id: `${id}-cut`, children: _jsx(motion.path, { "data-part": "blocked-cut", d: cut, clipRule: "evenodd" }) })] }), _jsxs(motion.g, { "data-layer": "shield-body", transform: body, style: ORIGIN, children: [_jsx("path", { "data-part": "shell", d: SHIELD_SHELL, stroke: color, strokeWidth: strokeWidth, clipPath: `url(#${id}-cut)` }), _jsxs("g", { clipPath: `url(#${id}-inside)`, children: [_jsx(motion.path, { "data-layer": "shield-scan", d: "M8 0H16", stroke: accentColor, strokeWidth: strokeWidth * 0.8, style: { y: scanY }, opacity: scan }), _jsx(motion.path, { "data-layer": "shield-confirm-circle", d: "M12 8.2a4.3 4.3 0 1 1 0 8.6a4.3 4.3 0 1 1 0-8.6", stroke: accentColor, strokeWidth: Math.min(strokeWidth * 0.65, 1.5), strokeDasharray: `${SHIELD_RING_LENGTH} ${SHIELD_RING_LENGTH}`, strokeDashoffset: circleOffset, opacity: circle }), _jsx(motion.path, { "data-layer": "shield-check", d: "m9.8 12.4 1.5 1.5 2.8-2.8", stroke: color, strokeWidth: Math.min(strokeWidth * 0.75, 1.5), strokeDasharray: `${SHIELD_CHECK_LENGTH} ${SHIELD_CHECK_LENGTH}`, strokeDashoffset: checkOffset, opacity: check }), _jsx(motion.path, { "data-layer": "shield-shine", d: "M16.8 7.2v2.2", stroke: accentColor, strokeWidth: strokeWidth * 0.7, opacity: shine })] }), _jsx(motion.path, { "data-layer": "shield-blocked", d: "M3 3l18 18", stroke: color, strokeWidth: strokeWidth, opacity: slash })] })] }));
|
|
25
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const shieldStates: readonly ["idle", "scanning", "verified", "blocked"];
|
|
2
|
+
export type ShieldState = (typeof shieldStates)[number];
|
|
3
|
+
export declare const SHIELD_SHELL = "M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z";
|
|
4
|
+
export declare const SHIELD_RING_LENGTH: number;
|
|
5
|
+
export declare const SHIELD_CHECK_LENGTH: number;
|
|
6
|
+
export declare const SHIELD_CONFIRM_SECONDS = 1.1;
|
|
7
|
+
export declare function shieldConfirmation(progress: number): {
|
|
8
|
+
circle: number;
|
|
9
|
+
check: number;
|
|
10
|
+
};
|
|
11
|
+
/** A planted shield: scan inside its boundary, receive a check, or close the gate. */
|
|
12
|
+
export declare function sampleShield(weights: readonly number[], time?: number, alive?: number, entry?: number): {
|
|
13
|
+
body: string;
|
|
14
|
+
scanY: number;
|
|
15
|
+
scan: number;
|
|
16
|
+
circle: number;
|
|
17
|
+
circleDraw: number;
|
|
18
|
+
check: number;
|
|
19
|
+
checkDraw: number;
|
|
20
|
+
blocked: number;
|
|
21
|
+
cut: string;
|
|
22
|
+
shine: number;
|
|
23
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { behaviorBeat } from "../../shared/behavior-curves.js";
|
|
2
|
+
export const shieldStates = ['idle', 'scanning', 'verified', 'blocked'];
|
|
3
|
+
export const SHIELD_SHELL = 'M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z';
|
|
4
|
+
const clamp = (n) => Math.max(0, Math.min(1, n));
|
|
5
|
+
export const SHIELD_RING_LENGTH = Math.PI * 8.6;
|
|
6
|
+
export const SHIELD_CHECK_LENGTH = Math.SQRT2 * 4.3;
|
|
7
|
+
export const SHIELD_CONFIRM_SECONDS = 1.1;
|
|
8
|
+
const smooth = (value) => {
|
|
9
|
+
const t = clamp(value);
|
|
10
|
+
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
11
|
+
};
|
|
12
|
+
export function shieldConfirmation(progress) {
|
|
13
|
+
return {
|
|
14
|
+
circle: smooth(progress / 0.66),
|
|
15
|
+
check: smooth((progress - 0.72) / 0.28),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** A planted shield: scan inside its boundary, receive a check, or close the gate. */
|
|
19
|
+
export function sampleShield(weights, time = 0, alive = 0, entry = 1) {
|
|
20
|
+
const [, s, v, b] = weights.map(clamp), life = clamp(alive);
|
|
21
|
+
const t = time % 7.2;
|
|
22
|
+
const sweep = behaviorBeat(t, 0.1, 1.15, 0.12, 1.15);
|
|
23
|
+
const second = behaviorBeat(t, 3.8, 0.75, 0.12, 0.75);
|
|
24
|
+
const confirm = behaviorBeat(t, 0.2, 0.3, 0.12, 0.7) + 0.55 * behaviorBeat(t, 4.1, 0.2, 0.1, 0.5);
|
|
25
|
+
const guard = behaviorBeat(t, 0.3, 0.28, 0.12, 0.7) - 0.4 * behaviorBeat(t, 4.4, 0.3, 0.1, 0.7);
|
|
26
|
+
const breathe = v * life * confirm;
|
|
27
|
+
const tilt = b * life * guard * -3;
|
|
28
|
+
const gap = b * 1.55;
|
|
29
|
+
const confirmation = shieldConfirmation(entry);
|
|
30
|
+
return {
|
|
31
|
+
body: `translate(12px, 12px) rotate(${tilt}deg) scale(${0.84 + breathe * 0.018}) translate(-12px, -12px)`,
|
|
32
|
+
scanY: 8 + s * life * (7 * sweep + 4 * second),
|
|
33
|
+
scan: s * (0.45 + life * 0.5),
|
|
34
|
+
circle: v * smooth(confirmation.circle / 0.035),
|
|
35
|
+
circleDraw: confirmation.circle,
|
|
36
|
+
check: v * smooth(confirmation.check / 0.035),
|
|
37
|
+
checkDraw: confirmation.check,
|
|
38
|
+
blocked: b,
|
|
39
|
+
cut: `M0 0H24V24H0Z M${2 - gap} ${2 + gap}L${22 - gap} ${22 + gap}L${22 + gap} ${22 - gap}L${2 + gap} ${2 - gap}Z`,
|
|
40
|
+
shine: v * life * confirm,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -9,7 +9,7 @@ import { sampleShip } from "./ship-rig.js";
|
|
|
9
9
|
export { shipStates } from "./ship-rig.js";
|
|
10
10
|
// Motion 13 renders transform as CSS, not an SVG transform attribute.
|
|
11
11
|
const SVG_ORIGIN = { originX: 0, originY: 0, transformBox: 'view-box' };
|
|
12
|
-
export const ShipIcon = memo(function ShipIcon({ state = 'docked', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.
|
|
12
|
+
export const ShipIcon = memo(function ShipIcon({ state = 'docked', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, colors, }) {
|
|
13
13
|
const reduced = useMotionPreference(), element = useRef(null);
|
|
14
14
|
const clipId = `ship-sea-${useId().replace(/:/g, '')}`;
|
|
15
15
|
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 SparkleState } from "./sparkle-rig.js";
|
|
3
|
+
export { sparkleStates, type SparkleState } from "./sparkle-rig.js";
|
|
4
|
+
export type SparkleIconProps = StatefulIconProps<SparkleState>;
|
|
5
|
+
export declare const SparkleIcon: import("react").NamedExoticComponent<SparkleIconProps>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } 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 { SPARKLE_BLINK_DURATION } from "../../shared/sparkle-motion.js";
|
|
8
|
+
import { SPARKLE_CORE, sampleSparkle } from "./sparkle-rig.js";
|
|
9
|
+
export { sparkleStates } from "./sparkle-rig.js";
|
|
10
|
+
const ORIGIN = { originX: '0px', originY: '0px', transformBox: 'view-box' };
|
|
11
|
+
export const SparkleIcon = memo(function SparkleIcon({ state = 'idle', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, }) {
|
|
12
|
+
const reduced = useMotionPreference(), element = useRef(null);
|
|
13
|
+
const pace = Number.isFinite(speed) ? Math.min(4, Math.max(0.1, speed)) : 1;
|
|
14
|
+
const idle = usePoseWeight(state === 'idle', pace, reduced), shimmering = usePoseWeight(state === 'shimmering', pace, reduced), blink = usePoseWeight(state === 'blink', pace, reduced), dimmed = usePoseWeight(state === 'dimmed', pace, reduced);
|
|
15
|
+
const performs = alive && !reduced && (state === 'shimmering' || state === 'blink');
|
|
16
|
+
const life = usePoseWeight(performs, pace, reduced), time = useBehaviorClock(element, performs, pace, life);
|
|
17
|
+
const first = useRef(true);
|
|
18
|
+
const canRestart = useCallback(() => {
|
|
19
|
+
const invisible = first.current || blink.get() < 0.001;
|
|
20
|
+
first.current = false;
|
|
21
|
+
return invisible;
|
|
22
|
+
}, [blink]);
|
|
23
|
+
const entry = useActionProgress(state === 'blink', pace, reduced, SPARKLE_BLINK_DURATION, canRestart);
|
|
24
|
+
const rig = useTransform(() => sampleSparkle([idle.get(), shimmering.get(), blink.get(), dimmed.get()], time.get(), life.get(), entry.get()));
|
|
25
|
+
const core = useTransform(rig, (r) => r.core), coreOpacity = useTransform(rig, (r) => r.coreOpacity), tint = useTransform(rig, (r) => r.tint);
|
|
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", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", pointerEvents: "none", style: { display: 'block', overflow: 'visible' }, children: _jsx(motion.g, { "data-layer": "sparkle-core", transform: core, style: ORIGIN, opacity: coreOpacity, children: _jsx(motion.path, { "data-part": "shell", d: SPARKLE_CORE, stroke: color, strokeWidth: strokeWidth, fill: accentColor, fillOpacity: tint }) }) }));
|
|
27
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { SPARKLE_CORE } from "../../shared/sparkle-motion.js";
|
|
2
|
+
export declare const sparkleStates: readonly ["idle", "shimmering", "blink", "dimmed"];
|
|
3
|
+
export type SparkleState = (typeof sparkleStates)[number];
|
|
4
|
+
export declare function sampleSparkle(weights: readonly number[], time?: number, alive?: number, entry?: number): {
|
|
5
|
+
core: string;
|
|
6
|
+
coreOpacity: number;
|
|
7
|
+
tint: number;
|
|
8
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { sampleSparkleCore } from "../../shared/sparkle-motion.js";
|
|
2
|
+
export { SPARKLE_CORE } from "../../shared/sparkle-motion.js";
|
|
3
|
+
export const sparkleStates = ['idle', 'shimmering', 'blink', 'dimmed'];
|
|
4
|
+
export function sampleSparkle(weights, time = 0, alive = 0, entry = 1) {
|
|
5
|
+
return sampleSparkleCore(weights, time, alive, entry, 0.85);
|
|
6
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { StatefulIconProps } from "../../shared/icon-types.js";
|
|
2
|
+
import { type SparklesState } from "./sparkles-rig.js";
|
|
3
|
+
export { sparklesStates, type SparklesState } from "./sparkles-rig.js";
|
|
4
|
+
export type SparklesIconProps = StatefulIconProps<SparklesState>;
|
|
5
|
+
export declare const SparklesIcon: import("react").NamedExoticComponent<SparklesIconProps>;
|
|
@@ -0,0 +1,27 @@
|
|
|
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 { SPARKLE_BLINK_DURATION } from "../../shared/sparkle-motion.js";
|
|
8
|
+
import { SPARKLES_CORE, SPARKLES_HIGH, SPARKLES_LOW, sampleSparkles, } from "./sparkles-rig.js";
|
|
9
|
+
export { sparklesStates } from "./sparkles-rig.js";
|
|
10
|
+
const ORIGIN = { originX: '0px', originY: '0px', transformBox: 'view-box' };
|
|
11
|
+
export const SparklesIcon = memo(function SparklesIcon({ state = 'idle', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, }) {
|
|
12
|
+
const reduced = useMotionPreference(), element = useRef(null);
|
|
13
|
+
const pace = Number.isFinite(speed) ? Math.min(4, Math.max(0.1, speed)) : 1;
|
|
14
|
+
const idle = usePoseWeight(state === 'idle', pace, reduced), shimmering = usePoseWeight(state === 'shimmering', pace, reduced), blink = usePoseWeight(state === 'blink', pace, reduced), dimmed = usePoseWeight(state === 'dimmed', pace, reduced);
|
|
15
|
+
const performs = alive && !reduced && (state === 'shimmering' || state === 'blink');
|
|
16
|
+
const life = usePoseWeight(performs, pace, reduced), time = useBehaviorClock(element, performs, pace, life);
|
|
17
|
+
const first = useRef(true);
|
|
18
|
+
const canRestart = useCallback(() => {
|
|
19
|
+
const invisible = first.current || blink.get() < 0.001;
|
|
20
|
+
first.current = false;
|
|
21
|
+
return invisible;
|
|
22
|
+
}, [blink]);
|
|
23
|
+
const entry = useActionProgress(state === 'blink', pace, reduced, SPARKLE_BLINK_DURATION, canRestart);
|
|
24
|
+
const rig = useTransform(() => sampleSparkles([idle.get(), shimmering.get(), blink.get(), dimmed.get()], time.get(), life.get(), entry.get()));
|
|
25
|
+
const core = useTransform(rig, (r) => r.core), high = useTransform(rig, (r) => r.high), low = useTransform(rig, (r) => r.low), coreOpacity = useTransform(rig, (r) => r.coreOpacity), highOpacity = useTransform(rig, (r) => r.highOpacity), lowOpacity = useTransform(rig, (r) => r.lowOpacity), tint = useTransform(rig, (r) => r.tint);
|
|
26
|
+
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(motion.g, { "data-layer": "sparkles-core", transform: core, style: ORIGIN, opacity: coreOpacity, children: _jsx(motion.path, { "data-part": "shell", d: SPARKLES_CORE, stroke: color, strokeWidth: strokeWidth, fill: accentColor, fillOpacity: tint }) }), _jsx(motion.path, { "data-layer": "sparkles-high", d: SPARKLES_HIGH, stroke: accentColor, strokeWidth: Math.min(strokeWidth * 0.6, 1.2), transform: high, style: ORIGIN, opacity: highOpacity }), _jsx(motion.path, { "data-layer": "sparkles-low", d: SPARKLES_LOW, stroke: accentColor, strokeWidth: Math.min(strokeWidth * 0.6, 1.2), transform: low, style: ORIGIN, opacity: lowOpacity })] }));
|
|
27
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { SPARKLE_CORE as SPARKLES_CORE } from "../../shared/sparkle-motion.js";
|
|
2
|
+
export declare const sparklesStates: readonly ["idle", "shimmering", "blink", "dimmed"];
|
|
3
|
+
export type SparklesState = (typeof sparklesStates)[number];
|
|
4
|
+
export declare function sampleSparkles(weights: readonly number[], time?: number, alive?: number, entry?: number): {
|
|
5
|
+
high: string;
|
|
6
|
+
low: string;
|
|
7
|
+
highOpacity: number;
|
|
8
|
+
lowOpacity: number;
|
|
9
|
+
core: string;
|
|
10
|
+
coreOpacity: number;
|
|
11
|
+
tint: number;
|
|
12
|
+
};
|
|
13
|
+
export declare const SPARKLES_HIGH = "M19 2.8Q19.4 4.6 21.2 5Q19.4 5.4 19 7.2Q18.6 5.4 16.8 5Q18.6 4.6 19 2.8Z";
|
|
14
|
+
export declare const SPARKLES_LOW = "M5 17.4Q5.3 18.7 6.6 19Q5.3 19.3 5 20.6Q4.7 19.3 3.4 19Q4.7 18.7 5 17.4Z";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { behaviorBeat } from "../../shared/behavior-curves.js";
|
|
2
|
+
import { sampleSparkleCore, sampleTwinkle } from "../../shared/sparkle-motion.js";
|
|
3
|
+
export { SPARKLE_CORE as SPARKLES_CORE } from "../../shared/sparkle-motion.js";
|
|
4
|
+
export const sparklesStates = ['idle', 'shimmering', 'blink', 'dimmed'];
|
|
5
|
+
export function sampleSparkles(weights, time = 0, alive = 0, entry = 1) {
|
|
6
|
+
const clamp = (n) => Math.max(0, Math.min(1, n));
|
|
7
|
+
const [, shimmering, blink, dimmed] = weights.map(clamp), life = clamp(alive);
|
|
8
|
+
const t = time % 6.4, e = clamp(entry);
|
|
9
|
+
// Separate authored schedules; never blink the complete glyph in unison.
|
|
10
|
+
const highBlink = sampleTwinkle(time, life, e, 0.44, [1.2, 4.2]), lowBlink = sampleTwinkle(time, life, e, 0.88, [2.2, 5.2]);
|
|
11
|
+
const highBeat = Math.max(behaviorBeat(e, 0.15, 0.2, 0.04, 0.48), life * behaviorBeat(t, 0.85, 0.28, 0.08, 0.65));
|
|
12
|
+
const lowBeat = Math.max(behaviorBeat(e, 0.32, 0.2, 0.03, 0.43), life * behaviorBeat(t, 1.4, 0.25, 0.08, 0.65));
|
|
13
|
+
const highScale = 1 - dimmed * 0.28 + shimmering * highBeat * 0.12;
|
|
14
|
+
const lowScale = 1 - dimmed * 0.28 + shimmering * lowBeat * 0.16;
|
|
15
|
+
return {
|
|
16
|
+
...sampleSparkleCore(weights, time, life, e, 0.67),
|
|
17
|
+
high: `translate(19px, 5px) rotate(${-blink * highBlink.tilt}deg) scale(${highScale}) translate(-19px, -5px)`,
|
|
18
|
+
low: `translate(5px, 19px) rotate(${blink * lowBlink.tilt * 0.75}deg) scale(${lowScale}) translate(-5px, -19px)`,
|
|
19
|
+
highOpacity: clamp(1 - dimmed * 0.55 - shimmering * life * (1 - highBeat) * 0.45 - blink * highBlink.dark * 0.9),
|
|
20
|
+
lowOpacity: clamp(1 - dimmed * 0.55 - shimmering * life * (1 - lowBeat) * 0.45 - blink * lowBlink.dark * 0.9),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export const SPARKLES_HIGH = 'M19 2.8Q19.4 4.6 21.2 5Q19.4 5.4 19 7.2Q18.6 5.4 16.8 5Q18.6 4.6 19 2.8Z';
|
|
24
|
+
export const SPARKLES_LOW = 'M5 17.4Q5.3 18.7 6.6 19Q5.3 19.3 5 20.6Q4.7 19.3 3.4 19Q4.7 18.7 5 17.4Z';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { StatefulIconProps } from "../../shared/icon-types.js";
|
|
2
|
+
import { type StarState } from "./star-rig.js";
|
|
3
|
+
export { starStates, type StarState } from "./star-rig.js";
|
|
4
|
+
export type StarIconProps = StatefulIconProps<StarState>;
|
|
5
|
+
export declare const StarIcon: import("react").NamedExoticComponent<StarIconProps>;
|
|
@@ -0,0 +1,38 @@
|
|
|
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 { STAR_SHELL, STAR_MOTE, STAR_ACTION_DURATION, sampleStar, } from "./star-rig.js";
|
|
8
|
+
export { starStates } from "./star-rig.js";
|
|
9
|
+
const ORIGIN = { originX: '0px', originY: '0px', transformBox: 'view-box' };
|
|
10
|
+
const MOTE_INDICES = [0, 1, 2, 3, 4, 5];
|
|
11
|
+
function StarMote({ rig, index, color, strokeWidth, }) {
|
|
12
|
+
const transform = useTransform(rig, (r) => r.motes[index].transform), opacity = useTransform(rig, (r) => r.motes[index].opacity);
|
|
13
|
+
return (_jsx(motion.path, { "data-layer": `star-mote-${index}`, d: STAR_MOTE, transform: transform, opacity: opacity, stroke: color, fill: color, strokeWidth: Math.min(strokeWidth * 0.3, 0.6), style: ORIGIN }));
|
|
14
|
+
}
|
|
15
|
+
export const StarIcon = memo(function StarIcon({ state = 'idle', size = 24, color = 'currentColor', accentColor = 'currentColor', strokeWidth = 1.65, speed = 1, alive = false, className, }) {
|
|
16
|
+
const reduced = useMotionPreference(), element = useRef(null);
|
|
17
|
+
const pace = Number.isFinite(speed) ? Math.min(4, Math.max(0.1, speed)) : 1;
|
|
18
|
+
const idle = usePoseWeight(state === 'idle', pace, reduced), shooting = usePoseWeight(state === 'shooting', pace, reduced), starred = usePoseWeight(state === 'starred', pace, reduced), loading = usePoseWeight(state === 'loading', pace, reduced);
|
|
19
|
+
const performs = alive && !reduced && state !== 'idle';
|
|
20
|
+
const life = usePoseWeight(performs, pace, reduced), time = useBehaviorClock(element, performs, pace, life);
|
|
21
|
+
const first = useRef(true);
|
|
22
|
+
const canRestart = useCallback(() => {
|
|
23
|
+
const invisible = first.current || starred.get() < 0.001;
|
|
24
|
+
first.current = false;
|
|
25
|
+
return invisible;
|
|
26
|
+
}, [starred]);
|
|
27
|
+
const firstFlight = useRef(true);
|
|
28
|
+
const canRestartFlight = useCallback(() => {
|
|
29
|
+
const invisible = firstFlight.current || shooting.get() < 0.001;
|
|
30
|
+
firstFlight.current = false;
|
|
31
|
+
return invisible;
|
|
32
|
+
}, [shooting]);
|
|
33
|
+
const entry = useActionProgress(state === 'starred', pace, reduced, STAR_ACTION_DURATION, canRestart);
|
|
34
|
+
const flightEntry = useActionProgress(state === 'shooting', pace, reduced, STAR_ACTION_DURATION, canRestartFlight);
|
|
35
|
+
const rig = useTransform(() => sampleStar([idle.get(), shooting.get(), starred.get(), loading.get()], time.get(), life.get(), entry.get(), flightEntry.get()));
|
|
36
|
+
const body = useTransform(rig, (r) => r.body), tint = useTransform(rig, (r) => r.tint), trail = useTransform(rig, (r) => r.trail), trailOpacity = useTransform(rig, (r) => r.trailOpacity), trailDash = useTransform(rig, (r) => r.trailDash), trailOffset = useTransform(rig, (r) => r.trailOffset), progress = useTransform(rig, (r) => r.progress), progressOffset = useTransform(rig, (r) => r.progressOffset);
|
|
37
|
+
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(motion.path, { "data-layer": "star-trail", d: trail, opacity: trailOpacity, strokeDasharray: trailDash, strokeDashoffset: trailOffset, stroke: accentColor, strokeWidth: Math.min(strokeWidth * 0.65, 1.2) }), _jsxs(motion.g, { "data-layer": "star-body", transform: body, style: ORIGIN, children: [_jsx(motion.path, { "data-part": "shell", d: STAR_SHELL, stroke: color, strokeWidth: strokeWidth, fill: accentColor, fillOpacity: tint }), _jsx(motion.circle, { "data-layer": "star-progress", cx: "12", cy: "12", r: "2.3", stroke: accentColor, strokeWidth: Math.min(strokeWidth * 0.65, 1.2), strokeDasharray: "3.6 10.85", strokeDashoffset: progressOffset, opacity: progress })] }), MOTE_INDICES.map((index) => (_jsx(StarMote, { rig: rig, index: index, color: accentColor, strokeWidth: strokeWidth }, index)))] }));
|
|
38
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const starStates: readonly ["idle", "shooting", "starred", "loading"];
|
|
2
|
+
export type StarState = (typeof starStates)[number];
|
|
3
|
+
export declare const STAR_ACTION_DURATION = 1.35;
|
|
4
|
+
export declare function sampleStar(weights: readonly number[], time?: number, alive?: number, entry?: number, flightEntry?: number): {
|
|
5
|
+
body: string;
|
|
6
|
+
tint: number;
|
|
7
|
+
motes: {
|
|
8
|
+
transform: string;
|
|
9
|
+
opacity: number;
|
|
10
|
+
}[];
|
|
11
|
+
trail: string;
|
|
12
|
+
trailOpacity: number;
|
|
13
|
+
trailDash: string;
|
|
14
|
+
trailOffset: number;
|
|
15
|
+
progress: number;
|
|
16
|
+
progressOffset: number;
|
|
17
|
+
};
|
|
18
|
+
export declare const STAR_MOTE = "M0 -1.2L.36 -.38L1.25 -.3L.63 .33L.8 1.2L0 .76L-.8 1.2L-.63 .33L-1.25 -.3L-.36 -.38Z";
|
|
19
|
+
/** Rounded Lucide star contour, preserved as one rigid silhouette. */
|
|
20
|
+
export declare const STAR_SHELL = "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { behaviorBeat } from "../../shared/behavior-curves.js";
|
|
2
|
+
export const starStates = ['idle', 'shooting', 'starred', 'loading'];
|
|
3
|
+
export const STAR_ACTION_DURATION = 1.35;
|
|
4
|
+
const clamp = (n) => Math.max(0, Math.min(1, n));
|
|
5
|
+
const shake = (seconds) => behaviorBeat(seconds, 0.14, 0.07, 0, 0.49) * Math.sin((seconds - 0.14) * Math.PI * 12) * 10;
|
|
6
|
+
const EMISSIONS = [
|
|
7
|
+
{ start: 0.32, anchor: [12, 2.3], travel: [-2, -1.2], turn: -30 },
|
|
8
|
+
{ start: 0.39, anchor: [21, 9.8], travel: [1.5, -3], turn: 35 },
|
|
9
|
+
{ start: 0.46, anchor: [6.4, 21], travel: [-3, -1.3], turn: -20 },
|
|
10
|
+
];
|
|
11
|
+
/** Each cohort keeps travelling outward; life fades opacity, never its accumulated travel. */
|
|
12
|
+
function motes(seconds, weight) {
|
|
13
|
+
return EMISSIONS.map(({ start, anchor, travel, turn }) => {
|
|
14
|
+
const q = clamp((seconds - start) / 0.7);
|
|
15
|
+
const angle = ((-4 + shake(start)) * Math.PI) / 180;
|
|
16
|
+
const x = (anchor[0] - 12) * 0.825, y = (anchor[1] - 12) * 0.825;
|
|
17
|
+
const radius = Math.hypot(x, y);
|
|
18
|
+
const clearance = 1.1;
|
|
19
|
+
const radialX = x * Math.cos(angle) - y * Math.sin(angle), radialY = x * Math.sin(angle) + y * Math.cos(angle);
|
|
20
|
+
const distance = 1 - (1 - q) ** 2;
|
|
21
|
+
const cx = 12 + radialX * (1 + clearance / radius) + travel[0] * distance;
|
|
22
|
+
const cy = 12 + radialY * (1 + clearance / radius) + travel[1] * distance;
|
|
23
|
+
return {
|
|
24
|
+
transform: `translate(${cx}px, ${cy}px) rotate(${turn * q}deg) scale(${0.35 + q * 0.45})`,
|
|
25
|
+
opacity: clamp(weight * behaviorBeat(seconds, start, 0.08, 0.12, 0.5)),
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export function sampleStar(weights, time = 0, alive = 0, entry = 1, flightEntry = 1) {
|
|
30
|
+
const [, shooting, starred, loading] = weights.map(clamp), life = clamp(alive);
|
|
31
|
+
const e = clamp(entry);
|
|
32
|
+
const t = time % 7.2;
|
|
33
|
+
const seconds = e * STAR_ACTION_DURATION;
|
|
34
|
+
const turn = shake(seconds) + life * shake(t - 3.2);
|
|
35
|
+
const firstFlight = behaviorBeat(clamp(flightEntry) * STAR_ACTION_DURATION, 0.04, 0.2, 0.04, 0.78);
|
|
36
|
+
// A continuous diagonal glide under the entry action, not one surge then a long hold.
|
|
37
|
+
const glide = life * Math.sin(time * 2.7) * 0.75;
|
|
38
|
+
const flight = firstFlight;
|
|
39
|
+
const cx = 12 + shooting * (2.8 + flight * 1.1 + glide), cy = 12 - shooting * (2.8 + flight * 0.9 + glide * 0.8);
|
|
40
|
+
const scale = 0.8 - shooting * 0.25 + starred * 0.025;
|
|
41
|
+
const wakeLife = life * shooting;
|
|
42
|
+
return {
|
|
43
|
+
body: `translate(${cx}px, ${cy}px) rotate(${shooting * (-18 - flight * 4) + starred * (-4 + turn) + loading * 12}deg) scale(${scale}) translate(-12px, -12px)`,
|
|
44
|
+
tint: starred * 0.16,
|
|
45
|
+
motes: [
|
|
46
|
+
...motes(seconds, starred),
|
|
47
|
+
...motes(life > 0 ? t - 3.2 : STAR_ACTION_DURATION, starred * life),
|
|
48
|
+
],
|
|
49
|
+
trail: `M3.6 16.8Q7.4 15.6 ${cx - 4.4} ${cy + 2.8}M7 20.4Q9.5 17.8 ${cx - 2.4} ${cy + 5}`,
|
|
50
|
+
trailOpacity: shooting * (0.65 + flight * 0.35),
|
|
51
|
+
// Constant total dash period makes modulo resets visually equivalent. Fade only the gap.
|
|
52
|
+
trailDash: `${8 - 2 * wakeLife} ${2 * wakeLife}`,
|
|
53
|
+
trailOffset: wakeLife > 0 ? -((time * 9) % 8) : 0,
|
|
54
|
+
progress: loading,
|
|
55
|
+
// Fade a bounded sweep into its resting arc, not an accumulated phase.
|
|
56
|
+
progressOffset: -Math.sin(time * 2.4) * life * 3.2,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export const STAR_MOTE = 'M0 -1.2L.36 -.38L1.25 -.3L.63 .33L.8 1.2L0 .76L-.8 1.2L-.63 .33L-1.25 -.3L-.36 -.38Z';
|
|
60
|
+
/** Rounded Lucide star contour, preserved as one rigid silhouette. */
|
|
61
|
+
export const STAR_SHELL = 'M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z';
|