@@ -0,0 +1,467 @@
{###############################################################################
_icon_picker.html — Sélecteur d'icône/emoji façon Notion (v5.6.0)
═══════════════════════════════════════════════════════════════════════════
Popover global (inclus par base.html) utilisé par :
• le menu contextuel (library / local-workspace) via fdCtx.setIcon
• l'éditeur de page (clic sur l'icône du titre)
Expose le store Alpine `fdIconPicker` + `window.FDIconPicker`.
Onglets Emoji / Icons / Upload, recherche, aléatoire, teintes de peau,
récents (localStorage) et emojis personnalisés du workspace (API).
############################################################################}
{% set picker_icons = ['folder','file','calendar','clock','star','bot','users','globe','lock','book','check-square','trash','help-circle','settings','refresh','log-out','message-square','home','search','link','plus','bell','image','download','list','bar-chart','grid','align-left','corner-down-right','copy','key','inbox','edit','eye','share','x','paperclip','external-link','sparkles','lightbulb','tag','file-text','save','upload','trending-up','zap','alert-triangle','user'] %}
< script data-cfasync = "false" >
/* Global JS mirror of _icons.html + helpers to render emoji / icon / custom-emoji URLs. */
(function () {
var FD_ICONS = {
'folder': '< path d = "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" / > ',
'file': '< path d = "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" / > < polyline points = "14 2 14 8 20 8" / > ',
'calendar': '< rect x = "3" y = "4" width = "18" height = "18" rx = "2" ry = "2" / > < line x1 = "16" y1 = "2" x2 = "16" y2 = "6" / > < line x1 = "8" y1 = "2" x2 = "8" y2 = "6" / > < line x1 = "3" y1 = "10" x2 = "21" y2 = "10" / > ',
'clock': '< circle cx = "12" cy = "12" r = "10" / > < polyline points = "12 6 12 12 16 14" / > ',
'star': '< polygon points = "12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" / > ',
'bot': '< rect x = "3" y = "7" width = "18" height = "13" rx = "2" / > < path d = "M8 7V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v3" / > < line x1 = "12" y1 = "20" x2 = "12" y2 = "24" / > < circle cx = "8" cy = "13" r = "1" / > < circle cx = "16" cy = "13" r = "1" / > ',
'users': '< path d = "M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" / > < circle cx = "9" cy = "7" r = "4" / > < path d = "M23 21v-2a4 4 0 0 0-3-3.87" / > < path d = "M16 3.13a4 4 0 0 1 0 7.75" / > ',
'globe': '< circle cx = "12" cy = "12" r = "10" / > < line x1 = "2" y1 = "12" x2 = "22" y2 = "12" / > < path d = "M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" / > ',
'lock': '< rect x = "3" y = "11" width = "18" height = "11" rx = "2" ry = "2" / > < path d = "M7 11V7a5 5 0 0 1 10 0v4" / > ',
'book': '< path d = "M4 19.5A2.5 2.5 0 0 1 6.5 17H20" / > < path d = "M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" / > ',
'check-square': '< polyline points = "9 11 12 14 22 4" / > < path d = "M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" / > ',
'trash': '< polyline points = "3 6 5 6 21 6" / > < path d = "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" / > ',
'help-circle': '< circle cx = "12" cy = "12" r = "10" / > < path d = "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" / > < line x1 = "12" y1 = "17" x2 = "12.01" y2 = "17" / > ',
'settings': '< circle cx = "12" cy = "12" r = "3" / > < path d = "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" / > ',
'refresh': '< polyline points = "23 4 23 10 17 10" / > < polyline points = "1 20 1 14 7 14" / > < path d = "M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" / > ',
'log-out': '< path d = "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" / > < polyline points = "16 17 21 12 16 7" / > < line x1 = "21" y1 = "12" x2 = "9" y2 = "12" / > ',
'message-square': '< path d = "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" / > ',
'home': '< path d = "M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" / > ',
'search': '< circle cx = "11" cy = "11" r = "8" / > < line x1 = "21" y1 = "21" x2 = "16.65" y2 = "16.65" / > ',
'link': '< path d = "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" / > < path d = "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" / > ',
'plus': '< line x1 = "12" y1 = "5" x2 = "12" y2 = "19" / > < line x1 = "5" y1 = "12" x2 = "19" y2 = "12" / > ',
'bell': '< path d = "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" / > < path d = "M13.73 21a2 2 0 0 1-3.46 0" / > ',
'image': '< rect x = "3" y = "3" width = "18" height = "18" rx = "2" ry = "2" / > < circle cx = "8.5" cy = "8.5" r = "1.5" / > < polyline points = "21 15 16 10 5 21" / > ',
'download': '< path d = "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" / > < polyline points = "7 10 12 15 17 10" / > < line x1 = "12" y1 = "15" x2 = "12" y2 = "3" / > ',
'chevron-left': '< polyline points = "15 18 9 12 15 6" / > ',
'chevron-right': '< polyline points = "9 18 15 12 9 6" / > ',
'list': '< line x1 = "8" y1 = "6" x2 = "21" y2 = "6" / > < line x1 = "8" y1 = "12" x2 = "21" y2 = "12" / > < line x1 = "8" y1 = "18" x2 = "21" y2 = "18" / > < line x1 = "3" y1 = "6" x2 = "3.01" y2 = "6" / > < line x1 = "3" y1 = "12" x2 = "3.01" y2 = "12" / > < line x1 = "3" y1 = "18" x2 = "3.01" y2 = "18" / > ',
'bar-chart': '< line x1 = "18" y1 = "20" x2 = "18" y2 = "10" / > < line x1 = "12" y1 = "20" x2 = "12" y2 = "4" / > < line x1 = "6" y1 = "20" x2 = "6" y2 = "14" / > ',
'grid': '< rect x = "3" y = "3" width = "7" height = "7" / > < rect x = "14" y = "3" width = "7" height = "7" / > < rect x = "14" y = "14" width = "7" height = "7" / > < rect x = "3" y = "14" width = "7" height = "7" / > ',
'align-left': '< line x1 = "17" y1 = "10" x2 = "3" y2 = "10" / > < line x1 = "21" y1 = "6" x2 = "3" y2 = "6" / > < line x1 = "17" y1 = "14" x2 = "3" y2 = "14" / > < line x1 = "21" y1 = "18" x2 = "3" y2 = "18" / > ',
'corner-down-right': '< polyline points = "15 10 20 15 15 20" / > < path d = "M4 4v7a4 4 0 0 0 4 4h12" / > ',
'copy': '< rect x = "9" y = "9" width = "13" height = "13" rx = "2" ry = "2" / > < path d = "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" / > ',
'chevron-down': '< polyline points = "6 9 12 15 18 9" / > ',
'key': '< path d = "M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" / > ',
'inbox': '< polyline points = "22 12 16 12 14 15 10 15 8 12 2 12" / > < path d = "M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" / > ',
'edit': '< path d = "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" / > < path d = "M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z" / > ',
'eye-off': '< path d = "M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24" / > < line x1 = "1" y1 = "1" x2 = "23" y2 = "23" / > ',
'eye': '< path d = "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" / > < circle cx = "12" cy = "12" r = "3" / > ',
'share': '< circle cx = "18" cy = "5" r = "3" / > < circle cx = "6" cy = "12" r = "3" / > < circle cx = "18" cy = "19" r = "3" / > < line x1 = "8.59" y1 = "13.51" x2 = "15.42" y2 = "17.49" / > < line x1 = "15.41" y1 = "6.51" x2 = "8.59" y2 = "10.49" / > ',
'more-horizontal': '< circle cx = "12" cy = "12" r = "1" / > < circle cx = "19" cy = "12" r = "1" / > < circle cx = "5" cy = "12" r = "1" / > ',
'x': '< line x1 = "18" y1 = "6" x2 = "6" y2 = "18" / > < line x1 = "6" y1 = "6" x2 = "18" y2 = "18" / > ',
'paperclip': '< path d = "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" / > ',
'external-link': '< path d = "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" / > < polyline points = "15 3 21 3 21 9" / > < line x1 = "10" y1 = "14" x2 = "21" y2 = "3" / > ',
'sparkles': '< path d = "M12 3l1.9 5.8 6.1.5-4.9 3.6 1.7 5.8-4.8-3.5-4.8 3.5 1.7-5.8-4.9-3.6 6.1-.5z" / > ',
'lightbulb': '< path d = "M9 18h6" / > < path d = "M10 22h4" / > < path d = "M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14" / > ',
'tag': '< path d = "M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z" / > < line x1 = "7" y1 = "7" x2 = "7.01" y2 = "7" / > ',
'file-text': '< path d = "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" / > < polyline points = "14 2 14 8 20 8" / > < line x1 = "16" y1 = "13" x2 = "8" y2 = "13" / > < line x1 = "16" y1 = "17" x2 = "8" y2 = "17" / > < polyline points = "10 9 9 9 8 9" / > ',
'save': '< path d = "M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" / > < polyline points = "17 21 17 13 7 13 7 21" / > < polyline points = "7 3 7 8 15 8" / > ',
'upload': '< path d = "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" / > < polyline points = "17 8 12 3 7 8" / > < line x1 = "12" y1 = "3" x2 = "12" y2 = "15" / > ',
'trending-up': '< polyline points = "23 6 13.5 15.5 8.5 10.5 1 18" / > < polyline points = "17 6 23 6 23 12" / > ',
'zap': '< polygon points = "13 2 3 14 12 14 11 22 21 10 12 10 13 2" / > ',
'alert-triangle': '< path d = "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" / > < line x1 = "12" y1 = "9" x2 = "12" y2 = "13" / > < line x1 = "12" y1 = "17" x2 = "12.01" y2 = "17" / > ',
'user': '< path d = "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" / > < circle cx = "12" cy = "7" r = "4" / > '
};
window.FD_ICONS = FD_ICONS;
window.fd_icon = function (name, size) {
size = size || 18;
var inner = FD_ICONS[name];
if (inner) return '< svg width = "' + size + '" height = "' + size + '" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" > ' + inner + '< / svg > ';
return (window.getSvgIcon ? getSvgIcon(name, size) : '');
};
/* Render a page_icon value: image URL, known icon name, or emoji text. */
window.fdIconHtml = function (value, size) {
size = size || 16;
var v = (value == null ? '' : String(value)).trim();
if (!v) return '';
if (v.charAt(0) === '/' || v.slice(0, 4) === 'http') {
return '< img src = "' + v.replace(/" / g , ' & quot ; ' ) + ' " alt = "" style = "width:' + size + 'px;height:' + size + 'px;object-fit:contain;vertical-align:middle;display:inline-block;" > ';
}
if (FD_ICONS[v] || (window.getSvgIcon & & getSvgIcon(v, size))) return window.fd_icon(v, size);
return v;
};
})();
< / script >
< script data-cfasync = "false" >
(function () {
if (window.__fdIconPickerRegistered) return;
window.__fdIconPickerRegistered = true;
var TONES = ['', '\u{1F3FB}', '\u{1F3FC}', '\u{1F3FD}', '\u{1F3FE}', '\u{1F3FF}'];
var TONEABLE = {};
['👋','🤚','🖐️','✋','🖖','👌','🤌','🤏','✌️','🤞','🤟','🤘','🤙','👈','👉','👆','👇','☝️','👍','👎','✊','👊','🤛','🤜','👏','🙌','🫶','👐','🤲','🤝','🙏','💪','🦵','🦶','👂','👃','👶','🧒','👦','👧','🧑','👨','👩','🧓','👴','👵','🙍','🙎','🙅','🙆','💁','🙋','🧏','🙇','🤦','🤷','👮','🕵️','💂','👷','🤴','👸','👳','👲','🧕','🤵','👰','🤰','🤱','👼','🎅','🤶','🦸','🦹','🧙','🧚','🧛','🧜','🧝','💆','💇','🚶','🧍','🧎','🏃','💃','🕺','👯','🧖','🧗','🏇','⛷️','🏂','🏌️','🏄','🚣','🏊','⛹️','🏋️','🚴','🚵','🤸','🤼','🤽','🤾','🤹','🧘','🛀','🛌'].forEach(function (e) { TONEABLE[e] = true; });
var CATS = [
{ id: 'people', label: 'People', items: [
['😀','grinning face smile happy'],['😃','smiley happy'],['😄','smile laugh happy'],['😁','grin happy'],['😆','laughing happy'],['😅','sweat smile'],['🤣','rofl rolling laugh'],['😂','joy tears laugh'],['🙂','slight smile'],['🙃','upside down'],['😉','wink'],['😊','blush smile happy'],['😇','innocent halo angel'],['🥰','love hearts'],['😍','heart eyes love'],['🤩','star struck wow'],['😘','kiss love'],['😗','kissing'],['😚','kissing'],['😙','kissing'],['🥲','tear smile happy'],['😋','yum tasty'],['😛','tongue'],['😜','wink tongue'],['🤪','crazy zany'],['😝','tongue'],['🤑','money rich'],['🤗','hug'],['🤭','giggle'],['🤫','shush quiet'],['🤔','thinking'],['🤐','zip quiet'],['🤨','raised eyebrow'],['😐','neutral'],['😑','expressionless'],['😶','no mouth'],['😏','smirk'],['😒','unamused'],['🙄','roll eyes'],['😬','grimace'],['🤥','lying'],['😌','relieved'],['😔','pensive sad'],['😪','sleepy'],['🤤','drool'],['😴','sleeping'],['😷','mask sick'],['🤒','sick thermometer'],['🤕','hurt bandage'],['🤢','nauseated'],['🤮','vomit'],['🤧','sneeze'],['🥵','hot'],['🥶','cold'],['🥴','woozy'],['😵','dizzy'],['🤯','mind blown'],['🤠','cowboy'],['🥳','party'],['🥺','pleading'],['😎','cool sunglasses'],['🤓','nerd'],['🧐','monocle'],['😕','confused'],['😟','worried'],['🙁','frown'],['☹️','frown sad'],['😮','surprised'],['😯','hushed'],['😲','astonished'],['😳','flushed'],['😨','fearful'],['😰','anxious'],['😥','sad'],['😢','cry'],['😭','sob cry'],['😱','scream fear'],['😖','confounded'],['😣','persevere'],['😞','disappointed'],['😓','sweat'],['😩','weary'],['😫','tired'],['🥱','yawn'],['😤','triumph'],['😡','angry'],['😠','rage'],['🤬','cursing'],['😈','devil'],['👿','imp'],['💀','skull'],['💩','poop'],['🤡','clown'],['👻','ghost'],['👽','alien'],['🤖','robot'],['😺','cat'],['🙈','monkey see'],['🙉','monkey hear'],['🙊','monkey speak'],['👋','wave hand'],['🤚','raised hand'],['🖐️','hand'],['✋','raised hand'],['🖖','vulcan'],['👌','ok'],['🤌','pinched'],['🤏','pinch'],['✌️','peace'],['🤞','cross fingers luck'],['🤟','love you'],['🤘','rock'],['🤙','call me'],['👈','point left'],['👉','point right'],['👆','point up'],['👇','point down'],['☝️','point up'],['👍','thumbs up like'],['👎','thumbs down dislike'],['✊','fist'],['👊','fist bump'],['🤛','fist'],['🤜','fist'],['👏','clap'],['🙌','raise hands celebrate'],['🫶','heart hands'],['👐','open hands'],['🤲','palms'],['🤝','handshake'],['🙏','pray thanks'],['💪','muscle strong'],['👂','ear'],['👃','nose'],['👀','eyes look'],['👁️','eye'],['🧠','brain'],['👶','baby'],['🧒','child'],['👦','boy'],['👧','girl'],['🧑','person'],['👨','man'],['👩','woman'],['🧓','older person'],['👴','old man'],['👵','old woman'],['👮','police'],['🕵️','detective'],['💂','guard'],['👷','worker'],['🤴','prince'],['👸','princess'],['👳','turban'],['🧕','hijab'],['🤵','tuxedo'],['👰','bride'],['🤰','pregnant'],['🤱','breastfeeding'],['👼','angel'],['🎅','santa'],['🤶','mrs claus'],['🦸','superhero'],['🦹','supervillain'],['🧙','mage wizard'],['🧚','fairy'],['🧛','vampire'],['🧜','mermaid'],['🧝','elf'],['💆','massage'],['💇','haircut'],['🚶','walk'],['🏃','run'],['💃','dance'],['🕺','dance'],['👯','people dancing'],['🧗','climb'],['🏇','horse race'],['🏂','snowboard'],['🏄','surf'],['🚣','row'],['🏊','swim'],['🚴','bike'],['🚵','mountain bike'],['🤸','cartwheel'],['🤼','wrestle'],['🤽','water polo'],['🤾','handball'],['🤹','juggle'],['🧘','meditate yoga'],['🛌','sleeping bed'],['💋','kiss mark'],['💌','love letter'],['❤️','heart love red'],['🧡','orange heart'],['💛','yellow heart'],['💚','green heart'],['💙','blue heart'],['💜','purple heart'],['🖤','black heart'],['🤍','white heart'],['🤎','brown heart'],['💔','broken heart'],['❣️','heart exclamation'],['💕','two hearts'],['💞','revolving hearts'],['💓','beating heart'],['💗','growing heart'],['💖','sparkling heart'],['💘','cupid heart'],['💝','heart gift'],['✨','sparkles'],['⭐','star'],['🌟','glowing star'],['💫','dizzy star'],['💥','boom'],['💯','hundred perfect']
]},
{ id: 'nature', label: 'Nature', items: [
['🐶','dog'],['🐱','cat'],['🐭','mouse'],['🐹','hamster'],['🐰','rabbit'],['🦊','fox'],['🐻','bear'],['🐼','panda'],['🐨','koala'],['🐯','tiger'],['🦁','lion'],['🐮','cow'],['🐷','pig'],['🐸','frog'],['🐵','monkey'],['🐔','chicken'],['🐧','penguin'],['🐦','bird'],['🐤','chick'],['🦆','duck'],['🦅','eagle'],['🦉','owl'],['🦇','bat'],['🐺','wolf'],['🐗','boar'],['🐴','horse'],['🦄','unicorn'],['🐝','bee'],['🐛','bug'],['🦋','butterfly'],['🐌','snail'],['🐞','ladybug'],['🐜','ant'],['🦗','cricket'],['🕷️','spider'],['🦂','scorpion'],['🐢','turtle'],['🐍','snake'],['🦎','lizard'],['🦖','dinosaur'],['🐙','octopus'],['🦑','squid'],['🦐','shrimp'],['🦀','crab'],['🐡','fish'],['🐠','fish'],['🐟','fish'],['🐬','dolphin'],['🐳','whale'],['🦈','shark'],['🐊','crocodile'],['🌵','cactus'],['🎄','tree christmas'],['🌲','tree evergreen'],['🌳','tree'],['🌴','palm tree'],['🌱','seedling plant'],['🌿','herb leaf'],['☘️','shamrock'],['🍀','clover luck'],['🎍','bamboo'],['🌾','wheat'],['🌷','tulip flower'],['🌹','rose flower'],['🌺','hibiscus flower'],['🌸','cherry blossom'],['🌼','flower'],['🌻','sunflower'],['🌞','sun'],['🌝','moon'],['🌚','moon'],['🌙','moon crescent'],['⭐','star'],['🌟','star'],['☀️','sun sunny'],['⛅','cloud sun'],['☁️','cloud'],['🌧️','rain'],['⛈️','storm'],['🌩️','lightning'],['❄️','snowflake'],['☃️','snowman'],['⛄','snowman'],['🔥','fire'],['💧','droplet water'],['🌊','wave water'],['🌈','rainbow'],['🌍','earth globe'],['🌎','earth globe'],['🌏','earth globe']
]},
{ id: 'food', label: 'Food', items: [
['🍏','apple green'],['🍎','apple red'],['🍐','pear'],['🍊','orange tangerine'],['🍋','lemon'],['🍌','banana'],['🍉','watermelon'],['🍇','grapes'],['🍓','strawberry'],['🫐','blueberry'],['🍈','melon'],['🍒','cherry'],['🍑','peach'],['🥭','mango'],['🍍','pineapple'],['🥥','coconut'],['🥝','kiwi'],['🍅','tomato'],['🍆','eggplant'],['🥑','avocado'],['🥦','broccoli'],['🥬','lettuce'],['🥒','cucumber'],['🌶️','pepper hot'],['🌽','corn'],['🥕','carrot'],['🧄','garlic'],['🧅','onion'],['🥔','potato'],['🍠','sweet potato'],['🥐','croissant'],['🥯','bagel'],['🍞','bread'],['🥖','baguette'],['🧀','cheese'],['🥚','egg'],['🍳','cooking egg'],['🥓','bacon'],['🥩','meat steak'],['🍗','chicken leg'],['🍖','meat'],['🌭','hot dog'],['🍔','burger'],['🍟','fries'],['🍕','pizza'],['🥪','sandwich'],['🥙','pita'],['🌮','taco'],['🌯','burrito'],['🥗','salad'],['🍝','pasta spaghetti'],['🍜','ramen noodles'],['🍲','stew'],['🍛','curry rice'],['🍣','sushi'],['🍱','bento'],['🥟','dumpling'],['🍤','shrimp fried'],['🍙','rice ball'],['🍚','rice'],['🍘','rice cracker'],['🍥','fish cake'],['🥠','fortune cookie'],['🍢','oden'],['🍡','dango'],['🍧','shaved ice'],['🍨','ice cream'],['🍦','ice cream'],['🥧','pie'],['🧁','cupcake'],['🍰','cake'],['🎂','birthday cake'],['🍮','custard'],['🍭','lollipop'],['🍬','candy'],['🍫','chocolate'],['🍿','popcorn'],['🍩','donut'],['🍪','cookie'],['☕','coffee'],['🍵','tea'],['🧃','juice'],['🥤','cup drink'],['🍺','beer'],['🍻','beers cheers'],['🥂','champagne'],['🍷','wine'],['🥃','whiskey'],['🍸','cocktail'],['🍹','tropical drink'],['🧉','mate'],['🍾','champagne bottle']
]},
{ id: 'activity', label: 'Activity', items: [
['⚽','soccer football'],['🏀','basketball'],['🏈','football'],['⚾','baseball'],['🥎','softball'],['🎾','tennis'],['🏐','volleyball'],['🏉','rugby'],['🥏','frisbee'],['🎱','pool billiards'],['🪀','yo-yo'],['🏓','ping pong'],['🏸','badminton'],['🏒','hockey'],['🏑','field hockey'],['🥍','lacrosse'],['🏏','cricket'],['🥅','goal'],['⛳','golf'],['🏹','archery'],['🎣','fishing'],['🥊','boxing'],['🥋','martial arts'],['🎽','running shirt'],['🛹','skateboard'],['🛼','roller skate'],['🛷','sled'],['⛸️','ice skate'],['🥌','curling'],['🎿','ski'],['⛷️','ski'],['🏂','snowboard'],['🏋️','weight lift'],['🤼','wrestle'],['🤸','cartwheel'],['⛹️','basketball'],['🤺','fencing'],['🤾','handball'],['🏌️','golf'],['🏇','horse race'],['🧘','yoga meditate'],['🏄','surf'],['🏊','swim'],['🤽','water polo'],['🚣','row'],['🧗','climb'],['🚴','bike'],['🚵','mountain bike'],['🎪','circus'],['🎭','theater masks'],['🎨','art palette'],['🎬','clapper film'],['🎤','microphone'],['🎧','headphones'],['🎼','music score'],['🎹','piano'],['🥁','drum'],['🎷','saxophone'],['🎺','trumpet'],['🎸','guitar'],['🪕','banjo'],['🎻','violin'],['🎲','dice random game'],['♟️','chess'],['🎯','target dart'],['🎳','bowling'],['🎮','game controller'],['🎰','slot machine'],['🧩','puzzle'],['🏆','trophy win'],['🥇','gold medal first'],['🥈','silver medal'],['🥉','bronze medal'],['🏅','medal'],['🎖️','military medal'],['🎗️','reminder ribbon'],['🎫','ticket'],['🎟️','tickets'],['🎁','gift present'],['🎉','party popper celebrate'],['🎊','confetti'],['🎈','balloon'],['🎂','cake birthday'],['🎃','pumpkin halloween'],['🎄','christmas tree'],['🎆','fireworks'],['🎇','fireworks'],['🧨','firecracker'],['✨','sparkles'],['🎓','graduation cap']
]},
{ id: 'travel', label: 'Travel', items: [
['🚗','car'],['🚕','taxi'],['🚙','car suv'],['🚌','bus'],['🚎','trolley bus'],['🏎️','race car'],['🚓','police car'],['🚑','ambulance'],['🚒','fire truck'],['🚐','van'],['🛻','pickup truck'],['🚚','truck'],['🚛','truck'],['🚜','tractor'],['🏍️','motorcycle'],['🛵','scooter'],['🚲','bicycle'],['🛴','kick scooter'],['🚨','police light'],['🚔','police car'],['🚍','bus'],['🚝','monorail'],['🚄','train'],['🚅','train bullet'],['🚈','train'],['🚂','locomotive train'],['🚆','train'],['🚇','metro subway'],['🚊','tram'],['🚉','station'],['✈️','airplane flight'],['🛫','airplane takeoff'],['🛬','airplane landing'],['🛩️','plane'],['💺','seat'],['🚁','helicopter'],['🛸','ufo'],['🚀','rocket launch'],['🛰️','satellite'],['🚢','ship'],['⛵','sailboat'],['🛥️','motor boat'],['🚤','speedboat'],['⛴️','ferry'],['🛳️','cruise ship'],['⚓','anchor'],['🚧','construction'],['⛽','fuel gas'],['🚏','bus stop'],['🗺️','map world'],['🗿','moai'],['🗽','statue liberty'],['🗼','tokyo tower'],['🏰','castle'],['🏯','castle japanese'],['🏟️','stadium'],['🎡','ferris wheel'],['🎢','roller coaster'],['🎠','carousel'],['⛲','fountain'],['⛱️','beach umbrella'],['🏖️','beach'],['🏝️','island'],['🏜️','desert'],['🌋','volcano'],['⛰️','mountain'],['🏔️','snow mountain'],['🗻','mount fuji'],['🏕️','camping'],['🏠','house home'],['🏡','house garden'],['🏢','office building'],['🏥','hospital'],['🏦','bank'],['🏨','hotel'],['🏫','school'],['🏭','factory'],['🏛️','classical building'],['⛪','church'],['🕌','mosque'],['🕍','synagogue'],['🛕','temple'],['🗼','tower'],['🌆','city sunset'],['🌃','night city'],['🌉','bridge night'],['🌌','milky way'],['🌠','shooting star'],['🌅','sunrise'],['🌄','sunrise mountain'],['🌇','sunset city']
]},
{ id: 'objects', label: 'Objects', items: [
['⌚','watch'],['📱','phone mobile'],['💻','laptop computer'],['⌨️','keyboard'],['🖥️','desktop computer'],['🖨️','printer'],['🖱️','mouse computer'],['💽','minidisc'],['💾','floppy disk save'],['💿','cd disk'],['📀','dvd'],['🧮','abacus'],['🎥','movie camera'],['🎞️','film frames'],['📽️','projector'],['📺','tv television'],['📷','camera'],['📸','camera flash'],['📹','video camera'],['📼','videocassette'],['🔍','magnifying search'],['🔎','magnifying search'],['🕯️','candle'],['💡','bulb idea'],['🔦','flashlight'],['🏮','lantern'],['🪔','lamp diya'],['📔','notebook'],['📕','book closed'],['📖','book open'],['📗','book green'],['📘','book blue'],['📙','book orange'],['📚','books library'],['📓','notebook'],['📒','ledger'],['📃','page'],['📜','scroll'],['📄','page document'],['📰','newspaper'],['🗞️','newspaper'],['📑','bookmark tabs'],['🔖','bookmark'],['🏷️','label tag'],['💰','money bag'],['🪙','coin'],['💴','yen'],['💵','dollar'],['💶','euro'],['💷','pound'],['💸','money wings'],['💳','credit card'],['🧾','receipt'],['✉️','envelope mail'],['📧','email'],['📨','envelope'],['📩','envelope'],['📤','outbox'],['📥','inbox'],['📦','package box'],['📫','mailbox'],['📪','mailbox'],['📬','mailbox'],['📭','mailbox'],['📮','postbox'],['🗳️','ballot box'],['✏️','pencil'],['✒️','pen nib'],['🖋️','pen'],['🖊️','pen'],['🖌️','paintbrush'],['🖍️','crayon'],['📝','memo note write'],['💼','briefcase work'],['📁','folder'],['📂','folder open'],['🗂️','card index'],['📅','calendar date'],['📆','calendar'],['🗒️','notepad'],['🗓️','calendar'],['📇','card index'],['📈','chart up trending'],['📉','chart down'],['📊','bar chart stats'],['📋','clipboard'],['📌','pushpin'],['📍','pin location'],['📎','paperclip attach'],['🖇️','paperclips'],['📏','ruler'],['📐','triangle ruler'],['✂️','scissors cut'],['🗃️','file box'],['🗄️','file cabinet'],['🗑️','trash waste'],['🔒','lock locked'],['🔓','lock open'],['🔑','key'],['🗝️','old key'],['🔨','hammer'],['🪓','axe'],['⛏️','pick'],['⚒️','tools'],['🛠️','tools'],['🔧','wrench'],['🔩','bolt nut'],['⚙️','gear settings'],['🧰','toolbox'],['🧲','magnet'],['🔫','water gun'],['💣','bomb'],['🧪','test tube science'],['🧫','petri dish'],['🧬','dna'],['🔬','microscope'],['🔭','telescope'],['📡','satellite antenna'],['💉','syringe'],['💊','pill medicine'],['🩹','bandage'],['🩺','stethoscope'],['🚪','door'],['🛏️','bed'],['🛋️','couch'],['🪑','chair'],['🚽','toilet'],['🚿','shower'],['🛁','bathtub'],['🧴','lotion'],['🧷','safety pin'],['🧹','broom'],['🧺','basket'],['🧻','toilet paper'],['🧼','soap'],['🪒','razor'],['🧽','sponge'],['🧯','extinguisher'],['🛒','cart shopping'],['🚬','cigarette'],['⚰️','coffin'],['🪦','headstone'],['⚱️','urn']
]},
{ id: 'symbols', label: 'Symbols', items: [
['❤️','heart love'],['🧡','orange heart'],['💛','yellow heart'],['💚','green heart'],['💙','blue heart'],['💜','purple heart'],['🖤','black heart'],['🤍','white heart'],['🤎','brown heart'],['💔','broken heart'],['❣️','heart'],['💕','hearts'],['💞','hearts'],['💓','heartbeat'],['💗','heart'],['💖','heart'],['💘','heart arrow'],['💝','heart gift'],['💟','heart decoration'],['☮️','peace'],['✝️','cross'],['☪️','star crescent'],['🕉️','om'],['☸️','dharma'],['✡️','star david'],['🔯','star'],['🕎','menorah'],['☯️','yin yang'],['☦️','orthodox cross'],['🛐','worship'],['⛎','ophiuchus'],['♈','aries'],['♉','taurus'],['♊','gemini'],['♋','cancer'],['♌','leo'],['♍','virgo'],['♎','libra'],['♏','scorpio'],['♐','sagittarius'],['♑','capricorn'],['♒','aquarius'],['♓','pisces'],['🆔','id'],['⚛️','atom'],['🉑','accept'],['☢️','radioactive'],['☣️','biohazard'],['📴','phone off'],['📳','vibrate'],['📵','no phone'],['🚭','no smoking'],['❗','exclamation'],['❕','exclamation'],['❓','question'],['❔','question'],['‼️','double exclamation'],['⁉️','exclamation question'],['🔅','dim'],['🔆','bright'],['〽️','part alternation'],['⚠️','warning'],['🚸','children crossing'],['🔱','trident'],['⚜️','fleur de lis'],['🔰','beginner'],['♻️','recycle'],['✅','check done'],['🈯','reserved'],['💹','chart yen'],['❇️','sparkle'],['✳️','asterisk'],['❎','cross mark'],['🌐','globe'],['💠','diamond'],['Ⓜ️','m'],['🌀','cyclone'],['💤','zzz sleep'],['🏧','atm'],['🚾','wc'],['♿','wheelchair'],['🅿️','parking'],['🈳','vacancy'],['🈂️','sa'],['🛂','passport control'],['🛃','customs'],['🛄','baggage'],['🛅','left luggage'],['🚹','men'],['🚺','women'],['🚼','baby'],['🚻','restroom'],['🚮','litter'],['🎦','cinema'],['📶','signal'],['🈁','here'],['🔣','symbols'],['ℹ ️','info'],['🔤','abc'],['🔡','abcd'],['🔠','abcd'],['🆖','ng'],['🆗','ok'],['🆙','up'],['🆒','cool'],['🆕','new'],['🆓','free'],['0️⃣','zero'],['1️⃣','one'],['2️⃣','two'],['3️⃣','three'],['4️⃣','four'],['5️⃣','five'],['6️⃣','six'],['7️⃣','seven'],['8️⃣','eight'],['9️⃣','nine'],['🔟','ten'],['🔢','numbers'],['#️⃣','hash'],['*️⃣','asterisk'],['⏏️','eject'],['▶️','play'],['⏸️','pause'],['⏹️','stop'],['⏺️','record'],['⏭️','next'],['⏮️','previous'],['⏩','fast forward'],['⏪','rewind'],['⏫','up'],['⏬','down'],['◀️','left'],['🔼','up'],['🔽','down'],['➡️','right'],['⬅️','left'],['⬆️','up'],['⬇️','down'],['↗️','up right'],['↘️','down right'],['↙️','down left'],['↖️','up left'],['↕️','up down'],['↔️','left right'],['↩️','return'],['↪️','redo'],['⤴️','up'],['⤵️','down'],['🔀','shuffle'],['🔁','repeat'],['🔂','repeat one'],['🔄','refresh'],['🔃','refresh'],['🎵','music note'],['🎶','music notes'],['➕ ','plus'],['➖ ','minus'],['➗','divide'],['✖️','multiply'],['♾️','infinity'],['💲','dollar'],['💱','currency'],['™️','tm'],['©️','copyright'],['®️','registered'],['〰️','wavy'],['➰','curly loop'],['➿','double loop'],['🔚','end'],['🔙','back'],['🔛','on'],['🔝','top'],['🔜','soon'],['✔️','check'],['☑️','checkbox'],['🔘','radio'],['🔴','red circle'],['🟠','orange circle'],['🟡','yellow circle'],['🟢','green circle'],['🔵','blue circle'],['🟣','purple circle'],['⚫','black circle'],['⚪','white circle'],['🟤','brown circle'],['🔺','red triangle'],['🔻','red triangle'],['🔸','orange diamond'],['🔹','blue diamond'],['🔶','orange diamond'],['🔷','blue diamond'],['🔳','white square'],['🔲','black square'],['▪️','black square'],['▫️','white square'],['◾','black square'],['◽','white square'],['◼️','black square'],['◻️','white square'],['🟥','red square'],['🟧','orange square'],['🟨','yellow square'],['🟩','green square'],['🟦','blue square'],['🟪','purple square'],['⬛','black square'],['⬜','white square'],['🟫','brown square'],['🔈','speaker'],['🔇','mute'],['🔉','speaker'],['🔊','speaker loud'],['🔔','bell'],['🔕','bell off'],['📣','megaphone'],['📢','loudspeaker'],['💬','speech bubble'],['💭','thought bubble'],['🗯️','anger bubble'],['♠️','spade'],['♣️','club'],['♥️','heart suit'],['♦️','diamond suit'],['🃏','joker'],['🎴','flower cards'],['🀄','mahjong']
]},
{ id: 'flags', label: 'Flags', items: [
['🏁','chequered flag finish'],['🚩','triangular flag'],['🎌','crossed flags'],['🏴','black flag'],['🏳️','white flag'],['🏳️🌈','rainbow flag pride'],['🏴☠️','pirate flag'],['🇺🇸','usa united states'],['🇬🇧','uk united kingdom'],['🇫🇷','france french'],['🇩🇪','germany german'],['🇪🇸','spain spanish'],['🇮🇹','italy italian'],['🇵🇹','portugal'],['🇳🇱','netherlands'],['🇧🇪','belgium'],['🇨🇭','switzerland'],['🇦🇹','austria'],['🇸🇪','sweden'],['🇳🇴','norway'],['🇩🇰','denmark'],['🇫🇮','finland'],['🇮🇪','ireland'],['🇵🇱','poland'],['🇬🇷','greece'],['🇷🇺','russia'],['🇺🇦','ukraine'],['🇹🇷','turkey'],['🇨🇦','canada'],['🇲🇽','mexico'],['🇧🇷','brazil'],['🇦🇷','argentina'],['🇨🇱','chile'],['🇨🇴','colombia'],['🇨🇳','china chinese'],['🇯🇵','japan japanese'],['🇰🇷','korea south'],['🇮🇳','india'],['🇦🇺','australia'],['🇳🇿','new zealand'],['🇿🇦','south africa'],['🇪🇬','egypt'],['🇲🇦','morocco'],['🇳🇬','nigeria'],['🇰🇪','kenya'],['🇸🇦','saudi arabia'],['🇦🇪','uae emirates'],['🇮🇱','israel'],['🇸🇬','singapore'],['🇹🇭','thailand'],['🇻🇳','vietnam'],['🇮🇩','indonesia'],['🇵🇭','philippines'],['🇲🇾','malaysia'],['🇵🇰','pakistan'],['🇧🇩','bangladesh'],['🇺🇳','united nations']
]}
];
document.addEventListener('alpine:init', function () {
if (window.Alpine & & window.Alpine.__fdIconPicker) return;
if (window.Alpine) window.Alpine.__fdIconPicker = true;
Alpine.store('fdIconPicker', {
open: false, x: 0, y: 0, tab: 'emoji', query: '', category: 'people',
skinTone: 0, toneOpen: false, recent: [], custom: [], customLoaded: false,
customModal: false, customName: '', customPreview: '', customFile: null, customBusy: false,
onPick: null, onRemove: null, _cx: 0, _cy: 0,
cats: CATS,
openFor(opts) {
opts = opts || {};
this.onPick = opts.onPick || null;
this.onRemove = opts.onRemove || null;
this.query = '';
this.toneOpen = false;
this.customModal = false;
this._cx = (opts.x != null) ? opts.x : 240;
this._cy = (opts.y != null) ? opts.y : 200;
this.x = this._cx;
this.y = this._cy;
this.open = true;
var self = this;
var place = function () {
var el = document.querySelector('.fd-icon-picker');
if (!el) return;
var w = el.offsetWidth || 344, h = el.offsetHeight || 440;
var vw = window.innerWidth, vh = window.innerHeight;
var nx = self._cx, ny = self._cy;
if (nx + w > vw - 8) nx = vw - w - 8;
if (ny + h > vh - 8) ny = vh - h - 8;
self.x = Math.max(8, nx);
self.y = Math.max(8, ny);
};
if (window.Alpine & & Alpine.nextTick) Alpine.nextTick(place);
else setTimeout(place, 0);
this.loadRecent();
this.loadCustom();
},
close() {
this.open = false;
this.customModal = false;
this.toneOpen = false;
this.onPick = null;
this.onRemove = null;
},
matches(name) {
var q = (this.query || '').trim().toLowerCase();
if (!q) return true;
return name.toLowerCase().indexOf(q) >= 0;
},
items() {
var q = (this.query || '').trim().toLowerCase();
if (q) {
var out = [];
this.cats.forEach(function (c) {
c.items.forEach(function (it) {
if ((it[1] || '').toLowerCase().indexOf(q) >= 0 || it[0].indexOf(q) >= 0) out.push(it[0]);
});
});
return out;
}
if (this.category === 'recent') return this.recent;
var found = [];
for (var i = 0; i < this.cats.length ; i + + ) {
if (this.cats[i].id === this.category) { found = this.cats[i].items.map(function (it) { return it[0]; }); break; }
}
return found;
},
withTone(e) {
if (!this.skinTone) return e;
if (TONEABLE[e]) return e + TONES[this.skinTone];
return e;
},
pick(v) {
v = (v || '').trim();
if (!v) return;
this.pushRecent(v);
var fn = this.onPick;
if (fn) { try { fn(v); } catch (e) { console.error('fdIconPicker.pick', e); } }
this.close();
},
remove() {
var fn = this.onRemove || this.onPick;
if (fn) { try { fn(''); } catch (e) {} }
this.close();
},
random() {
var pool = [];
this.cats.forEach(function (c) { c.items.forEach(function (it) { pool.push(it[0]); }); });
if (!pool.length) return;
this.pick(pool[Math.floor(Math.random() * pool.length)]);
},
setTone(i) { this.skinTone = i; this.toneOpen = false; },
setCategory(id) { this.category = id; this.tab = 'emoji'; this.query = ''; },
setTab(t) { this.tab = t; this.query = ''; if (t === 'upload') this.loadCustom(); },
loadRecent() {
try { this.recent = JSON.parse(localStorage.getItem('fd_icon_recent') || '[]'); }
catch (e) { this.recent = []; }
},
pushRecent(e) {
try {
var r = this.recent.filter(function (x) { return x !== e; });
r.unshift(e);
this.recent = r.slice(0, 32);
localStorage.setItem('fd_icon_recent', JSON.stringify(this.recent));
} catch (err) {}
},
async loadCustom() {
try {
var r = await fetch('/api/custom-emojis', { credentials: 'same-origin' });
var d = await r.json();
this.custom = (d & & d.emojis) || [];
this.customLoaded = true;
} catch (e) { this.custom = []; }
},
openCustomModal() {
this.customModal = true;
this.customName = '';
this.customPreview = '';
this.customFile = null;
this.tab = 'upload';
},
closeCustomModal() { this.customModal = false; },
onCustomFile(ev) {
var f = ev.target.files & & ev.target.files[0];
if (!f) return;
this.customFile = f;
var self = this;
var fr = new FileReader();
fr.onload = function () { self.customPreview = fr.result; };
fr.readAsDataURL(f);
if (!this.customName) this.customName = (f.name || '').replace(/\.[^.]+$/, '').slice(0, 40);
},
async saveCustom() {
if (!this.customFile || this.customBusy) return;
this.customBusy = true;
try {
var fd = new FormData();
fd.append('name', this.customName || 'emoji');
fd.append('file', this.customFile);
var csrf = (document.cookie.match(/csrf_token=([^;]+)/) || [])[1] || '';
var r = await fetch('/api/custom-emojis', { method: 'POST', headers: { 'X-CSRF-Token': csrf }, body: fd, credentials: 'same-origin' });
var d = await r.json();
if (d & & d.emoji) {
this.custom.unshift(d.emoji);
this.customModal = false;
this.pick(d.emoji.url);
}
} catch (e) {
if (window.showToast) window.showToast('Emoji upload failed', 'error');
}
this.customBusy = false;
},
async deleteCustom(id) {
try {
var csrf = (document.cookie.match(/csrf_token=([^;]+)/) || [])[1] || '';
await fetch('/api/custom-emojis/' + id, { method: 'DELETE', headers: { 'X-CSRF-Token': csrf }, credentials: 'same-origin' });
this.custom = this.custom.filter(function (e) { return e.id !== id; });
} catch (e) {}
}
});
});
window.FDIconPicker = {
openFor: function (opts) {
var s = window.Alpine & & Alpine.store('fdIconPicker');
if (s) s.openFor(opts);
}
};
})();
< / script >
< style >
.fd-icon-picker{position:fixed;top:0;left:0;width:344px;background:var(--bg-modal);border:1px solid var(--border);border-radius:10px;box-shadow:0 8px 32px rgba(0,0,0,.28);z-index:2200;display:flex;flex-direction:column;max-height:440px;overflow:hidden;padding:0;}
.fd-icon-picker .fdip-tabs{display:flex;align-items:center;gap:2px;padding:8px 10px 6px;border-bottom:1px solid var(--border);}
.fd-icon-picker .fdip-tab{background:transparent;border:none;color:var(--text-secondary);font-size:13px;padding:5px 10px;border-radius:6px;cursor:pointer;}
.fd-icon-picker .fdip-tab:hover{background:var(--bg-hover);}
.fd-icon-picker .fdip-tab.active{background:var(--bg-hover);color:var(--text-primary);font-weight:600;}
.fd-icon-picker .fdip-remove{margin-left:auto;background:transparent;border:none;color:var(--text-secondary);font-size:12px;padding:5px 8px;border-radius:6px;cursor:pointer;}
.fd-icon-picker .fdip-remove:hover{background:var(--bg-hover);color:var(--danger);}
.fd-icon-picker .fdip-tools{display:flex;align-items:center;gap:6px;padding:8px 10px;}
.fd-icon-picker .fdip-search{flex:1;min-width:0;font-size:13px;padding:6px 10px;background:var(--bg-primary);border:1px solid var(--border);border-radius:6px;color:var(--text-primary);outline:none;}
.fd-icon-picker .fdip-search:focus{border-color:var(--accent);}
.fd-icon-picker .fdip-tool{width:30px;height:30px;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:transparent;border:1px solid var(--border);border-radius:6px;cursor:pointer;font-size:15px;color:var(--text-primary);}
.fd-icon-picker .fdip-tool:hover{background:var(--bg-hover);}
.fd-icon-picker .fdip-tone-wrap{position:relative;}
.fd-icon-picker .fdip-tone-menu{position:absolute;right:0;top:34px;display:flex;gap:2px;padding:6px;background:var(--bg-modal);border:1px solid var(--border);border-radius:8px;box-shadow:0 6px 20px rgba(0,0,0,.25);z-index:5;}
.fd-icon-picker .fdip-tone{width:26px;height:26px;border:none;background:transparent;border-radius:6px;cursor:pointer;font-size:16px;}
.fd-icon-picker .fdip-tone:hover,.fd-icon-picker .fdip-tone.selected{background:var(--bg-hover);}
.fd-icon-picker .fdip-body{flex:1;overflow-y:auto;padding:0 10px 6px;}
.fd-icon-picker .fdip-section{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-dim);padding:6px 2px 2px;}
.fd-icon-picker .fdip-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:2px;}
.fd-icon-picker .fdip-emoji,.fd-icon-picker .fdip-icon{width:100%;aspect-ratio:1;display:flex;align-items:center;justify-content:center;background:transparent;border:none;border-radius:6px;cursor:pointer;font-size:21px;line-height:1;color:var(--text-primary);position:relative;}
.fd-icon-picker .fdip-emoji:hover,.fd-icon-picker .fdip-icon:hover{background:var(--bg-hover);}
.fd-icon-picker .fdip-custom img{width:24px;height:24px;object-fit:contain;}
.fd-icon-picker .fdip-del{position:absolute;top:-2px;right:-2px;width:14px;height:14px;border-radius:50%;background:var(--danger);color:#fff;font-size:10px;line-height:14px;text-align:center;display:none;}
.fd-icon-picker .fdip-custom:hover .fdip-del{display:block;}
.fd-icon-picker .fdip-empty{color:var(--text-dim);font-size:12px;text-align:center;padding:24px 0;}
.fd-icon-picker .fdip-cats{display:flex;align-items:center;gap:2px;padding:6px 10px;border-top:1px solid var(--border);}
.fd-icon-picker .fdip-cat{flex:1;height:30px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;border-radius:6px;cursor:pointer;color:var(--text-secondary);}
.fd-icon-picker .fdip-cat:hover{background:var(--bg-hover);}
.fd-icon-picker .fdip-cat.active{background:var(--bg-hover);color:var(--accent);}
.fd-icon-picker .fdip-cat svg{width:16px;height:16px;}
.fdip-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:2300;display:flex;align-items:center;justify-content:center;}
.fdip-modal{width:420px;max-width:92vw;background:var(--bg-modal);border:1px solid var(--border);border-radius:12px;padding:20px;box-shadow:0 16px 48px rgba(0,0,0,.35);}
.fdip-modal-title{font-size:16px;font-weight:600;color:var(--text-primary);margin-bottom:6px;}
.fdip-modal-desc{font-size:13px;color:var(--text-secondary);margin-bottom:16px;}
.fdip-upload-btn{display:inline-block;padding:8px 14px;font-size:13px;border:1px solid var(--border);border-radius:8px;cursor:pointer;color:var(--text-primary);background:var(--bg-secondary);}
.fdip-upload-btn:hover{background:var(--bg-hover);}
.fdip-preview-boxes{display:flex;gap:10px;margin:14px 0;}
.fdip-preview{width:64px;height:64px;border-radius:8px;display:flex;align-items:center;justify-content:center;border:1px solid var(--border);}
.fdip-preview.dark{background:#111;}
.fdip-preview.light{background:#fff;}
.fdip-preview img{max-width:44px;max-height:44px;object-fit:contain;}
.fdip-modal-label{display:block;font-size:12px;color:var(--text-secondary);margin:8px 0 4px;}
.fdip-modal-input{width:100%;font-size:14px;padding:8px 10px;background:var(--bg-primary);border:1px solid var(--border);border-radius:8px;color:var(--text-primary);outline:none;box-sizing:border-box;}
.fdip-modal-input:focus{border-color:var(--accent);}
.fdip-modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px;}
.fdip-modal-actions button{padding:8px 16px;font-size:13px;border-radius:8px;cursor:pointer;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text-primary);}
.fdip-modal-actions button.primary{background:var(--accent);border-color:var(--accent);color:#fff;}
.fdip-modal-actions button:disabled{opacity:.5;cursor:default;}
< / style >
< div x-data >
< div class = "fd-icon-picker" x-show = "$store.fdIconPicker.open" x-cloak
@click.outside="$store.fdIconPicker.customModal || $store.fdIconPicker.close()"
@keydown.escape.window="$store.fdIconPicker.close()"
:style="'top:' + $store.fdIconPicker.y + 'px;left:' + $store.fdIconPicker.x + 'px'">
< div class = "fdip-tabs" >
< button class = "fdip-tab" :class = "{ active: $store.fdIconPicker.tab === 'emoji' }" @ click = "$store.fdIconPicker.setTab('emoji')" > Emoji< / button >
< button class = "fdip-tab" :class = "{ active: $store.fdIconPicker.tab === 'icons' }" @ click = "$store.fdIconPicker.setTab('icons')" > Icons< / button >
< button class = "fdip-tab" :class = "{ active: $store.fdIconPicker.tab === 'upload' }" @ click = "$store.fdIconPicker.setTab('upload')" > Upload< / button >
< button class = "fdip-remove" @ click = "$store.fdIconPicker.remove()" > Remove< / button >
< / div >
< div class = "fdip-tools" x-show = "$store.fdIconPicker.tab !== 'upload'" >
< input class = "fdip-search" placeholder = "Filter..." x-model = "$store.fdIconPicker.query" >
< button class = "fdip-tool" title = "Random" @ click = "$store.fdIconPicker.random()" > 🎲< / button >
< div class = "fdip-tone-wrap" x-show = "$store.fdIconPicker.tab === 'emoji'" >
< button class = "fdip-tool" title = "Select skin tone" @ click . stop = "$store.fdIconPicker.toneOpen = !$store.fdIconPicker.toneOpen" > 👋< / button >
< div class = "fdip-tone-menu" x-show = "$store.fdIconPicker.toneOpen" @ click . outside = "$store.fdIconPicker.toneOpen = false" >
< template x-for = "(t, ti) in ['👋','👋🏻','👋🏼','👋🏽','👋🏾','👋🏿']" :key = "ti" >
< button class = "fdip-tone" :class = "{ selected: $store.fdIconPicker.skinTone === ti }" @ click = "$store.fdIconPicker.setTone(ti)" x-text = "t" > < / button >
< / template >
< / div >
< / div >
< / div >
< div class = "fdip-body" >
< template x-if = "$store.fdIconPicker.tab === 'emoji'" >
< div >
< div class = "fdip-section" x-show = "$store.fdIconPicker.category === 'recent' && !$store.fdIconPicker.query" > Recent< / div >
< div class = "fdip-grid" >
< template x-for = "e in $store.fdIconPicker.items()" :key = "e" >
< button class = "fdip-emoji" @ click = "$store.fdIconPicker.pick($store.fdIconPicker.withTone(e))" x-text = "$store.fdIconPicker.withTone(e)" > < / button >
< / template >
< / div >
< div class = "fdip-empty" x-show = "!$store.fdIconPicker.items().length" > No emoji found< / div >
< / div >
< / template >
< template x-if = "$store.fdIconPicker.tab === 'icons'" >
< div >
< div class = "fdip-grid" >
{% for name in picker_icons %}
< button class = "fdip-icon" x-show = "$store.fdIconPicker.matches('{{ name }}')" @ click = "$store.fdIconPicker.pick('{{ name }}')" title = "{{ name }}" > {{ fd_icon(name, 20) }}< / button >
{% endfor %}
< / div >
< / div >
< / template >
< template x-if = "$store.fdIconPicker.tab === 'upload'" >
< div >
< div class = "fdip-custom-head" style = "display:flex;align-items:center;justify-content:space-between;padding:6px 2px;" >
< span class = "fdip-section" style = "padding:0;" > Custom emojis< / span >
< button class = "fdip-tool" style = "width:auto;padding:0 10px;" @ click = "$store.fdIconPicker.openCustomModal()" > + Add< / button >
< / div >
< div class = "fdip-grid" >
< template x-for = "ce in $store.fdIconPicker.custom" :key = "ce.id" >
< button class = "fdip-emoji fdip-custom" @ click = "$store.fdIconPicker.pick(ce.url)" :title = "ce.name" >
< img :src = "ce.url" :alt = "ce.name" >
< span class = "fdip-del" @ click . stop = "$store.fdIconPicker.deleteCustom(ce.id)" > × < / span >
< / button >
< / template >
< / div >
< div class = "fdip-empty" x-show = "!$store.fdIconPicker.custom.length" > No custom emoji yet< / div >
< / div >
< / template >
< / div >
< div class = "fdip-cats" x-show = "$store.fdIconPicker.tab === 'emoji'" >
< button class = "fdip-cat" :class = "{ active: $store.fdIconPicker.category === 'recent' }" title = "Recent" @ click = "$store.fdIconPicker.setCategory('recent')" > < svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < circle cx = "12" cy = "12" r = "9" / > < polyline points = "12 7 12 12 15 14" / > < / svg > < / button >
< button class = "fdip-cat" :class = "{ active: $store.fdIconPicker.category === 'people' }" title = "People" @ click = "$store.fdIconPicker.setCategory('people')" > < svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < circle cx = "12" cy = "12" r = "9" / > < path d = "M8.5 14s1.3 2 3.5 2 3.5-2 3.5-2" / > < line x1 = "9" y1 = "9.5" x2 = "9.01" y2 = "9.5" / > < line x1 = "15" y1 = "9.5" x2 = "15.01" y2 = "9.5" / > < / svg > < / button >
< button class = "fdip-cat" :class = "{ active: $store.fdIconPicker.category === 'nature' }" title = "Nature" @ click = "$store.fdIconPicker.setCategory('nature')" > < svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < path d = "M11 20A7 7 0 0 1 4 13c0-6 7-10 16-10 0 9-4 16-10 16z" / > < path d = "M4 21c2-4 5-7 9-9" / > < / svg > < / button >
< button class = "fdip-cat" :class = "{ active: $store.fdIconPicker.category === 'food' }" title = "Food" @ click = "$store.fdIconPicker.setCategory('food')" > < svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < path d = "M3 21l6-6" / > < path d = "M14 3s5 1 7 7c-4 4-8 3-10 1s-1-6 3-8z" / > < path d = "M9 12l-4 4" / > < / svg > < / button >
< button class = "fdip-cat" :class = "{ active: $store.fdIconPicker.category === 'activity' }" title = "Activity" @ click = "$store.fdIconPicker.setCategory('activity')" > < svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < path d = "M6 11V6a2 2 0 0 1 4 0v5" / > < path d = "M10 11V4a2 2 0 0 1 4 0v7" / > < path d = "M14 11V6a2 2 0 0 1 4 0v9a7 7 0 0 1-7 7H9a6 6 0 0 1-6-6v-3a2 2 0 0 1 3-1.7" / > < / svg > < / button >
< button class = "fdip-cat" :class = "{ active: $store.fdIconPicker.category === 'travel' }" title = "Travel" @ click = "$store.fdIconPicker.setCategory('travel')" > < svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < path d = "M2 16l20-6-3-3-7 1-5-5-2 1 3 5-4 1-2-2-1 1z" / > < / svg > < / button >
< button class = "fdip-cat" :class = "{ active: $store.fdIconPicker.category === 'objects' }" title = "Objects" @ click = "$store.fdIconPicker.setCategory('objects')" > < svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < path d = "M9 18h6" / > < path d = "M10 22h4" / > < path d = "M12 2a6 6 0 0 0-4 10.5c.8.7 1 1.5 1 2.5h6c0-1 .2-1.8 1-2.5A6 6 0 0 0 12 2z" / > < / svg > < / button >
< button class = "fdip-cat" :class = "{ active: $store.fdIconPicker.category === 'symbols' }" title = "Symbols" @ click = "$store.fdIconPicker.setCategory('symbols')" > < svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < polyline points = "9 11 12 14 22 4" / > < path d = "M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11" / > < / svg > < / button >
< button class = "fdip-cat" :class = "{ active: $store.fdIconPicker.category === 'flags' }" title = "Flags" @ click = "$store.fdIconPicker.setCategory('flags')" > < svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < path d = "M4 22V4" / > < path d = "M4 4h13l-2 4 2 4H4" / > < / svg > < / button >
< button class = "fdip-cat" title = "Add custom emoji" @ click = "$store.fdIconPicker.openCustomModal()" > < svg viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" > < line x1 = "12" y1 = "5" x2 = "12" y2 = "19" / > < line x1 = "5" y1 = "12" x2 = "19" y2 = "12" / > < / svg > < / button >
< / div >
< / div >
< div class = "fdip-modal-overlay" x-show = "$store.fdIconPicker.customModal" x-cloak @ click . self = "$store.fdIconPicker.closeCustomModal()" >
< div class = "fdip-modal" >
< div class = "fdip-modal-title" > Add custom emoji< / div >
< div class = "fdip-modal-desc" > Custom emoji can be used by anyone in your workspace.< / div >
< label class = "fdip-upload-btn" >
< span x-show = "!$store.fdIconPicker.customPreview" > Upload an image< / span >
< span x-show = "$store.fdIconPicker.customPreview" > Replace< / span >
< input type = "file" accept = "image/*" @ change = "$store.fdIconPicker.onCustomFile($event)" style = "display:none" >
< / label >
< div class = "fdip-preview-boxes" x-show = "$store.fdIconPicker.customPreview" >
< div class = "fdip-preview dark" > < img :src = "$store.fdIconPicker.customPreview" alt = "" > < / div >
< div class = "fdip-preview light" > < img :src = "$store.fdIconPicker.customPreview" alt = "" > < / div >
< / div >
< label class = "fdip-modal-label" > Emoji name< / label >
< input class = "fdip-modal-input" x-model = "$store.fdIconPicker.customName" placeholder = "pushup" >
< div class = "fdip-modal-actions" >
< button @ click = "$store.fdIconPicker.closeCustomModal()" > Cancel< / button >
< button class = "primary" :disabled = "$store.fdIconPicker.customBusy || !$store.fdIconPicker.customFile" @ click = "$store.fdIconPicker.saveCustom()" > Save< / button >
< / div >
< / div >
< / div >
< / div >