/** * @name Emoji Picker * @description A Discord-style emoji picker for X's text boxes. X's own emoji button (or Ctrl+Shift+E) opens it right under the composer toolbar: search with skin tones on top, a category rail on the left, one scrolling grid with Frequently used first and sticky category headers, and a preview bar with the :shortcode:. Full keyboard control (arrows, Enter, Tab, PageUp/PageDown, Esc). In text boxes without X's own emoji suggestions, `:fire` suggests emoji (arrows/Tab/Enter insert, Esc closes) and a closing `:` inserts an exact match. The gear in the picker chooses whether X's emoji button opens this picker or X's own. * @version 3.2.0 * @author wayBetter * @permissions storage, ui, network:none */ const EMOJI_DATA = {"categories":["Smileys & Emotion","People & Body","Animals & Nature","Food & Drink","Travel & Places","Activities","Objects","Symbols","Flags"],"rows":[["๐Ÿ˜€","grinning","smile happy",0,0],["๐Ÿ˜ƒ","smiley","happy joy haha",0,0],["๐Ÿ˜„","smile","happy joy laugh pleased",0,0],["๐Ÿ˜","grin","",0,0],["๐Ÿ˜†","laughing;satisfied","happy haha",0,0],["๐Ÿ˜…","sweat_smile","hot",0,0],["๐Ÿคฃ","rofl","lol laughing",0,0],["๐Ÿ˜‚","joy","tears",0,0],["๐Ÿ™‚","slightly_smiling_face","",0,0],["๐Ÿ™ƒ","upside_down_face","",0,0],["๐Ÿซ ","melting_face","sarcasm dread",0,0],["๐Ÿ˜‰","wink","flirt",0,0],["๐Ÿ˜Š","blush","proud",0,0],["๐Ÿ˜‡","innocent","angel",0,0],["๐Ÿฅฐ","smiling_face_with_three_hearts","love",0,0],["๐Ÿ˜","heart_eyes","love crush",0,0],["๐Ÿคฉ","star_struck","eyes",0,0],["๐Ÿ˜˜","kissing_heart","flirt",0,0],["๐Ÿ˜—","kissing","",0,0],["โ˜บ๏ธ","relaxed","blush pleased",0,0],["๐Ÿ˜š","kissing_closed_eyes","",0,0],["๐Ÿ˜™","kissing_smiling_eyes","",0,0],["๐Ÿฅฒ","smiling_face_with_tear","",0,0],["๐Ÿ˜‹","yum","tongue lick",0,0],["๐Ÿ˜›","stuck_out_tongue","",0,0],["๐Ÿ˜œ","stuck_out_tongue_winking_eye","prank silly",0,0],["๐Ÿคช","zany_face","goofy wacky",0,0],["๐Ÿ˜","stuck_out_tongue_closed_eyes","prank",0,0],["๐Ÿค‘","money_mouth_face","rich",0,0],["๐Ÿค—","hugs","",0,0],["๐Ÿคญ","hand_over_mouth","quiet whoops",0,0],["๐Ÿซข","face_with_open_eyes_and_hand_over_mouth","gasp shock",0,0],["๐Ÿซฃ","face_with_peeking_eye","",0,0],["๐Ÿคซ","shushing_face","silence quiet",0,0],["๐Ÿค”","thinking","",0,0],["๐Ÿซก","saluting_face","respect",0,0],["๐Ÿค","zipper_mouth_face","silence hush",0,0],["๐Ÿคจ","raised_eyebrow","suspicious",0,0],["๐Ÿ˜","neutral_face","meh",0,0],["๐Ÿ˜‘","expressionless","",0,0],["๐Ÿ˜ถ","no_mouth","mute silence",0,0],["๐Ÿซฅ","dotted_line_face","invisible",0,0],["๐Ÿ˜ถโ€๐ŸŒซ๏ธ","face_in_clouds","",0,0],["๐Ÿ˜","smirk","smug",0,0],["๐Ÿ˜’","unamused","meh",0,0],["๐Ÿ™„","roll_eyes","",0,0],["๐Ÿ˜ฌ","grimacing","",0,0],["๐Ÿ˜ฎโ€๐Ÿ’จ","face_exhaling","",0,0],["๐Ÿคฅ","lying_face","liar",0,0],["๐Ÿ˜Œ","relieved","whew",0,0],["๐Ÿ˜”","pensive","",0,0],["๐Ÿ˜ช","sleepy","tired",0,0],["๐Ÿคค","drooling_face","",0,0],["๐Ÿ˜ด","sleeping","zzz",0,0],["๐Ÿ˜ท","mask","sick ill",0,0],["๐Ÿค’","face_with_thermometer","sick",0,0],["๐Ÿค•","face_with_head_bandage","hurt",0,0],["๐Ÿคข","nauseated_face","sick barf disgusted",0,0],["๐Ÿคฎ","vomiting_face","barf sick",0,0],["๐Ÿคง","sneezing_face","achoo sick",0,0],["๐Ÿฅต","hot_face","heat sweating",0,0],["๐Ÿฅถ","cold_face","freezing ice",0,0],["๐Ÿฅด","woozy_face","groggy",0,0],["๐Ÿ˜ต","dizzy_face","",0,0],["๐Ÿ˜ตโ€๐Ÿ’ซ","face_with_spiral_eyes","",0,0],["๐Ÿคฏ","exploding_head","mind blown",0,0],["๐Ÿค ","cowboy_hat_face","",0,0],["๐Ÿฅณ","partying_face","celebration birthday",0,0],["๐Ÿฅธ","disguised_face","",0,0],["๐Ÿ˜Ž","sunglasses","cool",0,0],["๐Ÿค“","nerd_face","geek glasses",0,0],["๐Ÿง","monocle_face","",0,0],["๐Ÿ˜•","confused","",0,0],["๐Ÿซค","face_with_diagonal_mouth","confused",0,0],["๐Ÿ˜Ÿ","worried","nervous",0,0],["๐Ÿ™","slightly_frowning_face","",0,0],["โ˜น๏ธ","frowning_face","",0,0],["๐Ÿ˜ฎ","open_mouth","surprise impressed wow",0,0],["๐Ÿ˜ฏ","hushed","silence speechless",0,0],["๐Ÿ˜ฒ","astonished","amazed gasp",0,0],["๐Ÿ˜ณ","flushed","",0,0],["๐Ÿฅบ","pleading_face","puppy eyes",0,0],["๐Ÿฅน","face_holding_back_tears","tears gratitude",0,0],["๐Ÿ˜ฆ","frowning","",0,0],["๐Ÿ˜ง","anguished","stunned",0,0],["๐Ÿ˜จ","fearful","scared shocked oops",0,0],["๐Ÿ˜ฐ","cold_sweat","nervous",0,0],["๐Ÿ˜ฅ","disappointed_relieved","phew sweat nervous",0,0],["๐Ÿ˜ข","cry","sad tear",0,0],["๐Ÿ˜ญ","sob","sad cry bawling",0,0],["๐Ÿ˜ฑ","scream","horror shocked",0,0],["๐Ÿ˜–","confounded","",0,0],["๐Ÿ˜ฃ","persevere","struggling",0,0],["๐Ÿ˜ž","disappointed","sad",0,0],["๐Ÿ˜“","sweat","",0,0],["๐Ÿ˜ฉ","weary","tired",0,0],["๐Ÿ˜ซ","tired_face","upset whine",0,0],["๐Ÿฅฑ","yawning_face","",0,0],["๐Ÿ˜ค","triumph","smug",0,0],["๐Ÿ˜ก","rage;pout","angry",0,0],["๐Ÿ˜ ","angry","mad annoyed",0,0],["๐Ÿคฌ","cursing_face","foul",0,0],["๐Ÿ˜ˆ","smiling_imp","devil evil horns",0,0],["๐Ÿ‘ฟ","imp","angry devil evil horns",0,0],["๐Ÿ’€","skull","dead danger poison",0,0],["โ˜ ๏ธ","skull_and_crossbones","danger pirate",0,0],["๐Ÿ’ฉ","hankey;poop;shit","crap",0,0],["๐Ÿคก","clown_face","",0,0],["๐Ÿ‘น","japanese_ogre","monster",0,0],["๐Ÿ‘บ","japanese_goblin","",0,0],["๐Ÿ‘ป","ghost","halloween",0,0],["๐Ÿ‘ฝ","alien","ufo",0,0],["๐Ÿ‘พ","space_invader","game retro",0,0],["๐Ÿค–","robot","",0,0],["๐Ÿ˜บ","smiley_cat","",0,0],["๐Ÿ˜ธ","smile_cat","",0,0],["๐Ÿ˜น","joy_cat","",0,0],["๐Ÿ˜ป","heart_eyes_cat","",0,0],["๐Ÿ˜ผ","smirk_cat","",0,0],["๐Ÿ˜ฝ","kissing_cat","",0,0],["๐Ÿ™€","scream_cat","horror",0,0],["๐Ÿ˜ฟ","crying_cat_face","sad tear",0,0],["๐Ÿ˜พ","pouting_cat","",0,0],["๐Ÿ™ˆ","see_no_evil","monkey blind ignore",0,0],["๐Ÿ™‰","hear_no_evil","monkey deaf",0,0],["๐Ÿ™Š","speak_no_evil","monkey mute hush",0,0],["๐Ÿ’‹","kiss","lipstick",0,0],["๐Ÿ’Œ","love_letter","email envelope",0,0],["๐Ÿ’˜","cupid","love heart",0,0],["๐Ÿ’","gift_heart","chocolates",0,0],["๐Ÿ’–","sparkling_heart","",0,0],["๐Ÿ’—","heartpulse","",0,0],["๐Ÿ’“","heartbeat","",0,0],["๐Ÿ’ž","revolving_hearts","",0,0],["๐Ÿ’•","two_hearts","",0,0],["๐Ÿ’Ÿ","heart_decoration","",0,0],["โฃ๏ธ","heavy_heart_exclamation","",0,0],["๐Ÿ’”","broken_heart","",0,0],["โค๏ธโ€๐Ÿ”ฅ","heart_on_fire","",0,0],["โค๏ธโ€๐Ÿฉน","mending_heart","",0,0],["โค๏ธ","heart","love",0,0],["๐Ÿงก","orange_heart","",0,0],["๐Ÿ’›","yellow_heart","",0,0],["๐Ÿ’š","green_heart","",0,0],["๐Ÿ’™","blue_heart","",0,0],["๐Ÿ’œ","purple_heart","",0,0],["๐ŸคŽ","brown_heart","",0,0],["๐Ÿ–ค","black_heart","",0,0],["๐Ÿค","white_heart","",0,0],["๐Ÿ’ฏ","100","score perfect",0,0],["๐Ÿ’ข","anger","angry",0,0],["๐Ÿ’ฅ","boom;collision","explode",0,0],["๐Ÿ’ซ","dizzy","star",0,0],["๐Ÿ’ฆ","sweat_drops","water workout",0,0],["๐Ÿ’จ","dash","wind blow fast",0,0],["๐Ÿ•ณ๏ธ","hole","",0,0],["๐Ÿ’ฃ","bomb","boom",0,0],["๐Ÿ’ฌ","speech_balloon","comment",0,0],["๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ","eye_speech_bubble","",0,0],["๐Ÿ—จ๏ธ","left_speech_bubble","",0,0],["๐Ÿ—ฏ๏ธ","right_anger_bubble","",0,0],["๐Ÿ’ญ","thought_balloon","thinking",0,0],["๐Ÿ’ค","zzz","sleeping",0,0],["๐Ÿ‘‹","wave","goodbye",1,1],["๐Ÿคš","raised_back_of_hand","",1,1],["๐Ÿ–๏ธ","raised_hand_with_fingers_splayed","",1,1],["โœ‹","hand;raised_hand","highfive stop",1,1],["๐Ÿ––","vulcan_salute","prosper spock",1,1],["๐Ÿซฑ","rightwards_hand","",1,1],["๐Ÿซฒ","leftwards_hand","",1,1],["๐Ÿซณ","palm_down_hand","",1,1],["๐Ÿซด","palm_up_hand","",1,1],["๐Ÿ‘Œ","ok_hand","",1,1],["๐ŸคŒ","pinched_fingers","",1,1],["๐Ÿค","pinching_hand","",1,1],["โœŒ๏ธ","v","victory peace",1,1],["๐Ÿคž","crossed_fingers","luck hopeful",1,1],["๐Ÿซฐ","hand_with_index_finger_and_thumb_crossed","",1,1],["๐ŸคŸ","love_you_gesture","",1,1],["๐Ÿค˜","metal","",1,1],["๐Ÿค™","call_me_hand","",1,1],["๐Ÿ‘ˆ","point_left","",1,1],["๐Ÿ‘‰","point_right","",1,1],["๐Ÿ‘†","point_up_2","",1,1],["๐Ÿ–•","middle_finger;fu","",1,1],["๐Ÿ‘‡","point_down","",1,1],["โ˜๏ธ","point_up","",1,1],["๐Ÿซต","index_pointing_at_the_viewer","",1,1],["๐Ÿ‘","+1;thumbsup","approve ok",1,1],["๐Ÿ‘Ž","-1;thumbsdown","disapprove bury",1,1],["โœŠ","fist_raised;fist","power",1,1],["๐Ÿ‘Š","fist_oncoming;facepunch;punch","attack",1,1],["๐Ÿค›","fist_left","",1,1],["๐Ÿคœ","fist_right","",1,1],["๐Ÿ‘","clap","praise applause",1,1],["๐Ÿ™Œ","raised_hands","hooray",1,1],["๐Ÿซถ","heart_hands","love",1,1],["๐Ÿ‘","open_hands","",1,1],["๐Ÿคฒ","palms_up_together","",1,1],["๐Ÿค","handshake","deal",1,1],["๐Ÿ™","pray","please hope wish",1,1],["โœ๏ธ","writing_hand","",1,1],["๐Ÿ’…","nail_care","beauty manicure",1,1],["๐Ÿคณ","selfie","",1,1],["๐Ÿ’ช","muscle","flex bicep strong workout",1,1],["๐Ÿฆพ","mechanical_arm","",1,0],["๐Ÿฆฟ","mechanical_leg","",1,0],["๐Ÿฆต","leg","",1,1],["๐Ÿฆถ","foot","",1,1],["๐Ÿ‘‚","ear","hear sound listen",1,1],["๐Ÿฆป","ear_with_hearing_aid","",1,1],["๐Ÿ‘ƒ","nose","smell",1,1],["๐Ÿง ","brain","",1,0],["๐Ÿซ€","anatomical_heart","",1,0],["๐Ÿซ","lungs","",1,0],["๐Ÿฆท","tooth","",1,0],["๐Ÿฆด","bone","",1,0],["๐Ÿ‘€","eyes","look see watch",1,0],["๐Ÿ‘๏ธ","eye","",1,0],["๐Ÿ‘…","tongue","taste",1,0],["๐Ÿ‘„","lips","kiss",1,0],["๐Ÿซฆ","biting_lip","",1,0],["๐Ÿ‘ถ","baby","child newborn",1,1],["๐Ÿง’","child","",1,1],["๐Ÿ‘ฆ","boy","child",1,1],["๐Ÿ‘ง","girl","child",1,1],["๐Ÿง‘","adult","",1,1],["๐Ÿ‘ฑ","blond_haired_person","",1,1],["๐Ÿ‘จ","man","mustache father dad",1,1],["๐Ÿง”","bearded_person","",1,1],["๐Ÿง”โ€โ™‚๏ธ","man_beard","",1,1],["๐Ÿง”โ€โ™€๏ธ","woman_beard","",1,1],["๐Ÿ‘จโ€๐Ÿฆฐ","red_haired_man","",1,1],["๐Ÿ‘จโ€๐Ÿฆฑ","curly_haired_man","",1,1],["๐Ÿ‘จโ€๐Ÿฆณ","white_haired_man","",1,1],["๐Ÿ‘จโ€๐Ÿฆฒ","bald_man","",1,1],["๐Ÿ‘ฉ","woman","girls",1,1],["๐Ÿ‘ฉโ€๐Ÿฆฐ","red_haired_woman","",1,1],["๐Ÿง‘โ€๐Ÿฆฐ","person_red_hair","",1,1],["๐Ÿ‘ฉโ€๐Ÿฆฑ","curly_haired_woman","",1,1],["๐Ÿง‘โ€๐Ÿฆฑ","person_curly_hair","",1,1],["๐Ÿ‘ฉโ€๐Ÿฆณ","white_haired_woman","",1,1],["๐Ÿง‘โ€๐Ÿฆณ","person_white_hair","",1,1],["๐Ÿ‘ฉโ€๐Ÿฆฒ","bald_woman","",1,1],["๐Ÿง‘โ€๐Ÿฆฒ","person_bald","",1,1],["๐Ÿ‘ฑโ€โ™€๏ธ","blond_haired_woman;blonde_woman","",1,1],["๐Ÿ‘ฑโ€โ™‚๏ธ","blond_haired_man","",1,1],["๐Ÿง“","older_adult","",1,1],["๐Ÿ‘ด","older_man","",1,1],["๐Ÿ‘ต","older_woman","",1,1],["๐Ÿ™","frowning_person","",1,1],["๐Ÿ™โ€โ™‚๏ธ","frowning_man","",1,1],["๐Ÿ™โ€โ™€๏ธ","frowning_woman","",1,1],["๐Ÿ™Ž","pouting_face","",1,1],["๐Ÿ™Žโ€โ™‚๏ธ","pouting_man","",1,1],["๐Ÿ™Žโ€โ™€๏ธ","pouting_woman","",1,1],["๐Ÿ™…","no_good","stop halt denied",1,1],["๐Ÿ™…โ€โ™‚๏ธ","no_good_man;ng_man","stop halt denied",1,1],["๐Ÿ™…โ€โ™€๏ธ","no_good_woman;ng_woman","stop halt denied",1,1],["๐Ÿ™†","ok_person","",1,1],["๐Ÿ™†โ€โ™‚๏ธ","ok_man","",1,1],["๐Ÿ™†โ€โ™€๏ธ","ok_woman","",1,1],["๐Ÿ’","tipping_hand_person;information_desk_person","",1,1],["๐Ÿ’โ€โ™‚๏ธ","tipping_hand_man;sassy_man","information",1,1],["๐Ÿ’โ€โ™€๏ธ","tipping_hand_woman;sassy_woman","information",1,1],["๐Ÿ™‹","raising_hand","",1,1],["๐Ÿ™‹โ€โ™‚๏ธ","raising_hand_man","",1,1],["๐Ÿ™‹โ€โ™€๏ธ","raising_hand_woman","",1,1],["๐Ÿง","deaf_person","",1,1],["๐Ÿงโ€โ™‚๏ธ","deaf_man","",1,1],["๐Ÿงโ€โ™€๏ธ","deaf_woman","",1,1],["๐Ÿ™‡","bow","respect thanks",1,1],["๐Ÿ™‡โ€โ™‚๏ธ","bowing_man","respect thanks",1,1],["๐Ÿ™‡โ€โ™€๏ธ","bowing_woman","respect thanks",1,1],["๐Ÿคฆ","facepalm","",1,1],["๐Ÿคฆโ€โ™‚๏ธ","man_facepalming","",1,1],["๐Ÿคฆโ€โ™€๏ธ","woman_facepalming","",1,1],["๐Ÿคท","shrug","",1,1],["๐Ÿคทโ€โ™‚๏ธ","man_shrugging","",1,1],["๐Ÿคทโ€โ™€๏ธ","woman_shrugging","",1,1],["๐Ÿง‘โ€โš•๏ธ","health_worker","",1,1],["๐Ÿ‘จโ€โš•๏ธ","man_health_worker","doctor nurse",1,1],["๐Ÿ‘ฉโ€โš•๏ธ","woman_health_worker","doctor nurse",1,1],["๐Ÿง‘โ€๐ŸŽ“","student","",1,1],["๐Ÿ‘จโ€๐ŸŽ“","man_student","graduation",1,1],["๐Ÿ‘ฉโ€๐ŸŽ“","woman_student","graduation",1,1],["๐Ÿง‘โ€๐Ÿซ","teacher","",1,1],["๐Ÿ‘จโ€๐Ÿซ","man_teacher","school professor",1,1],["๐Ÿ‘ฉโ€๐Ÿซ","woman_teacher","school professor",1,1],["๐Ÿง‘โ€โš–๏ธ","judge","",1,1],["๐Ÿ‘จโ€โš–๏ธ","man_judge","justice",1,1],["๐Ÿ‘ฉโ€โš–๏ธ","woman_judge","justice",1,1],["๐Ÿง‘โ€๐ŸŒพ","farmer","",1,1],["๐Ÿ‘จโ€๐ŸŒพ","man_farmer","",1,1],["๐Ÿ‘ฉโ€๐ŸŒพ","woman_farmer","",1,1],["๐Ÿง‘โ€๐Ÿณ","cook","",1,1],["๐Ÿ‘จโ€๐Ÿณ","man_cook","chef",1,1],["๐Ÿ‘ฉโ€๐Ÿณ","woman_cook","chef",1,1],["๐Ÿง‘โ€๐Ÿ”ง","mechanic","",1,1],["๐Ÿ‘จโ€๐Ÿ”ง","man_mechanic","",1,1],["๐Ÿ‘ฉโ€๐Ÿ”ง","woman_mechanic","",1,1],["๐Ÿง‘โ€๐Ÿญ","factory_worker","",1,1],["๐Ÿ‘จโ€๐Ÿญ","man_factory_worker","",1,1],["๐Ÿ‘ฉโ€๐Ÿญ","woman_factory_worker","",1,1],["๐Ÿง‘โ€๐Ÿ’ผ","office_worker","",1,1],["๐Ÿ‘จโ€๐Ÿ’ผ","man_office_worker","business",1,1],["๐Ÿ‘ฉโ€๐Ÿ’ผ","woman_office_worker","business",1,1],["๐Ÿง‘โ€๐Ÿ”ฌ","scientist","",1,1],["๐Ÿ‘จโ€๐Ÿ”ฌ","man_scientist","research",1,1],["๐Ÿ‘ฉโ€๐Ÿ”ฌ","woman_scientist","research",1,1],["๐Ÿง‘โ€๐Ÿ’ป","technologist","",1,1],["๐Ÿ‘จโ€๐Ÿ’ป","man_technologist","coder",1,1],["๐Ÿ‘ฉโ€๐Ÿ’ป","woman_technologist","coder",1,1],["๐Ÿง‘โ€๐ŸŽค","singer","",1,1],["๐Ÿ‘จโ€๐ŸŽค","man_singer","rockstar",1,1],["๐Ÿ‘ฉโ€๐ŸŽค","woman_singer","rockstar",1,1],["๐Ÿง‘โ€๐ŸŽจ","artist","",1,1],["๐Ÿ‘จโ€๐ŸŽจ","man_artist","painter",1,1],["๐Ÿ‘ฉโ€๐ŸŽจ","woman_artist","painter",1,1],["๐Ÿง‘โ€โœˆ๏ธ","pilot","",1,1],["๐Ÿ‘จโ€โœˆ๏ธ","man_pilot","",1,1],["๐Ÿ‘ฉโ€โœˆ๏ธ","woman_pilot","",1,1],["๐Ÿง‘โ€๐Ÿš€","astronaut","",1,1],["๐Ÿ‘จโ€๐Ÿš€","man_astronaut","space",1,1],["๐Ÿ‘ฉโ€๐Ÿš€","woman_astronaut","space",1,1],["๐Ÿง‘โ€๐Ÿš’","firefighter","",1,1],["๐Ÿ‘จโ€๐Ÿš’","man_firefighter","",1,1],["๐Ÿ‘ฉโ€๐Ÿš’","woman_firefighter","",1,1],["๐Ÿ‘ฎ","police_officer;cop","law",1,1],["๐Ÿ‘ฎโ€โ™‚๏ธ","policeman","law cop",1,1],["๐Ÿ‘ฎโ€โ™€๏ธ","policewoman","law cop",1,1],["๐Ÿ•ต๏ธ","detective","sleuth",1,1],["๐Ÿ•ต๏ธโ€โ™‚๏ธ","male_detective","sleuth",1,1],["๐Ÿ•ต๏ธโ€โ™€๏ธ","female_detective","sleuth",1,1],["๐Ÿ’‚","guard","",1,1],["๐Ÿ’‚โ€โ™‚๏ธ","guardsman","",1,1],["๐Ÿ’‚โ€โ™€๏ธ","guardswoman","",1,1],["๐Ÿฅท","ninja","",1,1],["๐Ÿ‘ท","construction_worker","helmet",1,1],["๐Ÿ‘ทโ€โ™‚๏ธ","construction_worker_man","helmet",1,1],["๐Ÿ‘ทโ€โ™€๏ธ","construction_worker_woman","helmet",1,1],["๐Ÿซ…","person_with_crown","",1,1],["๐Ÿคด","prince","crown royal",1,1],["๐Ÿ‘ธ","princess","crown royal",1,1],["๐Ÿ‘ณ","person_with_turban","",1,1],["๐Ÿ‘ณโ€โ™‚๏ธ","man_with_turban","",1,1],["๐Ÿ‘ณโ€โ™€๏ธ","woman_with_turban","",1,1],["๐Ÿ‘ฒ","man_with_gua_pi_mao","",1,1],["๐Ÿง•","woman_with_headscarf","hijab",1,1],["๐Ÿคต","person_in_tuxedo","groom marriage wedding",1,1],["๐Ÿคตโ€โ™‚๏ธ","man_in_tuxedo","",1,1],["๐Ÿคตโ€โ™€๏ธ","woman_in_tuxedo","",1,1],["๐Ÿ‘ฐ","person_with_veil","marriage wedding",1,1],["๐Ÿ‘ฐโ€โ™‚๏ธ","man_with_veil","",1,1],["๐Ÿ‘ฐโ€โ™€๏ธ","woman_with_veil;bride_with_veil","",1,1],["๐Ÿคฐ","pregnant_woman","",1,1],["๐Ÿซƒ","pregnant_man","",1,1],["๐Ÿซ„","pregnant_person","",1,1],["๐Ÿคฑ","breast_feeding","nursing",1,1],["๐Ÿ‘ฉโ€๐Ÿผ","woman_feeding_baby","",1,1],["๐Ÿ‘จโ€๐Ÿผ","man_feeding_baby","",1,1],["๐Ÿง‘โ€๐Ÿผ","person_feeding_baby","",1,1],["๐Ÿ‘ผ","angel","",1,1],["๐ŸŽ…","santa","christmas",1,1],["๐Ÿคถ","mrs_claus","santa",1,1],["๐Ÿง‘โ€๐ŸŽ„","mx_claus","",1,1],["๐Ÿฆธ","superhero","",1,1],["๐Ÿฆธโ€โ™‚๏ธ","superhero_man","",1,1],["๐Ÿฆธโ€โ™€๏ธ","superhero_woman","",1,1],["๐Ÿฆน","supervillain","",1,1],["๐Ÿฆนโ€โ™‚๏ธ","supervillain_man","",1,1],["๐Ÿฆนโ€โ™€๏ธ","supervillain_woman","",1,1],["๐Ÿง™","mage","wizard",1,1],["๐Ÿง™โ€โ™‚๏ธ","mage_man","wizard",1,1],["๐Ÿง™โ€โ™€๏ธ","mage_woman","wizard",1,1],["๐Ÿงš","fairy","",1,1],["๐Ÿงšโ€โ™‚๏ธ","fairy_man","",1,1],["๐Ÿงšโ€โ™€๏ธ","fairy_woman","",1,1],["๐Ÿง›","vampire","",1,1],["๐Ÿง›โ€โ™‚๏ธ","vampire_man","",1,1],["๐Ÿง›โ€โ™€๏ธ","vampire_woman","",1,1],["๐Ÿงœ","merperson","",1,1],["๐Ÿงœโ€โ™‚๏ธ","merman","",1,1],["๐Ÿงœโ€โ™€๏ธ","mermaid","",1,1],["๐Ÿง","elf","",1,1],["๐Ÿงโ€โ™‚๏ธ","elf_man","",1,1],["๐Ÿงโ€โ™€๏ธ","elf_woman","",1,1],["๐Ÿงž","genie","",1,0],["๐Ÿงžโ€โ™‚๏ธ","genie_man","",1,0],["๐Ÿงžโ€โ™€๏ธ","genie_woman","",1,0],["๐ŸงŸ","zombie","",1,0],["๐ŸงŸโ€โ™‚๏ธ","zombie_man","",1,0],["๐ŸงŸโ€โ™€๏ธ","zombie_woman","",1,0],["๐ŸงŒ","troll","",1,0],["๐Ÿ’†","massage","spa",1,1],["๐Ÿ’†โ€โ™‚๏ธ","massage_man","spa",1,1],["๐Ÿ’†โ€โ™€๏ธ","massage_woman","spa",1,1],["๐Ÿ’‡","haircut","beauty",1,1],["๐Ÿ’‡โ€โ™‚๏ธ","haircut_man","",1,1],["๐Ÿ’‡โ€โ™€๏ธ","haircut_woman","",1,1],["๐Ÿšถ","walking","",1,1],["๐Ÿšถโ€โ™‚๏ธ","walking_man","",1,1],["๐Ÿšถโ€โ™€๏ธ","walking_woman","",1,1],["๐Ÿง","standing_person","",1,1],["๐Ÿงโ€โ™‚๏ธ","standing_man","",1,1],["๐Ÿงโ€โ™€๏ธ","standing_woman","",1,1],["๐ŸงŽ","kneeling_person","",1,1],["๐ŸงŽโ€โ™‚๏ธ","kneeling_man","",1,1],["๐ŸงŽโ€โ™€๏ธ","kneeling_woman","",1,1],["๐Ÿง‘โ€๐Ÿฆฏ","person_with_probing_cane","",1,1],["๐Ÿ‘จโ€๐Ÿฆฏ","man_with_probing_cane","",1,1],["๐Ÿ‘ฉโ€๐Ÿฆฏ","woman_with_probing_cane","",1,1],["๐Ÿง‘โ€๐Ÿฆผ","person_in_motorized_wheelchair","",1,1],["๐Ÿ‘จโ€๐Ÿฆผ","man_in_motorized_wheelchair","",1,1],["๐Ÿ‘ฉโ€๐Ÿฆผ","woman_in_motorized_wheelchair","",1,1],["๐Ÿง‘โ€๐Ÿฆฝ","person_in_manual_wheelchair","",1,1],["๐Ÿ‘จโ€๐Ÿฆฝ","man_in_manual_wheelchair","",1,1],["๐Ÿ‘ฉโ€๐Ÿฆฝ","woman_in_manual_wheelchair","",1,1],["๐Ÿƒ","runner;running","exercise workout marathon",1,1],["๐Ÿƒโ€โ™‚๏ธ","running_man","exercise workout marathon",1,1],["๐Ÿƒโ€โ™€๏ธ","running_woman","exercise workout marathon",1,1],["๐Ÿ’ƒ","woman_dancing;dancer","dress",1,1],["๐Ÿ•บ","man_dancing","dancer",1,1],["๐Ÿ•ด๏ธ","business_suit_levitating","",1,1],["๐Ÿ‘ฏ","dancers","bunny",1,0],["๐Ÿ‘ฏโ€โ™‚๏ธ","dancing_men","bunny",1,0],["๐Ÿ‘ฏโ€โ™€๏ธ","dancing_women","bunny",1,0],["๐Ÿง–","sauna_person","steamy",1,1],["๐Ÿง–โ€โ™‚๏ธ","sauna_man","steamy",1,1],["๐Ÿง–โ€โ™€๏ธ","sauna_woman","steamy",1,1],["๐Ÿง—","climbing","bouldering",1,1],["๐Ÿง—โ€โ™‚๏ธ","climbing_man","bouldering",1,1],["๐Ÿง—โ€โ™€๏ธ","climbing_woman","bouldering",1,1],["๐Ÿคบ","person_fencing","",1,0],["๐Ÿ‡","horse_racing","",1,1],["โ›ท๏ธ","skier","",1,0],["๐Ÿ‚","snowboarder","",1,1],["๐ŸŒ๏ธ","golfing","",1,1],["๐ŸŒ๏ธโ€โ™‚๏ธ","golfing_man","",1,1],["๐ŸŒ๏ธโ€โ™€๏ธ","golfing_woman","",1,1],["๐Ÿ„","surfer","",1,1],["๐Ÿ„โ€โ™‚๏ธ","surfing_man","",1,1],["๐Ÿ„โ€โ™€๏ธ","surfing_woman","",1,1],["๐Ÿšฃ","rowboat","",1,1],["๐Ÿšฃโ€โ™‚๏ธ","rowing_man","",1,1],["๐Ÿšฃโ€โ™€๏ธ","rowing_woman","",1,1],["๐ŸŠ","swimmer","",1,1],["๐ŸŠโ€โ™‚๏ธ","swimming_man","",1,1],["๐ŸŠโ€โ™€๏ธ","swimming_woman","",1,1],["โ›น๏ธ","bouncing_ball_person","basketball",1,1],["โ›น๏ธโ€โ™‚๏ธ","bouncing_ball_man;basketball_man","",1,1],["โ›น๏ธโ€โ™€๏ธ","bouncing_ball_woman;basketball_woman","",1,1],["๐Ÿ‹๏ธ","weight_lifting","gym workout",1,1],["๐Ÿ‹๏ธโ€โ™‚๏ธ","weight_lifting_man","gym workout",1,1],["๐Ÿ‹๏ธโ€โ™€๏ธ","weight_lifting_woman","gym workout",1,1],["๐Ÿšด","bicyclist","",1,1],["๐Ÿšดโ€โ™‚๏ธ","biking_man","",1,1],["๐Ÿšดโ€โ™€๏ธ","biking_woman","",1,1],["๐Ÿšต","mountain_bicyclist","",1,1],["๐Ÿšตโ€โ™‚๏ธ","mountain_biking_man","",1,1],["๐Ÿšตโ€โ™€๏ธ","mountain_biking_woman","",1,1],["๐Ÿคธ","cartwheeling","",1,1],["๐Ÿคธโ€โ™‚๏ธ","man_cartwheeling","",1,1],["๐Ÿคธโ€โ™€๏ธ","woman_cartwheeling","",1,1],["๐Ÿคผ","wrestling","",1,0],["๐Ÿคผโ€โ™‚๏ธ","men_wrestling","",1,0],["๐Ÿคผโ€โ™€๏ธ","women_wrestling","",1,0],["๐Ÿคฝ","water_polo","",1,1],["๐Ÿคฝโ€โ™‚๏ธ","man_playing_water_polo","",1,1],["๐Ÿคฝโ€โ™€๏ธ","woman_playing_water_polo","",1,1],["๐Ÿคพ","handball_person","",1,1],["๐Ÿคพโ€โ™‚๏ธ","man_playing_handball","",1,1],["๐Ÿคพโ€โ™€๏ธ","woman_playing_handball","",1,1],["๐Ÿคน","juggling_person","",1,1],["๐Ÿคนโ€โ™‚๏ธ","man_juggling","",1,1],["๐Ÿคนโ€โ™€๏ธ","woman_juggling","",1,1],["๐Ÿง˜","lotus_position","meditation",1,1],["๐Ÿง˜โ€โ™‚๏ธ","lotus_position_man","meditation",1,1],["๐Ÿง˜โ€โ™€๏ธ","lotus_position_woman","meditation",1,1],["๐Ÿ›€","bath","shower",1,1],["๐Ÿ›Œ","sleeping_bed","",1,1],["๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘","people_holding_hands","couple date",1,1],["๐Ÿ‘ญ","two_women_holding_hands","couple date",1,1],["๐Ÿ‘ซ","couple","date",1,1],["๐Ÿ‘ฌ","two_men_holding_hands","couple date",1,1],["๐Ÿ’","couplekiss","",1,1],["๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ","couplekiss_man_woman","",1,1],["๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ","couplekiss_man_man","",1,1],["๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ","couplekiss_woman_woman","",1,1],["๐Ÿ’‘","couple_with_heart","",1,1],["๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘จ","couple_with_heart_woman_man","",1,1],["๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ","couple_with_heart_man_man","",1,1],["๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ","couple_with_heart_woman_woman","",1,1],["๐Ÿ‘ช","family","home parents child",1,0],["๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ","family_man_woman_boy","",1,0],["๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง","family_man_woman_girl","",1,0],["๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","family_man_woman_girl_boy","",1,0],["๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","family_man_woman_boy_boy","",1,0],["๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","family_man_woman_girl_girl","",1,0],["๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ","family_man_man_boy","",1,0],["๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง","family_man_man_girl","",1,0],["๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","family_man_man_girl_boy","",1,0],["๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","family_man_man_boy_boy","",1,0],["๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง","family_man_man_girl_girl","",1,0],["๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ","family_woman_woman_boy","",1,0],["๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง","family_woman_woman_girl","",1,0],["๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","family_woman_woman_girl_boy","",1,0],["๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","family_woman_woman_boy_boy","",1,0],["๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","family_woman_woman_girl_girl","",1,0],["๐Ÿ‘จโ€๐Ÿ‘ฆ","family_man_boy","",1,0],["๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","family_man_boy_boy","",1,0],["๐Ÿ‘จโ€๐Ÿ‘ง","family_man_girl","",1,0],["๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","family_man_girl_boy","",1,0],["๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง","family_man_girl_girl","",1,0],["๐Ÿ‘ฉโ€๐Ÿ‘ฆ","family_woman_boy","",1,0],["๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ","family_woman_boy_boy","",1,0],["๐Ÿ‘ฉโ€๐Ÿ‘ง","family_woman_girl","",1,0],["๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ","family_woman_girl_boy","",1,0],["๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง","family_woman_girl_girl","",1,0],["๐Ÿ—ฃ๏ธ","speaking_head","",1,0],["๐Ÿ‘ค","bust_in_silhouette","user",1,0],["๐Ÿ‘ฅ","busts_in_silhouette","users group team",1,0],["๐Ÿซ‚","people_hugging","",1,0],["๐Ÿ‘ฃ","footprints","feet tracks",1,0],["๐Ÿต","monkey_face","",2,0],["๐Ÿ’","monkey","",2,0],["๐Ÿฆ","gorilla","",2,0],["๐Ÿฆง","orangutan","",2,0],["๐Ÿถ","dog","pet",2,0],["๐Ÿ•","dog2","",2,0],["๐Ÿฆฎ","guide_dog","",2,0],["๐Ÿ•โ€๐Ÿฆบ","service_dog","",2,0],["๐Ÿฉ","poodle","dog",2,0],["๐Ÿบ","wolf","",2,0],["๐ŸฆŠ","fox_face","",2,0],["๐Ÿฆ","raccoon","",2,0],["๐Ÿฑ","cat","pet",2,0],["๐Ÿˆ","cat2","",2,0],["๐Ÿˆโ€โฌ›","black_cat","",2,0],["๐Ÿฆ","lion","",2,0],["๐Ÿฏ","tiger","",2,0],["๐Ÿ…","tiger2","",2,0],["๐Ÿ†","leopard","",2,0],["๐Ÿด","horse","",2,0],["๐ŸŽ","racehorse","speed",2,0],["๐Ÿฆ„","unicorn","",2,0],["๐Ÿฆ“","zebra","",2,0],["๐ŸฆŒ","deer","",2,0],["๐Ÿฆฌ","bison","",2,0],["๐Ÿฎ","cow","",2,0],["๐Ÿ‚","ox","",2,0],["๐Ÿƒ","water_buffalo","",2,0],["๐Ÿ„","cow2","",2,0],["๐Ÿท","pig","",2,0],["๐Ÿ–","pig2","",2,0],["๐Ÿ—","boar","",2,0],["๐Ÿฝ","pig_nose","",2,0],["๐Ÿ","ram","",2,0],["๐Ÿ‘","sheep","",2,0],["๐Ÿ","goat","",2,0],["๐Ÿช","dromedary_camel","desert",2,0],["๐Ÿซ","camel","",2,0],["๐Ÿฆ™","llama","",2,0],["๐Ÿฆ’","giraffe","",2,0],["๐Ÿ˜","elephant","",2,0],["๐Ÿฆฃ","mammoth","",2,0],["๐Ÿฆ","rhinoceros","",2,0],["๐Ÿฆ›","hippopotamus","",2,0],["๐Ÿญ","mouse","",2,0],["๐Ÿ","mouse2","",2,0],["๐Ÿ€","rat","",2,0],["๐Ÿน","hamster","pet",2,0],["๐Ÿฐ","rabbit","bunny",2,0],["๐Ÿ‡","rabbit2","",2,0],["๐Ÿฟ๏ธ","chipmunk","",2,0],["๐Ÿฆซ","beaver","",2,0],["๐Ÿฆ”","hedgehog","",2,0],["๐Ÿฆ‡","bat","",2,0],["๐Ÿป","bear","",2,0],["๐Ÿปโ€โ„๏ธ","polar_bear","",2,0],["๐Ÿจ","koala","",2,0],["๐Ÿผ","panda_face","",2,0],["๐Ÿฆฅ","sloth","",2,0],["๐Ÿฆฆ","otter","",2,0],["๐Ÿฆจ","skunk","",2,0],["๐Ÿฆ˜","kangaroo","",2,0],["๐Ÿฆก","badger","",2,0],["๐Ÿพ","feet;paw_prints","",2,0],["๐Ÿฆƒ","turkey","thanksgiving",2,0],["๐Ÿ”","chicken","",2,0],["๐Ÿ“","rooster","",2,0],["๐Ÿฃ","hatching_chick","",2,0],["๐Ÿค","baby_chick","",2,0],["๐Ÿฅ","hatched_chick","",2,0],["๐Ÿฆ","bird","",2,0],["๐Ÿง","penguin","",2,0],["๐Ÿ•Š๏ธ","dove","peace",2,0],["๐Ÿฆ…","eagle","",2,0],["๐Ÿฆ†","duck","",2,0],["๐Ÿฆข","swan","",2,0],["๐Ÿฆ‰","owl","",2,0],["๐Ÿฆค","dodo","",2,0],["๐Ÿชถ","feather","",2,0],["๐Ÿฆฉ","flamingo","",2,0],["๐Ÿฆš","peacock","",2,0],["๐Ÿฆœ","parrot","",2,0],["๐Ÿธ","frog","",2,0],["๐ŸŠ","crocodile","",2,0],["๐Ÿข","turtle","slow",2,0],["๐ŸฆŽ","lizard","",2,0],["๐Ÿ","snake","",2,0],["๐Ÿฒ","dragon_face","",2,0],["๐Ÿ‰","dragon","",2,0],["๐Ÿฆ•","sauropod","dinosaur",2,0],["๐Ÿฆ–","t-rex","dinosaur",2,0],["๐Ÿณ","whale","sea",2,0],["๐Ÿ‹","whale2","",2,0],["๐Ÿฌ","dolphin;flipper","",2,0],["๐Ÿฆญ","seal","",2,0],["๐ŸŸ","fish","",2,0],["๐Ÿ ","tropical_fish","",2,0],["๐Ÿก","blowfish","",2,0],["๐Ÿฆˆ","shark","",2,0],["๐Ÿ™","octopus","",2,0],["๐Ÿš","shell","sea beach",2,0],["๐Ÿชธ","coral","",2,0],["๐ŸŒ","snail","slow",2,0],["๐Ÿฆ‹","butterfly","",2,0],["๐Ÿ›","bug","",2,0],["๐Ÿœ","ant","",2,0],["๐Ÿ","bee;honeybee","",2,0],["๐Ÿชฒ","beetle","",2,0],["๐Ÿž","lady_beetle","bug",2,0],["๐Ÿฆ—","cricket","",2,0],["๐Ÿชณ","cockroach","",2,0],["๐Ÿ•ท๏ธ","spider","",2,0],["๐Ÿ•ธ๏ธ","spider_web","",2,0],["๐Ÿฆ‚","scorpion","",2,0],["๐ŸฆŸ","mosquito","",2,0],["๐Ÿชฐ","fly","",2,0],["๐Ÿชฑ","worm","",2,0],["๐Ÿฆ ","microbe","germ",2,0],["๐Ÿ’","bouquet","flowers",2,0],["๐ŸŒธ","cherry_blossom","flower spring",2,0],["๐Ÿ’ฎ","white_flower","",2,0],["๐Ÿชท","lotus","",2,0],["๐Ÿต๏ธ","rosette","",2,0],["๐ŸŒน","rose","flower",2,0],["๐Ÿฅ€","wilted_flower","",2,0],["๐ŸŒบ","hibiscus","",2,0],["๐ŸŒป","sunflower","",2,0],["๐ŸŒผ","blossom","",2,0],["๐ŸŒท","tulip","flower",2,0],["๐ŸŒฑ","seedling","plant",2,0],["๐Ÿชด","potted_plant","",2,0],["๐ŸŒฒ","evergreen_tree","wood",2,0],["๐ŸŒณ","deciduous_tree","wood",2,0],["๐ŸŒด","palm_tree","",2,0],["๐ŸŒต","cactus","",2,0],["๐ŸŒพ","ear_of_rice","",2,0],["๐ŸŒฟ","herb","",2,0],["โ˜˜๏ธ","shamrock","",2,0],["๐Ÿ€","four_leaf_clover","luck",2,0],["๐Ÿ","maple_leaf","canada",2,0],["๐Ÿ‚","fallen_leaf","autumn",2,0],["๐Ÿƒ","leaves","leaf",2,0],["๐Ÿชน","empty_nest","",2,0],["๐Ÿชบ","nest_with_eggs","",2,0],["๐Ÿ‡","grapes","",3,0],["๐Ÿˆ","melon","",3,0],["๐Ÿ‰","watermelon","",3,0],["๐ŸŠ","tangerine;orange;mandarin","",3,0],["๐Ÿ‹","lemon","",3,0],["๐ŸŒ","banana","fruit",3,0],["๐Ÿ","pineapple","",3,0],["๐Ÿฅญ","mango","",3,0],["๐ŸŽ","apple","",3,0],["๐Ÿ","green_apple","fruit",3,0],["๐Ÿ","pear","",3,0],["๐Ÿ‘","peach","",3,0],["๐Ÿ’","cherries","fruit",3,0],["๐Ÿ“","strawberry","fruit",3,0],["๐Ÿซ","blueberries","",3,0],["๐Ÿฅ","kiwi_fruit","",3,0],["๐Ÿ…","tomato","",3,0],["๐Ÿซ’","olive","",3,0],["๐Ÿฅฅ","coconut","",3,0],["๐Ÿฅ‘","avocado","",3,0],["๐Ÿ†","eggplant","aubergine",3,0],["๐Ÿฅ”","potato","",3,0],["๐Ÿฅ•","carrot","",3,0],["๐ŸŒฝ","corn","",3,0],["๐ŸŒถ๏ธ","hot_pepper","spicy",3,0],["๐Ÿซ‘","bell_pepper","",3,0],["๐Ÿฅ’","cucumber","",3,0],["๐Ÿฅฌ","leafy_green","",3,0],["๐Ÿฅฆ","broccoli","",3,0],["๐Ÿง„","garlic","",3,0],["๐Ÿง…","onion","",3,0],["๐Ÿ„","mushroom","",3,0],["๐Ÿฅœ","peanuts","",3,0],["๐Ÿซ˜","beans","",3,0],["๐ŸŒฐ","chestnut","",3,0],["๐Ÿž","bread","toast",3,0],["๐Ÿฅ","croissant","",3,0],["๐Ÿฅ–","baguette_bread","",3,0],["๐Ÿซ“","flatbread","",3,0],["๐Ÿฅจ","pretzel","",3,0],["๐Ÿฅฏ","bagel","",3,0],["๐Ÿฅž","pancakes","",3,0],["๐Ÿง‡","waffle","",3,0],["๐Ÿง€","cheese","",3,0],["๐Ÿ–","meat_on_bone","",3,0],["๐Ÿ—","poultry_leg","meat chicken",3,0],["๐Ÿฅฉ","cut_of_meat","",3,0],["๐Ÿฅ“","bacon","",3,0],["๐Ÿ”","hamburger","burger",3,0],["๐ŸŸ","fries","",3,0],["๐Ÿ•","pizza","",3,0],["๐ŸŒญ","hotdog","",3,0],["๐Ÿฅช","sandwich","",3,0],["๐ŸŒฎ","taco","",3,0],["๐ŸŒฏ","burrito","",3,0],["๐Ÿซ”","tamale","",3,0],["๐Ÿฅ™","stuffed_flatbread","",3,0],["๐Ÿง†","falafel","",3,0],["๐Ÿฅš","egg","",3,0],["๐Ÿณ","fried_egg","breakfast",3,0],["๐Ÿฅ˜","shallow_pan_of_food","paella curry",3,0],["๐Ÿฒ","stew","",3,0],["๐Ÿซ•","fondue","",3,0],["๐Ÿฅฃ","bowl_with_spoon","",3,0],["๐Ÿฅ—","green_salad","",3,0],["๐Ÿฟ","popcorn","",3,0],["๐Ÿงˆ","butter","",3,0],["๐Ÿง‚","salt","",3,0],["๐Ÿฅซ","canned_food","",3,0],["๐Ÿฑ","bento","",3,0],["๐Ÿ˜","rice_cracker","",3,0],["๐Ÿ™","rice_ball","",3,0],["๐Ÿš","rice","",3,0],["๐Ÿ›","curry","",3,0],["๐Ÿœ","ramen","noodle",3,0],["๐Ÿ","spaghetti","pasta",3,0],["๐Ÿ ","sweet_potato","",3,0],["๐Ÿข","oden","",3,0],["๐Ÿฃ","sushi","",3,0],["๐Ÿค","fried_shrimp","tempura",3,0],["๐Ÿฅ","fish_cake","",3,0],["๐Ÿฅฎ","moon_cake","",3,0],["๐Ÿก","dango","",3,0],["๐ŸฅŸ","dumpling","",3,0],["๐Ÿฅ ","fortune_cookie","",3,0],["๐Ÿฅก","takeout_box","",3,0],["๐Ÿฆ€","crab","",3,0],["๐Ÿฆž","lobster","",3,0],["๐Ÿฆ","shrimp","",3,0],["๐Ÿฆ‘","squid","",3,0],["๐Ÿฆช","oyster","",3,0],["๐Ÿฆ","icecream","",3,0],["๐Ÿง","shaved_ice","",3,0],["๐Ÿจ","ice_cream","",3,0],["๐Ÿฉ","doughnut","",3,0],["๐Ÿช","cookie","",3,0],["๐ŸŽ‚","birthday","party",3,0],["๐Ÿฐ","cake","dessert",3,0],["๐Ÿง","cupcake","",3,0],["๐Ÿฅง","pie","",3,0],["๐Ÿซ","chocolate_bar","",3,0],["๐Ÿฌ","candy","sweet",3,0],["๐Ÿญ","lollipop","",3,0],["๐Ÿฎ","custard","",3,0],["๐Ÿฏ","honey_pot","",3,0],["๐Ÿผ","baby_bottle","milk",3,0],["๐Ÿฅ›","milk_glass","",3,0],["โ˜•","coffee","cafe espresso",3,0],["๐Ÿซ–","teapot","",3,0],["๐Ÿต","tea","green breakfast",3,0],["๐Ÿถ","sake","",3,0],["๐Ÿพ","champagne","bottle bubbly celebration",3,0],["๐Ÿท","wine_glass","",3,0],["๐Ÿธ","cocktail","drink",3,0],["๐Ÿน","tropical_drink","summer vacation",3,0],["๐Ÿบ","beer","drink",3,0],["๐Ÿป","beers","drinks",3,0],["๐Ÿฅ‚","clinking_glasses","cheers toast",3,0],["๐Ÿฅƒ","tumbler_glass","whisky",3,0],["๐Ÿซ—","pouring_liquid","",3,0],["๐Ÿฅค","cup_with_straw","",3,0],["๐Ÿง‹","bubble_tea","",3,0],["๐Ÿงƒ","beverage_box","",3,0],["๐Ÿง‰","mate","",3,0],["๐ŸงŠ","ice_cube","",3,0],["๐Ÿฅข","chopsticks","",3,0],["๐Ÿฝ๏ธ","plate_with_cutlery","dining dinner",3,0],["๐Ÿด","fork_and_knife","cutlery",3,0],["๐Ÿฅ„","spoon","",3,0],["๐Ÿ”ช","hocho;knife","cut chop",3,0],["๐Ÿซ™","jar","",3,0],["๐Ÿบ","amphora","",3,0],["๐ŸŒ","earth_africa","globe world international",4,0],["๐ŸŒŽ","earth_americas","globe world international",4,0],["๐ŸŒ","earth_asia","globe world international",4,0],["๐ŸŒ","globe_with_meridians","world global international",4,0],["๐Ÿ—บ๏ธ","world_map","travel",4,0],["๐Ÿ—พ","japan","",4,0],["๐Ÿงญ","compass","",4,0],["๐Ÿ”๏ธ","mountain_snow","",4,0],["โ›ฐ๏ธ","mountain","",4,0],["๐ŸŒ‹","volcano","",4,0],["๐Ÿ—ป","mount_fuji","",4,0],["๐Ÿ•๏ธ","camping","",4,0],["๐Ÿ–๏ธ","beach_umbrella","",4,0],["๐Ÿœ๏ธ","desert","",4,0],["๐Ÿ๏ธ","desert_island","",4,0],["๐Ÿž๏ธ","national_park","",4,0],["๐ŸŸ๏ธ","stadium","",4,0],["๐Ÿ›๏ธ","classical_building","",4,0],["๐Ÿ—๏ธ","building_construction","",4,0],["๐Ÿงฑ","bricks","",4,0],["๐Ÿชจ","rock","",4,0],["๐Ÿชต","wood","",4,0],["๐Ÿ›–","hut","",4,0],["๐Ÿ˜๏ธ","houses","",4,0],["๐Ÿš๏ธ","derelict_house","",4,0],["๐Ÿ ","house","",4,0],["๐Ÿก","house_with_garden","",4,0],["๐Ÿข","office","",4,0],["๐Ÿฃ","post_office","",4,0],["๐Ÿค","european_post_office","",4,0],["๐Ÿฅ","hospital","",4,0],["๐Ÿฆ","bank","",4,0],["๐Ÿจ","hotel","",4,0],["๐Ÿฉ","love_hotel","",4,0],["๐Ÿช","convenience_store","",4,0],["๐Ÿซ","school","",4,0],["๐Ÿฌ","department_store","",4,0],["๐Ÿญ","factory","",4,0],["๐Ÿฏ","japanese_castle","",4,0],["๐Ÿฐ","european_castle","",4,0],["๐Ÿ’’","wedding","marriage",4,0],["๐Ÿ—ผ","tokyo_tower","",4,0],["๐Ÿ—ฝ","statue_of_liberty","",4,0],["โ›ช","church","",4,0],["๐Ÿ•Œ","mosque","",4,0],["๐Ÿ›•","hindu_temple","",4,0],["๐Ÿ•","synagogue","",4,0],["โ›ฉ๏ธ","shinto_shrine","",4,0],["๐Ÿ•‹","kaaba","",4,0],["โ›ฒ","fountain","",4,0],["โ›บ","tent","camping",4,0],["๐ŸŒ","foggy","karl",4,0],["๐ŸŒƒ","night_with_stars","",4,0],["๐Ÿ™๏ธ","cityscape","skyline",4,0],["๐ŸŒ„","sunrise_over_mountains","",4,0],["๐ŸŒ…","sunrise","",4,0],["๐ŸŒ†","city_sunset","",4,0],["๐ŸŒ‡","city_sunrise","",4,0],["๐ŸŒ‰","bridge_at_night","",4,0],["โ™จ๏ธ","hotsprings","",4,0],["๐ŸŽ ","carousel_horse","",4,0],["๐Ÿ›","playground_slide","",4,0],["๐ŸŽก","ferris_wheel","",4,0],["๐ŸŽข","roller_coaster","",4,0],["๐Ÿ’ˆ","barber","",4,0],["๐ŸŽช","circus_tent","",4,0],["๐Ÿš‚","steam_locomotive","train",4,0],["๐Ÿšƒ","railway_car","",4,0],["๐Ÿš„","bullettrain_side","train",4,0],["๐Ÿš…","bullettrain_front","train",4,0],["๐Ÿš†","train2","",4,0],["๐Ÿš‡","metro","",4,0],["๐Ÿšˆ","light_rail","",4,0],["๐Ÿš‰","station","",4,0],["๐ŸšŠ","tram","",4,0],["๐Ÿš","monorail","",4,0],["๐Ÿšž","mountain_railway","",4,0],["๐Ÿš‹","train","",4,0],["๐ŸšŒ","bus","",4,0],["๐Ÿš","oncoming_bus","",4,0],["๐ŸšŽ","trolleybus","",4,0],["๐Ÿš","minibus","",4,0],["๐Ÿš‘","ambulance","",4,0],["๐Ÿš’","fire_engine","",4,0],["๐Ÿš“","police_car","",4,0],["๐Ÿš”","oncoming_police_car","",4,0],["๐Ÿš•","taxi","",4,0],["๐Ÿš–","oncoming_taxi","",4,0],["๐Ÿš—","car;red_car","",4,0],["๐Ÿš˜","oncoming_automobile","",4,0],["๐Ÿš™","blue_car","",4,0],["๐Ÿ›ป","pickup_truck","",4,0],["๐Ÿšš","truck","",4,0],["๐Ÿš›","articulated_lorry","",4,0],["๐Ÿšœ","tractor","",4,0],["๐ŸŽ๏ธ","racing_car","",4,0],["๐Ÿ๏ธ","motorcycle","",4,0],["๐Ÿ›ต","motor_scooter","",4,0],["๐Ÿฆฝ","manual_wheelchair","",4,0],["๐Ÿฆผ","motorized_wheelchair","",4,0],["๐Ÿ›บ","auto_rickshaw","",4,0],["๐Ÿšฒ","bike","bicycle",4,0],["๐Ÿ›ด","kick_scooter","",4,0],["๐Ÿ›น","skateboard","",4,0],["๐Ÿ›ผ","roller_skate","",4,0],["๐Ÿš","busstop","",4,0],["๐Ÿ›ฃ๏ธ","motorway","",4,0],["๐Ÿ›ค๏ธ","railway_track","",4,0],["๐Ÿ›ข๏ธ","oil_drum","",4,0],["โ›ฝ","fuelpump","",4,0],["๐Ÿ›ž","wheel","",4,0],["๐Ÿšจ","rotating_light","911 emergency",4,0],["๐Ÿšฅ","traffic_light","",4,0],["๐Ÿšฆ","vertical_traffic_light","semaphore",4,0],["๐Ÿ›‘","stop_sign","",4,0],["๐Ÿšง","construction","wip",4,0],["โš“","anchor","ship",4,0],["๐Ÿ›Ÿ","ring_buoy","life preserver",4,0],["โ›ต","boat;sailboat","",4,0],["๐Ÿ›ถ","canoe","",4,0],["๐Ÿšค","speedboat","ship",4,0],["๐Ÿ›ณ๏ธ","passenger_ship","cruise",4,0],["โ›ด๏ธ","ferry","",4,0],["๐Ÿ›ฅ๏ธ","motor_boat","",4,0],["๐Ÿšข","ship","",4,0],["โœˆ๏ธ","airplane","flight",4,0],["๐Ÿ›ฉ๏ธ","small_airplane","flight",4,0],["๐Ÿ›ซ","flight_departure","",4,0],["๐Ÿ›ฌ","flight_arrival","",4,0],["๐Ÿช‚","parachute","",4,0],["๐Ÿ’บ","seat","",4,0],["๐Ÿš","helicopter","",4,0],["๐ŸšŸ","suspension_railway","",4,0],["๐Ÿš ","mountain_cableway","",4,0],["๐Ÿšก","aerial_tramway","",4,0],["๐Ÿ›ฐ๏ธ","artificial_satellite","orbit space",4,0],["๐Ÿš€","rocket","ship launch",4,0],["๐Ÿ›ธ","flying_saucer","ufo",4,0],["๐Ÿ›Ž๏ธ","bellhop_bell","",4,0],["๐Ÿงณ","luggage","",4,0],["โŒ›","hourglass","time",4,0],["โณ","hourglass_flowing_sand","time",4,0],["โŒš","watch","time",4,0],["โฐ","alarm_clock","morning",4,0],["โฑ๏ธ","stopwatch","",4,0],["โฒ๏ธ","timer_clock","",4,0],["๐Ÿ•ฐ๏ธ","mantelpiece_clock","",4,0],["๐Ÿ•›","clock12","",4,0],["๐Ÿ•ง","clock1230","",4,0],["๐Ÿ•","clock1","",4,0],["๐Ÿ•œ","clock130","",4,0],["๐Ÿ•‘","clock2","",4,0],["๐Ÿ•","clock230","",4,0],["๐Ÿ•’","clock3","",4,0],["๐Ÿ•ž","clock330","",4,0],["๐Ÿ•“","clock4","",4,0],["๐Ÿ•Ÿ","clock430","",4,0],["๐Ÿ•”","clock5","",4,0],["๐Ÿ• ","clock530","",4,0],["๐Ÿ••","clock6","",4,0],["๐Ÿ•ก","clock630","",4,0],["๐Ÿ•–","clock7","",4,0],["๐Ÿ•ข","clock730","",4,0],["๐Ÿ•—","clock8","",4,0],["๐Ÿ•ฃ","clock830","",4,0],["๐Ÿ•˜","clock9","",4,0],["๐Ÿ•ค","clock930","",4,0],["๐Ÿ•™","clock10","",4,0],["๐Ÿ•ฅ","clock1030","",4,0],["๐Ÿ•š","clock11","",4,0],["๐Ÿ•ฆ","clock1130","",4,0],["๐ŸŒ‘","new_moon","",4,0],["๐ŸŒ’","waxing_crescent_moon","",4,0],["๐ŸŒ“","first_quarter_moon","",4,0],["๐ŸŒ”","moon;waxing_gibbous_moon","",4,0],["๐ŸŒ•","full_moon","",4,0],["๐ŸŒ–","waning_gibbous_moon","",4,0],["๐ŸŒ—","last_quarter_moon","",4,0],["๐ŸŒ˜","waning_crescent_moon","",4,0],["๐ŸŒ™","crescent_moon","night",4,0],["๐ŸŒš","new_moon_with_face","",4,0],["๐ŸŒ›","first_quarter_moon_with_face","",4,0],["๐ŸŒœ","last_quarter_moon_with_face","",4,0],["๐ŸŒก๏ธ","thermometer","",4,0],["โ˜€๏ธ","sunny","weather",4,0],["๐ŸŒ","full_moon_with_face","",4,0],["๐ŸŒž","sun_with_face","summer",4,0],["๐Ÿช","ringed_planet","",4,0],["โญ","star","",4,0],["๐ŸŒŸ","star2","",4,0],["๐ŸŒ ","stars","",4,0],["๐ŸŒŒ","milky_way","",4,0],["โ˜๏ธ","cloud","",4,0],["โ›…","partly_sunny","weather cloud",4,0],["โ›ˆ๏ธ","cloud_with_lightning_and_rain","",4,0],["๐ŸŒค๏ธ","sun_behind_small_cloud","",4,0],["๐ŸŒฅ๏ธ","sun_behind_large_cloud","",4,0],["๐ŸŒฆ๏ธ","sun_behind_rain_cloud","",4,0],["๐ŸŒง๏ธ","cloud_with_rain","",4,0],["๐ŸŒจ๏ธ","cloud_with_snow","",4,0],["๐ŸŒฉ๏ธ","cloud_with_lightning","",4,0],["๐ŸŒช๏ธ","tornado","",4,0],["๐ŸŒซ๏ธ","fog","",4,0],["๐ŸŒฌ๏ธ","wind_face","",4,0],["๐ŸŒ€","cyclone","swirl",4,0],["๐ŸŒˆ","rainbow","",4,0],["๐ŸŒ‚","closed_umbrella","weather rain",4,0],["โ˜‚๏ธ","open_umbrella","",4,0],["โ˜”","umbrella","rain weather",4,0],["โ›ฑ๏ธ","parasol_on_ground","beach_umbrella",4,0],["โšก","zap","lightning thunder",4,0],["โ„๏ธ","snowflake","winter cold weather",4,0],["โ˜ƒ๏ธ","snowman_with_snow","winter christmas",4,0],["โ›„","snowman","winter",4,0],["โ˜„๏ธ","comet","",4,0],["๐Ÿ”ฅ","fire","burn",4,0],["๐Ÿ’ง","droplet","water",4,0],["๐ŸŒŠ","ocean","sea",4,0],["๐ŸŽƒ","jack_o_lantern","halloween",5,0],["๐ŸŽ„","christmas_tree","",5,0],["๐ŸŽ†","fireworks","festival celebration",5,0],["๐ŸŽ‡","sparkler","",5,0],["๐Ÿงจ","firecracker","",5,0],["โœจ","sparkles","shiny",5,0],["๐ŸŽˆ","balloon","party birthday",5,0],["๐ŸŽ‰","tada","hooray party",5,0],["๐ŸŽŠ","confetti_ball","",5,0],["๐ŸŽ‹","tanabata_tree","",5,0],["๐ŸŽ","bamboo","",5,0],["๐ŸŽŽ","dolls","",5,0],["๐ŸŽ","flags","",5,0],["๐ŸŽ","wind_chime","",5,0],["๐ŸŽ‘","rice_scene","",5,0],["๐Ÿงง","red_envelope","",5,0],["๐ŸŽ€","ribbon","",5,0],["๐ŸŽ","gift","present birthday christmas",5,0],["๐ŸŽ—๏ธ","reminder_ribbon","",5,0],["๐ŸŽŸ๏ธ","tickets","",5,0],["๐ŸŽซ","ticket","",5,0],["๐ŸŽ–๏ธ","medal_military","",5,0],["๐Ÿ†","trophy","award contest winner",5,0],["๐Ÿ…","medal_sports","gold winner",5,0],["๐Ÿฅ‡","1st_place_medal","gold",5,0],["๐Ÿฅˆ","2nd_place_medal","silver",5,0],["๐Ÿฅ‰","3rd_place_medal","bronze",5,0],["โšฝ","soccer","sports",5,0],["โšพ","baseball","sports",5,0],["๐ŸฅŽ","softball","",5,0],["๐Ÿ€","basketball","sports",5,0],["๐Ÿ","volleyball","",5,0],["๐Ÿˆ","football","sports",5,0],["๐Ÿ‰","rugby_football","",5,0],["๐ŸŽพ","tennis","sports",5,0],["๐Ÿฅ","flying_disc","",5,0],["๐ŸŽณ","bowling","",5,0],["๐Ÿ","cricket_game","",5,0],["๐Ÿ‘","field_hockey","",5,0],["๐Ÿ’","ice_hockey","",5,0],["๐Ÿฅ","lacrosse","",5,0],["๐Ÿ“","ping_pong","",5,0],["๐Ÿธ","badminton","",5,0],["๐ŸฅŠ","boxing_glove","",5,0],["๐Ÿฅ‹","martial_arts_uniform","",5,0],["๐Ÿฅ…","goal_net","",5,0],["โ›ณ","golf","",5,0],["โ›ธ๏ธ","ice_skate","skating",5,0],["๐ŸŽฃ","fishing_pole_and_fish","",5,0],["๐Ÿคฟ","diving_mask","",5,0],["๐ŸŽฝ","running_shirt_with_sash","marathon",5,0],["๐ŸŽฟ","ski","",5,0],["๐Ÿ›ท","sled","",5,0],["๐ŸฅŒ","curling_stone","",5,0],["๐ŸŽฏ","dart","target",5,0],["๐Ÿช€","yo_yo","",5,0],["๐Ÿช","kite","",5,0],["๐ŸŽฑ","8ball","pool billiards",5,0],["๐Ÿ”ฎ","crystal_ball","fortune",5,0],["๐Ÿช„","magic_wand","",5,0],["๐Ÿงฟ","nazar_amulet","",5,0],["๐Ÿชฌ","hamsa","",5,0],["๐ŸŽฎ","video_game","play controller console",5,0],["๐Ÿ•น๏ธ","joystick","",5,0],["๐ŸŽฐ","slot_machine","",5,0],["๐ŸŽฒ","game_die","dice gambling",5,0],["๐Ÿงฉ","jigsaw","",5,0],["๐Ÿงธ","teddy_bear","",5,0],["๐Ÿช…","pinata","",5,0],["๐Ÿชฉ","mirror_ball","disco party",5,0],["๐Ÿช†","nesting_dolls","",5,0],["โ™ ๏ธ","spades","",5,0],["โ™ฅ๏ธ","hearts","",5,0],["โ™ฆ๏ธ","diamonds","",5,0],["โ™ฃ๏ธ","clubs","",5,0],["โ™Ÿ๏ธ","chess_pawn","",5,0],["๐Ÿƒ","black_joker","",5,0],["๐Ÿ€„","mahjong","",5,0],["๐ŸŽด","flower_playing_cards","",5,0],["๐ŸŽญ","performing_arts","theater drama",5,0],["๐Ÿ–ผ๏ธ","framed_picture","",5,0],["๐ŸŽจ","art","design paint",5,0],["๐Ÿงต","thread","",5,0],["๐Ÿชก","sewing_needle","",5,0],["๐Ÿงถ","yarn","",5,0],["๐Ÿชข","knot","",5,0],["๐Ÿ‘“","eyeglasses","glasses",6,0],["๐Ÿ•ถ๏ธ","dark_sunglasses","",6,0],["๐Ÿฅฝ","goggles","",6,0],["๐Ÿฅผ","lab_coat","",6,0],["๐Ÿฆบ","safety_vest","",6,0],["๐Ÿ‘”","necktie","shirt formal",6,0],["๐Ÿ‘•","shirt;tshirt","",6,0],["๐Ÿ‘–","jeans","pants",6,0],["๐Ÿงฃ","scarf","",6,0],["๐Ÿงค","gloves","",6,0],["๐Ÿงฅ","coat","",6,0],["๐Ÿงฆ","socks","",6,0],["๐Ÿ‘—","dress","",6,0],["๐Ÿ‘˜","kimono","",6,0],["๐Ÿฅป","sari","",6,0],["๐Ÿฉฑ","one_piece_swimsuit","",6,0],["๐Ÿฉฒ","swim_brief","",6,0],["๐Ÿฉณ","shorts","",6,0],["๐Ÿ‘™","bikini","beach",6,0],["๐Ÿ‘š","womans_clothes","",6,0],["๐Ÿ‘›","purse","",6,0],["๐Ÿ‘œ","handbag","bag",6,0],["๐Ÿ‘","pouch","bag",6,0],["๐Ÿ›๏ธ","shopping","bags",6,0],["๐ŸŽ’","school_satchel","",6,0],["๐Ÿฉด","thong_sandal","",6,0],["๐Ÿ‘ž","mans_shoe;shoe","",6,0],["๐Ÿ‘Ÿ","athletic_shoe","sneaker sport running",6,0],["๐Ÿฅพ","hiking_boot","",6,0],["๐Ÿฅฟ","flat_shoe","",6,0],["๐Ÿ‘ ","high_heel","shoe",6,0],["๐Ÿ‘ก","sandal","shoe",6,0],["๐Ÿฉฐ","ballet_shoes","",6,0],["๐Ÿ‘ข","boot","",6,0],["๐Ÿ‘‘","crown","king queen royal",6,0],["๐Ÿ‘’","womans_hat","",6,0],["๐ŸŽฉ","tophat","hat classy",6,0],["๐ŸŽ“","mortar_board","education college university graduation",6,0],["๐Ÿงข","billed_cap","",6,0],["๐Ÿช–","military_helmet","",6,0],["โ›‘๏ธ","rescue_worker_helmet","",6,0],["๐Ÿ“ฟ","prayer_beads","",6,0],["๐Ÿ’„","lipstick","makeup",6,0],["๐Ÿ’","ring","wedding marriage engaged",6,0],["๐Ÿ’Ž","gem","diamond",6,0],["๐Ÿ”‡","mute","sound volume",6,0],["๐Ÿ”ˆ","speaker","",6,0],["๐Ÿ”‰","sound","volume",6,0],["๐Ÿ”Š","loud_sound","volume",6,0],["๐Ÿ“ข","loudspeaker","announcement",6,0],["๐Ÿ“ฃ","mega","",6,0],["๐Ÿ“ฏ","postal_horn","",6,0],["๐Ÿ””","bell","sound notification",6,0],["๐Ÿ”•","no_bell","volume off",6,0],["๐ŸŽผ","musical_score","",6,0],["๐ŸŽต","musical_note","",6,0],["๐ŸŽถ","notes","music",6,0],["๐ŸŽ™๏ธ","studio_microphone","podcast",6,0],["๐ŸŽš๏ธ","level_slider","",6,0],["๐ŸŽ›๏ธ","control_knobs","",6,0],["๐ŸŽค","microphone","sing",6,0],["๐ŸŽง","headphones","music earphones",6,0],["๐Ÿ“ป","radio","podcast",6,0],["๐ŸŽท","saxophone","",6,0],["๐Ÿช—","accordion","",6,0],["๐ŸŽธ","guitar","rock",6,0],["๐ŸŽน","musical_keyboard","piano",6,0],["๐ŸŽบ","trumpet","",6,0],["๐ŸŽป","violin","",6,0],["๐Ÿช•","banjo","",6,0],["๐Ÿฅ","drum","",6,0],["๐Ÿช˜","long_drum","",6,0],["๐Ÿ“ฑ","iphone","smartphone mobile",6,0],["๐Ÿ“ฒ","calling","call incoming",6,0],["โ˜Ž๏ธ","phone;telephone","",6,0],["๐Ÿ“ž","telephone_receiver","phone call",6,0],["๐Ÿ“Ÿ","pager","",6,0],["๐Ÿ“ ","fax","",6,0],["๐Ÿ”‹","battery","power",6,0],["๐Ÿชซ","low_battery","",6,0],["๐Ÿ”Œ","electric_plug","",6,0],["๐Ÿ’ป","computer","desktop screen",6,0],["๐Ÿ–ฅ๏ธ","desktop_computer","",6,0],["๐Ÿ–จ๏ธ","printer","",6,0],["โŒจ๏ธ","keyboard","",6,0],["๐Ÿ–ฑ๏ธ","computer_mouse","",6,0],["๐Ÿ–ฒ๏ธ","trackball","",6,0],["๐Ÿ’ฝ","minidisc","",6,0],["๐Ÿ’พ","floppy_disk","save",6,0],["๐Ÿ’ฟ","cd","",6,0],["๐Ÿ“€","dvd","",6,0],["๐Ÿงฎ","abacus","",6,0],["๐ŸŽฅ","movie_camera","film video",6,0],["๐ŸŽž๏ธ","film_strip","",6,0],["๐Ÿ“ฝ๏ธ","film_projector","",6,0],["๐ŸŽฌ","clapper","film",6,0],["๐Ÿ“บ","tv","",6,0],["๐Ÿ“ท","camera","photo",6,0],["๐Ÿ“ธ","camera_flash","photo",6,0],["๐Ÿ“น","video_camera","",6,0],["๐Ÿ“ผ","vhs","",6,0],["๐Ÿ”","mag","search zoom",6,0],["๐Ÿ”Ž","mag_right","",6,0],["๐Ÿ•ฏ๏ธ","candle","",6,0],["๐Ÿ’ก","bulb","idea light",6,0],["๐Ÿ”ฆ","flashlight","",6,0],["๐Ÿฎ","izakaya_lantern;lantern","",6,0],["๐Ÿช”","diya_lamp","",6,0],["๐Ÿ“”","notebook_with_decorative_cover","",6,0],["๐Ÿ“•","closed_book","",6,0],["๐Ÿ“–","book;open_book","",6,0],["๐Ÿ“—","green_book","",6,0],["๐Ÿ“˜","blue_book","",6,0],["๐Ÿ“™","orange_book","",6,0],["๐Ÿ“š","books","library",6,0],["๐Ÿ““","notebook","",6,0],["๐Ÿ“’","ledger","",6,0],["๐Ÿ“ƒ","page_with_curl","",6,0],["๐Ÿ“œ","scroll","document",6,0],["๐Ÿ“„","page_facing_up","document",6,0],["๐Ÿ“ฐ","newspaper","press",6,0],["๐Ÿ—ž๏ธ","newspaper_roll","press",6,0],["๐Ÿ“‘","bookmark_tabs","",6,0],["๐Ÿ”–","bookmark","",6,0],["๐Ÿท๏ธ","label","tag",6,0],["๐Ÿ’ฐ","moneybag","dollar cream",6,0],["๐Ÿช™","coin","",6,0],["๐Ÿ’ด","yen","",6,0],["๐Ÿ’ต","dollar","money",6,0],["๐Ÿ’ถ","euro","",6,0],["๐Ÿ’ท","pound","",6,0],["๐Ÿ’ธ","money_with_wings","dollar",6,0],["๐Ÿ’ณ","credit_card","subscription",6,0],["๐Ÿงพ","receipt","",6,0],["๐Ÿ’น","chart","",6,0],["โœ‰๏ธ","envelope","letter email",6,0],["๐Ÿ“ง","email;e-mail","",6,0],["๐Ÿ“จ","incoming_envelope","",6,0],["๐Ÿ“ฉ","envelope_with_arrow","",6,0],["๐Ÿ“ค","outbox_tray","",6,0],["๐Ÿ“ฅ","inbox_tray","",6,0],["๐Ÿ“ฆ","package","shipping",6,0],["๐Ÿ“ซ","mailbox","",6,0],["๐Ÿ“ช","mailbox_closed","",6,0],["๐Ÿ“ฌ","mailbox_with_mail","",6,0],["๐Ÿ“ญ","mailbox_with_no_mail","",6,0],["๐Ÿ“ฎ","postbox","",6,0],["๐Ÿ—ณ๏ธ","ballot_box","",6,0],["โœ๏ธ","pencil2","",6,0],["โœ’๏ธ","black_nib","",6,0],["๐Ÿ–‹๏ธ","fountain_pen","",6,0],["๐Ÿ–Š๏ธ","pen","",6,0],["๐Ÿ–Œ๏ธ","paintbrush","",6,0],["๐Ÿ–๏ธ","crayon","",6,0],["๐Ÿ“","memo;pencil","document note",6,0],["๐Ÿ’ผ","briefcase","business",6,0],["๐Ÿ“","file_folder","directory",6,0],["๐Ÿ“‚","open_file_folder","",6,0],["๐Ÿ—‚๏ธ","card_index_dividers","",6,0],["๐Ÿ“…","date","calendar schedule",6,0],["๐Ÿ“†","calendar","schedule",6,0],["๐Ÿ—’๏ธ","spiral_notepad","",6,0],["๐Ÿ—“๏ธ","spiral_calendar","",6,0],["๐Ÿ“‡","card_index","",6,0],["๐Ÿ“ˆ","chart_with_upwards_trend","graph metrics",6,0],["๐Ÿ“‰","chart_with_downwards_trend","graph metrics",6,0],["๐Ÿ“Š","bar_chart","stats metrics",6,0],["๐Ÿ“‹","clipboard","",6,0],["๐Ÿ“Œ","pushpin","location",6,0],["๐Ÿ“","round_pushpin","location",6,0],["๐Ÿ“Ž","paperclip","",6,0],["๐Ÿ–‡๏ธ","paperclips","",6,0],["๐Ÿ“","straight_ruler","",6,0],["๐Ÿ“","triangular_ruler","",6,0],["โœ‚๏ธ","scissors","cut",6,0],["๐Ÿ—ƒ๏ธ","card_file_box","",6,0],["๐Ÿ—„๏ธ","file_cabinet","",6,0],["๐Ÿ—‘๏ธ","wastebasket","trash",6,0],["๐Ÿ”’","lock","security private",6,0],["๐Ÿ”“","unlock","security",6,0],["๐Ÿ”","lock_with_ink_pen","",6,0],["๐Ÿ”","closed_lock_with_key","security",6,0],["๐Ÿ”‘","key","lock password",6,0],["๐Ÿ—๏ธ","old_key","",6,0],["๐Ÿ”จ","hammer","tool",6,0],["๐Ÿช“","axe","",6,0],["โ›๏ธ","pick","",6,0],["โš’๏ธ","hammer_and_pick","",6,0],["๐Ÿ› ๏ธ","hammer_and_wrench","",6,0],["๐Ÿ—ก๏ธ","dagger","",6,0],["โš”๏ธ","crossed_swords","",6,0],["๐Ÿ”ซ","gun","shoot weapon",6,0],["๐Ÿชƒ","boomerang","",6,0],["๐Ÿน","bow_and_arrow","archery",6,0],["๐Ÿ›ก๏ธ","shield","",6,0],["๐Ÿชš","carpentry_saw","",6,0],["๐Ÿ”ง","wrench","tool",6,0],["๐Ÿช›","screwdriver","",6,0],["๐Ÿ”ฉ","nut_and_bolt","",6,0],["โš™๏ธ","gear","",6,0],["๐Ÿ—œ๏ธ","clamp","",6,0],["โš–๏ธ","balance_scale","",6,0],["๐Ÿฆฏ","probing_cane","",6,0],["๐Ÿ”—","link","",6,0],["โ›“๏ธ","chains","",6,0],["๐Ÿช","hook","",6,0],["๐Ÿงฐ","toolbox","",6,0],["๐Ÿงฒ","magnet","",6,0],["๐Ÿชœ","ladder","",6,0],["โš—๏ธ","alembic","",6,0],["๐Ÿงช","test_tube","",6,0],["๐Ÿงซ","petri_dish","",6,0],["๐Ÿงฌ","dna","",6,0],["๐Ÿ”ฌ","microscope","science laboratory investigate",6,0],["๐Ÿ”ญ","telescope","",6,0],["๐Ÿ“ก","satellite","signal",6,0],["๐Ÿ’‰","syringe","health hospital needle",6,0],["๐Ÿฉธ","drop_of_blood","",6,0],["๐Ÿ’Š","pill","health medicine",6,0],["๐Ÿฉน","adhesive_bandage","",6,0],["๐Ÿฉผ","crutch","",6,0],["๐Ÿฉบ","stethoscope","",6,0],["๐Ÿฉป","x_ray","",6,0],["๐Ÿšช","door","",6,0],["๐Ÿ›—","elevator","",6,0],["๐Ÿชž","mirror","",6,0],["๐ŸชŸ","window","",6,0],["๐Ÿ›๏ธ","bed","",6,0],["๐Ÿ›‹๏ธ","couch_and_lamp","",6,0],["๐Ÿช‘","chair","",6,0],["๐Ÿšฝ","toilet","wc",6,0],["๐Ÿช ","plunger","",6,0],["๐Ÿšฟ","shower","bath",6,0],["๐Ÿ›","bathtub","",6,0],["๐Ÿชค","mouse_trap","",6,0],["๐Ÿช’","razor","",6,0],["๐Ÿงด","lotion_bottle","",6,0],["๐Ÿงท","safety_pin","",6,0],["๐Ÿงน","broom","",6,0],["๐Ÿงบ","basket","",6,0],["๐Ÿงป","roll_of_paper","toilet",6,0],["๐Ÿชฃ","bucket","",6,0],["๐Ÿงผ","soap","",6,0],["๐Ÿซง","bubbles","",6,0],["๐Ÿชฅ","toothbrush","",6,0],["๐Ÿงฝ","sponge","",6,0],["๐Ÿงฏ","fire_extinguisher","",6,0],["๐Ÿ›’","shopping_cart","",6,0],["๐Ÿšฌ","smoking","cigarette",6,0],["โšฐ๏ธ","coffin","funeral",6,0],["๐Ÿชฆ","headstone","",6,0],["โšฑ๏ธ","funeral_urn","",6,0],["๐Ÿ—ฟ","moyai","stone",6,0],["๐Ÿชง","placard","",6,0],["๐Ÿชช","identification_card","",6,0],["๐Ÿง","atm","",7,0],["๐Ÿšฎ","put_litter_in_its_place","",7,0],["๐Ÿšฐ","potable_water","",7,0],["โ™ฟ","wheelchair","accessibility",7,0],["๐Ÿšน","mens","",7,0],["๐Ÿšบ","womens","",7,0],["๐Ÿšป","restroom","toilet",7,0],["๐Ÿšผ","baby_symbol","",7,0],["๐Ÿšพ","wc","toilet restroom",7,0],["๐Ÿ›‚","passport_control","",7,0],["๐Ÿ›ƒ","customs","",7,0],["๐Ÿ›„","baggage_claim","airport",7,0],["๐Ÿ›…","left_luggage","",7,0],["โš ๏ธ","warning","wip",7,0],["๐Ÿšธ","children_crossing","",7,0],["โ›”","no_entry","limit",7,0],["๐Ÿšซ","no_entry_sign","block forbidden",7,0],["๐Ÿšณ","no_bicycles","",7,0],["๐Ÿšญ","no_smoking","",7,0],["๐Ÿšฏ","do_not_litter","",7,0],["๐Ÿšฑ","non-potable_water","",7,0],["๐Ÿšท","no_pedestrians","",7,0],["๐Ÿ“ต","no_mobile_phones","",7,0],["๐Ÿ”ž","underage","",7,0],["โ˜ข๏ธ","radioactive","",7,0],["โ˜ฃ๏ธ","biohazard","",7,0],["โฌ†๏ธ","arrow_up","",7,0],["โ†—๏ธ","arrow_upper_right","",7,0],["โžก๏ธ","arrow_right","",7,0],["โ†˜๏ธ","arrow_lower_right","",7,0],["โฌ‡๏ธ","arrow_down","",7,0],["โ†™๏ธ","arrow_lower_left","",7,0],["โฌ…๏ธ","arrow_left","",7,0],["โ†–๏ธ","arrow_upper_left","",7,0],["โ†•๏ธ","arrow_up_down","",7,0],["โ†”๏ธ","left_right_arrow","",7,0],["โ†ฉ๏ธ","leftwards_arrow_with_hook","return",7,0],["โ†ช๏ธ","arrow_right_hook","",7,0],["โคด๏ธ","arrow_heading_up","",7,0],["โคต๏ธ","arrow_heading_down","",7,0],["๐Ÿ”ƒ","arrows_clockwise","",7,0],["๐Ÿ”„","arrows_counterclockwise","sync",7,0],["๐Ÿ”™","back","",7,0],["๐Ÿ”š","end","",7,0],["๐Ÿ”›","on","",7,0],["๐Ÿ”œ","soon","",7,0],["๐Ÿ”","top","",7,0],["๐Ÿ›","place_of_worship","",7,0],["โš›๏ธ","atom_symbol","",7,0],["๐Ÿ•‰๏ธ","om","",7,0],["โœก๏ธ","star_of_david","",7,0],["โ˜ธ๏ธ","wheel_of_dharma","",7,0],["โ˜ฏ๏ธ","yin_yang","",7,0],["โœ๏ธ","latin_cross","",7,0],["โ˜ฆ๏ธ","orthodox_cross","",7,0],["โ˜ช๏ธ","star_and_crescent","",7,0],["โ˜ฎ๏ธ","peace_symbol","",7,0],["๐Ÿ•Ž","menorah","",7,0],["๐Ÿ”ฏ","six_pointed_star","",7,0],["โ™ˆ","aries","",7,0],["โ™‰","taurus","",7,0],["โ™Š","gemini","",7,0],["โ™‹","cancer","",7,0],["โ™Œ","leo","",7,0],["โ™","virgo","",7,0],["โ™Ž","libra","",7,0],["โ™","scorpius","",7,0],["โ™","sagittarius","",7,0],["โ™‘","capricorn","",7,0],["โ™’","aquarius","",7,0],["โ™“","pisces","",7,0],["โ›Ž","ophiuchus","",7,0],["๐Ÿ”€","twisted_rightwards_arrows","shuffle",7,0],["๐Ÿ”","repeat","loop",7,0],["๐Ÿ”‚","repeat_one","",7,0],["โ–ถ๏ธ","arrow_forward","",7,0],["โฉ","fast_forward","",7,0],["โญ๏ธ","next_track_button","",7,0],["โฏ๏ธ","play_or_pause_button","",7,0],["โ—€๏ธ","arrow_backward","",7,0],["โช","rewind","",7,0],["โฎ๏ธ","previous_track_button","",7,0],["๐Ÿ”ผ","arrow_up_small","",7,0],["โซ","arrow_double_up","",7,0],["๐Ÿ”ฝ","arrow_down_small","",7,0],["โฌ","arrow_double_down","",7,0],["โธ๏ธ","pause_button","",7,0],["โน๏ธ","stop_button","",7,0],["โบ๏ธ","record_button","",7,0],["โ๏ธ","eject_button","",7,0],["๐ŸŽฆ","cinema","film movie",7,0],["๐Ÿ”…","low_brightness","",7,0],["๐Ÿ”†","high_brightness","",7,0],["๐Ÿ“ถ","signal_strength","wifi",7,0],["๐Ÿ“ณ","vibration_mode","",7,0],["๐Ÿ“ด","mobile_phone_off","mute off",7,0],["โ™€๏ธ","female_sign","",7,0],["โ™‚๏ธ","male_sign","",7,0],["โšง๏ธ","transgender_symbol","",7,0],["โœ–๏ธ","heavy_multiplication_x","",7,0],["โž•","heavy_plus_sign","",7,0],["โž–","heavy_minus_sign","",7,0],["โž—","heavy_division_sign","",7,0],["๐ŸŸฐ","heavy_equals_sign","",7,0],["โ™พ๏ธ","infinity","",7,0],["โ€ผ๏ธ","bangbang","",7,0],["โ‰๏ธ","interrobang","",7,0],["โ“","question","confused",7,0],["โ”","grey_question","",7,0],["โ•","grey_exclamation","",7,0],["โ—","exclamation;heavy_exclamation_mark","bang",7,0],["ใ€ฐ๏ธ","wavy_dash","",7,0],["๐Ÿ’ฑ","currency_exchange","",7,0],["๐Ÿ’ฒ","heavy_dollar_sign","",7,0],["โš•๏ธ","medical_symbol","",7,0],["โ™ป๏ธ","recycle","environment green",7,0],["โšœ๏ธ","fleur_de_lis","",7,0],["๐Ÿ”ฑ","trident","",7,0],["๐Ÿ“›","name_badge","",7,0],["๐Ÿ”ฐ","beginner","",7,0],["โญ•","o","",7,0],["โœ…","white_check_mark","",7,0],["โ˜‘๏ธ","ballot_box_with_check","",7,0],["โœ”๏ธ","heavy_check_mark","",7,0],["โŒ","x","",7,0],["โŽ","negative_squared_cross_mark","",7,0],["โžฐ","curly_loop","",7,0],["โžฟ","loop","",7,0],["ใ€ฝ๏ธ","part_alternation_mark","",7,0],["โœณ๏ธ","eight_spoked_asterisk","",7,0],["โœด๏ธ","eight_pointed_black_star","",7,0],["โ‡๏ธ","sparkle","",7,0],["ยฉ๏ธ","copyright","",7,0],["ยฎ๏ธ","registered","",7,0],["โ„ข๏ธ","tm","trademark",7,0],["#๏ธโƒฃ","hash","number",7,0],["*๏ธโƒฃ","asterisk","",7,0],["0๏ธโƒฃ","zero","",7,0],["1๏ธโƒฃ","one","",7,0],["2๏ธโƒฃ","two","",7,0],["3๏ธโƒฃ","three","",7,0],["4๏ธโƒฃ","four","",7,0],["5๏ธโƒฃ","five","",7,0],["6๏ธโƒฃ","six","",7,0],["7๏ธโƒฃ","seven","",7,0],["8๏ธโƒฃ","eight","",7,0],["9๏ธโƒฃ","nine","",7,0],["๐Ÿ”Ÿ","keycap_ten","",7,0],["๐Ÿ” ","capital_abcd","letters",7,0],["๐Ÿ”ก","abcd","",7,0],["๐Ÿ”ข","1234","numbers",7,0],["๐Ÿ”ฃ","symbols","",7,0],["๐Ÿ”ค","abc","alphabet",7,0],["๐Ÿ…ฐ๏ธ","a","",7,0],["๐Ÿ†Ž","ab","",7,0],["๐Ÿ…ฑ๏ธ","b","",7,0],["๐Ÿ†‘","cl","",7,0],["๐Ÿ†’","cool","",7,0],["๐Ÿ†“","free","",7,0],["โ„น๏ธ","information_source","",7,0],["๐Ÿ†”","id","",7,0],["โ“‚๏ธ","m","",7,0],["๐Ÿ†•","new","fresh",7,0],["๐Ÿ†–","ng","",7,0],["๐Ÿ…พ๏ธ","o2","",7,0],["๐Ÿ†—","ok","yes",7,0],["๐Ÿ…ฟ๏ธ","parking","",7,0],["๐Ÿ†˜","sos","help emergency",7,0],["๐Ÿ†™","up","",7,0],["๐Ÿ†š","vs","",7,0],["๐Ÿˆ","koko","",7,0],["๐Ÿˆ‚๏ธ","sa","",7,0],["๐Ÿˆท๏ธ","u6708","",7,0],["๐Ÿˆถ","u6709","",7,0],["๐Ÿˆฏ","u6307","",7,0],["๐Ÿ‰","ideograph_advantage","",7,0],["๐Ÿˆน","u5272","",7,0],["๐Ÿˆš","u7121","",7,0],["๐Ÿˆฒ","u7981","",7,0],["๐Ÿ‰‘","accept","",7,0],["๐Ÿˆธ","u7533","",7,0],["๐Ÿˆด","u5408","",7,0],["๐Ÿˆณ","u7a7a","",7,0],["ใŠ—๏ธ","congratulations","",7,0],["ใŠ™๏ธ","secret","",7,0],["๐Ÿˆบ","u55b6","",7,0],["๐Ÿˆต","u6e80","",7,0],["๐Ÿ”ด","red_circle","",7,0],["๐ŸŸ ","orange_circle","",7,0],["๐ŸŸก","yellow_circle","",7,0],["๐ŸŸข","green_circle","",7,0],["๐Ÿ”ต","large_blue_circle","",7,0],["๐ŸŸฃ","purple_circle","",7,0],["๐ŸŸค","brown_circle","",7,0],["โšซ","black_circle","",7,0],["โšช","white_circle","",7,0],["๐ŸŸฅ","red_square","",7,0],["๐ŸŸง","orange_square","",7,0],["๐ŸŸจ","yellow_square","",7,0],["๐ŸŸฉ","green_square","",7,0],["๐ŸŸฆ","blue_square","",7,0],["๐ŸŸช","purple_square","",7,0],["๐ŸŸซ","brown_square","",7,0],["โฌ›","black_large_square","",7,0],["โฌœ","white_large_square","",7,0],["โ—ผ๏ธ","black_medium_square","",7,0],["โ—ป๏ธ","white_medium_square","",7,0],["โ—พ","black_medium_small_square","",7,0],["โ—ฝ","white_medium_small_square","",7,0],["โ–ช๏ธ","black_small_square","",7,0],["โ–ซ๏ธ","white_small_square","",7,0],["๐Ÿ”ถ","large_orange_diamond","",7,0],["๐Ÿ”ท","large_blue_diamond","",7,0],["๐Ÿ”ธ","small_orange_diamond","",7,0],["๐Ÿ”น","small_blue_diamond","",7,0],["๐Ÿ”บ","small_red_triangle","",7,0],["๐Ÿ”ป","small_red_triangle_down","",7,0],["๐Ÿ’ ","diamond_shape_with_a_dot_inside","",7,0],["๐Ÿ”˜","radio_button","",7,0],["๐Ÿ”ณ","white_square_button","",7,0],["๐Ÿ”ฒ","black_square_button","",7,0],["๐Ÿ","checkered_flag","milestone finish",8,0],["๐Ÿšฉ","triangular_flag_on_post","",8,0],["๐ŸŽŒ","crossed_flags","",8,0],["๐Ÿด","black_flag","",8,0],["๐Ÿณ๏ธ","white_flag","",8,0],["๐Ÿณ๏ธโ€๐ŸŒˆ","rainbow_flag","pride",8,0],["๐Ÿณ๏ธโ€โšง๏ธ","transgender_flag","",8,0],["๐Ÿดโ€โ˜ ๏ธ","pirate_flag","",8,0],["๐Ÿ‡ฆ๐Ÿ‡จ","ascension_island","",8,0],["๐Ÿ‡ฆ๐Ÿ‡ฉ","andorra","",8,0],["๐Ÿ‡ฆ๐Ÿ‡ช","united_arab_emirates","",8,0],["๐Ÿ‡ฆ๐Ÿ‡ซ","afghanistan","",8,0],["๐Ÿ‡ฆ๐Ÿ‡ฌ","antigua_barbuda","",8,0],["๐Ÿ‡ฆ๐Ÿ‡ฎ","anguilla","",8,0],["๐Ÿ‡ฆ๐Ÿ‡ฑ","albania","",8,0],["๐Ÿ‡ฆ๐Ÿ‡ฒ","armenia","",8,0],["๐Ÿ‡ฆ๐Ÿ‡ด","angola","",8,0],["๐Ÿ‡ฆ๐Ÿ‡ถ","antarctica","",8,0],["๐Ÿ‡ฆ๐Ÿ‡ท","argentina","",8,0],["๐Ÿ‡ฆ๐Ÿ‡ธ","american_samoa","",8,0],["๐Ÿ‡ฆ๐Ÿ‡น","austria","",8,0],["๐Ÿ‡ฆ๐Ÿ‡บ","australia","",8,0],["๐Ÿ‡ฆ๐Ÿ‡ผ","aruba","",8,0],["๐Ÿ‡ฆ๐Ÿ‡ฝ","aland_islands","",8,0],["๐Ÿ‡ฆ๐Ÿ‡ฟ","azerbaijan","",8,0],["๐Ÿ‡ง๐Ÿ‡ฆ","bosnia_herzegovina","",8,0],["๐Ÿ‡ง๐Ÿ‡ง","barbados","",8,0],["๐Ÿ‡ง๐Ÿ‡ฉ","bangladesh","",8,0],["๐Ÿ‡ง๐Ÿ‡ช","belgium","",8,0],["๐Ÿ‡ง๐Ÿ‡ซ","burkina_faso","",8,0],["๐Ÿ‡ง๐Ÿ‡ฌ","bulgaria","",8,0],["๐Ÿ‡ง๐Ÿ‡ญ","bahrain","",8,0],["๐Ÿ‡ง๐Ÿ‡ฎ","burundi","",8,0],["๐Ÿ‡ง๐Ÿ‡ฏ","benin","",8,0],["๐Ÿ‡ง๐Ÿ‡ฑ","st_barthelemy","",8,0],["๐Ÿ‡ง๐Ÿ‡ฒ","bermuda","",8,0],["๐Ÿ‡ง๐Ÿ‡ณ","brunei","",8,0],["๐Ÿ‡ง๐Ÿ‡ด","bolivia","",8,0],["๐Ÿ‡ง๐Ÿ‡ถ","caribbean_netherlands","",8,0],["๐Ÿ‡ง๐Ÿ‡ท","brazil","",8,0],["๐Ÿ‡ง๐Ÿ‡ธ","bahamas","",8,0],["๐Ÿ‡ง๐Ÿ‡น","bhutan","",8,0],["๐Ÿ‡ง๐Ÿ‡ป","bouvet_island","",8,0],["๐Ÿ‡ง๐Ÿ‡ผ","botswana","",8,0],["๐Ÿ‡ง๐Ÿ‡พ","belarus","",8,0],["๐Ÿ‡ง๐Ÿ‡ฟ","belize","",8,0],["๐Ÿ‡จ๐Ÿ‡ฆ","canada","",8,0],["๐Ÿ‡จ๐Ÿ‡จ","cocos_islands","keeling",8,0],["๐Ÿ‡จ๐Ÿ‡ฉ","congo_kinshasa","",8,0],["๐Ÿ‡จ๐Ÿ‡ซ","central_african_republic","",8,0],["๐Ÿ‡จ๐Ÿ‡ฌ","congo_brazzaville","",8,0],["๐Ÿ‡จ๐Ÿ‡ญ","switzerland","",8,0],["๐Ÿ‡จ๐Ÿ‡ฎ","cote_divoire","ivory",8,0],["๐Ÿ‡จ๐Ÿ‡ฐ","cook_islands","",8,0],["๐Ÿ‡จ๐Ÿ‡ฑ","chile","",8,0],["๐Ÿ‡จ๐Ÿ‡ฒ","cameroon","",8,0],["๐Ÿ‡จ๐Ÿ‡ณ","cn","china",8,0],["๐Ÿ‡จ๐Ÿ‡ด","colombia","",8,0],["๐Ÿ‡จ๐Ÿ‡ต","clipperton_island","",8,0],["๐Ÿ‡จ๐Ÿ‡ท","costa_rica","",8,0],["๐Ÿ‡จ๐Ÿ‡บ","cuba","",8,0],["๐Ÿ‡จ๐Ÿ‡ป","cape_verde","",8,0],["๐Ÿ‡จ๐Ÿ‡ผ","curacao","",8,0],["๐Ÿ‡จ๐Ÿ‡ฝ","christmas_island","",8,0],["๐Ÿ‡จ๐Ÿ‡พ","cyprus","",8,0],["๐Ÿ‡จ๐Ÿ‡ฟ","czech_republic","",8,0],["๐Ÿ‡ฉ๐Ÿ‡ช","de","flag germany",8,0],["๐Ÿ‡ฉ๐Ÿ‡ฌ","diego_garcia","",8,0],["๐Ÿ‡ฉ๐Ÿ‡ฏ","djibouti","",8,0],["๐Ÿ‡ฉ๐Ÿ‡ฐ","denmark","",8,0],["๐Ÿ‡ฉ๐Ÿ‡ฒ","dominica","",8,0],["๐Ÿ‡ฉ๐Ÿ‡ด","dominican_republic","",8,0],["๐Ÿ‡ฉ๐Ÿ‡ฟ","algeria","",8,0],["๐Ÿ‡ช๐Ÿ‡ฆ","ceuta_melilla","",8,0],["๐Ÿ‡ช๐Ÿ‡จ","ecuador","",8,0],["๐Ÿ‡ช๐Ÿ‡ช","estonia","",8,0],["๐Ÿ‡ช๐Ÿ‡ฌ","egypt","",8,0],["๐Ÿ‡ช๐Ÿ‡ญ","western_sahara","",8,0],["๐Ÿ‡ช๐Ÿ‡ท","eritrea","",8,0],["๐Ÿ‡ช๐Ÿ‡ธ","es","spain",8,0],["๐Ÿ‡ช๐Ÿ‡น","ethiopia","",8,0],["๐Ÿ‡ช๐Ÿ‡บ","eu;european_union","",8,0],["๐Ÿ‡ซ๐Ÿ‡ฎ","finland","",8,0],["๐Ÿ‡ซ๐Ÿ‡ฏ","fiji","",8,0],["๐Ÿ‡ซ๐Ÿ‡ฐ","falkland_islands","",8,0],["๐Ÿ‡ซ๐Ÿ‡ฒ","micronesia","",8,0],["๐Ÿ‡ซ๐Ÿ‡ด","faroe_islands","",8,0],["๐Ÿ‡ซ๐Ÿ‡ท","fr","france french",8,0],["๐Ÿ‡ฌ๐Ÿ‡ฆ","gabon","",8,0],["๐Ÿ‡ฌ๐Ÿ‡ง","gb;uk","flag british",8,0],["๐Ÿ‡ฌ๐Ÿ‡ฉ","grenada","",8,0],["๐Ÿ‡ฌ๐Ÿ‡ช","georgia","",8,0],["๐Ÿ‡ฌ๐Ÿ‡ซ","french_guiana","",8,0],["๐Ÿ‡ฌ๐Ÿ‡ฌ","guernsey","",8,0],["๐Ÿ‡ฌ๐Ÿ‡ญ","ghana","",8,0],["๐Ÿ‡ฌ๐Ÿ‡ฎ","gibraltar","",8,0],["๐Ÿ‡ฌ๐Ÿ‡ฑ","greenland","",8,0],["๐Ÿ‡ฌ๐Ÿ‡ฒ","gambia","",8,0],["๐Ÿ‡ฌ๐Ÿ‡ณ","guinea","",8,0],["๐Ÿ‡ฌ๐Ÿ‡ต","guadeloupe","",8,0],["๐Ÿ‡ฌ๐Ÿ‡ถ","equatorial_guinea","",8,0],["๐Ÿ‡ฌ๐Ÿ‡ท","greece","",8,0],["๐Ÿ‡ฌ๐Ÿ‡ธ","south_georgia_south_sandwich_islands","",8,0],["๐Ÿ‡ฌ๐Ÿ‡น","guatemala","",8,0],["๐Ÿ‡ฌ๐Ÿ‡บ","guam","",8,0],["๐Ÿ‡ฌ๐Ÿ‡ผ","guinea_bissau","",8,0],["๐Ÿ‡ฌ๐Ÿ‡พ","guyana","",8,0],["๐Ÿ‡ญ๐Ÿ‡ฐ","hong_kong","",8,0],["๐Ÿ‡ญ๐Ÿ‡ฒ","heard_mcdonald_islands","",8,0],["๐Ÿ‡ญ๐Ÿ‡ณ","honduras","",8,0],["๐Ÿ‡ญ๐Ÿ‡ท","croatia","",8,0],["๐Ÿ‡ญ๐Ÿ‡น","haiti","",8,0],["๐Ÿ‡ญ๐Ÿ‡บ","hungary","",8,0],["๐Ÿ‡ฎ๐Ÿ‡จ","canary_islands","",8,0],["๐Ÿ‡ฎ๐Ÿ‡ฉ","indonesia","",8,0],["๐Ÿ‡ฎ๐Ÿ‡ช","ireland","",8,0],["๐Ÿ‡ฎ๐Ÿ‡ฑ","israel","",8,0],["๐Ÿ‡ฎ๐Ÿ‡ฒ","isle_of_man","",8,0],["๐Ÿ‡ฎ๐Ÿ‡ณ","india","",8,0],["๐Ÿ‡ฎ๐Ÿ‡ด","british_indian_ocean_territory","",8,0],["๐Ÿ‡ฎ๐Ÿ‡ถ","iraq","",8,0],["๐Ÿ‡ฎ๐Ÿ‡ท","iran","",8,0],["๐Ÿ‡ฎ๐Ÿ‡ธ","iceland","",8,0],["๐Ÿ‡ฎ๐Ÿ‡น","it","italy",8,0],["๐Ÿ‡ฏ๐Ÿ‡ช","jersey","",8,0],["๐Ÿ‡ฏ๐Ÿ‡ฒ","jamaica","",8,0],["๐Ÿ‡ฏ๐Ÿ‡ด","jordan","",8,0],["๐Ÿ‡ฏ๐Ÿ‡ต","jp","japan",8,0],["๐Ÿ‡ฐ๐Ÿ‡ช","kenya","",8,0],["๐Ÿ‡ฐ๐Ÿ‡ฌ","kyrgyzstan","",8,0],["๐Ÿ‡ฐ๐Ÿ‡ญ","cambodia","",8,0],["๐Ÿ‡ฐ๐Ÿ‡ฎ","kiribati","",8,0],["๐Ÿ‡ฐ๐Ÿ‡ฒ","comoros","",8,0],["๐Ÿ‡ฐ๐Ÿ‡ณ","st_kitts_nevis","",8,0],["๐Ÿ‡ฐ๐Ÿ‡ต","north_korea","",8,0],["๐Ÿ‡ฐ๐Ÿ‡ท","kr","korea",8,0],["๐Ÿ‡ฐ๐Ÿ‡ผ","kuwait","",8,0],["๐Ÿ‡ฐ๐Ÿ‡พ","cayman_islands","",8,0],["๐Ÿ‡ฐ๐Ÿ‡ฟ","kazakhstan","",8,0],["๐Ÿ‡ฑ๐Ÿ‡ฆ","laos","",8,0],["๐Ÿ‡ฑ๐Ÿ‡ง","lebanon","",8,0],["๐Ÿ‡ฑ๐Ÿ‡จ","st_lucia","",8,0],["๐Ÿ‡ฑ๐Ÿ‡ฎ","liechtenstein","",8,0],["๐Ÿ‡ฑ๐Ÿ‡ฐ","sri_lanka","",8,0],["๐Ÿ‡ฑ๐Ÿ‡ท","liberia","",8,0],["๐Ÿ‡ฑ๐Ÿ‡ธ","lesotho","",8,0],["๐Ÿ‡ฑ๐Ÿ‡น","lithuania","",8,0],["๐Ÿ‡ฑ๐Ÿ‡บ","luxembourg","",8,0],["๐Ÿ‡ฑ๐Ÿ‡ป","latvia","",8,0],["๐Ÿ‡ฑ๐Ÿ‡พ","libya","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ฆ","morocco","",8,0],["๐Ÿ‡ฒ๐Ÿ‡จ","monaco","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ฉ","moldova","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ช","montenegro","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ซ","st_martin","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ฌ","madagascar","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ญ","marshall_islands","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ฐ","macedonia","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ฑ","mali","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ฒ","myanmar","burma",8,0],["๐Ÿ‡ฒ๐Ÿ‡ณ","mongolia","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ด","macau","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ต","northern_mariana_islands","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ถ","martinique","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ท","mauritania","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ธ","montserrat","",8,0],["๐Ÿ‡ฒ๐Ÿ‡น","malta","",8,0],["๐Ÿ‡ฒ๐Ÿ‡บ","mauritius","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ป","maldives","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ผ","malawi","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ฝ","mexico","",8,0],["๐Ÿ‡ฒ๐Ÿ‡พ","malaysia","",8,0],["๐Ÿ‡ฒ๐Ÿ‡ฟ","mozambique","",8,0],["๐Ÿ‡ณ๐Ÿ‡ฆ","namibia","",8,0],["๐Ÿ‡ณ๐Ÿ‡จ","new_caledonia","",8,0],["๐Ÿ‡ณ๐Ÿ‡ช","niger","",8,0],["๐Ÿ‡ณ๐Ÿ‡ซ","norfolk_island","",8,0],["๐Ÿ‡ณ๐Ÿ‡ฌ","nigeria","",8,0],["๐Ÿ‡ณ๐Ÿ‡ฎ","nicaragua","",8,0],["๐Ÿ‡ณ๐Ÿ‡ฑ","netherlands","",8,0],["๐Ÿ‡ณ๐Ÿ‡ด","norway","",8,0],["๐Ÿ‡ณ๐Ÿ‡ต","nepal","",8,0],["๐Ÿ‡ณ๐Ÿ‡ท","nauru","",8,0],["๐Ÿ‡ณ๐Ÿ‡บ","niue","",8,0],["๐Ÿ‡ณ๐Ÿ‡ฟ","new_zealand","",8,0],["๐Ÿ‡ด๐Ÿ‡ฒ","oman","",8,0],["๐Ÿ‡ต๐Ÿ‡ฆ","panama","",8,0],["๐Ÿ‡ต๐Ÿ‡ช","peru","",8,0],["๐Ÿ‡ต๐Ÿ‡ซ","french_polynesia","",8,0],["๐Ÿ‡ต๐Ÿ‡ฌ","papua_new_guinea","",8,0],["๐Ÿ‡ต๐Ÿ‡ญ","philippines","",8,0],["๐Ÿ‡ต๐Ÿ‡ฐ","pakistan","",8,0],["๐Ÿ‡ต๐Ÿ‡ฑ","poland","",8,0],["๐Ÿ‡ต๐Ÿ‡ฒ","st_pierre_miquelon","",8,0],["๐Ÿ‡ต๐Ÿ‡ณ","pitcairn_islands","",8,0],["๐Ÿ‡ต๐Ÿ‡ท","puerto_rico","",8,0],["๐Ÿ‡ต๐Ÿ‡ธ","palestinian_territories","",8,0],["๐Ÿ‡ต๐Ÿ‡น","portugal","",8,0],["๐Ÿ‡ต๐Ÿ‡ผ","palau","",8,0],["๐Ÿ‡ต๐Ÿ‡พ","paraguay","",8,0],["๐Ÿ‡ถ๐Ÿ‡ฆ","qatar","",8,0],["๐Ÿ‡ท๐Ÿ‡ช","reunion","",8,0],["๐Ÿ‡ท๐Ÿ‡ด","romania","",8,0],["๐Ÿ‡ท๐Ÿ‡ธ","serbia","",8,0],["๐Ÿ‡ท๐Ÿ‡บ","ru","russia",8,0],["๐Ÿ‡ท๐Ÿ‡ผ","rwanda","",8,0],["๐Ÿ‡ธ๐Ÿ‡ฆ","saudi_arabia","",8,0],["๐Ÿ‡ธ๐Ÿ‡ง","solomon_islands","",8,0],["๐Ÿ‡ธ๐Ÿ‡จ","seychelles","",8,0],["๐Ÿ‡ธ๐Ÿ‡ฉ","sudan","",8,0],["๐Ÿ‡ธ๐Ÿ‡ช","sweden","",8,0],["๐Ÿ‡ธ๐Ÿ‡ฌ","singapore","",8,0],["๐Ÿ‡ธ๐Ÿ‡ญ","st_helena","",8,0],["๐Ÿ‡ธ๐Ÿ‡ฎ","slovenia","",8,0],["๐Ÿ‡ธ๐Ÿ‡ฏ","svalbard_jan_mayen","",8,0],["๐Ÿ‡ธ๐Ÿ‡ฐ","slovakia","",8,0],["๐Ÿ‡ธ๐Ÿ‡ฑ","sierra_leone","",8,0],["๐Ÿ‡ธ๐Ÿ‡ฒ","san_marino","",8,0],["๐Ÿ‡ธ๐Ÿ‡ณ","senegal","",8,0],["๐Ÿ‡ธ๐Ÿ‡ด","somalia","",8,0],["๐Ÿ‡ธ๐Ÿ‡ท","suriname","",8,0],["๐Ÿ‡ธ๐Ÿ‡ธ","south_sudan","",8,0],["๐Ÿ‡ธ๐Ÿ‡น","sao_tome_principe","",8,0],["๐Ÿ‡ธ๐Ÿ‡ป","el_salvador","",8,0],["๐Ÿ‡ธ๐Ÿ‡ฝ","sint_maarten","",8,0],["๐Ÿ‡ธ๐Ÿ‡พ","syria","",8,0],["๐Ÿ‡ธ๐Ÿ‡ฟ","swaziland","",8,0],["๐Ÿ‡น๐Ÿ‡ฆ","tristan_da_cunha","",8,0],["๐Ÿ‡น๐Ÿ‡จ","turks_caicos_islands","",8,0],["๐Ÿ‡น๐Ÿ‡ฉ","chad","",8,0],["๐Ÿ‡น๐Ÿ‡ซ","french_southern_territories","",8,0],["๐Ÿ‡น๐Ÿ‡ฌ","togo","",8,0],["๐Ÿ‡น๐Ÿ‡ญ","thailand","",8,0],["๐Ÿ‡น๐Ÿ‡ฏ","tajikistan","",8,0],["๐Ÿ‡น๐Ÿ‡ฐ","tokelau","",8,0],["๐Ÿ‡น๐Ÿ‡ฑ","timor_leste","",8,0],["๐Ÿ‡น๐Ÿ‡ฒ","turkmenistan","",8,0],["๐Ÿ‡น๐Ÿ‡ณ","tunisia","",8,0],["๐Ÿ‡น๐Ÿ‡ด","tonga","",8,0],["๐Ÿ‡น๐Ÿ‡ท","tr","turkey",8,0],["๐Ÿ‡น๐Ÿ‡น","trinidad_tobago","",8,0],["๐Ÿ‡น๐Ÿ‡ป","tuvalu","",8,0],["๐Ÿ‡น๐Ÿ‡ผ","taiwan","",8,0],["๐Ÿ‡น๐Ÿ‡ฟ","tanzania","",8,0],["๐Ÿ‡บ๐Ÿ‡ฆ","ukraine","",8,0],["๐Ÿ‡บ๐Ÿ‡ฌ","uganda","",8,0],["๐Ÿ‡บ๐Ÿ‡ฒ","us_outlying_islands","",8,0],["๐Ÿ‡บ๐Ÿ‡ณ","united_nations","",8,0],["๐Ÿ‡บ๐Ÿ‡ธ","us","flag united america",8,0],["๐Ÿ‡บ๐Ÿ‡พ","uruguay","",8,0],["๐Ÿ‡บ๐Ÿ‡ฟ","uzbekistan","",8,0],["๐Ÿ‡ป๐Ÿ‡ฆ","vatican_city","",8,0],["๐Ÿ‡ป๐Ÿ‡จ","st_vincent_grenadines","",8,0],["๐Ÿ‡ป๐Ÿ‡ช","venezuela","",8,0],["๐Ÿ‡ป๐Ÿ‡ฌ","british_virgin_islands","",8,0],["๐Ÿ‡ป๐Ÿ‡ฎ","us_virgin_islands","",8,0],["๐Ÿ‡ป๐Ÿ‡ณ","vietnam","",8,0],["๐Ÿ‡ป๐Ÿ‡บ","vanuatu","",8,0],["๐Ÿ‡ผ๐Ÿ‡ซ","wallis_futuna","",8,0],["๐Ÿ‡ผ๐Ÿ‡ธ","samoa","",8,0],["๐Ÿ‡ฝ๐Ÿ‡ฐ","kosovo","",8,0],["๐Ÿ‡พ๐Ÿ‡ช","yemen","",8,0],["๐Ÿ‡พ๐Ÿ‡น","mayotte","",8,0],["๐Ÿ‡ฟ๐Ÿ‡ฆ","south_africa","",8,0],["๐Ÿ‡ฟ๐Ÿ‡ฒ","zambia","",8,0],["๐Ÿ‡ฟ๐Ÿ‡ผ","zimbabwe","",8,0],["๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ","england","",8,0],["๐Ÿด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ","scotland","",8,0],["๐Ÿด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ","wales","",8,0]]}; // Geometry (px). The card is a fixed-size panel like Discord's: header, a category rail on the // left, the grid, and a preview bar under the grid. Narrow panes shrink the width only: the grid // loses columns, the layout stays. const BT_EP_CELL = 40; const BT_EP_COLS = 9; const BT_EP_ROW = BT_EP_CELL; const BT_EP_HEAD = 32; const BT_EP_PAD_L = 8; const BT_EP_PAD_R = 4; const BT_EP_MIN_COLS = 5; const BT_EP_CARD_W = 432; // i18n:begin // This plugin's English words. Translations live in the app's catalogs (plugin..*, // docs/TRANSLATING.md); a plugin of your own can also pass { en: {...}, tr: {...} } to register. const MESSAGES = { "en": { "suggestions": "Emoji suggestions", "matching": "Emoji matching", "buttonTitle": "Emoji (Ctrl+Shift+E)", "picker": "Emoji picker", "title": "Emoji Picker", "replaceX": "Open this picker from X's emoji button", "replaceXHint": "Off: X's own picker stays on its button, and this one gets a button of its own next to it.", "cancel": "Cancel", "save": "Save", "results": "Search results", "frequent": "Frequently used", "search": "Search emoji", "clearSearch": "Clear search", "clear": "Clear", "skinTone": "Skin tone", "tone0": "Default", "tone1": "Light", "tone2": "Medium-light", "tone3": "Medium", "tone4": "Medium-dark", "tone5": "Dark", "categories": "Categories", "grid": "Emoji", "emptyHint": "Try a word like โ€œpartyโ€ or โ€œheartโ€.", "settingsLabel": "Emoji picker settings", "settings": "Settings", "noMatch": "No emoji match โ€œ{query}โ€", "resultCount": "{count, plural, one {# result} other {# results}}", "noResults": "No results", "pick": "Pick an emoji", "shiftClick": "Shift+click adds several", "added": "{name} added", "toneNamed": "Skin tone: {tone}", "flag": "Flag: {code}", "cat0": "Smileys & Emotion", "cat1": "People & Body", "cat2": "Animals & Nature", "cat3": "Food & Drink", "cat4": "Travel & Places", "cat5": "Activities", "cat6": "Objects", "cat7": "Symbols", "cat8": "Flags" } }; // i18n:end /** The words in the app's language (api.i18n); English before start() and outside the app. */ let i18n = null; const t = (key, params = {}) => (i18n ? i18n.t(key, params) : String(MESSAGES.en[key] ?? key) .replace(/\{(\w+), plural, one \{([^{}]*)\} other \{([^{}]*)\}\}/g, (m, name, one, other) => (Number(params[name]) === 1 ? one : other).replace('#', String(params[name]))) .replace(/\{(\w+)\}/g, (m, name) => (name in params ? String(params[name]) : m))); const BT_EP_CARD_H = 440; const BT_EP_CARD_MIN = 300; /** Glyph atlas: each emoji drawn once into a BT_EP_BOX square, BT_EP_SHEET_COLS to a sheet row. */ const BT_EP_BOX = 36; const BT_EP_SHEET_COLS = 8; const BT_EP_SHEET_SIZE = 64; const BT_EP_AC_W = 300; const BT_EP_TONES = ['\u{1F3FB}', '\u{1F3FC}', '\u{1F3FD}', '\u{1F3FE}', '\u{1F3FF}']; const BT_EP_TONE_NAMES = ['Default', 'Light', 'Medium-light', 'Medium', 'Medium-dark', 'Dark']; const BT_EP_TONE_BASE = 'โœ‹'; const BT_EP_CAT_ICONS = ['smileys', 'people', 'nature', 'food', 'travel', 'activities', 'objects', 'symbols', 'flags']; const BT_EP_TRIGGER = /(^|[\s(])[:]([a-z0-9_+-]{2,32})$/i; const BT_EP_EXACT = /(^|[\s(])[:]([a-z0-9_+-]{1,32})[:]$/i; const BT_EP_EDITABLE = '[contenteditable="true"], textarea, input[type="text"], input:not([type])'; const BT_EP_NAV = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown']; const BT_EP_GLYPH = '"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla", sans-serif'; /** X's own emoji button in a composer toolbar, by its label in any language X ships. */ const BT_EP_X_LABEL = /emoji|emoticon|smiley|็ตตๆ–‡ๅญ—|์ด๋ชจ์ง€|์ด๋ชจํ‹ฐ์ฝ˜|่กจๆƒ…|ัะผะพะดะทะธ|ะตะผะพะดะทั–|รฉmoji|ุฅูŠู…ูˆุฌูŠ|เคฐเคฎเฅ‹เคœเฅ€|เธญเธตเน‚เธกเธˆเธด/i; const BT_EP_SVG = 'http://www.w3.org/2000/svg'; const BT_EP_RING = 'M3 12a9 9 0 1 0 18 0a9 9 0 1 0-18 0'; // Monochrome line icons (24 px grid, stroked in currentColor). const BT_EP_ICONS = { frequent: [BT_EP_RING, 'M12 7.5V12l3 2'], smileys: [BT_EP_RING, 'M8.5 14.5a4.5 4.5 0 0 0 7 0', 'M9.2 9.3v1', 'M14.8 9.3v1'], people: ['M8.5 7.5a3.5 3.5 0 1 0 7 0a3.5 3.5 0 1 0-7 0', 'M5 20.5a7 7 0 0 1 14 0'], nature: ['M5 19C5 10.5 10 5 19.5 4.5 19.5 14 14 19 5 19Z', 'M5 19l8.5-8.5'], food: ['M5 9h11v5a5 5 0 0 1-5 5h-1a5 5 0 0 1-5-5V9Z', 'M16 10.5h1.5a2.5 2.5 0 0 1 0 5H16', 'M8.5 3.5V6', 'M12.5 3.5V6'], travel: ['M4 13l2.2-5.6A1.5 1.5 0 0 1 7.6 6.5h8.8a1.5 1.5 0 0 1 1.4.9L20 13v4.5H4Z', 'M4 13h16', 'M7 17.5v2', 'M17 17.5v2'], activities: [BT_EP_RING, 'M6 4.8c2.7 2 4 4.4 4 7.2s-1.3 5.2-4 7.2', 'M18 4.8c-2.7 2-4 4.4-4 7.2s1.3 5.2 4 7.2'], objects: ['M9.5 17.5h5', 'M10.5 20.5h3', 'M12 3.5a5.5 5.5 0 0 0-3.2 10c.7.5 1.2 1.3 1.2 2.2v.3h4v-.3c0-.9.5-1.7 1.2-2.2A5.5 5.5 0 0 0 12 3.5Z'], symbols: ['M12 19.5s-7.5-4.4-7.5-9.7A4.3 4.3 0 0 1 12 7.2a4.3 4.3 0 0 1 7.5 2.6c0 5.3-7.5 9.7-7.5 9.7Z'], flags: ['M5.5 21V4', 'M5.5 4.5h11.5l-2.5 4 2.5 4H5.5'], search: ['M4 10.5a6.5 6.5 0 1 0 13 0a6.5 6.5 0 1 0-13 0', 'M15.5 15.5 20 20'], clear: ['M7 7l10 10', 'M17 7 7 17'], empty: [BT_EP_RING, 'M9 15.5h6', 'M9.2 9.3v1', 'M14.8 9.3v1'], enter: ['M18.5 6v4.5a3 3 0 0 1-3 3H6', 'M9.5 10 6 13.5 9.5 17'], gear: ['M9.5 12a2.5 2.5 0 1 0 5 0a2.5 2.5 0 1 0-5 0', 'M12 3.5l1.6 2.1 2.6-.5.9 2.5 2.5.9-.5 2.6 2.1 1.6-2.1 1.6.5 2.6-2.5.9-.9 2.5-2.6-.5L12 20.5l-1.6-2.1-2.6.5-.9-2.5-2.5-.9.5-2.6L2.8 12l2.1-1.6-.5-2.6 2.5-.9.9-2.5 2.6.5Z'], }; // The toolbar button and the inline `:shortcode` list live in X's document (the list is referenced // by the editor's aria-activedescendant, which cannot point into a shadow root); `all: revert` keeps // X's stylesheet off them. The full picker is a shadow root of its own (BT_EP_CARD_CSS). const BT_EP_CSS = ` .bt-ep-slot { display: flex; flex: none; align-items: center; } .bt-ep-button { all: unset; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; min-width: 34px; border-radius: 9999px; color: var(--wb-accent, rgb(29,155,240)); cursor: pointer; transition: background-color .2s; } .bt-ep-button svg { display: block; width: 20px; height: 20px; } .bt-ep-button:hover, .bt-ep-button[aria-expanded="true"] { background: color-mix(in srgb, currentColor 10%, transparent); } .bt-ep-button:focus-visible { box-shadow: 0 0 0 2px currentColor; } .bt-ep-ac, .bt-ep-ac :where(:not(svg, svg *)) { all: revert; box-sizing: border-box; } .bt-ep-ac { position: fixed; z-index: 2147483640; inset: auto; margin: 0; padding: 0 0 8px; overflow: hidden auto; background: var(--ep-bg); color: var(--ep-text); border: 1px solid var(--ep-border); border-radius: 8px; box-shadow: var(--ep-shadow); font: 400 14px/1.25 var(--ep-font); -webkit-font-smoothing: antialiased; text-align: left; animation: bt-ep-ac-in .1s cubic-bezier(.2,.8,.2,1); } .bt-ep-ac::-webkit-scrollbar { width: 8px; } .bt-ep-ac::-webkit-scrollbar-thumb { background: var(--ep-thumb); background-clip: padding-box; border: 2px solid transparent; border-radius: 8px; } .bt-ep-ac-head { display: block; padding: 12px 16px 8px; font: 700 12px/16px var(--ep-font); letter-spacing: .02em; text-transform: uppercase; color: var(--ep-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .bt-ep-ac-head b { font-weight: 700; color: var(--ep-text); text-transform: none; letter-spacing: 0; } .bt-ep-ac-opt { display: flex; align-items: center; gap: 12px; height: 36px; margin: 0 8px; padding: 0 8px; border-radius: 6px; cursor: pointer; } .bt-ep-ac-opt[aria-selected="true"] { background: var(--ep-tile); } .bt-ep-ac-glyph { flex: none; display: grid; place-items: center; width: 24px; height: 24px; font: 22px/1 ${BT_EP_GLYPH}; } .bt-ep-ac-code { flex: 1; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--ep-muted); font-weight: 500; } .bt-ep-ac-code mark { background: none; color: var(--ep-text); font-weight: 700; } .bt-ep-ac-key { display: none; flex: none; height: 20px; padding: 0 6px; place-items: center; border-radius: 4px; background: var(--ep-bg); box-shadow: inset 0 0 0 1px var(--ep-border); color: var(--ep-muted); } .bt-ep-ac-opt[aria-selected="true"] .bt-ep-ac-key { display: inline-grid; } .bt-ep-ac-key svg { display: block; width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; } .bt-ep-ac-flag { display: inline-grid; place-items: center; width: 24px; height: 17px; border-radius: 3px; background: var(--ep-tile); box-shadow: inset 0 0 0 1px var(--ep-border); color: var(--ep-text); font: 700 9.5px/1 var(--ep-font); letter-spacing: .04em; } @keyframes bt-ep-ac-in { from { opacity: 0; transform: translateY(var(--ep-dy, -4px)); } } @media (prefers-reduced-motion: reduce) { .bt-ep-ac, .bt-ep-button { animation: none !important; transition: none !important; } } `; const BT_EP_CARD_CSS = ` :host { all: initial; } .ep-card { position: absolute; box-sizing: border-box; display: grid; grid-template-columns: 48px minmax(0, 1fr); grid-template-rows: 48px minmax(0, 1fr) 48px; width: ${BT_EP_CARD_W}px; height: ${BT_EP_CARD_H}px; overflow: hidden; pointer-events: auto; background: var(--ep-bg); color: var(--ep-text); border: 1px solid var(--ep-border); border-radius: 8px; box-shadow: var(--ep-shadow); font: 400 14px/1.25 var(--ep-font); -webkit-font-smoothing: antialiased; text-align: left; transform-origin: var(--ep-origin, top left); animation: ep-in .12s cubic-bezier(.2,.8,.2,1); } .ep-card *, .ep-card *::before, .ep-card *::after { box-sizing: border-box; } .ep-card.ep-closing { animation: ep-out .08s ease-in forwards; pointer-events: none; } /* Translate only: a scale would raster the whole card twice (at .96, then again at 1). */ @keyframes ep-in { from { opacity: 0; transform: translateY(var(--ep-dy, -4px)); } } @keyframes ep-out { to { opacity: 0; transform: translateY(var(--ep-dy, -4px)); } } @keyframes ep-drop { from { opacity: 0; transform: translateY(-4px); } } button { font: inherit; color: inherit; margin: 0; } svg { display: block; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; } /* Header: search, skin tone. */ .ep-head { grid-column: 1 / -1; grid-row: 1; position: relative; display: flex; align-items: center; gap: 8px; padding: 0 8px; border-bottom: 1px solid var(--ep-border); } .ep-search { flex: 1; min-width: 0; display: flex; align-items: center; gap: 4px; height: 32px; padding: 0 4px 0 10px; cursor: text; border-radius: 6px; background: var(--ep-input); box-shadow: inset 0 0 0 1px var(--ep-input-line); transition: box-shadow .1s; } .ep-search:focus-within { box-shadow: inset 0 0 0 1px var(--ep-accent); } .ep-search-input { flex: 1; min-width: 0; height: 100%; padding: 0; border: 0; outline: 0; background: transparent; color: var(--ep-text); font: 400 14px/1 var(--ep-font); } .ep-search-input::placeholder { color: var(--ep-muted); opacity: 1; } .ep-search-icon, .ep-clear { flex: none; display: grid; place-items: center; width: 24px; height: 24px; padding: 0; border: 0; border-radius: 4px; background: transparent; color: var(--ep-muted); } .ep-search-icon svg { width: 16px; height: 16px; } .ep-clear { cursor: pointer; } .ep-clear:hover { background: var(--ep-hover); color: var(--ep-text); } .ep-clear svg { width: 14px; height: 14px; stroke-width: 2; } .ep-search-icon[hidden], .ep-clear[hidden] { display: none; } .ep-tone-btn { flex: none; display: grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 0; border-radius: 6px; background: transparent; cursor: pointer; font: 22px/1 ${BT_EP_GLYPH}; transition: background-color .1s; } .ep-tone-btn:hover, .ep-tone-btn[aria-expanded="true"] { background: var(--ep-hover); } .ep-tones { position: absolute; top: 7px; right: 7px; z-index: 4; display: flex; flex-direction: column; gap: 2px; padding: 3px; background: var(--ep-bg); border: 1px solid var(--ep-border); border-radius: 8px; box-shadow: var(--ep-shadow-sm); animation: ep-drop .1s cubic-bezier(.2,.8,.2,1); } .ep-tones[hidden] { display: none; } .ep-tone { display: grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 0; border-radius: 6px; background: transparent; cursor: pointer; font: 22px/1 ${BT_EP_GLYPH}; } .ep-tone:hover { background: var(--ep-hover); } .ep-tone[aria-checked="true"] { background: var(--ep-sel); } /* Category rail. */ .ep-rail { grid-column: 1; grid-row: 2 / 4; display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 8px 0; overflow: hidden auto; scrollbar-width: none; background: var(--ep-rail); border-right: 1px solid var(--ep-border); } .ep-rail-btn { flex: none; display: grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--ep-muted); cursor: pointer; transition: background-color .1s, color .1s; } .ep-rail-btn svg { width: 20px; height: 20px; } .ep-rail-btn:hover { background: var(--ep-hover); color: var(--ep-text); } .ep-rail-btn[aria-current="true"] { background: var(--ep-sel); color: var(--ep-accent); } .ep-rail-sep { flex: none; width: 24px; height: 1px; margin: 2px 0; background: var(--ep-border); } /* The grid: one virtualized scroller, a sticky header over it, an empty state. */ .ep-body { grid-column: 2; grid-row: 2; position: relative; min-width: 0; min-height: 0; } .ep-scroll { position: absolute; inset: 0; overflow: hidden auto; outline: none; overscroll-behavior: contain; contain: strict; } .ep-scroll::-webkit-scrollbar { width: 8px; } .ep-scroll::-webkit-scrollbar-thumb { background: var(--ep-thumb); background-clip: padding-box; border: 2px solid transparent; border-radius: 8px; min-height: 32px; } .ep-scroll::-webkit-scrollbar-track { background: transparent; } .ep-sizer { position: relative; width: 100%; } .ep-row { position: absolute; top: 0; left: 0; display: flex; height: ${BT_EP_ROW}px; contain: layout style; } .ep-hdr, .ep-sticky { display: flex; align-items: center; gap: 6px; height: ${BT_EP_HEAD}px; padding: 4px 12px 0 var(--ep-hdr-pad, 12px); font: 700 12px/16px var(--ep-font); letter-spacing: .02em; text-transform: uppercase; color: var(--ep-muted); white-space: nowrap; } .ep-hdr { position: absolute; top: 0; left: 0; right: 0; } .ep-hdr svg, .ep-sticky svg { flex: none; width: 16px; height: 16px; stroke-width: 2; } .ep-hdr-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; } .ep-count { margin-left: auto; font-weight: 600; font-variant-numeric: tabular-nums; letter-spacing: 0; } .ep-sticky { position: absolute; top: 0; left: 0; right: 0; z-index: 2; background: var(--ep-bg); pointer-events: none; } .ep-sticky.ep-lifted { box-shadow: 0 1px 0 var(--ep-border); } .ep-sticky[hidden] { display: none; } .ep-cell { flex: none; display: grid; place-items: center; width: ${BT_EP_CELL}px; height: ${BT_EP_CELL}px; border-radius: 6px; cursor: pointer; font: 28px/1 ${BT_EP_GLYPH}; user-select: none; -webkit-user-select: none; } .ep-cell[aria-selected="true"] { background-color: var(--ep-tile); } /* Painted from the glyph atlas (the cell's background image; its sheet comes from a rule per sheet): the text glyph stays for copy and assistive tech, unpainted. */ .ep-cell.ep-bm { background-repeat: no-repeat; background-size: ${BT_EP_BOX * BT_EP_SHEET_COLS}px auto; } .ep-cell.ep-bm > span { visibility: hidden; } ${Array.from({ length: BT_EP_SHEET_COLS }, (_, i) => `.ep-bm.ep-c${i} { background-position-x: ${(BT_EP_CELL - BT_EP_BOX) / 2 - i * BT_EP_BOX}px; }`).join(' ')} ${Array.from({ length: BT_EP_SHEET_SIZE / BT_EP_SHEET_COLS }, (_, i) => `.ep-bm.ep-r${i} { background-position-y: ${(BT_EP_CELL - BT_EP_BOX) / 2 - i * BT_EP_BOX}px; }`).join(' ')} .ep-scroll.ep-kbd .ep-cell[aria-selected="true"] { box-shadow: inset 0 0 0 2px var(--ep-accent); } .ep-empty { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; padding: 24px; text-align: center; color: var(--ep-muted); font-size: 13px; } .ep-empty[hidden] { display: none; } .ep-empty svg { width: 40px; height: 40px; margin-bottom: 8px; stroke-width: 1.4; } .ep-empty strong { color: var(--ep-text); font-size: 15px; font-weight: 700; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* Preview bar. */ .ep-foot { grid-column: 2; grid-row: 3; display: flex; align-items: center; gap: 10px; min-width: 0; padding: 0 8px 0 12px; background: var(--ep-rail); border-top: 1px solid var(--ep-border); } .ep-preview-glyph { flex: none; display: grid; place-items: center; width: 28px; height: 28px; font: 26px/1 ${BT_EP_GLYPH}; color: var(--ep-muted); } .ep-preview-glyph svg { width: 24px; height: 24px; stroke-width: 1.6; } .ep-preview-text { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 8px; overflow: hidden; white-space: nowrap; } .ep-preview-code { flex: none; max-width: 100%; overflow: hidden; text-overflow: ellipsis; font-weight: 700; font-size: 14px; color: var(--ep-text); } .ep-preview-alt { min-width: 0; overflow: hidden; text-overflow: ellipsis; font-size: 12px; color: var(--ep-muted); } .ep-foot.ep-idle .ep-preview-code { font-weight: 600; color: var(--ep-muted); } .ep-gear { flex: none; display: grid; place-items: center; width: 28px; height: 28px; padding: 0; border: 0; border-radius: 6px; cursor: pointer; background: transparent; color: var(--ep-muted); transition: background-color .1s, color .1s; } .ep-gear:hover { background: var(--ep-hover); color: var(--ep-text); } .ep-gear svg { width: 18px; height: 18px; stroke-width: 1.7; } span.ep-flag { display: grid; place-items: center; width: 28px; height: 20px; border-radius: 4px; background: var(--ep-tile); box-shadow: inset 0 0 0 1px var(--ep-border); color: var(--ep-text); font: 700 10px/1 var(--ep-font); letter-spacing: .04em; } .ep-preview-glyph span.ep-flag { width: 28px; height: 20px; } .ep-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } :focus-visible { outline: 2px solid var(--ep-accent); outline-offset: -2px; } .ep-search-input:focus-visible, .ep-scroll:focus-visible { outline: none; } @media (prefers-reduced-motion: reduce) { .ep-card, .ep-card * { animation: none !important; transition: none !important; } } `; let BT_EP_FLAT = null; function btEpFlat() { if (BT_EP_FLAT) return BT_EP_FLAT; BT_EP_FLAT = EMOJI_DATA.rows.map(row => ({ emoji: row[0], names: row[1].split(';'), tags: row[2] ? row[2].split(' ') : [], cat: row[3], skin: row[4] === 1, })); return BT_EP_FLAT; } let BT_EP_BY_CAT = null; function btEpByCat() { if (BT_EP_BY_CAT) return BT_EP_BY_CAT; BT_EP_BY_CAT = EMOJI_DATA.categories.map(() => []); for (const entry of btEpFlat()) BT_EP_BY_CAT[entry.cat]?.push(entry); return BT_EP_BY_CAT; } function btEpIsEditable(el) { if (!el || el.nodeType !== 1) return false; const tag = el.tagName; if (tag === 'TEXTAREA') return true; if (tag === 'INPUT') return /^(text|search)$/i.test(el.type || 'text'); return Boolean(el.isContentEditable || (el.closest && el.closest('[contenteditable="true"]'))); } /** An element with a class and attributes; text only ever goes in as text. */ function btEpEl(tag, className, attrs, text) { const el = document.createElement(tag); if (className) el.className = className; if (attrs) for (const key of Object.keys(attrs)) el.setAttribute(key, attrs[key]); if (text != null) el.textContent = text; return el; } function btEpIcon(name) { const svg = document.createElementNS(BT_EP_SVG, 'svg'); svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('aria-hidden', 'true'); svg.setAttribute('fill', 'none'); svg.setAttribute('stroke', 'currentColor'); svg.setAttribute('stroke-width', '1.8'); svg.setAttribute('stroke-linecap', 'round'); svg.setAttribute('stroke-linejoin', 'round'); for (const d of BT_EP_ICONS[name] || []) { const path = document.createElementNS(BT_EP_SVG, 'path'); path.setAttribute('d', d); svg.appendChild(path); } return svg; } let BT_EP_FLAGS_OK = null; /** Whether this system's emoji font draws flags (Windows' does not: it shows two letters). */ function btEpFlagsRender() { if (BT_EP_FLAGS_OK != null) return BT_EP_FLAGS_OK; try { const ctx = document.createElement('canvas').getContext('2d'); ctx.font = '32px ' + BT_EP_GLYPH; BT_EP_FLAGS_OK = ctx.measureText('๐Ÿ‡บ๐Ÿ‡ธ').width < ctx.measureText('๐Ÿ‡บ').width * 1.5; } catch { BT_EP_FLAGS_OK = true; } return BT_EP_FLAGS_OK; } /** * An entry's glyph as an element: the emoji (with the skin tone), or, where the system cannot draw * flags, a small chip with the region code instead of two loose letters. */ function btEpGlyph(entry, tone, chipClass) { const code = btEpFlagsRender() ? null : module.exports.regionCode(entry.emoji); if (code) return btEpEl('span', chipClass, null, code); return btEpEl('span', null, null, module.exports.withSkinTone(entry.emoji, entry.skin ? tone : 0)); } /** The text a grid cell draws for `entry` (null for a flag shown as a letter chip). */ function btEpGlyphText(entry, tone) { if (!btEpFlagsRender() && module.exports.regionCode(entry.emoji)) return null; return module.exports.withSkinTone(entry.emoji, entry.skin ? tone : 0); } /** * Lifts `el` into the browser's top layer (a manual popover): above every X layer and out of any * clipping or transformed ancestor such as X's modal composer, while it stays in that dialog's DOM * (so X's focus trap keeps focus in it). False where popovers are not supported. */ function btEpTopLayer(el) { if (typeof el.showPopover !== 'function') return false; try { if (!el.hasAttribute('popover')) el.setAttribute('popover', 'manual'); if (!el.matches(':popover-open')) el.showPopover(); return true; } catch { el.removeAttribute('popover'); return false; } } // ---- Glyph atlas ---- // A color emoji font (Segoe UI Emoji's gradient glyphs on Windows 11) costs ~3-4 ms of raster per // glyph, and the compositor's glyph cache is too small to keep a whole emoji grid, so every row // scrolled into view was rasterized again: 100-2400 ms frames while scrolling the grid. The atlas // draws each glyph once, in a worker, into sprite sheets (plain images, cheap to raster and cached // by the compositor), keeps them in IndexedDB for the next page, and the grid paints its cells from // them. A cell keeps its text glyph (hidden) for copy, tests and the moment before its sheet exists. const BT_EP_ATLAS_VERSION = 1; const BT_EP_ATLAS_DB = 'wayBetter-emoji-atlas'; const BT_EP_ATLAS_WORKER = ` const cache = new Map(); onmessage = async event => { const { id, glyphs, dpr, family, cols, box, size } = event.data; try { const s = Math.round(box * dpr); const canvas = new OffscreenCanvas(cols * s, Math.ceil(glyphs.length / cols) * s); const ctx = canvas.getContext('2d'); ctx.font = (size * dpr) + 'px ' + family; ctx.textAlign = 'center'; ctx.textBaseline = 'alphabetic'; // The DOM glyph: a line box of font-size height, centered in the cell, baseline after the half-leading. const m = ctx.measureText('\\u{1F600}'); const ascent = m.fontBoundingBoxAscent / dpr; const descent = m.fontBoundingBoxDescent / dpr; const baseline = ((box - size) / 2 + (size - ascent - descent) / 2 + ascent) * dpr; glyphs.forEach((glyph, i) => ctx.fillText(glyph, ((i % cols) + 0.5) * s, Math.floor(i / cols) * s + baseline)); postMessage({ id, blob: await canvas.convertToBlob({ type: 'image/png' }) }); } catch (error) { postMessage({ id, error: String(error) }); } };`; class BtEpAtlas { constructor() { this.dpr = 0; /** glyph -> { url, col, row } */ this.ready = new Map(); this.queued = new Set(); this.queue = []; this.inFlight = 0; this.worker = null; this.failed = false; this.urls = []; this.listeners = new Set(); this.db = null; this.loaded = null; this.seq = 0; this.sheetSeq = 0; this.pending = new Map(); } static supported() { return typeof OffscreenCanvas === 'function' && typeof Worker === 'function' && typeof URL.createObjectURL === 'function'; } /** Matches the atlas to the current device pixel ratio (a monitor or zoom change starts over). */ sync() { const dpr = window.devicePixelRatio || 1; if (dpr === this.dpr) return; this.reset(); this.dpr = dpr; this.loaded = this.loadSaved(dpr); } reset() { for (const url of this.urls) URL.revokeObjectURL(url); this.urls = []; this.ready.clear(); this.queued.clear(); this.queue = []; this.pending.clear(); this.inFlight = 0; } get(glyph) { return this.ready.get(glyph) || null; } /** Sheet id -> blob URL of every sheet drawn so far. */ sheets() { const out = new Map(); for (const spot of this.ready.values()) out.set(spot.sheet, spot.url); return out; } onReady(fn) { this.listeners.add(fn); return () => this.listeners.delete(fn); } /** Queues glyphs (in order); `urgent` puts them ahead of everything else. */ request(glyphs, urgent = false) { if (this.failed || !BtEpAtlas.supported()) return; this.sync(); const fresh = []; for (const glyph of glyphs) { if (!glyph || this.ready.has(glyph)) continue; if (this.queued.has(glyph)) { if (urgent) fresh.push(glyph); continue; } this.queued.add(glyph); fresh.push(glyph); } if (!fresh.length) return; if (urgent) { const set = new Set(fresh); this.queue = [...fresh, ...this.queue.filter(glyph => !set.has(glyph))]; } else this.queue.push(...fresh); this.pump(); } pump() { if (this.failed) return; // Saved sheets first: a glyph already on disk is never drawn again. if (this.loaded) { const loaded = this.loaded; this.loaded = null; loaded.then(() => this.pump()); return; } while (this.inFlight < 2 && this.queue.length) { const glyphs = []; while (glyphs.length < BT_EP_SHEET_SIZE && this.queue.length) { const glyph = this.queue.shift(); if (!this.ready.has(glyph)) glyphs.push(glyph); } if (!glyphs.length) break; this.draw(glyphs); } } ensureWorker() { if (this.worker) return this.worker; const url = URL.createObjectURL(new Blob([BT_EP_ATLAS_WORKER], { type: 'text/javascript' })); try { this.worker = new Worker(url); } finally { URL.revokeObjectURL(url); } this.worker.onmessage = event => this.onSheet(event.data); this.worker.onerror = () => this.fail(); return this.worker; } fail() { this.failed = true; this.queue = []; this.queued.clear(); try { this.worker?.terminate(); } catch { /* Gone already. */ } this.worker = null; } draw(glyphs) { let worker; try { worker = this.ensureWorker(); } catch { this.fail(); return; } const id = ++this.seq; this.inFlight += 1; this.pending.set(id, { glyphs, dpr: this.dpr }); worker.postMessage({ id, glyphs, dpr: this.dpr, family: BT_EP_GLYPH, cols: BT_EP_SHEET_COLS, box: BT_EP_BOX, size: 28 }); } onSheet(data) { const job = this.pending.get(data.id); if (!job) return; this.pending.delete(data.id); this.inFlight = Math.max(0, this.inFlight - 1); if (data.error || !data.blob || job.dpr !== this.dpr) { if (data.error) this.fail(); else this.pump(); return; } this.add(job.glyphs, data.blob); this.save(job.glyphs, data.blob, job.dpr); this.pump(); } add(glyphs, blob) { const url = URL.createObjectURL(blob); this.urls.push(url); const sheet = ++this.sheetSeq; glyphs.forEach((glyph, i) => { this.ready.set(glyph, { url, sheet, col: i % BT_EP_SHEET_COLS, row: Math.floor(i / BT_EP_SHEET_COLS) }); this.queued.delete(glyph); }); for (const fn of [...this.listeners]) { try { fn(); } catch { /* A closed picker. */ } } } openDb() { if (this.db) return this.db; this.db = new Promise(resolve => { try { const request = indexedDB.open(BT_EP_ATLAS_DB, 1); request.onupgradeneeded = () => request.result.createObjectStore('sheets', { keyPath: 'k' }); request.onsuccess = () => resolve(request.result); request.onerror = () => resolve(null); request.onblocked = () => resolve(null); } catch { resolve(null); } }); return this.db; } async loadSaved(dpr) { const db = await this.openDb(); if (!db) return; const rows = await new Promise(resolve => { try { const request = db.transaction('sheets').objectStore('sheets').getAll(); request.onsuccess = () => resolve(request.result || []); request.onerror = () => resolve([]); } catch { resolve([]); } }); if (dpr !== this.dpr) return; for (const row of rows) { if (row.v !== BT_EP_ATLAS_VERSION || row.dpr !== dpr || row.family !== BT_EP_GLYPH || !(row.blob instanceof Blob) || !Array.isArray(row.glyphs)) continue; this.add(row.glyphs, row.blob); } // Queued meanwhile but found on disk: nothing to draw. this.queue = this.queue.filter(glyph => !this.ready.has(glyph)); } async save(glyphs, blob, dpr) { const db = await this.openDb(); if (!db) return; try { const k = [BT_EP_ATLAS_VERSION, dpr, glyphs.join('')].join('|'); db.transaction('sheets', 'readwrite').objectStore('sheets').put({ k, v: BT_EP_ATLAS_VERSION, dpr, family: BT_EP_GLYPH, glyphs, blob }); } catch { /* Quota or a closed database: the atlas just stays in memory. */ } } dispose() { this.fail(); this.reset(); this.listeners.clear(); this.db?.then(db => { try { db?.close(); } catch { /* Closed. */ } }); this.db = null; } } const btEpSheetRule = (id, url) => '.ep-bm[data-sh="' + id + '"] { background-image: url("' + url + '"); }'; /** * Paints `cell` from the atlas when its glyph is there; true when it did. No inline style: the * cell gets its sheet (one rule per sheet in the picker's style sheet) and its column/row classes. */ function btEpPaintCell(cell, atlas, p) { const glyph = cell.dataset.g; const spot = glyph && atlas && atlas.get(glyph); if (!spot) return false; if (!p.sheets.has(spot.sheet)) { // At the end of the sheet, after the cell rules. const sheet = p.atlasSheet || (p.atlasStyle && p.atlasStyle.sheet); if (!sheet) return false; try { sheet.insertRule(btEpSheetRule(spot.sheet, spot.url), sheet.cssRules.length); } catch { return false; } p.sheets.add(spot.sheet); } cell.dataset.sh = String(spot.sheet); cell.className = 'ep-cell ep-bm ep-c' + spot.col + ' ep-r' + spot.row; return true; } function btEpReducedMotion() { try { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; } catch { return false; } } module.exports = class EmojiPicker { // ---- Pure helpers: no DOM, safe to unit test directly. ---- /** All emoji rows expanded to objects; memoized. */ static entries() { return btEpFlat(); } /** `{ start, query }` when `text` (everything before the caret) ends in an open `:shortcode` of 2+ chars, else null. */ static parseTrigger(text) { const value = String(text || ''); const match = BT_EP_TRIGGER.exec(value); if (!match) return null; const query = match[2]; return { start: value.length - query.length - 1, query }; } /** * Characters to replace for the open `:trigger` that `text` (everything before the caret, read * now) ends in, or null when the caret is no longer in that trigger (`start` = where the trigger * began when the list opened). */ static replaceLength(text, start) { const trigger = EmojiPicker.parseTrigger(text); if (!trigger || (start != null && trigger.start !== start)) return null; return String(text).length - trigger.start; } /** `{ start, end, code }` when `text` ends in a just-closed `:shortcode:`, else null. `end` is `text.length`. */ static parseExact(text) { const value = String(text || ''); const match = BT_EP_EXACT.exec(value); if (!match) return null; const code = match[2]; return { start: value.length - code.length - 2, end: value.length, code }; } /** The entry whose primary or alternate shortcode equals `code` (case-insensitive), or null. */ static findExact(code) { const needle = String(code || '').toLowerCase(); if (!needle) return null; for (const entry of btEpFlat()) { if (entry.names.some(name => name.toLowerCase() === needle)) return entry; } return null; } /** * Ranked matches for `query`: exact shortcode first, then shortcode prefix, then a word inside a * shortcode (`face` in `smiling_face`), then tag word, then tag substring. Stable within a tier * (shorter primary name first). */ static search(query, limit) { const q = String(query || '').trim().toLowerCase(); if (!q) return []; const scored = []; for (const entry of btEpFlat()) { let score = -1; for (let i = 0; i < entry.names.length; i += 1) { const name = entry.names[i].toLowerCase(); if (name === q) { score = 100 - i; break; } if (name.startsWith(q)) score = Math.max(score, 60 - i); else if (name.includes('_' + q)) score = Math.max(score, 30); } if (score < 0) { for (const tag of entry.tags) { const lower = tag.toLowerCase(); if (lower === q) { score = 40; break; } if (lower.startsWith(q)) score = Math.max(score, 20); } } if (score < 0 && entry.tags.some(tag => tag.toLowerCase().includes(q))) score = 5; if (score >= 0) scored.push({ entry, score }); } scored.sort((a, b) => b.score - a.score || a.entry.names[0].length - b.entry.names[0].length); const sliced = limit ? scored.slice(0, limit) : scored; return sliced.map(item => item.entry); } /** * `emoji` with a Fitzpatrick modifier inserted after its first code point; `tone` is 0 (none) to 5. * A VS16 (U+FE0F) right after the base is dropped: the modifier already forces emoji presentation. */ static withSkinTone(emoji, tone) { if (!tone) return emoji; const modifier = BT_EP_TONES[tone - 1]; if (!modifier) return emoji; const chars = Array.from(emoji); if (!chars.length) return emoji; const rest = chars.slice(1); if (rest[0] === '๏ธ') rest.shift(); return chars[0] + modifier + rest.join(''); } /** * The region a flag emoji stands for: "US" for two regional indicators, "ENG"/"SCT"/"WLS" for * the subdivision flags; null for anything else. */ static regionCode(emoji) { const points = Array.from(String(emoji || ''), ch => ch.codePointAt(0)); if (points.length === 2 && points.every(cp => cp >= 0x1F1E6 && cp <= 0x1F1FF)) { return String.fromCharCode(...points.map(cp => cp - 0x1F1E6 + 65)); } if (points[0] === 0x1F3F4 && points[points.length - 1] === 0xE007F && points.length > 4) { const tag = String.fromCharCode(...points.slice(1, -1).map(cp => cp - 0xE0000)).toUpperCase(); return /^[A-Z]{2}[A-Z0-9]{1,3}$/.test(tag) ? tag.slice(2) : null; } return null; } /** A readable name for an entry: its first shortcode with letters, spaced and capitalized. */ static displayName(entry) { const first = entry.names.find(n => /[a-z]/i.test(n)) || entry.names[0]; if (/^[a-z]{2}$/.test(first) && entry.cat === EMOJI_DATA.categories.length - 1) return t('flag', { code: first.toUpperCase() }); const name = first.replace(/_/g, ' '); return name.charAt(0).toUpperCase() + name.slice(1); } /** * The virtual list's rows for sections of `counts` emoji: a header row per section, then rows of * `cols` cells. `rows[i]` = `{ kind: 'header'|'row', section, top, height, start?, end?, index? }` * (`index` counts emoji rows only), `sectionTops[s]` = where section `s` begins, * `sectionFirstRow[s]` = the `rows` index of its first emoji row, `height` = total height. */ static layoutRows(counts, cols = BT_EP_COLS, rowHeight = BT_EP_ROW, headerHeight = BT_EP_HEAD) { const rows = []; const sectionTops = []; const sectionFirstRow = []; let top = 0; let gridRows = 0; counts.forEach((count, section) => { sectionTops.push(top); rows.push({ kind: 'header', section, top, height: headerHeight }); top += headerHeight; sectionFirstRow.push(rows.length); for (let start = 0; start < count; start += cols) { rows.push({ kind: 'row', section, start, end: Math.min(count, start + cols), top, height: rowHeight, index: gridRows }); gridRows += 1; top += rowHeight; } }); return { rows, sectionTops, sectionFirstRow, height: top, gridRows }; } /** `{ start, end }` (end exclusive) of the rows that intersect the viewport, `overscan` px either side. */ static visibleRange(rows, scrollTop, viewport, overscan = 0) { const from = scrollTop - overscan; const to = scrollTop + viewport + overscan; let lo = 0; let hi = rows.length; while (lo < hi) { const mid = (lo + hi) >> 1; if (rows[mid].top + rows[mid].height <= from) lo = mid + 1; else hi = mid; } let end = lo; while (end < rows.length && rows[end].top < to) end += 1; return { start: lo, end }; } /** The section whose area contains `scrollTop` (the one the sticky header names). */ static sectionAt(sectionTops, scrollTop) { let section = 0; for (let i = 0; i < sectionTops.length; i += 1) { if (sectionTops[i] <= scrollTop + 0.5) section = i; else break; } return section; } /** * Keyboard movement in a grid of sections with `counts` cells, `cols` per row. `pos` is * `{ section, index }` or null (then the first cell). Arrows move in 2D and flow across sections * keeping the column, Home/End go to the row's ends (with `ctrl`: the first/last cell), * PageUp/PageDown jump to the previous/next section. Returns the new position (the same object's * values when there is nowhere to go), or null when there are no cells. */ static moveInGrid(counts, pos, key, cols = BT_EP_COLS, ctrl = false) { const filled = []; counts.forEach((count, section) => { if (count > 0) filled.push(section); }); if (!filled.length) return null; if (!pos || !(counts[pos.section] > 0)) return { section: filled[0], index: 0 }; const { section, index } = pos; const count = counts[section]; const col = index % cols; const at = filled.indexOf(section); const next = filled[at + 1]; const prev = filled[at - 1]; const lastRowStart = c => Math.floor((c - 1) / cols) * cols; switch (key) { case 'ArrowRight': if (index + 1 < count) return { section, index: index + 1 }; return next != null ? { section: next, index: 0 } : { section, index }; case 'ArrowLeft': if (index > 0) return { section, index: index - 1 }; return prev != null ? { section: prev, index: counts[prev] - 1 } : { section, index }; case 'ArrowDown': if (index + cols < count) return { section, index: index + cols }; if (index - col < lastRowStart(count)) return { section, index: count - 1 }; return next != null ? { section: next, index: Math.min(col, counts[next] - 1) } : { section, index }; case 'ArrowUp': if (index - cols >= 0) return { section, index: index - cols }; return prev != null ? { section: prev, index: Math.min(lastRowStart(counts[prev]) + col, counts[prev] - 1) } : { section, index }; case 'PageDown': return next != null ? { section: next, index: Math.min(col, counts[next] - 1) } : { section, index: Math.min(lastRowStart(count) + col, count - 1) }; case 'PageUp': if (index >= cols) return { section, index: col }; return prev != null ? { section: prev, index: Math.min(col, counts[prev] - 1) } : { section, index }; case 'Home': return ctrl ? { section: filled[0], index: 0 } : { section, index: index - col }; case 'End': { if (ctrl) { const last = filled[filled.length - 1]; return { section: last, index: counts[last] - 1 }; } return { section, index: Math.min(index - col + cols - 1, count - 1) }; } default: return { section, index }; } } /** * Where a `width` x `height` popup goes next to `anchor` (a rect) without covering `avoid` (the * text being typed; defaults to the anchor) inside a `viewWidth` x `viewHeight` pane: below when it * fits, else above, else on the roomier side shrunk to at least `minHeight`; only a pane too * small for that makes it overlap. Left-aligned with the anchor, clamped `margin` px inside. * Returns `{ left, top, width, height, side: 'below'|'above'|'over' }`. */ static placeCard(anchor, avoid, width, height, viewWidth, viewHeight, minHeight = 0, margin = 8, gap = 8) { const w = Math.max(0, Math.min(width, viewWidth - margin * 2)); const left = Math.round(Math.max(margin, Math.min(anchor.left, viewWidth - margin - w))); const area = avoid || anchor; const belowTop = Math.max(anchor.bottom, area.bottom) + gap; const aboveBottom = Math.min(anchor.top, area.top) - gap; const below = viewHeight - margin - belowTop; const above = aboveBottom - margin; const full = Math.max(0, Math.min(height, viewHeight - margin * 2)); let side; let h; if (below >= height) { side = 'below'; h = height; } else if (above >= height) { side = 'above'; h = height; } else if (Math.max(below, above) >= Math.min(minHeight, full)) { side = below >= above ? 'below' : 'above'; h = Math.min(height, Math.max(below, above)); } else { const top = Math.max(margin, Math.min(belowTop, viewHeight - margin - full)); return { left, top: Math.round(top), width: w, height: Math.round(full), side: 'over' }; } // An anchor scrolled out of view must not put the card off screen. const top = Math.max(margin, Math.min(side === 'below' ? belowTop : aboveBottom - h, viewHeight - margin - h)); return { left, top: Math.round(top), width: w, height: Math.round(h), side }; } /** How many columns of `cell` px fit in a grid `width` px wide (with the side padding), at least `min`. */ static columnsFor(width, cell = BT_EP_CELL, min = BT_EP_MIN_COLS) { return Math.max(min, Math.floor((Math.max(0, width) - BT_EP_PAD_L - BT_EP_PAD_R) / cell)); } /** Whether `el` is X's own emoji button in a composer toolbar (found by its label, any language). */ static isXEmojiButton(el) { if (!el || el.nodeType !== 1 || !el.closest) return false; if (el.classList && el.classList.contains('bt-ep-button')) return false; if (!(el.tagName === 'BUTTON' || el.getAttribute('role') === 'button')) return false; if (!el.closest('[data-testid="toolBar"]')) return false; return BT_EP_X_LABEL.test(el.getAttribute('aria-label') || ''); } // ---- Runtime ---- start() { const api = window.wayBetter.api; i18n = api.i18n || null; if (i18n) i18n.register(MESSAGES); this.api = api; this.store = api.storage('emoji-picker'); this.scope = api.scope(); this.tone = Math.max(0, Math.min(5, Number(this.store.get('tone', 0)) || 0)); this.recent = Array.isArray(this.store.get('recent', [])) ? this.store.get('recent', []) : []; this.freq = this.store.get('freq', {}) || {}; /** X's emoji button opens this picker (one picker, not two); off: X's stays and ours gets its own button. */ this.replaceX = this.store.get('replaceX', true) !== false; this.autocomplete = null; this.picker = null; this.settings = null; this.stopped = false; /** Picker hosts on the page, including ones still animating out. */ this.hosts = new Set(); /** Editors where X showed its own emoji typeahead; ours stays out of them. */ this.nativeEditors = new WeakSet(); this.scope.css('emoji-picker', BT_EP_CSS); this.atlas = new BtEpAtlas(); this.scope.defer(() => this.atlas.dispose()); // Anything left behind by an earlier run (buttons would make mountButton skip their toolbar). for (const stale of document.querySelectorAll('.bt-ep-slot, .bt-ep-button, .bt-ep-ac, wb-emoji-picker')) stale.remove(); this.scope.on(document, 'input', event => this.onInput(event), true); this.scope.on(document, 'keydown', event => this.onKeyDown(event), true); this.scope.on(document, 'pointerdown', event => this.onPointerDown(event), true); this.scope.on(document, 'mousedown', event => this.onXButtonMouseDown(event), true); this.scope.on(document, 'click', event => this.onXButtonClick(event), true); this.scope.on(document, 'selectionchange', () => this.onSelectionChange()); this.scope.on(window, 'resize', () => this.schedulePosition()); this.scope.on(document, 'scroll', () => this.schedulePosition(), true); this.scope.observe('[data-testid="toolBar"]', toolbar => this.mountButton(toolbar)); this.scope.onRouteChange(() => { this.closeAutocomplete(); this.closePicker({ immediate: true }); }); this.scope.onThemeChange(() => this.repaint()); this.scope.defer(() => { this.closeAutocomplete(); this.closePicker({ immediate: true }); for (const host of this.hosts) host.remove(); this.hosts.clear(); try { this.settings?.close(); } catch { /* Already closed. */ } this.settings = null; }); this.warmGlyphs(); } /** * Chromium shapes a color-emoji glyph slowly the first time it lays it out (about 1 ms each), which * would make the first open take ~100 ms. Lay out what an open shows first (Frequently used and the * top of every category) in small idle-time chunks, off screen, so opening is instant. */ warmGlyphs() { const queue = [...new Set([...this.frequentEntries(), ...btEpByCat().flatMap(list => list.slice(0, 72))].map(e => e.emoji))]; const idle = typeof window.requestIdleCallback === 'function' ? fn => window.requestIdleCallback(fn, { timeout: 5000 }) : fn => setTimeout(fn, 120); let stopped = false; this.scope.defer(() => { stopped = true; }); let first = true; const step = () => { if (stopped || !document.body) return; const chunk = queue.splice(0, 40); if (!chunk.length) { this.prime(); this.warmAtlas(); return; } const probe = btEpEl('span', null, { 'aria-hidden': 'true' }, chunk.join('')); probe.style.cssText = 'all:initial;position:fixed;left:0;top:0;width:1px;height:1px;overflow:hidden;visibility:hidden;contain:strict;pointer-events:none;font:28px/1 ' + BT_EP_GLYPH; if (first) { first = false; const text = btEpEl('span', null, null, 'FREQUENTLY USED SMILEYS & EMOTION Search emoji Pick an emoji'); text.style.cssText = 'font:700 12px/1 ' + this.themeFont(); probe.appendChild(text); } document.body.appendChild(probe); void probe.offsetWidth; probe.remove(); idle(step); }; idle(step); } /** Draws what an open shows first into the atlas (from disk when saved), once this page is on screen. */ warmAtlas() { if (this.stopped) return; if (document.visibilityState !== 'visible') { const later = () => { if (document.visibilityState === 'visible') { document.removeEventListener('visibilitychange', later); this.warmAtlas(); } }; this.scope.on(document, 'visibilitychange', later); return; } const entries = [...this.frequentEntries(), ...btEpByCat().flatMap(list => list.slice(0, 72))]; this.atlas.request(entries.map(entry => btEpGlyphText(entry, this.tone))); } stop() { this.stopped = true; this.scope.dispose(); this.autocomplete = null; this.picker = null; } // ---- Persistence ---- saveTone(tone) { this.tone = Math.max(0, Math.min(5, tone || 0)); this.store.set('tone', this.tone); } recordUsage(entry) { const name = entry.names[0]; this.recent = [name, ...this.recent.filter(n => n !== name)].slice(0, 36); this.store.set('recent', this.recent); this.freq[name] = (this.freq[name] || 0) + 1; this.store.set('freq', this.freq); } recentEntries() { return this.recent.map(name => EmojiPicker.findExact(name)).filter(Boolean); } /** Most used first; equal counts go by recency. */ frequentEntries(limit = BT_EP_COLS * 2) { const recency = name => { const i = this.recent.indexOf(name); return i < 0 ? 1e6 : i; }; return Object.keys(this.freq) .sort((a, b) => this.freq[b] - this.freq[a] || recency(a) - recency(b)) .slice(0, limit) .map(name => EmojiPicker.findExact(name)) .filter(Boolean); } // ---- Theme ---- themeFont() { try { const theme = this.api.theme.current(); return theme?.fonts?.dialog?.stack || theme?.font || 'system-ui, sans-serif'; } catch { return 'system-ui, sans-serif'; } } /** The active theme as `--ep-*` custom properties on `el` (the picker host or the inline list). */ paint(el) { let theme = null; try { theme = this.api.theme.current(); } catch { theme = null; } const c = theme?.colors || { bg: '#ffffff', surface: '#f7f9f9', elevated: '#eff3f4', border: '#cfd9de', text: '#0f1419', muted: '#536471', accent: '#1d9bf0', onAccent: '#ffffff' }; const dark = theme ? theme.mode === 'dark' : false; const font = theme?.fonts?.dialog?.stack || theme?.font || 'system-ui, sans-serif'; const mono = theme?.fonts?.mono?.stack || 'ui-monospace, "Cascadia Mono", Consolas, monospace'; const mix = (color, pct, base = 'transparent') => `color-mix(in srgb, ${color} ${pct}%, ${base})`; const vars = { '--ep-bg': c.bg, '--ep-border': c.border, '--ep-text': c.text, '--ep-muted': c.muted, '--ep-accent': c.accent, '--ep-font': font, '--ep-mono': mono, // The rail and the preview bar: one step off the card, like Discord's darker sidebar. '--ep-rail': dark ? c.surface : mix(c.text, 3.5, c.bg), '--ep-input': dark ? c.elevated : mix(c.text, 5, c.bg), '--ep-input-line': dark ? 'transparent' : mix(c.text, 6, 'transparent'), // The hover tile: an opaque surface step, crisp on any page background. '--ep-tile': dark ? mix(c.text, 12, c.bg) : mix(c.text, 8, c.bg), '--ep-hover': mix(c.text, dark ? 10 : 7), '--ep-sel': mix(c.accent, dark ? 20 : 13), '--ep-thumb': mix(c.text, dark ? 28 : 22), '--ep-shadow': dark ? '0 0 0 1px rgba(0,0,0,.5), 0 8px 24px rgba(0,0,0,.55), 0 0 16px rgba(255,255,255,.07)' : '0 0 0 1px rgba(15,20,25,.04), 0 8px 28px rgba(15,20,25,.18), 0 2px 6px rgba(15,20,25,.08)', '--ep-shadow-sm': dark ? '0 6px 16px rgba(0,0,0,.6)' : '0 6px 16px rgba(15,20,25,.16)', }; for (const name of Object.keys(vars)) el.style.setProperty(name, vars[name]); el.style.setProperty('color-scheme', dark ? 'dark' : 'light'); } repaint() { if (this.picker) this.paint(this.picker.host); if (this.autocomplete) this.paint(this.autocomplete.list); for (const button of document.querySelectorAll('.bt-ep-button')) this.matchXColor(button); } // ---- Caret / text reading ---- caretContext(target) { if (target.tagName === 'TEXTAREA' || target.tagName === 'INPUT') { const pos = target.selectionStart == null ? target.value.length : target.selectionStart; return { plain: true, target, text: target.value.slice(0, pos), pos }; } const root = target.closest('[contenteditable="true"]'); if (!root) return null; const sel = window.getSelection(); if (!sel || !sel.rangeCount || !sel.isCollapsed) return null; const anchor = sel.anchorNode; if (!root.contains(anchor)) return null; let text = ''; try { const range = document.createRange(); range.selectNodeContents(root); range.setEnd(anchor, sel.anchorOffset); text = range.toString(); } catch { return null; } return { plain: false, target, root, anchor, anchorOffset: sel.anchorOffset, text }; } caretRect(context) { if (context.plain) { // A plain field has no caret rect: open the list under the field, not over its text. const rect = context.target.getBoundingClientRect(); return { left: rect.left, right: rect.left, top: rect.bottom, bottom: rect.bottom }; } const sel = window.getSelection(); if (sel && sel.rangeCount) { const rect = EmojiPicker.rangeRect(sel.getRangeAt(0)); if (rect) return rect; } const rect = context.root.getBoundingClientRect(); return { left: rect.left, right: rect.left, top: rect.top, bottom: rect.top }; } /** The collapsed start of `range` on screen, or null when it has no box. */ static rangeRect(range) { if (!range) return null; try { const probe = range.cloneRange(); probe.collapse(true); const rects = probe.getClientRects(); if (rects.length) return rects[0]; const rect = probe.getBoundingClientRect(); if (rect && (rect.width || rect.height || rect.top)) return rect; } catch { /* Detached. */ } return null; } /** Puts the caret after the last text of a contenteditable editor and reads it; null when impossible. */ caretAtEnd(editor) { const root = editor.closest && editor.closest('[contenteditable="true"]'); if (!root) return null; const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT); let last = null; for (let node = walker.nextNode(); node; node = walker.nextNode()) last = node; // No text yet: inside the innermost last element (Draft.js keeps a
in an empty block). let holder = root; while (!last && holder.lastElementChild && holder.lastElementChild.tagName !== 'BR') holder = holder.lastElementChild; try { const range = document.createRange(); if (last) range.setStart(last, last.length); else range.setStart(holder, 0); range.collapse(true); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); } catch { return null; } return this.caretContext(editor); } // ---- Insertion (focus + execCommand insertText, like the rest of the app) ---- replaceBack(context, backChars, replacement) { if (context.plain) { const target = context.target; const end = context.pos; const start = Math.max(0, end - backChars); target.focus(); target.setSelectionRange(start, end); if (!document.execCommand('insertText', false, replacement)) { const value = target.value; target.value = value.slice(0, start) + replacement + value.slice(end); const at = start + replacement.length; target.setSelectionRange(at, at); target.dispatchEvent(new Event('input', { bubbles: true })); } return; } const walker = document.createTreeWalker(context.root, NodeFilter.SHOW_TEXT); const nodes = []; let node = walker.nextNode(); while (node) { nodes.push(node); node = walker.nextNode(); } let idx = nodes.indexOf(context.anchor); if (idx === -1 && backChars > 0) return; let remaining = backChars; let atNode = context.anchor; let atOffset = context.anchorOffset; while (remaining > 0) { if (atOffset >= remaining) { atOffset -= remaining; remaining = 0; break; } remaining -= atOffset; idx -= 1; if (idx < 0) { atOffset = 0; break; } atNode = nodes[idx]; atOffset = atNode.length; } const range = document.createRange(); range.setStart(atNode, atOffset); range.setEnd(context.anchor, context.anchorOffset); const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); context.root.focus(); if (!document.execCommand('insertText', false, replacement)) { range.deleteContents(); range.insertNode(document.createTextNode(replacement)); } } insertEntry(entry, context, backChars) { const emoji = EmojiPicker.withSkinTone(entry.emoji, entry.skin ? this.tone : 0); this.replaceBack(context, backChars == null ? 0 : backChars, emoji); this.recordUsage(entry); } // ---- Inline autocomplete ---- onInput(event) { if (event.isComposing) return; const target = event.target; if (!btEpIsEditable(target) || this.nativeEditors.has(target)) return; const context = this.caretContext(target); if (!context) { this.closeAutocomplete(); return; } const exact = EmojiPicker.parseExact(context.text); if (exact && EmojiPicker.findExact(exact.code)) { this.closeAutocomplete(); // Let X's editor (Draft.js/React) handle this input first, then swap on a fresh caret read. requestAnimationFrame(() => { if (this.stopped) return; const now = this.caretContext(target); const still = now && EmojiPicker.parseExact(now.text); if (still && still.code === exact.code) this.insertEntry(EmojiPicker.findExact(still.code), now, still.end - still.start); }); return; } const trigger = EmojiPicker.parseTrigger(context.text); if (!trigger) { this.closeAutocomplete(); return; } this.openAutocomplete(target, context, trigger); } openAutocomplete(target, context, trigger) { const items = EmojiPicker.search(trigger.query, 8); if (!items.length) { this.closeAutocomplete(); return; } if (!this.autocomplete) { const list = btEpEl('div', 'bt-ep-ac', { role: 'listbox', 'aria-label': t('suggestions') }); list.id = 'bt-ep-auto-' + Math.random().toString(36).slice(2); // Choosing on mousedown keeps focus (and the caret) in the editor. list.addEventListener('mousedown', event => { event.preventDefault(); const option = event.target.closest && event.target.closest('.bt-ep-ac-opt'); if (option) this.chooseAutocomplete(Number(option.dataset.index)); }); list.addEventListener('pointermove', event => { const option = event.target.closest && event.target.closest('.bt-ep-ac-opt'); if (option) this.selectAutocomplete(Number(option.dataset.index)); }); this.autocomplete = { target, list, items: [], options: [], active: 0, start: 0 }; } const ac = this.autocomplete; this.paint(ac.list); ac.target = target; ac.items = items; ac.active = 0; ac.start = trigger.start; const query = trigger.query.toLowerCase(); const head = btEpEl('div', 'bt-ep-ac-head', { 'aria-hidden': 'true' }, t('matching') + ' '); head.appendChild(btEpEl('b', null, null, ':' + trigger.query)); ac.options = items.map((entry, index) => { const option = btEpEl('div', 'bt-ep-ac-opt', { role: 'option', 'aria-selected': index === 0 ? 'true' : 'false' }); option.id = ac.list.id + '-' + index; option.dataset.index = String(index); option.setAttribute('aria-label', EmojiPicker.displayName(entry)); const glyph = btEpEl('span', 'bt-ep-ac-glyph'); glyph.appendChild(btEpGlyph(entry, this.tone, 'bt-ep-ac-flag')); const code = btEpEl('span', 'bt-ep-ac-code'); const name = entry.names.find(n => n.toLowerCase().startsWith(query)) || entry.names[0]; code.append(':'); if (name.toLowerCase().startsWith(query)) { code.appendChild(btEpEl('mark', null, null, name.slice(0, query.length))); code.append(name.slice(query.length) + ':'); } else code.append(name + ':'); const key = btEpEl('span', 'bt-ep-ac-key', { 'aria-hidden': 'true' }); key.appendChild(btEpIcon('enter')); option.append(glyph, code, key); return option; }); ac.list.replaceChildren(head, ...ac.options); // Inside X's modal composer when there is one (content outside an aria-modal dialog is hidden // from screen readers). const container = (target.closest && target.closest('[aria-modal="true"]')) || document.body; if (ac.list.parentNode !== container) container.appendChild(ac.list); btEpTopLayer(ac.list); let origin = { left: 0, top: 0 }; if (container !== document.body) { // A transformed dialog is the containing block of a fixed element: measure where 0,0 lands. ac.list.style.left = '0px'; ac.list.style.top = '0px'; const at = ac.list.getBoundingClientRect(); origin = { left: at.left, top: at.top }; } const rect = this.caretRect(context); const height = 36 + items.length * 36 + 8 + 2; const place = EmojiPicker.placeCard({ left: rect.left - 20, right: rect.right, top: rect.top, bottom: rect.bottom }, null, BT_EP_AC_W, height, window.innerWidth, window.innerHeight, 120, 8, 6); ac.list.style.left = (place.left - origin.left) + 'px'; ac.list.style.top = (place.top - origin.top) + 'px'; ac.list.style.width = place.width + 'px'; ac.list.style.maxHeight = place.height + 'px'; ac.list.style.setProperty('--ep-dy', place.side === 'above' ? '4px' : '-4px'); if (btEpIsEditable(target) && target.closest) { target.setAttribute('aria-activedescendant', ac.list.id + '-0'); } // X's post composer has its own `:emoji` typeahead: where it shows up, step aside for good. setTimeout(() => { const native = document.querySelector('[id^="typeaheadDropdown"]'); if (native && native.getBoundingClientRect().height > 0 && this.autocomplete && this.autocomplete.target === target) { this.nativeEditors.add(target); this.closeAutocomplete(); } }, 180); } selectAutocomplete(index) { const ac = this.autocomplete; if (!ac || !ac.options[index] || index === ac.active) return; ac.options[ac.active]?.setAttribute('aria-selected', 'false'); ac.active = index; const current = ac.options[index]; current.setAttribute('aria-selected', 'true'); current.scrollIntoView({ block: 'nearest' }); if (ac.target.setAttribute) ac.target.setAttribute('aria-activedescendant', current.id); } moveAutocomplete(delta) { const ac = this.autocomplete; if (!ac || !ac.items.length) return; this.selectAutocomplete((ac.active + delta + ac.items.length) % ac.items.length); } /** Replaces the `:trigger` with option `index`; false when the caret left the trigger (nothing inserted). */ chooseAutocomplete(index) { const ac = this.autocomplete; if (!ac || !ac.items[index]) return false; const target = ac.target; const context = this.caretContext(target); this.closeAutocomplete(); if (!context) return false; // Re-read the trigger at the caret now: it may have moved since the list opened. const length = EmojiPicker.replaceLength(context.text, ac.start); if (length == null) return false; this.insertEntry(ac.items[index], context, length); return true; } /** Closes the list once the caret leaves the `:trigger` it was opened for. */ onSelectionChange() { const ac = this.autocomplete; if (!ac) return; const context = this.caretContext(ac.target); if (!context || EmojiPicker.replaceLength(context.text, ac.start) == null) this.closeAutocomplete(); } closeAutocomplete() { const ac = this.autocomplete; if (!ac) return; ac.list.remove(); // X's own typeahead uses this attribute too: only clear it when it points at our list. const current = ac.target && ac.target.getAttribute ? ac.target.getAttribute('aria-activedescendant') : null; if (current && current.startsWith(ac.list.id)) ac.target.removeAttribute('aria-activedescendant'); this.autocomplete = null; } // ---- Global shortcut, X's emoji button, our toolbar button ---- onKeyDown(event) { // Keys that commit an IME composition belong to the IME. if (event.isComposing || event.keyCode === 229) return; if (this.autocomplete) { const ac = this.autocomplete; // Only keys typed in the editor the list belongs to, without modifiers (Ctrl+Enter still posts). const inEditor = event.target === ac.target || (ac.target.contains && ac.target.contains(event.target)); const plain = !event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey; const listKey = ['Escape', 'ArrowDown', 'ArrowUp', 'Enter', 'Tab'].includes(event.key); if (['ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown'].includes(event.key) || (listKey && (!inEditor || !plain))) { this.closeAutocomplete(); return; } if (event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); this.closeAutocomplete(); return; } if (event.key === 'ArrowDown') { event.preventDefault(); event.stopPropagation(); this.moveAutocomplete(1); return; } if (event.key === 'ArrowUp') { event.preventDefault(); event.stopPropagation(); this.moveAutocomplete(-1); return; } if (event.key === 'Enter' || event.key === 'Tab') { // The key is taken only when an emoji actually went in. if (this.chooseAutocomplete(ac.active)) { event.preventDefault(); event.stopPropagation(); } return; } return; } if (this.picker && !this.picker.host.isConnected) this.closePicker({ immediate: true }); if (this.picker) { // Keys typed inside the picker are handled (and kept from X) by the picker itself. if (event.composedPath().includes(this.picker.host)) return; if (event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); this.closePicker({ restoreFocus: true }); return; } } if (!event.ctrlKey || !event.shiftKey || event.altKey || event.metaKey) return; // event.code works on every keyboard layout; the key is only taken when a picker opens or closes. if (event.code !== 'KeyE') return; if (!this.togglePicker()) return; event.preventDefault(); event.stopPropagation(); } onPointerDown(event) { const path = event.composedPath ? event.composedPath() : [event.target]; if (this.autocomplete && !path.includes(this.autocomplete.list) && event.target !== this.autocomplete.target) { this.closeAutocomplete(); } const p = this.picker; if (p && !path.includes(p.host) && !(p.anchor !== p.editor && path.includes(p.anchor))) this.closePicker(); } /** X's emoji button, when it opens this picker: keep focus (and the caret) in the editor. */ onXButtonMouseDown(event) { if (!this.replaceX) return; const button = event.target.closest && event.target.closest('button, [role="button"]'); if (button && EmojiPicker.isXEmojiButton(button)) event.preventDefault(); } /** X's emoji button opens this picker instead of X's (setting, on by default). */ onXButtonClick(event) { if (!this.replaceX) return; const button = event.target.closest && event.target.closest('button, [role="button"]'); if (!button || !EmojiPicker.isXEmojiButton(button)) return; event.preventDefault(); event.stopImmediatePropagation(); this.toggleFromButton(button, button.closest('[data-testid="toolBar"]')); } toggleFromButton(button, toolbar) { if (this.picker && this.picker.anchor === button) { this.closePicker({ restoreFocus: true }); return; } const editor = this.editorNear(toolbar); if (editor) this.openPicker(button, editor); } /** X's emoji button in this toolbar row, if it has one. */ xEmojiButton(toolbar) { for (const el of toolbar.querySelectorAll('button[aria-label], [role="button"][aria-label]')) { if (EmojiPicker.isXEmojiButton(el)) return el; } return null; } /** Our button takes the color X gives its own toolbar icons (the theme's accent, or X's). */ matchXColor(button) { const row = button.closest('[role="tablist"]') || button.parentElement?.parentElement; const icon = row && [...row.querySelectorAll('svg')].find(svg => !button.contains(svg)); if (!icon) return; const color = getComputedStyle(icon).color; if (color) button.style.color = color; } mountButton(toolbar) { const xButton = this.xEmojiButton(toolbar); const existing = toolbar.querySelector('.bt-ep-slot'); // One picker, one button: when X's button opens ours, ours is not added next to it. if (this.replaceX && xButton) { existing?.remove(); return; } if (existing) return; const row = toolbar.querySelector('[role="tablist"]') || toolbar.firstElementChild || toolbar; const slot = btEpEl('div', 'bt-ep-slot', { role: 'presentation' }); const button = btEpEl('button', 'bt-ep-button', { type: 'button', title: t('buttonTitle'), 'aria-label': t('picker'), 'aria-haspopup': 'dialog', 'aria-expanded': 'false' }); button.appendChild(btEpIcon('smileys')); button.addEventListener('mousedown', event => event.preventDefault()); button.addEventListener('click', event => { event.preventDefault(); event.stopPropagation(); this.toggleFromButton(button, toolbar); }); slot.appendChild(button); // Right after X's own emoji button (its presentation wrapper), else at the end of the row. let after = xButton; while (after && after.parentElement && after.parentElement !== row) after = after.parentElement; if (after && after.parentElement === row) after.after(slot); else row.appendChild(slot); this.matchXColor(button); // Removed on stop(), like everything else this plugin adds. this.scope.add(slot); } remountButtons() { for (const slot of document.querySelectorAll('.bt-ep-slot')) slot.remove(); for (const toolbar of document.querySelectorAll('[data-testid="toolBar"]')) this.mountButton(toolbar); } setReplaceX(on) { this.replaceX = !!on; this.store.set('replaceX', this.replaceX); this.remountButtons(); } /** The picker's settings (the gear in the preview bar), in the app's themed dialog. */ openSettings() { const ui = this.api.ui; if (!ui || typeof ui.dialog !== 'function' || typeof ui.h !== 'function') return; this.closePicker({ restoreFocus: true, immediate: true }); const h = ui.h; const replace = h('input', { type: 'checkbox', checked: this.replaceX }); this.settings = ui.dialog({ title: t('title'), content: body => body.append( h('label', { class: 'wb-check' }, replace, h('span', null, t('replaceX'), h('small', null, t('replaceXHint')))), ), actions: [ { label: t('cancel') }, { label: t('save'), tone: 'primary', default: true, onClick: () => { this.setReplaceX(replace.checked); } }, ], onClose: () => { this.settings = null; }, }); } /** The single composer/reply/DM text box that shares this toolbar's compose area, if exactly one. */ editorNear(toolbar) { let area = toolbar && toolbar.parentElement; for (let depth = 0; area && depth < 40; depth += 1, area = area.parentElement) { const editors = area.querySelectorAll(BT_EP_EDITABLE); if (editors.length === 1) return editors[0]; // Several (a thread being written): the one that has the caret. if (editors.length > 1) { const anchor = window.getSelection()?.anchorNode; return anchor ? Array.from(editors).find(editor => editor.contains(anchor)) || null : null; } if (area.matches && area.matches('main,body,[data-testid="primaryColumn"]')) return null; } return document.activeElement && btEpIsEditable(document.activeElement) ? document.activeElement : null; } /** The emoji button (X's or ours) of the toolbar that belongs to `editor`, so Ctrl+Shift+E opens in the same place. */ buttonFor(editor) { let area = editor.parentElement; for (let depth = 0; area && depth < 24; depth += 1, area = area.parentElement) { const toolbars = area.querySelectorAll('[data-testid="toolBar"]'); if (toolbars.length) { if (toolbars.length !== 1 || this.editorNear(toolbars[0]) !== editor) return null; return toolbars[0].querySelector('.bt-ep-button') || this.xEmojiButton(toolbars[0]); } if (area.matches && area.matches('main,body')) return null; } return null; } /** Opens or closes the picker; false when there was nothing to do (no picker, no editor). */ togglePicker() { if (this.picker) { this.closePicker({ restoreFocus: true }); return true; } const active = document.activeElement; const editor = btEpIsEditable(active) ? active : this.editorNear(document.querySelector('[data-testid="toolBar"]') || document.body); if (!editor) return false; const button = this.buttonFor(editor); this.openPicker(button || editor, editor); return true; } // ---- Full picker ---- /** Sections for the list: search results, or Frequently used (when there is history) + categories. */ buildSections(query, cols = BT_EP_COLS) { if (query) return [{ id: 'search', title: t('results'), icon: 'search', entries: EmojiPicker.search(query) }]; const sections = []; const frequent = this.frequentEntries(cols * 2); if (frequent.length) sections.push({ id: 'frequent', title: t('frequent'), icon: 'frequent', entries: frequent }); EMOJI_DATA.categories.forEach((title, i) => sections.push({ id: 'cat' + i, title: t('cat' + i), icon: BT_EP_CAT_ICONS[i], entries: btEpByCat()[i] })); return sections; } /** * One hidden open and close once the glyphs are warm, so the first real open finds the card's * styles, fonts and icons ready too. Skipped while anything of ours is on screen. */ /** * The card's styles for `shadow`. Constructed sheets, built once and adopted by every picker this * page opens: parsed once instead of per open, not subject to X's CSP, and they stay with the * card wherever its host goes (into X's modal, the top layer). The card CSS, then the atlas rules * (one per sheet, shared, grown as sheets arrive). Without constructable sheets: