fixed gallery
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -5,6 +5,25 @@
|
||||
$gridV2 = request()->query('grid') === 'v2';
|
||||
@endphp
|
||||
|
||||
@php
|
||||
$seoPage = max(1, (int) request()->query('page', 1));
|
||||
$seoBase = url()->current();
|
||||
$seoQ = request()->query(); unset($seoQ['page']);
|
||||
$seoUrl = fn(int $p) => $seoBase . ($p > 1
|
||||
? '?' . http_build_query(array_merge($seoQ, ['page' => $p]))
|
||||
: (count($seoQ) ? '?' . http_build_query($seoQ) : ''));
|
||||
$seoPrev = $seoPage > 1 ? $seoUrl($seoPage - 1) : null;
|
||||
$seoNext = (isset($artworks) && method_exists($artworks, 'nextPageUrl'))
|
||||
? $artworks->nextPageUrl() : null;
|
||||
@endphp
|
||||
|
||||
@push('head')
|
||||
<link rel="canonical" href="{{ $seoUrl($seoPage) }}">
|
||||
@if($seoPrev)<link rel="prev" href="{{ $seoPrev }}">@endif
|
||||
@if($seoNext)<link rel="next" href="{{ $seoNext }}">@endif
|
||||
<meta name="robots" content="index,follow">
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="container-fluid legacy-page">
|
||||
@php Banner::ShowResponsiveAd(); @endphp
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<meta name="description" content="{{ $page_meta_description ?? '' }}">
|
||||
<meta name="keywords" content="{{ $page_meta_keywords ?? '' }}">
|
||||
@isset($page_robots)
|
||||
@@ -29,28 +30,6 @@
|
||||
|
||||
@vite(['resources/css/app.css','resources/css/nova-grid.css','resources/scss/nova.scss','resources/js/nova.js'])
|
||||
<style>
|
||||
/* Gallery loading overlay */
|
||||
.nova-loader-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
pointer-events: none;
|
||||
z-index: 40;
|
||||
}
|
||||
.nova-loader-spinner {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
border: 4px solid rgba(255,255,255,0.08);
|
||||
border-top-color: rgba(255,255,255,0.9);
|
||||
animation: novaSpin 0.9s linear infinite;
|
||||
box-shadow: 0 6px 18px rgba(2,6,23,0.6);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
@keyframes novaSpin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* Card enter animation */
|
||||
.nova-card-enter { opacity: 0; transform: translateY(10px) scale(0.995); }
|
||||
.nova-card-enter.nova-card-enter-active { transition: transform 380ms cubic-bezier(.2,.9,.2,1), opacity 380ms ease-out; opacity: 1; transform: none; }
|
||||
|
||||
@@ -2,6 +2,25 @@
|
||||
|
||||
@php($gridV2 = request()->query('grid') === 'v2')
|
||||
|
||||
@php
|
||||
$seoPage = max(1, (int) request()->query('page', 1));
|
||||
$seoBase = url()->current();
|
||||
$seoQ = request()->query(); unset($seoQ['page']);
|
||||
$seoUrl = fn(int $p) => $seoBase . ($p > 1
|
||||
? '?' . http_build_query(array_merge($seoQ, ['page' => $p]))
|
||||
: (count($seoQ) ? '?' . http_build_query($seoQ) : ''));
|
||||
$seoPrev = $seoPage > 1 ? $seoUrl($seoPage - 1) : null;
|
||||
$seoNext = (isset($artworks) && method_exists($artworks, 'nextPageUrl'))
|
||||
? $artworks->nextPageUrl() : null;
|
||||
@endphp
|
||||
|
||||
@push('head')
|
||||
<link rel="canonical" href="{{ $seoUrl($seoPage) }}">
|
||||
@if($seoPrev)<link rel="prev" href="{{ $seoPrev }}">@endif
|
||||
@if($seoNext)<link rel="next" href="{{ $seoNext }}">@endif
|
||||
<meta name="robots" content="index,follow">
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="container-fluid legacy-page">
|
||||
<div class="effect2 page-header-wrap">
|
||||
|
||||
@@ -1,85 +1,142 @@
|
||||
{{-- News and forum columns (migrated from legacy/home/news.blade.php) --}}
|
||||
{{-- News and forum columns --}}
|
||||
@php
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
@endphp
|
||||
|
||||
<div class="row news-row">
|
||||
<div class="col-sm-6">
|
||||
@forelse ($forumNews as $item)
|
||||
<div class="panel panel-skinbase effect2">
|
||||
<div class="panel-heading"><h4 class="panel-title">{{ $item->topic }}</h4></div>
|
||||
<div class="panel-body">
|
||||
<div class="text-muted news-head">
|
||||
Written by {{ $item->uname }} on {{ Carbon::parse($item->post_date)->format('j F Y \@ H:i') }}
|
||||
</div>
|
||||
{!! Str::limit(strip_tags($item->preview ?? ''), 240, '...') !!}
|
||||
<br>
|
||||
<a class="clearfix btn btn-xs btn-info" href="{{ route('forum.thread.show', ['thread' => $item->topic_id, 'slug' => Str::slug($item->topic ?? '')]) }}" title="{{ strip_tags($item->topic) }}">More</a>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p>No forum news available.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
@forelse ($ourNews as $news)
|
||||
<div class="panel panel-skinbase effect2">
|
||||
<div class="panel-heading"><h3 class="panel-title">{{ $news->headline }}</h3></div>
|
||||
<div class="panel-body">
|
||||
<div class="text-muted news-head">
|
||||
<i class="fa fa-user"></i> {{ $news->uname }}
|
||||
<i class="fa fa-calendar"></i> {{ Carbon::parse($news->create_date)->format('j F Y \@ H:i') }}
|
||||
<i class="fa fa-info"></i> {{ $news->category_name }}
|
||||
<i class="fa fa-info"></i> {{ $news->views }} reads
|
||||
<i class="fa fa-comment"></i> {{ $news->num_comments }} comments
|
||||
</div>
|
||||
<section class="px-6 pb-14 pt-2 md:px-10">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
|
||||
@if (!empty($news->picture))
|
||||
@php $nid = floor($news->news_id / 100); @endphp
|
||||
<div class="col-md-4">
|
||||
<img src="/archive/news/{{ $nid }}/{{ $news->picture }}" class="img-responsive" alt="{{ $news->headline }}">
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
{!! $news->preview !!}
|
||||
</div>
|
||||
@else
|
||||
{!! $news->preview !!}
|
||||
{{-- ── 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
|
||||
|
||||
<a class="clearfix btn btn-xs btn-info text-white" href="/news/{{ $news->news_id }}/{{ Str::slug($news->headline ?? '') }}">More</a>
|
||||
</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>
|
||||
</div>
|
||||
@empty
|
||||
<p>No news available.</p>
|
||||
@endforelse
|
||||
|
||||
{{-- Site info --}}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Info</strong></div>
|
||||
<div class="panel-body">
|
||||
<h4>Photography, Wallpapers and Skins... Thats Skinbase</h4>
|
||||
<p>Skinbase is the site dedicated to <strong>Photography</strong>, <strong>Wallpapers</strong> and <strong>Skins</strong> for <u>popular applications</u> for every major operating system like Windows, Mac OS X, Linux, iOS and Android</p>
|
||||
<em>Our members every day uploads new artworks to our site, so don't hesitate and check Skinbase frequently for updates. We also have forum where you can discuss with other members with anything.</em>
|
||||
<p>On the site toolbar you can click on Categories and start browsing our atwork (<i>photo</i>, <i>desktop themes</i>, <i>pictures</i>) and of course you can <u>download</u> them for free!</p>
|
||||
<p>We are also active on all major <b>social</b> sites, find us there too</p>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
{{-- Latest forum activity --}}
|
||||
<div class="panel panel-default activity-panel">
|
||||
<div class="panel-heading"><strong>Latest Forum Activity</strong></div>
|
||||
<div class="panel-body">
|
||||
<div class="list-group effect2">
|
||||
{{-- ── 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 class="list-group-item" href="{{ route('forum.thread.show', ['thread' => $topic->topic_id, 'slug' => Str::slug($topic->topic ?? '')]) }}">
|
||||
{{ $topic->topic }} <span class="badge badge-info">{{ $topic->numPosts }}</span>
|
||||
<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
|
||||
<p>No recent forum activity.</p>
|
||||
<div class="px-4 py-5 text-sm text-white/40 text-center">No recent forum activity.</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>{{-- end right column --}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
|
||||
@php($gridV2 = request()->query('grid') === 'v2')
|
||||
@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;
|
||||
@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" data-nova-gallery data-gallery-type="home-uploads">
|
||||
<div class="{{ $gridV2 ? 'gallery' : 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6' }}" data-gallery-grid>
|
||||
<div class="{{ ($gridV2 ?? false) ? 'gallery' : 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6' }}" data-gallery-grid>
|
||||
@forelse($latestUploads as $upload)
|
||||
<x-artwork-card :art="$upload" />
|
||||
@empty
|
||||
@@ -24,7 +41,7 @@
|
||||
</section>
|
||||
|
||||
@push('styles')
|
||||
@if(! $gridV2)
|
||||
@if(! ($gridV2 ?? false))
|
||||
<style>
|
||||
[data-nova-gallery].is-enhanced [data-gallery-grid] {
|
||||
display: grid;
|
||||
|
||||
Reference in New Issue
Block a user