feat: ship creator journey v2 and profile updates
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
<img
|
||||
src="{{ $art->thumb_url ?? 'https://files.skinbase.org/default/missing_md.webp' }}"
|
||||
@if(!empty($art->thumb_srcset)) srcset="{{ $art->thumb_srcset }}" @endif
|
||||
sizes="70px"
|
||||
alt="{{ $art->name ?? '' }}"
|
||||
class="img-thumbnail"
|
||||
style="width:70px;height:70px;object-fit:cover"
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
<img
|
||||
src="{{ $artwork->thumb_url ?? $artwork->thumb }}"
|
||||
@if(!empty($artwork->thumb_srcset)) srcset="{{ $artwork->thumb_srcset }}" @endif
|
||||
sizes="(max-width: 992px) 100vw, 400px"
|
||||
class="img-responsive"
|
||||
alt="{{ $artwork->title }}"
|
||||
>
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
<img
|
||||
src="{{ $artwork->thumb_url ?? $artwork->thumb }}"
|
||||
@if(!empty($artwork->thumb_srcset)) srcset="{{ $artwork->thumb_srcset }}" @endif
|
||||
sizes="70px"
|
||||
alt="{{ $artwork->title }}"
|
||||
class="img-thumbnail"
|
||||
style="width:70px;height:70px;object-fit:cover"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
'art',
|
||||
'loading' => 'lazy',
|
||||
'fetchpriority' => null,
|
||||
'imageSizes' => '(max-width: 640px) 50vw, (max-width: 1024px) 33vw, (max-width: 1536px) 25vw, 320px',
|
||||
])
|
||||
|
||||
@php
|
||||
@@ -105,6 +106,7 @@
|
||||
$imgSrcset = (string) ($art->thumb_srcset ?? $art->thumbnail_srcset ?? $imgSrc);
|
||||
$imgAvifSrcset = (string) ($art->thumb_avif_srcset ?? $imgSrcset);
|
||||
$imgWebpSrcset = (string) ($art->thumb_webp_srcset ?? $imgSrcset);
|
||||
$imgSizes = trim((string) $imageSizes);
|
||||
|
||||
$resolveDimension = function ($value, string $field, $fallback) {
|
||||
if (is_numeric($value)) {
|
||||
@@ -163,8 +165,17 @@
|
||||
if ($resolution !== '') {
|
||||
$metaParts[] = $resolution;
|
||||
}
|
||||
|
||||
$maturity = data_get($art, 'maturity');
|
||||
if (is_array($maturity)) {
|
||||
$maturity = (object) $maturity;
|
||||
}
|
||||
$shouldHide = (bool) data_get($maturity, 'should_hide', false);
|
||||
$shouldBlur = (bool) data_get($maturity, 'should_blur', false);
|
||||
$isMatureArtwork = (bool) data_get($maturity, 'is_mature_effective', false);
|
||||
@endphp
|
||||
|
||||
@unless($shouldHide)
|
||||
<article class="nova-card gallery-item artwork" itemscope itemtype="https://schema.org/ImageObject"
|
||||
data-art-id="{{ $art->id ?? '' }}"
|
||||
data-art-url="{{ $cardUrl }}"
|
||||
@@ -181,15 +192,19 @@
|
||||
<div class="absolute left-3 top-3 z-30 rounded-md bg-black/55 px-2 py-1 text-xs text-white backdrop-blur-sm">{{ $category }}</div>
|
||||
@endif
|
||||
|
||||
@if($shouldBlur && $isMatureArtwork)
|
||||
<div class="absolute right-3 top-3 z-30 rounded-md bg-amber-500/85 px-2 py-1 text-xs font-semibold text-black shadow-lg shadow-black/30 backdrop-blur-sm">Mature</div>
|
||||
@endif
|
||||
|
||||
<div class="nova-card-media relative overflow-hidden bg-neutral-900"@if($imgAspectRatio) style="aspect-ratio: {{ $imgAspectRatio }};"@endif>
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-white/10 via-white/5 to-transparent pointer-events-none"></div>
|
||||
<picture>
|
||||
<source srcset="{{ $imgAvifSrcset }}" type="image/avif">
|
||||
<source srcset="{{ $imgWebpSrcset }}" type="image/webp">
|
||||
<source srcset="{{ $imgAvifSrcset }}" @if($imgSizes !== '') sizes="{{ $imgSizes }}" @endif type="image/avif">
|
||||
<source srcset="{{ $imgWebpSrcset }}" @if($imgSizes !== '') sizes="{{ $imgSizes }}" @endif type="image/webp">
|
||||
<img
|
||||
src="{{ $imgSrc }}"
|
||||
srcset="{{ $imgSrcset }}"
|
||||
sizes="(max-width: 768px) 50vw, (max-width: 1280px) 33vw, 20vw"
|
||||
@if($imgSizes !== '') sizes="{{ $imgSizes }}" @endif
|
||||
loading="{{ $loading }}"
|
||||
decoding="{{ $loading === 'eager' ? 'sync' : 'async' }}"
|
||||
@if($fetchpriority) fetchpriority="{{ $fetchpriority }}" @endif
|
||||
@@ -197,11 +212,18 @@
|
||||
alt="{{ e($title) }}"
|
||||
@if($imgWidth) width="{{ $imgWidth }}" @endif
|
||||
@if($imgHeight) height="{{ $imgHeight }}" @endif
|
||||
class="{{ $imgAspectRatio ? 'h-full w-full object-cover' : 'w-full h-auto' }} transition-[transform,filter] duration-300 ease-out group-hover:scale-[1.04]"
|
||||
class="{{ $imgAspectRatio ? 'h-full w-full object-cover' : 'w-full h-auto' }} transition-[transform,filter] duration-300 ease-out {{ $shouldBlur ? 'blur-xl scale-[1.08]' : 'group-hover:scale-[1.04]' }}"
|
||||
itemprop="thumbnailUrl"
|
||||
/>
|
||||
</picture>
|
||||
|
||||
@if($shouldBlur && $isMatureArtwork)
|
||||
<div class="pointer-events-none absolute inset-0 z-10 bg-black/25"></div>
|
||||
<div class="pointer-events-none absolute inset-x-0 bottom-0 z-20 bg-gradient-to-t from-black/85 via-black/45 to-transparent px-3 py-3 text-xs font-semibold uppercase tracking-[0.18em] text-white/90">
|
||||
Mature content blurred
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="pointer-events-none absolute inset-x-0 bottom-0 z-20 bg-gradient-to-t from-black/80 via-black/40 to-transparent p-3 backdrop-blur-[2px] opacity-100 transition-opacity duration-200 md:opacity-0 md:group-hover:opacity-100 md:group-focus-visible:opacity-100">
|
||||
<div class="truncate text-sm font-semibold text-white">{{ $title }}</div>
|
||||
<div class="mt-1 flex items-start justify-between gap-3 text-xs text-white/80">
|
||||
@@ -222,3 +244,4 @@
|
||||
<span class="sr-only">{{ $title }} by {{ $author }}</span>
|
||||
</a>
|
||||
</article>
|
||||
@endunless
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
@if($artwork['thumb'])
|
||||
<div class="aspect-video w-full overflow-hidden bg-nova-700">
|
||||
<img src="{{ $artwork['thumb'] }}"
|
||||
@if(!empty($artwork['thumb_srcset'])) srcset="{{ $artwork['thumb_srcset'] }}" @endif
|
||||
sizes="(max-width: 768px) 50vw, 240px"
|
||||
alt="{{ $artwork['title'] }}"
|
||||
loading="lazy"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300 opacity-80 group-hover:opacity-100" />
|
||||
|
||||
@@ -48,81 +48,19 @@
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="container-fluid legacy-page">
|
||||
<div class="mx-auto w-full max-w-screen-2xl px-4 py-6 sm:px-6 lg:px-8">
|
||||
@php Banner::ShowResponsiveAd(); @endphp
|
||||
<div class="pt-0">
|
||||
|
||||
{{-- Source info --}}
|
||||
<div class="min-w-0 flex-1 space-y-2">
|
||||
<h1 class="text-2xl font-bold leading-tight text-white md:text-3xl">
|
||||
Artworks similar to
|
||||
<a href="{{ $src->url }}" class="underline decoration-white/20 underline-offset-4 transition hover:decoration-sky-400 focus-visible:outline-none">{{ $src->title }}</a>
|
||||
</h1>
|
||||
<script id="similar-artworks-header-props" type="application/json">
|
||||
{!! json_encode(['artwork' => $sourceArtwork], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) !!}
|
||||
</script>
|
||||
|
||||
{{-- Author & category --}}
|
||||
<div class="flex flex-wrap items-center gap-3 text-sm text-white/50">
|
||||
@if(!empty($src->author_name))
|
||||
<span class="flex items-center gap-2">
|
||||
@if(!empty($src->author_avatar))
|
||||
<img src="{{ $src->author_avatar }}"
|
||||
alt="{{ $src->author_name }}"
|
||||
class="h-5 w-5 rounded-full object-cover ring-1 ring-white/20"
|
||||
onerror="this.style.display='none'">
|
||||
@endif
|
||||
<a href="/{{ $src->author_username }}" class="font-medium text-white/70 hover:text-white transition">{{ $src->author_name }}</a>
|
||||
</span>
|
||||
@endif
|
||||
<div id="similar-artworks-header-root" class="mb-8"></div>
|
||||
|
||||
@if(!empty($src->category_name))
|
||||
<span class="inline-flex items-center gap-1 rounded-full border border-white/10 bg-white/[0.05] px-2.5 py-0.5 text-xs font-medium text-white/60">
|
||||
{{ $src->category_name }}
|
||||
</span>
|
||||
@endif
|
||||
|
||||
@if(!empty($src->content_type_name))
|
||||
<span class="inline-flex items-center gap-1 rounded-full border border-sky-400/20 bg-sky-400/[0.08] px-2.5 py-0.5 text-xs font-medium text-sky-300">
|
||||
{{ $src->content_type_name }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Tags --}}
|
||||
@if(!empty($src->tag_slugs))
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
@foreach($src->tag_slugs as $tagSlug)
|
||||
<a href="{{ route('tags.show', $tagSlug) }}"
|
||||
class="rounded-full border border-white/[0.08] bg-white/[0.04] px-2.5 py-0.5 text-xs text-white/50 transition hover:border-sky-400/30 hover:bg-sky-400/[0.07] hover:text-white/80">
|
||||
#{{ $tagSlug }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Actions --}}
|
||||
<div class="flex items-center gap-3 pt-1">
|
||||
<a href="{{ $src->url }}"
|
||||
class="inline-flex items-center gap-2 rounded-xl border border-white/10 bg-white/[0.06] px-4 py-2 text-sm font-medium text-white/80 transition hover:bg-white/[0.10] hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-400/60">
|
||||
<svg class="h-4 w-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||
</svg>
|
||||
Back to artwork
|
||||
</a>
|
||||
|
||||
@if(!empty($src->content_type_slug))
|
||||
<a href="/{{ $src->content_type_slug }}"
|
||||
class="inline-flex items-center gap-2 rounded-xl border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-medium text-white/60 transition hover:bg-white/[0.08] hover:text-white/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-400/60">
|
||||
Browse {{ $src->content_type_name ?: 'artworks' }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ══════════════════════════════════════════════════════════════ --}}
|
||||
{{-- RESULTS SECTION (loaded asynchronously) --}}
|
||||
{{-- ══════════════════════════════════════════════════════════════ --}}
|
||||
<section class="px-6 pb-10 pt-8 md:px-10" id="similar-results-section" data-artwork-id="{{ $src->id }}">
|
||||
{{-- ══════════════════════════════════════════════════════════════════ --}}
|
||||
{{-- RESULTS SECTION (loaded asynchronously) --}}
|
||||
{{-- ══════════════════════════════════════════════════════════════════ --}}
|
||||
<section id="similar-results-section" data-artwork-id="{{ $src->id }}">
|
||||
|
||||
{{-- Section heading --}}
|
||||
<div class="mb-6 flex flex-wrap items-center justify-between gap-3">
|
||||
@@ -210,17 +148,12 @@
|
||||
{{-- Pagination (hidden until loaded) --}}
|
||||
<div id="similar-pagination" style="display:none;" class="mt-10 flex items-center justify-center gap-3"></div>
|
||||
|
||||
</section>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
@vite('resources/js/entry-masonry-gallery.jsx')
|
||||
@vite(['resources/js/entry-masonry-gallery.jsx', 'resources/js/entry-similar-artworks-header.jsx'])
|
||||
<script>
|
||||
(function () {
|
||||
const section = document.getElementById('similar-results-section');
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
@php
|
||||
$gridVersion = request()->query('grid') === 'v2' ? 'v2' : 'v1';
|
||||
$deferToolbarSearch = request()->routeIs('index');
|
||||
$deferFontAwesome = request()->routeIs('index');
|
||||
$deferWebManifest = request()->routeIs('index');
|
||||
$isInertiaPage = isset($page) && is_array($page);
|
||||
$shouldRenderBladeSeo = ($useUnifiedSeo ?? false) && (($renderBladeSeo ?? false) || ! $isInertiaPage);
|
||||
$novaViteEntries = [
|
||||
@@ -27,60 +29,148 @@
|
||||
{{-- Global RSS feed discovery --}}
|
||||
<link rel="alternate" type="application/rss+xml" title="Skinbase Latest Artworks" href="{{ url('/rss') }}">
|
||||
|
||||
<!-- Icons (kept for now to preserve current visual output) -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" />
|
||||
<!-- Icons: keep CDN delivery, but keep homepage webfonts out of the initial critical path -->
|
||||
@if(!$deferFontAwesome)
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
|
||||
<link rel="dns-prefetch" href="//cdnjs.cloudflare.com">
|
||||
<link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" as="style" crossorigin>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" media="print" onload="this.media='all'" crossorigin>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" crossorigin>
|
||||
</noscript>
|
||||
@endif
|
||||
|
||||
<link rel="icon" type="image/png" href="/favicon/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/favicon/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/favicon/site.webmanifest" />
|
||||
@if(!$deferWebManifest)
|
||||
<link rel="manifest" href="/favicon/site.webmanifest" />
|
||||
@endif
|
||||
@vite($novaViteEntries)
|
||||
<script>
|
||||
window.SKINBASE_LIMITS = Object.assign({}, window.SKINBASE_LIMITS || {}, {
|
||||
tags: Object.assign({}, (window.SKINBASE_LIMITS && window.SKINBASE_LIMITS.tags) || {}, {
|
||||
max_user_tags: @json((int) config('tags.max_user_tags', 30)),
|
||||
}),
|
||||
});
|
||||
</script>
|
||||
@stack('head')
|
||||
|
||||
@if($deferToolbarSearch)
|
||||
<script type="module">
|
||||
(() => {
|
||||
const searchEntryUrl = @js(Vite::asset('resources/js/entry-search.jsx'));
|
||||
const triggerEvents = ['pointerdown', 'touchstart', 'focusin'];
|
||||
let searchLoaded = false;
|
||||
|
||||
const loadSearch = () => {
|
||||
const loadSearch = (intent = null) => {
|
||||
if (searchLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (intent) {
|
||||
window.__sbSearchIntent = intent;
|
||||
}
|
||||
|
||||
searchLoaded = true;
|
||||
cleanup();
|
||||
import(searchEntryUrl);
|
||||
};
|
||||
|
||||
const resolveIntent = (eventTarget) => {
|
||||
return eventTarget?.closest?.('[data-search-intent]')?.getAttribute('data-search-intent') || null;
|
||||
};
|
||||
|
||||
const handlePointerEnter = () => {
|
||||
loadSearch();
|
||||
};
|
||||
|
||||
const handleActivate = (event) => {
|
||||
const intent = resolveIntent(event.target);
|
||||
loadSearch(intent);
|
||||
};
|
||||
|
||||
const handleShortcut = (event) => {
|
||||
if (!((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k')) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
loadSearch(window.matchMedia('(max-width: 767px)').matches ? 'mobile' : 'desktop');
|
||||
};
|
||||
|
||||
const onReady = () => {
|
||||
const searchRoot = document.getElementById('topbar-search-root');
|
||||
if (!searchRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerEvents.forEach((eventName) => {
|
||||
searchRoot.addEventListener(eventName, loadSearch, { once: true, passive: true });
|
||||
});
|
||||
|
||||
if ('requestIdleCallback' in window) {
|
||||
window.requestIdleCallback(loadSearch, { timeout: 2000 });
|
||||
} else {
|
||||
window.setTimeout(loadSearch, 1500);
|
||||
}
|
||||
searchRoot.addEventListener('pointerenter', handlePointerEnter, { once: true, passive: true });
|
||||
searchRoot.addEventListener('click', handleActivate, { passive: true });
|
||||
searchRoot.addEventListener('touchstart', handleActivate, { passive: true });
|
||||
document.addEventListener('keydown', handleShortcut);
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
const searchRoot = document.getElementById('topbar-search-root');
|
||||
if (!searchRoot) {
|
||||
if (searchRoot) {
|
||||
searchRoot.removeEventListener('pointerenter', handlePointerEnter);
|
||||
searchRoot.removeEventListener('click', handleActivate);
|
||||
searchRoot.removeEventListener('touchstart', handleActivate);
|
||||
}
|
||||
document.removeEventListener('keydown', handleShortcut);
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', onReady, { once: true });
|
||||
} else {
|
||||
onReady();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
@endif
|
||||
|
||||
@if($deferFontAwesome)
|
||||
<script>
|
||||
(() => {
|
||||
const href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css';
|
||||
const linkId = 'deferred-font-awesome';
|
||||
let loaded = false;
|
||||
|
||||
const loadFontAwesome = () => {
|
||||
if (loaded || document.getElementById(linkId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerEvents.forEach((eventName) => {
|
||||
searchRoot.removeEventListener(eventName, loadSearch);
|
||||
});
|
||||
loaded = true;
|
||||
cleanup();
|
||||
|
||||
const link = document.createElement('link');
|
||||
link.id = linkId;
|
||||
link.rel = 'stylesheet';
|
||||
link.href = href;
|
||||
link.crossOrigin = 'anonymous';
|
||||
document.head.appendChild(link);
|
||||
};
|
||||
|
||||
const onReady = () => {
|
||||
const toolbar = document.getElementById('nova-toolbar');
|
||||
|
||||
if (toolbar) {
|
||||
toolbar.addEventListener('pointerenter', loadFontAwesome, { once: true, passive: true });
|
||||
toolbar.addEventListener('focusin', loadFontAwesome, { once: true, passive: true });
|
||||
toolbar.addEventListener('pointerdown', loadFontAwesome, { once: true, passive: true });
|
||||
}
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
const toolbar = document.getElementById('nova-toolbar');
|
||||
|
||||
if (toolbar) {
|
||||
toolbar.removeEventListener('pointerenter', loadFontAwesome);
|
||||
toolbar.removeEventListener('focusin', loadFontAwesome);
|
||||
toolbar.removeEventListener('pointerdown', loadFontAwesome);
|
||||
}
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<footer class="border-t border-neutral-800 bg-nova">
|
||||
<div class="px-6 md:px-10 py-8 flex flex-col md:flex-row md:items-center md:justify-between gap-2">
|
||||
<div class="text-xl font-semibold tracking-wide flex items-center gap-1">
|
||||
<img src="https://cdn.skinbase.org/images/skinbase_logo_64.webp" alt="Skinbase" width="320" height="64" class="h-16 w-auto object-contain">
|
||||
<img src="https://cdn.skinbase.org/images/skinbase_logo_64.webp" alt="" width="320" height="64" class="h-16 w-80 object-contain">
|
||||
<span class="sr-only">Skinbase</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<header class="fixed inset-x-0 top-0 z-50 h-16 bg-black/40 backdrop-blur border-b border-white/10">
|
||||
<header id="nova-toolbar" class="fixed inset-x-0 top-0 z-50 h-16 bg-black/40 backdrop-blur border-b border-white/10">
|
||||
<div class="mx-auto w-full h-full px-3 sm:px-4 flex items-center gap-2 sm:gap-3">
|
||||
|
||||
<!-- Mobile hamburger -->
|
||||
@@ -19,15 +19,29 @@
|
||||
|
||||
<!-- Logo -->
|
||||
<a href="/" class="flex items-center gap-2 pr-2 shrink-0">
|
||||
<img src="/gfx/sb_logo.webp" alt="Skinbase.org" width="289" height="100" class="h-9 w-auto rounded-sm shadow-sm object-contain">
|
||||
<img src="/gfx/sb_logo.webp" alt="" width="289" height="100" class="h-9 w-auto rounded-sm shadow-sm object-contain">
|
||||
<span class="sr-only">Skinbase.org</span>
|
||||
</a>
|
||||
|
||||
<!-- Desktop left nav: Discover · Browse · Groups · Creators · Community -->
|
||||
@php
|
||||
$toolbarContentTypes = collect($toolbarContentTypes ?? []);
|
||||
$toolbarContentTypeSlugs = $toolbarContentTypes
|
||||
->pluck('slug')
|
||||
->filter()
|
||||
->map(fn ($slug) => strtolower((string) $slug))
|
||||
->values()
|
||||
->all();
|
||||
$toolbarContentTypeIcons = [
|
||||
'photography' => 'fa-camera',
|
||||
'wallpapers' => 'fa-desktop',
|
||||
'skins' => 'fa-layer-group',
|
||||
'digital-art' => 'fa-palette',
|
||||
'other' => 'fa-folder-open',
|
||||
];
|
||||
$navSection = match(true) {
|
||||
request()->is('discover', 'discover/*') => 'discover',
|
||||
request()->is('browse', 'photography', 'wallpapers', 'skins', 'other', 'tags', 'tags/*') => 'browse',
|
||||
request()->is(...array_merge(['browse', 'tags', 'tags/*'], $toolbarContentTypeSlugs)) => 'browse',
|
||||
request()->is('groups', 'groups/*') => 'groups',
|
||||
request()->is('creators', 'creators/*', 'stories', 'stories/*', 'following', 'leaderboard') => 'creators',
|
||||
request()->is('forum', 'forum/*', 'news', 'news/*') => 'community',
|
||||
@@ -86,18 +100,15 @@
|
||||
<a class="flex items-center gap-3 px-4 py-2.5 text-sm hover:bg-white/5" href="/explore">
|
||||
<i class="fa-solid fa-border-all w-4 text-center text-sb-muted"></i>All Artworks
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-4 py-2.5 text-sm hover:bg-white/5" href="/photography">
|
||||
<i class="fa-solid fa-camera w-4 text-center text-sb-muted"></i>Photography
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-4 py-2.5 text-sm hover:bg-white/5" href="/wallpapers">
|
||||
<i class="fa-solid fa-desktop w-4 text-center text-sb-muted"></i>Wallpapers
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-4 py-2.5 text-sm hover:bg-white/5" href="/skins">
|
||||
<i class="fa-solid fa-layer-group w-4 text-center text-sb-muted"></i>Skins
|
||||
</a>
|
||||
<a class="flex items-center gap-3 px-4 py-2.5 text-sm hover:bg-white/5" href="/other">
|
||||
<i class="fa-solid fa-folder-open w-4 text-center text-sb-muted"></i>Other
|
||||
@foreach($toolbarContentTypes as $contentType)
|
||||
@php
|
||||
$contentTypeSlug = strtolower((string) $contentType->slug);
|
||||
$contentTypeIcon = $toolbarContentTypeIcons[$contentTypeSlug] ?? 'fa-folder-open';
|
||||
@endphp
|
||||
<a class="flex items-center gap-3 px-4 py-2.5 text-sm hover:bg-white/5" href="/{{ $contentTypeSlug }}">
|
||||
<i class="fa-solid {{ $contentTypeIcon }} w-4 text-center text-sb-muted"></i>{{ $contentType->name }}
|
||||
</a>
|
||||
@endforeach
|
||||
<a class="flex items-center gap-3 px-4 py-2.5 text-sm hover:bg-white/5" href="{{ route('categories.index') }}">
|
||||
<i class="fa-solid fa-folder-open w-4 text-center text-sb-muted"></i>Categories
|
||||
</a>
|
||||
@@ -197,7 +208,37 @@
|
||||
|
||||
<!-- Search: collapsed pill → expands on click -->
|
||||
<div class="flex-1 flex items-center justify-center px-1 sm:px-2 min-w-0">
|
||||
<div id="topbar-search-root" class="w-full flex justify-center"></div>
|
||||
<div id="topbar-search-root" class="w-full flex justify-center">
|
||||
@if(request()->routeIs('index'))
|
||||
<button
|
||||
type="button"
|
||||
data-search-intent="mobile"
|
||||
aria-label="Open search"
|
||||
class="md:hidden inline-flex items-center justify-center w-10 h-10 rounded-lg text-soft hover:text-white hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="hidden md:block w-full" style="max-width: clamp(8.75rem, 8vw + 4rem, 10.5rem);">
|
||||
<button
|
||||
type="button"
|
||||
data-search-intent="desktop"
|
||||
aria-label="Search"
|
||||
class="w-full h-10 flex items-center gap-2.5 px-3.5 rounded-lg bg-white/[0.05] border border-white/[0.09] text-soft hover:bg-white/[0.1] hover:border-white/20 hover:text-white transition-colors"
|
||||
>
|
||||
<svg class="w-4 h-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z"/>
|
||||
</svg>
|
||||
<span class="text-sm flex-1 text-left truncate">Search</span>
|
||||
<kbd class="hidden lg:inline-flex shrink-0 items-center gap-0.5 text-[10px] font-medium px-1.5 py-0.5 rounded-md bg-white/[0.06] border border-white/[0.10] text-white/30">
|
||||
CtrlK
|
||||
</kbd>
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@auth
|
||||
@@ -230,13 +271,28 @@
|
||||
<div class="relative">
|
||||
<button class="flex items-center gap-2 pl-1.5 sm:pl-2 pr-2 sm:pr-3 h-10 rounded-lg hover:bg-white/5 transition-colors shrink-0" data-dd="user">
|
||||
@php
|
||||
$toolbarUser = Auth::user();
|
||||
$toolbarUserId = (int) ($userId ?? Auth::id() ?? 0);
|
||||
$toolbarAvatarHash = $avatarHash ?? optional(Auth::user())->profile->avatar_hash ?? null;
|
||||
$toolbarAvatarHash = $avatarHash ?? optional($toolbarUser)->profile->avatar_hash ?? null;
|
||||
$toolbarEmailVerified = method_exists($toolbarUser, 'hasVerifiedEmail')
|
||||
? $toolbarUser->hasVerifiedEmail()
|
||||
: !empty($toolbarUser?->email_verified_at);
|
||||
$toolbarVerificationNoticeRoute = Route::has('verification.notice') ? route('verification.notice') : null;
|
||||
$toolbarVerificationSendRoute = Route::has('verification.send') ? route('verification.send') : null;
|
||||
$toolbarVerificationLinkSent = session('status') === 'verification-link-sent';
|
||||
@endphp
|
||||
<img class="w-7 h-7 rounded-full object-cover ring-1 ring-white/10"
|
||||
src="{{ \App\Support\AvatarUrl::forUser($toolbarUserId, $toolbarAvatarHash, 64) }}"
|
||||
alt="{{ $displayName ?? 'User' }}" />
|
||||
<span class="relative shrink-0">
|
||||
<img class="w-7 h-7 rounded-full object-cover ring-1 {{ $toolbarEmailVerified ? 'ring-white/10' : 'ring-amber-400/30' }}"
|
||||
src="{{ \App\Support\AvatarUrl::forUser($toolbarUserId, $toolbarAvatarHash, 64) }}"
|
||||
alt="" />
|
||||
@unless($toolbarEmailVerified)
|
||||
<span class="absolute -right-0.5 -top-0.5 h-2.5 w-2.5 rounded-full bg-amber-400 ring-2 ring-[#0b1220]" aria-hidden="true"></span>
|
||||
@endunless
|
||||
</span>
|
||||
<span class="hidden min-[900px]:inline-block max-w-[8rem] truncate text-sm text-white/90">{{ $displayName ?? 'User' }}</span>
|
||||
@unless($toolbarEmailVerified)
|
||||
<span class="hidden xl:inline-flex items-center rounded-full border border-amber-400/25 bg-amber-500/10 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-amber-100">Verify email</span>
|
||||
@endunless
|
||||
<svg class="w-4 h-4 opacity-70" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
@@ -262,6 +318,50 @@
|
||||
: route('setup.username.create');
|
||||
@endphp
|
||||
|
||||
@unless($toolbarEmailVerified)
|
||||
<div class="px-3 pt-3 pb-2">
|
||||
<div class="rounded-xl border border-amber-400/20 bg-amber-500/10 px-3 py-3 text-sm text-amber-50">
|
||||
<div class="flex items-start gap-3">
|
||||
<span class="mt-0.5 inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-amber-400/15 text-amber-200">
|
||||
<i class="fa-solid fa-envelope-circle-check text-sm"></i>
|
||||
</span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="font-semibold text-amber-100">Verify your email</div>
|
||||
<p class="mt-1 text-xs leading-5 text-amber-100/85">
|
||||
Confirm <span class="font-medium text-amber-50">{{ $toolbarUser?->email }}</span> to unlock medals and other mature-account actions.
|
||||
</p>
|
||||
|
||||
@if($toolbarVerificationLinkSent)
|
||||
<div class="mt-2 rounded-lg border border-emerald-400/20 bg-emerald-500/10 px-2.5 py-2 text-[11px] font-medium text-emerald-100">
|
||||
A fresh verification link was sent to your email.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
@if($toolbarVerificationNoticeRoute)
|
||||
<a class="inline-flex items-center rounded-lg border border-amber-300/25 bg-white/5 px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.14em] text-amber-50 transition-colors hover:bg-white/10"
|
||||
href="{{ $toolbarVerificationNoticeRoute }}">
|
||||
Open verification page
|
||||
</a>
|
||||
@endif
|
||||
|
||||
@if($toolbarVerificationSendRoute)
|
||||
<form method="POST" action="{{ $toolbarVerificationSendRoute }}">
|
||||
@csrf
|
||||
<button type="submit" class="inline-flex items-center rounded-lg border border-amber-300/25 bg-amber-300/10 px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.14em] text-amber-50 transition-colors hover:bg-amber-300/20">
|
||||
Resend verification email
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-panel my-1"></div>
|
||||
@endunless
|
||||
|
||||
<a class="flex items-center gap-3 px-4 py-2.5 text-sm hover:bg-white/5" href="{{ $routeUpload }}">
|
||||
<span class="w-6 h-6 rounded-md bg-white/5 inline-flex items-center justify-center shrink-0"><i class="fa-solid fa-upload text-xs text-sb-muted"></i></span>
|
||||
Upload
|
||||
@@ -393,10 +493,13 @@
|
||||
</button>
|
||||
<div id="mobileSectionBrowse" data-mobile-section-panel class="hidden mt-0.5 space-y-0.5">
|
||||
<a class="flex items-center gap-3 py-2.5 px-3 rounded-lg hover:bg-white/5" href="/browse"><i class="fa-solid fa-border-all w-4 text-center text-sb-muted"></i>All Artworks</a>
|
||||
<a class="flex items-center gap-3 py-2.5 px-3 rounded-lg hover:bg-white/5" href="/photography"><i class="fa-solid fa-camera w-4 text-center text-sb-muted"></i>Photography</a>
|
||||
<a class="flex items-center gap-3 py-2.5 px-3 rounded-lg hover:bg-white/5" href="/wallpapers"><i class="fa-solid fa-desktop w-4 text-center text-sb-muted"></i>Wallpapers</a>
|
||||
<a class="flex items-center gap-3 py-2.5 px-3 rounded-lg hover:bg-white/5" href="/skins"><i class="fa-solid fa-layer-group w-4 text-center text-sb-muted"></i>Skins</a>
|
||||
<a class="flex items-center gap-3 py-2.5 px-3 rounded-lg hover:bg-white/5" href="/other"><i class="fa-solid fa-folder-open w-4 text-center text-sb-muted"></i>Other</a>
|
||||
@foreach($toolbarContentTypes as $contentType)
|
||||
@php
|
||||
$contentTypeSlug = strtolower((string) $contentType->slug);
|
||||
$contentTypeIcon = $toolbarContentTypeIcons[$contentTypeSlug] ?? 'fa-folder-open';
|
||||
@endphp
|
||||
<a class="flex items-center gap-3 py-2.5 px-3 rounded-lg hover:bg-white/5" href="/{{ $contentTypeSlug }}"><i class="fa-solid {{ $contentTypeIcon }} w-4 text-center text-sb-muted"></i>{{ $contentType->name }}</a>
|
||||
@endforeach
|
||||
<a class="flex items-center gap-3 py-2.5 px-3 rounded-lg hover:bg-white/5" href="/tags"><i class="fa-solid fa-tags w-4 text-center text-sb-muted"></i>Tags</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
18
resources/views/moderation.blade.php
Normal file
18
resources/views/moderation.blade.php
Normal file
@@ -0,0 +1,18 @@
|
||||
@extends('layouts.nova')
|
||||
|
||||
@push('head')
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}" />
|
||||
@vite(['resources/js/moderation.jsx'])
|
||||
<style>
|
||||
body.page-moderation main { padding-top: 4rem; }
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.body.classList.add('page-moderation')
|
||||
})
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
@inertia
|
||||
@endsection
|
||||
@@ -4,37 +4,6 @@
|
||||
|
||||
@section('content')
|
||||
<div class="px-6 pt-10 pb-8 md:px-10" id="search-page" data-q="{{ $q ?? '' }}">
|
||||
@php
|
||||
$hasQuery = isset($q) && $q !== '';
|
||||
$resultCount = method_exists($artworks, 'total') ? (int) $artworks->total() : 0;
|
||||
$groupResults = collect($groups ?? []);
|
||||
$groupResultCount = $groupResults->count();
|
||||
$newsResults = collect($news ?? []);
|
||||
$newsResultCount = $newsResults->count();
|
||||
$hasAnyResults = $resultCount > 0 || $groupResultCount > 0 || $newsResultCount > 0;
|
||||
$galleryArtworks = collect($artworks->items())->map(fn ($art) => [
|
||||
'id' => $art->id ?? null,
|
||||
'name' => $art->name ?? null,
|
||||
'thumb' => $art->thumb_url ?? $art->thumb ?? null,
|
||||
'thumb_srcset' => $art->thumb_srcset ?? null,
|
||||
'uname' => $art->uname ?? '',
|
||||
'username' => $art->username ?? '',
|
||||
'avatar_url' => $art->avatar_url ?? null,
|
||||
'profile_url' => $art->profile_url ?? null,
|
||||
'published_as_type' => $art->published_as_type ?? null,
|
||||
'publisher' => $art->publisher ?? null,
|
||||
'category_name' => $art->category_name ?? '',
|
||||
'category_slug' => $art->category_slug ?? '',
|
||||
'slug' => $art->slug ?? '',
|
||||
'width' => $art->width ?? null,
|
||||
'height' => $art->height ?? null,
|
||||
'views' => $art->views ?? null,
|
||||
'likes' => $art->likes ?? null,
|
||||
'downloads' => $art->downloads ?? null,
|
||||
])->values();
|
||||
$galleryNextPageUrl = method_exists($artworks, 'nextPageUrl') ? $artworks->nextPageUrl() : null;
|
||||
@endphp
|
||||
|
||||
<div class="mb-8">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<img
|
||||
src="{{ $item->thumb_url ?? '' }}"
|
||||
@if(!empty($item->thumb_srcset)) srcset="{{ $item->thumb_srcset }}" @endif
|
||||
sizes="(max-width: 768px) 176px, 208px"
|
||||
alt="{{ $item->name ?? 'Featured artwork' }}"
|
||||
loading="lazy"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
'profile_url' => $art->profile_url ?? null,
|
||||
'published_as_type' => $art->published_as_type ?? null,
|
||||
'publisher' => $art->publisher ?? null,
|
||||
'maturity' => $art->maturity ?? null,
|
||||
'category_name' => $art->category_name ?? '',
|
||||
'category_slug' => $art->category_slug ?? '',
|
||||
'width' => $art->width ?? null,
|
||||
|
||||
@@ -4,10 +4,16 @@
|
||||
|
||||
@push('head')
|
||||
{{-- Preload hero image for faster LCP --}}
|
||||
@if(!empty($props['hero']['thumb_lg']))
|
||||
<link rel="preload" as="image" href="{{ $props['hero']['thumb_lg'] }}">
|
||||
@if(!empty($props['hero']['thumb']) || !empty($props['hero']['thumb_lg']))
|
||||
<link
|
||||
rel="preload"
|
||||
as="image"
|
||||
href="{{ $props['hero']['thumb_lg'] ?? $props['hero']['thumb'] }}"
|
||||
@if(!empty($props['hero']['thumb_srcset'])) imagesrcset="{{ $props['hero']['thumb_srcset'] }}" imagesizes="100vw" @endif
|
||||
fetchpriority="high"
|
||||
>
|
||||
@elseif(!empty($props['hero']['thumb']))
|
||||
<link rel="preload" as="image" href="{{ $props['hero']['thumb'] }}">
|
||||
<link rel="preload" as="image" href="{{ $props['hero']['thumb'] }}" fetchpriority="high">
|
||||
@endif
|
||||
@endpush
|
||||
|
||||
@@ -21,17 +27,18 @@
|
||||
{!! json_encode($props, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) !!}
|
||||
</script>
|
||||
|
||||
<div id="homepage-root" class="min-h-[40vh]">
|
||||
{{-- Loading skeleton (replaced by React on hydration) --}}
|
||||
<div class="space-y-10 px-4 pt-10 sm:px-6 lg:px-8">
|
||||
<div class="h-14 rounded-2xl bg-nova-800/70"></div>
|
||||
<div class="grid gap-4 lg:grid-cols-4">
|
||||
<div class="aspect-video rounded-2xl bg-nova-800/70"></div>
|
||||
<div class="aspect-video rounded-2xl bg-nova-800/70"></div>
|
||||
<div class="aspect-video rounded-2xl bg-nova-800/70"></div>
|
||||
<div class="aspect-video rounded-2xl bg-nova-800/70"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="homepage-root">
|
||||
@if(!empty($props['is_logged_in']))
|
||||
@include('web.home.skeleton-sections', [
|
||||
'showWelcomeSpacer' => true,
|
||||
'variants' => ['gallery', 'gallery', 'gallery', 'gallery', 'gallery', 'gallery', 'gallery', 'gallery', 'collections', 'groups', 'categories', 'creators', 'tags', 'creators', 'news', 'cta'],
|
||||
])
|
||||
@else
|
||||
@include('web.home.skeleton-sections', [
|
||||
'showWelcomeSpacer' => false,
|
||||
'variants' => ['gallery', 'gallery', 'gallery', 'gallery', 'gallery', 'collections', 'groups', 'categories', 'tags', 'creators', 'news', 'cta'],
|
||||
])
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@vite(['resources/js/Pages/Home/HomePage.jsx'])
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
$heroArtwork = $artwork ?? null;
|
||||
$fallbackImage = 'https://files.skinbase.org/default/missing_lg.webp';
|
||||
$heroImage = $heroArtwork['thumb_lg'] ?? $heroArtwork['thumb'] ?? $fallbackImage;
|
||||
$heroImageSrcset = $heroArtwork['thumb_srcset'] ?? null;
|
||||
$heroImageSizes = '100vw';
|
||||
@endphp
|
||||
|
||||
@if (!$heroArtwork)
|
||||
@@ -15,19 +17,23 @@
|
||||
Discover. Create. Inspire.
|
||||
</p>
|
||||
<div class="mt-4 flex flex-wrap gap-3">
|
||||
<a href="/discover/trending" class="rounded-xl bg-accent px-5 py-2 text-sm font-semibold text-white shadow-lg transition hover:brightness-110">Explore Trending</a>
|
||||
<a href="/discover/trending" class="btn-accent-solid rounded-xl px-5 py-2 text-sm font-semibold">Explore Trending</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@else
|
||||
<section class="group relative flex min-h-[62vh] max-h-[420px] w-full items-end overflow-hidden bg-nova-900 md:min-h-[38vh] md:max-h-[460px]">
|
||||
<img
|
||||
src="{{ $heroImage }}"
|
||||
alt="{{ $heroArtwork['title'] ?? 'Featured artwork' }}"
|
||||
class="absolute inset-0 h-full w-full object-cover transition-transform duration-700 group-hover:scale-[1.02]"
|
||||
fetchpriority="high"
|
||||
decoding="async"
|
||||
/>
|
||||
<picture>
|
||||
<img
|
||||
src="{{ $heroImage }}"
|
||||
@if($heroImageSrcset) srcset="{{ $heroImageSrcset }}" sizes="{{ $heroImageSizes }}" @endif
|
||||
alt="{{ $heroArtwork['title'] ?? 'Featured artwork' }}"
|
||||
class="absolute inset-0 h-full w-full object-cover transition-transform duration-700 group-hover:scale-[1.02]"
|
||||
fetchpriority="high"
|
||||
loading="eager"
|
||||
decoding="sync"
|
||||
/>
|
||||
</picture>
|
||||
|
||||
<div class="pointer-events-none absolute inset-0 bg-gradient-to-t from-nova-900 via-nova-900/55 to-transparent"></div>
|
||||
|
||||
@@ -47,7 +53,7 @@
|
||||
<div class="mt-4 flex flex-wrap gap-3">
|
||||
<a
|
||||
href="/discover/trending"
|
||||
class="rounded-xl bg-accent px-5 py-2 text-sm font-semibold text-white shadow-lg transition hover:brightness-110"
|
||||
class="btn-accent-solid rounded-xl px-5 py-2 text-sm font-semibold"
|
||||
>
|
||||
Explore Trending
|
||||
</a>
|
||||
|
||||
67
resources/views/web/home/skeleton-sections.blade.php
Normal file
67
resources/views/web/home/skeleton-sections.blade.php
Normal file
@@ -0,0 +1,67 @@
|
||||
@if(!empty($showWelcomeSpacer))
|
||||
<div class="mt-10 px-4 sm:px-6 lg:px-8" aria-hidden="true">
|
||||
<div class="h-20 animate-pulse rounded-[28px] border border-white/10 bg-nova-800/70"></div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@foreach(($variants ?? []) as $variant)
|
||||
@if($variant === 'tags')
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8" aria-hidden="true">
|
||||
<div class="mb-5 h-8 w-48 animate-pulse rounded-xl bg-nova-800/70"></div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="h-9 w-24 animate-pulse rounded-full bg-nova-800/70"></div>
|
||||
<div class="h-9 w-32 animate-pulse rounded-full bg-nova-800/70"></div>
|
||||
<div class="h-9 w-28 animate-pulse rounded-full bg-nova-800/70"></div>
|
||||
<div class="h-9 w-36 animate-pulse rounded-full bg-nova-800/70"></div>
|
||||
<div class="h-9 w-24 animate-pulse rounded-full bg-nova-800/70"></div>
|
||||
<div class="h-9 w-32 animate-pulse rounded-full bg-nova-800/70"></div>
|
||||
<div class="h-9 w-28 animate-pulse rounded-full bg-nova-800/70"></div>
|
||||
<div class="h-9 w-36 animate-pulse rounded-full bg-nova-800/70"></div>
|
||||
</div>
|
||||
</section>
|
||||
@elseif($variant === 'cta')
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8" aria-hidden="true">
|
||||
<div class="h-40 animate-pulse rounded-[28px] border border-white/10 bg-nova-800/70"></div>
|
||||
</section>
|
||||
@else
|
||||
@php
|
||||
$showSubtitle = in_array($variant, ['collections', 'groups', 'news'], true);
|
||||
$gridClass = match ($variant) {
|
||||
'creators' => 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-6',
|
||||
'news' => 'grid-cols-1',
|
||||
'categories' => 'grid-cols-2 lg:grid-cols-4',
|
||||
'collections' => 'grid-cols-1 lg:grid-cols-2 xl:grid-cols-3',
|
||||
'groups' => 'grid-cols-1 sm:grid-cols-2 xl:grid-cols-4',
|
||||
default => 'grid-cols-2 xl:grid-cols-4',
|
||||
};
|
||||
$cardClass = match ($variant) {
|
||||
'categories' => 'h-28 rounded-2xl',
|
||||
'news' => 'h-24 rounded-2xl',
|
||||
'creators' => 'h-64 rounded-2xl',
|
||||
'collections', 'groups' => 'h-80 rounded-[28px]',
|
||||
default => 'aspect-[4/3] rounded-2xl',
|
||||
};
|
||||
$cardCount = match ($variant) {
|
||||
'creators' => 6,
|
||||
'news' => 4,
|
||||
default => 4,
|
||||
};
|
||||
@endphp
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8" aria-hidden="true">
|
||||
<div class="mb-5 flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<div class="h-8 w-48 animate-pulse rounded-xl bg-nova-800/70"></div>
|
||||
@if($showSubtitle)
|
||||
<div class="mt-3 h-4 w-80 max-w-full animate-pulse rounded bg-nova-800/60"></div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="hidden h-5 w-24 animate-pulse rounded bg-nova-800/60 sm:block"></div>
|
||||
</div>
|
||||
<div class="grid gap-4 {{ $gridClass }}">
|
||||
@for($i = 0; $i < $cardCount; $i++)
|
||||
<div class="animate-pulse bg-nova-800/70 {{ $cardClass }}"></div>
|
||||
@endfor
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
@endforeach
|
||||
@@ -70,12 +70,14 @@
|
||||
<input
|
||||
id="tags-search"
|
||||
type="search"
|
||||
role="combobox"
|
||||
name="q"
|
||||
value="{{ $query }}"
|
||||
placeholder="Search aesthetics, games, styles..."
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
aria-autocomplete="list"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded="false"
|
||||
aria-controls="tags-search-suggestions"
|
||||
data-tags-search-input
|
||||
|
||||
Reference in New Issue
Block a user