@dofy/native-emoji-motion 0.6.0 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.4 — 2026-11-14
4
+
5
+ - Add ripple, splash and wake effects for water.
6
+ - Add families for swimmers, surfers, water polo players and rowers, covering their gender and skin tone variants.
7
+ - Boats, the ring buoy, the water wave, the dolphin and the diving mask now use water effects.
8
+
3
9
  ## 0.6.0 — 2026-11-14
4
10
 
5
11
  - Add motion families for vegetables, land animals, clothing, tools, books, hand gestures, boats and sweet treats.
package/README.md CHANGED
@@ -24,13 +24,13 @@ Bring 👋, ❤️, 📡 and more to life in React, Vue, Svelte or plain HTML. E
24
24
 
25
25
  ## 📊 Collection at a glance
26
26
 
27
- | In version 0.6.0 | Count | What it covers |
27
+ | In version 0.6.4 | Count | What it covers |
28
28
  | --- | ---: | --- |
29
29
  | Handcrafted presets | 177 | Individually configured base emoji |
30
- | Reviewed motion families | 27 | 200 additional base emoji sharing reviewed motions |
30
+ | Reviewed motion families | 31 | 212 additional base emoji sharing reviewed motions |
31
31
  | Categories | 10 | Groups used to organize the collection |
32
32
  | Reusable motions | 53 | Motions shared by presets and families |
33
- | Emoji with optional SVG effects | 73 | Base emoji with decorative effects |
33
+ | Emoji with optional SVG effects | 96 | Base emoji with decorative effects |
34
34
  | Recognized Unicode sequences | 5,235 | Unicode Emoji 18.0 sequences, including skin tones and other variants |
35
35
 
36
36
  Skin tones and other variants count as additional Unicode sequences. Similar emoji may share a motion. Recognized emoji without a preset or reviewed family receive a category-based fallback.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dofy/native-emoji-motion",
3
- "version": "0.6.0",
3
+ "version": "0.6.4",
4
4
  "description": "Expressive, extensible animations for native emoji with optional SVG effects",
5
5
  "type": "module",
6
6
  "exports": {
package/src/presets.js CHANGED
@@ -101,11 +101,14 @@ const rolePeople = ['🧑', '👨', '👩'];
101
101
  const roleSuffixes = ['⚕️', '🎓', '🏫', '🌾', '🔧', '🔬', '🚒', '⚖️', '✈️', '🎤'];
102
102
  const roleMembers = roleSuffixes.flatMap(suffix => rolePeople.map(person => `${person}‍${suffix}`));
103
103
  const roleAliases = Object.fromEntries(roleSuffixes.flatMap(suffix => rolePeople.flatMap(person => Object.values(skinTones(person)).map(variant => [`${variant}‍${suffix}`, `${person}‍${suffix}`]))));
104
- const walkerMembers = ['🚶', '🚶‍♂️', '🚶‍♀️', '🏃‍♂️', '🏃‍♀️'];
105
- const walkerAliases = Object.fromEntries(walkerMembers.flatMap(glyph => {
104
+ // Gendered sequences take the modifier on the person, before the joiner.
105
+ const genderedToneAliases = glyphs => Object.fromEntries(glyphs.flatMap(glyph => {
106
106
  const [person, suffix = ''] = glyph.split('‍');
107
107
  return Object.values(skinTones(person)).map(variant => [suffix ? `${variant}‍${suffix}` : variant, glyph]);
108
108
  }));
109
+ const gendered = person => [person, `${person}‍♂️`, `${person}‍♀️`];
110
+ const walkerMembers = ['🚶', '🚶‍♂️', '🚶‍♀️', '🏃‍♂️', '🏃‍♀️'];
111
+ const walkerAliases = genderedToneAliases(walkerMembers);
109
112
  // Members are reviewed explicitly; no entire Unicode subgroup is assumed to move alike.
110
113
  export const families = {
111
114
  clocks: { label: 'Clocks', group: 'objects', motion: 'clockTick', members: ['⌚', '🕰️', '⏲️', '🕛', '🕧'], aliases: { '🕰': '🕰️', '⏲': '⏲️' } },
@@ -117,7 +120,7 @@ export const families = {
117
120
  handTools: { label: 'Hand tools', group: 'objects', motion: 'toolWiggle', members: ['🔨', '🪓', '🪚', '🪛', '🔧', '🪠', '🔩'] },
118
121
  books: { label: 'Books', group: 'objects', motion: 'bookPeek', members: ['📕', '📗', '📘', '📙', '📔', '📓', '📒'] },
119
122
  handGestures: { label: 'Hand gestures', group: 'gestures', motion: 'handEmphasis', members: ['🤏', '🫳', '🫴', '🫲', '🫱', '🫷', '🫸', '🤌', '🫵'], aliases: familyToneAliases(['🤏', '🫳', '🫴', '🫲', '🫱', '🫷', '🫸', '🤌', '🫵']) },
120
- vessels: { label: 'Boats', group: 'travel', motion: 'vesselRock', members: ['🚤', '🛥️', '🛳️', '⛴️', '🛶'], aliases: { '🛥': '🛥️', '🛳': '🛳️', '⛴': '⛴️' } },
123
+ vessels: { label: 'Boats', group: 'travel', motion: 'vesselRock', members: ['🚤', '🛥️', '🛳️', '⛴️', '🛶'], aliases: { '🛥': '🛥️', '🛳': '🛳️', '⛴': '⛴️' }, decoration: 'wake' },
121
124
  treats: { label: 'Sweet treats', group: 'food', motion: 'treatPop', members: ['🍬', '🍭', '🍫', '🍮', '🧁', '🍨'], decoration: 'sparkle' },
122
125
  fruit: { label: 'Fruit', group: 'food', motion: 'fruitBounce', members: ['🍊', '🍋', '🍐', '🍑', '🍇', '🫐', '🍒', '🥝', '🍍', '🥭'], decoration: 'sparkle' },
123
126
  animalFaces: { label: 'Animal faces', group: 'nature', motion: 'curiousTilt', members: ['🐶', '🦊', '🐹', '🦝', '🐨', '🐯', '🐭', '🐻‍❄️'] },
@@ -131,6 +134,10 @@ export const families = {
131
134
  poses: { label: 'People in poses', group: 'people', motion: 'pose', members: ['🧎', '🧍', '🙇'], aliases: familyToneAliases(['🧎', '🧍', '🙇']) },
132
135
  professionals: { label: 'Professionals', group: 'people', motion: 'pose', members: roleMembers, aliases: roleAliases },
133
136
  walkers: { label: 'Walking and running', group: 'people', motion: 'steps', members: walkerMembers, aliases: walkerAliases },
137
+ swimmers: { label: 'Swimmers', group: 'people', motion: 'bob', members: gendered('🏊'), aliases: genderedToneAliases(gendered('🏊')), decoration: 'ripple' },
138
+ surfers: { label: 'Surfers', group: 'people', motion: 'ride', members: gendered('🏄'), aliases: genderedToneAliases(gendered('🏄')), decoration: 'splash' },
139
+ waterPolo: { label: 'Water polo', group: 'people', motion: 'bob', members: gendered('🤽'), aliases: genderedToneAliases(gendered('🤽')), decoration: 'splash' },
140
+ rowers: { label: 'Rowers', group: 'people', motion: 'vesselRock', members: gendered('🚣'), aliases: genderedToneAliases(gendered('🚣')), decoration: 'wake' },
134
141
  seaLife: { label: 'Sea life', group: 'nature', motion: 'underwater', members: ['🐠', '🐡', '🦈', '🐋', '🐳', '🦭'], decoration: 'bubbles' },
135
142
  bakery: { label: 'Bakery', group: 'food', motion: 'pastryBounce', members: ['🍞', '🥐', '🥖', '🥨', '🥯', '🥞', '🧇'] },
136
143
  rail: { label: 'Rail vehicles', group: 'travel', motion: 'railRide', members: ['🚄', '🚅', '🚆', '🚇', '🚈', '🚊', '🚝', '🚞'] },
@@ -163,9 +170,9 @@ export const emoji = {
163
170
  '🔍': { group: 'objects', name: 'Magnifying glass', motion: 'search' }, '🔔': { group: 'objects', name: 'Bell', motion: 'swing', decoration: 'sound' }, '🎁': { group: 'objects', name: 'Gift', motion: 'shake' }, '📡': { group: 'objects', name: 'Satellite antenna', motion: 'wobble', decoration: 'signal' }, '⏱️': { group: 'objects', name: 'Stopwatch', motion: 'swing', variants: ['⏱'] },
164
171
  '💡': { group: 'objects', name: 'Light bulb', motion: 'breathe', decoration: 'glow' }, '📷': { group: 'objects', name: 'Camera', motion: 'bounce', decoration: 'flash' }, '🔑': { group: 'objects', name: 'Key', motion: 'wobble' }, '📌': { group: 'objects', name: 'Pushpin', motion: 'bounce' }, '📦': { group: 'objects', name: 'Package', motion: 'shake' }, '🧭': { group: 'objects', name: 'Compass', motion: 'wobble' },
165
172
  '🌸': { group: 'nature', name: 'Blossom', decoration: 'petals' }, '🍃': { group: 'nature', name: 'Leaves' }, '☀️': { group: 'nature', name: 'Sun', motion: 'spin', decoration: 'glow', variants: ['☀'] },
166
- '🌻': { group: 'nature', name: 'Sunflower', motion: 'leafSway' }, '🌈': { group: 'nature', name: 'Rainbow', motion: 'float' }, '🌙': { group: 'nature', name: 'Crescent moon', motion: 'float' }, '⭐': { group: 'nature', name: 'Star', motion: 'pulse', decoration: 'sparkle' }, '🔥': { group: 'nature', name: 'Fire', motion: 'wobble', decoration: 'flicker' }, '🌊': { group: 'nature', name: 'Water wave', motion: 'float' },
173
+ '🌻': { group: 'nature', name: 'Sunflower', motion: 'leafSway' }, '🌈': { group: 'nature', name: 'Rainbow', motion: 'float' }, '🌙': { group: 'nature', name: 'Crescent moon', motion: 'float' }, '⭐': { group: 'nature', name: 'Star', motion: 'pulse', decoration: 'sparkle' }, '🔥': { group: 'nature', name: 'Fire', motion: 'wobble', decoration: 'flicker' }, '🌊': { group: 'nature', name: 'Water wave', motion: 'float', decoration: 'splash' },
167
174
  '🚀': { group: 'travel', name: 'Rocket' }, '✈️': { group: 'travel', name: 'Airplane', motion: 'glide', variants: ['✈'] }, '🚲': { group: 'travel', name: 'Bicycle', motion: 'ride' },
168
- '🚗': { group: 'travel', name: 'Car', motion: 'ride' }, '🚂': { group: 'travel', name: 'Locomotive', motion: 'ride' }, '🛸': { group: 'travel', name: 'Flying saucer', motion: 'float' }, '🚢': { group: 'travel', name: 'Ship', motion: 'wobble' },
175
+ '🚗': { group: 'travel', name: 'Car', motion: 'ride' }, '🚂': { group: 'travel', name: 'Locomotive', motion: 'ride' }, '🛸': { group: 'travel', name: 'Flying saucer', motion: 'float' }, '🚢': { group: 'travel', name: 'Ship', motion: 'vesselRock', decoration: 'wake' },
169
176
  '✨': { group: 'symbols', name: 'Sparkles', motion: 'pulse', decoration: 'sparkle' }, '⚙️': { group: 'symbols', name: 'Gear', variants: ['⚙'] }, '🎯': { group: 'symbols', name: 'Target', motion: 'bounce' }, '🎵': { group: 'symbols', name: 'Musical note', motion: 'swing', decoration: 'sound' }, '💤': { group: 'symbols', name: 'Sleeping symbol', motion: 'float' }, '💥': { group: 'symbols', name: 'Collision', motion: 'pulse' }, '❗': { group: 'symbols', name: 'Exclamation mark', motion: 'bounce' }, '❓': { group: 'symbols', name: 'Question mark', motion: 'wobble' }, '✅': { group: 'symbols', name: 'Check mark', motion: 'bounce' },
170
177
  '🐰': { group: 'nature', name: 'Rabbit face', motion: 'hop' }, '🦋': { group: 'nature', name: 'Butterfly', motion: 'flutter' }, '🐝': { group: 'nature', name: 'Bee', motion: 'flutter' }, '🌱': { group: 'nature', name: 'Seedling', motion: 'sway' }, '🌧️': { group: 'nature', name: 'Cloud with rain', motion: 'float', decoration: 'rain', variants: ['🌧'] },
171
178
  '☕': { group: 'food', name: 'Hot beverage', motion: 'sip', decoration: 'steam' }, '🍵': { group: 'food', name: 'Teacup', motion: 'sip', decoration: 'steam' }, '🍕': { group: 'food', name: 'Pizza', motion: 'bounce' }, '🍦': { group: 'food', name: 'Soft ice cream', motion: 'sway' },
@@ -184,7 +191,7 @@ export const emoji = {
184
191
  '🫰': { group: 'gestures', name: 'Hand with index finger and thumb crossed', motion: 'pop', skinTones: skinTones('🫰') },
185
192
  '🐱': { group: 'nature', name: 'Cat face', motion: 'nod' }, '🐻': { group: 'nature', name: 'Bear', motion: 'nod' }, '🐼': { group: 'nature', name: 'Panda', motion: 'sway' },
186
193
  '🐸': { group: 'nature', name: 'Frog', motion: 'hop' }, '🐧': { group: 'nature', name: 'Penguin', motion: 'sway' }, '🐥': { group: 'nature', name: 'Front facing baby chick', motion: 'hop' },
187
- '🐢': { group: 'nature', name: 'Turtle', motion: 'bob' }, '🐟': { group: 'nature', name: 'Fish', motion: 'bob' }, '🐬': { group: 'nature', name: 'Dolphin', motion: 'bob' },
194
+ '🐢': { group: 'nature', name: 'Turtle', motion: 'bob' }, '🐟': { group: 'nature', name: 'Fish', motion: 'bob' }, '🐬': { group: 'nature', name: 'Dolphin', motion: 'hop', decoration: 'splash' },
188
195
  '🐙': { group: 'nature', name: 'Octopus', motion: 'twirl' }, '🌵': { group: 'nature', name: 'Cactus', motion: 'sway' }, '🌲': { group: 'nature', name: 'Evergreen tree', motion: 'sway' },
189
196
  '🌼': { group: 'nature', name: 'Blossom flower', motion: 'sway', decoration: 'petals' }, '❄️': { group: 'nature', name: 'Snowflake', motion: 'float', variants: ['❄'] }, '☁️': { group: 'nature', name: 'Cloud', motion: 'float', variants: ['☁'] },
190
197
  '🍎': { group: 'food', name: 'Red apple', motion: 'nod' }, '🍉': { group: 'food', name: 'Watermelon', motion: 'wobble' }, '🍓': { group: 'food', name: 'Strawberry', motion: 'bounce' },
@@ -196,14 +203,13 @@ export const emoji = {
196
203
  '🥁': { group: 'activities', name: 'Drum', motion: 'shake', decoration: 'sound' }, '🎮': { group: 'activities', name: 'Game controller', motion: 'wobble' }, '🪁': { group: 'activities', name: 'Kite', motion: 'flutter' },
197
204
  '🙋': { group: 'people', name: 'Person raising hand', motion: 'wave', skinTones: skinTones('🙋') }, '🧘': { group: 'people', name: 'Person in lotus position', motion: 'breathe', skinTones: skinTones('🧘') },
198
205
  '🏃': { group: 'people', name: 'Person running', motion: 'hop', skinTones: skinTones('🏃') }, '🤸': { group: 'people', name: 'Person cartwheeling', motion: 'twirl', skinTones: skinTones('🤸') },
199
- '🏊': { group: 'people', name: 'Person swimming', motion: 'bob', skinTones: skinTones('🏊') }, '🏄': { group: 'people', name: 'Person surfing', motion: 'bob', skinTones: skinTones('🏄') },
200
206
  '🧑‍🍳': { group: 'people', name: 'Cook', motion: 'nod', skinTones: personSkinTones('🍳') }, '🧑‍🎨': { group: 'people', name: 'Artist', motion: 'twirl', skinTones: personSkinTones('🎨') },
201
207
  '🧑‍🚀': { group: 'people', name: 'Astronaut', motion: 'float', skinTones: personSkinTones('🚀') }, '🧑‍💻': { group: 'people', name: 'Technologist', motion: 'nod', skinTones: personSkinTones('💻') },
202
208
  '📱': { group: 'objects', name: 'Mobile phone', motion: 'shake' }, '📚': { group: 'objects', name: 'Books', motion: 'nod' }, '✉️': { group: 'objects', name: 'Envelope', motion: 'bob', variants: ['✉'] },
203
209
  '💌': { group: 'objects', name: 'Love letter', motion: 'bob', decoration: 'sparkle' }, '🔒': { group: 'objects', name: 'Locked', motion: 'wobble' }, '🔓': { group: 'objects', name: 'Unlocked', motion: 'pop' },
204
210
  '🔦': { group: 'objects', name: 'Flashlight', motion: 'nod', decoration: 'flash' }, '🪄': { group: 'objects', name: 'Magic wand', motion: 'twirl', decoration: 'sparkle' }, '🎤': { group: 'objects', name: 'Microphone', motion: 'swing', decoration: 'sound' },
205
211
  '🚌': { group: 'travel', name: 'Bus', motion: 'ride' }, '🚕': { group: 'travel', name: 'Taxi', motion: 'ride' }, '🚁': { group: 'travel', name: 'Helicopter', motion: 'bob' },
206
- '🎡': { group: 'travel', name: 'Ferris wheel', motion: 'steady' }, '⛵': { group: 'travel', name: 'Sailboat', motion: 'bob' }, '🎠': { group: 'travel', name: 'Carousel horse', motion: 'carousel' },
212
+ '🎡': { group: 'travel', name: 'Ferris wheel', motion: 'steady' }, '⛵': { group: 'travel', name: 'Sailboat', motion: 'vesselRock', decoration: 'wake' }, '🎠': { group: 'travel', name: 'Carousel horse', motion: 'carousel' },
207
213
  // Direction-neutral lighting for other objects that naturally emit or reflect light.
208
214
  '📸': { group: 'objects', name: 'Camera with flash', motion: 'bounce', decoration: 'flash' },
209
215
  '🪔': { group: 'objects', name: 'Oil lamp', motion: 'sway', decoration: 'flicker' },
@@ -219,6 +225,9 @@ export const emoji = {
219
225
  '💎': { group: 'objects', name: 'Gem stone', motion: 'pulse', decoration: 'sparkle' },
220
226
  '🪩': { group: 'objects', name: 'Mirror ball', motion: 'spin', decoration: 'sparkle' },
221
227
  '🌠': { group: 'nature', name: 'Shooting star', motion: 'float', decoration: 'sparkle' },
228
+ // Water gear borrows the sea life and boat motions with matching water effects.
229
+ '🤿': { group: 'activities', name: 'Diving mask', motion: 'underwater', decoration: 'bubbles' },
230
+ '🛟': { group: 'travel', name: 'Ring buoy', motion: 'vesselRock', decoration: 'wake' },
222
231
  '🪙': { group: 'objects', name: 'Coin', motion: 'flipHorizontal' },
223
232
  '🎲': { group: 'activities', name: 'Game die', motion: 'spin' }
224
233
  };
@@ -238,6 +247,21 @@ const centered = { width: '160%', height: '160%', overflow: 'visible' };
238
247
  const steamShape = { viewBox: '0 0 100 100', elements: [
239
248
  { tag: 'path', attrs: { d: 'M38 30c-5-5 5-8 0-14M50 27c-5-5 5-8 0-14M62 30c-5-5 5-8 0-14', fill: 'none', stroke: '#a29aa5', 'stroke-width': '2.5', 'stroke-linecap': 'round' } }
240
249
  ] };
250
+ // Water effects sit at the waterline below the glyph and spread symmetrically.
251
+ // y is the waterline in viewBox units: at the body for swimmers, below the hull for boats.
252
+ const rippleRings = y => [0, 320].map(delay => ({
253
+ svg: { viewBox: '0 0 100 100', elements: [{ tag: 'ellipse', attrs: { cx: '50', cy: String(y), rx: '30', ry: '6', fill: 'none', stroke: '#7cafd0', 'stroke-width': '2.5' } }] },
254
+ style: { ...centered, transformOrigin: `50% ${y}%` },
255
+ keyframes: [{ opacity: .8, transform: 'translate(-50%, -50%) scale(.5)' }, { opacity: 0, transform: 'translate(-50%, -50%) scale(1.25)' }],
256
+ options: { duration: 1100, delay, easing: 'cubic-bezier(.25,.6,.4,1)', fill: 'backwards' }
257
+ }));
258
+ const splashShape = { viewBox: '0 0 100 100', elements: [
259
+ { tag: 'path', attrs: { d: 'M39 70l-9-8M45 67l-3-10M61 70l9-8M55 67l3-10', fill: 'none', stroke: '#7cafd0', 'stroke-width': '2.5', 'stroke-linecap': 'round' } },
260
+ { tag: 'circle', attrs: { cx: '25', cy: '56', r: '2.6', fill: '#7cafd0' } },
261
+ { tag: 'circle', attrs: { cx: '39', cy: '50', r: '2', fill: '#7cafd0' } },
262
+ { tag: 'circle', attrs: { cx: '75', cy: '56', r: '2.6', fill: '#7cafd0' } },
263
+ { tag: 'circle', attrs: { cx: '61', cy: '50', r: '2', fill: '#7cafd0' } }
264
+ ] };
241
265
  const rainShape = { viewBox: '0 0 100 100', elements: [
242
266
  { tag: 'path', attrs: { d: 'M30 67v6M50 72v6M70 67v6', fill: 'none', stroke: '#7c9fca', 'stroke-width': '3', 'stroke-linecap': 'round' } }
243
267
  ] };
@@ -281,6 +305,12 @@ export const decorations = {
281
305
  keyframes: [{ opacity: 0, transform: 'translate(-50%, -55%)' }, { opacity: .9, transform: 'translate(-50%, -50%)', offset: .3 }, { opacity: 0, transform: 'translate(-50%, -40%)' }],
282
306
  options: { duration: 950, easing: 'cubic-bezier(.22,1,.36,1)' }
283
307
  }] },
308
+ ripple: { anchor: { x: '50%', y: '50%' }, layers: rippleRings(66) },
309
+ wake: { anchor: { x: '50%', y: '50%' }, layers: rippleRings(75) },
310
+ splash: { anchor: { x: '50%', y: '50%' }, layers: [{ svg: splashShape, style: { ...centered, transformOrigin: '50% 70%' },
311
+ keyframes: [{ opacity: 0, transform: 'translate(-50%, -50%) scale(.55)' }, { opacity: .9, transform: 'translate(-50%, -50%) scale(1)', offset: .35 }, { opacity: 0, transform: 'translate(-50%, -54%) scale(1.15)' }],
312
+ options: { duration: 850, easing: curve }
313
+ }] },
284
314
  signal: {
285
315
  anchor: { x: '50%', y: '50%' },
286
316
  layers: [0, 180, 360].map(delay => ({