Files
dewemoji/app/resources/views/site/emoji-detail.blade.php
2026-02-12 00:52:40 +07:00

400 lines
21 KiB
PHP

@extends('site.layout')
@section('title', ($emoji['name'] ?? 'Emoji').' - Dewemoji')
@section('meta_description', ($emoji['description'] ?? 'Emoji detail').' Discover meaning, keywords, and copy-ready formats on Dewemoji.')
@php
$name = $emoji['name'] ?? '';
$category = $emoji['category'] ?? '';
$subcategory = $emoji['subcategory'] ?? '';
$symbol = $emoji['emoji'] ?? '';
$slug = $emoji['slug'] ?? '';
$description = $emoji['description'] ?? '';
$unified = $emoji['unified'] ?? '';
$shortcode = $emoji['shortcodes'][0] ?? '';
$user = auth()->user();
$userTier = $userTier ?? $user?->tier;
$isPersonal = $userTier === 'personal';
$userKeywords = $userKeywords ?? collect();
$htmlHex = '';
$cssCode = '';
if (!empty($emoji['codepoints'][0])) {
$hex = strtoupper($emoji['codepoints'][0]);
$htmlHex = '&#x'.$hex.';';
$cssCode = '\\'.$hex;
}
$related = $relatedDetails ?? [];
$keywords = array_slice($emoji['keywords_en'] ?? [], 0, 16);
@endphp
@push('jsonld')
<script type="application/ld+json">
{
"@@context": "https://schema.org",
"@@graph": [
{
"@@type": "CreativeWork",
"name": @json($name),
"description": @json($description),
"url": @json(url('/emoji/'.$slug))
},
{
"@@type": "BreadcrumbList",
"itemListElement": [
{"@@type":"ListItem","position":1,"name":"Home","item":@json(url('/'))},
{"@@type":"ListItem","position":2,"name":"Emoji","item":@json(url('/browse'))},
{"@@type":"ListItem","position":3,"name":@json($name),"item":@json(url('/emoji/'.$slug))}
]
}
]
}
</script>
@endpush
@section('content')
<div class="flex h-screen">
<aside class="hidden lg:flex w-20 lg:w-64 h-full glass-panel flex-col justify-between p-4 z-20">
<div>
<div class="flex items-center gap-3 px-2 mb-10 mt-2">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-white to-gray-300 flex items-center justify-center shadow-lg shadow-white/20">
<img src="/assets/logo/logo-mark.svg" alt="Dewemoji logo" class="w-7 h-7 object-contain" />
</div>
<h1 class="font-display font-bold text-lg tracking-tight hidden lg:block">Dewemoji</h1>
</div>
<nav class="space-y-2">
<a href="{{ route('home') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-300 hover:text-white hover:bg-white/5 transition-all group">
<i data-lucide="arrow-left" class="w-5 h-5 text-brand-sun"></i>
<span class="text-sm font-medium hidden lg:block text-brand-sun">Back</span>
</a>
</nav>
</div>
</aside>
<main class="flex-1 h-full overflow-y-auto relative p-4 pt-0 sm:p-6 lg:p-10 pb-24 lg:pb-10 flex flex-col">
<div class="sticky top-0 z-40 -mx-4 px-4 py-3 mb-6 bg-[var(--app-bg)]/90 backdrop-blur border-b border-white/10 sm:static sm:mx-0 sm:px-0 sm:py-0 sm:mb-8 sm:border-0">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2 text-xs sm:text-sm text-gray-500 font-mono">
<a href="{{ route('home') }}" class="hover:text-white transition-colors">Home</a>
<i data-lucide="chevron-right" class="w-3 h-3"></i>
<span class="hidden sm:inline-flex hover:text-white">{{ $category }}</span>
<i data-lucide="chevron-right" class="w-3 h-3 hidden sm:inline-flex"></i>
<span class="text-brand-sun">{{ $name }}</span>
</div>
<button id="theme-toggle" class="w-9 h-9 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>
<i data-lucide="sun" class="w-4 h-4 hidden" data-theme-icon="light"></i>
</button>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8 max-w-6xl mx-auto w-full">
<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 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 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"></i>
</button>
</div>
</div>
@if(count($related) > 0)
<div class="glass-card rounded-2xl p-5">
<h3 class="text-xs font-bold text-gray-500 uppercase tracking-wider mb-4">Related</h3>
<div class="flex gap-2 overflow-x-auto pb-2">
@foreach($related as $item)
<div class="relative group">
@if(!empty($item['slug']))
<a
href="{{ route('emoji-detail', ['slug' => $item['slug']]) }}"
class="w-12 h-12 rounded-lg bg-white/5 hover:bg-white/10 flex items-center justify-center text-2xl transition-colors"
title="{{ $item['name'] }}"
>{{ $item['emoji'] }}</a>
@else
<div class="w-12 h-12 rounded-lg bg-white/5 flex items-center justify-center text-2xl">{{ $item['emoji'] }}</div>
@endif
<button
type="button"
onclick="copyToClipboard('{{ $item['emoji'] }}'); event.preventDefault(); event.stopPropagation();"
class="absolute -bottom-1 -right-1 w-5 h-5 rounded bg-black/70 border border-white/10 text-[10px] text-gray-200 hover:bg-brand-ocean/40"
title="Copy {{ $item['name'] }}"
></button>
</div>
@endforeach
</div>
</div>
@endif
</div>
<div class="lg:col-span-7 flex flex-col gap-6">
<div>
<div class="flex items-center gap-3 mb-2">
<span class="bg-yellow-500/10 text-yellow-400 border border-yellow-500/20 px-3 py-1 rounded-full text-xs font-bold uppercase">{{ $subcategory }}</span>
</div>
<h1 class="font-display text-5xl font-bold mb-4">{{ $name }}</h1>
<p class="text-gray-400 text-lg leading-relaxed">{{ $description }}</p>
</div>
<div class="flex gap-4">
<button onclick="copyToClipboard('{{ $symbol }}')" class="flex-1 bg-brand-ocean hover:bg-brand-oceanSoft text-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"></i>
Copy Emoji
</button>
</div>
<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">
<div class="flex justify-between items-start mb-1">
<span class="text-xs font-mono text-gray-500">Shortcode</span>
<i data-lucide="copy" class="w-3 h-3 text-gray-600 group-hover:text-brand-oceanSoft transition-colors"></i>
</div>
<div class="font-mono text-brand-sun group-hover:text-brand-sunSoft font-medium truncate">{{ $shortcode }}</div>
</button>
@endif
@if($unified !== '')
<button onclick="copyToClipboard('{{ $unified }}')" class="glass-card p-4 rounded-xl group text-left">
<div class="flex justify-between items-start mb-1">
<span class="text-xs font-mono text-gray-500">Unicode</span>
<i data-lucide="copy" class="w-3 h-3 text-gray-600 group-hover:text-brand-oceanSoft transition-colors"></i>
</div>
<div class="font-mono text-gray-300 group-hover:text-white font-medium">{{ $unified }}</div>
</button>
@endif
@if($htmlHex !== '')
<button onclick="copyToClipboard('{{ $htmlHex }}')" class="glass-card p-4 rounded-xl group text-left">
<div class="flex justify-between items-start mb-1">
<span class="text-xs font-mono text-gray-500">HTML Entity</span>
<i data-lucide="copy" class="w-3 h-3 text-gray-600 group-hover:text-brand-oceanSoft transition-colors"></i>
</div>
<div class="font-mono text-gray-400 group-hover:text-white font-medium truncate">{{ $htmlHex }}</div>
</button>
@endif
@if($cssCode !== '')
<button onclick="copyToClipboard('{{ $cssCode }}')" class="glass-card p-4 rounded-xl group text-left">
<div class="flex justify-between items-start mb-1">
<span class="text-xs font-mono text-gray-500">CSS Content</span>
<i data-lucide="copy" class="w-3 h-3 text-gray-600 group-hover:text-brand-oceanSoft transition-colors"></i>
</div>
<div class="font-mono text-gray-400 group-hover:text-white font-medium">{{ $cssCode }}</div>
</button>
@endif
</div>
@if(count($keywords) > 0)
<div class="mt-2">
<div class="flex items-center gap-2 mb-4">
<i data-lucide="hash" class="w-4 h-4 text-brand-sun"></i>
<h3 class="text-sm font-bold text-gray-200 uppercase tracking-wide">Semantic Tags</h3>
</div>
<div class="flex flex-wrap gap-2">
@foreach($keywords as $tag)
<a href="{{ route('home') }}?q={{ urlencode($tag) }}" class="px-3 py-1.5 rounded-lg bg-white/5 hover:bg-brand-ocean/10 border border-white/5 hover:border-brand-ocean/30 text-sm text-gray-300 hover:text-brand-oceanSoft transition-all">{{ $tag }}</a>
@endforeach
</div>
</div>
@endif
<div class="mt-6 glass-card rounded-2xl p-5">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<i data-lucide="sparkles" class="w-4 h-4 text-brand-ocean"></i>
<h3 class="text-sm font-bold text-gray-200 uppercase tracking-wide">Your Keywords</h3>
</div>
@if ($isPersonal)
<button id="user-keyword-add" class="rounded-full bg-brand-ocean text-white text-xs font-semibold px-3 py-1.5">Add keyword</button>
@endif
</div>
@if ($isPersonal)
<div id="user-keyword-list" class="mt-4 flex flex-wrap gap-2">
@forelse ($userKeywords as $keyword)
<span class="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/5 border border-white/10 text-sm text-gray-200">
<span>{{ $keyword->keyword }}</span>
<span class="text-[10px] uppercase tracking-[0.2em] text-gray-500">{{ $keyword->lang ?? 'und' }}</span>
</span>
@empty
<span class="text-sm text-gray-400">No private keywords yet. Add one to personalize search.</span>
@endforelse
</div>
@elseif ($user)
<div class="mt-4 rounded-xl border border-brand-sun/30 bg-brand-sun/10 p-3 text-sm text-brand-sun">
Upgrade to Personal to add private keywords for this emoji.
</div>
<div class="mt-4 flex flex-wrap gap-2 opacity-70">
<span class="px-3 py-1.5 rounded-lg bg-white/5 border border-white/10 text-xs text-gray-300">your-tag-1</span>
<span class="px-3 py-1.5 rounded-lg bg-white/5 border border-white/10 text-xs text-gray-300">your-tag-2</span>
<span class="px-3 py-1.5 rounded-lg bg-white/5 border border-white/10 text-xs text-gray-300">your-tag-3</span>
<span class="px-3 py-1.5 rounded-lg bg-white/5 border border-white/10 text-xs text-gray-400">Unlock with Personal</span>
</div>
<a href="{{ route('pricing') }}" class="mt-3 inline-flex items-center justify-center rounded-full bg-brand-sun text-black font-semibold px-4 py-2 text-sm">Upgrade to Personal</a>
@else
<div class="mt-4 rounded-xl border border-white/10 bg-white/5 p-3 text-sm text-gray-300">
Sign up to personalize keywords and sync across devices.
</div>
<a href="{{ route('register') }}" class="mt-3 inline-flex items-center justify-center rounded-full bg-brand-sun text-black font-semibold px-4 py-2 text-sm">Sign up free</a>
@endif
</div>
</div>
</div>
<footer class="mt-auto pt-10 text-center text-gray-600 text-xs">Dewemoji Native Press 'C' to copy</footer>
</main>
</div>
<div id="toast" class="fixed bottom-10 left-1/2 -translate-x-1/2 translate-y-24 opacity-0 transition-all duration-300 z-50 pointer-events-none">
<div class="bg-brand-ocean text-white px-6 py-2 rounded-full font-bold shadow-[0_0_20px_rgba(32,83,255,0.45)] flex items-center gap-2">
<i data-lucide="check" class="w-4 h-4"></i>
<span id="toast-msg">Copied!</span>
</div>
</div>
<div id="user-keyword-modal" class="hidden fixed inset-0 z-50 items-center justify-center">
<div class="absolute inset-0 bg-black/70 backdrop-blur-sm"></div>
<div class="relative z-10 w-full max-w-lg rounded-3xl glass-card p-6">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold text-white">Add keyword</h3>
<button id="user-keyword-close" class="w-8 h-8 rounded-full bg-white/10 hover:bg-white/20 flex items-center justify-center text-gray-200">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
<form id="user-keyword-form" class="mt-4 grid gap-4">
<div>
<label class="text-xs uppercase tracking-[0.2em] text-gray-400">Keyword</label>
<input type="text" name="keyword" id="user-keyword-input" class="mt-2 w-full rounded-xl bg-black/40 border border-white/10 px-4 py-3 text-sm text-gray-200 focus:outline-none focus:border-brand-ocean" placeholder="magic" required>
</div>
<div>
<label class="text-xs uppercase tracking-[0.2em] text-gray-400">Language</label>
<input type="text" name="lang" id="user-keyword-lang" class="mt-2 w-full rounded-xl bg-black/40 border border-white/10 px-4 py-3 text-sm text-gray-200 focus:outline-none focus:border-brand-ocean" placeholder="en">
</div>
<div class="flex items-center justify-end gap-2">
<button type="button" id="user-keyword-cancel" class="rounded-full border border-white/10 px-4 py-2 text-sm text-gray-200 hover:bg-white/5">Cancel</button>
<button type="submit" class="rounded-full bg-brand-ocean text-white font-semibold px-5 py-2 text-sm">Save keyword</button>
</div>
</form>
</div>
</div>
@endsection
@push('scripts')
<script>
const RECENT_KEY = 'dewemoji_recent';
function loadRecent() {
try {
return JSON.parse(localStorage.getItem(RECENT_KEY) || '[]');
} catch {
return [];
}
}
function saveRecent(items) {
localStorage.setItem(RECENT_KEY, JSON.stringify(items.slice(0, 8)));
}
function addRecent(emoji) {
const curr = loadRecent().filter((e) => e !== emoji);
curr.unshift(emoji);
saveRecent(curr);
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
addRecent(text);
const toast = document.getElementById('toast');
const msg = document.getElementById('toast-msg');
msg.innerText = `Copied ${text}`;
toast.classList.remove('translate-y-24', 'opacity-0');
setTimeout(() => {
toast.classList.add('translate-y-24', 'opacity-0');
}, 1500);
});
}
document.addEventListener('keydown', (e) => {
if (e.key === 'c' && !e.metaKey && !e.ctrlKey && document.activeElement.tagName !== 'INPUT') {
copyToClipboard(@json($symbol));
}
});
// Treat opening the single-emoji page as a "recently viewed emoji" event.
addRecent(@json($symbol));
(() => {
const isPersonal = @json($isPersonal);
if (!isPersonal) return;
const modal = document.getElementById('user-keyword-modal');
const openBtn = document.getElementById('user-keyword-add');
const closeBtn = document.getElementById('user-keyword-close');
const cancelBtn = document.getElementById('user-keyword-cancel');
const form = document.getElementById('user-keyword-form');
const keywordInput = document.getElementById('user-keyword-input');
const langInput = document.getElementById('user-keyword-lang');
const list = document.getElementById('user-keyword-list');
const csrf = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
const openModal = () => {
modal.classList.remove('hidden');
modal.classList.add('flex');
keywordInput.value = '';
langInput.value = '';
keywordInput.focus();
};
const closeModal = () => {
modal.classList.add('hidden');
modal.classList.remove('flex');
};
openBtn?.addEventListener('click', openModal);
closeBtn?.addEventListener('click', closeModal);
cancelBtn?.addEventListener('click', closeModal);
modal?.addEventListener('click', (e) => {
if (e.target === modal) closeModal();
});
form?.addEventListener('submit', async (e) => {
e.preventDefault();
const payload = {
emoji_slug: @json($slug),
keyword: keywordInput.value.trim(),
lang: langInput.value.trim() || 'und',
};
if (!payload.keyword) return;
const res = await fetch('{{ route('dashboard.keywords.store') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
...(csrf ? { 'X-CSRF-TOKEN': csrf } : {}),
},
body: JSON.stringify(payload),
});
const data = await res.json().catch(() => null);
if (!res.ok || !data?.ok) {
showToast('Could not save keyword');
return;
}
const badge = document.createElement('span');
badge.className = 'inline-flex items-center gap-2 px-3 py-1.5 rounded-lg bg-white/5 border border-white/10 text-sm text-gray-200';
badge.innerHTML = `<span>${payload.keyword}</span><span class="text-[10px] uppercase tracking-[0.2em] text-gray-500">${payload.lang}</span>`;
if (list) {
const empty = list.querySelector('span.text-sm');
if (empty) empty.remove();
list.prepend(badge);
}
closeModal();
showToast('Keyword added');
});
})();
</script>
@endpush