// ==UserScript==
// @name KG_Full_Emoticons
// @namespace http://klavogonki.ru/
// @version 1.5
// @description Display a popup panel with every available emoticon on the site, remembering the last selected emoticon per category by name.
// @match *://klavogonki.ru/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=klavogonki.ru
// @grant none
// ==/UserScript==
(function () {
// State management
const state = {
eventListeners: [],
activeCategory: localStorage.getItem("activeCategory") || "Boys",
isPopupCreated: false,
categoryHistory: [],
currentSortedEmoticons: [],
lastFocusedInput: null,
latestCategoryRequest: null,
lastKeyTimes: {},
lastUsedEmoticons: JSON.parse(localStorage.getItem("lastUsedEmoticons")) || {}
};
// Helper function to handle double key presses
function handleDoubleKeyPress(e, targetKey, threshold, callback) {
const now = Date.now();
if (e.code === targetKey) {
if (now - (state.lastKeyTimes[targetKey] || 0) < threshold) {
e.preventDefault();
callback();
state.lastKeyTimes[targetKey] = 0; // Reset after triggering
} else {
state.lastKeyTimes[targetKey] = now;
}
} else {
// Reset the timing for the target key using dot notation
state.lastKeyTimes.Semicolon = 0;
}
}
// Constants
const UI = {
borderRadius: '0.2em',
boxShadow: '0 8px 30px rgba(0, 0, 0, 0.12), 0 4px 6px rgba(0, 0, 0, 0.04), 0 2px 2px rgba(0, 0, 0, 0.08)'
};
const categories = {
Boys: [
"hello",
"hi",
"smile",
"wink",
"biggrin",
"laugh",
"happy",
"cool",
"rofl",
"rofl2",
"rolleyes",
"spiteful",
"crazy",
"acute",
"silence",
"tongue",
"whistle",
"music",
"ph34r",
"excl",
"no",
"yes",
"ok",
"bye",
"victory",
"good",
"clapping",
"dance",
"cult",
"power",
"boystroking",
"complaugh",
"badcomp",
"gamer",
"first",
"second",
"third",
"formula1",
"friends",
"popcorn",
"tea",
"beer",
"grats",
"birthday",
"holmes",
"kidtruck",
"musketeer",
"pioneer",
"mellow",
"dry",
"sleep",
"cry",
"sick",
"sorry",
"unsure",
"boredom",
"facepalm",
"scare",
"blink",
"sad",
"confuse",
"nervous",
"wacko",
"huh",
"ohmy",
"megashok",
"shok",
"bad",
"russian",
"dash",
"angry",
"angry2",
],
Girls: [
"cheerful",
"cheerleader",
"clapgirl",
"curtsey",
"enjoygift",
"girlblum",
"girlconfuse",
"girlcrazy",
"girlcry",
"girlicecream",
"girlimpossible",
"girlinlove",
"girlkiss",
"girlkissboy",
"girlmad",
"girlmusic",
"girlnervous",
"girlnotebook",
"girlobserve",
"girlrevolve",
"girlsad",
"umbrage",
"girlscare",
"girlshighfive",
"girlsick",
"girlsilence",
"girlstop",
"girlstroking",
"girlsuper",
"girltea",
"girlwacko",
"girlwink",
"girlwitch",
"girlwonder",
"goody",
"hairdryer",
"hiya",
"hysteric",
"kgagainstaz",
"kgrace",
"primp",
"respect",
"spruceup",
"spruceup1",
"supergirl",
"tender",
"angrygirl",
"girldevil",
],
Christmas: [
"firework",
"confetti",
"cheers",
"wine",
"champ",
"champ2",
"santa",
"santa2",
"santa3",
"snowhand",
"snowhit",
"heyfrombag",
"snowgirlwave",
"snowball",
"snegurochka",
"santasnegurka",
"snowman",
"merrychristmas",
"spruce",
"moose",
"christmasevil",
],
Inlove: [
"inlove",
"hug",
"boykiss",
"wecheers",
"wedance",
"adultery",
"cave",
"leisure",
"wedding",
"airkiss",
"kissed",
"flowers",
"grose",
"flowers2",
"rose",
"smell",
"frog",
"girlfrog",
"rocker",
"serenade",
"val",
"girlval",
"bemine",
"heartcake",
"heart2",
"girlheart2",
"girllove",
"nolove",
"heart",
"blush",
"wub",
],
Army: [
"uzi",
"ak47",
"barret",
"chaingun",
"pogranminigun",
"partizan",
"dandy",
"gangster",
"mafia",
"foolrifle",
"cowboy",
"armyscare",
"armystar",
"armyfriends",
"armytongue",
"soldier",
"bayanist",
"pogranmail",
"pogran",
"pogranflowers",
"pogranrose",
"pograntort",
"girlpogran",
"pogranmama",
"budenov",
"captain",
"vdv",
"comandos",
"kirpich",
"girlvdv",
"girlranker",
"ranker",
"girlrogatka",
"rogatka",
"radistka",
"prival",
"vtik",
"vpered",
"tank",
"fly",
],
Halloween: [
"alien",
"ghost",
"cyborg",
"robot",
"terminator",
"turtle",
"batman",
"bebebe",
"bite",
"corsair",
"girlpirate",
"indigenous",
"clown",
"jester",
"death",
"paladin",
"pirate",
"dwarf",
"pirates",
"witch",
"wizard",
"spider",
"diablo",
"vampire",
"carpet",
],
Favourites: []
}
const categoryEmojis = {
Boys: "😃", Girls: "👩🦰", Christmas: "🎄", Inlove: "❤️", Army: "🔫", Halloween: "🎃", Favourites: "🌟"
};
// Initialize state
const bodyLightness = getLightness(window.getComputedStyle(document.body).backgroundColor);
const colors = {
popupBackground: getAdjustedBackground("popupBackground"),
defaultButton: getAdjustedBackground("defaultButton"),
hoverButton: getAdjustedBackground("hoverButton"),
activeButton: getAdjustedBackground("activeButton"),
selectedButton: getAdjustedBackground("selectedButton")
};
// Initialize last used emoticons
Object.keys(categories).forEach(cat => {
if (!Object.prototype.hasOwnProperty.call(state.lastUsedEmoticons, cat) || !categories[cat].includes(state.lastUsedEmoticons[cat])) {
state.lastUsedEmoticons[cat] = categories[cat][0] || '';
}
});
// UI/Color utility functions
function getLightness(color) {
const match = color.match(/\d+/g);
if (match && match.length === 3) {
const [r, g, b] = match.map(Number);
const max = Math.max(r, g, b) / 255;
const min = Math.min(r, g, b) / 255;
return Math.round(((max + min) / 2) * 100);
}
return 0;
}
function getAdjustedBackground(type) {
const adjustments = {
popupBackground: 10, defaultButton: 15, hoverButton: 25, activeButton: 35, selectedButton: 45
};
const adjustment = adjustments[type] || 0;
const adjustedLightness = bodyLightness < 50 ? bodyLightness + adjustment : bodyLightness - adjustment;
return `hsl(0, 0%, ${adjustedLightness}%)`;
}
// Data management functions
function loadFavoriteEmoticons() {
categories.Favourites = JSON.parse(localStorage.getItem("favoriteEmoticons")) || [];
}
function loadEmoticonUsageData() {
return JSON.parse(localStorage.getItem("emoticonUsageData")) || {};
}
function saveEmoticonUsageData(data) {
localStorage.setItem("emoticonUsageData", JSON.stringify(data));
}
function incrementEmoticonUsage(emoticon) {
const data = loadEmoticonUsageData();
data[state.activeCategory] = data[state.activeCategory] || {};
data[state.activeCategory][emoticon] = (data[state.activeCategory][emoticon] || 0) + 1;
saveEmoticonUsageData(data);
}
function getSortedEmoticons(category) {
const usage = loadEmoticonUsageData()[category] || {};
return categories[category].slice().sort((a, b) => (usage[b] || 0) - (usage[a] || 0));
}
function isEmoticonFavorite(emoticon) {
const fav = JSON.parse(localStorage.getItem("favoriteEmoticons")) || [];
return fav.includes(emoticon);
}
// Page context utility
function getPageContext() {
const path = window.location.pathname;
const hash = window.location.hash;
const searchParams = new URLSearchParams(window.location.search);
const gmid = searchParams.get('gmid');
const profileMatch = hash.match(/#\/(\d+)\//);
return {
isForum: path.includes('/forum/'),
isGamelist: path.includes('/gamelist/'),
isGame: !!gmid,
isProfile: path === '/u/' && !!profileMatch,
gmid: gmid || null,
profileId: profileMatch?.[1] || null
};
}
// Event handlers
function onFocusIn(e) {
if (e.target.matches("textarea, input.text, input#message-input")) {
state.lastFocusedInput = e.target;
}
}
function onKeyDown(e) {
// Close popup if Ctrl+V is detected, assuming paste intention
if (e.code === 'KeyV' && e.ctrlKey) {
const popup = document.querySelector(".emoticons-popup");
if (popup) {
removeEmoticonsPopup();
}
return; // Allow the paste to proceed normally
}
// Use the helper function for detecting a double semicolon press
if (e.code === 'Semicolon') {
handleDoubleKeyPress(e, 'Semicolon', 500, function () {
// Remove duplicated trailing character from the focused text field, if available
if (state.lastFocusedInput) {
let value = state.lastFocusedInput.value;
// If the last two characters are identical, remove them; otherwise remove one character
if (value.length >= 2 && value.slice(-1) === value.slice(-2, -1)) {
value = value.slice(0, -2);
} else if (value.length >= 1) {
value = value.slice(0, -1);
}
state.lastFocusedInput.value = value;
// Set the cursor at the end of the updated value
const pos = value.length;
state.lastFocusedInput.setSelectionRange(pos, pos);
}
toggleEmoticonsPopup();
});
} else {
// Reset the semicolon double press timer
state.lastKeyTimes.Semicolon = 0;
}
}
function onMouseUp(e) {
// Check for ctrl+click on text inputs (textarea or input with class "text")
if (e.ctrlKey && e.button === 0 && e.target.matches("textarea, input.text, input#message-input")) {
e.preventDefault();
toggleEmoticonsPopup();
}
}
function closePopupOnKeydown(e) {
const popup = document.querySelector(".emoticons-popup");
// Close popup if the key is Escape or KeyQ (using e.code for layout independence)
if (popup && (e.code === 'Escape' || e.code === 'KeyQ')) {
e.preventDefault();
removeEmoticonsPopup();
}
}
function closePopupOnClickOutside(e) {
const popup = document.querySelector(".emoticons-popup");
if (popup && !popup.contains(e.target)) {
removeEmoticonsPopup();
}
}
function getEmoticonCode(emoticon) {
const { isForum } = getPageContext();
// Check if there is a focused element and if it is a textarea
if (isForum && state.lastFocusedInput && state.lastFocusedInput.tagName.toLowerCase() === "textarea") {
// Use bbcode format for textarea on forum pages
return `[img]https://klavogonki.ru/img/smilies/${emoticon}.gif[/img] `;
} else {
// Otherwise, use the colon-based format
return `:${emoticon}: `;
}
}
function insertEmoticonCode(emoticon) {
const context = getPageContext();
let targetInput = state.lastFocusedInput;
if (!targetInput) {
if (context.isForum) targetInput = document.getElementById('fast-reply_textarea');
else if (context.isGamelist) targetInput = document.querySelector('#chat-general.chat .messages input.text');
else if (context.isGame) targetInput = document.querySelector('[id^="chat-game"].chat .messages input.text');
if (!targetInput) {
const labels = {
isForum: "the forum", isProfile: "the profile", isGamelist: "general chat", isGame: "game chat"
};
const detected = Object.entries(labels)
.filter(([key]) => context[key])
.map(([_, value]) => value)
.join(", ");
alert(`Please focus on a text field in ${detected}.`);
return;
}
targetInput.focus();
state.lastFocusedInput = targetInput;
}
const code = getEmoticonCode(emoticon);
const pos = targetInput.selectionStart || 0;
targetInput.value = targetInput.value.slice(0, pos) + code + targetInput.value.slice(pos);
targetInput.setSelectionRange(pos + code.length, pos + code.length);
targetInput.focus();
}
// Event listeners cleanup
function removeEventListeners() {
state.eventListeners.forEach(({ event, handler }) => {
document.removeEventListener(event, handler);
});
state.eventListeners = [];
}
// Animation utilities
function toggleContainerSmoothly(container, action) {
if (action === "show") {
document.body.appendChild(container);
requestAnimationFrame(() => {
container.style.opacity = "1";
});
} else {
container.style.opacity = "0";
setTimeout(() => container.remove(), 300);
}
}
// Popup control
function removeEmoticonsPopup() {
const popup = document.querySelector(".emoticons-popup");
if (popup) {
removeEventListeners();
toggleContainerSmoothly(popup, "hide");
state.isPopupCreated = false;
}
}
function toggleEmoticonsPopup() {
if (state.isPopupCreated) {
removeEmoticonsPopup();
} else {
setTimeout(() => {
createEmoticonsPopup(state.activeCategory);
}, 10);
}
}
// UI creation
function createEmoticonsPopup(category) {
if (state.isPopupCreated) return;
loadFavoriteEmoticons();
const popup = document.createElement("div");
popup.className = "emoticons-popup";
popup.style.setProperty('border-radius', '0.4em', 'important');
popup.style.setProperty('box-shadow', UI.boxShadow, 'important');
Object.assign(popup.style, {
opacity: "0",
transition: "opacity 0.3s cubic-bezier(0.25, 0.8, 0.25, 1)",
position: "fixed",
display: "grid",
gridTemplateRows: "50px auto",
gap: "10px",
backgroundColor: colors.popupBackground,
padding: "10px",
zIndex: "2000",
top: "20vh",
left: "50vw",
transform: "translateX(-50%)",
maxWidth: "50vw",
minWidth: "630px",
width: "50vw",
maxHeight: "50vh",
overflow: "hidden"
});
const headerButtons = document.createElement("div");
headerButtons.classList.add("header-buttons");
Object.assign(headerButtons.style, {
display: "flex",
flexDirection: "row",
justifyContent: "space-between"
});
// Create buttons
const createBtn = (className, title, innerHTML, bgColor, clickHandler) => {
const btn = document.createElement("button");
btn.classList.add(className);
btn.title = title;
btn.innerHTML = innerHTML;
btn.style.setProperty('border-radius', UI.borderRadius, 'important');
Object.assign(btn.style, {
border: "none",
background: bgColor,
cursor: "pointer",
boxSizing: "border-box",
width: "50px",
height: "50px",
margin: "0 5px",
fontSize: "1.4em"
});
if (clickHandler) btn.addEventListener("click", clickHandler);
return btn;
};
const clearButton = createBtn(
'clear-button',
"Clear usage data",
"🗑️",
"hsl(40deg 50% 15%)",
() => {
if (confirm("Clear emoticon usage data?")) {
localStorage.removeItem("emoticonUsageData");
}
}
);
const closeButton = createBtn(
'close-button',
"Close emoticons panel (or press 'q')",
"❌",
"hsl(0deg 50% 15%)",
removeEmoticonsPopup
);
headerButtons.appendChild(clearButton);
headerButtons.appendChild(createCategoryContainer());
headerButtons.appendChild(closeButton);
popup.appendChild(headerButtons);
createEmoticonsContainer(category).then((container) => {
popup.appendChild(container);
requestAnimationFrame(updateEmoticonHighlight);
});
popup.addEventListener("dblclick", removeEmoticonsPopup);
const eventListenersArray = [
{ event: "keydown", handler: navigateEmoticons },
{ event: "keydown", handler: switchEmoticonCategory },
{ event: "keydown", handler: closePopupOnKeydown },
{ event: "click", handler: closePopupOnClickOutside }
];
eventListenersArray.forEach(({ event, handler }) => {
state.eventListeners.push({ event, handler });
document.addEventListener(event, handler);
});
document.body.appendChild(popup);
toggleContainerSmoothly(popup, "show");
state.isPopupCreated = true;
}
function createCategoryContainer() {
const container = document.createElement("div");
container.className = "category-buttons";
Object.assign(container.style, {
display: "flex",
justifyContent: "center",
});
for (let cat in categories) {
if (Object.prototype.hasOwnProperty.call(categories, cat)) {
const btn = document.createElement("button");
btn.classList.add("category-button");
btn.innerHTML = categoryEmojis[cat];
btn.dataset.category = cat;
btn.title = cat;
btn.style.setProperty("border-radius", UI.borderRadius, "important");
Object.assign(btn.style, {
background: (cat === state.activeCategory ? colors.activeButton : colors.defaultButton),
border: "none",
cursor: "pointer",
width: "50px",
height: "50px",
fontSize: "1.4em",
margin: "0 5px"
});
// Special handling for "Favourites"
if (cat === "Favourites") {
if (categories.Favourites.length === 0) {
btn.style.opacity = "0.5";
btn.style.pointerEvents = "none";
}
btn.addEventListener("click", handleFavouritesClick);
}
btn.addEventListener("click", (e) => handleCategoryClick(cat, e));
btn.addEventListener("mouseout", () => handleCategoryMouseOut(btn, cat));
btn.addEventListener("mouseover", () => {
btn.style.background = colors.hoverButton;
});
container.appendChild(btn);
}
}
return container;
}
// Category handling
function handleCategoryClick(cat, e) {
if (!e.shiftKey && !e.ctrlKey) {
changeActiveCategoryOnClick(cat);
}
}
function handleCategoryMouseOut(btn, cat) {
btn.style.background = (cat === state.activeCategory ? colors.activeButton : colors.defaultButton);
if (cat === "Favourites") {
btn.style.opacity = categories.Favourites.length ? "" : "0.5";
}
}
function handleFavouritesClick(e) {
if (e.ctrlKey) {
localStorage.removeItem("favoriteEmoticons");
categories.Favourites = [];
updateEmoticonHighlight();
if (state.categoryHistory.length) {
state.activeCategory = state.categoryHistory.pop();
localStorage.setItem("activeCategory", state.activeCategory);
updateCategoryButtonsState(state.activeCategory);
updateEmoticonsContainer();
}
}
}
function updateCategoryButtonsState(newCategory) {
document.querySelectorAll(".category-buttons button").forEach((btn) => {
btn.style.background = btn.dataset.category === newCategory ? colors.activeButton : colors.defaultButton;
if (btn.dataset.category === "Favourites") {
if (categories.Favourites.length === 0) {
btn.style.opacity = "0.5";
btn.style.pointerEvents = "none";
} else {
btn.style.removeProperty("opacity");
btn.style.removeProperty("pointer-events");
}
}
});
}
function changeActiveCategoryOnClick(newCategory) {
if (newCategory === "Favourites" && categories.Favourites.length === 0) return;
if (state.activeCategory !== "Favourites") {
state.categoryHistory.push(state.activeCategory);
}
state.activeCategory = newCategory;
localStorage.setItem("activeCategory", state.activeCategory);
state.currentSortedEmoticons = getSortedEmoticons(state.activeCategory);
updateCategoryButtonsState(state.activeCategory);
updateEmoticonsContainer();
}
// Emoticon container creation
async function createEmoticonsContainer(category) {
const container = document.createElement("div");
container.className = "emoticon-buttons";
state.currentSortedEmoticons = getSortedEmoticons(category);
const promises = [];
state.currentSortedEmoticons.forEach((emoticon) => {
const btn = document.createElement("button");
btn.classList.add('emoticon-button');
const imgSrc = `/img/smilies/${emoticon}.gif`;
btn.innerHTML = `<img src="${imgSrc}" alt="${emoticon}">`;
btn.title = emoticon;
btn.style.setProperty('border-radius', UI.borderRadius, 'important');
Object.assign(btn.style, {
position: 'relative',
border: "none",
cursor: "pointer",
filter: emoticon === state.lastUsedEmoticons[state.activeCategory] ? "sepia(0.7)" : "none",
background: emoticon === state.lastUsedEmoticons[state.activeCategory]
? colors.selectedButton
: colors.defaultButton
});
// Preload the image
promises.push(new Promise(resolve => {
const img = new Image();
img.onload = resolve;
img.src = imgSrc;
}));
// Mouseover: change background to hover color
btn.addEventListener("mouseover", () => {
btn.style.background = colors.hoverButton;
});
// Mouseout: reset background based on state
btn.addEventListener("mouseout", () => {
if (emoticon === state.lastUsedEmoticons[state.activeCategory]) {
btn.style.background = colors.selectedButton;
} else if (state.activeCategory !== "Favourites" && isEmoticonFavorite(emoticon)) {
btn.style.background = colors.activeButton;
} else {
btn.style.background = colors.defaultButton;
}
});
btn.addEventListener("click", (e) => {
e.stopPropagation();
if (e.shiftKey) {
insertEmoticonCode(emoticon);
} else if (e.ctrlKey) {
const fav = JSON.parse(localStorage.getItem("favoriteEmoticons")) || [];
const pos = fav.indexOf(emoticon);
if (category === "Favourites" && pos !== -1) {
fav.splice(pos, 1);
categories.Favourites.splice(pos, 1);
} else if (category !== "Favourites" && !fav.includes(emoticon)) {
fav.push(emoticon);
categories.Favourites.push(emoticon);
}
localStorage.setItem("favoriteEmoticons", JSON.stringify(fav));
updateCategoryButtonsState(category);
if (category === "Favourites") updateEmoticonsContainer();
} else {
insertEmoticonCode(emoticon);
incrementEmoticonUsage(emoticon);
state.lastUsedEmoticons[state.activeCategory] = emoticon;
localStorage.setItem("lastUsedEmoticons", JSON.stringify(state.lastUsedEmoticons));
removeEmoticonsPopup();
}
updateEmoticonHighlight();
});
container.appendChild(btn);
});
await Promise.all(promises);
const { maxImageWidth, maxImageHeight } = await calculateMaxImageDimensions(state.currentSortedEmoticons);
Object.assign(container.style, {
display: "grid",
gap: "10px",
scrollbarWidth: "none",
overflowY: "auto",
overflowX: "hidden",
maxHeight: "calc(-80px + 50vh)",
gridTemplateColumns: `repeat(auto-fit, minmax(${maxImageWidth}px, 1fr))`,
gridAutoRows: `minmax(${maxImageHeight}px, auto)`
});
return container;
}
async function calculateMaxImageDimensions(emoticonsImages) {
const minValue = 34;
const imageDimensions = await Promise.all(
emoticonsImages.map((imageName) => {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve({ width: img.width, height: img.height });
img.src = `/img/smilies/${imageName}.gif`;
});
})
);
const maxWidth = Math.max(minValue, ...imageDimensions.map(img => img.width));
const maxHeight = Math.max(minValue, ...imageDimensions.map(img => img.height));
return { maxImageWidth: maxWidth, maxImageHeight: maxHeight };
}
function updateEmoticonsContainer() {
const requestTimestamp = Date.now();
state.latestCategoryRequest = requestTimestamp;
// Remove all old containers
document.querySelectorAll(".emoticon-buttons").forEach(container => container.remove());
createEmoticonsContainer(state.activeCategory).then((container) => {
// Ensure this is still the latest request before appending
if (state.latestCategoryRequest !== requestTimestamp) return;
const popup = document.querySelector(".emoticons-popup");
if (popup) {
popup.appendChild(container);
updateEmoticonHighlight();
}
});
}
// Navigation and selection
function updateEmoticonHighlight() {
requestAnimationFrame(() => {
const buttons = document.querySelectorAll(".emoticon-buttons button");
buttons.forEach((btn) => {
const emoticon = btn.title;
const isSelected = emoticon === state.lastUsedEmoticons[state.activeCategory];
if (isSelected) {
btn.style.background = colors.selectedButton;
btn.style.filter = "sepia(0.7)";
} else {
btn.style.filter = "none";
if (state.activeCategory !== "Favourites" && isEmoticonFavorite(emoticon)) {
btn.style.background = colors.activeButton;
} else {
btn.style.background = colors.defaultButton;
}
}
});
});
}
function updateActiveEmoticon(direction) {
const currentIndex = state.currentSortedEmoticons.indexOf(state.lastUsedEmoticons[state.activeCategory]);
let newIndex = currentIndex === -1 ? 0 : currentIndex + direction;
// Handle wrapping
if (newIndex < 0) newIndex = state.currentSortedEmoticons.length - 1;
if (newIndex >= state.currentSortedEmoticons.length) newIndex = 0;
// Update state
state.lastUsedEmoticons[state.activeCategory] = state.currentSortedEmoticons[newIndex];
localStorage.setItem("lastUsedEmoticons", JSON.stringify(state.lastUsedEmoticons));
// Update UI
updateEmoticonHighlight();
}
function navigateEmoticons(e) {
const popup = document.querySelector(".emoticons-popup");
if (!popup || !state.currentSortedEmoticons || state.currentSortedEmoticons.length === 0) return;
const handledKeys = new Set(['Enter', 'Semicolon', 'ArrowLeft', 'KeyJ', 'ArrowRight', 'KeyK']);
if (!handledKeys.has(e.code)) return;
e.preventDefault();
if (e.code === "Enter" || e.code === "Semicolon") {
const emoticon = state.lastUsedEmoticons[state.activeCategory];
if (emoticon && state.currentSortedEmoticons.includes(emoticon)) {
insertEmoticonCode(emoticon);
incrementEmoticonUsage(emoticon);
if (!e.shiftKey) removeEmoticonsPopup();
}
}
else if (e.code === "ArrowLeft" || e.code === "KeyJ") {
updateActiveEmoticon(-1); // Move left
}
else if (e.code === "ArrowRight" || e.code === "KeyK") {
updateActiveEmoticon(1); // Move right
}
}
function switchEmoticonCategory(e) {
const emoticonPopup = document.querySelector(".emoticons-popup");
if (!emoticonPopup || (!["Tab", "KeyH", "KeyL"].includes(e.code) && !(e.code === "Tab" && e.shiftKey))) return;
e.preventDefault();
const keys = Object.keys(categories);
const favs = JSON.parse(localStorage.getItem("favoriteEmoticons")) || [];
const navKeys = favs.length === 0 ? keys.filter(key => key !== "Favourites") : keys;
let idx = navKeys.indexOf(state.activeCategory);
if (idx === -1) idx = 0;
let newIdx = ((e.code === "Tab" && !e.shiftKey) || e.code === "KeyL") && idx < navKeys.length - 1 ? idx + 1 :
((e.code === "KeyH" || (e.code === "Tab" && e.shiftKey)) && idx > 0) ? idx - 1 : idx;
if (newIdx === idx) return;
const next = navKeys[newIdx];
state.currentSortedEmoticons = getSortedEmoticons(next);
localStorage.setItem("activeCategory", next);
changeActiveCategoryOnClick(next);
}
// Set up main event listeners
document.addEventListener("focusin", onFocusIn);
document.addEventListener("mouseup", onMouseUp);
document.addEventListener("keydown", onKeyDown);
})();