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

@@ -12,6 +12,20 @@
$description = $emoji['description'] ?? '';
$unified = $emoji['unified'] ?? '';
$shortcode = $emoji['shortcodes'][0] ?? '';
$supportsTone = (bool) ($emoji['supports_skin_tone'] ?? false);
$emojiBase = $symbol;
if ($supportsTone && $symbol !== '') {
$emojiBase = preg_replace('/\x{1F3FB}|\x{1F3FC}|\x{1F3FD}|\x{1F3FE}|\x{1F3FF}/u', '', $symbol) ?: $symbol;
}
$toneVariants = $supportsTone
? [
'light' => $emojiBase."\u{1F3FB}",
'medium-light' => $emojiBase."\u{1F3FC}",
'medium' => $emojiBase."\u{1F3FD}",
'medium-dark' => $emojiBase."\u{1F3FE}",
'dark' => $emojiBase."\u{1F3FF}",
]
: [];
$user = auth()->user();
$userTier = $userTier ?? $user?->tier;
$isPersonal = $userTier === 'personal';
@@ -93,9 +107,9 @@
<div class="lg:col-span-5 flex flex-col gap-6">
<div class="glass-card rounded-[32px] aspect-square flex items-center justify-center relative overflow-hidden group">
<div class="absolute w-64 h-64 bg-yellow-500/20 rounded-full blur-3xl group-hover:bg-yellow-500/30 transition-colors duration-500"></div>
<div class="text-[140px] md:text-[180px] leading-none select-none relative z-10 animate-float drop-shadow-2xl">{{ $symbol }}</div>
<div id="emoji-hero-symbol" class="text-[140px] md:text-[180px] leading-none select-none relative z-10 animate-float drop-shadow-2xl">{{ $symbol }}</div>
<div class="absolute bottom-6 flex gap-3 opacity-0 group-hover:opacity-100 transition-all transform translate-y-2 group-hover:translate-y-0">
<button onclick="copyToClipboard('{{ $symbol }}')" class="bg-black/50 backdrop-blur text-white force-white p-3 rounded-full hover:bg-brand-sun hover:text-black transition-colors border border-white/10" title="Copy">
<button onclick="copyCurrentEmoji()" class="bg-black/50 backdrop-blur text-white force-white p-3 rounded-full hover:bg-brand-sun hover:text-black transition-colors border border-white/10" title="Copy">
<i data-lucide="copy" class="w-5 h-5 force-white"></i>
</button>
</div>
@@ -139,12 +153,26 @@
</div>
<div class="flex gap-4">
<button onclick="copyToClipboard('{{ $symbol }}')" class="flex-1 bg-brand-ocean hover:bg-brand-oceanSoft text-white force-white font-bold h-14 rounded-xl flex items-center justify-center gap-3 text-lg transition-all shadow-[0_0_20px_rgba(32,83,255,0.35)] hover:shadow-[0_0_30px_rgba(32,83,255,0.55)]">
<button id="copy-current-emoji-btn" onclick="copyCurrentEmoji()" class="flex-1 bg-brand-ocean hover:bg-brand-oceanSoft text-white force-white font-bold h-14 rounded-xl flex items-center justify-center gap-3 text-lg transition-all shadow-[0_0_20px_rgba(32,83,255,0.35)] hover:shadow-[0_0_30px_rgba(32,83,255,0.55)]">
<i data-lucide="copy" class="w-5 h-5 force-white"></i>
Copy Emoji
</button>
</div>
@if($supportsTone)
<div class="glass-card rounded-xl p-3">
<div class="text-xs font-mono text-gray-500 mb-2">Skin tone</div>
<div class="flex flex-wrap gap-2">
<button type="button" data-tone="off" class="tone-chip px-3 py-1.5 rounded-lg border border-white/10 text-sm text-gray-200 bg-white/5 hover:bg-white/10">Default</button>
<button type="button" data-tone="light" class="tone-chip px-3 py-1.5 rounded-lg border border-white/10 text-sm text-gray-200 bg-white/5 hover:bg-white/10">🏻</button>
<button type="button" data-tone="medium-light" class="tone-chip px-3 py-1.5 rounded-lg border border-white/10 text-sm text-gray-200 bg-white/5 hover:bg-white/10">🏼</button>
<button type="button" data-tone="medium" class="tone-chip px-3 py-1.5 rounded-lg border border-white/10 text-sm text-gray-200 bg-white/5 hover:bg-white/10">🏽</button>
<button type="button" data-tone="medium-dark" class="tone-chip px-3 py-1.5 rounded-lg border border-white/10 text-sm text-gray-200 bg-white/5 hover:bg-white/10">🏾</button>
<button type="button" data-tone="dark" class="tone-chip px-3 py-1.5 rounded-lg border border-white/10 text-sm text-gray-200 bg-white/5 hover:bg-white/10">🏿</button>
</div>
</div>
@endif
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
@if($shortcode !== '')
<button onclick="copyToClipboard('{{ $shortcode }}')" class="glass-card p-4 rounded-xl group text-left">
@@ -300,6 +328,35 @@
@push('scripts')
<script>
const RECENT_KEY = 'dewemoji_recent';
const TONE_STORAGE_KEY = 'dewemoji_skin_tone';
const SYMBOL_DEFAULT = @json($symbol);
const TONE_VARIANTS = @json($toneVariants);
let currentDisplayEmoji = SYMBOL_DEFAULT;
function getStoredTone() {
return localStorage.getItem(TONE_STORAGE_KEY) || 'off';
}
function setStoredTone(value) {
localStorage.setItem(TONE_STORAGE_KEY, value || 'off');
}
function emojiByTone(tone) {
if (!tone || tone === 'off') return SYMBOL_DEFAULT;
return TONE_VARIANTS[tone] || SYMBOL_DEFAULT;
}
function applyTone(tone) {
currentDisplayEmoji = emojiByTone(tone);
const hero = document.getElementById('emoji-hero-symbol');
if (hero) hero.textContent = currentDisplayEmoji;
document.querySelectorAll('.tone-chip').forEach((chip) => {
const active = chip.dataset.tone === tone;
chip.classList.toggle('bg-brand-ocean/20', active);
chip.classList.toggle('border-brand-ocean/50', active);
chip.classList.toggle('text-brand-oceanSoft', active);
});
}
function loadRecent() {
try {
@@ -319,6 +376,10 @@ function addRecent(emoji) {
saveRecent(curr);
}
function copyCurrentEmoji() {
copyToClipboard(currentDisplayEmoji);
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
addRecent(text);
@@ -334,12 +395,24 @@ function copyToClipboard(text) {
document.addEventListener('keydown', (e) => {
if (e.key === 'c' && !e.metaKey && !e.ctrlKey && document.activeElement.tagName !== 'INPUT') {
copyToClipboard(@json($symbol));
copyCurrentEmoji();
}
});
(() => {
const initialTone = getStoredTone();
applyTone(initialTone);
document.querySelectorAll('.tone-chip').forEach((chip) => {
chip.addEventListener('click', () => {
const tone = chip.dataset.tone || 'off';
setStoredTone(tone);
applyTone(tone);
});
});
})();
// Treat opening the single-emoji page as a "recently viewed emoji" event.
addRecent(@json($symbol));
addRecent(currentDisplayEmoji);
(() => {
const canManageKeywords = @json($canManageKeywords);