fixed gallery from legacy into one

This commit is contained in:
2026-02-15 17:46:08 +01:00
parent 7dbfdab40e
commit b053c0cc48
9 changed files with 263 additions and 485 deletions

1
.gitignore vendored
View File

@@ -16,6 +16,7 @@
/public/build
/public/hot
/public/storage
/public/files
/storage/*.key
/storage/pail
/vendor

View File

@@ -0,0 +1,181 @@
<?php
namespace App\Http\Controllers;
use App\Models\Category;
use App\Models\ContentType;
use App\Services\ArtworkService;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
class BrowseGalleryController extends Controller
{
private const CONTENT_TYPE_SLUGS = ['photography', 'wallpapers', 'skins', 'other'];
public function __construct(private ArtworkService $artworks)
{
}
public function browse(Request $request)
{
$sort = (string) $request->query('sort', 'latest');
$perPage = $this->resolvePerPage($request);
$artworks = $this->artworks->browsePublicArtworks($perPage, $sort);
$mainCategories = $this->mainCategories();
return view('gallery.index', [
'gallery_type' => 'browse',
'mainCategories' => $mainCategories,
'subcategories' => $mainCategories,
'contentType' => null,
'category' => null,
'artworks' => $artworks,
'hero_title' => 'Browse Artworks',
'hero_description' => 'List of all uploaded artworks across Skins, Wallpapers, Photography, and Other.',
'breadcrumbs' => collect(),
'page_title' => 'Browse Uploaded Artworks - Photography, Wallpapers and Skins at SkinBase',
'page_meta_description' => "Browse Uploaded Photography, Wallpapers and Skins to one of the world's oldest online social community for artists and art enthusiasts.",
'page_meta_keywords' => 'photography, wallpapers, skins, stock, browse, social, community, artist, picture, photo',
'page_canonical' => url('/browse'),
]);
}
public function content(Request $request, string $contentTypeSlug, ?string $path = null)
{
$contentSlug = strtolower($contentTypeSlug);
if (! in_array($contentSlug, self::CONTENT_TYPE_SLUGS, true)) {
abort(404);
}
$contentType = ContentType::where('slug', $contentSlug)->first();
if (! $contentType) {
abort(404);
}
$sort = (string) $request->query('sort', 'latest');
$perPage = $this->resolvePerPage($request);
$mainCategories = $this->mainCategories();
$rootCategories = $contentType->rootCategories()->orderBy('sort_order')->orderBy('name')->get();
$normalizedPath = trim((string) $path, '/');
if ($normalizedPath === '') {
$artworks = $this->artworks->getArtworksByContentType($contentSlug, $perPage, $sort);
return view('gallery.index', [
'gallery_type' => 'content-type',
'mainCategories' => $mainCategories,
'subcategories' => $rootCategories,
'contentType' => $contentType,
'category' => null,
'artworks' => $artworks,
'hero_title' => $contentType->name,
'hero_description' => $contentType->description ?? ($contentType->name . ' artworks on Skinbase.'),
'breadcrumbs' => collect([(object) ['name' => $contentType->name, 'url' => '/' . $contentSlug]]),
'page_title' => $contentType->name,
'page_meta_description' => $contentType->description ?? ($contentType->name . ' artworks on Skinbase'),
'page_meta_keywords' => strtolower($contentType->slug) . ', skinbase, artworks, wallpapers, skins, photography',
'page_canonical' => url('/' . $contentSlug),
]);
}
$segments = array_values(array_filter(explode('/', $normalizedPath)));
$category = Category::findByPath($contentSlug, $segments);
if (! $category) {
abort(404);
}
$artworks = $this->artworks->getArtworksByCategoryPath(array_merge([$contentSlug], $segments), $perPage, $sort);
$subcategories = $category->children()->orderBy('sort_order')->orderBy('name')->get();
if ($subcategories->isEmpty()) {
$subcategories = $rootCategories;
}
$breadcrumbs = collect($category->breadcrumbs)
->map(function (Category $crumb) {
return (object) [
'name' => $crumb->name,
'url' => $crumb->url,
];
});
return view('gallery.index', [
'gallery_type' => 'category',
'mainCategories' => $mainCategories,
'subcategories' => $subcategories,
'contentType' => $contentType,
'category' => $category,
'artworks' => $artworks,
'hero_title' => $category->name,
'hero_description' => $category->description ?? ($contentType->name . ' artworks on Skinbase.'),
'breadcrumbs' => $breadcrumbs,
'page_title' => $category->name,
'page_meta_description' => $category->description ?? ($contentType->name . ' artworks on Skinbase'),
'page_meta_keywords' => strtolower($contentType->slug) . ', skinbase, artworks, wallpapers, skins, photography',
'page_canonical' => url('/' . $contentSlug . '/' . strtolower($category->full_slug_path)),
]);
}
public function showArtwork(Request $request, string $contentTypeSlug, string $categoryPath, string $artwork)
{
return app(ArtworkController::class)->show(
$request,
strtolower($contentTypeSlug),
trim($categoryPath, '/'),
$artwork
);
}
public function legacyCategory(Request $request, ?string $group = null, ?string $slug = null, ?string $id = null)
{
if ($id !== null && ctype_digit((string) $id)) {
$category = Category::with('contentType')->find((int) $id);
if (! $category || ! $category->contentType) {
abort(404);
}
return redirect($category->url, 301);
}
$contentSlug = strtolower((string) $group);
if (! in_array($contentSlug, self::CONTENT_TYPE_SLUGS, true)) {
abort(404);
}
$target = '/' . $contentSlug;
$normalizedSlug = trim((string) $slug, '/');
if ($normalizedSlug !== '') {
$target .= '/' . strtolower($normalizedSlug);
}
if ($request->query()) {
$target .= '?' . http_build_query($request->query());
}
return redirect($target, 301);
}
private function resolvePerPage(Request $request): int
{
$value = (int) $request->query('per_page', 40);
return max(12, min($value, 80));
}
private function mainCategories(): Collection
{
return ContentType::orderBy('id')
->get(['name', 'slug'])
->map(function (ContentType $type) {
return (object) [
'id' => $type->id,
'name' => $type->name,
'slug' => $type->slug,
'url' => '/' . strtolower($type->slug),
];
});
}
}

View File

@@ -24,20 +24,31 @@
<div class="mt-6 text-sm text-neutral-400">
<div class="font-semibold text-white/80 mb-2">Main Categories:</div>
<ul class="space-y-2">
@foreach($rootCategories as $root)
@foreach($mainCategories as $main)
<li>
<a class="flex items-center gap-2 hover:text-white" href="{{ $root->url }}"><span class="opacity-70">📁</span> {{ $root->name }}</a>
<a class="flex items-center gap-2 hover:text-white" href="{{ $main->url }}"><span class="opacity-70">📁</span> {{ $main->name }}</a>
</li>
@endforeach
</ul>
<div class="mt-6 font-semibold text-white/80 mb-2">Browse Subcategories:</div>
<ul class="space-y-2 sb-scrollbar max-h-56 overflow-auto pr-2">
@foreach($rootCategories as $root)
@forelse($subcategories as $sub)
@php
$subName = $sub->category_name ?? $sub->name ?? null;
$subUrl = $sub->url ?? ((isset($sub->slug) && isset($contentType)) ? '/' . $contentType->slug . '/' . $sub->slug : null);
$isActive = isset($category) && isset($sub->id) && $category && ((int) $sub->id === (int) $category->id);
@endphp
<li>
<a class="hover:text-white text-neutral-400" href="{{ $root->url }}">{{ $root->name }}</a>
@if($subUrl)
<a class="hover:text-white {{ $isActive ? 'font-semibold text-white' : 'text-neutral-400' }}" href="{{ $subUrl }}">{{ $subName }}</a>
@else
<span class="text-neutral-400">{{ $subName }}</span>
@endif
</li>
@endforeach
@empty
<li><span class="text-neutral-500">No subcategories</span></li>
@endforelse
</ul>
</div>
</div>
@@ -48,21 +59,34 @@
<div class="absolute inset-0 opacity-35"></div>
<div class="relative px-6 py-8 md:px-10 md:py-10">
<div class="text-sm text-neutral-400">Browse</div>
<div class="text-sm text-neutral-400">
@if(($gallery_type ?? null) === 'browse')
Browse
@elseif(isset($contentType) && $contentType)
<a class="hover:text-white" href="/{{ $contentType->slug }}">{{ $contentType->name }}</a>
@if(($gallery_type ?? null) === 'category')
@foreach($breadcrumbs as $crumb)
<span class="opacity-50"></span>
<a class="hover:text-white" href="{{ $crumb->url }}">{{ $crumb->name }}</a>
@endforeach
@endif
@endif
</div>
<h1 class="mt-2 text-3xl md:text-4xl font-semibold tracking-tight text-white/95">Browse Artworks</h1>
<h1 class="mt-2 text-3xl md:text-4xl font-semibold tracking-tight text-white/95">{{ $hero_title ?? 'Browse Artworks' }}</h1>
<section class="mt-5 bg-white/5 border border-white/10 rounded-2xl shadow-lg">
<div class="p-5 md:p-6">
<div class="text-lg font-semibold text-white/90">All categories</div>
<p class="mt-2 text-sm leading-6 text-neutral-400">List of all uploaded artworks across Skins, Wallpapers, Photography, and Other.</p>
<div class="text-lg font-semibold text-white/90">{{ $hero_title ?? 'Browse Artworks' }}</div>
<p class="mt-2 text-sm leading-6 text-neutral-400">{!! $hero_description ?? '' !!}</p>
</div>
</section>
<div class="absolute left-0 right-0 bottom-0 h-36 nb-hero-fade pointer-events-none" aria-hidden="true"></div>
</div>
</div>
<section class="px-6 pb-10 pt-8 md:px-10" data-nova-gallery data-gallery-type="browse">
<section class="px-6 pb-10 pt-8 md:px-10" data-nova-gallery data-gallery-type="{{ $gallery_type ?? 'browse' }}">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6" data-gallery-grid>
@forelse ($artworks as $art)
@include('legacy._artwork_card', ['art' => $art])
@@ -77,7 +101,9 @@
</div>
<div class="flex justify-center mt-10" data-gallery-pagination>
{{ $artworks->withQueryString()->links() }}
@if ($artworks instanceof \Illuminate\Contracts\Pagination\Paginator)
{{ method_exists($artworks, 'withQueryString') ? $artworks->withQueryString()->links() : $artworks->links() }}
@endif
</div>
<div class="hidden mt-8" data-gallery-skeleton></div>
</section>
@@ -90,9 +116,6 @@
@push('styles')
<style>
.nb-hero-fade {
background: linear-gradient(180deg, rgba(17,24,39,0) 0%, rgba(7,10,15,0.9) 60%, rgba(7,10,15,1) 100%);
}
[data-nova-gallery].is-enhanced [data-gallery-grid] {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
@@ -100,14 +123,12 @@
gap: 1rem;
}
@media (min-width: 768px) {
/* From 768px up to ~991px show 2 columns */
[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(4, minmax(0, 1fr)); }
}
@media (min-width: 2600px) {
/* Very large displays: 5 columns */
[data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}
[data-nova-gallery].is-enhanced [data-gallery-grid] > .nova-card { margin: 0 !important; }
@@ -123,6 +144,9 @@
@keyframes novaShimmer {
to { background-position-x: -200%; }
}
.nb-hero-fade {
background: linear-gradient(180deg, rgba(17,24,39,0) 0%, rgba(7,10,15,0.9) 60%, rgba(7,10,15,1) 100%);
}
</style>
@endpush

View File

@@ -17,16 +17,48 @@
$category = trim((string) ($art->category_name ?? $art->category ?? 'General'));
$license = trim((string) ($art->license ?? 'Standard'));
$resolution = trim((string) ($art->resolution ?? ((isset($art->width, $art->height) && $art->width && $art->height) ? ($art->width . '×' . $art->height) : '')));
$likes = (int) ($art->likes ?? $art->favourites ?? 0);
$downloads = (int) ($art->downloads ?? $art->downloaded ?? 0);
// Safe integer extractor: handle numeric, arrays, Collections, or relations
$safeInt = function ($v, $fallback = 0) {
if (is_numeric($v)) return (int) $v;
if (is_array($v)) return count($v);
if (is_object($v)) {
// Eloquent Collections implement Countable and have count()
if (method_exists($v, 'count')) return (int) $v->count();
if ($v instanceof Countable) return (int) count($v);
}
return (int) $fallback;
};
$likes = $safeInt($art->likes ?? $art->favourites ?? 0);
$downloads = $safeInt($art->downloads ?? $art->downloaded ?? 0);
$img_src = (string) ($art->thumb ?? $art->thumbnail_url ?? '/images/placeholder.jpg');
$img_srcset = (string) ($art->thumb_srcset ?? $art->thumbnail_srcset ?? $img_src);
$img_avif_srcset = (string) ($art->thumb_avif_srcset ?? $img_srcset);
$img_webp_srcset = (string) ($art->thumb_webp_srcset ?? $img_srcset);
$img_width = max(1, (int) ($art->width ?? 800));
$img_height = max(1, (int) ($art->height ?? 600));
// Width/height may be stored directly or as a related object; handle safely
$resolveDimension = function ($val, $fallback) {
if (is_numeric($val)) return (int) $val;
if (is_array($val)) {
$v = reset($val);
return is_numeric($v) ? (int) $v : (int) $fallback;
}
if (is_object($val)) {
if (method_exists($val, 'first')) {
$f = $val->first();
if (is_object($f) && isset($f->width)) return (int) ($f->width ?: $fallback);
if (is_object($f) && isset($f->height)) return (int) ($f->height ?: $fallback);
}
// Try numeric cast otherwise
if (isset($val->width)) return (int) $val->width;
if (isset($val->height)) return (int) $val->height;
}
return (int) $fallback;
};
$img_width = max(1, $resolveDimension($art->width ?? null, 800));
$img_height = max(1, $resolveDimension($art->height ?? null, 600));
$contentUrl = $img_src;
$cardUrl = (string) ($art->url ?? '#');

View File

@@ -1,148 +0,0 @@
@extends('layouts.nova')
@php
use App\Banner;
@endphp
@section('content')
<div class="container-fluid legacy-page">
@php Banner::ShowResponsiveAd(); @endphp
<div class="pt-0">
<div class="mx-auto w-full">
<div class="flex min-h-[calc(100vh-64px)]">
<!-- SIDEBAR -->
<aside id="sidebar" class="hidden md:block w-72 shrink-0 border-r border-neutral-800 bg-nova-900/60 backdrop-blur-sm">
<div class="p-4">
<button class="w-full h-12 rounded-xl bg-white/5 hover:bg-white/7 border border-white/5 flex items-center gap-3 px-4">
<span class="w-8 h-8 rounded-lg bg-white/5 inline-flex items-center justify-center">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
</span>
<span class="text-sm text-white/90">Menu</span>
</button>
<div class="mt-6 text-sm text-neutral-400">
<div class="font-semibold text-white/80 mb-2">Main Categories:</div>
<ul class="space-y-2">
@foreach($rootCategories as $root)
<li>
<a class="flex items-center gap-2 hover:text-white" href="{{ $root->url }}"><span class="opacity-70">📁</span> {{ $root->name }}</a>
</li>
@endforeach
</ul>
<div class="mt-6 font-semibold text-white/80 mb-2">Browse Subcategories:</div>
<ul class="space-y-2 sb-scrollbar max-h-56 overflow-auto pr-2">
@foreach($subcategories as $sub)
<li><a class="hover:text-white {{ $sub->id === $category->id ? 'font-semibold text-white' : 'text-neutral-400' }}" href="{{ $sub->url }}">{{ $sub->name }}</a></li>
@endforeach
</ul>
</div>
</div>
</aside>
<!-- MAIN -->
<main class="flex-1">
<div class="relative overflow-hidden nb-hero-radial">
<div class="absolute inset-0 opacity-35"></div>
<div class="relative px-6 py-8 md:px-10 md:py-10">
<div class="text-sm text-neutral-400">
<a class="hover:text-white" href="/{{ $contentType->slug }}">{{ $contentType->name }}</a>
@foreach ($category->breadcrumbs as $crumb)
<span class="opacity-50"></span> <a class="hover:text-white" href="{{ $crumb->url }}">{{ $crumb->name }}</a>
@endforeach
</div>
@php
// Use the current (last) breadcrumb as the header so
// the page shows the leaf category name (e.g. "Winamp").
$breadcrumbs = is_array($category->breadcrumbs) ? $category->breadcrumbs : [$category];
$headerCategory = end($breadcrumbs) ?: $category;
@endphp
<h1 class="mt-2 text-3xl md:text-4xl font-semibold tracking-tight text-white/95">{{ $headerCategory->name }}</h1>
<section class="mt-5 bg-white/5 border border-white/10 rounded-2xl shadow-lg">
<div class="p-5 md:p-6">
<div class="text-lg font-semibold text-white/90">{{ $headerCategory->name }}</div>
<p class="mt-2 text-sm leading-6 text-neutral-400">{!! $headerCategory->description ?? ($contentType->name . ' artworks on Skinbase.') !!}</p>
</div>
</section>
{{-- soft fade at bottom of hero to blend into main background --}}
<div class="absolute left-0 right-0 bottom-0 h-36 nb-hero-fade pointer-events-none" aria-hidden="true"></div>
</div>
</div>
<!-- Grid -->
<section class="px-6 pb-10 md:px-10" data-nova-gallery data-gallery-type="category-slug">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6" data-gallery-grid>
@forelse ($artworks as $art)
@include('legacy._artwork_card', ['art' => $art])
@empty
<div class="panel panel-default effect2">
<div class="panel-heading"><strong>No Artworks Yet</strong></div>
<div class="panel-body">
<p>Once uploads arrive they will appear here. Check back soon.</p>
</div>
</div>
@endforelse
</div>
<div class="flex justify-center mt-10" data-gallery-pagination>
@if ($artworks instanceof \Illuminate\Contracts\Pagination\Paginator)
{{ $artworks->links() }}
@endif
</div>
<div class="hidden mt-8" data-gallery-skeleton></div>
</section>
</main>
</div>
</div>
</div>
</div> <!-- end .legacy-page -->
@endsection
@push('styles')
<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) {
/* From 768px up to ~991px show 2 columns */
[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(4, minmax(0, 1fr)); }
}
@media (min-width: 2600px) {
/* Very large displays: 5 columns */
[data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(5, 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; }
.nova-skeleton-card {
border-radius: 1rem;
min-height: 180px;
background: linear-gradient(110deg, rgba(255,255,255,.06) 8%, rgba(255,255,255,.12) 18%, rgba(255,255,255,.06) 33%);
background-size: 200% 100%;
animation: novaShimmer 1.2s linear infinite;
}
@keyframes novaShimmer {
to { background-position-x: -200%; }
}
.nb-hero-fade {
background: linear-gradient(180deg, rgba(17,24,39,0) 0%, rgba(7,10,15,0.9) 60%, rgba(7,10,15,1) 100%);
/* tweak the colors above if the page background differs */
}
</style>
@endpush
@push('scripts')
<script src="/js/legacy-gallery-init.js" defer></script>
@endpush

View File

@@ -1,158 +0,0 @@
@extends('layouts.nova')
@php
use App\Banner;
@endphp
@section('content')
<div class="container-fluid legacy-page">
@php Banner::ShowResponsiveAd(); @endphp
<div class="pt-0">
<div class="mx-auto w-full">
<div class="flex min-h-[calc(100vh-64px)]">
<!-- SIDEBAR -->
<aside id="sidebar" class="hidden md:block w-72 shrink-0 border-r border-neutral-800 bg-nova-900/60 backdrop-blur-sm">
<div class="p-4">
<button class="w-full h-12 rounded-xl bg-white/5 hover:bg-white/7 border border-white/5 flex items-center gap-3 px-4">
<span class="w-8 h-8 rounded-lg bg-white/5 inline-flex items-center justify-center">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
</span>
<span class="text-sm text-white/90">Menu</span>
</button>
<div class="mt-6 text-sm text-neutral-400">
<div class="font-semibold text-white/80 mb-2">Main Categories:</div>
<ul class="space-y-2">
@foreach($rootCategories as $root)
<li>
<a class="flex items-center gap-2 hover:text-white" href="{{ $root->url }}"><span class="opacity-70">📁</span> {{ $root->name }}</a>
</li>
@endforeach
</ul>
<div class="mt-6 font-semibold text-white/80 mb-2">Browse Subcategories:</div>
<ul class="space-y-2 sb-scrollbar max-h-56 overflow-auto pr-2">
@if(isset($subcategories) && $subcategories && count($subcategories))
@foreach($subcategories as $sub)
@php
// support legacy objects (category_id/category_name/slug) and Category models
$slug = $sub->slug ?? ($sub->slug ?? null);
$name = $sub->category_name ?? $sub->name ?? null;
$url = null;
if (!empty($slug) && isset($contentType)) {
$url = '/' . $contentType->slug . '/' . $slug;
} elseif (!empty($sub->url)) {
$url = $sub->url;
}
@endphp
<li>
@if($url)
<a class="hover:text-white text-neutral-400" href="{{ $url }}">{{ $name }}</a>
@else
<span class="text-neutral-400">{{ $name }}</span>
@endif
</li>
@endforeach
@else
@foreach(\App\Models\ContentType::orderBy('id')->get() as $ct)
<li><a class="hover:text-white text-neutral-400" href="/{{ $ct->slug }}">{{ $ct->name }}</a></li>
@endforeach
@endif
</ul>
</div>
</div>
</aside>
<!-- MAIN -->
<main class="flex-1">
<div class="relative overflow-hidden nb-hero-radial">
<div class="absolute inset-0 opacity-35"></div>
<div class="relative px-6 py-8 md:px-10 md:py-10">
<div class="text-sm text-neutral-400">
<a class="hover:text-white" href="/{{ $contentType->slug }}">{{ $contentType->name }}</a>
</div>
<h1 class="mt-2 text-3xl md:text-4xl font-semibold tracking-tight text-white/95">{{ $contentType->name }}</h1>
<section class="mt-5 bg-white/5 border border-white/10 rounded-2xl shadow-lg">
<div class="p-5 md:p-6">
<div class="text-lg font-semibold text-white/90">{{ $contentType->name }}</div>
<p class="mt-2 text-sm leading-6 text-neutral-400">{!! $page_meta_description ?? ($contentType->name . ' artworks on Skinbase.') !!}</p>
</div>
</section>
{{-- soft fade at bottom of hero to blend into main background --}}
<div class="absolute left-0 right-0 bottom-0 h-36 nb-hero-fade pointer-events-none" aria-hidden="true"></div>
</div>
</div>
<!-- Artworks gallery (same layout as subcategory pages) -->
<section class="px-6 pb-10 md:px-10" data-nova-gallery data-gallery-type="content-type">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6" data-gallery-grid>
@forelse ($artworks as $art)
@include('legacy._artwork_card', ['art' => $art])
@empty
<div class="panel panel-default effect2">
<div class="panel-heading"><strong>No Artworks Yet</strong></div>
<div class="panel-body">
<p>Once uploads arrive they will appear here. Check back soon.</p>
</div>
</div>
@endforelse
</div>
<div class="flex justify-center mt-10" data-gallery-pagination>
{{ $artworks->withQueryString()->links('pagination::tailwind') }}
</div>
<div class="hidden mt-8" data-gallery-skeleton></div>
</section>
</main>
</div>
</div>
</div>
</div> <!-- end .legacy-page -->
@endsection
@push('styles')
<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) {
/* From 768px up to ~991px show 2 columns */
[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(4, minmax(0, 1fr)); }
}
@media (min-width: 2600px) {
/* Very large displays: 5 columns */
[data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(5, 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; }
.nova-skeleton-card {
border-radius: 1rem;
min-height: 180px;
background: linear-gradient(110deg, rgba(255,255,255,.06) 8%, rgba(255,255,255,.12) 18%, rgba(255,255,255,.06) 33%);
background-size: 200% 100%;
animation: novaShimmer 1.2s linear infinite;
}
@keyframes novaShimmer {
to { background-position-x: -200%; }
}
.nb-hero-fade {
background: linear-gradient(180deg, rgba(17,24,39,0) 0%, rgba(7,10,15,0.9) 60%, rgba(7,10,15,1) 100%);
}
</style>
@endpush
@push('scripts')
<script src="/js/legacy-gallery-init.js" defer></script>
@endpush

View File

@@ -1,153 +0,0 @@
@extends('layouts.nova')
@section('content')
<div class="container-fluid legacy-page">
@php \App\Banner::ShowResponsiveAd(); @endphp
<div class="effect2 page-header-wrap">
<header class="page-heading">
<h1 class="page-header">{{ $page_title }}</h1>
<p style="clear:both">{!! $tidy ?? '' !!}</p>
</header>
</div>
<div class="md:hidden px-4 py-3">
<button id="sidebarToggle" aria-controls="sidebar" aria-expanded="false" class="inline-flex items-center gap-2 px-3 py-2 rounded-lg bg-white/5 hover:bg-white/7 border border-white/5">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
<span class="text-sm text-white/90">Categories</span>
</button>
</div>
<div class="mx-auto w-full">
<div class="flex min-h-[calc(100vh-64px)]">
<!-- SIDEBAR -->
<aside id="sidebar" class="hidden md:block w-72 shrink-0 border-r border-neutral-800 bg-nova-900/60 backdrop-blur-sm">
<div class="p-4">
<button class="w-full h-12 rounded-xl bg-white/5 hover:bg-white/7 border border-white/5 flex items-center gap-3 px-4">
<span class="w-8 h-8 rounded-lg bg-white/5 inline-flex items-center justify-center">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
</span>
<span class="text-sm text-white/90">Menu</span>
</button>
<div class="mt-6 text-sm text-neutral-400">
<div class="font-semibold text-white/80 mb-2">Main Categories:</div>
<ul class="space-y-2">
<li><a class="flex items-center gap-2 hover:text-white" href="/photography"><span class="opacity-70">📷</span> Photography</a></li>
<li><a class="flex items-center gap-2 hover:text-white" href="/wallpapers"><span class="opacity-70">🖼️</span> Wallpapers</a></li>
<li><a class="flex items-center gap-2 hover:text-white" href="/skins"><span class="opacity-70">🧩</span> Skins</a></li>
<li><a class="flex items-center gap-2 hover:text-white" href="/other"><span class="opacity-70"></span> Other</a></li>
</ul>
<div class="mt-6 font-semibold text-white/80 mb-2">Browse Subcategories:</div>
<ul class="space-y-2 sb-scrollbar max-h-56 overflow-auto pr-2">
@foreach($subcategories ?? [] as $skupina)
@php
// Prefer an explicit slug when provided by the model/mapping, otherwise build one from the name
$slug = $skupina->slug ?? \Illuminate\Support\Str::slug($skupina->category_name);
$ctype = strtolower($group ?? 'photography');
$addit = (isset($id) && ($skupina->category_id ?? null) == $id) ? 'selected_group' : '' ;
@endphp
<li class="subgroup {{ $addit }}"><a class="hover:text-white" href="/{{ $ctype }}/{{ $slug }}">{{ $skupina->category_name }}</a></li>
@endforeach
</ul>
<div class="mt-6 font-semibold text-white/80 mb-2">Daily Uploads <span class="text-neutral-400 font-normal">(245)</span></div>
<div class="rounded-xl bg-white/5 border border-white/5 overflow-hidden">
<button class="w-full px-4 py-3 text-left hover:bg-white/5">All</button>
<button class="w-full px-4 py-3 text-left hover:bg-white/5">Hot</button>
</div>
<a class="mt-4 inline-flex items-center gap-2 text-neutral-400 hover:text-white" href="#">
<span>Link, more</span>
<span class="opacity-60"></span>
</a>
</div>
</div>
</aside>
<!-- Mobile overlay (shown when sidebar opens) -->
<div id="sidebarOverlay" class="hidden md:hidden fixed inset-0 bg-black/50 z-30"></div>
<!-- MAIN -->
<main class="flex-1">
<div class="nova-gallery grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-5 gap-4">
@foreach($artworks as $art)
@include('legacy._artwork_card', ['art' => $art])
@endforeach
</div>
<div class="paginationMenu text-center mt-6">
{{ method_exists($artworks, 'withQueryString') ? $artworks->withQueryString()->links() : '' }}
</div>
</main>
</div>
</div>
</div>
@endsection
@push('styles')
<style>
/* Nova-like gallery tweaks: fixed-height thumbnails, tighter spacing, refined typography */
.nova-gallery {
margin-top: 1.25rem;
}
.nova-gallery .artwork a { display: block; }
/* Ensure consistent gap and card sizing across breakpoints */
@media (min-width: 1024px) {
.nova-gallery { gap: 1rem; }
}
/* Typography refinements to match Nova */
.nova-gallery .artwork h3 { font-size: 0.95rem; line-height: 1.15; }
.nova-gallery .artwork .text-xs { font-size: 0.72rem; }
/* Improve image loading artifact handling */
.nova-gallery img { background: linear-gradient(180deg,#0b0b0b,#0f0f10); }
/* Remove any default margins on article cards that can create vertical gaps */
.nova-gallery .artwork { margin: 0; }
/* Ensure grid items don't collapse when overlay hidden */
.nova-gallery .artwork a { min-height: 0; }
</style>
@endpush
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function () {
var toggle = document.getElementById('sidebarToggle');
var sidebar = document.getElementById('sidebar');
var overlay = document.getElementById('sidebarOverlay');
if (!toggle || !sidebar) return;
function openSidebar() {
sidebar.classList.remove('hidden');
sidebar.classList.add('fixed','left-0','top-0','bottom-0','z-40');
overlay.classList.remove('hidden');
toggle.setAttribute('aria-expanded','true');
}
function closeSidebar() {
sidebar.classList.add('hidden');
sidebar.classList.remove('fixed','left-0','top-0','bottom-0','z-40');
overlay.classList.add('hidden');
toggle.setAttribute('aria-expanded','false');
}
toggle.addEventListener('click', function (e) {
e.preventDefault();
if (sidebar.classList.contains('hidden')) openSidebar(); else closeSidebar();
});
overlay && overlay.addEventListener('click', function () { closeSidebar(); });
// Close on Escape
document.addEventListener('keyup', function (e) { if (e.key === 'Escape') closeSidebar(); });
});
</script>
@endpush

View File

@@ -7,7 +7,6 @@ use App\Http\Controllers\Legacy\AvatarController;
use App\Http\Controllers\Legacy\ForumController;
use App\Http\Controllers\Legacy\NewsController;
use App\Http\Controllers\Legacy\CategoryController;
use App\Http\Controllers\Legacy\BrowseController;
use App\Http\Controllers\Legacy\FeaturedArtworksController;
use App\Http\Controllers\Legacy\DailyUploadsController;
use App\Http\Controllers\Legacy\ChatController;
@@ -27,7 +26,7 @@ use App\Http\Controllers\BrowseCategoriesController;
use App\Http\Controllers\GalleryController;
use App\Http\Controllers\Legacy\ReceivedCommentsController;
use App\Http\Controllers\Legacy\UserController as LegacyUserController;
use App\Http\Controllers\Legacy\PhotographyController;
use App\Http\Controllers\BrowseGalleryController;
// Legacy site routes
Route::get('/', [HomeController::class, 'index'])->name('legacy.home');
@@ -44,9 +43,9 @@ Route::get('/forum/{topic_id}/{slug?}', [ForumController::class, 'topic'])->wher
Route::get('/news/{id}/{slug?}', [NewsController::class, 'show'])->where('id', '\\d+')->name('legacy.news.show');
Route::get('/categories', [CategoryController::class, 'index'])->name('legacy.categories');
Route::get('/category/{group}/{slug?}/{id?}', [CategoryController::class, 'show'])->name('legacy.category');
Route::get('/category/{group}/{slug?}/{id?}', [BrowseGalleryController::class, 'legacyCategory'])->name('legacy.category');
Route::get('/browse', [BrowseController::class, 'index'])->name('legacy.browse');
Route::get('/browse', [BrowseGalleryController::class, 'browse'])->name('legacy.browse');
Route::get('/featured', [FeaturedArtworksController::class, 'index'])->name('legacy.featured');
Route::get('/featured-artworks', [FeaturedArtworksController::class, 'index'])->name('legacy.featured_artworks');
Route::get('/daily-uploads', [DailyUploadsController::class, 'index'])->name('legacy.daily_uploads');

View File

@@ -115,12 +115,12 @@ Route::bind('artwork', function ($value) {
// to serve photography's root page. This catch-all route delegates to a controller that
// will forward to the appropriate existing controller (artwork or category handlers).
// Provide a named route alias for legacy artwork URL generation used in tests.
Route::get('/{contentTypeSlug}/{categoryPath}/{artwork}', [\App\Http\Controllers\ContentRouterController::class, 'handle'])
Route::get('/{contentTypeSlug}/{categoryPath}/{artwork}', [\App\Http\Controllers\BrowseGalleryController::class, 'showArtwork'])
->where('contentTypeSlug', 'photography|wallpapers|skins|other')
->where('categoryPath', '[^/]+(?:/[^/]+)*')
->name('artworks.show');
Route::get('/{contentTypeSlug}/{path?}', [\App\Http\Controllers\ContentRouterController::class, 'handle'])
Route::get('/{contentTypeSlug}/{path?}', [\App\Http\Controllers\BrowseGalleryController::class, 'content'])
->where('contentTypeSlug', 'photography|wallpapers|skins|other')
->where('path', '.*')
->name('content.route');