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

View File

@@ -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

View File

@@ -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; }

View File

@@ -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">

View File

@@ -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&apos;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>

View File

@@ -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;