@nice2dev/icons 1.3.16 → 1.3.18

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 (55) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +21 -1
  3. package/dist/cjs/animationControls.js +10 -5
  4. package/dist/cjs/animationSpeed.js +102 -0
  5. package/dist/cjs/createIcon.js +68 -24
  6. package/dist/cjs/iconStyles.js +1171 -1081
  7. package/dist/cjs/icons/actions.js +2 -2
  8. package/dist/cjs/icons/brand.js +4 -747
  9. package/dist/cjs/icons/business.js +59 -23
  10. package/dist/cjs/icons/files.js +54 -54
  11. package/dist/cjs/icons/food.js +75 -557
  12. package/dist/cjs/icons/fun.js +51 -33
  13. package/dist/cjs/icons/index.js +0 -7
  14. package/dist/cjs/icons/navigation.js +26 -7
  15. package/dist/cjs/icons/omniverk.js +17 -244
  16. package/dist/cjs/icons/science.js +63 -170
  17. package/dist/cjs/icons/sports.js +45 -137
  18. package/dist/cjs/index.js +0 -7
  19. package/dist/cjs/reducedMotion.js +30 -0
  20. package/dist/cjs/smilAnimations.js +16 -3
  21. package/dist/cjs/utilities.js +4 -25
  22. package/dist/esm/animationControls.js +10 -5
  23. package/dist/esm/animationSpeed.js +102 -0
  24. package/dist/esm/createIcon.js +69 -25
  25. package/dist/esm/iconStyles.js +1171 -1081
  26. package/dist/esm/icons/actions.js +2 -2
  27. package/dist/esm/icons/brand.js +5 -748
  28. package/dist/esm/icons/business.js +59 -23
  29. package/dist/esm/icons/files.js +54 -54
  30. package/dist/esm/icons/food.js +74 -556
  31. package/dist/esm/icons/fun.js +51 -33
  32. package/dist/esm/icons/index.js +3 -10
  33. package/dist/esm/icons/navigation.js +26 -7
  34. package/dist/esm/icons/omniverk.js +17 -244
  35. package/dist/esm/icons/science.js +63 -170
  36. package/dist/esm/icons/sports.js +45 -137
  37. package/dist/esm/index.js +3 -10
  38. package/dist/esm/nicetodev-icons.css +1 -1
  39. package/dist/esm/reducedMotion.js +30 -0
  40. package/dist/esm/smilAnimations.js +16 -3
  41. package/dist/esm/utilities.js +5 -26
  42. package/dist/types/animationSpeed.d.ts +28 -0
  43. package/dist/types/createIcon.d.ts +6 -0
  44. package/dist/types/iconStyles.d.ts +12 -1
  45. package/dist/types/icons/brand.d.ts +0 -4
  46. package/dist/types/icons/food.d.ts +0 -3
  47. package/dist/types/icons/index.d.ts +3 -3
  48. package/dist/types/icons/omniverk.d.ts +0 -3
  49. package/dist/types/icons/science.d.ts +1 -1
  50. package/dist/types/reducedMotion.d.ts +6 -0
  51. package/dist/types/smilAnimations.d.ts +10 -2
  52. package/dist/types/types.d.ts +26 -0
  53. package/dist/types/utilities.d.ts +2 -2
  54. package/package.json +2 -2
  55. package/dist/cjs/nicetodev-icons.css +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # @nice2dev/icons — Changelog
2
2
 
3
+ ## 1.3.18
4
+
5
+ ### Patch Changes
6
+
7
+ - 00b696e: Icons and motion (OmniVerk wishlist §1, §8):
8
+
9
+ - **Reduced motion:** for users who ask for it (`prefers-reduced-motion: reduce`) every `Ntd*` icon holds still on
10
+ its own — no CSS animation class, and its SVG (SMIL) animations jump to their last frame (an entrance shows its
11
+ result, a loop rests on the shape as drawn): the icons' own animations (NtdOmniVerk…), `smilAnimation`, gradient
12
+ animations, the style decorators and `SmilAnimationWrapper`. `reducedMotion="ignore"` plays anyway. The
13
+ stylesheet has a reduced-motion rule for anything animated by hand. `withReducedMotionProps` also clears
14
+ `smilAnimation`; `useReducedMotion` follows the same store.
15
+ - NiceSetupWizard pauses SVG animations in its `loadingIndicator` for reduced motion (CSS animations stopped
16
+ already; SMIL kept going).
17
+ - **`animationMode="single"`** plays a looping `animation` once (the parts an icon animates inside itself
18
+ included): the stylesheet repeats `var(--ntd-anim-iterations, infinite)`. **`playKey`** starts the animations over
19
+ when it changes; **`replayOnClick`** on every click.
20
+ - InteractiveIcon with a `click` trigger plays again on every click; `AnimationController.play()` starts over while
21
+ already playing.
22
+
23
+ - 92418c0: OmniVerk wishlist §3, packaging:
24
+
25
+ - No test declarations in `dist` any more: erp-adapter (16 files), auth (12), ui-communication (8), ui-presentations
26
+ (14), and the test `setup.d.ts` of ui-3d, ui-graphics and game-engine.
27
+ - **One stylesheet per package**: icons, ui-3d and ui-graphics ship theirs in `dist/esm` only — where `exports`
28
+ points; the `dist/cjs` copy was reached from nothing.
29
+ - design-tokens builds its CLI as ES modules only: `bin` and `./cli` point at `dist/cli/index.js`; the CommonJS
30
+ `dist/cli/index.cjs` nothing referred to is gone.
31
+ - ui README: the Tools section and its link say 12 components (the link pointed at `#tools-14-components`).
32
+
33
+ ## 1.3.17
34
+
35
+ ### Patch Changes
36
+
37
+ - 59a1a84: Icons, showcase feedback round 41:
38
+
39
+ - **Removed** (breaking): NtdGym (use NtdDumbbell), NtdPizzaFood (NtdPizza), NtdDelivery (NtdTruck),
40
+ NtdOmniVerkBadge, NtdOmniVerkLogo, NtdOmniVerkSpinner (`<NtdOmniVerk animation="spin" />` or NiceSpinner) and
41
+ NtdOmniVerkWatermark (NiceLogo).
42
+ - **`animationSpeed`** on every icon, the style decorators and SmilAnimationWrapper — and every animation now runs
43
+ at half its earlier pace by default: CSS animations, `smilAnimation`, gradient animations and the icons' own
44
+ (NtdOmniVerk, NtdOmniVerkAnimated). `animationSpeed={2}` is the old pace; an explicit duration keeps its length.
45
+ createIcon's new `animated` option scales an icon's own SVG animation times.
46
+ - **Redrawn**: NtdNuke, NtdRunning, NtdDumbbell, NtdBeer, NtdCrown, NtdTarget, NtdMolecule, NtdPlate, NtdFood,
47
+ NtdCoffeeFood, NtdTruck, NtdRefresh, NtdLayers, NtdTrash, NtdCoins; NtdPlanet's ring is primary with a thin
48
+ accent rim.
49
+
3
50
  ## 1.3.16
4
51
 
5
52
  ### Patch Changes
package/README.md CHANGED
@@ -110,6 +110,11 @@ Every icon component accepts these props (extends `SVGProps<SVGSVGElement>`):
110
110
  | `strokeLinejoin` | `'round' \| 'miter' \| 'bevel'` | `'round'` | Stroke line join style |
111
111
  | `animation` | `IconAnimation` | `undefined` | CSS animation name |
112
112
  | `animationDuration` | `string` | CSS default | Duration override (e.g. `'0.5s'`) |
113
+ | `animationSpeed` | `number` | `1` | Pace multiplier; `2` = twice as slow |
114
+ | `animationMode` | `'loop' \| 'single'` | `'loop'` | `'single'` plays the animation once |
115
+ | `playKey` | `string \| number` | `undefined` | A new value plays it again |
116
+ | `replayOnClick` | `boolean` | `false` | Each click plays it again |
117
+ | `reducedMotion` | `'auto' \| 'ignore'` | `'auto'` | `'ignore'` keeps moving under reduce |
113
118
  | `gradient` | `GradientPreset` | `undefined` | Apply a gradient fill preset |
114
119
  | `gradientType` | `'linear' \| 'radial'` | `'linear'` | Gradient shape |
115
120
  | `gradientAngle` | `number` | `135` | Angle for linear gradients (degrees) |
@@ -203,6 +208,21 @@ sand pours while it holds, in step with the turns at any `animationDuration`:
203
208
 
204
209
  See [Full Animation Catalog](#full-animation-catalog) for the complete list.
205
210
 
211
+ **Once, and again on demand (1.3.18).** Animations loop. `animationMode="single"` plays one run and
212
+ stops; `replayOnClick` plays it again on every click (your `onClick` still runs), and a new `playKey`
213
+ plays it again from code — both remount the `<svg>`, so built-in SMIL entrances replay too:
214
+
215
+ ```tsx
216
+ <NtdOmniVerk animation="flip" animationMode="single" replayOnClick />
217
+ <NtdBell animation="shake" animationMode="single" playKey={unread} />
218
+ ```
219
+
220
+ **Reduced motion (1.3.18).** Under `prefers-reduced-motion: reduce` every icon stops by itself: SMIL
221
+ animations jump to their end frame (an entrance shows its final state, a loop its base state), the
222
+ `ntd-anim-*` class is left out and gradients stand still — no stylesheet needed. A backstop in
223
+ `animations.css` covers icons rendered elsewhere. `reducedMotion="ignore"` keeps an icon moving (e.g.
224
+ a progress indicator that must be seen to work).
225
+
206
226
  ---
207
227
 
208
228
  ### Stacking icons
@@ -524,7 +544,7 @@ Properties passed to every path renderer (pre-resolved from user props):
524
544
 
525
545
  ### Brand & Logo (14 icons)
526
546
 
527
- `NtdNiceToDev` · `NtdNiceMonogram` · `NtdNiceOrbit` · `NtdNiceSpark` · `NtdNiceShield` · `NtdNiceBadge` · `NtdNiceCore` · `NtdNiceCrest` · `NtdNiceHelix` · `NtdNicePortal` · `NtdNicePulseMark` · `NtdNiceFlagship` · `NtdOmniVerk` · `NtdOmniVerkLogo`
547
+ `NtdNiceToDev` · `NtdNiceMonogram` · `NtdNiceOrbit` · `NtdNiceSpark` · `NtdNiceShield` · `NtdNiceBadge` · `NtdNiceCore` · `NtdNiceCrest` · `NtdNiceHelix` · `NtdNicePortal` · `NtdNicePulseMark` · `NtdNiceFlagship` · `NtdOmniVerk` · `NtdOmniVerkAnimated`
528
548
 
529
549
  ### Brand Studio (25 icons)
530
550
 
@@ -188,7 +188,7 @@ const AnimationController = /* @__PURE__ */ React.forwardRef(function AnimationC
188
188
  const [singleState, setSingleState] = React.useState(
189
189
  autoPlay ? "playing" : "idle"
190
190
  );
191
- const singleIterationRef = React.useRef(0);
191
+ const [singleIteration, setSingleIteration] = React.useState(0);
192
192
  const sequenceConfig = isSequence ? animation : { steps: [{ animation, duration: 1e3 }] };
193
193
  const sequence = useAnimationSequence(sequenceConfig);
194
194
  const state = isSequence ? sequence.state : singleState;
@@ -247,7 +247,7 @@ const AnimationController = /* @__PURE__ */ React.forwardRef(function AnimationC
247
247
  sequence.play();
248
248
  } else {
249
249
  setSingleState("playing");
250
- singleIterationRef.current++;
250
+ setSingleIteration((n) => n + 1);
251
251
  }
252
252
  onStart?.();
253
253
  },
@@ -264,7 +264,7 @@ const AnimationController = /* @__PURE__ */ React.forwardRef(function AnimationC
264
264
  sequence.reset();
265
265
  } else {
266
266
  setSingleState("idle");
267
- singleIterationRef.current++;
267
+ setSingleIteration((n) => n + 1);
268
268
  }
269
269
  },
270
270
  goToStep: (index) => {
@@ -310,7 +310,7 @@ const AnimationController = /* @__PURE__ */ React.forwardRef(function AnimationC
310
310
  ...children.props.style,
311
311
  ...animationStyles
312
312
  },
313
- key: isSequence ? `seq-${currentStepIndex}-${sequence.loopIteration}` : `single-${singleIterationRef.current}`
313
+ key: isSequence ? `seq-${currentStepIndex}-${sequence.loopIteration}` : `single-${singleIteration}`
314
314
  });
315
315
  return /* @__PURE__ */ jsxRuntime.jsx(
316
316
  "span",
@@ -335,6 +335,7 @@ const InteractiveIcon = /* @__PURE__ */ React.forwardRef(
335
335
  const [isActive, setIsActive] = React.useState(false);
336
336
  const [isLeaving, setIsLeaving] = React.useState(false);
337
337
  const [hasPlayed, setHasPlayed] = React.useState(false);
338
+ const [plays, setPlays] = React.useState(0);
338
339
  const wrapperRef = React.useRef(null);
339
340
  const timeoutRef = React.useRef(null);
340
341
  const setRefs = React.useCallback(
@@ -368,11 +369,13 @@ const InteractiveIcon = /* @__PURE__ */ React.forwardRef(
368
369
  timeoutRef.current = setTimeout(() => {
369
370
  setIsActive(true);
370
371
  setHasPlayed(true);
372
+ setPlays((n) => n + 1);
371
373
  onInteractionStart?.();
372
374
  }, config.delay);
373
375
  } else {
374
376
  setIsActive(true);
375
377
  setHasPlayed(true);
378
+ setPlays((n) => n + 1);
376
379
  onInteractionStart?.();
377
380
  }
378
381
  }, [disabled, config.once, config.delay, hasPlayed, clearTimer, onInteractionStart]);
@@ -425,7 +428,9 @@ const InteractiveIcon = /* @__PURE__ */ React.forwardRef(
425
428
  style: {
426
429
  ...children.props.style,
427
430
  ...animationStyles
428
- }
431
+ },
432
+ // a click plays the animation again: a new key restarts it
433
+ ...config.trigger === "click" ? { key: `play-${plays}` } : {}
429
434
  });
430
435
  return /* @__PURE__ */ jsxRuntime.jsx(
431
436
  "span",
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const React = require("react");
4
+ const ANIMATION_SLOWDOWN = 2;
5
+ function animationScale(speed) {
6
+ const s = typeof speed === "number" && speed > 0 && Number.isFinite(speed) ? speed : 1;
7
+ return ANIMATION_SLOWDOWN / s;
8
+ }
9
+ function durationScale(durationGiven, speed) {
10
+ return durationGiven ? animationScale(speed) / ANIMATION_SLOWDOWN : animationScale(speed);
11
+ }
12
+ const SMIL_ELEMENTS = /* @__PURE__ */ new Set([
13
+ "animate",
14
+ "animateTransform",
15
+ "animateMotion",
16
+ "animateColor",
17
+ "set"
18
+ ]);
19
+ const TIME_PROPS = ["dur", "begin", "end"];
20
+ const CLOCK = /^(\d*\.?\d+)(ms|s|min|h)?$/;
21
+ function scaleTime(value, scale) {
22
+ if (typeof value === "number") {
23
+ return Number((value * scale).toFixed(4));
24
+ }
25
+ return value.split(";").map((part) => {
26
+ const m = CLOCK.exec(part.trim());
27
+ if (!m) {
28
+ return part;
29
+ }
30
+ return `${Number((Number(m[1]) * scale).toFixed(4))}${m[2] ?? "s"}`;
31
+ }).join(";");
32
+ }
33
+ function retimeTree(node, scale) {
34
+ if (scale === 1) {
35
+ return node;
36
+ }
37
+ const walk = (n) => {
38
+ if (Array.isArray(n)) {
39
+ return n.map(walk);
40
+ }
41
+ if (!React.isValidElement(n)) {
42
+ return n;
43
+ }
44
+ const props = n.props;
45
+ const isHost = typeof n.type === "string";
46
+ const isFragment = n.type === React.Fragment;
47
+ if (!isHost && !isFragment) {
48
+ return n;
49
+ }
50
+ const next = {};
51
+ if (isHost && SMIL_ELEMENTS.has(n.type)) {
52
+ for (const key of TIME_PROPS) {
53
+ const v = props[key];
54
+ if (typeof v === "string" || typeof v === "number") {
55
+ next[key] = scaleTime(v, scale);
56
+ }
57
+ }
58
+ }
59
+ if (props.children !== void 0) {
60
+ next.children = React.Children.map(props.children, walk);
61
+ }
62
+ return Object.keys(next).length > 0 ? React.cloneElement(n, next) : n;
63
+ };
64
+ return walk(node);
65
+ }
66
+ function stillTree(node) {
67
+ const walk = (n) => {
68
+ if (Array.isArray(n)) {
69
+ return n.map(walk);
70
+ }
71
+ if (!React.isValidElement(n)) {
72
+ return n;
73
+ }
74
+ const props = n.props;
75
+ const isHost = typeof n.type === "string";
76
+ if (!isHost && n.type !== React.Fragment) {
77
+ return n;
78
+ }
79
+ const next = {};
80
+ if (isHost && SMIL_ELEMENTS.has(n.type)) {
81
+ next.begin = "0s";
82
+ next.dur = "0.001s";
83
+ next.repeatCount = "1";
84
+ next.repeatDur = void 0;
85
+ next.end = void 0;
86
+ }
87
+ if (props.children !== void 0) {
88
+ next.children = React.Children.map(props.children, walk);
89
+ }
90
+ return Object.keys(next).length > 0 ? React.cloneElement(n, next) : n;
91
+ };
92
+ return walk(node);
93
+ }
94
+ function motionTree(node, scale, still) {
95
+ return still ? stillTree(node) : retimeTree(node, scale);
96
+ }
97
+ exports.ANIMATION_SLOWDOWN = ANIMATION_SLOWDOWN;
98
+ exports.animationScale = animationScale;
99
+ exports.durationScale = durationScale;
100
+ exports.motionTree = motionTree;
101
+ exports.retimeTree = retimeTree;
102
+ exports.stillTree = stillTree;
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const React = require("react");
5
+ const animationSpeed = require("./animationSpeed.js");
6
+ const reducedMotion = require("./reducedMotion.js");
5
7
  const sharpen = require("./sharpen.js");
6
8
  const smilAnimations = require("./smilAnimations.js");
7
9
  const stacking = require("./stacking.js");
@@ -26,11 +28,12 @@ function useColorScheme() {
26
28
  return React.useSyncExternalStore(subscribeColorScheme, getColorScheme, () => "light");
27
29
  }
28
30
  const BOX_CENTER = [12, 12];
29
- function renderGradientDef(id, preset, type, angle, animation) {
31
+ function renderGradientDef(id, preset, type, angle, animation, timeScale = 1) {
30
32
  const stops = types.GRADIENT_PRESETS[preset];
31
33
  if (!stops) {
32
34
  return null;
33
35
  }
36
+ const loop = `${Number((3 * timeScale).toFixed(4))}s`;
34
37
  const stopEls = stops.map((color, i) => {
35
38
  const offset = `${Math.round(i / (stops.length - 1) * 100)}%`;
36
39
  const nextColor = stops[(i + 1) % stops.length];
@@ -40,7 +43,7 @@ function renderGradientDef(id, preset, type, angle, animation) {
40
43
  {
41
44
  attributeName: "stop-color",
42
45
  values: `${color};${nextColor};${color}`,
43
- dur: "3s",
46
+ dur: loop,
44
47
  repeatCount: "indefinite"
45
48
  }
46
49
  ) }, i);
@@ -60,7 +63,7 @@ function renderGradientDef(id, preset, type, angle, animation) {
60
63
  type: "rotate",
61
64
  from: "0 12 12",
62
65
  to: "360 12 12",
63
- dur: "3s",
66
+ dur: loop,
64
67
  repeatCount: "indefinite"
65
68
  },
66
69
  "spin"
@@ -74,7 +77,7 @@ function renderGradientDef(id, preset, type, angle, animation) {
74
77
  {
75
78
  attributeName: "x1",
76
79
  values: "-24;24;-24",
77
- dur: "3s",
80
+ dur: loop,
78
81
  repeatCount: "indefinite"
79
82
  },
80
83
  "shift-x1"
@@ -84,7 +87,7 @@ function renderGradientDef(id, preset, type, angle, animation) {
84
87
  {
85
88
  attributeName: "x2",
86
89
  values: "0;48;0",
87
- dur: "3s",
90
+ dur: loop,
88
91
  repeatCount: "indefinite"
89
92
  },
90
93
  "shift-x2"
@@ -167,7 +170,13 @@ function renderCutoutShape(shape, fill, maskId) {
167
170
  }
168
171
  }
169
172
  function createIcon(options) {
170
- const { displayName, viewBox = "0 0 24 24", paths, textCenter = BOX_CENTER } = options;
173
+ const {
174
+ displayName,
175
+ viewBox = "0 0 24 24",
176
+ paths,
177
+ textCenter = BOX_CENTER,
178
+ animated = false
179
+ } = options;
171
180
  const solidDuotone = (props) => paths.solid({ ...props, accentColor: props.secondaryColor, accentOpacity: 1 });
172
181
  const perfect = {
173
182
  x: options.perfectStackX ?? textCenter[0],
@@ -194,8 +203,13 @@ function createIcon(options) {
194
203
  strokeLinejoin: propLinejoin,
195
204
  animation,
196
205
  animationDuration,
206
+ animationSpeed: animationSpeed$1,
207
+ animationMode = "loop",
208
+ playKey,
209
+ replayOnClick = false,
210
+ reducedMotion: reducedMotion$1 = "auto",
197
211
  smilAnimation,
198
- smilDuration = 1,
212
+ smilDuration,
199
213
  smilRepeatCount = "indefinite",
200
214
  smilEasing = "ease-in-out",
201
215
  smilDelay = 0,
@@ -228,6 +242,8 @@ function createIcon(options) {
228
242
  const gradId = React.useId();
229
243
  const uid = React.useId();
230
244
  const center = { x: 12, y: 12 };
245
+ const still = reducedMotion.useStillMotion(reducedMotion$1);
246
+ const [replays, setReplays] = React.useState(0);
231
247
  const colorScheme = useColorScheme();
232
248
  const adaptiveColor = lightColor && darkColor ? colorScheme === "dark" ? darkColor : lightColor : void 0;
233
249
  const primaryColor = adaptiveColor ?? (color ? `var(${types.SEMANTIC_COLOR_VARS[color]}, ${types.SEMANTIC_COLOR_DEFAULTS[color]})` : propPrimaryColor);
@@ -255,7 +271,12 @@ function createIcon(options) {
255
271
  const filterId = React.useId();
256
272
  const isCutout = variant === "cutout";
257
273
  const maskId = React.useId();
258
- const classes = ["ntd-icon", animation ? `ntd-anim-${animation}` : "", className ?? ""].filter(Boolean).join(" ");
274
+ const classes = [
275
+ "ntd-icon",
276
+ animation && !still ? `ntd-anim-${animation}` : "",
277
+ reducedMotion$1 === "ignore" ? "ntd-motion-ignore" : "",
278
+ className ?? ""
279
+ ].filter(Boolean).join(" ");
259
280
  const transforms = [];
260
281
  if (rotate) {
261
282
  transforms.push(`rotate(${rotate}deg)`);
@@ -271,26 +292,34 @@ function createIcon(options) {
271
292
  }
272
293
  const transformString = transforms.length > 0 ? transforms.join(" ") : void 0;
273
294
  const transitionValue = transition === true ? "transform 0.3s ease" : typeof transition === "string" ? `transform ${transition}` : void 0;
295
+ const timeScale = animationSpeed.animationScale(animationSpeed$1);
274
296
  const animStyle = {
275
297
  // the variable carries the duration to the parts an icon animates inside itself
276
298
  ...animationDuration ? { animationDuration, "--ntd-anim-duration": animationDuration } : {},
299
+ // animations.css multiplies its times by it (default 2 there, the slowdown)
300
+ ...animationSpeed$1 !== void 0 ? { "--ntd-anim-scale": String(timeScale) } : {},
301
+ // a looping animation once (1.3.18): animations.css repeats `var(--ntd-anim-iterations, infinite)`
302
+ ...animationMode === "single" ? { "--ntd-anim-iterations": "1" } : {},
277
303
  ...transformString ? { transform: transformString } : {},
278
304
  ...transformString ? { transformOrigin } : {},
279
305
  ...transitionValue ? { transition: transitionValue } : {},
280
306
  ...style
281
307
  };
282
- const smilAnimationElements = smilAnimation ? smilAnimations.generateSmilAnimation(
308
+ const smilGenerated = smilAnimation ? smilAnimations.generateSmilAnimation(
283
309
  {
284
310
  type: smilAnimation,
285
- duration: smilDuration,
311
+ // 1 s before 1.3.17 by default; a given smilDuration keeps its length
312
+ duration: (smilDuration ?? 1) * animationSpeed.durationScale(smilDuration !== void 0, animationSpeed$1),
286
313
  repeatCount: smilRepeatCount,
287
314
  easing: smilEasing,
288
315
  delay: smilDelay
289
316
  },
290
317
  center
291
318
  ) : null;
319
+ const smilAnimationElements = still ? animationSpeed.stillTree(smilGenerated) : smilGenerated;
292
320
  const needsSmilScaleWrapper = smilAnimation && ["heartbeat", "scale-pulse", "grow", "shrink", "rubber", "pop"].includes(smilAnimation);
293
321
  const rendered = isCutout ? renderCutoutShape(cutoutShape, gradient ? effectivePrimary : resolvedSecondary, maskId) : variant === "sharp" ? sharpen.sharpenTree(renderer(renderProps)) : variant === "rounded" ? sharpen.roundTree(renderer(renderProps)) : variant === "wireframe" ? sharpen.wireTree(renderer(renderProps), effectivePrimary) : renderer(renderProps);
322
+ const paced = animated ? animationSpeed.motionTree(rendered, timeScale, still) : rendered;
294
323
  const stacked = stacking.renderStackChildren(children, {
295
324
  variant,
296
325
  primaryColor: effectivePrimary,
@@ -302,9 +331,9 @@ function createIcon(options) {
302
331
  textSize
303
332
  });
304
333
  const drawn = stacked ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
305
- rendered,
334
+ paced,
306
335
  stacked
307
- ] }) : rendered;
336
+ ] }) : paced;
308
337
  const iconContent = variant === "mini" ? /* @__PURE__ */ jsxRuntime.jsx(
309
338
  "g",
310
339
  {
@@ -329,22 +358,36 @@ function createIcon(options) {
329
358
  "aria-hidden": title ? void 0 : true,
330
359
  "aria-label": title,
331
360
  ...rest,
361
+ onClick: replayOnClick ? (event) => {
362
+ setReplays((n) => n + 1);
363
+ rest.onClick?.(event);
364
+ } : rest.onClick,
332
365
  children: [
333
366
  title && /* @__PURE__ */ jsxRuntime.jsx("title", { children: title }),
334
- gradient && /* @__PURE__ */ jsxRuntime.jsx("defs", { children: renderGradientDef(gradId, gradient, gradientType, gradientAngle, gradientAnimation) }),
367
+ gradient && /* @__PURE__ */ jsxRuntime.jsx("defs", { children: renderGradientDef(
368
+ gradId,
369
+ gradient,
370
+ gradientType,
371
+ gradientAngle,
372
+ still ? void 0 : gradientAnimation,
373
+ timeScale
374
+ ) }),
335
375
  is3d && /* @__PURE__ */ jsxRuntime.jsx("defs", { children: render3dFilterDef(filterId) }),
336
376
  isCutout && /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("mask", { id: maskId, children: [
337
377
  /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "0", y: "0", width: "24", height: "24", fill: "white" }),
338
- /* @__PURE__ */ jsxRuntime.jsx("g", { transform: "translate(12,12) scale(0.8) translate(-12,-12)", children: paths.solid({
339
- ...renderProps,
340
- primaryColor: "black",
341
- secondaryColor: "black",
342
- accentColor: "white",
343
- primaryOpacity: 1,
344
- secondaryOpacity: 1,
345
- accentOpacity: 1,
346
- uid: `${uid}m`
347
- }) })
378
+ /* @__PURE__ */ jsxRuntime.jsx("g", { transform: "translate(12,12) scale(0.8) translate(-12,-12)", children: (() => {
379
+ const mask = paths.solid({
380
+ ...renderProps,
381
+ primaryColor: "black",
382
+ secondaryColor: "black",
383
+ accentColor: "white",
384
+ primaryOpacity: 1,
385
+ secondaryOpacity: 1,
386
+ accentOpacity: 1,
387
+ uid: `${uid}m`
388
+ });
389
+ return animated ? animationSpeed.motionTree(mask, timeScale, still) : mask;
390
+ })() })
348
391
  ] }) }),
349
392
  smilAnimation ? needsSmilScaleWrapper ? /* @__PURE__ */ jsxRuntime.jsxs(
350
393
  "g",
@@ -361,7 +404,8 @@ function createIcon(options) {
361
404
  smilAnimationElements
362
405
  ] }) : is3d ? /* @__PURE__ */ jsxRuntime.jsx("g", { filter: `url(#${filterId})`, children: iconContent }) : iconContent
363
406
  ]
364
- }
407
+ },
408
+ `${playKey ?? ""}:${replays}`
365
409
  );
366
410
  }
367
411
  );