Add skin tone support for discover grid and emoji detail

This commit is contained in:
Dwindi Ramadhana
2026-02-18 22:54:48 +07:00
parent 6c4d5b3ca7
commit 0d239b1967
3 changed files with 128 additions and 19 deletions

View File

@@ -96,6 +96,14 @@
<select id="subcategory" class="bg-[#151518] border border-white/10 rounded-xl px-4 text-sm text-gray-300 focus:outline-none focus:border-brand-ocean hover:bg-white/5 transition-colors h-11 cursor-pointer appearance-none theme-surface" disabled>
<option value="">All Subcategories</option>
</select>
<select id="skin-tone" class="bg-[#151518] border border-white/10 rounded-xl px-4 text-sm text-gray-300 focus:outline-none focus:border-brand-ocean hover:bg-white/5 transition-colors h-11 cursor-pointer appearance-none theme-surface">
<option value="off">Default tone</option>
<option value="light">Light</option>
<option value="medium-light">Medium light</option>
<option value="medium">Medium</option>
<option value="medium-dark">Medium dark</option>
<option value="dark">Dark</option>
</select>
<button id="theme-toggle" class="w-10 h-10 rounded-full theme-surface border border-white/10 shadow-lg flex items-center justify-center text-gray-300 hover:text-white transition-colors">
<span class="sr-only">Toggle theme</span>
<i data-lucide="moon" class="w-4 h-4" data-theme-icon="dark"></i>
@@ -226,6 +234,7 @@
const qEl = document.getElementById('q');
const catEl = document.getElementById('category');
const subEl = document.getElementById('subcategory');
const toneEl = document.getElementById('skin-tone');
const grid = document.getElementById('grid');
const count = document.getElementById('count');
const more = document.getElementById('more');
@@ -250,6 +259,14 @@
const keywordEditSlug = document.getElementById('keyword-edit-slug');
const keywordEditText = document.getElementById('keyword-edit-text');
const keywordEditLang = document.getElementById('keyword-edit-lang');
const toneStorageKey = 'dewemoji_skin_tone';
const toneIndexMap = {
light: 0,
'medium-light': 1,
medium: 2,
'medium-dark': 3,
dark: 4,
};
if (initialQuery) qEl.value = initialQuery;
@@ -310,6 +327,20 @@
return String(s || '').replace(/[&<>"']/g, (c) => ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[c]));
}
function selectedTone() {
return String(toneEl?.value || 'off');
}
function emojiWithTone(item) {
const tone = selectedTone();
if (tone === 'off') return item.emoji;
if (!item?.supports_skin_tone) return item.emoji;
const variants = Array.isArray(item?.variants) ? item.variants : [];
const idx = toneIndexMap[tone];
if (typeof idx === 'number' && variants[idx]) return variants[idx];
return item.emoji_base || item.emoji;
}
function applyGridDensity(level) {
const sizes = [
{ min: 90, emoji: '2.2rem' },
@@ -484,12 +515,12 @@
}
items.forEach((item) => {
const isPrivate = item.source === 'private';
const renderedEmoji = emojiWithTone(item);
const card = document.createElement('div');
card.className = 'emoji-card relative aspect-square rounded-lg bg-white/5 hover:bg-white/10 transition-transform hover:scale-[1.02] border border-transparent hover:border-white/20 overflow-hidden group';
card.innerHTML = `
<a href="/emoji/${encodeURIComponent(item.slug)}" class="absolute inset-0 flex items-center justify-center pb-10">
<span class="leading-none" style="font-size:var(--emoji-size)">${esc(item.emoji)}</span>
<span class="leading-none" style="font-size:var(--emoji-size)">${esc(renderedEmoji)}</span>
</a>
<div class="emoji-card-bar absolute bottom-0 left-0 right-0 border-t border-white/10 bg-black/20 px-2 py-1.5 flex items-start gap-1">
<span class="emoji-name-clamp text-[10px] text-gray-300 text-left flex-1">${esc(item.name)}</span>
@@ -501,17 +532,17 @@
copyBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
navigator.clipboard.writeText(item.emoji).then(() => {
showToast('Copied ' + item.emoji);
addRecent(item.emoji);
navigator.clipboard.writeText(renderedEmoji).then(() => {
showToast('Copied ' + renderedEmoji);
addRecent(renderedEmoji);
});
});
}
card.addEventListener('contextmenu', (e) => {
e.preventDefault();
navigator.clipboard.writeText(item.emoji).then(() => {
showToast('Copied ' + item.emoji);
addRecent(item.emoji);
navigator.clipboard.writeText(renderedEmoji).then(() => {
showToast('Copied ' + renderedEmoji);
addRecent(renderedEmoji);
});
});
grid.appendChild(card);
@@ -537,6 +568,10 @@
});
subEl.addEventListener('change', () => fetchEmojis(true));
toneEl?.addEventListener('change', () => {
localStorage.setItem(toneStorageKey, selectedTone());
fetchEmojis(true);
});
more.addEventListener('click', async () => {
state.page += 1;
@@ -609,6 +644,9 @@
}
(async () => {
if (toneEl) {
toneEl.value = localStorage.getItem(toneStorageKey) || 'off';
}
await loadCategories();
if (initialCategory && state.categories[initialCategory]) {
catEl.value = initialCategory;