693 lines
32 KiB
PHP
693 lines
32 KiB
PHP
<!doctype html>
|
|
<html lang="en" class="dark">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
@php
|
|
$canonicalPath = $canonicalPath ?? request()->getPathInfo();
|
|
$canonicalPath = $canonicalPath === '' ? '/' : $canonicalPath;
|
|
if ($canonicalPath !== '/') {
|
|
$canonicalPath = '/'.trim($canonicalPath, '/');
|
|
}
|
|
$canonicalUrl = rtrim(config('app.url', request()->getSchemeAndHttpHost()), '/').$canonicalPath;
|
|
$metaDescription = trim($__env->yieldContent('meta_description')) ?: 'Search, copy, and explore emojis with Dewemoji. Free browser extension and API for developers.';
|
|
$metaTitle = trim($__env->yieldContent('title')) ?: 'Dewemoji';
|
|
@endphp
|
|
<title>@yield('title', 'Dewemoji')</title>
|
|
<meta name="description" content="{{ $metaDescription }}">
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
<link rel="canonical" href="{{ $canonicalUrl }}">
|
|
<meta property="og:title" content="{{ $metaTitle }}">
|
|
<meta property="og:description" content="{{ $metaDescription }}">
|
|
<meta property="og:url" content="{{ $canonicalUrl }}">
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:site_name" content="Dewemoji">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="{{ $metaTitle }}">
|
|
<meta name="twitter:description" content="{{ $metaDescription }}">
|
|
<meta name="theme-color" content="#2053ff">
|
|
<link rel="icon" href="/favicon.ico">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="/assets/logo/logo-mark-128.png">
|
|
<link rel="icon" type="image/png" sizes="192x192" href="/assets/logo/logo-mark-512.png">
|
|
<link rel="apple-touch-icon" sizes="180x180" href="/assets/logo/logo-mark-512.png">
|
|
<script type="application/ld+json">
|
|
{
|
|
"@@context": "https://schema.org",
|
|
"@@graph": [
|
|
{
|
|
"@@type": "WebSite",
|
|
"name": "Dewemoji",
|
|
"url": "{{ rtrim(config('app.url', request()->getSchemeAndHttpHost()), '/') }}/",
|
|
"potentialAction": {
|
|
"@@type": "SearchAction",
|
|
"target": "{{ rtrim(config('app.url', request()->getSchemeAndHttpHost()), '/') }}/?q={search_term_string}",
|
|
"query-input": "required name=search_term_string"
|
|
}
|
|
},
|
|
{
|
|
"@@type": "Organization",
|
|
"name": "Dewemoji",
|
|
"url": "{{ rtrim(config('app.url', request()->getSchemeAndHttpHost()), '/') }}/",
|
|
"logo": "{{ rtrim(config('app.url', request()->getSchemeAndHttpHost()), '/') }}/assets/logo/logo-mark.svg"
|
|
}
|
|
]
|
|
}
|
|
</script>
|
|
<script>
|
|
(() => {
|
|
const stored = localStorage.getItem('dewemoji_theme');
|
|
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
const mode = stored || (prefersDark ? 'dark' : 'light');
|
|
const root = document.documentElement;
|
|
if (mode === 'dark') {
|
|
root.classList.add('dark');
|
|
root.classList.remove('theme-light');
|
|
} else {
|
|
root.classList.remove('dark');
|
|
root.classList.add('theme-light');
|
|
}
|
|
})();
|
|
</script>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<link rel="preload" href="/assets/fonts/PlusJakartaSans-Regular.ttf" as="font" type="font/ttf" crossorigin>
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
@vite(['resources/js/app.js'])
|
|
<script>
|
|
tailwind.config = {
|
|
darkMode: 'class',
|
|
theme: {
|
|
extend: {
|
|
fontFamily: {
|
|
sans: ['"Plus Jakarta Sans"', 'system-ui', 'sans-serif'],
|
|
display: ['"Plus Jakarta Sans"', 'system-ui', 'sans-serif'],
|
|
},
|
|
colors: {
|
|
brand: {
|
|
sun: '#fcb735',
|
|
sunSoft: '#ffda9c',
|
|
ocean: '#2053ff',
|
|
oceanSoft: '#356cf0',
|
|
},
|
|
},
|
|
animation: {
|
|
'float': 'float 6s ease-in-out infinite',
|
|
'pulse-slow': 'pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
|
},
|
|
keyframes: {
|
|
float: {
|
|
'0%, 100%': { transform: 'translateY(0)' },
|
|
'50%': { transform: 'translateY(-10px)' },
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
<style>
|
|
@font-face {
|
|
font-family: "Plus Jakarta Sans";
|
|
src: url("/assets/fonts/PlusJakartaSans-Light.ttf") format("truetype");
|
|
font-weight: 300;
|
|
font-style: normal;
|
|
font-display: swap;
|
|
}
|
|
@font-face {
|
|
font-family: "Plus Jakarta Sans";
|
|
src: url("/assets/fonts/PlusJakartaSans-Regular.ttf") format("truetype");
|
|
font-weight: 400;
|
|
font-style: normal;
|
|
font-display: swap;
|
|
}
|
|
@font-face {
|
|
font-family: "Plus Jakarta Sans";
|
|
src: url("/assets/fonts/PlusJakartaSans-Medium.ttf") format("truetype");
|
|
font-weight: 500;
|
|
font-style: normal;
|
|
font-display: swap;
|
|
}
|
|
@font-face {
|
|
font-family: "Plus Jakarta Sans";
|
|
src: url("/assets/fonts/PlusJakartaSans-SemiBold.ttf") format("truetype");
|
|
font-weight: 600;
|
|
font-style: normal;
|
|
font-display: swap;
|
|
}
|
|
@font-face {
|
|
font-family: "Plus Jakarta Sans";
|
|
src: url("/assets/fonts/PlusJakartaSans-Bold.ttf") format("truetype");
|
|
font-weight: 700;
|
|
font-style: normal;
|
|
font-display: swap;
|
|
}
|
|
@font-face {
|
|
font-family: "Plus Jakarta Sans";
|
|
src: url("/assets/fonts/PlusJakartaSans-LightItalic.ttf") format("truetype");
|
|
font-weight: 300;
|
|
font-style: italic;
|
|
font-display: swap;
|
|
}
|
|
@font-face {
|
|
font-family: "Plus Jakarta Sans";
|
|
src: url("/assets/fonts/PlusJakartaSans-Italic.ttf") format("truetype");
|
|
font-weight: 400;
|
|
font-style: italic;
|
|
font-display: swap;
|
|
}
|
|
@font-face {
|
|
font-family: "Plus Jakarta Sans";
|
|
src: url("/assets/fonts/PlusJakartaSans-MediumItalic.ttf") format("truetype");
|
|
font-weight: 500;
|
|
font-style: italic;
|
|
font-display: swap;
|
|
}
|
|
@font-face {
|
|
font-family: "Plus Jakarta Sans";
|
|
src: url("/assets/fonts/PlusJakartaSans-SemiBoldItalic.ttf") format("truetype");
|
|
font-weight: 600;
|
|
font-style: italic;
|
|
font-display: swap;
|
|
}
|
|
@font-face {
|
|
font-family: "Plus Jakarta Sans";
|
|
src: url("/assets/fonts/PlusJakartaSans-BoldItalic.ttf") format("truetype");
|
|
font-weight: 700;
|
|
font-style: italic;
|
|
font-display: swap;
|
|
}
|
|
:root {
|
|
--font-family: "Plus Jakarta Sans", system-ui, sans-serif;
|
|
--fs-xl: clamp(2.5rem, 5vw, 3rem);
|
|
--fs-l: clamp(1.875rem, 4vw, 2rem);
|
|
--fs-m: clamp(1.5rem, 3vw, 1.5rem);
|
|
--fs-s: 1.125rem;
|
|
--fs-xs: 0.875rem;
|
|
--fw-light: 300;
|
|
--fw-normal: 400;
|
|
--fw-medium: 500;
|
|
--fw-semibold: 600;
|
|
--fw-bold: 700;
|
|
--app-bg: #050505;
|
|
--app-fg: #f8fafc;
|
|
--muted-text: #9ca3af;
|
|
--muted-strong: #e5e7eb;
|
|
--muted-border: rgba(148,163,184,0.2);
|
|
--panel-bg: rgba(20, 20, 23, 0.6);
|
|
--panel-border: rgba(255, 255, 255, 0.08);
|
|
--card-bg: linear-gradient(145deg, rgba(255,255,255,0.03) 0%, rgba(255,255,255,0.01) 100%);
|
|
--card-border: rgba(255, 255, 255, 0.05);
|
|
--card-hover-bg: linear-gradient(145deg, rgba(255,255,255,0.07) 0%, rgba(255,255,255,0.02) 100%);
|
|
--card-hover-border: rgba(53, 108, 240, 0.35);
|
|
--header-bg: rgba(5, 5, 5, 0.85);
|
|
--header-border: rgba(255, 255, 255, 0.05);
|
|
--surface-bg: #151518;
|
|
--surface-border: rgba(255, 255, 255, 0.1);
|
|
--nav-bg: rgba(11, 11, 15, 0.95);
|
|
--code-bg: rgba(0,0,0,0.3);
|
|
--scrollbar-thumb: rgba(255,255,255,.1);
|
|
--scrollbar-thumb-hover: rgba(255,255,255,.2);
|
|
}
|
|
html.theme-light {
|
|
--app-bg: #f6f7fb;
|
|
--app-fg: #0f172a;
|
|
--muted-text: #64748b;
|
|
--muted-strong: #1f2937;
|
|
--muted-border: rgba(15,23,42,0.12);
|
|
--panel-bg: rgba(255, 255, 255, 0.78);
|
|
--panel-border: rgba(15, 23, 42, 0.08);
|
|
--card-bg: linear-gradient(145deg, rgba(15,23,42,0.04) 0%, rgba(15,23,42,0.02) 100%);
|
|
--card-border: rgba(15, 23, 42, 0.08);
|
|
--card-hover-bg: linear-gradient(145deg, rgba(15,23,42,0.06) 0%, rgba(15,23,42,0.02) 100%);
|
|
--card-hover-border: rgba(32, 83, 255, 0.2);
|
|
--header-bg: rgba(255, 255, 255, 0.85);
|
|
--header-border: rgba(15, 23, 42, 0.08);
|
|
--surface-bg: #ffffff;
|
|
--surface-border: rgba(15, 23, 42, 0.12);
|
|
--nav-bg: rgba(255, 255, 255, 0.9);
|
|
--code-bg: rgba(15,23,42,0.06);
|
|
--scrollbar-thumb: rgba(15,23,42,.18);
|
|
--scrollbar-thumb-hover: rgba(15,23,42,.28);
|
|
}
|
|
::-webkit-scrollbar { width: 6px; }
|
|
::-webkit-scrollbar-track { background: transparent; }
|
|
::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 10px; }
|
|
::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); }
|
|
.glass-panel {
|
|
background: var(--panel-bg);
|
|
backdrop-filter: blur(24px);
|
|
-webkit-backdrop-filter: blur(24px);
|
|
border: 1px solid var(--panel-border);
|
|
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
|
}
|
|
.glass-card {
|
|
background: var(--card-bg);
|
|
border: 1px solid var(--card-border);
|
|
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
}
|
|
.glass-card:hover {
|
|
background: var(--card-hover-bg);
|
|
border-color: var(--card-hover-border);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 10px 40px -10px rgba(32, 83, 255, 0.2);
|
|
}
|
|
.glass-header {
|
|
background: var(--header-bg);
|
|
backdrop-filter: blur(20px);
|
|
border-bottom: 1px solid var(--header-border);
|
|
}
|
|
.theme-surface {
|
|
background: var(--surface-bg) !important;
|
|
border-color: var(--surface-border) !important;
|
|
}
|
|
.theme-nav {
|
|
background: var(--nav-bg) !important;
|
|
border-color: var(--panel-border) !important;
|
|
}
|
|
.text-gradient {
|
|
background: linear-gradient(to right, #fcb735, #2053ff);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
}
|
|
.emoji-name-clamp {
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
line-height: 1.1;
|
|
max-height: 2.2em;
|
|
}
|
|
h1 { font-size: var(--fs-xl); font-weight: var(--fw-bold); line-height: 1.1; }
|
|
h2 { font-size: var(--fs-l); font-weight: var(--fw-semibold); line-height: 1.25; }
|
|
h3 { font-size: var(--fs-m); font-weight: var(--fw-semibold); line-height: 1.3; }
|
|
html.theme-light .text-white:not(.force-white) { color: var(--app-fg) !important; }
|
|
html.theme-light button.text-white,
|
|
html.theme-light a.text-white,
|
|
html.theme-light [role="button"].text-white,
|
|
html.theme-light button .text-white,
|
|
html.theme-light a .text-white,
|
|
html.theme-light [role="button"] .text-white {
|
|
color: #ffffff !important;
|
|
}
|
|
html.theme-light .text-gray-200 { color: #334155 !important; }
|
|
html.theme-light .text-gray-300 { color: #475569 !important; }
|
|
html.theme-light .text-gray-400 { color: var(--muted-text) !important; }
|
|
html.theme-light .text-gray-500 { color: #94a3b8 !important; }
|
|
html.theme-light .text-gray-100 { color: #1f2937 !important; }
|
|
html.theme-light .border-white\/5 { border-color: rgba(15,23,42,0.08) !important; }
|
|
html.theme-light .border-white\/10 { border-color: rgba(15,23,42,0.12) !important; }
|
|
html.theme-light .border-white\/20 { border-color: rgba(15,23,42,0.18) !important; }
|
|
html.theme-light .bg-white\/5 { background: rgba(15,23,42,0.04) !important; }
|
|
html.theme-light .bg-white\/10 { background: rgba(15,23,42,0.06) !important; }
|
|
html.theme-light .bg-white\/20 { background: rgba(15,23,42,0.1) !important; }
|
|
html.theme-light .bg-black\/30 { background: var(--code-bg) !important; }
|
|
html.theme-light .bg-black\/40 { background: var(--code-bg) !important; }
|
|
html.theme-light .copy-btn {
|
|
background: rgba(255,255,255,0.9);
|
|
border-color: rgba(15,23,42,0.2);
|
|
color: var(--app-fg);
|
|
}
|
|
html.theme-light .copy-btn:hover { background: rgba(32,83,255,0.18); }
|
|
html.theme-light .emoji-card {
|
|
background: #ffffff;
|
|
border-color: rgba(15,23,42,0.12);
|
|
box-shadow: 0 6px 18px rgba(15,23,42,0.08);
|
|
}
|
|
html.theme-light .emoji-card:hover {
|
|
border-color: rgba(32,83,255,0.35);
|
|
box-shadow: 0 10px 22px rgba(32,83,255,0.18);
|
|
}
|
|
html.theme-light .emoji-card-bar {
|
|
background: rgba(15,23,42,0.04);
|
|
border-top-color: rgba(15,23,42,0.1);
|
|
}
|
|
.offcanvas-panel {
|
|
background: var(--panel-bg);
|
|
border-color: var(--panel-border);
|
|
color: var(--app-fg);
|
|
}
|
|
.offcanvas-backdrop {
|
|
background: rgba(0,0,0,0.4);
|
|
}
|
|
html.theme-light .offcanvas-backdrop {
|
|
background: rgba(15,23,42,0.3);
|
|
}
|
|
.offcanvas-code {
|
|
background: var(--code-bg);
|
|
}
|
|
.cookie-banner {
|
|
background: var(--panel-bg);
|
|
border: 1px solid var(--panel-border);
|
|
color: var(--app-fg);
|
|
box-shadow: 0 12px 40px rgba(0,0,0,0.15);
|
|
}
|
|
.cookie-btn {
|
|
background: rgba(32,83,255,0.14);
|
|
border: 1px solid rgba(32,83,255,0.4);
|
|
color: var(--app-fg);
|
|
}
|
|
.cookie-btn:hover { background: rgba(32,83,255,0.22); }
|
|
.cookie-btn.secondary {
|
|
background: transparent;
|
|
border: 1px solid var(--muted-border);
|
|
color: var(--muted-text);
|
|
}
|
|
html.theme-light .cookie-btn.secondary { color: var(--app-fg); }
|
|
</style>
|
|
@stack('head')
|
|
@stack('jsonld')
|
|
</head>
|
|
<body class="bg-[var(--app-bg)] text-[var(--app-fg)] min-h-screen selection:bg-brand-ocean selection:text-white" style="font-family: var(--font-family); font-size: var(--fs-s); font-weight: var(--fw-normal); line-height: 1.6;">
|
|
<div class="fixed top-0 left-0 w-full h-full overflow-hidden -z-10 pointer-events-none">
|
|
<div class="absolute top-[-10%] right-[-5%] w-[500px] h-[500px] bg-brand-ocean/20 rounded-full blur-[120px] animate-pulse-slow"></div>
|
|
<div class="absolute bottom-[-10%] left-[-10%] w-[600px] h-[600px] bg-blue-900/10 rounded-full blur-[120px]"></div>
|
|
</div>
|
|
|
|
@yield('content')
|
|
|
|
@hasSection('mobile_nav')
|
|
@yield('mobile_nav')
|
|
@else
|
|
<nav class="lg:hidden fixed bottom-0 inset-x-0 z-50 border-t border-white/10 bg-[#0b0b0f]/95 backdrop-blur px-2 py-2 theme-nav">
|
|
<div class="grid grid-cols-4 gap-1 text-[11px]">
|
|
<a href="{{ route('home') }}" class="flex flex-col items-center justify-center gap-1 rounded-lg py-2 {{ request()->routeIs('home') ? 'text-brand-sun bg-white/5' : 'text-gray-300 hover:bg-white/5' }}">
|
|
<i data-lucide="layout-grid" class="w-4 h-4"></i><span>Discover</span>
|
|
</a>
|
|
<a href="{{ route('api-docs') }}" class="flex flex-col items-center justify-center gap-1 rounded-lg py-2 {{ request()->routeIs('api-docs') ? 'text-brand-sun bg-white/5' : 'text-gray-300 hover:bg-white/5' }}">
|
|
<i data-lucide="book-open" class="w-4 h-4"></i><span>Docs</span>
|
|
</a>
|
|
<a href="{{ route('pricing') }}" class="flex flex-col items-center justify-center gap-1 rounded-lg py-2 {{ request()->routeIs('pricing') ? 'text-brand-sun bg-white/5' : 'text-gray-300 hover:bg-white/5' }}">
|
|
<i data-lucide="badge-dollar-sign" class="w-4 h-4"></i><span>Pricing</span>
|
|
</a>
|
|
<button id="more-menu-btn" class="flex flex-col items-center justify-center gap-1 rounded-lg py-2 text-gray-300 hover:bg-white/5">
|
|
<i data-lucide="more-horizontal" class="w-4 h-4"></i><span>More</span>
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
@endif
|
|
|
|
@hasSection('more_menu')
|
|
@yield('more_menu')
|
|
@else
|
|
<div id="more-menu-backdrop" class="lg:hidden fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden"></div>
|
|
<div id="more-menu" class="lg:hidden fixed bottom-16 left-4 right-4 glass-panel rounded-2xl p-4 z-50 hidden">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<span class="text-sm font-semibold">More</span>
|
|
<button id="more-menu-close" class="w-8 h-8 rounded-full bg-white/5 hover:bg-white/10 flex items-center justify-center">
|
|
<i data-lucide="x" class="w-4 h-4"></i>
|
|
</button>
|
|
</div>
|
|
<div class="flex flex-col gap-2 text-sm">
|
|
@auth
|
|
<a href="{{ route('dashboard.overview') }}" class="flex items-center gap-3 rounded-xl px-4 py-3 bg-white/5 hover:bg-white/10">
|
|
<i data-lucide="layout-dashboard" class="w-4 h-4"></i><span>Dashboard</span>
|
|
</a>
|
|
@endauth
|
|
@guest
|
|
<a href="{{ route('login') }}" class="flex items-center gap-3 rounded-xl px-4 py-3 bg-white/5 hover:bg-white/10">
|
|
<i data-lucide="log-in" class="w-4 h-4"></i><span>Login</span>
|
|
</a>
|
|
@endguest
|
|
<a href="{{ route('download') }}" class="flex items-center gap-3 rounded-xl px-4 py-3 bg-white/5 hover:bg-white/10">
|
|
<i data-lucide="download" class="w-4 h-4"></i><span>Download</span>
|
|
</a>
|
|
<a href="{{ route('support') }}" class="flex items-center gap-3 rounded-xl px-4 py-3 bg-white/5 hover:bg-white/10">
|
|
<i data-lucide="life-buoy" class="w-4 h-4"></i><span>Support</span>
|
|
</a>
|
|
<a href="{{ route('privacy') }}" class="flex items-center gap-3 rounded-xl px-4 py-3 bg-white/5 hover:bg-white/10">
|
|
<i data-lucide="shield-check" class="w-4 h-4"></i><span>Privacy</span>
|
|
</a>
|
|
<a href="{{ route('terms') }}" class="flex items-center gap-3 rounded-xl px-4 py-3 bg-white/5 hover:bg-white/10">
|
|
<i data-lucide="file-text" class="w-4 h-4"></i><span>Terms</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<div id="cookie-banner" class="cookie-banner fixed bottom-6 left-6 right-6 md:right-8 md:left-auto md:max-w-xl rounded-2xl p-4 md:p-5 hidden z-50">
|
|
<div class="flex flex-col gap-3">
|
|
<div>
|
|
<p class="text-sm font-semibold">Cookies & analytics</p>
|
|
<p class="text-xs text-gray-400">We use cookies to measure usage and improve Dewemoji. No tracking on staging. You can change this anytime.</p>
|
|
</div>
|
|
<div class="flex flex-wrap gap-2">
|
|
<button id="cookie-accept" class="cookie-btn rounded-xl px-3 py-2 text-xs font-semibold">Accept analytics</button>
|
|
<button id="cookie-decline" class="cookie-btn secondary rounded-xl px-3 py-2 text-xs font-semibold">Decline</button>
|
|
<a href="/privacy" class="text-xs text-gray-400 underline underline-offset-2">Privacy</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="confirm-dialog" class="fixed inset-0 z-[100] hidden items-center justify-center">
|
|
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm"></div>
|
|
<div class="relative w-full max-w-md rounded-2xl border border-slate-200 bg-white p-6 text-slate-900 shadow-2xl dark:border-white/10 dark:bg-slate-950 dark:text-white">
|
|
<div class="text-xs uppercase tracking-[0.25em] text-slate-400" id="confirm-title">Confirm action</div>
|
|
<div class="mt-3 text-lg font-semibold" id="confirm-message">Are you sure?</div>
|
|
<div class="mt-6 flex items-center justify-end gap-2">
|
|
<button id="confirm-cancel" class="rounded-full border border-slate-200 px-4 py-2 text-sm text-slate-600 hover:bg-slate-100 dark:border-white/10 dark:text-slate-300 dark:hover:bg-white/5">
|
|
Cancel
|
|
</button>
|
|
<button id="confirm-ok" class="rounded-full bg-rose-500 px-4 py-2 text-sm font-semibold text-white hover:bg-rose-600">
|
|
Confirm
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>lucide.createIcons();</script>
|
|
<script>
|
|
(() => {
|
|
const root = document.documentElement;
|
|
const toggle = document.getElementById('theme-toggle');
|
|
const iconDark = document.querySelector('[data-theme-icon="dark"]');
|
|
const iconLight = document.querySelector('[data-theme-icon="light"]');
|
|
|
|
const setTheme = (mode) => {
|
|
if (mode === 'dark') {
|
|
root.classList.add('dark');
|
|
root.classList.remove('theme-light');
|
|
} else {
|
|
root.classList.remove('dark');
|
|
root.classList.add('theme-light');
|
|
}
|
|
localStorage.setItem('dewemoji_theme', mode);
|
|
if (iconDark && iconLight) {
|
|
iconDark.classList.toggle('hidden', mode !== 'dark');
|
|
iconLight.classList.toggle('hidden', mode === 'dark');
|
|
}
|
|
};
|
|
|
|
const stored = localStorage.getItem('dewemoji_theme');
|
|
if (stored) setTheme(stored);
|
|
else setTheme(root.classList.contains('dark') ? 'dark' : 'light');
|
|
|
|
if (toggle) {
|
|
toggle.addEventListener('click', () => {
|
|
const next = root.classList.contains('dark') ? 'light' : 'dark';
|
|
setTheme(next);
|
|
});
|
|
}
|
|
})();
|
|
</script>
|
|
<script>
|
|
(() => {
|
|
const moreMenuBtn = document.getElementById('more-menu-btn');
|
|
const moreMenu = document.getElementById('more-menu');
|
|
const moreMenuBackdrop = document.getElementById('more-menu-backdrop');
|
|
const moreMenuClose = document.getElementById('more-menu-close');
|
|
|
|
const openMoreMenu = () => {
|
|
if (moreMenu) moreMenu.classList.remove('hidden');
|
|
if (moreMenuBackdrop) moreMenuBackdrop.classList.remove('hidden');
|
|
};
|
|
const closeMoreMenu = () => {
|
|
if (moreMenu) moreMenu.classList.add('hidden');
|
|
if (moreMenuBackdrop) moreMenuBackdrop.classList.add('hidden');
|
|
};
|
|
|
|
if (moreMenuBtn) moreMenuBtn.addEventListener('click', openMoreMenu);
|
|
if (moreMenuBackdrop) moreMenuBackdrop.addEventListener('click', closeMoreMenu);
|
|
if (moreMenuClose) moreMenuClose.addEventListener('click', closeMoreMenu);
|
|
})();
|
|
</script>
|
|
<script>
|
|
(() => {
|
|
const GA_ID = 'G-R7FYYRBVJK';
|
|
const allowedHosts = new Set(['dewemoji.com', 'www.dewemoji.com']);
|
|
const consentKey = 'dewemoji_cookie_consent';
|
|
const banner = document.getElementById('cookie-banner');
|
|
const acceptBtn = document.getElementById('cookie-accept');
|
|
const declineBtn = document.getElementById('cookie-decline');
|
|
|
|
const loadGA = () => {
|
|
if (!allowedHosts.has(window.location.hostname)) return;
|
|
if (window.__dewemojiGaLoaded) return;
|
|
window.__dewemojiGaLoaded = true;
|
|
const script = document.createElement('script');
|
|
script.async = true;
|
|
script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_ID}`;
|
|
document.head.appendChild(script);
|
|
window.dataLayer = window.dataLayer || [];
|
|
function gtag(){ dataLayer.push(arguments); }
|
|
gtag('js', new Date());
|
|
gtag('config', GA_ID, { anonymize_ip: true });
|
|
};
|
|
|
|
const consent = localStorage.getItem(consentKey);
|
|
if (!consent && banner) banner.classList.remove('hidden');
|
|
if (consent === 'accepted') loadGA();
|
|
|
|
if (acceptBtn) {
|
|
acceptBtn.addEventListener('click', () => {
|
|
localStorage.setItem(consentKey, 'accepted');
|
|
if (banner) banner.classList.add('hidden');
|
|
loadGA();
|
|
});
|
|
}
|
|
if (declineBtn) {
|
|
declineBtn.addEventListener('click', () => {
|
|
localStorage.setItem(consentKey, 'declined');
|
|
if (banner) banner.classList.add('hidden');
|
|
});
|
|
}
|
|
})();
|
|
</script>
|
|
<script>
|
|
(() => {
|
|
const dialog = document.getElementById('confirm-dialog');
|
|
const titleEl = document.getElementById('confirm-title');
|
|
const msgEl = document.getElementById('confirm-message');
|
|
const okBtn = document.getElementById('confirm-ok');
|
|
const cancelBtn = document.getElementById('confirm-cancel');
|
|
let resolver = null;
|
|
|
|
const close = (result) => {
|
|
if (!dialog) return;
|
|
dialog.classList.add('hidden');
|
|
dialog.classList.remove('flex');
|
|
if (resolver) resolver(result);
|
|
resolver = null;
|
|
};
|
|
|
|
window.dewemojiConfirm = (message, options = {}) => {
|
|
if (!dialog) return Promise.resolve(false);
|
|
if (titleEl) titleEl.textContent = options.title || 'Confirm action';
|
|
if (msgEl) msgEl.textContent = message || 'Are you sure?';
|
|
if (okBtn) okBtn.textContent = options.okText || 'Confirm';
|
|
if (cancelBtn) cancelBtn.textContent = options.cancelText || 'Cancel';
|
|
if (okBtn) {
|
|
okBtn.classList.toggle('bg-rose-500', options.danger !== false);
|
|
okBtn.classList.toggle('hover:bg-rose-600', options.danger !== false);
|
|
okBtn.classList.toggle('bg-brand-ocean', options.danger === false);
|
|
okBtn.classList.toggle('hover:bg-brand-ocean/90', options.danger === false);
|
|
}
|
|
dialog.classList.remove('hidden');
|
|
dialog.classList.add('flex');
|
|
return new Promise((resolve) => {
|
|
resolver = resolve;
|
|
});
|
|
};
|
|
|
|
okBtn?.addEventListener('click', () => close(true));
|
|
cancelBtn?.addEventListener('click', () => close(false));
|
|
dialog?.addEventListener('click', (event) => {
|
|
if (event.target === dialog) close(false);
|
|
});
|
|
dialog?.querySelector(':scope > div.absolute')?.addEventListener('click', () => close(false));
|
|
document.addEventListener('keydown', (event) => {
|
|
if (dialog?.classList.contains('hidden')) return;
|
|
if (event.key === 'Escape') close(false);
|
|
});
|
|
|
|
document.addEventListener('submit', async (event) => {
|
|
const form = event.target;
|
|
if (!(form instanceof HTMLFormElement)) return;
|
|
const message = form.getAttribute('data-confirm');
|
|
if (!message || form.dataset.confirmed === 'true') return;
|
|
event.preventDefault();
|
|
const ok = await window.dewemojiConfirm(message, {
|
|
title: form.getAttribute('data-confirm-title') || undefined,
|
|
okText: form.getAttribute('data-confirm-ok') || undefined,
|
|
cancelText: form.getAttribute('data-confirm-cancel') || undefined,
|
|
danger: form.getAttribute('data-confirm-danger') !== 'false',
|
|
});
|
|
if (ok) {
|
|
form.dataset.confirmed = 'true';
|
|
form.submit();
|
|
}
|
|
});
|
|
})();
|
|
</script>
|
|
<script>
|
|
(() => {
|
|
const alpha = 'abcdefghijklmnopqrstuvwxyz';
|
|
const deprecatedAliases = {
|
|
in: 'id',
|
|
iw: 'he',
|
|
ji: 'yi',
|
|
jw: 'jv',
|
|
};
|
|
|
|
const makeIso6391Codes = () => {
|
|
const codes = [];
|
|
for (let i = 0; i < alpha.length; i += 1) {
|
|
for (let j = 0; j < alpha.length; j += 1) {
|
|
codes.push(alpha[i] + alpha[j]);
|
|
}
|
|
}
|
|
return codes;
|
|
};
|
|
|
|
const canonicalLanguageCode = (raw) => {
|
|
const code = String(raw || '').trim().toLowerCase();
|
|
if (!code) return '';
|
|
return deprecatedAliases[code] || code;
|
|
};
|
|
|
|
window.dewemojiCanonicalLanguageCode = canonicalLanguageCode;
|
|
|
|
window.dewemojiPopulateLanguageSelect = (target) => {
|
|
const select = typeof target === 'string' ? document.getElementById(target) : target;
|
|
if (!(select instanceof HTMLSelectElement) || select.dataset.generated === '1') return;
|
|
|
|
const existing = Array.from(select.options).map((opt) => ({
|
|
code: canonicalLanguageCode(opt.value),
|
|
label: (opt.textContent || '').trim(),
|
|
})).filter((row) => row.code !== '');
|
|
|
|
const byCode = new Map(existing.map((row) => [row.code.toLowerCase(), row]));
|
|
|
|
if (typeof Intl !== 'undefined' && typeof Intl.DisplayNames === 'function') {
|
|
const dn = new Intl.DisplayNames(['en'], { type: 'language' });
|
|
makeIso6391Codes().forEach((code) => {
|
|
const name = dn.of(code);
|
|
if (!name) return;
|
|
// Unknown/unsupported codes are echoed back by many engines.
|
|
if (name.toLowerCase() === code) return;
|
|
const canonical = canonicalLanguageCode(code);
|
|
if (!canonical) return;
|
|
byCode.set(canonical, { code: canonical, label: `${name} (${canonical})` });
|
|
});
|
|
}
|
|
|
|
const rows = Array.from(byCode.values()).sort((a, b) => {
|
|
if (a.code === 'und') return -1;
|
|
if (b.code === 'und') return 1;
|
|
return a.label.localeCompare(b.label);
|
|
});
|
|
|
|
const previous = (select.value || '').trim().toLowerCase();
|
|
select.innerHTML = '';
|
|
rows.forEach((row) => {
|
|
const option = document.createElement('option');
|
|
option.value = row.code;
|
|
option.textContent = row.label;
|
|
select.appendChild(option);
|
|
});
|
|
|
|
select.value = previous || select.value;
|
|
select.dataset.generated = '1';
|
|
};
|
|
})();
|
|
</script>
|
|
@stack('scripts')
|
|
</body>
|
|
</html>
|