feat(academy): prepare AI Academy v1 for production enablement
This commit is contained in:
@@ -22,288 +22,90 @@
|
||||
@section('content')
|
||||
<?php
|
||||
$isLoggedIn = ! empty($props['is_logged_in']);
|
||||
$artFallback = 'https://files.skinbase.org/default/missing_md.webp';
|
||||
$avatarFallback = 'https://files.skinbase.org/default/avatar_default.webp';
|
||||
$guestArtworkSections = [
|
||||
[
|
||||
'title' => 'Rising Now',
|
||||
'href' => '/discover/rising',
|
||||
'link_label' => 'See all',
|
||||
'items' => is_array($props['rising'] ?? null) ? $props['rising'] : [],
|
||||
'layout' => 'rail',
|
||||
'columns' => 'lg:grid-cols-5',
|
||||
'badge' => 'Rising',
|
||||
'badge_class' => 'bg-emerald-500/80 text-white',
|
||||
],
|
||||
[
|
||||
'title' => 'Trending Now',
|
||||
'title' => 'Trending This Week',
|
||||
'href' => '/discover/trending',
|
||||
'link_label' => 'See all',
|
||||
'items' => is_array($props['trending'] ?? null) ? $props['trending'] : [],
|
||||
'badge' => 'Trending',
|
||||
'badge_class' => 'bg-sky-500/80 text-white',
|
||||
'layout' => 'grid',
|
||||
'columns' => 'xl:grid-cols-4',
|
||||
],
|
||||
[
|
||||
'title' => 'Community Favorites',
|
||||
'href' => '/explore?sort=top-rated',
|
||||
'link_label' => 'See all',
|
||||
'items' => is_array($props['community_favorites'] ?? null) ? $props['community_favorites'] : [],
|
||||
'badge' => 'Favorites',
|
||||
'badge_class' => 'bg-amber-500/85 text-slate-950',
|
||||
'layout' => 'grid',
|
||||
'columns' => 'xl:grid-cols-4',
|
||||
'description' => 'Recent medal momentum from the community. This rail highlights the strongest 30-day medal signal.',
|
||||
],
|
||||
[
|
||||
'title' => 'Hall of Fame',
|
||||
'href' => '/explore/best',
|
||||
'items' => is_array($props['hall_of_fame'] ?? null) ? $props['hall_of_fame'] : [],
|
||||
'layout' => 'grid',
|
||||
'columns' => 'xl:grid-cols-4',
|
||||
'description' => 'All-time medal standouts that keep being remembered long after publication.',
|
||||
],
|
||||
[
|
||||
'title' => 'Fresh Uploads',
|
||||
'href' => '/discover/fresh',
|
||||
'link_label' => 'See all',
|
||||
'items' => is_array($props['fresh'] ?? null) ? $props['fresh'] : [],
|
||||
'badge' => 'Fresh',
|
||||
'badge_class' => 'bg-fuchsia-500/80 text-white',
|
||||
'layout' => 'grid',
|
||||
'columns' => 'xl:grid-cols-4',
|
||||
],
|
||||
];
|
||||
$guestTags = is_array($props['tags'] ?? null) ? $props['tags'] : [];
|
||||
$guestCreators = is_array($props['creators'] ?? null) ? $props['creators'] : [];
|
||||
$guestNews = is_array($props['news'] ?? null) ? $props['news'] : [];
|
||||
?>
|
||||
|
||||
@include('web.home.hero', ['artwork' => $props['hero'] ?? null])
|
||||
|
||||
{{-- Inline props for the React component (avoids data-attribute length limits) --}}
|
||||
<script id="homepage-props" type="application/json">
|
||||
{!! json_encode($props, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP) !!}
|
||||
</script>
|
||||
|
||||
<div id="homepage-root">
|
||||
<main class="pb-24">
|
||||
@include('web.home.announcement', ['announcement' => $props['announcement'] ?? null])
|
||||
|
||||
@if($isLoggedIn)
|
||||
@include('web.home.skeleton-sections', [
|
||||
'showWelcomeSpacer' => true,
|
||||
'variants' => ['gallery', 'gallery', 'gallery', 'gallery', 'gallery', 'gallery', 'gallery', 'gallery', 'collections', 'groups', 'categories', 'creators', 'tags', 'creators', 'news', 'cta'],
|
||||
])
|
||||
@include('web.home.sections.welcome-row', ['userData' => $props['user_data'] ?? null])
|
||||
@include('web.home.sections.artwork-section', ['title' => 'From Creators You Follow', 'href' => '/discover/following', 'items' => $props['from_following'] ?? [], 'layout' => 'grid', 'columns' => 'lg:grid-cols-5', 'emptyMessage' => "You're not following anyone yet.", 'emptyDescription' => 'Follow creators you love to see their latest uploads here.', 'emptyCtaHref' => '/creators/top', 'emptyCtaLabel' => 'Discover creators'])
|
||||
@include('web.home.sections.artwork-section', ['eyebrow' => 'Personalized feed', 'title' => 'Picked For You', 'description' => !empty($props['preferences']['top_tags'][0]) ? 'Fresh recommendations informed by your recent interest in #' . $props['preferences']['top_tags'][0] . '.' : 'A live preview of your personalized discovery feed.', 'href' => '/discover/for-you', 'items' => $props['for_you'] ?? [], 'layout' => 'grid', 'columns' => 'xl:grid-cols-4'])
|
||||
@include('web.home.sections.artwork-section', ['title' => 'Rising Now', 'href' => '/discover/rising', 'items' => $props['rising'] ?? [], 'layout' => 'rail', 'columns' => 'lg:grid-cols-5', 'badge' => 'Rising', 'badge_class' => 'bg-emerald-500/80 text-white'])
|
||||
@include('web.home.sections.artwork-section', ['title' => 'Trending This Week', 'href' => '/discover/trending', 'items' => $props['trending'] ?? [], 'layout' => 'grid', 'columns' => 'xl:grid-cols-4'])
|
||||
@include('web.home.sections.artwork-section', ['title' => 'Community Favorites', 'href' => '/explore?sort=top-rated', 'items' => $props['community_favorites'] ?? [], 'layout' => 'grid', 'columns' => 'xl:grid-cols-4', 'description' => 'Recent medal momentum from the community. This rail highlights the strongest 30-day medal signal.'])
|
||||
@include('web.home.sections.artwork-section', ['title' => 'Hall of Fame', 'href' => '/explore/best', 'items' => $props['hall_of_fame'] ?? [], 'layout' => 'grid', 'columns' => 'xl:grid-cols-4', 'description' => 'All-time medal standouts that keep being remembered long after publication.'])
|
||||
@if(!empty($props['preferences']['top_tags'][0]))
|
||||
@include('web.home.sections.artwork-section', ['title' => 'Because You Like #' . $props['preferences']['top_tags'][0], 'href' => '/browse?tags=' . rawurlencode((string) $props['preferences']['top_tags'][0]), 'items' => $props['by_categories'] ?? [], 'layout' => 'grid', 'columns' => 'lg:grid-cols-5'])
|
||||
@endif
|
||||
@include('web.home.sections.artwork-section', ['title' => 'Fresh Uploads', 'href' => '/discover/fresh', 'items' => $props['fresh'] ?? [], 'layout' => 'grid', 'columns' => 'xl:grid-cols-4'])
|
||||
@include('web.home.sections.collections', ['collections' => $props['collections_trending'] ?: ($props['collections_featured'] ?: ($props['collections_recent'] ?? []))])
|
||||
@include('web.home.sections.world-spotlight', ['world' => $props['world_spotlight'] ?? null])
|
||||
@include('web.home.sections.groups', ['groups' => $props['groups'] ?? []])
|
||||
@include('web.home.sections.categories')
|
||||
@include('web.home.sections.suggested-creators', ['creators' => $props['suggested_creators'] ?? []])
|
||||
@include('web.home.sections.tags', ['tags' => $props['tags'] ?? []])
|
||||
@include('web.home.sections.creators', ['creators' => $props['creators'] ?? []])
|
||||
@include('web.home.sections.news', ['items' => $props['news'] ?? []])
|
||||
@include('web.home.sections.cta', ['isLoggedIn' => true])
|
||||
@else
|
||||
<div class="pb-24">
|
||||
@foreach ($guestArtworkSections as $section)
|
||||
@if (count($section['items']) > 0)
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-5 flex items-center justify-between gap-4">
|
||||
<h2 class="text-xl font-bold text-white">{{ $section['title'] }}</h2>
|
||||
<a href="{{ $section['href'] }}" class="text-sm text-nova-300 transition hover:text-white">
|
||||
{{ $section['link_label'] }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="flex snap-x snap-mandatory gap-4 overflow-x-auto pb-3 lg:grid lg:grid-cols-5 lg:overflow-visible">
|
||||
@foreach (array_slice(array_values($section['items']), 0, 5) as $item)
|
||||
@php
|
||||
$itemTitle = (string) ($item['title'] ?? $item['name'] ?? 'Untitled');
|
||||
$itemUrl = (string) ($item['url'] ?? '#');
|
||||
$itemThumb = (string) ($item['thumb'] ?? $item['thumb_url'] ?? $artFallback);
|
||||
$itemAuthor = (string) ($item['author'] ?? 'Artist');
|
||||
$itemAuthorAvatar = (string) ($item['author_avatar'] ?? $item['avatar_url'] ?? $avatarFallback);
|
||||
$itemAuthorUsername = (string) ($item['author_username'] ?? $item['username'] ?? '');
|
||||
// Generate responsive srcset from md thumbnail (CDN has sm/md/lg variants)
|
||||
$itemThumbSm = str_contains($itemThumb, '/artworks/md/')
|
||||
? str_replace('/artworks/md/', '/artworks/sm/', $itemThumb)
|
||||
: '';
|
||||
$itemThumbLg = str_contains($itemThumb, '/artworks/md/')
|
||||
? str_replace('/artworks/md/', '/artworks/lg/', $itemThumb)
|
||||
: '';
|
||||
@endphp
|
||||
<article class="min-w-[72%] snap-start sm:min-w-[44%] lg:min-w-0">
|
||||
<a
|
||||
href="{{ $itemUrl }}"
|
||||
class="group relative block overflow-hidden rounded-2xl bg-black/20 shadow-lg shadow-black/40 ring-1 ring-white/5 transition-all duration-200 ease-out hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/70"
|
||||
>
|
||||
<div class="relative aspect-video overflow-hidden bg-neutral-900">
|
||||
<div class="pointer-events-none absolute inset-0 z-10 bg-gradient-to-br from-white/10 via-white/5 to-transparent"></div>
|
||||
<img
|
||||
src="{{ $itemThumb }}"
|
||||
@if($itemThumbSm !== '' && $itemThumbLg !== '')
|
||||
srcset="{{ $itemThumbSm }} 300w, {{ $itemThumb }} 500w, {{ $itemThumbLg }} 900w"
|
||||
sizes="(max-width: 640px) 72vw, (max-width: 1024px) 44vw, 240px"
|
||||
@endif
|
||||
alt="{{ $itemTitle }}"
|
||||
class="h-full w-full object-cover transition-[transform,filter] duration-300 ease-out group-hover:scale-[1.04]"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
|
||||
<div class="absolute left-3 top-3 z-30">
|
||||
<span class="inline-flex items-center rounded-md px-2 py-1 text-[11px] font-bold ring-1 ring-white/10 backdrop-blur-sm {{ $section['badge_class'] }}">
|
||||
{{ $section['badge'] }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<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">{{ $itemTitle }}</div>
|
||||
<div class="mt-1 flex items-center gap-2 text-xs text-white/80">
|
||||
<img
|
||||
src="{{ $itemAuthorAvatar }}"
|
||||
alt="{{ $itemAuthor }}"
|
||||
class="h-6 w-6 shrink-0 rounded-full object-cover"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
<span class="truncate">{{ $itemAuthor }}</span>
|
||||
@if ($itemAuthorUsername !== '')
|
||||
<span class="shrink-0 text-white/50">@{{ $itemAuthorUsername }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="sr-only">{{ $itemTitle }} by {{ $itemAuthor }}</span>
|
||||
</a>
|
||||
</article>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
@endforeach
|
||||
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div class="overflow-hidden rounded-2xl border border-white/10 bg-[linear-gradient(180deg,rgba(11,17,28,0.92),rgba(7,11,19,0.95))] px-6 py-8 ring-1 ring-white/5 sm:px-8">
|
||||
<div class="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div class="max-w-2xl">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.22em] text-white/35">Browse the archive</p>
|
||||
<h2 class="mt-3 text-2xl font-bold text-white sm:text-3xl">Explore categories, wallpapers, skins, and creator collections.</h2>
|
||||
<p class="mt-3 text-sm leading-7 text-white/60">
|
||||
Dive into the full Skinbase directory to browse curated categories, trending artwork types, and classic collections.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<a href="/categories" class="rounded-full bg-sky-500 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-sky-400">Open categories</a>
|
||||
<a href="/wallpapers" class="rounded-full border border-white/10 bg-white/[0.04] px-5 py-2.5 text-sm font-semibold text-white/80 transition hover:border-white/20 hover:bg-white/[0.08] hover:text-white">Wallpapers</a>
|
||||
<a href="/skins" class="rounded-full border border-white/10 bg-white/[0.04] px-5 py-2.5 text-sm font-semibold text-white/80 transition hover:border-white/20 hover:bg-white/[0.08] hover:text-white">Skins</a>
|
||||
<a href="/photography" class="rounded-full border border-white/10 bg-white/[0.04] px-5 py-2.5 text-sm font-semibold text-white/80 transition hover:border-white/20 hover:bg-white/[0.08] hover:text-white">Photography</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@if (count($guestTags) > 0)
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<h2 class="mb-5 text-xl font-bold text-white">Popular Tags</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@foreach ($guestTags as $tag)
|
||||
<a
|
||||
href="/tag/{{ $tag['slug'] ?? '' }}"
|
||||
class="rounded-full bg-nova-800 px-4 py-1.5 text-sm font-medium text-nova-200 transition hover:bg-nova-700 hover:text-white"
|
||||
>
|
||||
{{ $tag['name'] ?? 'Tag' }}
|
||||
@if ((int) ($tag['count'] ?? 0) > 0)
|
||||
<span class="ml-1.5 text-xs text-soft">{{ number_format((int) ($tag['count'] ?? 0)) }}</span>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
|
||||
@if (count($guestCreators) > 0)
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-5 flex items-center justify-between gap-4">
|
||||
<h2 class="text-xl font-bold text-white">Creator Spotlight</h2>
|
||||
<a href="/members" class="text-sm text-nova-300 transition hover:text-white">All creators</a>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6">
|
||||
@foreach (array_slice(array_values($guestCreators), 0, 6) as $creator)
|
||||
@php
|
||||
$creatorName = (string) ($creator['name'] ?? 'Creator');
|
||||
$creatorUrl = (string) ($creator['url'] ?? '#');
|
||||
$creatorAvatar = (string) ($creator['avatar'] ?? $avatarFallback);
|
||||
$creatorBgThumb = (string) ($creator['bg_thumb'] ?? '');
|
||||
@endphp
|
||||
<a
|
||||
href="{{ $creatorUrl }}"
|
||||
aria-label="View {{ $creatorName }} profile"
|
||||
class="group relative flex min-h-[16rem] flex-col items-center overflow-hidden rounded-xl bg-panel p-5 text-center shadow-sm transition hover:ring-1 hover:ring-nova-500"
|
||||
@if ($creatorBgThumb !== '')
|
||||
style="background-image: linear-gradient(to top, rgba(13, 19, 28, 0.96), rgba(13, 19, 28, 0.7)), url('{{ $creatorBgThumb }}'); background-size: cover; background-position: center;"
|
||||
@endif
|
||||
>
|
||||
<img
|
||||
src="{{ $creatorAvatar }}"
|
||||
alt="{{ $creatorName }}"
|
||||
class="relative mx-auto h-16 w-16 rounded-full bg-nova-800/80 object-cover ring-4 ring-nova-800"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
<h3 class="relative mt-2 text-sm font-semibold text-white">{{ $creatorName }}</h3>
|
||||
<p class="relative mt-1 flex flex-wrap justify-center gap-x-3 gap-y-1 text-xs text-soft">
|
||||
<span>Uploads {{ number_format((int) ($creator['uploads'] ?? 0)) }}</span>
|
||||
@if ((int) ($creator['weekly_uploads'] ?? 0) > 0)
|
||||
<span class="font-semibold text-accent">{{ number_format((int) ($creator['weekly_uploads'] ?? 0)) }} this week</span>
|
||||
@endif
|
||||
<span>Views {{ number_format((int) ($creator['views'] ?? 0)) }}</span>
|
||||
@if ((int) ($creator['awards'] ?? 0) > 0)
|
||||
<span>Awards {{ number_format((int) ($creator['awards'] ?? 0)) }}</span>
|
||||
@endif
|
||||
</p>
|
||||
<span class="relative mt-3 inline-flex items-center justify-center rounded-lg bg-nova-700 px-4 py-1.5 text-xs font-semibold text-white transition group-hover:bg-nova-600">
|
||||
View profile
|
||||
</span>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
|
||||
@if (count($guestNews) > 0)
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-5 flex items-center justify-between gap-4">
|
||||
<h2 class="text-xl font-bold text-white">News and Updates</h2>
|
||||
<a href="/news" class="text-sm text-nova-300 transition hover:text-white">All news</a>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded-[24px] border border-white/10 bg-panel divide-y divide-nova-800">
|
||||
@foreach (array_slice(array_values($guestNews), 0, 6) as $item)
|
||||
<a
|
||||
href="{{ $item['url'] ?? '#' }}"
|
||||
class="grid gap-3 px-5 py-4 transition hover:bg-nova-800 sm:grid-cols-[minmax(0,1fr)_auto] sm:items-start"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
@if (!empty($item['eyebrow']))
|
||||
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-nova-300">{{ $item['eyebrow'] }}</div>
|
||||
@endif
|
||||
<div class="mt-1 line-clamp-2 text-sm font-medium text-white">{{ $item['title'] ?? 'News item' }}</div>
|
||||
@if (!empty($item['excerpt']))
|
||||
<p class="mt-2 line-clamp-2 text-sm leading-6 text-soft">{{ $item['excerpt'] }}</p>
|
||||
@endif
|
||||
</div>
|
||||
@if (!empty($item['date']))
|
||||
<span class="shrink-0 text-xs text-soft">{{ $item['date'] }}</span>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-accent/20 via-nova-800 to-nova-900 px-8 py-12 text-center ring-1 ring-white/5">
|
||||
<div class="pointer-events-none absolute -right-12 -top-12 h-40 w-40 rounded-full bg-accent/10 blur-3xl"></div>
|
||||
<div class="pointer-events-none absolute -bottom-10 -left-10 h-32 w-32 rounded-full bg-sky-500/10 blur-2xl"></div>
|
||||
|
||||
<div class="relative z-10">
|
||||
<p class="text-xs font-semibold uppercase tracking-widest text-accent">Join the community</p>
|
||||
<h2 class="mt-2 text-2xl font-bold text-white sm:text-3xl">Ready to share your creativity?</h2>
|
||||
<p class="mx-auto mt-3 max-w-md text-sm text-nova-300">
|
||||
Upload your artworks, wallpapers, and skins to reach thousands of enthusiasts around the world.
|
||||
</p>
|
||||
<div class="mt-6 flex flex-wrap justify-center gap-3">
|
||||
<a href="/login?redirect=/upload" class="btn-accent-solid rounded-xl px-6 py-2.5 text-sm font-semibold">
|
||||
Upload your artwork
|
||||
</a>
|
||||
<a href="/register" class="rounded-xl border border-white/10 bg-nova-700 px-6 py-2.5 text-sm font-semibold text-white transition hover:bg-nova-600">
|
||||
Create account
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@foreach ($guestArtworkSections as $section)
|
||||
@include('web.home.sections.artwork-section', $section)
|
||||
@endforeach
|
||||
@include('web.home.sections.collections', ['collections' => $props['collections_trending'] ?: ($props['collections_featured'] ?: ($props['collections_editorial'] ?? []))])
|
||||
@include('web.home.sections.world-spotlight', ['world' => $props['world_spotlight'] ?? null])
|
||||
@include('web.home.sections.groups', ['groups' => $props['groups'] ?? []])
|
||||
@include('web.home.sections.categories')
|
||||
@include('web.home.sections.tags', ['tags' => $props['tags'] ?? []])
|
||||
@include('web.home.sections.creators', ['creators' => $props['creators'] ?? []])
|
||||
@include('web.home.sections.news', ['items' => $props['news'] ?? []])
|
||||
@include('web.home.sections.cta', ['isLoggedIn' => false])
|
||||
@endif
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@vite(['resources/js/Pages/Home/HomePage.jsx'])
|
||||
@vite(['resources/js/public/home.js'])
|
||||
@endsection
|
||||
|
||||
|
||||
@@ -1,58 +1,93 @@
|
||||
@php
|
||||
$homepageAnnouncement = is_array($announcement ?? null) ? $announcement : null;
|
||||
$overlayOpacity = max(0, min(100, (int) ($homepageAnnouncement['overlay_opacity'] ?? 55)));
|
||||
$announcementId = (int) ($homepageAnnouncement['id'] ?? 0);
|
||||
$dismissVersion = (int) ($homepageAnnouncement['dismiss_version'] ?? 1);
|
||||
@endphp
|
||||
|
||||
@if ($homepageAnnouncement)
|
||||
<section class="px-4 pt-8 sm:px-6 lg:px-8">
|
||||
<div class="relative mx-auto max-w-7xl overflow-hidden rounded-[2rem] border border-cyan-300/15 bg-[radial-gradient(circle_at_top_left,rgba(34,211,238,0.26),transparent_30%),radial-gradient(circle_at_80%_20%,rgba(168,85,247,0.22),transparent_28%),linear-gradient(135deg,rgba(6,12,24,0.96),rgba(10,17,34,0.9))] text-white shadow-[0_28px_90px_rgba(0,0,0,0.35)]">
|
||||
@if (!empty($homepageAnnouncement['background_image_url']))
|
||||
<div class="absolute inset-0">
|
||||
<img src="{{ $homepageAnnouncement['background_image_url'] }}" alt="" class="h-full w-full object-cover">
|
||||
<div class="absolute inset-0 bg-slate-950" style="opacity: {{ $overlayOpacity / 100 }}"></div>
|
||||
</div>
|
||||
@endif
|
||||
<section class="px-4 pt-8 sm:px-6 lg:px-8" data-home-announcement-wrapper>
|
||||
<div class="mx-auto max-w-7xl">
|
||||
<button
|
||||
type="button"
|
||||
class="hidden items-center gap-2 rounded-full border border-white/10 bg-white/[0.06] px-4 py-2 text-sm font-semibold text-white/90 transition hover:border-white/20 hover:bg-white/[0.1]"
|
||||
data-home-announcement-restore
|
||||
data-announcement-id="{{ $announcementId }}"
|
||||
data-dismiss-version="{{ $dismissVersion }}"
|
||||
>
|
||||
<span aria-hidden="true">✨</span>
|
||||
<span>Show Skinbase announcement</span>
|
||||
</button>
|
||||
|
||||
<div class="pointer-events-none absolute inset-0 bg-gradient-to-br from-indigo-400/20 via-sky-400/12 to-transparent"></div>
|
||||
<div class="pointer-events-none absolute inset-0 bg-[linear-gradient(180deg,rgba(255,255,255,0.06),transparent_22%,rgba(2,6,23,0.15)_100%)]"></div>
|
||||
<div
|
||||
class="relative overflow-hidden rounded-[2rem] border border-cyan-300/15 bg-[radial-gradient(circle_at_top_left,rgba(34,211,238,0.26),transparent_30%),radial-gradient(circle_at_80%_20%,rgba(168,85,247,0.22),transparent_28%),linear-gradient(135deg,rgba(6,12,24,0.96),rgba(10,17,34,0.9))] text-white shadow-[0_28px_90px_rgba(0,0,0,0.35)]"
|
||||
data-home-announcement
|
||||
data-announcement-id="{{ $announcementId }}"
|
||||
data-dismiss-version="{{ $dismissVersion }}"
|
||||
>
|
||||
@if (!empty($homepageAnnouncement['background_image_url']))
|
||||
<div class="absolute inset-0">
|
||||
<img src="{{ $homepageAnnouncement['background_image_url'] }}" alt="" class="h-full w-full object-cover">
|
||||
<div class="absolute inset-0 bg-slate-950" style="opacity: {{ $overlayOpacity / 100 }}"></div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="relative grid gap-8 px-6 py-7 sm:px-8 lg:grid-cols-[minmax(0,1.25fr)_auto] lg:items-end lg:px-10 lg:py-10">
|
||||
<div class="max-w-3xl">
|
||||
@if (!empty($homepageAnnouncement['badge_text']))
|
||||
<div class="inline-flex rounded-full border border-cyan-200/20 bg-cyan-300/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-cyan-100">
|
||||
{{ $homepageAnnouncement['badge_text'] }}
|
||||
</div>
|
||||
@endif
|
||||
<div class="pointer-events-none absolute inset-0 bg-gradient-to-br from-indigo-400/20 via-sky-400/12 to-transparent"></div>
|
||||
<div class="pointer-events-none absolute inset-0 bg-[linear-gradient(180deg,rgba(255,255,255,0.06),transparent_22%,rgba(2,6,23,0.15)_100%)]"></div>
|
||||
|
||||
<h2 class="mt-4 text-3xl font-semibold tracking-[-0.05em] sm:text-4xl lg:text-[3.15rem]">
|
||||
{{ $homepageAnnouncement['title'] ?? '' }}
|
||||
</h2>
|
||||
@if (!empty($homepageAnnouncement['is_dismissible']))
|
||||
<button
|
||||
type="button"
|
||||
class="absolute right-5 top-5 z-10 inline-flex items-center gap-2 rounded-full border border-white/10 bg-black/30 px-3 py-2 text-xs font-semibold uppercase tracking-[0.18em] text-white/70 transition hover:border-white/20 hover:bg-black/45 hover:text-white sm:right-6 sm:top-6 lg:right-8 lg:top-8"
|
||||
data-home-announcement-dismiss
|
||||
aria-label="Dismiss homepage announcement"
|
||||
>
|
||||
<svg aria-hidden="true" viewBox="0 0 16 16" class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||||
<path d="M3.5 3.5 12.5 12.5"></path>
|
||||
<path d="M12.5 3.5 3.5 12.5"></path>
|
||||
</svg>
|
||||
Dismiss
|
||||
</button>
|
||||
@endif
|
||||
|
||||
@if (!empty($homepageAnnouncement['subtitle']))
|
||||
<p class="mt-3 max-w-2xl text-base leading-7 text-white/80 sm:text-lg">
|
||||
{{ $homepageAnnouncement['subtitle'] }}
|
||||
</p>
|
||||
@endif
|
||||
|
||||
@if (!empty($homepageAnnouncement['content_html']))
|
||||
<div class="prose prose-invert mt-5 max-w-2xl text-sm leading-7 prose-a:text-cyan-200 prose-strong:text-white [&_blockquote]:my-5 [&_h2]:mb-3 [&_h2]:mt-7 [&_h3]:mb-2 [&_h3]:mt-6 [&_li+li]:mt-1.5 [&_ol]:my-5 [&_p]:my-0 [&_p+p]:mt-6 [&_ul]:my-5 sm:text-base">
|
||||
{!! $homepageAnnouncement['content_html'] !!}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-start gap-3 lg:items-end">
|
||||
<div class="flex flex-wrap gap-3 lg:justify-end">
|
||||
@if (!empty($homepageAnnouncement['primary_link']['url']) && !empty($homepageAnnouncement['primary_link']['label']))
|
||||
<a href="{{ $homepageAnnouncement['primary_link']['url'] }}" class="inline-flex items-center justify-center rounded-full border border-cyan-300/30 bg-cyan-300/15 px-5 py-3 text-sm font-semibold text-cyan-50 transition hover:bg-cyan-300/22">
|
||||
{{ $homepageAnnouncement['primary_link']['label'] }}
|
||||
</a>
|
||||
<div class="relative grid gap-8 px-6 py-7 sm:px-8 lg:grid-cols-[minmax(0,1.25fr)_auto] lg:items-end lg:px-10 lg:py-10">
|
||||
<div class="max-w-3xl">
|
||||
@if (!empty($homepageAnnouncement['badge_text']))
|
||||
<div class="inline-flex rounded-full border border-cyan-200/20 bg-cyan-300/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-cyan-100">
|
||||
{{ $homepageAnnouncement['badge_text'] }}
|
||||
</div>
|
||||
@endif
|
||||
@if (!empty($homepageAnnouncement['secondary_link']['url']) && !empty($homepageAnnouncement['secondary_link']['label']))
|
||||
<a href="{{ $homepageAnnouncement['secondary_link']['url'] }}" class="inline-flex items-center justify-center rounded-full border border-white/12 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white/85 transition hover:bg-white/[0.1]">
|
||||
{{ $homepageAnnouncement['secondary_link']['label'] }}
|
||||
</a>
|
||||
|
||||
<h2 class="mt-4 text-3xl font-semibold tracking-[-0.05em] sm:text-4xl lg:text-[3.15rem]">
|
||||
{{ $homepageAnnouncement['title'] ?? '' }}
|
||||
</h2>
|
||||
|
||||
@if (!empty($homepageAnnouncement['subtitle']))
|
||||
<p class="mt-3 max-w-2xl text-base leading-7 text-white/80 sm:text-lg">
|
||||
{{ $homepageAnnouncement['subtitle'] }}
|
||||
</p>
|
||||
@endif
|
||||
|
||||
@if (!empty($homepageAnnouncement['content_html']))
|
||||
<div class="prose prose-invert mt-5 max-w-2xl text-sm leading-7 prose-a:text-cyan-200 prose-strong:text-white [&_blockquote]:my-5 [&_h2]:mb-3 [&_h2]:mt-7 [&_h3]:mb-2 [&_h3]:mt-6 [&_li+li]:mt-1.5 [&_ol]:my-5 [&_p]:my-0 [&_p+p]:mt-6 [&_ul]:my-5 sm:text-base">
|
||||
{!! $homepageAnnouncement['content_html'] !!}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-start gap-3 lg:items-end">
|
||||
<div class="flex flex-wrap gap-3 lg:justify-end">
|
||||
@if (!empty($homepageAnnouncement['primary_link']['url']) && !empty($homepageAnnouncement['primary_link']['label']))
|
||||
<a href="{{ $homepageAnnouncement['primary_link']['url'] }}" class="inline-flex items-center justify-center rounded-full border border-cyan-300/30 bg-cyan-300/15 px-5 py-3 text-sm font-semibold text-cyan-50 transition hover:bg-cyan-300/22">
|
||||
{{ $homepageAnnouncement['primary_link']['label'] }}
|
||||
</a>
|
||||
@endif
|
||||
@if (!empty($homepageAnnouncement['secondary_link']['url']) && !empty($homepageAnnouncement['secondary_link']['label']))
|
||||
<a href="{{ $homepageAnnouncement['secondary_link']['url'] }}" class="inline-flex items-center justify-center rounded-full border border-white/12 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white/85 transition hover:bg-white/[0.1]">
|
||||
{{ $homepageAnnouncement['secondary_link']['label'] }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
112
resources/views/web/home/sections/artwork-card.blade.php
Normal file
112
resources/views/web/home/sections/artwork-card.blade.php
Normal file
@@ -0,0 +1,112 @@
|
||||
@php
|
||||
$artFallback = 'https://files.skinbase.org/default/missing_md.webp';
|
||||
$avatarFallback = 'https://files.skinbase.org/default/avatar_default.webp';
|
||||
$artwork = is_array($item ?? null) ? $item : [];
|
||||
$titleText = (string) ($artwork['title'] ?? $artwork['name'] ?? 'Untitled');
|
||||
$artworkUrl = (string) ($artwork['url'] ?? '#');
|
||||
$thumbUrl = (string) ($artwork['thumb'] ?? $artwork['thumb_url'] ?? $artFallback);
|
||||
$authorName = (string) ($artwork['author'] ?? 'Artist');
|
||||
$authorUsername = (string) ($artwork['author_username'] ?? $artwork['username'] ?? '');
|
||||
$authorAvatar = (string) ($artwork['author_avatar'] ?? $artwork['avatar_url'] ?? $avatarFallback);
|
||||
$authorUrl = !empty($artwork['publisher']['profile_url'] ?? null)
|
||||
? (string) $artwork['publisher']['profile_url']
|
||||
: ($authorUsername !== '' ? route('profile.show', ['username' => strtolower($authorUsername)]) : null);
|
||||
$metricBadge = is_array($artwork['metric_badge'] ?? null) ? $artwork['metric_badge'] : null;
|
||||
$maturity = is_array($artwork['maturity'] ?? null) ? $artwork['maturity'] : [];
|
||||
$shouldBlur = (bool) ($maturity['should_blur'] ?? false);
|
||||
$cardImageId = ($idPrefix ?? 'artwork') . '-image-' . ($index ?? 0);
|
||||
$medalScore = (int) data_get($artwork, 'medals.score_30d', data_get($artwork, 'medals.score', 0));
|
||||
@endphp
|
||||
|
||||
<article class="{{ ($layout ?? 'grid') === 'rail' ? 'min-w-[72%] snap-start sm:min-w-[44%] lg:min-w-0' : 'min-w-0' }}">
|
||||
<div class="group overflow-hidden rounded-2xl bg-black/20 shadow-lg shadow-black/40 ring-1 ring-white/5 transition-all duration-200 ease-out hover:-translate-y-0.5 focus-within:ring-2 focus-within:ring-sky-300/70">
|
||||
<a href="{{ $artworkUrl }}" class="relative block overflow-hidden">
|
||||
<div class="relative aspect-video overflow-hidden bg-neutral-900">
|
||||
<div class="pointer-events-none absolute inset-0 z-10 bg-gradient-to-br from-white/10 via-white/5 to-transparent"></div>
|
||||
<img
|
||||
id="{{ $cardImageId }}"
|
||||
src="{{ $thumbUrl }}"
|
||||
@if (!empty($artwork['thumb_srcset']))
|
||||
srcset="{{ $artwork['thumb_srcset'] }}"
|
||||
sizes="{{ $sizes ?? '100vw' }}"
|
||||
@endif
|
||||
alt="{{ $titleText }}"
|
||||
width="{{ max(1, (int) ($artwork['width'] ?? 1600)) }}"
|
||||
height="{{ max(1, (int) ($artwork['height'] ?? 900)) }}"
|
||||
class="h-full w-full object-cover transition-[transform,filter] duration-300 ease-out group-hover:scale-[1.04] {{ $shouldBlur ? 'blur-2xl scale-[1.03]' : '' }}"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
>
|
||||
|
||||
@if (!empty($badge))
|
||||
<div class="absolute left-3 top-3 z-30">
|
||||
<span class="inline-flex items-center rounded-md px-2 py-1 text-[11px] font-bold ring-1 ring-white/10 backdrop-blur-sm {{ $badgeClass ?? 'bg-sky-500/80 text-white' }}">
|
||||
{{ $badge }}
|
||||
</span>
|
||||
</div>
|
||||
@elseif ($metricBadge && !empty($metricBadge['label']))
|
||||
<div class="absolute left-3 top-3 z-30">
|
||||
<span class="inline-flex items-center rounded-full border border-sky-300/30 bg-sky-500/14 px-2.5 py-1 text-[11px] font-semibold text-sky-100 ring-1 ring-sky-300/20 backdrop-blur-sm">
|
||||
{{ $metricBadge['label'] }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($medalScore > 0)
|
||||
<div class="absolute right-3 top-3 z-30">
|
||||
<span class="inline-flex items-center rounded-full border border-amber-300/20 bg-amber-300/12 px-2.5 py-1 text-[11px] font-semibold text-amber-100 ring-1 ring-amber-300/20 backdrop-blur-sm">
|
||||
Medal {{ number_format($medalScore) }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($shouldBlur)
|
||||
<div class="absolute inset-0 z-20 flex items-center justify-center bg-slate-950/55 p-4" data-home-mature-overlay>
|
||||
<div class="rounded-2xl border border-white/10 bg-black/45 px-4 py-4 text-center shadow-2xl backdrop-blur-md">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-white/70">Mature content</p>
|
||||
<p class="mt-2 max-w-[16rem] text-sm text-white/90">This artwork may contain mature material.</p>
|
||||
<button
|
||||
type="button"
|
||||
data-home-mature-toggle="{{ $cardImageId }}"
|
||||
class="mt-4 inline-flex items-center rounded-full border border-white/15 bg-white/10 px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-white transition hover:bg-white/20"
|
||||
>
|
||||
Reveal image
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<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 p-3 backdrop-blur-[2px] opacity-100 transition-opacity duration-200 md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100">
|
||||
<div class="truncate text-sm font-semibold text-white">{{ $titleText }}</div>
|
||||
<div class="mt-1 flex items-center gap-2 text-xs text-white/80">
|
||||
<img src="{{ $authorAvatar }}" alt="{{ $authorName }}" class="h-6 w-6 shrink-0 rounded-full object-cover" loading="lazy" decoding="async">
|
||||
<span class="truncate">{{ $authorName }}</span>
|
||||
@if ($authorUsername !== '')
|
||||
<span class="shrink-0 text-white/50">@{{ $authorUsername }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="flex items-start justify-between gap-3 border-t border-white/5 bg-slate-950/40 px-3 py-3">
|
||||
<div class="min-w-0">
|
||||
<a href="{{ $artworkUrl }}" class="block truncate text-sm font-semibold text-white transition hover:text-sky-100">{{ $titleText }}</a>
|
||||
<div class="mt-1 flex items-center gap-2 text-xs text-soft">
|
||||
@if ($authorUrl)
|
||||
<a href="{{ $authorUrl }}" class="truncate text-nova-200 transition hover:text-white">{{ $authorName }}</a>
|
||||
@else
|
||||
<span class="truncate">{{ $authorName }}</span>
|
||||
@endif
|
||||
@if (!empty($artwork['category_name']))
|
||||
<span class="shrink-0 text-white/35">•</span>
|
||||
<span class="truncate">{{ $artwork['category_name'] }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ $artworkUrl }}" class="shrink-0 rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.14em] text-white/80 transition hover:border-white/20 hover:bg-white/[0.08] hover:text-white">
|
||||
View
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
59
resources/views/web/home/sections/artwork-section.blade.php
Normal file
59
resources/views/web/home/sections/artwork-section.blade.php
Normal file
@@ -0,0 +1,59 @@
|
||||
@php
|
||||
$sectionItems = collect(is_array($items ?? null) ? $items : [])
|
||||
->filter(fn ($item) => !data_get($item, 'maturity.should_hide', false))
|
||||
->values();
|
||||
$sectionLayout = $layout ?? 'grid';
|
||||
$sectionColumns = $columns ?? 'xl:grid-cols-4';
|
||||
$shouldRenderEmpty = !empty($emptyMessage);
|
||||
@endphp
|
||||
|
||||
@if ($sectionItems->isNotEmpty() || $shouldRenderEmpty)
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-5 flex flex-col gap-3 md:flex-row md:items-end md:justify-between">
|
||||
<div>
|
||||
@if (!empty($eyebrow))
|
||||
<p class="text-[0.7rem] font-semibold uppercase tracking-[0.28em] text-sky-200/70">{{ $eyebrow }}</p>
|
||||
@endif
|
||||
<h2 class="text-xl font-bold text-white">{{ $title }}</h2>
|
||||
@if (!empty($description))
|
||||
<p class="mt-1 max-w-2xl text-sm text-slate-300">{{ $description }}</p>
|
||||
@endif
|
||||
</div>
|
||||
@if (!empty($href))
|
||||
<a href="{{ $href }}" class="text-sm text-nova-300 transition hover:text-white">
|
||||
See all →
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($sectionItems->isNotEmpty())
|
||||
<div class="{{ $sectionLayout === 'rail' ? 'flex snap-x snap-mandatory gap-4 overflow-x-auto pb-3 ' . $sectionColumns . ' lg:grid lg:overflow-visible' : 'grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4 ' . $sectionColumns }}">
|
||||
@foreach ($sectionItems as $index => $item)
|
||||
@include('web.home.sections.artwork-card', [
|
||||
'item' => $item,
|
||||
'layout' => $sectionLayout,
|
||||
'badge' => $badge ?? null,
|
||||
'badgeClass' => $badge_class ?? null,
|
||||
'sizes' => $sectionLayout === 'rail'
|
||||
? '(max-width: 640px) 72vw, (max-width: 1024px) 44vw, 240px'
|
||||
: '(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw',
|
||||
'idPrefix' => Str::slug((string) $title, '-'),
|
||||
'index' => $index,
|
||||
])
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="rounded-2xl border border-white/5 bg-nova-800/40 px-6 py-10 text-center">
|
||||
<p class="text-sm text-soft">{{ $emptyMessage }}</p>
|
||||
@if (!empty($emptyDescription))
|
||||
<p class="mt-1 text-xs text-nova-400">{{ $emptyDescription }}</p>
|
||||
@endif
|
||||
@if (!empty($emptyCtaHref) && !empty($emptyCtaLabel))
|
||||
<a href="{{ $emptyCtaHref }}" class="mt-4 inline-flex items-center rounded-xl bg-nova-700 px-4 py-2 text-sm font-medium text-white transition hover:bg-nova-600">
|
||||
{{ $emptyCtaLabel }} →
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</section>
|
||||
@endif
|
||||
32
resources/views/web/home/sections/categories.blade.php
Normal file
32
resources/views/web/home/sections/categories.blade.php
Normal file
@@ -0,0 +1,32 @@
|
||||
@php
|
||||
$categories = [
|
||||
['label' => 'Wallpapers', 'description' => 'Desktop & mobile backgrounds', 'href' => '/wallpapers', 'mascot' => '/gfx/mascot_wallpapers.webp', 'color' => 'from-sky-500/20 to-sky-900/40'],
|
||||
['label' => 'Photography', 'description' => 'Real-world captures & edits', 'href' => '/photography', 'mascot' => '/gfx/mascot_photography.webp', 'color' => 'from-emerald-500/20 to-emerald-900/40'],
|
||||
['label' => 'Skins', 'description' => 'App & game skins', 'href' => '/skins', 'mascot' => '/gfx/mascot_skins.webp', 'color' => 'from-purple-500/20 to-purple-900/40'],
|
||||
['label' => 'Digital Art', 'description' => 'Illustrations & concept art', 'href' => '/digital-art', 'mascot' => '/gfx/mascot_other.webp', 'color' => 'from-rose-500/20 to-rose-900/40'],
|
||||
['label' => 'Tags Hub', 'description' => 'Browse by theme or style', 'href' => '/tags', 'mascot' => '/gfx/mascot_other.webp', 'color' => 'from-amber-500/20 to-amber-900/40'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-5 flex items-center justify-between">
|
||||
<h2 class="text-xl font-bold text-white">Explore Categories</h2>
|
||||
<a href="/browse" class="text-sm text-nova-300 transition hover:text-white">Browse all →</a>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-5">
|
||||
@foreach ($categories as $category)
|
||||
<a
|
||||
href="{{ $category['href'] }}"
|
||||
class="group relative flex min-h-[7rem] flex-col justify-end overflow-hidden rounded-2xl bg-gradient-to-br {{ $category['color'] }} ring-1 ring-white/5 transition hover:-translate-y-1 hover:ring-white/15 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/70"
|
||||
>
|
||||
<div class="pointer-events-none absolute inset-0 bg-nova-900/20 transition group-hover:bg-nova-900/10"></div>
|
||||
<img src="{{ $category['mascot'] }}" alt="" aria-hidden="true" class="pointer-events-none absolute bottom-0 right-0 h-24 w-auto translate-y-2 object-contain drop-shadow-xl transition-transform duration-300 group-hover:-translate-y-1 group-hover:scale-105" loading="lazy">
|
||||
<div class="relative z-10 p-3 pr-24">
|
||||
<p class="font-semibold leading-tight text-white">{{ $category['label'] }}</p>
|
||||
<p class="mt-0.5 text-xs text-nova-300">{{ $category['description'] }}</p>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
67
resources/views/web/home/sections/collections.blade.php
Normal file
67
resources/views/web/home/sections/collections.blade.php
Normal file
@@ -0,0 +1,67 @@
|
||||
@php
|
||||
$collectionItems = collect(is_array($collections ?? null) ? $collections : [])->filter()->take(3)->values();
|
||||
$coverFallback = 'https://files.skinbase.org/default/missing_md.webp';
|
||||
@endphp
|
||||
|
||||
@if ($collectionItems->isNotEmpty())
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-5 flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-white">Trending Collections</h2>
|
||||
<p class="mt-1 max-w-2xl text-sm text-nova-300">Collections getting the strongest mix of follows, saves, and engagement right now.</p>
|
||||
</div>
|
||||
<a href="/collections/trending" class="shrink-0 text-sm text-nova-300 transition hover:text-white">All collections →</a>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 lg:grid-cols-2 xl:grid-cols-3">
|
||||
@foreach ($collectionItems as $collection)
|
||||
<article class="group overflow-hidden rounded-[28px] border border-white/10 bg-[linear-gradient(180deg,rgba(12,19,30,0.96),rgba(5,10,18,0.96))] shadow-[0_22px_70px_rgba(2,6,23,0.28)] transition hover:border-white/15">
|
||||
<a href="{{ $collection['url'] ?? '#' }}" class="block">
|
||||
<div class="relative aspect-[16/10] overflow-hidden bg-slate-900">
|
||||
<img src="{{ $collection['cover_image'] ?? $coverFallback }}" alt="{{ $collection['title'] ?? 'Collection' }}" class="h-full w-full object-cover transition duration-500 group-hover:scale-[1.03]" loading="lazy" decoding="async">
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-slate-950 via-slate-950/30 to-transparent"></div>
|
||||
<div class="absolute left-4 top-4 flex flex-wrap gap-2">
|
||||
@if (!empty($collection['badge_label']))
|
||||
<span class="rounded-full border border-amber-300/20 bg-amber-300/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-amber-100">{{ $collection['badge_label'] }}</span>
|
||||
@endif
|
||||
@if (!empty($collection['type']))
|
||||
<span class="rounded-full border border-white/10 bg-white/[0.06] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.16em] text-white/75">{{ str_replace('_', ' ', $collection['type']) }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="px-5 py-5">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0">
|
||||
<a href="{{ $collection['url'] ?? '#' }}" class="block truncate text-lg font-semibold text-white transition hover:text-sky-100">{{ $collection['title'] ?? 'Collection' }}</a>
|
||||
@if (!empty($collection['subtitle']))
|
||||
<p class="mt-1 truncate text-sm text-slate-300">{{ $collection['subtitle'] }}</p>
|
||||
@endif
|
||||
</div>
|
||||
@if (!empty(data_get($collection, 'owner.username')))
|
||||
<span class="shrink-0 text-xs font-semibold uppercase tracking-[0.14em] text-sky-100/80">@{{ data_get($collection, 'owner.username') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if (!empty($collection['description_excerpt']))
|
||||
<p class="mt-3 text-sm leading-6 text-slate-300">{{ $collection['description_excerpt'] }}</p>
|
||||
@endif
|
||||
|
||||
<div class="mt-4 flex flex-wrap gap-3 text-xs text-slate-400">
|
||||
@if ((int) ($collection['artworks_count'] ?? 0) > 0)
|
||||
<span>{{ number_format((int) ($collection['artworks_count'] ?? 0)) }} artworks</span>
|
||||
@endif
|
||||
@if ((int) ($collection['followers_count'] ?? 0) > 0)
|
||||
<span>{{ number_format((int) ($collection['followers_count'] ?? 0)) }} followers</span>
|
||||
@endif
|
||||
@if ((int) ($collection['saves_count'] ?? 0) > 0)
|
||||
<span>{{ number_format((int) ($collection['saves_count'] ?? 0)) }} saves</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
42
resources/views/web/home/sections/creators.blade.php
Normal file
42
resources/views/web/home/sections/creators.blade.php
Normal file
@@ -0,0 +1,42 @@
|
||||
@php
|
||||
$creatorItems = collect(is_array($creators ?? null) ? $creators : [])->filter()->take(6)->values();
|
||||
$avatarFallback = 'https://files.skinbase.org/default/avatar_default.webp';
|
||||
@endphp
|
||||
|
||||
@if ($creatorItems->isNotEmpty())
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-5 flex items-center justify-between gap-4">
|
||||
<h2 class="text-xl font-bold text-white">Creator Spotlight</h2>
|
||||
<a href="/members" class="text-sm text-nova-300 transition hover:text-white">All creators</a>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6">
|
||||
@foreach ($creatorItems as $creator)
|
||||
<a
|
||||
href="{{ $creator['url'] ?? '#' }}"
|
||||
aria-label="View {{ $creator['name'] ?? 'Creator' }} profile"
|
||||
class="group relative flex min-h-[16rem] flex-col items-center overflow-hidden rounded-xl bg-panel p-5 text-center shadow-sm transition hover:ring-1 hover:ring-nova-500"
|
||||
@if (!empty($creator['bg_thumb']))
|
||||
style="background-image: linear-gradient(to top, rgba(13, 19, 28, 0.96), rgba(13, 19, 28, 0.7)), url('{{ $creator['bg_thumb'] }}'); background-size: cover; background-position: center;"
|
||||
@endif
|
||||
>
|
||||
<img src="{{ $creator['avatar'] ?? $avatarFallback }}" alt="{{ $creator['name'] ?? 'Creator' }}" class="relative mx-auto h-16 w-16 rounded-full bg-nova-800/80 object-cover ring-4 ring-nova-800" loading="lazy" decoding="async">
|
||||
<h3 class="relative mt-2 text-sm font-semibold text-white">{{ $creator['name'] ?? 'Creator' }}</h3>
|
||||
<p class="relative mt-1 flex flex-wrap justify-center gap-x-3 gap-y-1 text-xs text-soft">
|
||||
<span>Uploads {{ number_format((int) ($creator['uploads'] ?? 0)) }}</span>
|
||||
@if ((int) ($creator['weekly_uploads'] ?? 0) > 0)
|
||||
<span class="font-semibold text-accent">{{ number_format((int) ($creator['weekly_uploads'] ?? 0)) }} this week</span>
|
||||
@endif
|
||||
<span>Views {{ number_format((int) ($creator['views'] ?? 0)) }}</span>
|
||||
@if ((int) ($creator['awards'] ?? 0) > 0)
|
||||
<span>Awards {{ number_format((int) ($creator['awards'] ?? 0)) }}</span>
|
||||
@endif
|
||||
</p>
|
||||
<span class="relative mt-3 inline-flex items-center justify-center rounded-lg bg-nova-700 px-4 py-1.5 text-xs font-semibold text-white transition group-hover:bg-nova-600">
|
||||
View profile
|
||||
</span>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
28
resources/views/web/home/sections/cta.blade.php
Normal file
28
resources/views/web/home/sections/cta.blade.php
Normal file
@@ -0,0 +1,28 @@
|
||||
@php
|
||||
$uploadHref = !empty($isLoggedIn) ? '/upload' : '/login?redirect=/upload';
|
||||
@endphp
|
||||
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div class="relative overflow-hidden rounded-2xl bg-gradient-to-br from-accent/20 via-nova-800 to-nova-900 px-8 py-12 text-center ring-1 ring-white/5">
|
||||
<div class="pointer-events-none absolute -right-12 -top-12 h-40 w-40 rounded-full bg-accent/10 blur-3xl"></div>
|
||||
<div class="pointer-events-none absolute -bottom-10 -left-10 h-32 w-32 rounded-full bg-sky-500/10 blur-2xl"></div>
|
||||
|
||||
<div class="relative z-10">
|
||||
<p class="text-xs font-semibold uppercase tracking-widest text-accent">Join the community</p>
|
||||
<h2 class="mt-2 text-2xl font-bold text-white sm:text-3xl">Ready to share your creativity?</h2>
|
||||
<p class="mx-auto mt-3 max-w-md text-sm text-nova-300">
|
||||
Upload your artworks, wallpapers, and skins to reach thousands of enthusiasts around the world.
|
||||
</p>
|
||||
<div class="mt-6 flex flex-wrap justify-center gap-3">
|
||||
<a href="{{ $uploadHref }}" class="btn-accent-solid rounded-xl px-6 py-2.5 text-sm font-semibold">
|
||||
Upload your artwork
|
||||
</a>
|
||||
@if (empty($isLoggedIn))
|
||||
<a href="/register" class="rounded-xl border border-white/10 bg-nova-700 px-6 py-2.5 text-sm font-semibold text-white transition hover:bg-nova-600">
|
||||
Create account
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
74
resources/views/web/home/sections/groups.blade.php
Normal file
74
resources/views/web/home/sections/groups.blade.php
Normal file
@@ -0,0 +1,74 @@
|
||||
@php
|
||||
$groupPool = collect([
|
||||
data_get($groups, 'spotlight'),
|
||||
...array_values(is_array(data_get($groups, 'featured')) ? data_get($groups, 'featured') : []),
|
||||
...array_values(is_array(data_get($groups, 'recruiting')) ? data_get($groups, 'recruiting') : []),
|
||||
...array_values(is_array(data_get($groups, 'rising')) ? data_get($groups, 'rising') : []),
|
||||
])->filter();
|
||||
|
||||
$groupItems = $groupPool->unique(fn ($group) => (int) ($group['id'] ?? 0))->take(4)->values();
|
||||
@endphp
|
||||
|
||||
@if ($groupItems->isNotEmpty())
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-5 flex items-center justify-between">
|
||||
<h2 class="text-xl font-bold text-white">Group Spotlight</h2>
|
||||
<a href="/groups" class="text-sm text-nova-300 transition hover:text-white">All groups →</a>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
@foreach ($groupItems as $group)
|
||||
@php
|
||||
$stats = collect([
|
||||
['key' => 'artworks', 'label' => 'artworks', 'value' => (int) data_get($group, 'counts.artworks', 0)],
|
||||
['key' => 'members', 'label' => 'members', 'value' => (int) data_get($group, 'counts.members', 0)],
|
||||
['key' => 'followers', 'label' => 'followers', 'value' => (int) data_get($group, 'counts.followers', 0)],
|
||||
])->filter(fn ($item) => $item['value'] > 0)->values();
|
||||
@endphp
|
||||
<article class="group relative flex flex-col overflow-hidden rounded-xl bg-panel p-5 shadow-sm transition hover:ring-1 hover:ring-nova-500">
|
||||
@if (!empty($group['banner_url']))
|
||||
<img src="{{ $group['banner_url'] }}" alt="" aria-hidden="true" class="pointer-events-none absolute inset-0 h-full w-full object-cover opacity-40 transition duration-500 group-hover:scale-105 group-hover:opacity-20" loading="lazy" decoding="async">
|
||||
<div class="pointer-events-none absolute inset-0 bg-gradient-to-t from-panel via-panel/85 to-panel/70"></div>
|
||||
@endif
|
||||
|
||||
<a href="{{ data_get($group, 'urls.public', '/groups') }}" class="relative block">
|
||||
<div class="flex h-16 w-16 items-center justify-center overflow-hidden rounded-2xl bg-nova-800/80 ring-4 ring-nova-800">
|
||||
@if (!empty($group['avatar_url']))
|
||||
<img src="{{ $group['avatar_url'] }}" alt="" class="h-full w-full object-cover" loading="lazy" decoding="async">
|
||||
@else
|
||||
<span class="text-2xl text-white">G</span>
|
||||
@endif
|
||||
</div>
|
||||
<h3 class="mt-3 text-base font-semibold text-white">{{ $group['name'] ?? 'Group' }}</h3>
|
||||
</a>
|
||||
|
||||
<p class="relative mt-2 line-clamp-3 text-sm text-soft">{{ $group['headline'] ?? $group['bio_excerpt'] ?? 'Shared publishing identity for collaborative releases and artwork.' }}</p>
|
||||
|
||||
<div class="relative mt-3 flex flex-wrap gap-2 text-xs text-soft">
|
||||
@if (!empty($group['is_recruiting']))
|
||||
<span class="rounded-full bg-emerald-400/15 px-2.5 py-1 font-semibold text-emerald-200">Recruiting</span>
|
||||
@endif
|
||||
@if (!empty($group['is_verified']))
|
||||
<span class="rounded-full bg-sky-400/15 px-2.5 py-1 font-semibold text-sky-200">Verified</span>
|
||||
@endif
|
||||
@if (!empty(data_get($group, 'owner.username')) || !empty(data_get($group, 'owner.name')))
|
||||
<span>Led by {{ data_get($group, 'owner.username') ?: data_get($group, 'owner.name') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($stats->isNotEmpty())
|
||||
<div class="relative mt-4 flex flex-wrap gap-3 text-xs text-soft">
|
||||
@foreach ($stats as $stat)
|
||||
<span>{{ number_format($stat['value']) }} {{ $stat['label'] }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<a href="{{ data_get($group, 'urls.public', '/groups') }}" class="relative mt-4 inline-flex w-fit rounded-lg bg-nova-700 px-4 py-1.5 text-xs font-semibold text-white transition hover:bg-nova-600">
|
||||
View Group
|
||||
</a>
|
||||
</article>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
31
resources/views/web/home/sections/news.blade.php
Normal file
31
resources/views/web/home/sections/news.blade.php
Normal file
@@ -0,0 +1,31 @@
|
||||
@php
|
||||
$newsItems = collect(is_array($items ?? null) ? $items : [])->filter()->take(6)->values();
|
||||
@endphp
|
||||
|
||||
@if ($newsItems->isNotEmpty())
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-5 flex items-center justify-between gap-4">
|
||||
<h2 class="text-xl font-bold text-white">News & Updates</h2>
|
||||
<a href="/news" class="text-sm text-nova-300 transition hover:text-white">All news</a>
|
||||
</div>
|
||||
|
||||
<div class="divide-y divide-nova-800 overflow-hidden rounded-[24px] border border-white/10 bg-panel">
|
||||
@foreach ($newsItems as $item)
|
||||
<a href="{{ $item['url'] ?? '#' }}" class="grid gap-3 px-5 py-4 transition hover:bg-nova-800 sm:grid-cols-[minmax(0,1fr)_auto] sm:items-start">
|
||||
<div class="min-w-0">
|
||||
@if (!empty($item['eyebrow']))
|
||||
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-nova-300">{{ $item['eyebrow'] }}</div>
|
||||
@endif
|
||||
<div class="mt-1 line-clamp-2 text-sm font-medium text-white">{{ $item['title'] ?? 'News item' }}</div>
|
||||
@if (!empty($item['excerpt']))
|
||||
<p class="mt-2 line-clamp-2 text-sm leading-6 text-soft">{{ $item['excerpt'] }}</p>
|
||||
@endif
|
||||
</div>
|
||||
@if (!empty($item['date']))
|
||||
<span class="shrink-0 text-xs text-soft">{{ $item['date'] }}</span>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
@@ -0,0 +1,43 @@
|
||||
@php
|
||||
$creatorItems = collect(is_array($creators ?? null) ? $creators : [])->filter()->take(4)->values();
|
||||
$avatarFallback = 'https://files.skinbase.org/default/avatar_default.webp';
|
||||
@endphp
|
||||
|
||||
@if ($creatorItems->isNotEmpty())
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div class="mb-5 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-white">Suggested Creators</h2>
|
||||
<p class="mt-0.5 text-xs text-nova-400">Creators you might enjoy following</p>
|
||||
</div>
|
||||
<a href="/creators/top" class="text-sm text-nova-300 transition hover:text-white">Explore all →</a>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4">
|
||||
@foreach ($creatorItems as $creator)
|
||||
<article class="group flex flex-col items-center rounded-2xl bg-nova-800/60 p-5 ring-1 ring-white/5 transition hover:bg-nova-800 hover:ring-white/10">
|
||||
<a href="{{ $creator['url'] ?? '#' }}" class="block">
|
||||
<img src="{{ $creator['avatar'] ?? $avatarFallback }}" alt="{{ $creator['name'] ?? 'Creator' }}" class="mx-auto h-14 w-14 rounded-full object-cover ring-2 ring-white/10 transition group-hover:ring-accent/50" loading="lazy" decoding="async">
|
||||
</a>
|
||||
<div class="mt-3 w-full text-center">
|
||||
<a href="{{ $creator['url'] ?? '#' }}" class="block truncate text-sm font-semibold text-white transition hover:text-accent">{{ $creator['name'] ?? 'Creator' }}</a>
|
||||
@if (!empty($creator['username']))
|
||||
<p class="truncate text-xs text-nova-400">@{{ $creator['username'] }}</p>
|
||||
@endif
|
||||
<div class="mt-2 flex items-center justify-center gap-3 text-xs text-nova-500">
|
||||
@if ((int) ($creator['followers_count'] ?? 0) > 0)
|
||||
<span>{{ number_format((int) ($creator['followers_count'] ?? 0)) }} followers</span>
|
||||
@endif
|
||||
@if ((int) ($creator['artworks_count'] ?? 0) > 0)
|
||||
<span>{{ number_format((int) ($creator['artworks_count'] ?? 0)) }} artworks</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ $creator['url'] ?? '#' }}" class="mt-4 w-full rounded-lg bg-nova-700 py-1.5 text-center text-xs font-medium text-white transition hover:bg-nova-600">
|
||||
View Profile
|
||||
</a>
|
||||
</article>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
20
resources/views/web/home/sections/tags.blade.php
Normal file
20
resources/views/web/home/sections/tags.blade.php
Normal file
@@ -0,0 +1,20 @@
|
||||
@php
|
||||
$tagItems = collect(is_array($tags ?? null) ? $tags : [])->filter()->take(16)->values();
|
||||
@endphp
|
||||
|
||||
@if ($tagItems->isNotEmpty())
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<h2 class="mb-5 text-xl font-bold text-white">Popular Tags</h2>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@foreach ($tagItems as $tag)
|
||||
<a href="/tag/{{ $tag['slug'] ?? '' }}" class="rounded-full bg-nova-800 px-4 py-1.5 text-sm font-medium text-nova-200 transition hover:bg-nova-700 hover:text-white">
|
||||
{{ $tag['name'] ?? 'Tag' }}
|
||||
@if ((int) ($tag['count'] ?? 0) > 0)
|
||||
<span class="ml-1.5 text-xs text-soft">{{ number_format((int) ($tag['count'] ?? 0)) }}</span>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
47
resources/views/web/home/sections/welcome-row.blade.php
Normal file
47
resources/views/web/home/sections/welcome-row.blade.php
Normal file
@@ -0,0 +1,47 @@
|
||||
@php
|
||||
$userData = is_array($userData ?? null) ? $userData : null;
|
||||
$avatarFallback = 'https://files.skinbase.org/default/avatar_default.webp';
|
||||
$firstName = trim((string) Str::of((string) ($userData['name'] ?? 'there'))->before(' '));
|
||||
@endphp
|
||||
|
||||
@if ($userData)
|
||||
<section class="border-b border-white/5 bg-nova-900/60 backdrop-blur-sm">
|
||||
<div class="mx-auto max-w-7xl px-4 py-4 sm:px-6 lg:px-8">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="{{ $userData['url'] ?? '/profile' }}">
|
||||
<img
|
||||
src="{{ $userData['avatar'] ?? $avatarFallback }}"
|
||||
alt="{{ $userData['name'] ?? 'Member' }}"
|
||||
class="h-9 w-9 rounded-full object-cover ring-2 ring-white/10 transition hover:ring-accent/60"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
>
|
||||
</a>
|
||||
<div>
|
||||
<p class="text-sm text-soft">Welcome back,</p>
|
||||
<p class="text-sm font-semibold leading-tight text-white">{{ $firstName !== '' ? $firstName : ($userData['name'] ?? 'there') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
@if ((int) ($userData['messages_unread'] ?? 0) > 0)
|
||||
<a href="/messages" class="inline-flex items-center gap-1.5 rounded-lg bg-nova-800 px-3 py-1.5 text-xs font-medium text-white ring-1 ring-white/10 transition hover:bg-nova-700">
|
||||
<span>{{ (int) $userData['messages_unread'] }} new</span>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
@if ((int) ($userData['notifications_unread'] ?? 0) > 0)
|
||||
<a href="/dashboard/notifications" class="inline-flex items-center gap-1.5 rounded-lg bg-nova-800 px-3 py-1.5 text-xs font-medium text-white ring-1 ring-white/10 transition hover:bg-nova-700">
|
||||
<span>{{ (int) $userData['notifications_unread'] }}</span>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
<a href="/upload" class="btn-accent-solid inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-xs font-semibold">
|
||||
Upload
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
76
resources/views/web/home/sections/world-spotlight.blade.php
Normal file
76
resources/views/web/home/sections/world-spotlight.blade.php
Normal file
@@ -0,0 +1,76 @@
|
||||
@php
|
||||
$primaryWorld = is_array(data_get($world, 'primary')) ? data_get($world, 'primary') : (is_array($world ?? null) ? $world : null);
|
||||
$secondaryWorlds = collect(is_array(data_get($world, 'secondary')) ? data_get($world, 'secondary') : [])->take(3)->values();
|
||||
@endphp
|
||||
|
||||
@if ($primaryWorld)
|
||||
<section class="mt-14 px-4 sm:px-6 lg:px-8">
|
||||
<div class="overflow-hidden rounded-[28px] border border-white/10 bg-[linear-gradient(145deg,rgba(4,10,20,0.98),rgba(10,17,34,0.94))] shadow-[0_32px_110px_rgba(2,6,23,0.45)]">
|
||||
<div class="grid gap-0 lg:grid-cols-[minmax(0,1.2fr)_26rem]">
|
||||
<div class="relative min-h-[22rem] overflow-hidden">
|
||||
@if (!empty($primaryWorld['cover_url']))
|
||||
<img src="{{ $primaryWorld['cover_url'] }}" alt="{{ $primaryWorld['headline'] ?? $primaryWorld['title'] ?? 'World spotlight' }}" class="absolute inset-0 h-full w-full object-cover" loading="lazy" decoding="async">
|
||||
@endif
|
||||
<div class="absolute inset-0 bg-[linear-gradient(90deg,rgba(2,6,23,0.94),rgba(2,6,23,0.65),rgba(2,6,23,0.5))]"></div>
|
||||
<div class="relative z-10 flex h-full flex-col justify-end px-6 py-7 sm:px-8 lg:px-10">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="inline-flex rounded-full border border-sky-300/20 bg-sky-300/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-100">Homepage spotlight</span>
|
||||
@if (!empty($primaryWorld['campaign_label']))
|
||||
<span class="inline-flex rounded-full border border-white/10 bg-white/[0.06] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-white/75">{{ $primaryWorld['campaign_label'] }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<h2 class="mt-4 max-w-2xl text-3xl font-semibold tracking-[-0.05em] text-white sm:text-4xl">{{ $primaryWorld['headline'] ?? $primaryWorld['title'] ?? 'World spotlight' }}</h2>
|
||||
@if (!empty($primaryWorld['summary']))
|
||||
<p class="mt-3 max-w-2xl text-sm leading-7 text-slate-200/85 sm:text-base">{{ $primaryWorld['summary'] }}</p>
|
||||
@endif
|
||||
<div class="mt-5 flex flex-wrap gap-3 text-xs text-slate-300">
|
||||
@if (!empty($primaryWorld['timeframe_label']))
|
||||
<span>{{ $primaryWorld['timeframe_label'] }}</span>
|
||||
@endif
|
||||
@if ((int) ($primaryWorld['live_submission_count'] ?? 0) > 0)
|
||||
<span>{{ number_format((int) ($primaryWorld['live_submission_count'] ?? 0)) }} live submissions</span>
|
||||
@endif
|
||||
@if ((int) ($primaryWorld['featured_submission_count'] ?? 0) > 0)
|
||||
<span>{{ number_format((int) ($primaryWorld['featured_submission_count'] ?? 0)) }} featured</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="mt-6 flex flex-wrap gap-3">
|
||||
<a href="{{ $primaryWorld['cta_url'] ?? $primaryWorld['public_url'] ?? '#' }}" class="inline-flex items-center justify-center rounded-full border border-sky-300/30 bg-sky-300/15 px-5 py-3 text-sm font-semibold text-sky-50 transition hover:bg-sky-300/22">
|
||||
{{ $primaryWorld['cta_label'] ?? 'Explore world' }}
|
||||
</a>
|
||||
<a href="{{ data_get($world, 'index_url', '/worlds') }}" class="inline-flex items-center justify-center rounded-full border border-white/12 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white/85 transition hover:bg-white/[0.1]">
|
||||
More worlds
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-white/10 bg-black/20 lg:border-l lg:border-t-0">
|
||||
<div class="px-6 py-6 sm:px-8">
|
||||
<p class="text-[11px] font-semibold uppercase tracking-[0.2em] text-white/45">More live worlds</p>
|
||||
<div class="mt-5 space-y-4">
|
||||
@foreach ($secondaryWorlds as $secondary)
|
||||
<a href="{{ $secondary['public_url'] ?? '#' }}" class="group block overflow-hidden rounded-2xl border border-white/8 bg-white/[0.03] p-4 transition hover:border-white/15 hover:bg-white/[0.05]">
|
||||
<div class="flex items-start gap-4">
|
||||
@if (!empty($secondary['cover_url']))
|
||||
<img src="{{ $secondary['cover_url'] }}" alt="{{ $secondary['title'] ?? 'World' }}" class="h-20 w-24 rounded-xl object-cover" loading="lazy" decoding="async">
|
||||
@endif
|
||||
<div class="min-w-0">
|
||||
<div class="truncate text-sm font-semibold text-white">{{ $secondary['teaser_title'] ?? $secondary['title'] ?? 'World' }}</div>
|
||||
@if (!empty($secondary['summary']))
|
||||
<p class="mt-1 line-clamp-2 text-xs leading-5 text-slate-300">{{ $secondary['summary'] }}</p>
|
||||
@endif
|
||||
@if (!empty($secondary['timeframe_label']))
|
||||
<div class="mt-2 text-[11px] uppercase tracking-[0.16em] text-sky-200/70">{{ $secondary['timeframe_label'] }}</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
Reference in New Issue
Block a user