// Single source of truth for the avatar-decoration CDN base URL. // scripts/syncDecorations.mjs reads this exact `DECORATION_CDN` declaration out // of this file at runtime (by regex) instead of re-declaring it, so the two can // never drift. If you migrate the CDN, change it here ONLY — keep the // `export const DECORATION_CDN = '...'` shape so the sync script can still parse it. export const DECORATION_CDN = 'https://drive.lotusguild.org/public.php/dav/files/bHswJ9pNKp2t26N/cinny-decorations'; // Runtime base. A deployment can repoint the decorations at a different host // without editing the catalog literal above (which scripts/syncDecorations.mjs // and the build read) by setting `VITE_DECORATION_CDN`; otherwise it falls back // to DECORATION_CDN. `import.meta.env` is undefined under the tsx test runner, // hence the guard. Trailing slashes are trimmed so `decorationUrl` stays clean. const envDecorationCdn = (import.meta as unknown as { env?: Record }) .env?.VITE_DECORATION_CDN; const RESOLVED_DECORATION_CDN = (envDecorationCdn || DECORATION_CDN).replace(/\/+$/, ''); export type AvatarDecoration = { slug: string; name: string; }; export type DecorationCategory = { id: string; label: string; decorations: AvatarDecoration[]; }; export const DECORATION_CATEGORIES: DecorationCategory[] = [ { id: 'gaming', label: 'Gaming', decorations: [ { slug: 'slither_n_snack', name: "Slither 'n Snack" }, { slug: 'joystick', name: 'Joystick' }, { slug: 'mallow_jump', name: 'Mallow Jump' }, { slug: 'hot_shot', name: 'Hot Shot' }, { slug: 'pipedream', name: 'Pipedream' }, { slug: 'disxcore_headset', name: 'Gaming Headset' }, { slug: 'pink_headset', name: 'Pink Headset' }, { slug: 'green_headset', name: 'Green Headset' }, { slug: 'feelin_awe', name: "Feelin' Awe" }, { slug: 'feelin_panic', name: "Feelin' Panic" }, { slug: 'feelin_nervous', name: "Feelin' Nervous" }, { slug: 'feelin_scrumptious', name: "Feelin' Scrumptious" }, ], }, { id: 'cyber', label: 'Cyber', decorations: [ { slug: 'cybernetic', name: 'Cybernetic' }, { slug: 'glitch', name: 'Glitch' }, { slug: 'digital_sunrise', name: 'Digital Sunrise' }, { slug: 'implant', name: 'Implant' }, { slug: 'blue_futuristic_ui', name: 'Futuristic UI (Blue)' }, { slug: 'green_futuristic_ui', name: 'Futuristic UI (Green)' }, { slug: 'pink_futuristic_ui', name: 'Futuristic UI (Pink)' }, { slug: 'chromawave', name: 'Chromawave' }, { slug: 'hex_lights', name: 'Hex Lights' }, ], }, { id: 'space', label: 'Space', decorations: [ { slug: 'stardust', name: 'Stardust' }, { slug: 'black_hole', name: 'Black Hole' }, { slug: 'constellations', name: 'Constellations' }, { slug: 'solar_orbit', name: 'Solar Orbit' }, { slug: 'astronaut_helmet', name: 'Astronaut Helmet' }, { slug: 'ufo', name: 'UFO' }, { slug: 'aurora', name: 'Aurora' }, ], }, { id: 'fantasy', label: 'Fantasy', decorations: [ { slug: 'kitsune', name: 'Kitsune' }, { slug: 'phoenix', name: 'Phoenix' }, { slug: 'unicorn', name: 'Unicorn' }, { slug: 'flaming_sword', name: 'Flaming Sword' }, { slug: 'skull_medallion', name: 'Skull Medallion' }, { slug: 'glowing_runes', name: 'Glowing Runes' }, { slug: 'eldritch_ring', name: 'Eldritch Ring' }, { slug: 'arcane_sigil', name: 'Arcane Sigil' }, { slug: 'midnight_sorceress', name: 'Midnight Sorceress' }, { slug: 'deaths_edge', name: "Death's Edge" }, { slug: 'malefic_crown', name: 'Malefic Crown' }, { slug: 'spirit_embers', name: 'Spirit Embers' }, { slug: 'defensive_shield', name: 'Defensive Shield' }, { slug: 'magical_potion', name: 'Magical Potion' }, { slug: 'wizards_staff', name: "Wizard's Staff" }, { slug: 'crystal_ball_purple', name: 'Crystal Ball (Purple)' }, { slug: 'crystal_ball_blue', name: 'Crystal Ball (Blue)' }, { slug: 'owlbear_cub', name: 'Owlbear Cub' }, { slug: 'owlbear_cub_snowy', name: 'Snowy Owlbear Cub' }, { slug: 'baby_displacer_beast', name: 'Baby Displacer Beast' }, { slug: 'dice_violet', name: 'Violet Dice' }, { slug: 'dice_azure', name: 'Azure Dice' }, ], }, { id: 'elements', label: 'Elements', decorations: [ { slug: 'fire', name: 'Fire' }, { slug: 'water', name: 'Water' }, { slug: 'air', name: 'Air' }, { slug: 'earth', name: 'Earth' }, { slug: 'lightning', name: 'Lightning' }, { slug: 'balance', name: 'Balance' }, { slug: 'ki_energy', name: 'Ki Energy' }, ], }, { id: 'japanese', label: 'Japanese', decorations: [ { slug: 'kabuto', name: 'Kabuto' }, { slug: 'oni_mask', name: 'Oni Mask' }, { slug: 'sakura_warrior', name: 'Sakura Warrior' }, { slug: 'sakura_ink', name: 'Sakura Ink' }, { slug: 'shurikens_mask', name: "Shuriken's Mask" }, { slug: 'straw_hat', name: 'Straw Hat' }, ], }, { id: 'nature', label: 'Nature', decorations: [ { slug: 'lotus_flower', name: 'Lotus Flower' }, { slug: 'koi_pond', name: 'Koi Pond' }, { slug: 'sakura', name: 'Sakura' }, { slug: 'sakura_pink', name: 'Pink Sakura' }, { slug: 'fall_leaves', name: 'Fall Leaves' }, { slug: 'fall_leaves_scarlet', name: 'Scarlet Leaves' }, { slug: 'butterflies', name: 'Butterflies' }, { slug: 'honeyblossom', name: 'Honeyblossom' }, { slug: 'dandelion_duo', name: 'Dandelion Duo' }, { slug: 'lunar_lanterns', name: 'Lunar Lanterns' }, { slug: 'firecrackers', name: 'Firecrackers' }, { slug: 'dragons_smile', name: "Dragon's Smile" }, ], }, { id: 'spooky', label: 'Spooky', decorations: [ { slug: 'candlelight', name: 'Candlelight' }, { slug: 'candlelight_crimson', name: 'Crimson Candlelight' }, { slug: 'witch_hat_midnight', name: 'Midnight Witch Hat' }, { slug: 'witch_hat_plum', name: 'Plum Witch Hat' }, { slug: 'hood_dark', name: 'Dark Hood' }, { slug: 'hood_crimson', name: 'Crimson Hood' }, { slug: 'zombie_food', name: 'Zombie Food' }, { slug: 'bloodthirsty', name: 'Bloodthirsty' }, { slug: 'bloodthirsty_gold', name: 'Bloodthirsty (Gold)' }, { slug: 'jack_o_lantern', name: "Jack-o'-Lantern" }, { slug: 'pumpkin_spice', name: 'Pumpkin Spice' }, { slug: 'spooky_cat_ears', name: 'Spooky Cat Ears' }, { slug: 'ghosts', name: 'Ghosts' }, ], }, { id: 'cozy', label: 'Cozy', decorations: [ { slug: 'cozy_cat', name: 'Cozy Cat' }, { slug: 'rainy_mood', name: 'Rainy Mood' }, { slug: 'oasis', name: 'Oasis' }, { slug: 'cozy_headphones', name: 'Cozy Headphones' }, { slug: 'doodling', name: 'Doodling' }, { slug: 'fox_hat', name: 'Fox Hat' }, { slug: 'fox_hat_chestnut', name: 'Chestnut Fox Hat' }, { slug: 'fox_hat_snow', name: 'Snow Fox Hat' }, { slug: 'cat_ears', name: 'Cat Ears' }, { slug: 'frog_hat', name: 'Frog Hat' }, { slug: 'polar_bear_hat', name: 'Polar Bear Hat' }, ], }, { id: 'reactions', label: 'Reactions', decorations: [ { slug: 'shy', name: 'Shy' }, { slug: 'shy_in_love', name: 'Shy (In Love)' }, { slug: 'rumbling', name: 'Rumbling' }, { slug: 'angry', name: 'Angry' }, { slug: 'angry_pink', name: 'Angry (Pink)' }, { slug: 'angry_yellow', name: 'Angry (Yellow)' }, { slug: 'radiating_energy', name: 'Radiating Energy' }, { slug: 'radiating_energy_yellow', name: 'Radiating Energy (Yellow)' }, { slug: 'radiating_energy_blue', name: 'Radiating Energy (Blue)' }, { slug: 'sweat_drops', name: 'Sweat Drops' }, { slug: 'sweat_drops_cyan', name: 'Sweat Drops (Cyan)' }, { slug: 'sweat_drops_pink', name: 'Sweat Drops (Pink)' }, { slug: 'soul_leaving_body', name: 'Soul Leaving Body' }, { slug: 'soul_leaving_body_blue', name: 'Soul Leaving Body (Blue)' }, { slug: 'starry_eyed', name: 'Starry Eyed' }, { slug: 'starry_eyed_green', name: 'Starry Eyed (Green)' }, { slug: 'starry_eyed_pink', name: 'Starry Eyed (Pink)' }, { slug: 'shocked', name: 'Shocked' }, { slug: 'shocked_green', name: 'Shocked (Green)' }, { slug: 'shocked_yellow', name: 'Shocked (Yellow)' }, { slug: 'in_love', name: 'In Love' }, { slug: 'in_love_blue', name: 'In Love (Blue)' }, { slug: 'in_love_green', name: 'In Love (Green)' }, { slug: 'ki_energy_green', name: 'Ki Energy (Green)' }, { slug: 'ki_energy_cyan', name: 'Ki Energy (Cyan)' }, { slug: 'ki_energy_blue', name: 'Ki Energy (Blue)' }, { slug: 'ki_energy_fuchsia', name: 'Ki Energy (Fuchsia)' }, { slug: 'heartbloom', name: 'Heartbloom' }, { slug: 'heartbloom_purple', name: 'Heartbloom (Purple)' }, { slug: 'heartbloom_blue', name: 'Heartbloom (Blue)' }, { slug: 'heartbloom_yellow', name: 'Heartbloom (Yellow)' }, { slug: 'heartbloom_green', name: 'Heartbloom (Green)' }, { slug: 'dismay', name: 'Dismay' }, { slug: 'dismay_purple', name: 'Dismay (Purple)' }, { slug: 'dismay_pink', name: 'Dismay (Pink)' }, { slug: 'dismay_yellow', name: 'Dismay (Yellow)' }, { slug: 'dismay_green', name: 'Dismay (Green)' }, { slug: 'in_tears', name: 'In Tears' }, { slug: 'in_tears_purple', name: 'In Tears (Purple)' }, { slug: 'in_tears_pink', name: 'In Tears (Pink)' }, { slug: 'in_tears_yellow', name: 'In Tears (Yellow)' }, { slug: 'in_tears_green', name: 'In Tears (Green)' }, { slug: 'rage', name: 'Rage' }, { slug: 'rage_green', name: 'Rage (Green)' }, { slug: 'rage_blue', name: 'Rage (Blue)' }, { slug: 'rage_purple', name: 'Rage (Purple)' }, { slug: 'rage_red', name: 'Rage (Red)' }, { slug: 'copium', name: 'Copium' }, { slug: 'trash', name: 'Trash' }, { slug: 'grass_toucher', name: 'Grass Toucher' }, { slug: 'touch_grass', name: 'Touch Grass' }, { slug: 'press_f_black', name: 'Press F (Black)' }, { slug: 'press_f_white', name: 'Press F (White)' }, { slug: 'git_gud', name: 'Git Gud' }, { slug: 'ggez', name: 'GGEZ' }, { slug: 'questionable', name: 'Questionable' }, { slug: 'buffering', name: 'Buffering' }, { slug: 'toilet_lid', name: 'Toilet Lid' }, { slug: 'feeling_cute', name: 'Feeling Cute' }, { slug: 'im_a_clown', name: "I'm a Clown" }, ], }, { id: 'critters', label: 'Critters', decorations: [ { slug: 'cat_1', name: 'Lazy Cat (Ginger)' }, { slug: 'cat_2', name: 'Lazy Cat (Tabby)' }, { slug: 'cat_3', name: 'Lazy Cat (Calico)' }, { slug: 'cat_4', name: 'Lazy Cat (Siamese)' }, { slug: 'dog_1', name: 'Dogs (Tan)' }, { slug: 'dog_2', name: 'Dogs (Spotted)' }, { slug: 'dog_3', name: 'Dogs (Brown)' }, { slug: 'frog_1', name: 'Frog' }, { slug: 'frog_derpy', name: 'Derpy Frog' }, { slug: 'frog_3', name: 'Sleepy Frog' }, { slug: 'frog_angry', name: 'Angry Frog' }, { slug: 'mushroom_1', name: 'Mushroom (Moss)' }, { slug: 'mushroom_2', name: 'Mushroom (Amber)' }, { slug: 'mushroom_3', name: 'Mushroom (Rust)' }, { slug: 'mushroom_4', name: 'Mushroom (Neon)' }, { slug: 'hoppy_day', name: 'Hoppy Day' }, { slug: 'hoppy_boi', name: 'Hoppy Boi' }, { slug: 'cow_glider', name: 'Cow Glider' }, { slug: 'moomoo_hood', name: 'Moomoo Hood' }, { slug: 'cosy_deer', name: 'Cosy Deer' }, { slug: 'berry_bunny', name: 'Berry Bunny' }, { slug: 'mochi_bunny_pink', name: 'Mochi Bunny (Pink)' }, { slug: 'mochi_bunny_blue', name: 'Mochi Bunny (Blue)' }, { slug: 'nibbles', name: 'Nibbles' }, { slug: 'hammy', name: 'Hammy' }, { slug: 'hammy_blue', name: 'Hammy (Blue)' }, { slug: 'hammy_green', name: 'Hammy (Green)' }, { slug: 'hammy_pink', name: 'Hammy (Pink)' }, { slug: 'shroomling', name: 'Shroomling' }, { slug: 'shroomling_blue', name: 'Shroomling (Blue)' }, { slug: 'shroomling_brown', name: 'Shroomling (Brown)' }, { slug: 'shroomling_pink', name: 'Shroomling (Pink)' }, { slug: 'shroomling_purple', name: 'Shroomling (Purple)' }, { slug: 'sleepy_bee', name: 'Sleepy Bee' }, { slug: 'baker_bear', name: 'Baker Bear' }, { slug: 'dewdrop', name: 'Dewdrop' }, { slug: 'gone_fishin', name: "Gone Fishin'" }, { slug: 'happy_harvest', name: 'Happy Harvest' }, { slug: 'cat_onesie', name: 'Cat Onesie' }, { slug: 'cat_onesie_pink', name: 'Cat Onesie (Pink)' }, { slug: 'cat_onesie_black', name: 'Cat Onesie (Black)' }, { slug: 'cat_ears_yellow', name: 'Cat Ears (Yellow)' }, { slug: 'cat_ears_green', name: 'Cat Ears (Green)' }, { slug: 'cat_ears_blue', name: 'Cat Ears (Blue)' }, { slug: 'cat_ears_purple', name: 'Cat Ears (Purple)' }, { slug: 'greenhouse_cat', name: 'Greenhouse Cat' }, { slug: 'graveyard_cat', name: 'Graveyard Cat' }, { slug: 'forest_frolic', name: 'Forest Frolic' }, { slug: 'a_real_fungi', name: 'A Real Fungi' }, { slug: 'mokoko', name: 'Mokoko' }, ], }, { id: 'monsters', label: 'Little Monsters', decorations: [ { slug: 'beamchop', name: 'Beamchop' }, { slug: 'chuck', name: 'Chuck' }, { slug: 'winkle', name: 'Winkle' }, { slug: 'chewbert', name: 'Chewbert' }, { slug: 'doodlezard', name: 'Doodlezard' }, { slug: 'glop', name: 'Glop' }, { slug: 'gawblehop', name: 'Gawblehop' }, { slug: 'stinkums', name: 'Stinkums' }, { slug: 'goblin_stinkums', name: 'Goblin Stinkums' }, { slug: 'chomp_chomp', name: 'Chomp Chomp' }, ], }, { id: 'snacks', label: 'Snacks', decorations: [ { slug: 'pancakes', name: 'Pancakes' }, { slug: 'blueberry_jam', name: 'Blueberry Jam' }, { slug: 'donut', name: 'Donut' }, { slug: 'fried_egg', name: 'Fried Egg' }, { slug: 'green_fried_egg', name: 'Green Fried Egg' }, { slug: 'toast', name: 'Toast' }, { slug: 'burnt_toast', name: 'Burnt Toast' }, { slug: 'morning_coffee', name: 'Morning Coffee' }, { slug: 'sushi_roll', name: 'Sushi Roll' }, { slug: 'ramen_bowl', name: 'Ramen Bowl' }, { slug: 'ramen_bowl_toppings', name: 'Ramen Bowl (Toppings)' }, { slug: 'donut_cow', name: 'Donut Cow' }, { slug: 'donut_cat', name: 'Donut Cat' }, { slug: 'donut_bear', name: 'Donut Bear' }, { slug: 'bread_doggy', name: 'Bread Doggy' }, { slug: 'ice_cream_frog', name: 'Ice Cream Frog' }, { slug: 'jelly_bear', name: 'Jelly Bear' }, { slug: 'nom_kitty', name: 'Nom Kitty' }, { slug: 'egg', name: 'Egg' }, { slug: 'chicken_nugget', name: 'Chicken Nugget' }, { slug: 'sippy_juice', name: 'Sippy Juice' }, { slug: 'bubble_tea', name: 'Bubble Tea' }, { slug: 'freshly_picked', name: 'Freshly Picked' }, { slug: 'strawberry_vine', name: 'Strawberry Vine' }, { slug: 'a_hint_of_clove', name: 'A Hint of Clove' }, ], }, { id: 'scene', label: 'Scene', decorations: [ { slug: 'neon_cat_hoodie', name: 'Neon Cat Hoodie' }, { slug: 'neon_cat_hoodie_blue', name: 'Neon Cat Hoodie (Blue)' }, { slug: 'neon_cat_hoodie_pink', name: 'Neon Cat Hoodie (Pink)' }, { slug: 'neon_cat_hoodie_black', name: 'Neon Cat Hoodie (Black)' }, { slug: 'neon_decora_hair', name: 'Neon Decora Hair' }, { slug: 'neon_spike_choker', name: 'Neon Spike Choker' }, { slug: 'neon_spike_choker_black', name: 'Neon Spike Choker (Black)' }, { slug: 'mischievous_kitties', name: 'Mischievous Kitties' }, { slug: 'mischievous_kitties_hearts', name: 'Mischievous Kitties (Hearts)' }, { slug: 'mischievous_kitties_blue', name: 'Mischievous Kitties (Blue)' }, { slug: 'skully_charms', name: 'Skully Charms' }, { slug: 'rawr_xd', name: 'Rawr xD' }, { slug: 'magical_girl', name: 'Magical Girl' }, { slug: 'sakura_scholar', name: 'Sakura Scholar' }, ], }, { id: 'dreamy', label: 'Dreamy', decorations: [ { slug: 'jet_ring', name: 'Jet Ring' }, { slug: 'pondering_portal', name: 'Pondering Portal' }, { slug: 'magic_mists', name: 'Magic Mists' }, { slug: 'infinite_swirl', name: 'Infinite Swirl' }, { slug: 'blue_gyroscope', name: 'Blue Gyroscope' }, { slug: 'pink_gyroscope', name: 'Pink Gyroscope' }, { slug: 'green_gyroscope', name: 'Green Gyroscope' }, { slug: 'saw', name: 'Saw' }, { slug: 'blue_smoke', name: 'Blue Smoke' }, { slug: 'pink_smoke', name: 'Pink Smoke' }, { slug: 'green_smoke', name: 'Green Smoke' }, { slug: 'starlight_whales', name: 'Starlight Whales' }, { slug: 'head_in_the_clouds', name: 'Head In the Clouds' }, { slug: 'head_in_the_clouds_sunset', name: 'Head In the Clouds (Sunset)' }, { slug: 'mirage', name: 'Mirage' }, { slug: 'mirage_void', name: 'Mirage (Void)' }, { slug: 'mirage_twilight', name: 'Mirage (Twilight)' }, { slug: 'mirage_nightshade', name: 'Mirage (Nightshade)' }, { slug: 'lava_blobs', name: 'Lava Blobs' }, { slug: 'lava_blobs_pink', name: 'Lava Blobs (Pink)' }, { slug: 'lava_blobs_blue', name: 'Lava Blobs (Blue)' }, { slug: 'lava_blobs_slime', name: 'Lava Blobs (Slime)' }, { slug: 'luminescent_lotus', name: 'Luminescent Lotus' }, { slug: 'woolgathering', name: 'Woolgathering' }, { slug: 'travel_ring', name: 'Travel Ring' }, { slug: 'dream_dive_stars', name: 'Dream Dive Stars' }, { slug: 'rift_butterfly', name: 'Rift Butterfly' }, ], }, { id: 'celestial', label: 'Celestial', decorations: [ { slug: 'lunar_flowers', name: 'Lunar Flowers' }, { slug: 'starlight_tiara', name: 'Starlight Tiara' }, { slug: 'astral_aura', name: 'Astral Aura' }, { slug: 'luna_moths', name: 'Luna Moths' }, { slug: 'moonlit_charms', name: 'Moonlit Charms' }, { slug: 'liquid_moon', name: 'Liquid Moon' }, ], }, { id: 'garden', label: 'Garden', decorations: [ { slug: 'terrarium', name: 'Terrarium' }, { slug: 'mushcaps', name: 'Mushcaps' }, { slug: 'blossomburst', name: 'Blossomburst' }, { slug: 'rainy_canopy', name: 'Rainy Canopy' }, { slug: 'snowdrops', name: 'Snowdrops' }, { slug: 'afternoon_breeze', name: 'Afternoon Breeze' }, { slug: 'shower_stroll', name: 'Shower Stroll' }, { slug: 'sakura_gyoiko', name: 'Gyoiko Sakura' }, { slug: 'sakura_ukon', name: 'Ukon Sakura' }, { slug: 'forest', name: 'Forest' }, { slug: 'fall_leaves_woodland', name: 'Woodland Leaves' }, { slug: 'autumn_foliage', name: 'Autumn Foliage' }, { slug: 'flower_clouds', name: 'Flower Clouds' }, { slug: 'the_petal_pack', name: 'The Petal Pack' }, { slug: 'chrysanthemums_morning', name: 'Morning Chrysanthemums' }, { slug: 'chrysanthemums_twilight', name: 'Twilight Chrysanthemums' }, { slug: 'dusk_and_dawn', name: 'Dusk and Dawn' }, { slug: 'floral_harmony', name: 'Floral Harmony' }, { slug: 'floral_harmony_sunburst', name: 'Floral Harmony (Sunburst)' }, { slug: 'autumns_arbor', name: "Autumn's Arbor" }, { slug: 'autumns_arbor_aurora', name: "Autumn's Arbor (Aurora)" }, { slug: 'autumn_crown', name: 'Autumn Crown' }, { slug: 'faces_of_the_moon', name: 'Faces of the Moon' }, { slug: 'lover_girl_autumn', name: 'Lover Girl Autumn' }, ], }, { id: 'summer', label: 'Summer', decorations: [ { slug: 'sunflowers', name: 'Sunflowers' }, { slug: 'beach_hat', name: 'Beach Hat' }, { slug: 'a_duck', name: 'A Duck' }, { slug: 'bubble', name: 'Bubble' }, { slug: 'fireflies', name: 'Fireflies' }, ], }, { id: 'love', label: 'Love', decorations: [ { slug: 'rose_bearer_pink', name: 'Rose Bearer (Pink)' }, { slug: 'rose_bearer_blue', name: 'Rose Bearer (Blue)' }, { slug: 'angel', name: 'Angel' }, { slug: 'devil', name: 'Devil' }, { slug: 'aim_for_love', name: 'Aim for Love' }, { slug: 'lovestruck', name: 'Lovestruck' }, { slug: 'ring_of_roses_red', name: 'Ring of Roses (Red)' }, { slug: 'ring_of_roses_blue', name: 'Ring of Roses (Blue)' }, { slug: 'heartstrings_red', name: 'Heartstrings (Red)' }, { slug: 'heartstrings_blue', name: 'Heartstrings (Blue)' }, { slug: 'heart_to_heart', name: 'Heart-to-Heart' }, { slug: 'ruby_hearts', name: 'Ruby Hearts' }, ], }, { id: 'winter', label: 'Winter', decorations: [ { slug: 'snowfall', name: 'Snowfall' }, { slug: 'santa_cat_ears', name: 'Holiday Cat Ears' }, { slug: 'string_lights', name: 'String Lights' }, { slug: 'string_lights_aurora', name: 'String Lights (Aurora)' }, { slug: 'string_lights_ember', name: 'String Lights (Ember)' }, { slug: 'string_lights_dusk', name: 'String Lights (Dusk)' }, { slug: 'string_lights_mix', name: 'String Lights (Mix)' }, { slug: 'snowglobe', name: 'Snowglobe' }, { slug: 'snowglobe_wood', name: 'Snowglobe (Wood)' }, { slug: 'snowglobe_pink', name: 'Snowglobe (Pink)' }, { slug: 'snowglobe_blue', name: 'Snowglobe (Blue)' }, { slug: 'snowglobe_green', name: 'Snowglobe (Green)' }, { slug: 'fresh_pine', name: 'Fresh Pine' }, { slug: 'fresh_pine_cinnamon', name: 'Fresh Pine (Cinnamon)' }, { slug: 'fresh_pine_ribbon', name: 'Fresh Pine (Ribbon)' }, { slug: 'blanket_green', name: 'Green Blanket' }, { slug: 'blanket_orange', name: 'Orange Blanket' }, { slug: 'blanket_pink', name: 'Pink Blanket' }, { slug: 'blanket_purple', name: 'Purple Blanket' }, { slug: 'box_white_blue', name: 'Gift Box (White)' }, { slug: 'box_blue_yellow', name: 'Gift Box (Blue)' }, { slug: 'box_green_red', name: 'Gift Box (Green)' }, { slug: 'box_red_white', name: 'Gift Box (Red)' }, { slug: 'box_red_white_blue', name: 'Gift Box (Red, White & Blue)' }, { slug: 'ice_cube', name: 'Ice Cube' }, { slug: 'icicle_gleaming', name: 'Gleaming Icicle' }, { slug: 'icicle_snowing', name: 'Snowing Icicle' }, { slug: 'teacup_blue', name: 'Blue Teacup' }, { slug: 'teacup_orange', name: 'Orange Teacup' }, { slug: 'teacup_pink', name: 'Pink Teacup' }, { slug: 'teacup_red', name: 'Red Teacup' }, ], }, { id: 'celebrate', label: 'Celebrate', decorations: [ { slug: 'new_year_2024', name: '2024 Balloons' }, { slug: 'new_year_2025', name: '2025 Balloons' }, { slug: 'confetti_festive', name: 'Festive Confetti' }, { slug: 'confetti_fire', name: 'Fire Confetti' }, { slug: 'confetti_ice', name: 'Ice Confetti' }, { slug: 'confetti_mint', name: 'Mint Confetti' }, { slug: 'confetti_star', name: 'Star Confetti' }, { slug: 'confetti_vaporwave', name: 'Vaporwave Confetti' }, { slug: 'snakes_hug', name: "Snake's Hug" }, { slug: 'red_lantern', name: 'Red Lantern' }, { slug: 'fan_flourish', name: 'Fan Flourish' }, { slug: 'lucky_envelopes', name: 'Lucky Envelopes' }, ], }, { id: 'steampunk', label: 'Steampunk', decorations: [ { slug: 'steampunk_cat_ears', name: 'Steampunk Cat Ears' }, { slug: 'mech_flora', name: 'Mech Flora' }, { slug: 'bowler_hat', name: 'Bowler Hat' }, { slug: 'brass_beats', name: 'Brass Beats' }, { slug: 'timekeepers_clock', name: "Timekeeper's Clock" }, { slug: 'flux_alchemy', name: 'Flux Alchemy' }, { slug: 'magic_portal_purple', name: 'Magic Portal (Purple)' }, { slug: 'magic_portal_blue', name: 'Magic Portal (Blue)' }, { slug: 'fairy_sprites', name: 'Fairy Sprites' }, { slug: 'fairy_sprites_pink', name: 'Fairy Sprites (Pink)' }, { slug: 'fairy_sprites_blue', name: 'Fairy Sprites (Blue)' }, { slug: 'wizard_hat_purple', name: 'Wizard Hat (Purple)' }, { slug: 'wizard_hat_blue', name: 'Wizard Hat (Blue)' }, { slug: 'magical_wand_purple', name: 'Magical Wand (Purple)' }, { slug: 'magical_wand_green', name: 'Magical Wand (Green)' }, { slug: 'mooncaps_pink', name: 'Mooncaps (Pink)' }, { slug: 'mooncaps_blue', name: 'Mooncaps (Blue)' }, { slug: 'cottage_home', name: 'Cottage Home' }, { slug: 'treasure_and_key', name: 'Treasure and Key' }, ], }, { id: 'pirates', label: 'Pirates', decorations: [ { slug: 'pirate_captain', name: 'Pirate Captain' }, { slug: 'scallywag', name: 'Scallywag' }, { slug: 'good_ol_pepper', name: "Good Ol' Pepper" }, { slug: 'crossbones', name: 'Crossbones' }, { slug: 'cannon_fire', name: 'Cannon Fire' }, { slug: 'helmsman', name: 'Helmsman' }, ], }, { id: 'neo_tokyo', label: 'Neo Tokyo', decorations: [ { slug: 'bonsai', name: 'Bonsai' }, { slug: 'sakura_city', name: 'Sakura City' }, { slug: 'hologram_dragon', name: 'Hologram Dragon' }, { slug: 'cyber_katana', name: 'Cyber Katana' }, { slug: 'o_n_i', name: 'O.N.I' }, { slug: 'jingasa', name: 'Jingasa' }, { slug: 'white_mana', name: 'White Mana' }, { slug: 'blue_mana', name: 'Blue Mana' }, { slug: 'black_mana', name: 'Black Mana' }, { slug: 'red_mana', name: 'Red Mana' }, { slug: 'green_mana', name: 'Green Mana' }, ], }, { id: 'lofi', label: 'Lofi', decorations: [ { slug: 'lofi_girl_outfit', name: 'Lofi Girl Outfit' }, { slug: 'playful_lofi_cat', name: 'Playful Lofi Cat' }, { slug: 'sleepy_chilledcow', name: 'Sleepy ChilledCow' }, { slug: 'study_session', name: 'Study Session' }, { slug: 'group_hug', name: 'Group Hug' }, { slug: 'cozy_post_it', name: 'Cozy Post-It' }, { slug: 'cozy_post_it_festive', name: 'Cozy Post-It (Festive)' }, { slug: 'cat_ear_headset', name: 'Cat Ear Headset' }, { slug: 'bunny_zzzs', name: 'Bunny Zzzs' }, { slug: 'sproutling', name: 'Sproutling' }, { slug: 'bloomling', name: 'Bloomling' }, { slug: 'neon_nibbles', name: 'Neon Nibbles' }, { slug: 'uwu_xp', name: 'UwU XP' }, { slug: 'dancing_fairies', name: 'Dancing Fairies' }, { slug: 'crystal_elk', name: 'Crystal Elk' }, { slug: 'mermaid_serenade', name: 'Mermaid Serenade' }, ], }, { id: 'game_night', label: 'Game Night', decorations: [ { slug: 'hugh_the_rainbow', name: 'Hugh the Rainbow' }, { slug: 'imagination', name: 'Imagination' }, { slug: 'depresso', name: 'Depresso' }, { slug: 'selyne', name: 'Selyne' }, { slug: 'cattiva', name: 'Cattiva' }, { slug: 'gold_laurel_wreath', name: 'Gold Laurel Wreath' }, { slug: 'golden_hex', name: 'Golden Hex' }, { slug: 'terrain_tiles', name: 'Terrain Tiles' }, { slug: 'hex_tiles', name: 'Hex Tiles' }, { slug: 'city_walls', name: 'City Walls' }, { slug: 'next_turn_button', name: 'Next Turn Button' }, { slug: 'armamenter', name: 'Armamenter' }, { slug: 'e_d_hacker', name: 'E.D Hacker' }, { slug: 'got_xenoglossy', name: 'Got Xenoglossy' }, { slug: 'port_of_soul', name: 'Port of Soul' }, { slug: 'rocket_puncher', name: 'Rocket Puncher' }, { slug: 'lights_camera_action_orange', name: 'Lights, Camera, Action! (Orange)' }, { slug: 'lights_camera_action_teal', name: 'Lights, Camera, Action! (Teal)' }, { slug: 'victory_crown', name: 'Fortnite Victory Crown' }, { slug: 'shield_potion', name: 'Shield Potion' }, { slug: 'slurp_barrel', name: 'Slurp Barrel' }, { slug: 'supply_llama', name: 'Supply Llama' }, { slug: 'bush_camper', name: 'Bush Camper' }, ], }, { id: 'crossover_screen', label: 'Movies & TV', decorations: [ { slug: 'lightsabers_blue_and_red', name: 'Lightsabers (Blue and Red)' }, { slug: 'lightsabers_green_and_red', name: 'Lightsabers (Green and Red)' }, { slug: 'r2_d2_on_tatooine', name: 'R2-D2 on Tatooine' }, { slug: 'curious_bb_8', name: 'Curious BB-8' }, { slug: 'yoda_on_dagobah', name: 'Yoda on Dagobah' }, { slug: 'millennium_falcon_hyperdrive', name: 'Millennium Falcon Hyperdrive' }, { slug: 'space_battle', name: 'Space Battle' }, { slug: 'the_thing', name: 'The Thing' }, { slug: 'invisible_woman', name: 'Invisible Woman' }, { slug: 'mister_fantastic', name: 'Mister Fantastic' }, { slug: 'human_torch', name: 'Human Torch' }, { slug: 'silver_surfer', name: 'Silver Surfer' }, { slug: 'the_fantastic_four', name: 'The Fantastic Four' }, { slug: 'dragon_balls', name: 'Dragon Balls' }, { slug: 'gomah', name: 'Gomah' }, { slug: 'shenron', name: 'Shenron' }, { slug: 'mini_goku', name: 'Mini Goku' }, { slug: 'mini_vegeta', name: 'Mini Vegeta' }, { slug: 'glorio', name: 'Glorio' }, { slug: 'mini_supreme_kai', name: 'Mini Supreme Kai' }, { slug: 'mini_piccolo', name: 'Mini Piccolo' }, { slug: 'panzy', name: 'Panzy' }, { slug: 'izuku_midoriya', name: 'Izuku Midoriya' }, { slug: 'katsuki_bakugo', name: 'Katsuki Bakugo' }, { slug: 'ochaco_uraraka', name: 'Ochaco Uraraka' }, { slug: 'shoto_todoroki', name: 'Shoto Todoroki' }, { slug: 'endeavor', name: 'Endeavor' }, { slug: 'hawks', name: 'Hawks' }, { slug: 'all_might', name: 'All Might' }, { slug: 'tomura_shigaraki', name: 'Tomura Shigaraki' }, { slug: 'spongebob', name: 'SpongeBob' }, { slug: 'patrick_star', name: 'Patrick Star' }, { slug: 'gary_the_snail', name: 'Gary the Snail' }, { slug: 'sandy_cheeks', name: 'Sandy Cheeks' }, { slug: 'musclebob', name: 'MuscleBob' }, { slug: 'skibidi_toilet', name: 'Skibidi Toilet' }, { slug: 'g_toilet', name: 'G-Toilet' }, { slug: 'cameraman_glitch', name: 'Cameraman Glitch' }, { slug: 'speakerwoman', name: 'Speakerwoman' }, { slug: 'tv_woman', name: 'TV Woman' }, { slug: 'juggernaut_astro', name: 'Juggernaut Astro' }, { slug: 'the_anomaly', name: 'The Anomaly' }, { slug: 'the_mark', name: 'The Mark' }, { slug: 'the_monster_you_created', name: 'The Monster You Created' }, { slug: 'the_atlas_gauntlets', name: 'The Atlas Gauntlets' }, { slug: 'flame_chompers', name: 'Flame Chompers' }, { slug: 'fishbones', name: 'FISHBONES!' }, { slug: 'the_hexcore', name: 'The Hexcore' }, { slug: 'powered_by_shimmer', name: 'Powered by Shimmer' }, { slug: 'minions', name: 'Minions' }, { slug: 'daredevil', name: 'Daredevil' }, { slug: 'jean_grey_phoenix', name: 'Phoenix' }, { slug: 'emma_frost', name: 'Emma Frost' }, { slug: 'ultron', name: 'Ultron' }, { slug: 'marvel_snap_venom', name: 'Marvel Snap Venom' }, { slug: 'jeff_the_land_shark', name: 'Jeff the Land Shark' }, { slug: 'hank_hill', name: 'Hank Hill' }, { slug: 'how_to_train_your_dragon', name: 'How to Train Your Dragon' }, { slug: 'jurassic_world_rebirth', name: 'Jurassic World Rebirth Trailer' }, { slug: 'mission_impossible', name: 'Mission: Impossible' }, { slug: 'bad_guys_2', name: 'The Bad Guys 2 Trailer' }, { slug: 'm3gan_2_0', name: 'M3GAN 2.0' }, { slug: '28_years_later', name: '28 Years Later' }, { slug: 'black_phone_2', name: 'Black Phone 2' }, { slug: 'the_conjuring_last_rites', name: 'The Conjuring: Last Rites' }, { slug: 'ballerina', name: 'Ballerina' }, { slug: 'wendys_x_wednesday', name: "Wendy's x Wednesday" }, { slug: 'batarang', name: 'Batarang' }, { slug: 'espn', name: 'ESPN' }, { slug: 'aespa_fanlight', name: 'aespa Fanlight' }, { slug: 'bing_bong', name: 'Bing Bong' }, ], }, { id: 'crossover_games', label: 'Game Crossovers', decorations: [ { slug: 'trick_pumpkin', name: 'Trick Pumpkin' }, { slug: 'treat_pumpkin', name: 'Treat Pumpkin' }, { slug: 'trick_spider', name: 'Trick Spider' }, { slug: 'treat_spider', name: 'Treat Spider' }, { slug: 'trick_skull', name: 'Trick Skull' }, { slug: 'treat_skull', name: 'Treat Skull' }, { slug: 'trick_ghost', name: 'Trick Ghost' }, { slug: 'treat_ghost', name: 'Treat Ghost' }, { slug: 'the_final_peel', name: 'The Final Peel' }, { slug: 'warframe_clem', name: 'Warframe Clem' }, { slug: 'dart_monkey', name: 'Dart Monkey' }, { slug: 'kom', name: 'KOM' }, { slug: 'mr_m', name: 'Mr. M' }, { slug: 'fc_26_icon', name: 'FC 26 Icon' }, { slug: 'skate', name: 'Skate' }, { slug: 'airona_fireworks', name: 'Airona Fireworks' }, { slug: 'champions_tactibear', name: 'Champions Tactibear' }, { slug: 'cab_monster', name: 'Cab Monster' }, { slug: 'atsus_mask', name: "Atsu's Mask" }, { slug: 'tactibear_flex', name: 'Tactibear Flex' }, { slug: 'angela_avatar', name: 'Angela Avatar' }, { slug: 'echo_4', name: 'Echo 4' }, { slug: 'nba_2k26', name: 'NBA 2K26' }, { slug: 'eye_of_prophesy', name: 'Eye of Prophesy' }, { slug: 'inzoi_psycat', name: 'inZOI Psycat' }, { slug: 'bf_soldier_helmet', name: 'BF Soldier Helmet' }, { slug: 'the_entity', name: 'The Entity' }, { slug: 'descendant', name: 'Descendant' }, { slug: 'sweet_tooth', name: 'Sweet Tooth' }, { slug: 'call_of_duty_mobile', name: 'Call of Duty: Mobile' }, { slug: 'thps_half_pipe', name: 'THPS Half Pipe' }, { slug: 'mecha_break', name: 'Mecha BREAK' }, { slug: 'valorant_summer_kickoff', name: 'VALORANT Summer Kickoff' }, { slug: 'palia', name: 'Palia' }, { slug: 'supercell', name: 'SuperCell' }, { slug: 'i_love_repo', name: 'I Love R.E.P.O.' }, { slug: 'lego_fortnite', name: 'LEGO® Fortnite' }, { slug: 'r6_siege_x_avatar', name: 'R6 Siege X Avatar' }, { slug: 'towerborne_play', name: 'Towerborne Play' }, { slug: 'starlight_revolver', name: 'Starlight Revolver' }, { slug: 'open_beta', name: 'Open Beta' }, { slug: 'fortnite_galactic_battle', name: 'Fortnite Galactic Battle' }, { slug: 'shield_saw', name: 'Shield Saw' }, { slug: 'friend_of_dex', name: 'Friend of Dex' }, { slug: 'hackclaw', name: 'Hackclaw' }, { slug: 'signal_from_tau_ceti', name: 'Signal from Tau Ceti' }, { slug: 'face_of_corruption', name: 'Face of Corruption' }, { slug: 'clicker', name: 'Clicker' }, { slug: 'gallica', name: 'Gallica' }, { slug: 'khazan', name: 'Khazan' }, { slug: 'split', name: 'Split' }, { slug: 'pathojen', name: 'Pathojen' }, { slug: 'big_dill_chain', name: 'Big Dill Chain' }, { slug: 'exoborne', name: 'Exoborne' }, { slug: 'scout', name: 'Scout' }, { slug: 'fortnite_boogie_bomb', name: 'Fortnite Boogie Bomb' }, { slug: 'fuchsia_agent', name: 'Fuchsia Agent' }, { slug: 'wingmans_got_it', name: "WINGMAN'S GOT IT" }, { slug: 'rec_room_lightning', name: 'Rec Room Lightning' }, { slug: 'shadow', name: 'Shadow' }, { slug: 'tga_controller', name: 'TGA Controller' }, { slug: 'wallach_spaceport', name: 'Wallach IX Spaceport' }, { slug: 'wolf_morph', name: 'Wolf Morph' }, { slug: 'street_fighter_6_battle_field', name: 'Street Fighter 6 Battle Field' }, { slug: 'torgal_puppy', name: 'Torgal Puppy' }, { slug: 'bunny', name: 'Bunny Mech' }, { slug: 'hailey', name: 'Hailey' }, { slug: 'los_santos', name: 'Los Santos' }, { slug: 'wingman_boba', name: 'Wingman Boba' }, { slug: 'freezer_bunny_lovebug', name: 'Freezer Bunny Lovebug' }, { slug: 'im_dancin', name: "I'm Dancin'!" }, { slug: 'finger_gun', name: 'Finger Gun' }, { slug: 'bandit_mask', name: 'Bandit Mask' }, { slug: 'mad_moxxi', name: 'Mad Moxxi' }, { slug: 'elpis', name: 'Elpis' }, { slug: 'loot_lightshow', name: 'Loot Lightshow' }, { slug: 'spirit_blossom_zed', name: 'Spirit Blossom Zed' }, { slug: 'spirit_blossom_springs_sett', name: 'Spirit Blossom Springs Sett' }, { slug: 'spirit_blossom_morgana', name: 'Spirit Blossom Morgana' }, { slug: 'spirit_blossom_karma', name: 'Spirit Blossom Karma' }, { slug: 'spirit_blossom_springs', name: 'Spirit Blossom Springs' }, { slug: 'spirit_blossom_springs_ahri', name: 'Spirit Blossom Springs Ahri' }, { slug: 'yunara', name: 'Yunara' }, { slug: 'gelatinous_cube_green', name: 'Green Gelatinous Cube' }, { slug: 'gelatinous_cube_blue', name: 'Blue Gelatinous Cube' }, { slug: 'spooky_cat_ears_midnight', name: 'Spooky Cat Ears (Midnight)' }, { slug: 'candlelight_dark', name: 'Dark Candlelight' }, { slug: 'zombie_food_purple', name: 'Zombie Food (Purple)' }, { slug: 'bloodthirsty_green', name: 'Bloodthirsty (Green)' }, { slug: 'ryu', name: 'Ryu' }, { slug: 'chun_li', name: 'Chun-Li' }, { slug: 'ken', name: 'Ken' }, { slug: 'akuma', name: 'Akuma' }, { slug: 'cammy', name: 'Cammy' }, { slug: 'guile', name: 'Guile' }, { slug: 'juri', name: 'Juri' }, { slug: 'm_bison', name: 'M. Bison' }, { slug: 'valorant_champions_2024', name: 'VALORANT Champions 2024' }, { slug: 'yoru_dimensional_drift', name: 'Yoru Dimensional Drift' }, { slug: 'viper_poison_cloud', name: 'Viper Poison Cloud' }, { slug: 'cypher_neural_theft', name: 'Cypher Neural Theft' }, { slug: 'omens_cowl', name: "Omen's Cowl" }, { slug: 'reynas_leer', name: "Reyna's Leer" }, { slug: 'frag_out', name: 'FRAG OUT' }, { slug: 'blade_storm', name: 'Blade Storm' }, { slug: 'chillet', name: 'Chillet' }, { slug: 'pal_sphere', name: 'Pal Sphere' }, { slug: 'lamball', name: 'Lamball' }, ], }, ]; export const ALL_DECORATIONS: AvatarDecoration[] = DECORATION_CATEGORIES.flatMap( (c) => c.decorations, ); const DECORATION_SLUGS = new Set(ALL_DECORATIONS.map((d) => d.slug)); /** * Whether `slug` is a known catalog decoration. `io.lotus.avatar_decoration` * is a free-form MSC4133 profile field set by a remote user (and their * homeserver), and its value is interpolated verbatim into `decorationUrl` * — so anything not in the catalog (path traversal, a query string, an * oversized value) must be rejected before it reaches a URL. */ export function isValidDecorationSlug(slug: string): boolean { return DECORATION_SLUGS.has(slug); } export function decorationUrl(slug: string): string { return `${RESOLVED_DECORATION_CDN}/${slug}.png`; } /** * Small static WebP preview (first busy frame, 144px) for the picker. The full * decorations are ~1 MB animated PNGs, so the picker shows these and only * loads `decorationUrl` on hover/focus/selection. Built by * scripts/makeDecorationThumbs.py; callers fall back to `decorationUrl` if a * thumbnail is missing. */ export function decorationThumbUrl(slug: string): string { return `${RESOLVED_DECORATION_CDN}/thumbs/${slug}.webp`; }