emoji sur les roles

This commit is contained in:
Flatlogic Bot 2026-02-15 19:21:41 +00:00
parent 652014e524
commit 98888d0370
2 changed files with 216 additions and 31 deletions

View File

@ -27,8 +27,103 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
// Emoji list for reactions
const EMOJIS = ['๐Ÿ‘', 'โค๏ธ', '๐Ÿ˜‚', '๐Ÿ˜ฎ', '๐Ÿ˜ข', '๐Ÿ”ฅ', 'โœ…', '๐Ÿš€', 'โ“', '๐Ÿ’ก', '๐Ÿ“Œ', '๐Ÿ’ฏ'];
// Emoji list categorized
const EMOJI_CATEGORIES = {
'Smileys': ['๐Ÿ˜€', '๐Ÿ˜ƒ', '๐Ÿ˜„', '๐Ÿ˜', '๐Ÿ˜†', '๐Ÿ˜…', '๐Ÿคฃ', '๐Ÿ˜‚', '๐Ÿ™‚', '๐Ÿ™ƒ', '๐Ÿ˜‰', '๐Ÿ˜Š', '๐Ÿ˜‡', '๐Ÿฅฐ', '๐Ÿ˜', '๐Ÿคฉ', '๐Ÿ˜˜', '๐Ÿ˜—', '๐Ÿ˜š', '๐Ÿ˜™', '๐Ÿ˜‹', '๐Ÿ˜›', '๐Ÿ˜œ', '๐Ÿคช', '๐Ÿ˜', '๐Ÿค‘', '๐Ÿค—', '๐Ÿคญ', '๐Ÿคซ', '๐Ÿค”', '๐Ÿค', '๐Ÿคจ', '๐Ÿ˜', '๐Ÿ˜‘', '๐Ÿ˜ถ', '๐Ÿ˜', '๐Ÿ˜’', '๐Ÿ™„', '๐Ÿ˜ฌ', '๐Ÿคฅ', '๐Ÿ˜Œ', '๐Ÿ˜”', '๐Ÿ˜ช', '๐Ÿคค', '๐Ÿ˜ด', '๐Ÿ˜ท', '๐Ÿค’', '๐Ÿค•', '๐Ÿคข', '๐Ÿคฎ', '๐Ÿคง', '๐Ÿฅต', '๐Ÿฅถ', '๐Ÿฅด', '๐Ÿ˜ต', '๐Ÿคฏ', '๐Ÿค ', '๐Ÿฅณ', '๐Ÿ˜Ž', '๐Ÿค“', '๐Ÿง', '๐Ÿ˜•', '๐Ÿ˜Ÿ', '๐Ÿ™', 'โ˜น๏ธ', '๐Ÿ˜ฎ', '๐Ÿ˜ฏ', '๐Ÿ˜ฒ', '๐Ÿ˜ณ', '๐Ÿฅบ', '๐Ÿ˜ฆ', '๐Ÿ˜ง', '๐Ÿ˜จ', '๐Ÿ˜ฐ', '๐Ÿ˜ฅ', '๐Ÿ˜ข', '๐Ÿ˜ญ', '๐Ÿ˜ฑ', '๐Ÿ˜–', '๐Ÿ˜ฃ', '๐Ÿ˜ž', '๐Ÿ˜“', '๐Ÿ˜ฉ', '๐Ÿ˜ซ', '๐Ÿฅฑ', '๐Ÿ˜ค', '๐Ÿ˜ก', '๐Ÿ˜ ', '๐Ÿคฌ', '๐Ÿ˜ˆ', '๐Ÿ‘ฟ', '๐Ÿ‘น', '๐Ÿ‘บ', '๐Ÿ’€', 'โ˜ ๏ธ', '๐Ÿ’ฉ', '๐Ÿคก', '๐Ÿ‘น', '๐Ÿ‘บ', '๐Ÿ‘ป', '๐Ÿ‘ฝ', '๐Ÿ‘พ', '๐Ÿค–', '๐Ÿ˜บ', '๐Ÿ˜ธ', '๐Ÿ˜ป', '๐Ÿ˜ผ', '๐Ÿ˜ฝ', '๐Ÿ™€', '๐Ÿ˜ฟ', '๐Ÿ˜พ'],
'People': ['๐Ÿ‘‹', '๐Ÿคš', '๐Ÿ–๏ธ', 'โœ‹', '๐Ÿ––', '๐Ÿ‘Œ', '๐Ÿค', 'โœŒ๏ธ', '๐Ÿคž', '๐ŸคŸ', '๐Ÿค˜', '๐Ÿค™', '๐Ÿ‘ˆ', '๐Ÿ‘‰', '๐Ÿ‘†', '๐Ÿ–•', '๐Ÿ‘‡', 'โ˜๏ธ', '๐Ÿ‘', '๐Ÿ‘Ž', 'โœŠ', '๐Ÿ‘Š', '๐Ÿค›', '๐Ÿคœ', '๐Ÿ‘', '๐Ÿ™Œ', '๐Ÿ‘', '๐Ÿคฒ', '๐Ÿค', '๐Ÿ™', 'โœ๏ธ', '๐Ÿ’…', '๐Ÿคณ', '๐Ÿ’ช', '๐Ÿฆพ', '๐Ÿฆต', '๐Ÿฆฟ', '๐Ÿฆถ', '๐Ÿ‘‚', '๐Ÿฆป', '๐Ÿ‘ƒ', '๐Ÿง ', '๐Ÿฆท', '๐Ÿฆด', '๐Ÿ‘€', '๐Ÿ‘๏ธ', '๐Ÿ‘…', '๐Ÿ‘„', '๐Ÿ‘ถ', '๐Ÿง’', '๐Ÿ‘ฆ', '๐Ÿ‘ง', '๐Ÿง‘', '๐Ÿ‘ฑ', '๐Ÿ‘จ', '๐Ÿง”', '๐Ÿ‘ฉ', '๐Ÿง“', '๐Ÿ‘ด', '๐Ÿ‘ต', '๐Ÿ‘ฎ', '๐Ÿ•ต๏ธ', '๐Ÿ’‚', '๐Ÿ‘ท', '๐Ÿคด', '๐Ÿ‘ธ', '๐Ÿ‘ณ', '๐Ÿ‘ฒ', '๐Ÿง•', '๐Ÿคต', '๐Ÿ‘ฐ', '๐Ÿคฐ', '๐Ÿคฑ', '๐Ÿ‘ผ', '๐ŸŽ…', '๐Ÿคถ', '๐Ÿฆธ', '๐Ÿฆน', '๐Ÿง™', '๐Ÿงš', '๐Ÿง›', '๐Ÿงœ', '๐Ÿง', '๐Ÿงž', '๐ŸงŸ', '๐Ÿ’†', '๐Ÿ’‡', '๐Ÿšถ', '๐Ÿƒ', '๐Ÿ’ƒ', '๐Ÿ•บ', '๐Ÿ•ด๏ธ', '๐Ÿ‘ฏ', '๐Ÿง–', '๐Ÿง—'],
'Animals': ['๐Ÿถ', '๐Ÿฑ', '๐Ÿญ', '๐Ÿน', '๐Ÿฐ', '๐ŸฆŠ', '๐Ÿป', '๐Ÿผ', '๐Ÿจ', '๐Ÿฏ', '๐Ÿฆ', '๐Ÿฎ', '๐Ÿท', '๐Ÿฝ', '๐Ÿธ', '๐Ÿต', '๐Ÿ™ˆ', '๐Ÿ™‰', '๐Ÿ™Š', '๐Ÿ’', '๐Ÿ”', '๐Ÿง', '๐Ÿฆ', '๐Ÿค', '๐Ÿฃ', '๐Ÿฅ', '๐Ÿฆ†', '๐Ÿฆ…', '๐Ÿฆ‰', '๐Ÿฆ‡', '๐Ÿบ', '๐Ÿ—', '๐Ÿด', '๐Ÿฆ„', '๐Ÿ', '๐Ÿ›', '๐Ÿฆ‹', '๐ŸŒ', '๐Ÿž', '๐Ÿœ', '๐ŸฆŸ', '๐Ÿฆ—', '๐Ÿ•ท๏ธ', '๐Ÿ•ธ๏ธ', '่ ', '๐Ÿข', '๐Ÿ', '๐ŸฆŽ', '๐Ÿฆ–', '๐Ÿฆ•', '๐Ÿ™', '๐Ÿฆ‘', '๐Ÿฆ', '๐Ÿฆž', '๐Ÿฆ€', '๐Ÿก', '๐Ÿ ', '๐ŸŸ', '๐Ÿฌ', '๐Ÿณ', '๐Ÿ‹', '๐Ÿฆˆ', '๐ŸŠ', '๐Ÿ…', '๐Ÿ†', '๐Ÿฆ“', '๐Ÿฆ', '๐Ÿฆง', '๐Ÿ˜', '๐Ÿฆ›', '๐Ÿฆ', '๐Ÿช', '๐Ÿซ', '๐Ÿฆ’', '๐Ÿฆ˜', '๐Ÿƒ', '๐Ÿ‚', '๐Ÿ„', '๐ŸŽ', '๐Ÿ–', '๐Ÿ', '๐Ÿ‘', '๐Ÿ', '๐ŸฆŒ', '๐Ÿ•', '๐Ÿฉ', '๐Ÿฆฎ', '๐Ÿˆ', '๐Ÿ“', '๐Ÿฆƒ', '๐Ÿฆš', '๐Ÿฆœ', '๐Ÿฆข', '๐Ÿฆฉ', '๐Ÿ•Š๏ธ', '๐Ÿ‡', '๐Ÿฆ', '๐Ÿฆจ', '๐Ÿฆก', '๐Ÿฆฆ', '๐Ÿฆฅ', '๐Ÿ', '๐Ÿ€', '๐Ÿฟ๏ธ', '๐Ÿฆ”'],
'Nature': ['๐ŸŒต', '๐ŸŽ„', '๐ŸŒฒ', '๐ŸŒณ', '๐ŸŒด', '๐ŸŒฑ', '๐ŸŒฟ', 'โ˜˜๏ธ', '๐Ÿ€', '๐ŸŽ', '๐ŸŽ‹', '๐Ÿƒ', '๐Ÿ‚', '๐Ÿ', '๐Ÿ„', '๐Ÿš', '๐ŸŒพ', '๐Ÿ’', '๐ŸŒท', '๐ŸŒน', '๐Ÿฅ€', '๐ŸŒบ', '๐ŸŒธ', '๐ŸŒผ', '๐ŸŒป', '๐ŸŒž', '๐ŸŒ', '๐ŸŒ›', '๐ŸŒœ', '๐ŸŒš', '๐ŸŒ•', '๐ŸŒ–', '๐ŸŒ—', '๐ŸŒ˜', '๐ŸŒ‘', '๐ŸŒ’', '๐ŸŒ“', '๐ŸŒ”', '๐ŸŒ™', '๐ŸŒŽ', '๐ŸŒ', '๐ŸŒ', '๐Ÿช', '๐Ÿ’ซ', 'โญ๏ธ', '๐ŸŒŸ', 'โœจ', 'โšก๏ธ', 'โ˜„๏ธ', '๐Ÿ’ฅ', '๐Ÿ”ฅ', '๐ŸŒช๏ธ', '๐ŸŒˆ', 'โ˜€๏ธ', '๐ŸŒค๏ธ', 'โ›…๏ธ', '๐ŸŒฅ๏ธ', 'โ˜๏ธ', '๐ŸŒฆ๏ธ', '๐ŸŒง๏ธ', '๐ŸŒจ๏ธ', '๐ŸŒฉ๏ธ', 'โ„๏ธ', 'โ˜ƒ๏ธ', 'โ›„๏ธ', '๐ŸŒฌ๏ธ', '๐Ÿ’จ', '๐Ÿ’ง', '๐Ÿ’ฆ', 'โ˜”๏ธ', 'โ˜‚๏ธ', '๐ŸŒŠ', '๐ŸŒซ๏ธ'],
'Food': ['๐Ÿ', '๐ŸŽ', '๐Ÿ', '๐ŸŠ', '๐Ÿ‹', ' BANANA', '๐Ÿ‰', '๐Ÿ‡', '๐Ÿ“', '๐Ÿˆ', '๐Ÿ’', '๐Ÿ‘', '๐Ÿฅญ', '๐Ÿ', '๐Ÿฅฅ', '๐Ÿฅ', '๐Ÿ…', '๐Ÿ†', '๐Ÿฅ‘', '๐Ÿฅฆ', '๐Ÿฅฌ', '๐Ÿฅ’', '๐ŸŒฝ', '๐Ÿฅ•', '๐Ÿง„', '๐Ÿฅ”', '๐Ÿ ', '๐Ÿฅ', '๐Ÿฅฏ', '๐Ÿž', '๐Ÿฅ–', '๐Ÿฅจ', '๐Ÿง€', '๐Ÿฅš', '๐Ÿณ', '๐Ÿงˆ', '๐Ÿฅž', ' waffle', '๐Ÿฅ“', '๐Ÿฅฉ', '๐Ÿ—', '๐Ÿ–', '๐Ÿฆด', '๐ŸŒญ', '๐Ÿ”', '๐ŸŸ', '๐Ÿ•', '๐Ÿฅช', '๐Ÿฅ™', '๐Ÿง†', '๐ŸŒฎ', '๐ŸŒฏ', '๐Ÿฅ—', '๐Ÿฅ˜', '๐Ÿ', '๐Ÿœ', '๐Ÿฒ', '๐Ÿ›', ' sushi', ' Bento', ' Dumpling', ' Oyster', '๐Ÿค', ' Rice Ball', ' Rice', ' Rice Cracker', '๐Ÿฅ', '๐Ÿฅ ', '๐Ÿฅฎ', '๐Ÿข', '๐Ÿก', '๐Ÿง', '๐Ÿจ', '๐Ÿฆ', '๐Ÿฅง', '๐Ÿง', '๐Ÿฐ', '๐ŸŽ‚', '๐Ÿฎ', '๐Ÿญ', '๐Ÿฌ', '๐Ÿซ', ' popcorn', '๐Ÿฉ', '๐Ÿช', '๐ŸŒฐ', ' peanuts', '๐Ÿฏ', '๐Ÿฅ›', 'โ˜•๏ธ', '๐Ÿต', '๐Ÿฅค', '๐Ÿถ', '๐Ÿบ', '๐Ÿป', '๐Ÿฅ‚', '๐Ÿท', '๐Ÿฅƒ', '๐Ÿธ', '๐Ÿน', '๐Ÿง‰', '๐Ÿพ', '๐ŸงŠ', '๐Ÿฅ„', '๐Ÿด', '๐Ÿฝ๏ธ', '๐Ÿฅฃ', '๐Ÿฅก'],
'Travel': ['๐Ÿš—', '๐Ÿš•', '๐Ÿš™', '๐ŸšŒ', '๐ŸšŽ', '๐ŸŽ๏ธ', '๐Ÿš“', '๐Ÿš‘', '๐Ÿš’', '๐Ÿš', '๐Ÿšš', '๐Ÿš›', '๐Ÿšœ', '๐Ÿ›ต', '๐Ÿšฒ', '๐Ÿ›ด', '๐Ÿš', '๐Ÿ›ฃ๏ธ', '๐Ÿ›ค๏ธ', 'โ›ฝ๏ธ', '๐Ÿšจ', '๐Ÿšฅ', '๐Ÿšฆ', '๐Ÿšง', 'โš“๏ธ', 'โ›ต๏ธ', '๐Ÿšค', '๐Ÿ›ณ๏ธ', 'โ›ด๏ธ', '๐Ÿšข', 'โœˆ๏ธ', '๐Ÿ›ซ', '๐Ÿ›ฌ', '๐Ÿ’บ', '๐Ÿš', '๐ŸšŸ', '๐Ÿš ', '๐Ÿšก', '๐Ÿš€', '๐Ÿ›ธ', '๐Ÿ›ฐ๏ธ', 'โŒ›๏ธ', 'โณ', 'โŒš๏ธ', 'โฐ', 'โฑ๏ธ', 'โฒ๏ธ', '๐Ÿ•ฐ๏ธ', '๐ŸŒก๏ธ', 'โ˜€๏ธ', '๐Ÿช', '๐ŸŒŸ', 'โ˜๏ธ', 'โ›…๏ธ', 'โ›ˆ๏ธ', '๐ŸŒˆ', 'โ›ฐ๏ธ', '๐Ÿ”๏ธ', '๐Ÿ—ป', '๐ŸŒ‹', '๐Ÿœ๏ธ', '๐Ÿ•๏ธ', 'โ›บ๏ธ', '๐Ÿ ', '๐Ÿก', '๐Ÿข', '๐Ÿฃ', '๐Ÿค', '๐Ÿฅ', '๐Ÿฆ', '๐Ÿจ', '๐Ÿฉ', '๐Ÿช', '๐Ÿซ', '๐Ÿฌ', '๐Ÿญ', '๐Ÿฏ', '๐Ÿฐ', '๐Ÿ’’', '๐Ÿ—ผ', '๐Ÿ—ฝ', 'โ›ช๏ธ', '๐Ÿ•Œ', '๐Ÿ•', 'โ›ฉ๏ธ', '๐Ÿ•‹', 'โ›ฒ๏ธ', '๐ŸŒ', '๐ŸŒƒ', '๐Ÿ™๏ธ', '๐ŸŒ„', '๐ŸŒ…', '๐ŸŒ†', '๐ŸŒ‡', '๐ŸŒ‰', '๐ŸŽ ', '๐ŸŽก', '๐ŸŽข', '๐Ÿš‚', '๐Ÿšƒ', '๐Ÿš„', '๐Ÿš…', '๐Ÿš†', '๐Ÿš‡', '๐Ÿšˆ', '๐Ÿš‰', '๐ŸšŠ', '๐Ÿš', '๐Ÿšž', '๐Ÿš‹'],
'Activities': ['โšฝ๏ธ', '๐Ÿ€', '๐Ÿˆ', 'โšพ๏ธ', '๐ŸฅŽ', '๐ŸŽพ', '๐Ÿ', '๐Ÿ‰', '๐ŸŽฑ', '๐Ÿ“', '๐Ÿธ', '๐Ÿฅ…', ' hockey', ' field hockey', ' cricket', 'โ›ณ๏ธ', '๐Ÿน', '๐ŸŽฃ', ' boxing', '๐Ÿฅ‹', ' skateboard', '๐Ÿ›ท', 'โ›ธ๏ธ', '๐ŸฅŒ', '๐ŸŽฟ', 'โ›ท๏ธ', '๐Ÿ‚', '๐Ÿ‹๏ธ', ' fencing', '๐Ÿคผ', ' gymnastics', ' basketball player', '๐Ÿคฝ', ' handball', ' juggle', '๐Ÿง˜', '๐Ÿ‡', ' rowing', ' swimming', '๐Ÿšด', '๐Ÿšต', '๐Ÿง—', '๐ŸŽ–๏ธ', '๐Ÿ†', '๐Ÿ…', '๐Ÿฅ‡', '๐Ÿฅˆ', '๐Ÿฅ‰', '๐ŸŽซ', '๐ŸŽŸ๏ธ', '๐ŸŽญ', '๐ŸŽจ', '๐ŸŽฌ', '๐ŸŽค', '๐ŸŽง', '๐ŸŽผ', '๐ŸŽน', '๐Ÿฅ', '๐ŸŽท', '๐ŸŽบ', '๐ŸŽธ', '๐Ÿช•', '๐ŸŽป', '๐ŸŽฒ', 'โ™Ÿ๏ธ', '๐ŸŽฏ', '๐ŸŽณ', '๐ŸŽฎ', '๐ŸŽฐ', '๐Ÿงฉ'],
'Objects': ['โŒš๏ธ', '๐Ÿ“ฑ', '๐Ÿ“ฒ', '๐Ÿ’ป', 'โŒจ๏ธ', '๐Ÿ–ฑ๏ธ', '๐Ÿ–ฒ๏ธ', '๐Ÿ•น๏ธ', '๐Ÿ—œ๏ธ', '๐Ÿ’ฝ', '๐Ÿ’พ', '๐Ÿ’ฟ', '๐ŸŽž๏ธ', '๐Ÿ“ท', '๐Ÿ“ธ', '๐Ÿ“น', '๐Ÿ“ผ', '๐Ÿ”', '๐Ÿ”Ž', '๐Ÿ•ฏ๏ธ', '๐Ÿ’ก', '๐Ÿ”ฆ', '๐Ÿฎ', '๐Ÿช”', '๐Ÿ“”', '๐Ÿ“•', '๐Ÿ“–', '๐Ÿ“—', '๐Ÿ“˜', '๐Ÿ“™', '๐Ÿ“š', '๐Ÿ““', '๐Ÿ“’', '๐Ÿ“ƒ', '๐Ÿ“œ', '๐Ÿ“„', '๐Ÿ“ฐ', '๐Ÿ—ž๏ธ', '๐Ÿ“‘', '๐Ÿ”–', '๐Ÿท๏ธ', '๐Ÿ’ฐ', '๐Ÿ’ด', '๐Ÿ’ต', '๐Ÿ’ถ', '๐Ÿ’ท', '๐Ÿ’ธ', '๐Ÿ’ณ', '๐Ÿงพ', '๐Ÿ’น', 'โœ‰๏ธ', '๐Ÿ“ง', '๐Ÿ“จ', '๐Ÿ“ฉ', '๐Ÿ“ค', '๐Ÿ“ฅ', '๐Ÿ“ฆ', '๐Ÿ“ซ', '๐Ÿ“ฉ', '๐Ÿ“ฌ', '๐Ÿ“ญ', '๐Ÿ“ฎ', '๐Ÿ—ณ๏ธ', 'โœ๏ธ', 'โœ’๏ธ', '๐Ÿ–‹๏ธ', '๐Ÿ–Š๏ธ', '๐Ÿ–Œ๏ธ', '๐Ÿ–๏ธ', '๐Ÿ“', '๐Ÿ’ผ', '๐Ÿ“', '๐Ÿ“‚', '๐Ÿ—‚๏ธ', '๐Ÿ“…', '๐Ÿ“†', '๐Ÿ—’๏ธ', '๐Ÿ—“๏ธ', '๐Ÿ“‡', '๐Ÿ“ˆ', '๐Ÿ“‰', '๐Ÿ“Š', '๐Ÿ“‹', '๐Ÿ“Œ', '๐Ÿ“', '๐Ÿ“Ž', '๐Ÿ–‡๏ธ', '๐Ÿ“', '๐Ÿ“', 'โœ‚๏ธ', '๐Ÿ—ƒ๏ธ', '๐Ÿ—„๏ธ', '๐Ÿ—‘๏ธ', '๐Ÿ”’', '๐Ÿ”“', '๐Ÿ”', '๐Ÿ”', '๐Ÿ”‘', '๐Ÿ—๏ธ', '๐Ÿ”จ', '๐Ÿช“', 'โ›๏ธ', 'โš’๏ธ', '๐Ÿ› ๏ธ', '๐Ÿ—ก๏ธ', 'โš”๏ธ', '๐Ÿ”ซ', '๐Ÿ›ก๏ธ', '๐Ÿ”ง', '๐Ÿ”ฉ', 'โš™๏ธ', '๐Ÿ—œ๏ธ', 'โš–๏ธ', '๐Ÿฆฏ', '๐Ÿ”—', 'โ›“๏ธ', '๐Ÿงฐ', '๐Ÿงฒ', 'โš—๏ธ', '๐Ÿงช', '๐Ÿงซ', '๐Ÿงฌ', '๐Ÿ”ฌ', '๐Ÿ”ญ', '๐Ÿ“ก', '๐Ÿ’‰', '๐Ÿฉธ', '๐Ÿ’Š', '๐Ÿฉน', '๐Ÿฉบ', '๐Ÿšช', '๐Ÿ›๏ธ', '๐Ÿ›‹๏ธ', '๐Ÿช‘', '๐Ÿšฝ', '๐Ÿšฟ', '๐Ÿ›€', '๐Ÿช’', '๐Ÿงด', '๐Ÿงท', '๐Ÿงน', '๐Ÿงบ', '๐Ÿงป', '๐Ÿงผ', '๐Ÿงฏ', '๐Ÿ›’', '๐Ÿšฌ', 'โšฐ๏ธ', 'โšฑ๏ธ', '๐Ÿ—ฟ'],
'Symbols': ['๐Ÿ’˜', '๐Ÿ’', '๐Ÿ’–', '๐Ÿ’—', '๐Ÿ’“', '๐Ÿ’ž', '๐Ÿ’•', '๐Ÿ’Ÿ', 'โฃ๏ธ', '๐Ÿ’”', 'โค๏ธ', '๐Ÿงก', '๐Ÿ’›', '๐Ÿ’š', '๐Ÿ’™', '๐Ÿ’œ', '๐Ÿ–ค', '๐Ÿค', '๐ŸคŽ', '๐Ÿ’ฏ', '๐Ÿ’ข', '๐Ÿ’ฅ', '๐Ÿ’ซ', '๐Ÿ’ฆ', '๐Ÿ’จ', '๐Ÿ•ณ๏ธ', '๐Ÿ’ฃ', '๐Ÿ’ฌ', '๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ', '๐Ÿ—จ๏ธ', '๐Ÿ—ฏ๏ธ', '๐Ÿ’ญ', '๐Ÿ’ค', '๐ŸŒ', 'โ™ ๏ธ', 'โ™ฅ๏ธ', 'โ™ฆ๏ธ', 'โ™ฃ๏ธ', '๐Ÿƒ', '๐Ÿ€„๏ธ', '๐ŸŽด', '๐ŸŽญ', '๐Ÿ”‡', '๐Ÿ”ˆ', '๐Ÿ”‰', '๐Ÿ”Š', '๐Ÿ“ข', '๐Ÿ“ฃ', '๐Ÿ“ฏ', '๐Ÿ””', '๐Ÿ”•', '๐ŸŽผ', '๐ŸŽต', '๐ŸŽถ', '๐Ÿ’น', '๐Ÿง', '๐Ÿšฎ', '๐Ÿšฐ', 'โ™ฟ๏ธ', '๐Ÿšน', '๐Ÿšบ', '๐Ÿšป', '๐Ÿšผ', '๐Ÿšพ', '๐Ÿ›‚', '๐Ÿ›ƒ', '๐Ÿ›„', '๐Ÿ›…', 'โš ๏ธ', '๐Ÿšธ', 'โ›”๏ธ', '๐Ÿšซ', '๐Ÿšณ', '๐Ÿšญ', '๐Ÿšฏ', '๐Ÿšฑ', '๐Ÿšท', '๐Ÿ“ต', '๐Ÿ”ž', 'โ˜ข๏ธ', 'โ˜ฃ๏ธ', 'โฌ†๏ธ', 'โ†—๏ธ', 'โžก๏ธ', 'โ†˜๏ธ', 'โฌ‡๏ธ', 'โ†™๏ธ', 'โฌ…๏ธ', 'โ†–๏ธ', 'โ†•๏ธ', 'โ†”๏ธ', 'โ†ฉ๏ธ', 'โ†ช๏ธ', 'โคด๏ธ', 'โคต๏ธ', '๐Ÿ”ƒ', '๐Ÿ”„', '๐Ÿ”™', '๐Ÿ”š', '๐Ÿ”›', '๐Ÿ”œ', '๐Ÿ”', '๐Ÿ›', 'โš›๏ธ', '๐Ÿ•‰๏ธ', 'โœก๏ธ', 'โ˜ธ๏ธ', 'โ˜ฏ๏ธ', 'โœ๏ธ', 'โ˜ฆ๏ธ', 'โ˜ช๏ธ', 'โ˜ฎ๏ธ', '๐Ÿ•Ž', '๐Ÿ”ฏ', 'โ™ˆ๏ธ', 'โ™‰๏ธ', 'โ™Š๏ธ', 'โ™‹๏ธ', 'โ™Œ๏ธ', 'โ™๏ธ', 'โ™Ž๏ธ', 'โ™๏ธ', 'โ™๏ธ', 'โ™‘๏ธ', 'โ™’๏ธ', 'โ™“๏ธ', 'โ›Ž', '๐Ÿ”€', '๐Ÿ”', '๐Ÿ”‚', 'โ–ถ๏ธ', 'โฉ', 'โญ๏ธ', 'โฏ๏ธ', 'โ—€๏ธ', 'โช', 'โฎ๏ธ', '๐Ÿ”ผ', 'โซ', '๐Ÿ”ฝ', 'โฌ', 'โธ๏ธ', 'โน๏ธ', 'โบ๏ธ', 'โ๏ธ', '๐ŸŽฆ', '๐Ÿ”…', '๐Ÿ”†', '๐Ÿ“ถ', '๐Ÿ“ณ', '๐Ÿ“ด', 'โž•', 'โž–', 'โž—', 'โœ–๏ธ', 'โ™พ๏ธ', 'โ€ผ๏ธ', 'โ‰๏ธ', 'โ“', 'โ”', 'โ•', 'โ—๏ธ', 'ใ€ฐ๏ธ', '๐Ÿ’ฑ', '๐Ÿ’ฒ', 'โš•๏ธ', 'โ™ป๏ธ', 'โšœ๏ธ', '๐Ÿ”ฑ', '๐Ÿ“›', '๐Ÿ”ฐ', 'โญ•๏ธ', 'โœ…', 'โ˜‘๏ธ', 'โœ”๏ธ', 'โœ–๏ธ', 'โŒ', 'โŽ', 'โžฐ', 'โžฟ', 'ใ€ฝ๏ธ', 'โœณ๏ธ', 'โœด๏ธ', 'โ‡๏ธ', 'โ€ผ๏ธ', '๐Ÿˆ', '๐Ÿˆ‚๏ธ', '๐Ÿˆท๏ธ', '๐Ÿˆถ', '๐Ÿˆฏ๏ธ', '๐Ÿ‰', '๐Ÿˆน', '๐Ÿˆš๏ธ', '๐Ÿˆฒ', '๐Ÿ‰‘', '๐Ÿˆธ', '๐Ÿˆด', '๐Ÿˆณ', 'ใŠ—๏ธ', 'ใŠ™๏ธ', '๐Ÿˆบ', '๐Ÿˆต', '๐Ÿ”ด', '๐ŸŸ ', '๐ŸŸก', '๐ŸŸข', '๐Ÿ”ต', '๐ŸŸฃ', '๐ŸŸค', 'โšซ๏ธ', 'โšช๏ธ', '๐ŸŸฅ', '๐ŸŸง', '๐ŸŸจ', '๐ŸŸฉ', '๐ŸŸฆ', '๐ŸŸช', '๐ŸŸซ', 'โฌ›๏ธ', 'โฌœ๏ธ'],
'Flags': ['๐Ÿ', '๐Ÿšฉ', '๐ŸŽŒ', '๐Ÿด', '๐Ÿณ๏ธ', '๐Ÿณ๏ธโ€๐ŸŒˆ', '๐Ÿณ๏ธโ€โšง๏ธ', '๐Ÿดโ€โ˜ ๏ธ', '๐Ÿ‡ฆ๐Ÿ‡ซ', '๐Ÿ‡ฆ๐Ÿ‡ฝ', '๐Ÿ‡ฆ๐Ÿ‡ฑ', '๐Ÿ‡ฉ๐Ÿ‡ฟ', '๐Ÿ‡ฆ๐Ÿ‡ฒ', '๐Ÿ‡ฆ๐Ÿ‡บ', '๐Ÿ‡ฆ๐Ÿ‡น', '๐Ÿ‡ฆ๐Ÿ‡ฟ', '๐Ÿ‡ง๐Ÿ‡ช', '๐Ÿ‡ง๐Ÿ‡ท', '๐Ÿ‡จ๐Ÿ‡ฆ', '๐Ÿ‡จ๐Ÿ‡ฑ', '๐Ÿ‡จ๐Ÿ‡ณ', '๐Ÿ‡จ๐Ÿ‡ด', '๐Ÿ‡จ๐Ÿ‡ฟ', '๐Ÿ‡ฉ๐Ÿ‡ฐ', '๐Ÿ‡ช๐Ÿ‡ฌ', '๐Ÿ‡ซ๐Ÿ‡ฎ', '๐Ÿ‡ซ๐Ÿ‡ท', '๐Ÿ‡ฉ๐Ÿ‡ช', '๐Ÿ‡ฌ๐Ÿ‡ท', '๐Ÿ‡ญ๐Ÿ‡ฐ', '๐Ÿ‡ฎ๐Ÿ‡ณ', '๐Ÿ‡ฎ๐Ÿ‡ฉ', '๐Ÿ‡ฎ๐Ÿ‡ช', '๐Ÿ‡ฎ๐Ÿ‡ฑ', '๐Ÿ‡ฎ๐Ÿ‡น', '๐Ÿ‡ฏ๐Ÿ‡ต', '๐Ÿ‡ฐ๐Ÿ‡ท', '๐Ÿ‡ฒ๐Ÿ‡ฝ', '๐Ÿ‡ณ๐Ÿ‡ฑ', '๐Ÿ‡ณ๐Ÿ‡ฟ', '๐Ÿ‡ณ๐Ÿ‡ด', '๐Ÿ‡ต๐Ÿ‡ฐ', '๐Ÿ‡ต๐Ÿ‡ญ', '๐Ÿ‡ต๐Ÿ‡ฑ', '๐Ÿ‡ต๐Ÿ‡น', '๐Ÿ‡ท๐Ÿ‡บ', '๐Ÿ‡ธ๐Ÿ‡ฆ', '๐Ÿ‡ธ๐Ÿ‡ฌ', '๐Ÿ‡ฟ๐Ÿ‡ฆ', '๐Ÿ‡ช๐Ÿ‡ธ', '๐Ÿ‡ธ๐Ÿ‡ช', '๐Ÿ‡จ๐Ÿ‡ญ', '๐Ÿ‡น๐Ÿ‡ญ', '๐Ÿ‡น๐Ÿ‡ท', '๐Ÿ‡บ๐Ÿ‡ฆ', '๐Ÿ‡ฆ๐Ÿ‡ช', '๐Ÿ‡ฌ๐Ÿ‡ง', '๐Ÿ‡บ๐Ÿ‡ธ', '๐Ÿ‡ป๐Ÿ‡ณ']
};
// Global EMOJIS list for reactions (flattened)
const EMOJIS = Object.values(EMOJI_CATEGORIES).flat();
function populateEmojiGrid(category = null, searchTerm = '') {
const roleEmojiGrid = document.getElementById('role-emoji-grid');
if (!roleEmojiGrid) return;
roleEmojiGrid.innerHTML = '';
let emojis = [];
if (searchTerm) {
emojis = EMOJIS.filter(e => e.includes(searchTerm) || searchTerm === '');
// For simple search by name, we'd need names, but since we only have the emoji characters,
// the user might search by the emoji itself or we could just show all if search is empty.
// Actually, without a mapping of emoji to names, searching is limited.
// But I'll implement it anyway so if they paste an emoji it works,
// or I can add common names if I had a mapping.
// Given the constraint, I'll just filter by character for now,
// or maybe the user just wants a way to filter the massive list.
} else {
emojis = EMOJI_CATEGORIES[category] || [];
}
emojis.forEach(emoji => {
const span = document.createElement('span');
span.textContent = emoji;
span.style.cursor = 'pointer';
span.style.fontSize = '20px';
span.style.padding = '5px';
span.style.textAlign = 'center';
span.className = 'rounded role-emoji-item';
span.onclick = () => {
document.getElementById('edit-role-icon').value = emoji;
document.getElementById('selected-role-emoji-preview').textContent = emoji;
};
roleEmojiGrid.appendChild(span);
});
}
function initEmojiCategories() {
const categoriesContainer = document.getElementById('role-emoji-categories');
const searchInput = document.getElementById('role-emoji-search');
if (!categoriesContainer) return;
categoriesContainer.innerHTML = '';
Object.keys(EMOJI_CATEGORIES).forEach((cat, index) => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'btn btn-sm btn-dark text-nowrap px-2 py-1';
btn.style.fontSize = '0.75em';
btn.textContent = cat;
if (index === 0) btn.classList.add('active', 'btn-primary');
btn.onclick = () => {
if (searchInput) searchInput.value = '';
categoriesContainer.querySelectorAll('button').forEach(b => b.classList.remove('active', 'btn-primary'));
btn.classList.add('active', 'btn-primary');
populateEmojiGrid(cat);
};
categoriesContainer.appendChild(btn);
});
if (searchInput) {
searchInput.oninput = () => {
const term = searchInput.value.trim();
if (term) {
categoriesContainer.querySelectorAll('button').forEach(b => b.classList.remove('active', 'btn-primary'));
populateEmojiGrid(null, term);
} else {
const activeCat = categoriesContainer.querySelector('button.active')?.textContent || Object.keys(EMOJI_CATEGORIES)[0];
populateEmojiGrid(activeCat);
}
};
}
// Initial load
populateEmojiGrid(Object.keys(EMOJI_CATEGORIES)[0]);
}
// Call init if elements exist
if (document.getElementById('role-emoji-grid')) {
initEmojiCategories();
}
// Scroll to bottom
scrollToBottom(true);
@ -268,20 +363,100 @@ document.addEventListener('DOMContentLoaded', () => {
function showEmojiPicker(anchor, callback) {
document.querySelector('.emoji-picker')?.remove();
const picker = document.createElement('div');
picker.className = 'emoji-picker';
EMOJIS.forEach(emoji => {
const span = document.createElement('span');
span.textContent = emoji;
span.onclick = () => {
callback(emoji);
picker.remove();
picker.className = 'emoji-picker p-0 overflow-hidden d-flex flex-column';
picker.style.width = '280px';
picker.style.height = '320px';
picker.style.backgroundColor = 'var(--bg-secondary)';
picker.style.border = '1px solid var(--bg-tertiary)';
picker.style.borderRadius = '8px';
picker.style.boxShadow = '0 8px 24px rgba(0,0,0,0.5)';
picker.style.zIndex = '2000';
const tabs = document.createElement('div');
tabs.className = 'd-flex overflow-auto border-bottom border-secondary p-1 bg-dark';
tabs.style.gap = '2px';
const searchContainer = document.createElement('div');
searchContainer.className = 'p-2 border-bottom border-secondary';
const searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = 'Search emoji...';
searchInput.className = 'form-control form-control-sm bg-dark border-secondary text-white';
searchContainer.appendChild(searchInput);
const grid = document.createElement('div');
grid.className = 'flex-grow-1 overflow-auto p-2';
grid.style.display = 'grid';
grid.style.gridTemplateColumns = 'repeat(7, 1fr)';
grid.style.gap = '2px';
const renderGrid = (cat = null, term = '') => {
grid.innerHTML = '';
let emojis = [];
if (term) {
emojis = EMOJIS.filter(e => e.includes(term));
} else {
emojis = EMOJI_CATEGORIES[cat] || [];
}
emojis.forEach(emoji => {
const span = document.createElement('span');
span.textContent = emoji;
span.style.cursor = 'pointer';
span.style.fontSize = '20px';
span.style.padding = '5px';
span.style.textAlign = 'center';
span.className = 'rounded role-emoji-item';
span.onclick = () => {
callback(emoji);
picker.remove();
};
grid.appendChild(span);
});
};
searchInput.oninput = () => {
const term = searchInput.value.trim();
if (term) {
tabs.querySelectorAll('button').forEach(b => b.classList.remove('text-primary'));
renderGrid(null, term);
} else {
const activeCat = tabs.querySelector('button.text-primary')?.textContent || Object.keys(EMOJI_CATEGORIES)[0];
renderGrid(activeCat);
}
};
Object.keys(EMOJI_CATEGORIES).forEach((cat, index) => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'btn btn-sm btn-dark text-nowrap px-2 py-1 border-0';
btn.style.fontSize = '0.7em';
btn.textContent = cat;
if (index === 0) btn.classList.add('text-primary');
btn.onclick = (e) => {
e.stopPropagation();
searchInput.value = '';
tabs.querySelectorAll('button').forEach(b => b.classList.remove('text-primary'));
btn.classList.add('text-primary');
renderGrid(cat);
};
picker.appendChild(span);
tabs.appendChild(btn);
});
picker.appendChild(tabs);
picker.appendChild(searchContainer);
picker.appendChild(grid);
document.body.appendChild(picker);
renderGrid(Object.keys(EMOJI_CATEGORIES)[0]);
const rect = anchor.getBoundingClientRect();
picker.style.top = `${rect.top - picker.offsetHeight - 5}px`;
picker.style.left = `${rect.left}px`;
let top = rect.top - picker.offsetHeight - 5;
if (top < 0) top = rect.bottom + 5;
picker.style.position = 'fixed';
picker.style.top = `${top}px`;
picker.style.left = `${Math.min(rect.left, window.innerWidth - 300)}px`;
}
function updateReactionUI(messageId, reactions) {
@ -436,7 +611,7 @@ document.addEventListener('DOMContentLoaded', () => {
<div style="flex: 1;">
<div class="message-author" style="font-size: 0.85em; ${authorStyle}">
${escapeHTML(msg.username)}
${msg.role_icon ? `<img src="${msg.role_icon}" class="role-icon ms-1" style="width: 16px; height: 16px; vertical-align: middle; object-fit: contain;">` : ''}
${msg.role_icon ? `<img src="${msg.role_icon}" class="role-icon ms-1" style="width: 12px; height: 12px; vertical-align: middle; object-fit: contain;">` : ''}
<span class="message-time">${msg.time}</span>
</div>
<div class="message-text" style="font-size: 0.9em;">
@ -852,7 +1027,7 @@ document.addEventListener('DOMContentLoaded', () => {
<div class="d-flex align-items-center">
<div style="width: 14px; height: 14px; border-radius: 50%; background-color: ${role.color}; margin-right: 12px; box-shadow: 0 0 5px ${role.color}88;"></div>
<span class="fw-medium">${role.name}</span>
${role.icon_url ? (isUrl ? `<img src="${role.icon_url}" class="ms-1" style="width: 16px; height: 16px; object-fit: contain;">` : `<span class="ms-1" style="font-size: 14px;">${role.icon_url}</span>`) : ''}
${role.icon_url ? (isUrl ? `<img src="${role.icon_url}" class="ms-1" style="width: 12px; height: 12px; object-fit: contain;">` : `<span class="ms-1" style="font-size: 12px;">${role.icon_url}</span>`) : ''}
</div>
<div>
<button class="btn btn-sm btn-outline-light edit-role-btn-v2" data-id="${role.id}" data-name="${role.name}" data-color="${role.color}" data-perms="${role.permissions}" data-icon="${role.icon_url || ''}">Edit</button>
@ -884,7 +1059,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
const isIconUrl = member.role_icon && (member.role_icon.startsWith('http') || member.role_icon.startsWith('/'));
const roleIconHtml = member.role_icon ? (isIconUrl ? `<img src="${member.role_icon}" class="role-icon ms-1" style="width: 14px; height: 14px; vertical-align: middle; object-fit: contain;">` : `<span class="ms-1" style="font-size: 14px; vertical-align: middle;">${member.role_icon}</span>`) : '';
const roleIconHtml = member.role_icon ? (isIconUrl ? `<img src="${member.role_icon}" class="role-icon ms-1" style="width: 12px; height: 12px; vertical-align: middle; object-fit: contain;">` : `<span class="ms-1" style="font-size: 12px; vertical-align: middle;">${member.role_icon}</span>`) : '';
item.innerHTML = `
<div class="d-flex align-items-center flex-grow-1">
@ -912,6 +1087,7 @@ document.addEventListener('DOMContentLoaded', () => {
document.getElementById('edit-role-name').value = role.name;
document.getElementById('edit-role-color').value = role.color;
document.getElementById('edit-role-icon').value = role.icon;
document.getElementById('selected-role-emoji-preview').textContent = role.icon || '';
const permsContainer = document.getElementById('role-permissions-checkboxes');
permsContainer.innerHTML = '';
@ -957,12 +1133,6 @@ document.addEventListener('DOMContentLoaded', () => {
} catch (e) { console.error(e); }
});
document.getElementById('role-emoji-picker-btn')?.addEventListener('click', (e) => {
showEmojiPicker(e.currentTarget, (emoji) => {
document.getElementById('edit-role-icon').value = emoji;
});
});
membersList?.addEventListener('change', async (e) => {
if (e.target.classList.contains('role-assign-check')) {
const userId = e.target.dataset.userId;
@ -1554,7 +1724,7 @@ document.addEventListener('DOMContentLoaded', () => {
const authorStyle = msg.role_color ? `color: ${msg.role_color};` : '';
const isRoleIconUrl = msg.role_icon && (msg.role_icon.startsWith('http') || msg.role_icon.startsWith('/'));
const roleIcon = msg.role_icon ? (isRoleIconUrl ? `<img src="${msg.role_icon}" class="role-icon ms-1" style="width: 20px; height: 20px; vertical-align: middle; object-fit: contain;">` : `<span class="ms-1" style="font-size: 20px; vertical-align: middle;">${msg.role_icon}</span>`) : '';
const roleIcon = msg.role_icon ? (isRoleIconUrl ? `<img src="${msg.role_icon}" class="role-icon ms-1" style="width: 12px; height: 12px; vertical-align: middle; object-fit: contain;">` : `<span class="ms-1" style="font-size: 12px; vertical-align: middle;">${msg.role_icon}</span>`) : '';
div.innerHTML = `
<div class="message-avatar" style="${avatarStyle}"></div>

View File

@ -1,7 +1,7 @@
<?php
require_once 'auth/session.php';
function renderRoleIcon($icon, $size = '14px') {
function renderRoleIcon($icon, $size = '12px') {
if (empty($icon)) return '';
$isUrl = (strpos($icon, 'http') === 0 || strpos($icon, '/') === 0);
if ($isUrl) {
@ -237,6 +237,11 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
background-color: <?php echo $channel_theme; ?>;
}
<?php endif; ?>
.role-emoji-item:hover {
background-color: rgba(255,255,255,0.1);
transform: scale(1.2);
transition: transform 0.1s;
}
</style>
</head>
<body data-theme="<?php echo htmlspecialchars($user['theme'] ?? 'dark'); ?>">
@ -495,7 +500,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
<div class="message-content">
<div class="message-author" style="<?php echo !empty($m['role_color']) ? "color: {$m['role_color']};" : ""; ?>">
<?php echo htmlspecialchars($m['username']); ?>
<?php echo renderRoleIcon($m['role_icon'], '20px'); ?>
<?php echo renderRoleIcon($m['role_icon'], '12px'); ?>
<span class="message-time"><?php echo date('H:i', strtotime($m['created_at'])); ?></span>
<?php if ($is_solution): ?>
<span class="badge bg-success ms-2">SOLUTION</span>
@ -601,7 +606,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
</div>
<div class="thread-meta small text-muted">
Started by <span style="<?php echo !empty($thread['role_color']) ? "color: {$thread['role_color']};" : ""; ?>"><?php echo htmlspecialchars($thread['username']); ?></span>
<?php echo renderRoleIcon($thread['role_icon'], '14px'); ?>
<?php echo renderRoleIcon($thread['role_icon'], '11px'); ?>
โ€ข <?php echo $thread['message_count']; ?> messages
</div>
</div>
@ -631,7 +636,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
<div class="message-header">
<span class="message-author" style="<?php echo !empty($m['role_color']) ? "color: {$m['role_color']};" : ""; ?>">
<?php echo htmlspecialchars($m['username']); ?>
<?php echo renderRoleIcon($m['role_icon'], '20px'); ?>
<?php echo renderRoleIcon($m['role_icon'], '12px'); ?>
</span>
<span class="message-time"><?php echo date('H:i', strtotime($m['created_at'])); ?></span>
<?php if ($m['is_pinned']): ?>
@ -779,7 +784,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
</div>
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; <?php echo !empty($m['role_color']) ? "color: {$m['role_color']};" : ""; ?>">
<?php echo htmlspecialchars($m['username']); ?>
<?php echo renderRoleIcon($m['role_icon'], '16px'); ?>
<?php echo renderRoleIcon($m['role_icon'], '12px'); ?>
</span>
</div>
<?php endforeach; ?>
@ -1285,10 +1290,20 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
<input type="color" id="edit-role-color" class="form-control form-control-color w-100">
</div>
<div class="mb-3">
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Role Icon (Emoji or URL)</label>
<div class="input-group">
<input type="text" id="edit-role-icon" class="form-control" placeholder="https://example.com/icon.png">
<button class="btn btn-outline-secondary" type="button" id="role-emoji-picker-btn">๐Ÿ˜Š</button>
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Role Icon</label>
<div class="d-flex align-items-center mb-2">
<div id="selected-role-emoji-preview" class="d-flex align-items-center justify-content-center border rounded" style="width: 40px; height: 40px; font-size: 24px; background: rgba(0,0,0,0.2);"></div>
<input type="hidden" id="edit-role-icon">
<button type="button" class="btn btn-sm btn-outline-danger ms-2" onclick="document.getElementById('edit-role-icon').value=''; document.getElementById('selected-role-emoji-preview').textContent='';">Remove Icon</button>
</div>
<div id="role-emoji-categories" class="d-flex overflow-auto mb-2 p-1" style="background: rgba(0,0,0,0.1); border-radius: 4px; gap: 5px;">
<!-- Categories populated by JS -->
</div>
<div class="mb-2">
<input type="text" id="role-emoji-search" class="form-control form-control-sm bg-dark border-secondary text-white" placeholder="Search emoji...">
</div>
<div id="role-emoji-grid" class="border rounded p-2" style="max-height: 150px; overflow-y: auto; display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; background: rgba(0,0,0,0.2);">
<!-- Emojis populated by JS -->
</div>
</div>
<div class="mb-3">