Save workspace changes

This commit is contained in:
2026-04-18 17:02:56 +02:00
parent f02ea9a711
commit 87d60af5a9
4220 changed files with 1388603 additions and 1554 deletions

View File

@@ -0,0 +1,46 @@
{{-- Featured row use Nova cards for consistent layout with browse/gallery --}}
<section class="px-6 pt-8 pb-6 md:px-10">
<div class="flex items-center gap-2 mb-6">
<span class="inline-flex items-center justify-center w-7 h-7 rounded-md bg-amber-500/15 text-amber-400">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"/></svg>
</span>
<h2 class="text-base font-semibold text-white/90 tracking-wide uppercase">Featured</h2>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
@if(!empty($featured))
<div>
@include('web.partials._artwork_card', ['art' => $featured])
</div>
@else
<div class="rounded-2xl ring-1 ring-white/5 bg-white/[0.03] p-4">
<p class="text-sm text-neutral-400">No featured artwork set.</p>
</div>
@endif
@if(!empty($memberFeatured))
<div>
@include('web.partials._artwork_card', ['art' => $memberFeatured])
</div>
@else
<div class="rounded-2xl ring-1 ring-white/5 bg-white/[0.03] p-4">
<p class="text-sm text-neutral-400">No member featured artwork.</p>
</div>
@endif
<div>
<div class="group relative flex flex-col overflow-hidden rounded-2xl ring-1 ring-white/5 bg-white/[0.03] shadow-lg h-full">
<a href="{{ route('register') }}" title="Join Skinbase" class="block shrink-0">
<img src="/gfx/sb_join.jpg" alt="Join SkinBase Community" class="w-full h-48 object-cover">
</a>
<div class="flex flex-col flex-1 p-5 text-center">
<div class="text-lg font-semibold text-white/90">Join Skinbase World</div>
<p class="mt-2 text-sm text-neutral-400 flex-1">Join our community upload, share and explore curated photography and skins.</p>
<a href="{{ route('register') }}" class="mt-4 inline-block px-4 py-2 rounded-lg bg-sky-500 hover:bg-sky-400 transition-colors text-white text-sm font-medium">Create an account</a>
</div>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,69 @@
@php
$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)
<section class="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]">
<div class="pointer-events-none absolute inset-0 bg-gradient-to-t from-nova-900 via-nova-900/60 to-transparent"></div>
<div class="relative z-10 w-full px-6 pb-7 sm:px-10 lg:px-16">
<h1 class="text-2xl font-bold tracking-tight text-white sm:text-4xl">
Skinbase Nova
</h1>
<p class="mt-2 max-w-xl text-sm text-soft">
Discover. Create. Inspire.
</p>
<div class="mt-4 flex flex-wrap gap-3">
<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]">
<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>
<div class="relative z-10 w-full px-6 pb-7 sm:px-10 lg:px-16">
<p class="mb-1.5 text-xs font-semibold uppercase tracking-widest text-accent">
Featured Artwork
</p>
<h1 class="text-2xl font-bold tracking-tight text-white drop-shadow sm:text-4xl lg:text-5xl">
{{ $heroArtwork['title'] ?? 'Untitled' }}
</h1>
<p class="mt-1.5 text-sm text-soft">
by
<a href="{{ $heroArtwork['url'] ?? '#' }}" class="text-nova-200 transition hover:text-white">
{{ $heroArtwork['author'] ?? 'Artist' }}
</a>
</p>
<div class="mt-4 flex flex-wrap gap-3">
<a
href="/discover/trending"
class="btn-accent-solid rounded-xl px-5 py-2 text-sm font-semibold"
>
Explore Trending
</a>
<a
href="{{ $heroArtwork['url'] ?? '#' }}"
class="rounded-xl border border-nova-600 px-5 py-2 text-sm font-semibold text-nova-200 shadow transition hover:border-nova-400 hover:text-white"
>
View Artwork
</a>
</div>
</div>
</section>
@endif

View File

@@ -0,0 +1,142 @@
{{-- News and forum columns --}}
@php
use Carbon\Carbon;
use Illuminate\Support\Str;
@endphp
<section class="px-6 pb-14 pt-2 md:px-10">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
{{-- ── LEFT: Forum News ── --}}
<div class="space-y-1">
<div class="flex items-center gap-2 mb-5">
<span class="inline-flex items-center justify-center w-7 h-7 rounded-md bg-sky-500/15 text-sky-400">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M7 8h10M7 12h6m-6 4h4M5 20H4a2 2 0 01-2-2V6a2 2 0 012-2h16a2 2 0 012 2v12a2 2 0 01-2 2h-1l-4 4-4-4z"/></svg>
</span>
<h2 class="text-base font-semibold text-white/90 tracking-wide uppercase">Forum News</h2>
</div>
@forelse ($forumNews as $item)
<article class="group rounded-xl bg-white/[0.03] border border-white/[0.06] hover:border-sky-500/30 hover:bg-white/[0.05] transition-all duration-200 p-4">
<a href="{{ route('forum.thread.show', ['thread' => $item->topic_id, 'slug' => Str::slug($item->topic ?? '')]) }}"
class="block text-sm font-semibold text-white/90 group-hover:text-sky-300 transition-colors leading-snug mb-1 line-clamp-2">
{{ $item->topic }}
</a>
<div class="flex items-center gap-3 text-xs text-white/40 mb-2">
<span class="inline-flex items-center gap-1">
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
{{ $item->uname }}
</span>
<span class="inline-flex items-center gap-1">
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
{{ Carbon::parse($item->post_date)->format('j M Y') }}
</span>
</div>
@if (!empty($item->preview))
<p class="text-xs text-white/50 leading-relaxed line-clamp-3">{{ Str::limit(strip_tags($item->preview), 200) }}</p>
@endif
</article>
@empty
<div class="rounded-xl bg-white/[0.03] border border-white/[0.06] p-6 text-sm text-white/40 text-center">
No forum news available.
</div>
@endforelse
</div>
{{-- ── RIGHT: Site News + Info + Forum Activity ── --}}
<div class="space-y-8">
{{-- Site News --}}
<div>
<div class="flex items-center gap-2 mb-5">
<span class="inline-flex items-center justify-center w-7 h-7 rounded-md bg-violet-500/15 text-violet-400">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10l6 6v8a2 2 0 01-2 2z"/><path stroke-linecap="round" stroke-linejoin="round" d="M13 4v6h6"/></svg>
</span>
<h2 class="text-base font-semibold text-white/90 tracking-wide uppercase">Site News</h2>
</div>
@forelse ($ourNews as $news)
@php $nid = floor($news->news_id / 100); @endphp
<article class="group rounded-xl bg-white/[0.03] border border-white/[0.06] hover:border-violet-500/30 hover:bg-white/[0.05] transition-all duration-200 p-4 mb-3 last:mb-0">
<a href="/news/{{ $news->news_id }}/{{ Str::slug($news->headline ?? '') }}"
class="block text-sm font-semibold text-white/90 group-hover:text-violet-300 transition-colors leading-snug mb-1 line-clamp-2">
{{ $news->headline }}
</a>
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-white/40 mb-3">
<span class="inline-flex items-center gap-1">
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
{{ $news->uname }}
</span>
<span class="inline-flex items-center gap-1">
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
{{ Carbon::parse($news->create_date)->format('j M Y') }}
</span>
<span class="inline-flex items-center gap-1">
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/></svg>
{{ number_format($news->views) }}
</span>
<span class="inline-flex items-center gap-1">
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>
{{ $news->num_comments }}
</span>
</div>
@if (!empty($news->picture))
<div class="flex gap-3">
<img src="/archive/news/{{ $nid }}/{{ $news->picture }}"
class="w-20 h-14 object-cover rounded-lg flex-shrink-0 ring-1 ring-white/10"
alt="{{ e($news->headline) }}" loading="lazy">
<p class="text-xs text-white/50 leading-relaxed line-clamp-3">{!! Str::limit(strip_tags($news->preview ?? ''), 180) !!}</p>
</div>
@else
<p class="text-xs text-white/50 leading-relaxed line-clamp-3">{!! Str::limit(strip_tags($news->preview ?? ''), 240) !!}</p>
@endif
</article>
@empty
<div class="rounded-xl bg-white/[0.03] border border-white/[0.06] p-6 text-sm text-white/40 text-center">
No news available.
</div>
@endforelse
</div>
{{-- About Skinbase --}}
<div class="rounded-xl bg-gradient-to-br from-sky-500/10 to-violet-500/10 border border-white/[0.07] p-5">
<div class="flex items-center gap-2 mb-3">
<span class="inline-flex items-center justify-center w-6 h-6 rounded-md bg-sky-500/20 text-sky-400">
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
</span>
<h3 class="text-sm font-semibold text-white/80">About Skinbase</h3>
</div>
<p class="text-xs text-white/55 leading-relaxed">
Skinbase is dedicated to <span class="text-white/80 font-medium">Photography</span>, <span class="text-white/80 font-medium">Wallpapers</span> and <span class="text-white/80 font-medium">Skins</span> for popular applications on Windows, macOS, Linux, iOS and Android.
Browse categories, discover curated artwork, and download everything for free.
</p>
</div>
{{-- Latest Forum Activity --}}
<div>
<div class="flex items-center gap-2 mb-4">
<span class="inline-flex items-center justify-center w-7 h-7 rounded-md bg-emerald-500/15 text-emerald-400">
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M17 8h2a2 2 0 012 2v6a2 2 0 01-2 2h-2v4l-4-4H9a1.994 1.994 0 01-1.414-.586m0 0L11 14h4a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2v4l.586-.586z"/></svg>
</span>
<h2 class="text-base font-semibold text-white/90 tracking-wide uppercase">Forum Activity</h2>
</div>
<div class="rounded-xl border border-white/[0.06] overflow-hidden divide-y divide-white/[0.05]">
@forelse ($latestForumActivity as $topic)
<a href="{{ route('forum.thread.show', ['thread' => $topic->topic_id, 'slug' => Str::slug($topic->topic ?? '')]) }}"
class="flex items-center justify-between gap-3 px-4 py-3 text-sm text-white/70 hover:bg-white/[0.04] hover:text-white transition-colors group">
<span class="truncate group-hover:text-emerald-300 transition-colors">{{ $topic->topic }}</span>
<span class="flex-shrink-0 inline-flex items-center gap-1 text-xs text-white/35 group-hover:text-white/60 transition-colors">
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>
{{ $topic->numPosts }}
</span>
</a>
@empty
<div class="px-4 py-5 text-sm text-white/40 text-center">No recent forum activity.</div>
@endforelse
</div>
</div>
</div>{{-- end right column --}}
</div>
</section>

View 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

View File

@@ -0,0 +1,84 @@
@php
$gridV2 = request()->query('grid') === 'v2';
$seoPage = (int) request()->query('page', 1);
$seoBase = url()->current();
$seoCanonical = $seoPage > 1 ? $seoBase . '?page=' . $seoPage : $seoBase;
$seoPrev = $seoPage > 1
? ($seoPage === 2 ? $seoBase : $seoBase . '?page=' . ($seoPage - 1))
: null;
$seoNext = (isset($latestUploads) && method_exists($latestUploads, 'hasMorePages') && $latestUploads->hasMorePages())
? $seoBase . '?page=' . ($seoPage + 1)
: null;
$homeUploadsItems = collect(method_exists($latestUploads ?? null, 'items') ? $latestUploads->items() : ($latestUploads ?? []));
$homeGalleryArtworks = $homeUploadsItems->map(fn ($upload) => [
'id' => $upload->id ?? null,
'name' => $upload->name ?? $upload->title ?? null,
'slug' => $upload->slug ?? \Illuminate\Support\Str::slug($upload->name ?? $upload->title ?? 'artwork'),
'url' => $upload->url ?? ((isset($upload->id) && $upload->id) ? '/art/' . $upload->id . '/' . ($upload->slug ?? \Illuminate\Support\Str::slug($upload->name ?? $upload->title ?? 'artwork')) : '#'),
'thumb' => $upload->thumb ?? $upload->thumb_url ?? null,
'thumb_url' => $upload->thumb_url ?? $upload->thumb ?? null,
'thumb_srcset' => $upload->thumb_srcset ?? null,
'uname' => $upload->uname ?? $upload->author_name ?? '',
'username' => $upload->username ?? $upload->uname ?? '',
'avatar_url' => $upload->avatar_url ?? null,
'content_type_name' => $upload->content_type_name ?? '',
'content_type_slug' => $upload->content_type_slug ?? '',
'category_name' => $upload->category_name ?? '',
'category_slug' => $upload->category_slug ?? '',
'width' => $upload->width ?? null,
'height' => $upload->height ?? null,
'published_at' => !empty($upload->published_at)
? (method_exists($upload->published_at, 'toIsoString') ? $upload->published_at->toIsoString() : (string) $upload->published_at)
: null,
]);
$homeGalleryNextPageUrl = method_exists($latestUploads ?? null, 'nextPageUrl') ? $latestUploads->nextPageUrl() : null;
@endphp
@push('head')
<link rel="canonical" href="{{ $seoCanonical ?? url()->current() }}">
@if(!empty($seoPrev ?? null))<link rel="prev" href="{{ $seoPrev }}">@endif
@if(!empty($seoNext ?? null))<link rel="next" href="{{ $seoNext }}">@endif
@endpush
{{-- Latest uploads grid use same Nova gallery layout as /browse --}}
<section class="px-6 pb-10 pt-6 md:px-10">
<div
data-react-masonry-gallery
data-artworks='@json($homeGalleryArtworks)'
data-gallery-type="home-uploads"
@if ($homeGalleryNextPageUrl) data-next-page-url="{{ $homeGalleryNextPageUrl }}" @endif
data-limit="{{ method_exists($latestUploads ?? null, 'perPage') ? $latestUploads->perPage() : $homeGalleryArtworks->count() }}"
class="min-h-32"
></div>
</section>
@push('styles')
@if(! ($gridV2 ?? false))
<style>
[data-nova-gallery].is-enhanced [data-gallery-grid] {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
grid-auto-rows: 8px;
gap: 1rem;
}
@media (min-width: 768px) {
[data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
[data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}
@media (min-width: 2600px) {
[data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}
[data-nova-gallery].is-enhanced [data-gallery-grid] > .nova-card { margin: 0 !important; }
[data-nova-gallery].is-enhanced [data-gallery-pagination] { display: none; }
[data-gallery-skeleton].is-loading { display: grid !important; grid-template-columns: inherit; gap: 1rem; }
</style>
@endif
@endpush
@push('scripts')
@vite('resources/js/entry-masonry-gallery.jsx')
@endpush