fixed gallery

This commit is contained in:
2026-02-22 17:09:34 +01:00
parent 48e2055b6a
commit 5c97488e80
33 changed files with 2062 additions and 550 deletions

View File

@@ -1,266 +1,96 @@
@extends('layouts.nova')
@php
use App\Banner;
use App\Models\Category;
use Illuminate\Pagination\LengthAwarePaginator;
@push('head')
<title>{{ $meta['title'] }}</title>
<meta name="description" content="{{ $meta['description'] }}">
<link rel="canonical" href="{{ $meta['canonical'] }}">
// Determine a sensible category/context for this artwork so the
// legacy layout (sidebar + hero) can be rendered similarly to
// category listing pages.
$category = $artwork->categories->first() ?? null;
$contentType = $category ? $category->contentType : null;
<meta property="og:type" content="article">
<meta property="og:site_name" content="Skinbase">
<meta property="og:title" content="{{ $meta['title'] }}">
<meta property="og:description" content="{{ $meta['description'] }}">
<meta property="og:url" content="{{ $meta['canonical'] }}">
@if(!empty($meta['og_image']))
<meta property="og:image" content="{{ $meta['og_image'] }}">
<meta property="og:image:type" content="image/webp">
@if(!empty($meta['og_width']))
<meta property="og:image:width" content="{{ $meta['og_width'] }}">
@endif
@if(!empty($meta['og_height']))
<meta property="og:image:height" content="{{ $meta['og_height'] }}">
@endif
@endif
if ($contentType) {
$rootCategories = Category::where('content_type_id', $contentType->id)
->whereNull('parent_id')
->orderBy('sort_order')
->get();
} else {
$rootCategories = collect();
}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ $meta['title'] }}">
<meta name="twitter:description" content="{{ $meta['description'] }}">
@if(!empty($meta['og_image']))
<meta name="twitter:image" content="{{ $meta['og_image'] }}">
@endif
$subcategories = $category ? $category->children()->orderBy('sort_order')->get() : collect();
// Provide an empty paginator to satisfy any shared pagination partials
$artworks = new LengthAwarePaginator([], 0, 24, 1, ['path' => request()->url()]);
@endphp
@php
$authorName = $artwork->user?->name ?: $artwork->user?->username ?: null;
$keywords = $artwork->tags->pluck('name')->merge($artwork->categories->pluck('name'))->filter()->unique()->implode(', ');
$license = $artwork->license_url ?? null;
$imageObject = [
'@context' => 'https://schema.org',
'@type' => 'ImageObject',
'name' => (string) $artwork->title,
'description' => (string) ($artwork->description ?? ''),
'url' => $meta['canonical'],
'contentUrl' => $meta['og_image'] ?? null,
'thumbnailUrl' => $presentMd['url'] ?? ($meta['og_image'] ?? null),
'encodingFormat' => 'image/webp',
'width' => !empty($meta['og_width']) ? (int) $meta['og_width'] : null,
'height' => !empty($meta['og_height']) ? (int) $meta['og_height'] : null,
'author' => $authorName ? ['@type' => 'Person', 'name' => $authorName] : null,
'datePublished' => optional($artwork->published_at)->toAtomString(),
'license' => $license,
'keywords' => $keywords !== '' ? $keywords : null,
];
$creativeWork = [
'@context' => 'https://schema.org',
'@type' => 'CreativeWork',
'name' => (string) $artwork->title,
'description' => (string) ($artwork->description ?? ''),
'url' => $meta['canonical'],
'author' => $authorName ? ['@type' => 'Person', 'name' => $authorName] : null,
'datePublished' => optional($artwork->published_at)->toAtomString(),
'license' => $license,
'keywords' => $keywords !== '' ? $keywords : null,
'image' => $meta['og_image'] ?? null,
];
$imageObject = array_filter($imageObject, static fn ($value) => $value !== null && $value !== '');
$creativeWork = array_filter($creativeWork, static fn ($value) => $value !== null && $value !== '');
$preloadSrcset = ($presentMd['url'] ?? '') . ' 640w, ' . ($presentLg['url'] ?? '') . ' 1280w, ' . ($presentXl['url'] ?? '') . ' 1920w';
@endphp
@if(!empty($presentLg['url']))
<link rel="preload" as="image"
href="{{ $presentLg['url'] }}"
imagesrcset="{{ trim($preloadSrcset) }}"
imagesizes="(min-width: 1280px) 1200px, (min-width: 768px) 90vw, 100vw">
@endif
<script type="application/ld+json">{!! json_encode($imageObject, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) !!}</script>
<script type="application/ld+json">{!! json_encode($creativeWork, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) !!}</script>
@endpush
@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 pr-2">
@foreach($subcategories as $sub)
<li><a class="hover:text-white {{ $category && $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">
@if($contentType)
<a class="hover:text-white" href="/{{ $contentType->slug }}">{{ $contentType->name }}</a>
@endif
@if($category)
@foreach ($category->breadcrumbs as $crumb)
<span class="opacity-50"></span> <a class="hover:text-white" href="{{ $crumb->url }}">{{ $crumb->name }}</a>
@endforeach
@endif
</div>
@php
$breadcrumbs = $category ? (is_array($category->breadcrumbs) ? $category->breadcrumbs : [$category]) : [];
$headerCategory = !empty($breadcrumbs) ? end($breadcrumbs) : ($category ?? null);
@endphp
<h1 class="mt-2 text-3xl md:text-4xl font-semibold tracking-tight text-white/95">{{ $headerCategory->name ?? $artwork->title }}</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">{{ $artwork->title }}</div>
<p class="mt-2 text-sm leading-6 text-neutral-400">{!! $artwork->description ?? ($headerCategory->description ?? ($contentType->name ?? 'Artwork')) !!}</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>
<!-- Artwork detail -->
<section class="px-6 pb-10 md:px-10">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="col-span-2">
<div class="rounded-2xl overflow-hidden bg-black/20 border border-white/10 shadow-lg">
<img src="{{ $artwork->thumbnail_url ?? '/images/placeholder.jpg' }}" alt="{{ $artwork->title }}" class="w-full h-auto object-contain" />
</div>
</div>
<aside class="p-4 bg-white/3 rounded-2xl border border-white/6">
<h3 class="font-semibold text-white">{{ $artwork->title }}</h3>
<p class="text-sm text-neutral-400 mt-2">{!! $artwork->description ?? 'No description provided.' !!}</p>
<div class="mt-4">
<a href="{{ $artwork->file_path ?? '#' }}" class="inline-block px-4 py-2 bg-indigo-600 text-white rounded">Download</a>
</div>
</aside>
</div>
@if(isset($similarItems) && $similarItems->isNotEmpty())
<section class="mt-8" data-similar-analytics data-algo-version="{{ $similarAlgoVersion ?? '' }}" data-artwork-id="{{ $artwork->id }}">
<div class="mb-3 flex items-center justify-between">
<h2 class="text-lg md:text-xl font-semibold text-white/95">Similar artworks</h2>
</div>
<div class="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4">
@foreach($similarItems as $item)
<article class="rounded-2xl overflow-hidden border border-white/10 bg-black/20 shadow-lg">
<a
href="{{ $item['url'] }}"
class="group block"
data-similar-click
data-similar-id="{{ $item['id'] }}"
data-similar-title="{{ e($item['title']) }}"
>
<div class="aspect-[16/10] bg-neutral-900">
<img
src="{{ $item['thumb'] }}"
@if(!empty($item['thumb_srcset'])) srcset="{{ $item['thumb_srcset'] }}" @endif
loading="lazy"
decoding="async"
alt="{{ $item['title'] }}"
class="h-full w-full object-cover transition group-hover:scale-[1.02]"
/>
</div>
<div class="p-3">
<div class="truncate text-sm font-medium text-white/90">{{ $item['title'] }}</div>
@if(!empty($item['author']))
<div class="mt-1 truncate text-xs text-neutral-400">by {{ $item['author'] }}</div>
@endif
</div>
</a>
</article>
@endforeach
</div>
</section>
@endif
</section>
</main>
</div>
</div>
<div id="artwork-page"
data-artwork='@json($artworkData)'
data-related='@json($relatedItems)'
data-present-md='@json($presentMd)'
data-present-lg='@json($presentLg)'
data-present-xl='@json($presentXl)'
data-present-sq='@json($presentSq)'
data-cdn='@json(rtrim((string) config("cdn.files_url", "https://files.skinbase.org"), "/"))'
data-canonical='@json($meta["canonical"])'>
</div>
</div> <!-- end .legacy-page -->
@php
$jsonLdType = str_starts_with((string) ($artwork->mime_type ?? ''), 'image/') ? 'ImageObject' : 'CreativeWork';
$keywords = $artwork->tags()->pluck('name')->values()->all();
$jsonLd = [
'@context' => 'https://schema.org',
'@type' => $jsonLdType,
'name' => (string) $artwork->title,
'description' => trim(strip_tags((string) ($artwork->description ?? ''))),
'author' => [
'@type' => 'Person',
'name' => (string) optional($artwork->user)->name,
],
'datePublished' => optional($artwork->published_at)->toAtomString(),
'url' => request()->url(),
'image' => (string) ($artwork->thumbnail_url ?? ''),
'keywords' => $keywords,
];
@endphp
<script type="application/ld+json">{!! json_encode($jsonLd, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) !!}</script>
@if(isset($similarItems) && $similarItems->isNotEmpty())
<script>
(function () {
var section = document.querySelector('[data-similar-analytics]');
if (!section) return;
var algoVersion = section.getAttribute('data-algo-version') || '';
var sourceArtworkId = section.getAttribute('data-artwork-id') || '';
var anchors = section.querySelectorAll('[data-similar-click]');
var impressionPayload = {
event: 'similar_artworks_impression',
source_artwork_id: sourceArtworkId,
algo_version: algoVersion,
item_ids: Array.prototype.map.call(anchors, function (anchor) {
return anchor.getAttribute('data-similar-id');
})
};
window.dataLayer = window.dataLayer || [];
function sendAnalytics(payload) {
var endpoint = '/api/analytics/similar-artworks';
var body = JSON.stringify(payload);
if (navigator.sendBeacon) {
var blob = new Blob([body], { type: 'application/json' });
navigator.sendBeacon(endpoint, blob);
return;
}
fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: body,
keepalive: true
}).catch(function () {
// ignore analytics transport errors
});
}
window.dataLayer.push(impressionPayload);
anchors.forEach(function (anchor, index) {
sendAnalytics({
event_type: 'impression',
source_artwork_id: Number(sourceArtworkId),
similar_artwork_id: Number(anchor.getAttribute('data-similar-id')),
algo_version: algoVersion,
position: index + 1,
items_count: anchors.length
});
});
anchors.forEach(function (anchor, index) {
anchor.addEventListener('click', function () {
window.dataLayer.push({
event: 'similar_artworks_click',
source_artwork_id: sourceArtworkId,
algo_version: algoVersion,
similar_artwork_id: anchor.getAttribute('data-similar-id'),
similar_artwork_title: anchor.getAttribute('data-similar-title') || '',
position: index + 1
});
sendAnalytics({
event_type: 'click',
source_artwork_id: Number(sourceArtworkId),
similar_artwork_id: Number(anchor.getAttribute('data-similar-id')),
algo_version: algoVersion,
position: index + 1
});
});
});
})();
</script>
@endif
@vite(['resources/js/Pages/ArtworkPage.jsx'])
@endsection
@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%);
}
</style>
@endpush