Wire admin studio SSR and search infrastructure

This commit is contained in:
2026-05-01 11:46:06 +02:00
parent 257b0dbef6
commit 18cea8b0f0
329 changed files with 197465 additions and 2741 deletions

View File

@@ -1,6 +1,6 @@
@extends('layouts.nova')
@php($useUnifiedSeo = true)
<?php $useUnifiedSeo = true; ?>
@push('head')
{{-- Preload hero image for faster LCP --}}
@@ -20,6 +20,49 @@
@section('main-class', '')
@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'] : [],
'badge' => 'Rising',
'badge_class' => 'bg-emerald-500/80 text-white',
],
[
'title' => 'Trending Now',
'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',
],
[
'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',
],
[
'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',
],
];
$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) --}}
@@ -28,16 +71,236 @@
</script>
<div id="homepage-root">
@if(!empty($props['is_logged_in']))
@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'],
])
@else
@include('web.home.skeleton-sections', [
'showWelcomeSpacer' => false,
'variants' => ['gallery', 'gallery', 'gallery', 'gallery', 'gallery', 'collections', 'groups', 'categories', 'tags', 'creators', 'news', 'cta'],
])
<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>
@endif
</div>