feat: ui polish, docs, api hardening, and common pages
This commit is contained in:
@@ -70,6 +70,7 @@ DEWEMOJI_MAX_LIMIT=50
|
||||
DEWEMOJI_FREE_MAX_LIMIT=20
|
||||
DEWEMOJI_PRO_MAX_LIMIT=50
|
||||
DEWEMOJI_FREE_DAILY_LIMIT=30
|
||||
DEWEMOJI_RATE_LIMIT_ENABLED=true
|
||||
DEWEMOJI_LICENSE_ACCEPT_ALL=true
|
||||
DEWEMOJI_PRO_KEYS=
|
||||
DEWEMOJI_LICENSE_MAX_DEVICES=3
|
||||
@@ -84,6 +85,7 @@ DEWEMOJI_MAYAR_ENABLED=false
|
||||
DEWEMOJI_MAYAR_VERIFY_URL=
|
||||
DEWEMOJI_MAYAR_API_BASE=
|
||||
DEWEMOJI_MAYAR_ENDPOINT_VERIFY=/v1/license/verify
|
||||
DEWEMOJI_MAYAR_PRODUCT_IDS=
|
||||
DEWEMOJI_MAYAR_API_KEY=
|
||||
DEWEMOJI_MAYAR_SECRET_KEY=
|
||||
DEWEMOJI_MAYAR_TIMEOUT=8
|
||||
|
||||
@@ -398,6 +398,23 @@ class EmojiApiController extends Controller
|
||||
private function trackDailyUsage(Request $request, string $q, string $category, string $subcategory): array
|
||||
{
|
||||
$dailyLimit = max((int) config('dewemoji.free_daily_limit', 30), 1);
|
||||
$rateLimitEnabled = (bool) config('dewemoji.rate_limit_enabled', true);
|
||||
|
||||
// Local development should not silently look broken because of daily metering.
|
||||
if (!$rateLimitEnabled || app()->environment('local')) {
|
||||
return [
|
||||
'blocked' => false,
|
||||
'meta' => [
|
||||
'used' => 0,
|
||||
'limit' => $dailyLimit,
|
||||
'remaining' => $dailyLimit,
|
||||
'window' => 'daily',
|
||||
'window_ends_at' => Carbon::tomorrow('UTC')->toIso8601String(),
|
||||
'count_basis' => 'distinct_query',
|
||||
'metering' => 'disabled',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$key = trim((string) $request->query('key', ''));
|
||||
if ($key === '') {
|
||||
|
||||
@@ -131,10 +131,14 @@ class SiteController extends Controller
|
||||
|
||||
$items = $decoded['emojis'] ?? [];
|
||||
$match = null;
|
||||
$byEmoji = [];
|
||||
foreach ($items as $item) {
|
||||
$char = (string) ($item['emoji'] ?? '');
|
||||
if ($char !== '' && !isset($byEmoji[$char])) {
|
||||
$byEmoji[$char] = $item;
|
||||
}
|
||||
if (($item['slug'] ?? '') === $slug) {
|
||||
$match = $item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,8 +146,20 @@ class SiteController extends Controller
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$relatedDetails = [];
|
||||
foreach (array_slice($match['related'] ?? [], 0, 8) as $relatedEmoji) {
|
||||
$relatedEmoji = (string) $relatedEmoji;
|
||||
$ref = $byEmoji[$relatedEmoji] ?? null;
|
||||
$relatedDetails[] = [
|
||||
'emoji' => $relatedEmoji,
|
||||
'slug' => (string) ($ref['slug'] ?? ''),
|
||||
'name' => (string) ($ref['name'] ?? $relatedEmoji),
|
||||
];
|
||||
}
|
||||
|
||||
return view('site.emoji-detail', [
|
||||
'emoji' => $match,
|
||||
'relatedDetails' => $relatedDetails,
|
||||
'canonicalPath' => '/emoji/'.$slug,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -175,6 +175,15 @@ class LicenseVerificationService
|
||||
}
|
||||
|
||||
$purchase = is_array($json['purchase'] ?? null) ? $json['purchase'] : [];
|
||||
if (!$this->isTruthy($purchase['is_valid'] ?? true)) {
|
||||
continue;
|
||||
}
|
||||
if ($this->isTruthy($purchase['refunded'] ?? false)) {
|
||||
continue;
|
||||
}
|
||||
if ($this->isTruthy($purchase['chargebacked'] ?? false)) {
|
||||
continue;
|
||||
}
|
||||
$isRecurring = !empty($purchase['recurrence']);
|
||||
|
||||
return [
|
||||
@@ -231,6 +240,10 @@ class LicenseVerificationService
|
||||
if ($apiKey === '') {
|
||||
$apiKey = trim((string) config('dewemoji.billing.providers.mayar.secret_key', ''));
|
||||
}
|
||||
$productIds = config('dewemoji.billing.providers.mayar.product_ids', []);
|
||||
if (!is_array($productIds)) {
|
||||
$productIds = [];
|
||||
}
|
||||
if ($url === '') {
|
||||
return ['ok' => false, 'err' => 'mayar_missing_url'];
|
||||
}
|
||||
@@ -240,10 +253,15 @@ class LicenseVerificationService
|
||||
$request = Http::timeout($timeout)
|
||||
->withHeaders(['Accept' => 'application/json']);
|
||||
if ($apiKey !== '') {
|
||||
$request = $request->withToken($apiKey);
|
||||
$request = $request->withToken($apiKey)
|
||||
->withHeaders(['X-API-Key' => $apiKey]);
|
||||
}
|
||||
|
||||
$response = $request->post($url, ['license_key' => $key]);
|
||||
$response = $request->post($url, [
|
||||
'license_key' => $key,
|
||||
'license' => $key,
|
||||
'key' => $key,
|
||||
]);
|
||||
|
||||
if (!$response->successful()) {
|
||||
return ['ok' => false, 'err' => 'mayar_http_'.$response->status()];
|
||||
@@ -255,17 +273,38 @@ class LicenseVerificationService
|
||||
}
|
||||
|
||||
$data = is_array($json['data'] ?? null) ? $json['data'] : [];
|
||||
$valid = (($json['success'] ?? false) === true) || (($json['valid'] ?? false) === true) || (($data['valid'] ?? false) === true);
|
||||
$status = strtolower((string) ($data['status'] ?? $json['status'] ?? ''));
|
||||
$valid = (($json['success'] ?? false) === true)
|
||||
|| (($json['valid'] ?? false) === true)
|
||||
|| (($data['valid'] ?? false) === true)
|
||||
|| in_array($status, ['active', 'paid', 'completed', 'valid'], true);
|
||||
if (!$valid) {
|
||||
return ['ok' => false, 'err' => 'mayar_invalid'];
|
||||
}
|
||||
|
||||
$productId = $this->firstString([
|
||||
$data['product_id'] ?? null,
|
||||
$data['productId'] ?? null,
|
||||
$data['product_code'] ?? null,
|
||||
$json['product_id'] ?? null,
|
||||
]);
|
||||
if (!empty($productIds) && ($productId === null || !in_array($productId, $productIds, true))) {
|
||||
return ['ok' => false, 'err' => 'mayar_no_match'];
|
||||
}
|
||||
|
||||
$planType = strtolower((string) ($data['type'] ?? 'lifetime'));
|
||||
$expiresAt = $this->firstString([
|
||||
$data['expires_at'] ?? null,
|
||||
$data['expired_at'] ?? null,
|
||||
$data['expiry_date'] ?? null,
|
||||
$data['valid_until'] ?? null,
|
||||
$json['expires_at'] ?? null,
|
||||
]);
|
||||
return [
|
||||
'ok' => true,
|
||||
'plan' => 'pro',
|
||||
'product_id' => (string) ($data['product_id'] ?? '') ?: null,
|
||||
'expires_at' => isset($data['expires_at']) ? (string) $data['expires_at'] : null,
|
||||
'product_id' => $productId,
|
||||
'expires_at' => $expiresAt,
|
||||
'meta' => [
|
||||
'plan_type' => $planType,
|
||||
],
|
||||
@@ -332,4 +371,38 @@ class LicenseVerificationService
|
||||
'details' => $details,
|
||||
];
|
||||
}
|
||||
|
||||
private function isTruthy(mixed $value): bool
|
||||
{
|
||||
if (is_bool($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (is_numeric($value)) {
|
||||
return (int) $value === 1;
|
||||
}
|
||||
if (is_string($value)) {
|
||||
$normalized = strtolower(trim($value));
|
||||
return in_array($normalized, ['1', 'true', 'yes', 'y', 'on'], true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int,mixed> $values
|
||||
*/
|
||||
private function firstString(array $values): ?string
|
||||
{
|
||||
foreach ($values as $value) {
|
||||
if (!is_string($value)) {
|
||||
continue;
|
||||
}
|
||||
$value = trim($value);
|
||||
if ($value !== '') {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'data_path' => env('DEWEMOJI_DATA_PATH', base_path('data/emojis.json')),
|
||||
'data_path' => env('DEWEMOJI_DATA_PATH') ?: base_path('data/emojis.json'),
|
||||
|
||||
'pagination' => [
|
||||
'default_limit' => (int) env('DEWEMOJI_DEFAULT_LIMIT', 20),
|
||||
@@ -11,6 +11,7 @@ return [
|
||||
],
|
||||
|
||||
'free_daily_limit' => (int) env('DEWEMOJI_FREE_DAILY_LIMIT', 30),
|
||||
'rate_limit_enabled' => filter_var(env('DEWEMOJI_RATE_LIMIT_ENABLED', true), FILTER_VALIDATE_BOOL),
|
||||
|
||||
'license' => [
|
||||
'accept_all' => filter_var(env('DEWEMOJI_LICENSE_ACCEPT_ALL', false), FILTER_VALIDATE_BOOL),
|
||||
@@ -36,6 +37,7 @@ return [
|
||||
'verify_url' => env('DEWEMOJI_MAYAR_VERIFY_URL', ''),
|
||||
'api_base' => env('DEWEMOJI_MAYAR_API_BASE', ''),
|
||||
'endpoint_verify' => env('DEWEMOJI_MAYAR_ENDPOINT_VERIFY', '/v1/license/verify'),
|
||||
'product_ids' => array_values(array_filter(array_map('trim', explode(',', (string) env('DEWEMOJI_MAYAR_PRODUCT_IDS', ''))))),
|
||||
'api_key' => env('DEWEMOJI_MAYAR_API_KEY', ''),
|
||||
'secret_key' => env('DEWEMOJI_MAYAR_SECRET_KEY', ''),
|
||||
'timeout' => (int) env('DEWEMOJI_MAYAR_TIMEOUT', 8),
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
User-agent: *
|
||||
Disallow:
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://dewemoji.com/sitemap.xml
|
||||
|
||||
@@ -3,6 +3,29 @@
|
||||
@section('title', 'API Docs - Dewemoji')
|
||||
@section('meta_description', 'Dewemoji API docs for emoji search, categories, license verification, activation, and system endpoints.')
|
||||
|
||||
@push('head')
|
||||
<style>
|
||||
.doc-table tbody tr { border-bottom: 1px solid rgba(148,163,184,0.2); }
|
||||
.doc-table tbody tr:hover { background: rgba(32,83,255,0.08); }
|
||||
.codewrap { position: relative; }
|
||||
.copy-btn {
|
||||
position: absolute;
|
||||
top: .5rem;
|
||||
right: .5rem;
|
||||
font-size: .7rem;
|
||||
padding: .2rem .45rem;
|
||||
border-radius: .375rem;
|
||||
border: 1px solid rgba(255,255,255,0.15);
|
||||
background: rgba(15, 15, 20, 0.7);
|
||||
color: #e5e7eb;
|
||||
}
|
||||
.copy-btn:hover { background: rgba(32,83,255,0.35); }
|
||||
@media (max-width: 1023px) {
|
||||
#toc-aside { position: static !important; top: auto !important; }
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@push('jsonld')
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
@@ -16,27 +39,335 @@
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-5xl mx-auto px-4 sm:px-6 py-10">
|
||||
<h1 class="font-display text-4xl font-bold">API Docs</h1>
|
||||
<p class="text-gray-400 mt-2">Base URL: <code>{{ url('/v1') }}</code></p>
|
||||
<div class="flex h-screen w-full">
|
||||
<aside class="hidden lg:flex w-20 lg:w-64 h-full glass-panel flex-col justify-between p-4 z-50 shrink-0">
|
||||
<div>
|
||||
<div class="flex items-center gap-3 px-2 mb-8 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 shrink-0">
|
||||
<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-1">
|
||||
<a href="{{ route('home') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="layout-grid" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Discover</span></a>
|
||||
<a href="{{ route('api-docs') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl bg-white/10 text-brand-sun border border-white/5 transition-all"><i data-lucide="book-open" class="w-5 h-5"></i><span class="text-sm hidden lg:block">API Docs</span></a>
|
||||
<a href="{{ route('pricing') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="badge-dollar-sign" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Pricing</span></a>
|
||||
<a href="{{ route('support') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="life-buoy" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Support</span></a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<a href="{{ route('privacy') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="shield-check" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Privacy</span></a>
|
||||
<a href="{{ route('terms') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="file-text" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Terms</span></a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="glass-card rounded-2xl p-5 mt-6">
|
||||
<h2 class="text-lg font-semibold">Core endpoints</h2>
|
||||
<ul class="mt-3 space-y-1 text-sm text-gray-300">
|
||||
<li><code>GET /v1/categories</code></li>
|
||||
<li><code>GET /v1/emojis?q=<term>&category=<label>&subcategory=<slug></code></li>
|
||||
<li><code>GET /v1/emoji/{slug}</code> or <code>/v1/emoji?slug=</code></li>
|
||||
<li><code>POST /v1/license/verify</code></li>
|
||||
<li><code>POST /v1/license/activate</code></li>
|
||||
<li><code>POST /v1/license/deactivate</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
<main class="flex-1 flex flex-col h-full min-w-0 relative z-10">
|
||||
<header class="glass-header px-6 py-5 shrink-0">
|
||||
<div class="text-[11px] uppercase tracking-wider text-gray-500 mb-1">Public / Documentation</div>
|
||||
<h1 class="font-display text-2xl md:text-3xl font-bold">API Documentation</h1>
|
||||
</header>
|
||||
|
||||
<div class="glass-card rounded-2xl p-5 mt-4">
|
||||
<h2 class="text-lg font-semibold">Auth</h2>
|
||||
<p class="text-sm text-gray-300 mt-2">Use <code>Authorization: Bearer YOUR_LICENSE_KEY</code> (preferred) or <code>X-License-Key</code>.</p>
|
||||
<pre class="mt-3 text-xs overflow-x-auto bg-black/30 rounded-lg p-3"><code>curl -H "Authorization: Bearer YOUR_LICENSE_KEY" \
|
||||
"{{ url('/v1/emojis') }}?q=love&limit=50&page=1"</code></pre>
|
||||
</div>
|
||||
<div class="flex-1 overflow-y-auto p-6 md:p-10">
|
||||
<div id="docs-wrap" class="max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-12 gap-8 items-start">
|
||||
<aside id="toc-aside" class="lg:col-span-3 self-start sticky top-28">
|
||||
<nav class="space-y-2 text-sm">
|
||||
<p class="uppercase tracking-wide text-gray-500 font-semibold mb-2">On this page</p>
|
||||
<a href="#overview" class="block px-3 py-2 rounded hover:bg-white/5">Overview</a>
|
||||
<a href="#auth" class="block px-3 py-2 rounded hover:bg-white/5">Authentication</a>
|
||||
<a href="#plans" class="block px-3 py-2 rounded hover:bg-white/5">Plans & limits</a>
|
||||
<a href="#endpoints" class="block px-3 py-2 rounded hover:bg-white/5">Endpoints</a>
|
||||
<a href="#try-it" class="block px-3 py-2 rounded hover:bg-white/5">Try it</a>
|
||||
<a href="#errors" class="block px-3 py-2 rounded hover:bg-white/5">Errors & caching</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<section class="lg:col-span-9 space-y-8">
|
||||
<section id="overview" class="glass-card rounded-2xl p-6 scroll-mt-28">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<h2 class="font-display text-2xl font-bold">Emoji API Docs</h2>
|
||||
<span id="pro-badge" class="hidden text-[10px] font-semibold px-2 py-1 rounded bg-emerald-500/20 text-emerald-300 border border-emerald-400/40">Pro mode</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-300">Read-only API with search, categories, and emoji detail. Responses are cacheable and include ETag.</p>
|
||||
<div class="codewrap mt-4">
|
||||
<button class="copy-btn" data-copy-target="base-url-code">Copy</button>
|
||||
<pre class="text-xs overflow-x-auto bg-black/30 rounded-lg p-3"><code id="base-url-code">{{ url('/v1') }}</code></pre>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="auth" class="glass-card rounded-2xl p-6 scroll-mt-28">
|
||||
<h3 class="text-lg font-semibold">Authentication</h3>
|
||||
<p class="text-sm text-gray-300 mt-2">Free tier is public with daily caps. Pro unlocks higher limits with license key.</p>
|
||||
<div class="mt-3 space-y-3">
|
||||
<div class="codewrap">
|
||||
<button class="copy-btn" data-copy-target="auth-bearer">Copy</button>
|
||||
<pre class="text-xs overflow-x-auto bg-black/30 rounded-lg p-3"><code id="auth-bearer">Authorization: Bearer YOUR_LICENSE_KEY</code></pre>
|
||||
</div>
|
||||
<div class="codewrap">
|
||||
<button class="copy-btn" data-copy-target="auth-query">Copy</button>
|
||||
<pre class="text-xs overflow-x-auto bg-black/30 rounded-lg p-3"><code id="auth-query">?key=YOUR_LICENSE_KEY</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="mt-3 list-disc pl-5 text-sm text-gray-300 space-y-1">
|
||||
<li><code>Authorization</code> (recommended)</li>
|
||||
<li><code>X-License-Key</code> (also supported)</li>
|
||||
<li><code>X-Account-Id</code> (optional usage association)</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section id="plans" class="glass-card rounded-2xl p-6 scroll-mt-28">
|
||||
<h3 class="text-lg font-semibold">Plans & limits</h3>
|
||||
<div class="overflow-x-auto mt-3">
|
||||
<table class="min-w-full text-sm doc-table">
|
||||
<thead>
|
||||
<tr class="text-left text-gray-400">
|
||||
<th class="py-2 pr-6">Tier</th>
|
||||
<th class="py-2 pr-6 text-right">Page size cap</th>
|
||||
<th class="py-2 pr-6 text-right">Daily cap</th>
|
||||
<th class="py-2">Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="align-top text-gray-200">
|
||||
<tr>
|
||||
<td class="py-2 pr-6"><strong>Free</strong></td>
|
||||
<td class="py-2 pr-6 text-right">20</td>
|
||||
<td class="py-2 pr-6 text-right">~30 (page-1, distinct)</td>
|
||||
<td class="py-2">Cached responses do not count.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="py-2 pr-6"><strong>Pro</strong></td>
|
||||
<td class="py-2 pr-6 text-right">50</td>
|
||||
<td class="py-2 pr-6 text-right">5,000 / day / license</td>
|
||||
<td class="py-2">Up to 3 Chrome profiles.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="endpoints" class="glass-card rounded-2xl p-6 scroll-mt-28">
|
||||
<h3 class="text-lg font-semibold mb-3">Endpoints</h3>
|
||||
<ul class="list-disc pl-5 text-sm text-gray-300 space-y-1">
|
||||
<li><code>/emojis</code> — search & paginate</li>
|
||||
<li><code>/emoji?slug=<slug></code> — single emoji (query)</li>
|
||||
<li><code>/emoji/<slug></code> — single emoji (pretty URL)</li>
|
||||
<li><code>/categories</code> — category map</li>
|
||||
<li><code>/license/verify</code> — license check</li>
|
||||
<li><code>/license/activate</code> — bind profile</li>
|
||||
<li><code>/license/deactivate</code> — unbind profile</li>
|
||||
</ul>
|
||||
|
||||
<div class="mt-5">
|
||||
<h4 class="font-semibold">GET <code>/emojis</code></h4>
|
||||
<p class="text-sm text-gray-300">Search across name, category, aliases, and EN/ID keywords.</p>
|
||||
<div class="overflow-x-auto mt-3">
|
||||
<table class="min-w-full text-sm doc-table">
|
||||
<thead>
|
||||
<tr class="text-left text-gray-400">
|
||||
<th class="py-2 pr-6">Query Param</th>
|
||||
<th class="py-2 pr-6">Type</th>
|
||||
<th class="py-2 pr-6">Description</th>
|
||||
<th class="py-2">Default</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="align-top text-gray-200">
|
||||
<tr><td class="py-2 pr-6"><code>q</code> / <code>query</code></td><td class="py-2 pr-6">string</td><td class="py-2 pr-6">Keyword search (AND match).</td><td class="py-2"><em>empty</em></td></tr>
|
||||
<tr><td class="py-2 pr-6"><code>category</code></td><td class="py-2 pr-6">string</td><td class="py-2 pr-6">Top-level category label.</td><td class="py-2"><code>all</code></td></tr>
|
||||
<tr><td class="py-2 pr-6"><code>subcategory</code></td><td class="py-2 pr-6">string</td><td class="py-2 pr-6">Subcategory slug.</td><td class="py-2"><em>empty</em></td></tr>
|
||||
<tr><td class="py-2 pr-6"><code>page</code></td><td class="py-2 pr-6">number</td><td class="py-2 pr-6">1-based page.</td><td class="py-2"><code>1</code></td></tr>
|
||||
<tr><td class="py-2 pr-6"><code>limit</code></td><td class="py-2 pr-6">number</td><td class="py-2 pr-6">Items per page (max 50).</td><td class="py-2"><code>20</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="codewrap mt-3">
|
||||
<button class="copy-btn" data-copy-target="ex-curl-emojis">Copy</button>
|
||||
<pre class="text-xs overflow-x-auto bg-black/30 rounded-lg p-3"><code id="ex-curl-emojis">curl "<BASE_URL>/emojis?q=love&category=Smileys%20%26%20Emotion&limit=20&page=1"</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 border-t border-white/10 pt-6">
|
||||
<h4 class="font-semibold">GET <code>/emoji</code> and <code>/emoji/<slug></code></h4>
|
||||
<div class="codewrap mt-3">
|
||||
<button class="copy-btn" data-copy-target="ex-curl-emoji">Copy</button>
|
||||
<pre class="text-xs overflow-x-auto bg-black/30 rounded-lg p-3"><code id="ex-curl-emoji">curl "<BASE_URL>/emoji/smiling-face-with-smiling-eyes"</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 border-t border-white/10 pt-6">
|
||||
<h4 class="font-semibold">GET <code>/categories</code></h4>
|
||||
<div class="codewrap mt-3">
|
||||
<button class="copy-btn" data-copy-target="ex-curl-cats">Copy</button>
|
||||
<pre class="text-xs overflow-x-auto bg-black/30 rounded-lg p-3"><code id="ex-curl-cats">curl "<BASE_URL>/categories"</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="try-it" class="glass-card rounded-2xl p-6 scroll-mt-28">
|
||||
<h3 class="text-lg font-semibold">Try it</h3>
|
||||
<p class="text-sm text-gray-300 mt-2">Demo request is limited to <strong>page=1</strong> and <strong>limit=10</strong>.</p>
|
||||
<p id="ti-pro-note" class="hidden text-xs text-emerald-300 mt-2">Using Pro key via Authorization header.</p>
|
||||
<div class="grid gap-3 md:grid-cols-3 mt-4">
|
||||
<input id="ti-q" class="bg-[#151518] border border-white/10 rounded-xl px-3 py-2 text-sm text-gray-200" placeholder="keyword (love)">
|
||||
<input id="ti-category" class="bg-[#151518] border border-white/10 rounded-xl px-3 py-2 text-sm text-gray-200" placeholder="category (Smileys & Emotion)">
|
||||
<input id="ti-subcategory" class="bg-[#151518] border border-white/10 rounded-xl px-3 py-2 text-sm text-gray-200" placeholder="subcategory (face-smiling)">
|
||||
</div>
|
||||
<div class="mt-4 flex gap-3">
|
||||
<button id="ti-run" type="button" class="px-4 py-2 rounded-lg bg-brand-ocean hover:bg-brand-oceanSoft text-white text-sm">Run /emojis</button>
|
||||
<button id="ti-cats" type="button" class="px-4 py-2 rounded-lg bg-white/5 hover:bg-white/10 text-sm">Run /categories</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="errors" class="glass-card rounded-2xl p-6 scroll-mt-28">
|
||||
<h3 class="text-lg font-semibold">Errors & caching</h3>
|
||||
<ul class="list-disc pl-5 text-sm text-gray-300 space-y-1">
|
||||
<li><code>400</code> — invalid_request</li>
|
||||
<li><code>401</code> — invalid_key</li>
|
||||
<li><code>404</code> — not_found</li>
|
||||
<li><code>429</code> — rate_limited</li>
|
||||
</ul>
|
||||
<p class="text-sm text-gray-300 mt-2">Responses may include <code>ETag</code> and <code>Cache-Control: public, max-age=300</code>.</p>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
@push('scripts')
|
||||
<script>
|
||||
(() => {
|
||||
const BASE_API = @json(url('/v1'));
|
||||
const urlParams = new URLSearchParams(location.search);
|
||||
const proKey = urlParams.get('key') || '';
|
||||
const proBadge = document.getElementById('pro-badge');
|
||||
const proNote = document.getElementById('ti-pro-note');
|
||||
if (proKey) {
|
||||
proBadge?.classList.remove('hidden');
|
||||
proNote?.classList.remove('hidden');
|
||||
}
|
||||
|
||||
const baseUrlCode = document.getElementById('base-url-code');
|
||||
if (baseUrlCode) baseUrlCode.textContent = BASE_API;
|
||||
|
||||
const updateExamples = () => {
|
||||
const q = 'love';
|
||||
const cat = 'Smileys%20%26%20Emotion';
|
||||
const lim = 10;
|
||||
const exCurlEmojis = document.getElementById('ex-curl-emojis');
|
||||
if (exCurlEmojis) {
|
||||
exCurlEmojis.textContent = proKey
|
||||
? `curl -H "Authorization: Bearer ${proKey}" "${BASE_API}/emojis?q=${q}&category=${cat}&limit=${lim}&page=1"`
|
||||
: `curl "${BASE_API}/emojis?q=${q}&category=${cat}&limit=${lim}&page=1"`;
|
||||
}
|
||||
const exCurlCats = document.getElementById('ex-curl-cats');
|
||||
if (exCurlCats) {
|
||||
exCurlCats.textContent = proKey
|
||||
? `curl -H "Authorization: Bearer ${proKey}" "${BASE_API}/categories"`
|
||||
: `curl "${BASE_API}/categories"`;
|
||||
}
|
||||
const exCurlEmoji = document.getElementById('ex-curl-emoji');
|
||||
if (exCurlEmoji) {
|
||||
exCurlEmoji.textContent = `curl "${BASE_API}/emoji/smiling-face-with-smiling-eyes"`;
|
||||
}
|
||||
};
|
||||
updateExamples();
|
||||
|
||||
function textOf(id) {
|
||||
const el = document.getElementById(id);
|
||||
return el ? (el.textContent || el.innerText || '') : '';
|
||||
}
|
||||
document.querySelectorAll('.copy-btn').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const target = btn.getAttribute('data-copy-target');
|
||||
if (!target) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(textOf(target));
|
||||
const orig = btn.textContent;
|
||||
btn.textContent = '✅';
|
||||
setTimeout(() => { btn.textContent = orig || 'Copy'; }, 1200);
|
||||
} catch {}
|
||||
});
|
||||
});
|
||||
|
||||
const resultDrawer = document.createElement('aside');
|
||||
const backdrop = document.createElement('div');
|
||||
resultDrawer.id = 'ti-drawer';
|
||||
resultDrawer.className = 'fixed top-0 right-0 h-full w-full max-w-xl bg-[#0b0b0f] border-l border-white/10 translate-x-full transition-transform z-50';
|
||||
backdrop.className = 'fixed inset-0 bg-black/40 opacity-0 pointer-events-none transition-opacity z-40';
|
||||
resultDrawer.innerHTML = `
|
||||
<div class="flex items-center justify-between p-4 border-b border-white/10">
|
||||
<h3 class="text-lg font-semibold">Try it — Result</h3>
|
||||
<button id="ti-close" class="p-2 rounded-full text-gray-400 hover:bg-white/5">✕</button>
|
||||
</div>
|
||||
<div class="p-4 h-[calc(100%-64px)] overflow-auto">
|
||||
<pre class="p-3 rounded bg-black/30 overflow-auto text-xs"><code id="ti-result">{ }</code></pre>
|
||||
</div>`;
|
||||
document.body.appendChild(backdrop);
|
||||
document.body.appendChild(resultDrawer);
|
||||
|
||||
const openDrawer = () => {
|
||||
resultDrawer.classList.remove('translate-x-full');
|
||||
backdrop.classList.remove('opacity-0', 'pointer-events-none');
|
||||
backdrop.classList.add('opacity-100');
|
||||
document.body.style.overflow = 'hidden';
|
||||
};
|
||||
const closeDrawer = () => {
|
||||
resultDrawer.classList.add('translate-x-full');
|
||||
backdrop.classList.add('opacity-0', 'pointer-events-none');
|
||||
backdrop.classList.remove('opacity-100');
|
||||
document.body.style.overflow = '';
|
||||
};
|
||||
backdrop.addEventListener('click', closeDrawer);
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target;
|
||||
if (target && target.id === 'ti-close') closeDrawer();
|
||||
});
|
||||
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeDrawer(); });
|
||||
|
||||
const setResult = (obj) => {
|
||||
const el = document.getElementById('ti-result');
|
||||
if (!el) return;
|
||||
el.textContent = typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2);
|
||||
};
|
||||
|
||||
document.getElementById('ti-run')?.addEventListener('click', async () => {
|
||||
const params = new URLSearchParams();
|
||||
const q = (document.getElementById('ti-q')?.value || '').trim();
|
||||
const c = (document.getElementById('ti-category')?.value || '').trim();
|
||||
const sc = (document.getElementById('ti-subcategory')?.value || '').trim();
|
||||
if (q) params.set('q', q);
|
||||
if (c) params.set('category', c);
|
||||
if (sc) params.set('subcategory', sc);
|
||||
params.set('limit', '10');
|
||||
params.set('page', '1');
|
||||
|
||||
const headers = {};
|
||||
if (proKey) headers['Authorization'] = 'Bearer ' + proKey;
|
||||
try {
|
||||
const res = await fetch(`${BASE_API}/emojis?` + params.toString(), { headers });
|
||||
if (!res.ok) throw new Error('Request failed: ' + res.status);
|
||||
const data = await res.json();
|
||||
setResult(data);
|
||||
} catch (e) {
|
||||
setResult(String(e));
|
||||
} finally {
|
||||
openDrawer();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('ti-cats')?.addEventListener('click', async () => {
|
||||
const headers = {};
|
||||
if (proKey) headers['Authorization'] = 'Bearer ' + proKey;
|
||||
try {
|
||||
const res = await fetch(`${BASE_API}/categories`, { headers });
|
||||
if (!res.ok) throw new Error('Request failed: ' + res.status);
|
||||
const data = await res.json();
|
||||
setResult(data);
|
||||
} catch (e) {
|
||||
setResult(String(e));
|
||||
} finally {
|
||||
openDrawer();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@endpush
|
||||
@endsection
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
$htmlHex = '&#x'.$hex.';';
|
||||
$cssCode = '\\'.$hex;
|
||||
}
|
||||
$related = array_slice($emoji['related'] ?? [], 0, 8);
|
||||
$related = $relatedDetails ?? [];
|
||||
$keywords = array_slice($emoji['keywords_en'] ?? [], 0, 16);
|
||||
@endphp
|
||||
|
||||
@@ -92,7 +92,23 @@
|
||||
<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)
|
||||
<button onclick="copyToClipboard('{{ $item }}')" class="w-10 h-10 rounded-lg bg-white/5 hover:bg-white/10 flex items-center justify-center text-xl transition-colors">{{ $item }}</button>
|
||||
<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>
|
||||
@@ -210,8 +226,29 @@
|
||||
|
||||
@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}`;
|
||||
@@ -227,5 +264,8 @@ document.addEventListener('keydown', (e) => {
|
||||
copyToClipboard(@json($symbol));
|
||||
}
|
||||
});
|
||||
|
||||
// Treat opening the single-emoji page as a "recently viewed emoji" event.
|
||||
addRecent(@json($symbol));
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
#grid {
|
||||
--card-min: 104px;
|
||||
--emoji-size: 2rem;
|
||||
grid-template-columns: repeat(auto-fill, minmax(var(--card-min), 1fr));
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@@ -136,10 +141,15 @@
|
||||
<div class="flex items-center gap-2 mb-4 px-1">
|
||||
<h4 class="font-bold text-gray-200">All Emojis</h4>
|
||||
<div class="h-px bg-white/10 flex-1"></div>
|
||||
<div class="hidden sm:flex items-center gap-2 text-[11px] text-gray-400">
|
||||
<button id="grid-smaller" class="w-7 h-7 rounded-md bg-white/5 hover:bg-white/10 border border-white/10">-</button>
|
||||
<input id="grid-size" type="range" min="0" max="2" step="1" value="1" class="w-20 accent-[#2053ff]">
|
||||
<button id="grid-bigger" class="w-7 h-7 rounded-md bg-white/5 hover:bg-white/10 border border-white/10">+</button>
|
||||
</div>
|
||||
<span id="count" class="text-xs text-gray-500">0 / 0</span>
|
||||
</div>
|
||||
|
||||
<div id="grid" class="grid grid-cols-5 sm:grid-cols-6 md:grid-cols-8 lg:grid-cols-10 xl:grid-cols-14 2xl:grid-cols-16 gap-2 pb-8"></div>
|
||||
<div id="grid" class="grid gap-2 pb-8"></div>
|
||||
|
||||
<div class="py-8 flex justify-center">
|
||||
<button id="more" class="hidden px-4 py-2 rounded-xl bg-white/10 hover:bg-white/20 border border-white/10 text-sm text-gray-200 transition-colors">Load more</button>
|
||||
@@ -202,6 +212,10 @@
|
||||
const heroMain = document.getElementById('hero-main');
|
||||
const heroOptional1 = document.getElementById('hero-optional-1');
|
||||
const heroOptional2 = document.getElementById('hero-optional-2');
|
||||
const gridSizeEl = document.getElementById('grid-size');
|
||||
const gridSmallerEl = document.getElementById('grid-smaller');
|
||||
const gridBiggerEl = document.getElementById('grid-bigger');
|
||||
const densityStorageKey = 'dewemoji_grid_density';
|
||||
|
||||
if (initialQuery) qEl.value = initialQuery;
|
||||
|
||||
@@ -262,6 +276,21 @@
|
||||
return String(s || '').replace(/[&<>"']/g, (c) => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c]));
|
||||
}
|
||||
|
||||
function applyGridDensity(level) {
|
||||
const sizes = [
|
||||
{ min: 90, emoji: '2.2rem' },
|
||||
{ min: 108, emoji: '2.45rem' },
|
||||
{ min: 132, emoji: '2.8rem' },
|
||||
];
|
||||
const safe = Math.max(0, Math.min(2, Number(level) || 0));
|
||||
const conf = sizes[safe];
|
||||
grid.style.setProperty('--card-min', `${conf.min}px`);
|
||||
grid.style.setProperty('--emoji-size', conf.emoji);
|
||||
if (gridSizeEl) gridSizeEl.value = String(safe);
|
||||
localStorage.setItem(densityStorageKey, String(safe));
|
||||
}
|
||||
|
||||
|
||||
function hasActiveFilters() {
|
||||
return qEl.value.trim() !== '' || catEl.value !== '' || subEl.value !== '';
|
||||
}
|
||||
@@ -396,6 +425,14 @@
|
||||
|
||||
const res = await fetch('/v1/emojis?' + params.toString());
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
const msg = data.message || data.error || `API error (${res.status})`;
|
||||
grid.innerHTML = `<p class="text-xs text-amber-300 col-span-full">${esc(msg)}</p>`;
|
||||
state.total = 0;
|
||||
state.items = [];
|
||||
updateStats();
|
||||
return;
|
||||
}
|
||||
state.total = data.total || 0;
|
||||
|
||||
const incoming = data.items || [];
|
||||
@@ -412,13 +449,28 @@
|
||||
}
|
||||
|
||||
items.forEach((item) => {
|
||||
const card = document.createElement('a');
|
||||
card.href = '/emoji/' + encodeURIComponent(item.slug);
|
||||
card.className = 'aspect-square rounded-lg bg-white/5 hover:bg-white/10 flex flex-col items-center justify-center gap-1 text-center transition-transform hover:scale-105 border border-transparent hover:border-white/20';
|
||||
const card = document.createElement('div');
|
||||
card.className = '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 = `
|
||||
<span class="text-2xl leading-none">${esc(item.emoji)}</span>
|
||||
<span class="emoji-name-clamp text-[10px] text-gray-400 px-1 w-full">${esc(item.name)}</span>
|
||||
<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>
|
||||
</a>
|
||||
<div class="absolute bottom-0 left-0 right-0 border-t border-white/10 bg-black/20 px-2 py-1.5 flex items-end gap-1">
|
||||
<span class="emoji-name-clamp text-[10px] text-gray-300 text-left flex-1">${esc(item.name)}</span>
|
||||
<button type="button" class="copy-btn shrink-0 w-6 h-6 rounded bg-white/10 hover:bg-brand-ocean/30 text-[11px] text-gray-200 hover:text-white transition-colors" title="Copy emoji">⧉</button>
|
||||
</div>
|
||||
`;
|
||||
const copyBtn = card.querySelector('.copy-btn');
|
||||
if (copyBtn) {
|
||||
copyBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
navigator.clipboard.writeText(item.emoji).then(() => {
|
||||
showToast('Copied ' + item.emoji);
|
||||
addRecent(item.emoji);
|
||||
});
|
||||
});
|
||||
}
|
||||
card.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault();
|
||||
navigator.clipboard.writeText(item.emoji).then(() => {
|
||||
@@ -462,6 +514,15 @@
|
||||
});
|
||||
});
|
||||
|
||||
if (gridSizeEl && gridSmallerEl && gridBiggerEl) {
|
||||
const initialDensity = localStorage.getItem(densityStorageKey) ?? '1';
|
||||
applyGridDensity(Number(initialDensity));
|
||||
|
||||
gridSizeEl.addEventListener('input', () => applyGridDensity(Number(gridSizeEl.value)));
|
||||
gridSmallerEl.addEventListener('click', () => applyGridDensity(Number(gridSizeEl.value) - 1));
|
||||
gridBiggerEl.addEventListener('click', () => applyGridDensity(Number(gridSizeEl.value) + 1));
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await loadCategories();
|
||||
if (initialCategory && state.categories[initialCategory]) {
|
||||
|
||||
@@ -25,6 +25,29 @@
|
||||
<meta name="twitter:title" content="{{ $metaTitle }}">
|
||||
<meta name="twitter:description" content="{{ $metaDescription }}">
|
||||
<meta name="theme-color" content="#2053ff">
|
||||
<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 src="https://cdn.tailwindcss.com"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@extends('site.layout')
|
||||
|
||||
@section('title', 'Pricing - Dewemoji')
|
||||
@section('meta_description', 'See Dewemoji plans for Free, Pro subscription, and Lifetime. One key unlocks Extension + API with up to 3 Chrome profile activations.')
|
||||
@section('meta_description', 'Choose Dewemoji pricing for Free, Pro subscription, and Lifetime access for website, extension, and API usage.')
|
||||
|
||||
@push('jsonld')
|
||||
<script type="application/ld+json">
|
||||
@@ -11,7 +11,7 @@
|
||||
{
|
||||
"@@type": "Product",
|
||||
"name": "Dewemoji Pro License",
|
||||
"description": "One Pro license unlocks the Dewemoji extension and API.",
|
||||
"description": "One Pro license unlocks Dewemoji extension and API access.",
|
||||
"brand": {"@@type": "Brand", "name": "Dewemoji"},
|
||||
"offers": [
|
||||
{"@@type":"Offer","price":"3.00","priceCurrency":"USD","availability":"https://schema.org/InStock","url":"https://dwindown.gumroad.com/l/dewemoji-pro-subscription"},
|
||||
@@ -21,7 +21,7 @@
|
||||
{
|
||||
"@@type": "Product",
|
||||
"name": "Dewemoji Lifetime License",
|
||||
"description": "Lifetime access to extension + API.",
|
||||
"description": "Lifetime access to Dewemoji extension and API.",
|
||||
"brand": {"@@type": "Brand", "name": "Dewemoji"},
|
||||
"offers": {"@@type":"Offer","price":"69.00","priceCurrency":"USD","availability":"https://schema.org/InStock","url":"https://dwindown.gumroad.com/l/dewemoji-pro-lifetime"}
|
||||
}
|
||||
@@ -31,56 +31,103 @@
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6 py-10">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="font-display text-4xl font-bold">Simple, fair pricing</h1>
|
||||
<p class="text-gray-400 mt-2">One Pro unlocks both Extension + API.</p>
|
||||
</div>
|
||||
<div class="flex h-screen w-full">
|
||||
<aside class="hidden lg:flex w-20 lg:w-64 h-full glass-panel flex-col justify-between p-4 z-50 shrink-0">
|
||||
<div>
|
||||
<div class="flex items-center gap-3 px-2 mb-8 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 shrink-0">
|
||||
<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-1">
|
||||
<a href="{{ route('home') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all">
|
||||
<i data-lucide="layout-grid" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Discover</span>
|
||||
</a>
|
||||
<a href="{{ route('api-docs') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all">
|
||||
<i data-lucide="book-open" class="w-5 h-5"></i><span class="text-sm hidden lg:block">API Docs</span>
|
||||
</a>
|
||||
<a href="{{ route('pricing') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl bg-white/10 text-brand-sun border border-white/5 transition-all">
|
||||
<i data-lucide="badge-dollar-sign" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Pricing</span>
|
||||
</a>
|
||||
<a href="{{ route('support') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all">
|
||||
<i data-lucide="life-buoy" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Support</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<a href="{{ route('privacy') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all">
|
||||
<i data-lucide="shield-check" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Privacy</span>
|
||||
</a>
|
||||
<a href="{{ route('terms') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all">
|
||||
<i data-lucide="file-text" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Terms</span>
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-3">
|
||||
<section class="glass-card rounded-2xl p-6">
|
||||
<h2 class="text-xl font-semibold">Free</h2>
|
||||
<p class="text-gray-400 text-sm mt-1">For casual usage</p>
|
||||
<p class="text-3xl font-bold mt-4">$0</p>
|
||||
<ul class="mt-4 space-y-2 text-sm text-gray-300">
|
||||
<li>Extension access</li>
|
||||
<li>API testing access</li>
|
||||
<li>Daily cap 30 queries</li>
|
||||
</ul>
|
||||
</section>
|
||||
<main class="flex-1 flex flex-col h-full min-w-0 relative z-10">
|
||||
<header class="glass-header px-6 py-5 shrink-0 flex justify-end items-center gap-4">
|
||||
<span class="text-xs text-gray-500 hidden md:block">Billing shown in USD</span>
|
||||
<button class="text-sm font-semibold text-brand-oceanSoft hover:text-white transition-colors">Restore Purchases</button>
|
||||
</header>
|
||||
|
||||
<section class="glass-card rounded-2xl p-6 border border-brand-ocean/40">
|
||||
<h2 class="text-xl font-semibold">Pro</h2>
|
||||
<p class="text-gray-400 text-sm mt-1">Most popular</p>
|
||||
<p class="text-3xl font-bold mt-4">$3/mo <span class="text-sm text-gray-400">or $27/yr</span></p>
|
||||
<ul class="mt-4 space-y-2 text-sm text-gray-300">
|
||||
<li>Unlimited extension usage</li>
|
||||
<li>API pro access</li>
|
||||
<li>Up to 3 Chrome profiles</li>
|
||||
</ul>
|
||||
<a href="https://dwindown.gumroad.com/l/dewemoji-pro-subscription" target="_blank" rel="noopener noreferrer" class="mt-5 inline-flex w-full justify-center rounded-lg bg-brand-ocean hover:bg-brand-oceanSoft px-4 py-2 font-semibold">Subscribe on Gumroad</a>
|
||||
</section>
|
||||
<div class="flex-1 overflow-y-auto p-6 md:p-10">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="text-center mb-12">
|
||||
<h1 class="font-display text-4xl md:text-5xl font-bold mb-3">Supercharge your <span class="text-gradient">emoji workflow</span></h1>
|
||||
<p class="text-gray-400">Use Dewemoji for search, extension, and API in one license system.</p>
|
||||
</div>
|
||||
|
||||
<section class="glass-card rounded-2xl p-6">
|
||||
<h2 class="text-xl font-semibold">Lifetime</h2>
|
||||
<p class="text-gray-400 text-sm mt-1">Pay once, use forever</p>
|
||||
<p class="text-3xl font-bold mt-4">$69</p>
|
||||
<ul class="mt-4 space-y-2 text-sm text-gray-300">
|
||||
<li>Extension + API access</li>
|
||||
<li>Future updates included</li>
|
||||
<li>IDR/Mayar available on request</li>
|
||||
</ul>
|
||||
<a href="https://dwindown.gumroad.com/l/dewemoji-pro-lifetime" target="_blank" rel="noopener noreferrer" class="mt-5 inline-flex w-full justify-center rounded-lg bg-brand-sun text-black hover:bg-brand-sunSoft px-4 py-2 font-semibold">Buy Lifetime</a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-5 mb-8">
|
||||
<section class="glass-card rounded-3xl p-6 flex flex-col">
|
||||
<h2 class="font-display text-xl font-bold">Starter</h2>
|
||||
<p class="text-sm text-gray-500 mt-1">For casual usage</p>
|
||||
<div class="mt-5 mb-6"><span class="text-4xl font-bold">$0</span><span class="text-gray-500">/mo</span></div>
|
||||
<ul class="space-y-2 text-sm text-gray-300 flex-1">
|
||||
<li>Extension access</li>
|
||||
<li>Basic API testing</li>
|
||||
<li>30 daily free queries</li>
|
||||
</ul>
|
||||
<button class="mt-6 w-full py-2.5 rounded-xl bg-white/5 border border-white/10 text-gray-300">Current Plan</button>
|
||||
</section>
|
||||
|
||||
<div class="glass-card rounded-2xl p-6 mt-6 text-sm text-gray-300">
|
||||
<h3 class="font-semibold text-white">Licensing basics</h3>
|
||||
<ul class="mt-3 list-disc pl-5 space-y-1">
|
||||
<li>One key unlocks both extension and API.</li>
|
||||
<li>Use <code>Authorization: Bearer YOUR_LICENSE_KEY</code> for API requests.</li>
|
||||
<li>One license supports up to 3 active Chrome profiles.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<section class="rounded-3xl p-6 flex flex-col border border-brand-ocean/40 bg-gradient-to-b from-brand-ocean/15 to-transparent shadow-[0_0_28px_rgba(32,83,255,0.18)] md:-translate-y-2">
|
||||
<div class="inline-flex w-fit mb-3 px-2 py-1 text-[10px] font-bold rounded-full bg-brand-sun text-black">MOST POPULAR</div>
|
||||
<h2 class="font-display text-xl font-bold text-brand-oceanSoft">Pro</h2>
|
||||
<p class="text-sm text-gray-400 mt-1">For power users & teams</p>
|
||||
<div class="mt-5 mb-6"><span class="text-4xl font-bold">$3</span><span class="text-gray-400">/mo</span> <span class="text-sm text-gray-500">or $27/yr</span></div>
|
||||
<ul class="space-y-2 text-sm text-gray-200 flex-1">
|
||||
<li>Semantic search engine</li>
|
||||
<li>API Pro access</li>
|
||||
<li>3 active Chrome profiles</li>
|
||||
<li>Priority support</li>
|
||||
</ul>
|
||||
<a href="https://dwindown.gumroad.com/l/dewemoji-pro-subscription" target="_blank" rel="noopener noreferrer" class="mt-6 w-full py-2.5 rounded-xl bg-brand-ocean hover:bg-brand-oceanSoft text-white font-semibold text-center">Upgrade to Pro</a>
|
||||
</section>
|
||||
|
||||
<section class="glass-card rounded-3xl p-6 flex flex-col">
|
||||
<h2 class="font-display text-xl font-bold">Lifetime</h2>
|
||||
<p class="text-sm text-gray-500 mt-1">Pay once, use forever</p>
|
||||
<div class="mt-5 mb-6"><span class="text-4xl font-bold">$69</span></div>
|
||||
<ul class="space-y-2 text-sm text-gray-300 flex-1">
|
||||
<li>Lifetime extension + API</li>
|
||||
<li>Future updates included</li>
|
||||
<li>IDR/Mayar available on request</li>
|
||||
</ul>
|
||||
<a href="https://dwindown.gumroad.com/l/dewemoji-pro-lifetime" target="_blank" rel="noopener noreferrer" class="mt-6 w-full py-2.5 rounded-xl bg-brand-sun hover:bg-brand-sunSoft text-black font-semibold text-center">Buy Lifetime</a>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="glass-card rounded-2xl p-6 text-sm text-gray-300">
|
||||
<h3 class="font-semibold text-white mb-2">Licensing basics</h3>
|
||||
<ul class="list-disc pl-5 space-y-1">
|
||||
<li>One key unlocks extension and API.</li>
|
||||
<li>Use <code>Authorization: Bearer YOUR_LICENSE_KEY</code> for API requests.</li>
|
||||
<li>Maximum 3 active Chrome profiles per license.</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -1,11 +1,83 @@
|
||||
@extends('site.layout')
|
||||
|
||||
@section('title', 'Privacy - Dewemoji')
|
||||
@section('meta_description', 'Read Dewemoji privacy information and data handling practices.')
|
||||
@section('title', 'Privacy Policy - Dewemoji')
|
||||
@section('meta_description', 'Read Dewemoji privacy policy, data collection scope, cookies, and support contacts.')
|
||||
|
||||
@push('head')
|
||||
<style>
|
||||
.legal-h2 { font-family: 'Space Grotesk', sans-serif; margin-top: 2rem; margin-bottom: .75rem; font-size: 1.3rem; color: #fff; font-weight: 700; }
|
||||
.legal-h3 { font-family: 'Space Grotesk', sans-serif; margin-top: 1.4rem; margin-bottom: .5rem; font-size: 1.05rem; color: #e5e7eb; font-weight: 600; }
|
||||
.legal-p { color: #9ca3af; line-height: 1.7; margin-bottom: .9rem; }
|
||||
.legal-ul { list-style: disc; padding-left: 1.2rem; color: #9ca3af; margin-bottom: .9rem; }
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<section class="card" style="padding: 20px;">
|
||||
<h1 style="margin-top:0;">Privacy</h1>
|
||||
<p style="color:var(--muted);">Phase 3 placeholder page. We will migrate the full privacy text from legacy content in a later pass.</p>
|
||||
</section>
|
||||
<div class="flex h-screen w-full">
|
||||
<aside class="hidden lg:flex w-20 lg:w-64 h-full glass-panel flex-col justify-between p-4 z-50 shrink-0">
|
||||
<div>
|
||||
<div class="flex items-center gap-3 px-2 mb-8 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 shrink-0">
|
||||
<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-1">
|
||||
<a href="{{ route('home') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="layout-grid" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Discover</span></a>
|
||||
<a href="{{ route('api-docs') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="book-open" class="w-5 h-5"></i><span class="text-sm hidden lg:block">API Docs</span></a>
|
||||
<a href="{{ route('pricing') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="badge-dollar-sign" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Pricing</span></a>
|
||||
<a href="{{ route('support') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="life-buoy" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Support</span></a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<a href="{{ route('privacy') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl bg-white/10 text-brand-sun border border-white/5 transition-all"><i data-lucide="shield-check" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Privacy</span></a>
|
||||
<a href="{{ route('terms') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="file-text" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Terms</span></a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="flex-1 flex flex-col h-full min-w-0 relative z-10">
|
||||
<header class="glass-header px-6 py-5 shrink-0 flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-[11px] uppercase tracking-wider text-gray-500">Legal / Privacy Policy</div>
|
||||
<h1 class="font-display text-2xl md:text-3xl font-bold">Privacy Policy</h1>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-[10px] uppercase tracking-wider text-gray-500">Last Updated</div>
|
||||
<div class="text-sm text-gray-200">February 5, 2026</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-6 md:p-10">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="glass-card p-6 rounded-2xl mb-8">
|
||||
<p class="legal-p mb-0">We respect your privacy and keep data collection minimal. This page explains what we collect and why.</p>
|
||||
</div>
|
||||
|
||||
<h2 class="legal-h2">1. Information we collect</h2>
|
||||
<p class="legal-p">We collect service diagnostics and usage signals needed to operate Dewemoji reliably.</p>
|
||||
<ul class="legal-ul">
|
||||
<li>API usage metadata (endpoint, timing, status)</li>
|
||||
<li>Basic device/browser information</li>
|
||||
<li>License validation and activation metadata</li>
|
||||
</ul>
|
||||
|
||||
<h2 class="legal-h2">2. How we use information</h2>
|
||||
<ul class="legal-ul">
|
||||
<li>Provide and improve search quality and API performance</li>
|
||||
<li>Prevent abuse and enforce fair usage limits</li>
|
||||
<li>Support license verification and activation workflows</li>
|
||||
</ul>
|
||||
|
||||
<h2 class="legal-h2">3. Cookies and local storage</h2>
|
||||
<p class="legal-p">We use browser storage for user experience features (for example recent emoji history). This stays on your device unless explicitly submitted to API endpoints.</p>
|
||||
|
||||
<h2 class="legal-h2">4. Data sharing</h2>
|
||||
<p class="legal-p">We do not sell personal data. Limited sharing may occur with payment and infrastructure providers only when required to run the service.</p>
|
||||
|
||||
<h2 class="legal-h2">5. Contact</h2>
|
||||
<p class="legal-p">Questions? Contact <a href="mailto:hello@dewemoji.com" class="text-brand-oceanSoft hover:text-white">hello@dewemoji.com</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@extends('site.layout')
|
||||
|
||||
@section('title', 'Support - Dewemoji')
|
||||
@section('meta_description', 'Get help with Dewemoji installation, Pro activation, API usage, billing, and troubleshooting.')
|
||||
@section('meta_description', 'Get help with Dewemoji installation, Pro activation, API usage, billing, and common troubleshooting.')
|
||||
|
||||
@push('jsonld')
|
||||
<script type="application/ld+json">
|
||||
@@ -18,35 +18,62 @@
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="max-w-5xl mx-auto px-4 sm:px-6 py-10">
|
||||
<h1 class="font-display text-4xl font-bold text-center">Support</h1>
|
||||
<p class="text-gray-400 text-center mt-2">Need help with Dewemoji? We got you.</p>
|
||||
<div class="flex h-screen w-full">
|
||||
<aside class="hidden lg:flex w-20 lg:w-64 h-full glass-panel flex-col justify-between p-4 z-50 shrink-0">
|
||||
<div>
|
||||
<div class="flex items-center gap-3 px-2 mb-8 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 shrink-0">
|
||||
<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-1">
|
||||
<a href="{{ route('home') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="layout-grid" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Discover</span></a>
|
||||
<a href="{{ route('api-docs') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="book-open" class="w-5 h-5"></i><span class="text-sm hidden lg:block">API Docs</span></a>
|
||||
<a href="{{ route('pricing') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="badge-dollar-sign" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Pricing</span></a>
|
||||
<a href="{{ route('support') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl bg-white/10 text-brand-sun border border-white/5 transition-all"><i data-lucide="life-buoy" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Support</span></a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<a href="{{ route('privacy') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="shield-check" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Privacy</span></a>
|
||||
<a href="{{ route('terms') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="file-text" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Terms</span></a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="grid gap-4 mt-8 md:grid-cols-2">
|
||||
<section class="glass-card rounded-2xl p-5">
|
||||
<h2 class="font-semibold">Install & activate</h2>
|
||||
<ol class="mt-3 list-decimal pl-5 text-sm text-gray-300 space-y-1">
|
||||
<li>Install from Chrome Web Store.</li>
|
||||
<li>Open Dewemoji settings.</li>
|
||||
<li>Paste key in Pro tab and activate.</li>
|
||||
</ol>
|
||||
</section>
|
||||
<main class="flex-1 flex flex-col h-full min-w-0 relative z-10">
|
||||
<header class="glass-header px-6 py-5 shrink-0">
|
||||
<div class="text-[11px] uppercase tracking-wider text-gray-500 mb-1">Public / Support</div>
|
||||
<h1 class="font-display text-2xl md:text-3xl font-bold">Support Center</h1>
|
||||
</header>
|
||||
|
||||
<section class="glass-card rounded-2xl p-5">
|
||||
<h2 class="font-semibold">API quick start</h2>
|
||||
<pre class="mt-3 text-xs overflow-x-auto bg-black/30 rounded-lg p-3"><code>curl -H "Authorization: Bearer YOUR_LICENSE_KEY" \
|
||||
<div class="flex-1 overflow-y-auto p-6 md:p-10">
|
||||
<div class="max-w-5xl mx-auto grid gap-4 md:grid-cols-2">
|
||||
<section class="glass-card rounded-2xl p-5">
|
||||
<h2 class="font-semibold mb-2">Install & activate</h2>
|
||||
<ol class="list-decimal pl-5 text-sm text-gray-300 space-y-1">
|
||||
<li>Install Dewemoji extension.</li>
|
||||
<li>Open settings and go to Pro tab.</li>
|
||||
<li>Paste your license key and activate.</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="glass-card rounded-2xl p-5">
|
||||
<h2 class="font-semibold mb-2">API quick start</h2>
|
||||
<pre class="text-xs overflow-x-auto bg-black/30 rounded-lg p-3"><code>curl -H "Authorization: Bearer YOUR_LICENSE_KEY" \
|
||||
"{{ url('/v1/emojis') }}?q=love&limit=20"</code></pre>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="glass-card rounded-2xl p-5 md:col-span-2">
|
||||
<h2 class="font-semibold">Common issues</h2>
|
||||
<ul class="mt-3 list-disc pl-5 text-sm text-gray-300 space-y-1">
|
||||
<li><strong>License limit reached:</strong> deactivate old profile first.</li>
|
||||
<li><strong>API 401 invalid key:</strong> ensure key is valid and active.</li>
|
||||
<li><strong>Insert not working:</strong> click into input and retry.</li>
|
||||
</ul>
|
||||
<p class="mt-4 text-sm text-gray-400">Still stuck? Email <a class="text-brand-ocean hover:underline" href="mailto:hello@dewemoji.com">hello@dewemoji.com</a>.</p>
|
||||
</section>
|
||||
</div>
|
||||
<section class="glass-card rounded-2xl p-5 md:col-span-2">
|
||||
<h2 class="font-semibold mb-2">Common issues</h2>
|
||||
<ul class="list-disc pl-5 text-sm text-gray-300 space-y-1">
|
||||
<li><strong>License limit reached:</strong> deactivate old profile first.</li>
|
||||
<li><strong>API 401 invalid key:</strong> ensure key is active and valid.</li>
|
||||
<li><strong>Insert not working:</strong> focus target input and retry.</li>
|
||||
</ul>
|
||||
<p class="mt-4 text-sm text-gray-400">Need direct help? Email <a class="text-brand-oceanSoft hover:text-white" href="mailto:hello@dewemoji.com">hello@dewemoji.com</a>.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -1,11 +1,77 @@
|
||||
@extends('site.layout')
|
||||
|
||||
@section('title', 'Terms - Dewemoji')
|
||||
@section('meta_description', 'Read Dewemoji terms and conditions for website, extension, and API usage.')
|
||||
@section('meta_description', 'Read Dewemoji terms of service for website, API, and extension usage.')
|
||||
|
||||
@push('head')
|
||||
<style>
|
||||
.legal-h2 { font-family: 'Space Grotesk', sans-serif; margin-top: 2rem; margin-bottom: .75rem; font-size: 1.3rem; color: #fff; font-weight: 700; }
|
||||
.legal-p { color: #9ca3af; line-height: 1.7; margin-bottom: .9rem; }
|
||||
.legal-ul { list-style: disc; padding-left: 1.2rem; color: #9ca3af; margin-bottom: .9rem; }
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<section class="card" style="padding: 20px;">
|
||||
<h1 style="margin-top:0;">Terms</h1>
|
||||
<p style="color:var(--muted);">Phase 3 placeholder page. We will migrate complete terms content from legacy sources in a later pass.</p>
|
||||
</section>
|
||||
<div class="flex h-screen w-full">
|
||||
<aside class="hidden lg:flex w-20 lg:w-64 h-full glass-panel flex-col justify-between p-4 z-50 shrink-0">
|
||||
<div>
|
||||
<div class="flex items-center gap-3 px-2 mb-8 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 shrink-0">
|
||||
<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-1">
|
||||
<a href="{{ route('home') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="layout-grid" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Discover</span></a>
|
||||
<a href="{{ route('api-docs') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="book-open" class="w-5 h-5"></i><span class="text-sm hidden lg:block">API Docs</span></a>
|
||||
<a href="{{ route('pricing') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="badge-dollar-sign" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Pricing</span></a>
|
||||
<a href="{{ route('support') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="life-buoy" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Support</span></a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<a href="{{ route('privacy') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl text-gray-400 hover:text-white hover:bg-white/5 transition-all"><i data-lucide="shield-check" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Privacy</span></a>
|
||||
<a href="{{ route('terms') }}" class="flex items-center gap-4 px-3 py-3 rounded-xl bg-white/10 text-brand-sun border border-white/5 transition-all"><i data-lucide="file-text" class="w-5 h-5"></i><span class="text-sm hidden lg:block">Terms</span></a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="flex-1 flex flex-col h-full min-w-0 relative z-10">
|
||||
<header class="glass-header px-6 py-5 shrink-0 flex items-center justify-between">
|
||||
<div>
|
||||
<div class="text-[11px] uppercase tracking-wider text-gray-500">Legal / Terms</div>
|
||||
<h1 class="font-display text-2xl md:text-3xl font-bold">Terms of Service</h1>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-[10px] uppercase tracking-wider text-gray-500">Last Updated</div>
|
||||
<div class="text-sm text-gray-200">February 5, 2026</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-6 md:p-10">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="glass-card p-6 rounded-2xl mb-8">
|
||||
<p class="legal-p mb-0">By using Dewemoji website, extension, or API, you agree to these terms.</p>
|
||||
</div>
|
||||
|
||||
<h2 class="legal-h2">1. Acceptable use</h2>
|
||||
<ul class="legal-ul">
|
||||
<li>Use the service lawfully and without abuse.</li>
|
||||
<li>Do not attempt to bypass license controls or request limits.</li>
|
||||
<li>Do not scrape or redistribute restricted content in violation of terms.</li>
|
||||
</ul>
|
||||
|
||||
<h2 class="legal-h2">2. Accounts and licenses</h2>
|
||||
<p class="legal-p">Paid features require a valid license key. Device/profile activation limits apply based on plan rules.</p>
|
||||
|
||||
<h2 class="legal-h2">3. Service availability</h2>
|
||||
<p class="legal-p">We continuously improve stability, but uptime is not guaranteed. Features may change over time.</p>
|
||||
|
||||
<h2 class="legal-h2">4. Limitation of liability</h2>
|
||||
<p class="legal-p">Dewemoji is provided “as is” without warranties. We are not liable for indirect or consequential damages from service use.</p>
|
||||
|
||||
<h2 class="legal-h2">5. Contact</h2>
|
||||
<p class="legal-p">Questions about terms: <a href="mailto:hello@dewemoji.com" class="text-brand-oceanSoft hover:text-white">hello@dewemoji.com</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
Reference in New Issue
Block a user