@dofy/native-emoji-motion 0.5.1 → 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 +12 -0
- package/README.md +4 -4
- package/package.json +1 -1
- package/src/presets.js +59 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
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
|
+
|
|
9
|
+
## 0.6.0 — 2026-11-14
|
|
10
|
+
|
|
11
|
+
- Add motion families for vegetables, land animals, clothing, tools, books, hand gestures, boats and sweet treats.
|
|
12
|
+
- Support the official skin tone variants for the new hand gestures.
|
|
13
|
+
- Add gentle ticking, falling and settling motions for clocks, rain and containers.
|
|
14
|
+
|
|
3
15
|
## 0.5.1 — 2026-11-13
|
|
4
16
|
|
|
5
17
|
- Fix the npm package page to show the English README by default.
|
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.
|
|
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 |
|
|
30
|
+
| Reviewed motion families | 31 | 212 additional base emoji sharing reviewed motions |
|
|
31
31
|
| Categories | 10 | Groups used to organize the collection |
|
|
32
|
-
| Reusable motions |
|
|
33
|
-
| Emoji with optional SVG effects |
|
|
32
|
+
| Reusable motions | 53 | Motions shared by presets and families |
|
|
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
package/src/presets.js
CHANGED
|
@@ -61,6 +61,17 @@ export const motions = {
|
|
|
61
61
|
cloudDrift: { keyframes: [{ transform: 'translate(0, 0)' }, { transform: 'translate(-5%, -4%)', offset: .35 }, { transform: 'translate(5%, -4%)', offset: .7 }, { transform: 'translate(0, 0)' }], options: { duration: 1400, easing: curve } },
|
|
62
62
|
orbitFloat: { keyframes: [{ transform: 'translateY(0) scale(1)' }, { transform: 'translateY(-9%) scale(1.04)', offset: .45 }, { transform: 'translateY(0) scale(1)' }], options: { duration: 1200, easing: curve } },
|
|
63
63
|
writingTilt: { keyframes: [{ transform: 'rotate(0deg)' }, { transform: 'rotate(-5deg)', offset: .3 }, { transform: 'rotate(5deg)', offset: .65 }, { transform: 'rotate(0deg)' }], options: { duration: 1000, easing: curve }, origin: '50% 65%' },
|
|
64
|
+
gardenBounce: { keyframes: [{ transform: 'translateY(0) rotate(0deg)' }, { transform: 'translateY(-6%) rotate(-3deg)', offset: .32 }, { transform: 'translateY(-3%) rotate(3deg)', offset: .65 }, { transform: 'translateY(0) rotate(0deg)' }], options: { duration: 1050, easing: curve }, origin: '50% 85%' },
|
|
65
|
+
animalHop: { keyframes: [{ transform: 'translateY(0) scale(1)' }, { transform: 'translateY(-11%) scale(1.02,.98)', offset: .35 }, { transform: 'translateY(0) scale(1.04,.96)', offset: .68 }, { transform: 'translateY(0) scale(1)' }], options: { duration: 950, easing: curve }, origin: '50% 90%' },
|
|
66
|
+
fabricSway: createSwayMotion({ angle: 6, duration: 1250, origin: '50% 15%' }),
|
|
67
|
+
toolWiggle: createSwayMotion({ angle: 7, duration: 900, origin: '50% 70%' }),
|
|
68
|
+
bookPeek: { keyframes: [{ transform: 'perspective(500px) rotateY(0deg) translateY(0)' }, { transform: 'perspective(500px) rotateY(-20deg) translateY(-3%)', offset: .32 }, { transform: 'perspective(500px) rotateY(20deg) translateY(-3%)', offset: .68 }, { transform: 'perspective(500px) rotateY(0deg) translateY(0)' }], options: { duration: 1050, easing: curve } },
|
|
69
|
+
handEmphasis: { keyframes: [{ transform: 'translateY(0) scale(1)' }, { transform: 'translateY(-5%) scale(1.08)', offset: .35 }, { transform: 'translateY(0) scale(.98)', offset: .7 }, { transform: 'translateY(0) scale(1)' }], options: { duration: 800, easing: curve }, origin: '50% 80%' },
|
|
70
|
+
vesselRock: { keyframes: [{ transform: 'translateY(0) rotate(0deg)' }, { transform: 'translateY(-4%) rotate(-4deg)', offset: .3 }, { transform: 'translateY(2%) rotate(4deg)', offset: .68 }, { transform: 'translateY(0) rotate(0deg)' }], options: { duration: 1300, easing: curve }, origin: '50% 80%' },
|
|
71
|
+
treatPop: { keyframes: [{ transform: 'scale(1)' }, { transform: 'scale(.96)', offset: .22 }, { transform: 'scale(1.08)', offset: .5 }, { transform: 'scale(1)' }], options: { duration: 850, easing: curve } },
|
|
72
|
+
clockTick: { keyframes: [{ transform: 'rotate(0deg)' }, { transform: 'rotate(-3deg)', offset: .25 }, { transform: 'rotate(3deg)', offset: .5 }, { transform: 'rotate(-2deg)', offset: .75 }, { transform: 'rotate(0deg)' }], options: { duration: 1200, easing: curve }, origin: '50% 50%' },
|
|
73
|
+
gentleFall: { keyframes: [{ transform: 'translateY(0) scale(1)' }, { transform: 'translateY(7%) scale(.98,1.02)', offset: .45 }, { transform: 'translateY(0) scale(1)' }], options: { duration: 1100, easing: curve } },
|
|
74
|
+
softSettle: { keyframes: [{ transform: 'translateY(0) scale(1)' }, { transform: 'translateY(-5%) scale(1)', offset: .35 }, { transform: 'translateY(0) scale(1.03,.97)', offset: .7 }, { transform: 'translateY(0) scale(1)' }], options: { duration: 950, easing: curve }, origin: '50% 90%' },
|
|
64
75
|
steps: { keyframes: [{ transform: 'translateY(0)' }, { transform: 'translateY(-7%)', offset: .25 }, { transform: 'translateY(0)', offset: .5 }, { transform: 'translateY(-5%)', offset: .75 }, { transform: 'translateY(0)' }], options: { duration: 1000, easing: curve }, origin: '50% 90%' }
|
|
65
76
|
};
|
|
66
77
|
|
|
@@ -90,13 +101,27 @@ const rolePeople = ['🧑', '👨', '👩'];
|
|
|
90
101
|
const roleSuffixes = ['⚕️', '🎓', '🏫', '🌾', '🔧', '🔬', '🚒', '⚖️', '✈️', '🎤'];
|
|
91
102
|
const roleMembers = roleSuffixes.flatMap(suffix => rolePeople.map(person => `${person}${suffix}`));
|
|
92
103
|
const roleAliases = Object.fromEntries(roleSuffixes.flatMap(suffix => rolePeople.flatMap(person => Object.values(skinTones(person)).map(variant => [`${variant}${suffix}`, `${person}${suffix}`]))));
|
|
93
|
-
|
|
94
|
-
const
|
|
104
|
+
// Gendered sequences take the modifier on the person, before the joiner.
|
|
105
|
+
const genderedToneAliases = glyphs => Object.fromEntries(glyphs.flatMap(glyph => {
|
|
95
106
|
const [person, suffix = ''] = glyph.split('');
|
|
96
107
|
return Object.values(skinTones(person)).map(variant => [suffix ? `${variant}${suffix}` : variant, glyph]);
|
|
97
108
|
}));
|
|
109
|
+
const gendered = person => [person, `${person}♂️`, `${person}♀️`];
|
|
110
|
+
const walkerMembers = ['🚶', '🚶♂️', '🚶♀️', '🏃♂️', '🏃♀️'];
|
|
111
|
+
const walkerAliases = genderedToneAliases(walkerMembers);
|
|
98
112
|
// Members are reviewed explicitly; no entire Unicode subgroup is assumed to move alike.
|
|
99
113
|
export const families = {
|
|
114
|
+
clocks: { label: 'Clocks', group: 'objects', motion: 'clockTick', members: ['⌚', '🕰️', '⏲️', '🕛', '🕧'], aliases: { '🕰': '🕰️', '⏲': '⏲️' } },
|
|
115
|
+
rain: { label: 'Rain and water', group: 'nature', motion: 'gentleFall', members: ['💧', '💦', '🌨️', '☔'], aliases: { '🌨': '🌨️' } },
|
|
116
|
+
containers: { label: 'Containers', group: 'objects', motion: 'softSettle', members: ['🧳', '🧰', '🧺', '🗃️', '🗄️', '🗳️', '🗑️'], aliases: { '🗃': '🗃️', '🗄': '🗄️', '🗳': '🗳️', '🗑': '🗑️' } },
|
|
117
|
+
vegetables: { label: 'Vegetables', group: 'food', motion: 'gardenBounce', members: ['🥕', '🥦', '🥬', '🥒', '🌽', '🫑', '🍆', '🧅', '🧄', '🥔'] },
|
|
118
|
+
landAnimals: { label: 'Land animals', group: 'nature', motion: 'animalHop', members: ['🐕', '🐈', '🐖', '🐑', '🐐', '🐄', '🐎', '🦌', '🐘', '🦒'] },
|
|
119
|
+
clothing: { label: 'Clothing', group: 'objects', motion: 'fabricSway', members: ['👕', '👖', '🧥', '👗', '🩳', '👚', '👔'] },
|
|
120
|
+
handTools: { label: 'Hand tools', group: 'objects', motion: 'toolWiggle', members: ['🔨', '🪓', '🪚', '🪛', '🔧', '🪠', '🔩'] },
|
|
121
|
+
books: { label: 'Books', group: 'objects', motion: 'bookPeek', members: ['📕', '📗', '📘', '📙', '📔', '📓', '📒'] },
|
|
122
|
+
handGestures: { label: 'Hand gestures', group: 'gestures', motion: 'handEmphasis', members: ['🤏', '🫳', '🫴', '🫲', '🫱', '🫷', '🫸', '🤌', '🫵'], aliases: familyToneAliases(['🤏', '🫳', '🫴', '🫲', '🫱', '🫷', '🫸', '🤌', '🫵']) },
|
|
123
|
+
vessels: { label: 'Boats', group: 'travel', motion: 'vesselRock', members: ['🚤', '🛥️', '🛳️', '⛴️', '🛶'], aliases: { '🛥': '🛥️', '🛳': '🛳️', '⛴': '⛴️' }, decoration: 'wake' },
|
|
124
|
+
treats: { label: 'Sweet treats', group: 'food', motion: 'treatPop', members: ['🍬', '🍭', '🍫', '🍮', '🧁', '🍨'], decoration: 'sparkle' },
|
|
100
125
|
fruit: { label: 'Fruit', group: 'food', motion: 'fruitBounce', members: ['🍊', '🍋', '🍐', '🍑', '🍇', '🫐', '🍒', '🥝', '🍍', '🥭'], decoration: 'sparkle' },
|
|
101
126
|
animalFaces: { label: 'Animal faces', group: 'nature', motion: 'curiousTilt', members: ['🐶', '🦊', '🐹', '🦝', '🐨', '🐯', '🐭', '🐻❄️'] },
|
|
102
127
|
clouds: { label: 'Clouds', group: 'nature', motion: 'cloudDrift', members: ['🌤️', '⛅', '🌥️', '🌦️', '🌫️'], aliases: { '🌤': '🌤️', '🌥': '🌥️', '🌦': '🌦️', '🌫': '🌫️' } },
|
|
@@ -109,6 +134,10 @@ export const families = {
|
|
|
109
134
|
poses: { label: 'People in poses', group: 'people', motion: 'pose', members: ['🧎', '🧍', '🙇'], aliases: familyToneAliases(['🧎', '🧍', '🙇']) },
|
|
110
135
|
professionals: { label: 'Professionals', group: 'people', motion: 'pose', members: roleMembers, aliases: roleAliases },
|
|
111
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' },
|
|
112
141
|
seaLife: { label: 'Sea life', group: 'nature', motion: 'underwater', members: ['🐠', '🐡', '🦈', '🐋', '🐳', '🦭'], decoration: 'bubbles' },
|
|
113
142
|
bakery: { label: 'Bakery', group: 'food', motion: 'pastryBounce', members: ['🍞', '🥐', '🥖', '🥨', '🥯', '🥞', '🧇'] },
|
|
114
143
|
rail: { label: 'Rail vehicles', group: 'travel', motion: 'railRide', members: ['🚄', '🚅', '🚆', '🚇', '🚈', '🚊', '🚝', '🚞'] },
|
|
@@ -141,9 +170,9 @@ export const emoji = {
|
|
|
141
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: ['⏱'] },
|
|
142
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' },
|
|
143
172
|
'🌸': { group: 'nature', name: 'Blossom', decoration: 'petals' }, '🍃': { group: 'nature', name: 'Leaves' }, '☀️': { group: 'nature', name: 'Sun', motion: 'spin', decoration: 'glow', variants: ['☀'] },
|
|
144
|
-
'🌻': { 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' },
|
|
145
174
|
'🚀': { group: 'travel', name: 'Rocket' }, '✈️': { group: 'travel', name: 'Airplane', motion: 'glide', variants: ['✈'] }, '🚲': { group: 'travel', name: 'Bicycle', motion: 'ride' },
|
|
146
|
-
'🚗': { group: 'travel', name: 'Car', motion: 'ride' }, '🚂': { group: 'travel', name: 'Locomotive', motion: 'ride' }, '🛸': { group: 'travel', name: 'Flying saucer', motion: 'float' }, '🚢': { group: 'travel', name: 'Ship', motion: '
|
|
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' },
|
|
147
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' },
|
|
148
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: ['🌧'] },
|
|
149
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' },
|
|
@@ -162,7 +191,7 @@ export const emoji = {
|
|
|
162
191
|
'🫰': { group: 'gestures', name: 'Hand with index finger and thumb crossed', motion: 'pop', skinTones: skinTones('🫰') },
|
|
163
192
|
'🐱': { group: 'nature', name: 'Cat face', motion: 'nod' }, '🐻': { group: 'nature', name: 'Bear', motion: 'nod' }, '🐼': { group: 'nature', name: 'Panda', motion: 'sway' },
|
|
164
193
|
'🐸': { group: 'nature', name: 'Frog', motion: 'hop' }, '🐧': { group: 'nature', name: 'Penguin', motion: 'sway' }, '🐥': { group: 'nature', name: 'Front facing baby chick', motion: 'hop' },
|
|
165
|
-
'🐢': { group: 'nature', name: 'Turtle', motion: 'bob' }, '🐟': { group: 'nature', name: 'Fish', motion: 'bob' }, '🐬': { group: 'nature', name: 'Dolphin', motion: '
|
|
194
|
+
'🐢': { group: 'nature', name: 'Turtle', motion: 'bob' }, '🐟': { group: 'nature', name: 'Fish', motion: 'bob' }, '🐬': { group: 'nature', name: 'Dolphin', motion: 'hop', decoration: 'splash' },
|
|
166
195
|
'🐙': { group: 'nature', name: 'Octopus', motion: 'twirl' }, '🌵': { group: 'nature', name: 'Cactus', motion: 'sway' }, '🌲': { group: 'nature', name: 'Evergreen tree', motion: 'sway' },
|
|
167
196
|
'🌼': { group: 'nature', name: 'Blossom flower', motion: 'sway', decoration: 'petals' }, '❄️': { group: 'nature', name: 'Snowflake', motion: 'float', variants: ['❄'] }, '☁️': { group: 'nature', name: 'Cloud', motion: 'float', variants: ['☁'] },
|
|
168
197
|
'🍎': { group: 'food', name: 'Red apple', motion: 'nod' }, '🍉': { group: 'food', name: 'Watermelon', motion: 'wobble' }, '🍓': { group: 'food', name: 'Strawberry', motion: 'bounce' },
|
|
@@ -174,14 +203,13 @@ export const emoji = {
|
|
|
174
203
|
'🥁': { group: 'activities', name: 'Drum', motion: 'shake', decoration: 'sound' }, '🎮': { group: 'activities', name: 'Game controller', motion: 'wobble' }, '🪁': { group: 'activities', name: 'Kite', motion: 'flutter' },
|
|
175
204
|
'🙋': { group: 'people', name: 'Person raising hand', motion: 'wave', skinTones: skinTones('🙋') }, '🧘': { group: 'people', name: 'Person in lotus position', motion: 'breathe', skinTones: skinTones('🧘') },
|
|
176
205
|
'🏃': { group: 'people', name: 'Person running', motion: 'hop', skinTones: skinTones('🏃') }, '🤸': { group: 'people', name: 'Person cartwheeling', motion: 'twirl', skinTones: skinTones('🤸') },
|
|
177
|
-
'🏊': { group: 'people', name: 'Person swimming', motion: 'bob', skinTones: skinTones('🏊') }, '🏄': { group: 'people', name: 'Person surfing', motion: 'bob', skinTones: skinTones('🏄') },
|
|
178
206
|
'🧑🍳': { group: 'people', name: 'Cook', motion: 'nod', skinTones: personSkinTones('🍳') }, '🧑🎨': { group: 'people', name: 'Artist', motion: 'twirl', skinTones: personSkinTones('🎨') },
|
|
179
207
|
'🧑🚀': { group: 'people', name: 'Astronaut', motion: 'float', skinTones: personSkinTones('🚀') }, '🧑💻': { group: 'people', name: 'Technologist', motion: 'nod', skinTones: personSkinTones('💻') },
|
|
180
208
|
'📱': { group: 'objects', name: 'Mobile phone', motion: 'shake' }, '📚': { group: 'objects', name: 'Books', motion: 'nod' }, '✉️': { group: 'objects', name: 'Envelope', motion: 'bob', variants: ['✉'] },
|
|
181
209
|
'💌': { group: 'objects', name: 'Love letter', motion: 'bob', decoration: 'sparkle' }, '🔒': { group: 'objects', name: 'Locked', motion: 'wobble' }, '🔓': { group: 'objects', name: 'Unlocked', motion: 'pop' },
|
|
182
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' },
|
|
183
211
|
'🚌': { group: 'travel', name: 'Bus', motion: 'ride' }, '🚕': { group: 'travel', name: 'Taxi', motion: 'ride' }, '🚁': { group: 'travel', name: 'Helicopter', motion: 'bob' },
|
|
184
|
-
'🎡': { group: 'travel', name: 'Ferris wheel', motion: 'steady' }, '⛵': { group: 'travel', name: 'Sailboat', motion: '
|
|
212
|
+
'🎡': { group: 'travel', name: 'Ferris wheel', motion: 'steady' }, '⛵': { group: 'travel', name: 'Sailboat', motion: 'vesselRock', decoration: 'wake' }, '🎠': { group: 'travel', name: 'Carousel horse', motion: 'carousel' },
|
|
185
213
|
// Direction-neutral lighting for other objects that naturally emit or reflect light.
|
|
186
214
|
'📸': { group: 'objects', name: 'Camera with flash', motion: 'bounce', decoration: 'flash' },
|
|
187
215
|
'🪔': { group: 'objects', name: 'Oil lamp', motion: 'sway', decoration: 'flicker' },
|
|
@@ -197,6 +225,9 @@ export const emoji = {
|
|
|
197
225
|
'💎': { group: 'objects', name: 'Gem stone', motion: 'pulse', decoration: 'sparkle' },
|
|
198
226
|
'🪩': { group: 'objects', name: 'Mirror ball', motion: 'spin', decoration: 'sparkle' },
|
|
199
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' },
|
|
200
231
|
'🪙': { group: 'objects', name: 'Coin', motion: 'flipHorizontal' },
|
|
201
232
|
'🎲': { group: 'activities', name: 'Game die', motion: 'spin' }
|
|
202
233
|
};
|
|
@@ -216,6 +247,21 @@ const centered = { width: '160%', height: '160%', overflow: 'visible' };
|
|
|
216
247
|
const steamShape = { viewBox: '0 0 100 100', elements: [
|
|
217
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' } }
|
|
218
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
|
+
] };
|
|
219
265
|
const rainShape = { viewBox: '0 0 100 100', elements: [
|
|
220
266
|
{ tag: 'path', attrs: { d: 'M30 67v6M50 72v6M70 67v6', fill: 'none', stroke: '#7c9fca', 'stroke-width': '3', 'stroke-linecap': 'round' } }
|
|
221
267
|
] };
|
|
@@ -259,6 +305,12 @@ export const decorations = {
|
|
|
259
305
|
keyframes: [{ opacity: 0, transform: 'translate(-50%, -55%)' }, { opacity: .9, transform: 'translate(-50%, -50%)', offset: .3 }, { opacity: 0, transform: 'translate(-50%, -40%)' }],
|
|
260
306
|
options: { duration: 950, easing: 'cubic-bezier(.22,1,.36,1)' }
|
|
261
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
|
+
}] },
|
|
262
314
|
signal: {
|
|
263
315
|
anchor: { x: '50%', y: '50%' },
|
|
264
316
|
layers: [0, 180, 360].map(delay => ({
|