Files
SkinbaseNova/resources/views/components/artwork-card.blade.php

248 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@props([
'art',
'loading' => 'lazy',
'fetchpriority' => null,
'imageSizes' => '(max-width: 640px) 50vw, (max-width: 1024px) 33vw, (max-width: 1536px) 25vw, 320px',
])
@php
if (isset($art) && (is_array($art) || $art instanceof Illuminate\Support\Collection || $art instanceof Illuminate\Database\Eloquent\Collection)) {
$first = null;
if (is_array($art) && !\Illuminate\Support\Arr::isAssoc($art)) {
$first = reset($art);
} elseif ($art instanceof Illuminate\Support\Collection || $art instanceof Illuminate\Database\Eloquent\Collection) {
$first = $art->first();
}
if ($first) {
$art = $first;
}
}
if (is_array($art) && \Illuminate\Support\Arr::isAssoc($art)) {
$art = (object) $art;
}
$title = trim((string) ($art->name ?? $art->title ?? 'Untitled artwork'));
$publisherType = (string) (data_get($art, 'publisher.type') ?? ($art->published_as_type ?? ''));
$isGroupPublisher = $publisherType === 'group';
$author = trim((string) (
($isGroupPublisher ? data_get($art, 'publisher.name') : null)
?? $art->uname
?? $art->author_name
?? $art->author
?? ($art->user->name ?? null)
?? ($art->user->username ?? null)
?? 'Skinbase'
));
$username = $isGroupPublisher
? ''
: trim((string) (
$art->username
?? ($art->user->username ?? null)
?? ''
));
$rawContentType = trim((string) (
$art->content_type_name
?? $art->content_type
?? $art->content_type_slug
?? ''
));
$contentType = match (strtolower($rawContentType)) {
'artworks', 'artwork' => 'Artwork',
'wallpapers', 'wallpaper' => 'Wallpaper',
'skins', 'skin' => 'Skin',
'photography', 'photo', 'photos' => 'Photography',
'other' => 'Other',
default => $rawContentType !== ''
?
Illuminate\Support\Str::title(str_replace(['-', '_'], ' ', $rawContentType))
: '',
};
$category = trim((string) ($art->category_name ?? $art->category ?? ''));
$avatarUserId = $art->user->id ?? $art->user_id ?? null;
$avatarHash = $art->user->profile->avatar_hash ?? $art->avatar_hash ?? null;
$avatarUrl = trim((string) (
($isGroupPublisher ? data_get($art, 'publisher.avatar_url') : null)
?? ($art->avatar_url ?? null)
?? ''
));
if ($avatarUrl === '') {
$avatarUrl = \App\Support\AvatarUrl::forUser((int) ($avatarUserId ?? 0), $avatarHash, 64);
}
$license = trim((string) ($art->license ?? 'Standard'));
$resolution = trim((string) ($art->resolution ?? ((isset($art->width, $art->height) && $art->width && $art->height) ? ($art->width . '×' . $art->height) : '')));
$safeInt = function ($value, $fallback = 0) {
if (is_numeric($value)) {
return (int) $value;
}
if (is_array($value)) {
return count($value);
}
if (is_object($value)) {
if (method_exists($value, 'count')) {
return (int) $value->count();
}
if ($value instanceof Countable) {
return (int) count($value);
}
}
return (int) $fallback;
};
$likes = $safeInt($art->likes ?? $art->favourites ?? 0);
$comments = $safeInt($art->comments_count ?? $art->comment_count ?? $art->comments ?? 0);
$thumbUrl = is_object($art) && method_exists($art, 'thumbUrl') ? $art->thumbUrl('md') : null;
$imgSrc = (string) ($art->thumb ?? $art->thumbnail_url ?? $thumbUrl ?? '/images/placeholder.jpg');
$imgSrcset = (string) ($art->thumb_srcset ?? $art->thumbnail_srcset ?? $imgSrc);
$imgAvifSrcset = (string) ($art->thumb_avif_srcset ?? $imgSrcset);
$imgWebpSrcset = (string) ($art->thumb_webp_srcset ?? $imgSrcset);
$imgSizes = trim((string) $imageSizes);
$resolveDimension = function ($value, string $field, $fallback) {
if (is_numeric($value)) {
return (int) $value;
}
if (is_array($value)) {
$current = reset($value);
return is_numeric($current) ? (int) $current : (int) $fallback;
}
if (is_object($value)) {
if (method_exists($value, 'first')) {
$first = $value->first();
if (is_object($first) && isset($first->{$field})) {
return (int) ($first->{$field} ?: $fallback);
}
}
if (isset($value->{$field})) {
return (int) $value->{$field};
}
}
return (int) $fallback;
};
// Use stored dimensions when available; otherwise leave ratio unconstrained
// so the thumbnail displays at its natural proportions (no 1:1 or 16:9 forcing).
$hasDimensions = ($art->width ?? 0) > 0 && ($art->height ?? 0) > 0;
$imgWidth = $hasDimensions ? max(1, $resolveDimension($art->width, 'width', 0)) : null;
$imgHeight = $hasDimensions ? max(1, $resolveDimension($art->height, 'height', 0)) : null;
$imgAspectRatio = $hasDimensions ? ($imgWidth . ' / ' . $imgHeight) : null;
$contentUrl = $imgSrc;
$cardUrl = (string) ($art->url ?? '');
if ($cardUrl === '' || $cardUrl === '#') {
if (isset($art->id) && is_numeric($art->id)) {
$cardUrl = '/art/' . (int) $art->id . '/' . \Illuminate\Support\Str::slug($title);
} else {
$cardUrl = '#';
}
}
$authorUrl = trim((string) (
($isGroupPublisher ? data_get($art, 'publisher.profile_url') : null)
?? ($art->profile_url ?? null)
?? ($username !== '' ? '/@' . strtolower($username) : '')
));
if ($authorUrl === '') {
$authorUrl = null;
}
$metaParts = [];
if ($contentType !== '') {
$metaParts[] = $contentType;
}
if ($category !== '') {
$metaParts[] = $category;
}
if ($resolution !== '') {
$metaParts[] = $resolution;
}
$maturity = data_get($art, 'maturity');
if (is_array($maturity)) {
$maturity = (object) $maturity;
}
$shouldHide = (bool) data_get($maturity, 'should_hide', false);
$shouldBlur = (bool) data_get($maturity, 'should_blur', false);
$isMatureArtwork = (bool) data_get($maturity, 'is_mature_effective', false);
@endphp
@unless($shouldHide)
<article class="nova-card gallery-item artwork" itemscope itemtype="https://schema.org/ImageObject"
data-art-id="{{ $art->id ?? '' }}"
data-art-url="{{ $cardUrl }}"
data-art-title="{{ e($title) }}"
data-art-img="{{ $imgSrc }}">
<meta itemprop="name" content="{{ $title }}">
<meta itemprop="contentUrl" content="{{ $contentUrl }}">
<meta itemprop="creator" content="{{ $author }}">
<meta itemprop="license" content="{{ $license }}">
<a href="{{ $cardUrl }}" itemprop="url" class="group relative block overflow-hidden rounded-2xl ring-1 ring-white/5 bg-black/20 shadow-lg shadow-black/40 transition-all duration-200 ease-out hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/70">
@if($category !== '')
<div class="absolute left-3 top-3 z-30 rounded-md bg-black/55 px-2 py-1 text-xs text-white backdrop-blur-sm">{{ $category }}</div>
@endif
@if($shouldBlur && $isMatureArtwork)
<div class="absolute right-3 top-3 z-30 rounded-md bg-amber-500/85 px-2 py-1 text-xs font-semibold text-black shadow-lg shadow-black/30 backdrop-blur-sm">Mature</div>
@endif
<div class="nova-card-media relative overflow-hidden bg-neutral-900"@if($imgAspectRatio) style="aspect-ratio: {{ $imgAspectRatio }};"@endif>
<div class="absolute inset-0 bg-gradient-to-br from-white/10 via-white/5 to-transparent pointer-events-none"></div>
<picture>
<source srcset="{{ $imgAvifSrcset }}" @if($imgSizes !== '') sizes="{{ $imgSizes }}" @endif type="image/avif">
<source srcset="{{ $imgWebpSrcset }}" @if($imgSizes !== '') sizes="{{ $imgSizes }}" @endif type="image/webp">
<img
src="{{ $imgSrc }}"
srcset="{{ $imgSrcset }}"
@if($imgSizes !== '') sizes="{{ $imgSizes }}" @endif
loading="{{ $loading }}"
decoding="{{ $loading === 'eager' ? 'sync' : 'async' }}"
@if($fetchpriority) fetchpriority="{{ $fetchpriority }}" @endif
@if($loading !== 'eager') data-blur-preview @endif
alt="{{ e($title) }}"
@if($imgWidth) width="{{ $imgWidth }}" @endif
@if($imgHeight) height="{{ $imgHeight }}" @endif
class="{{ $imgAspectRatio ? 'h-full w-full object-cover' : 'w-full h-auto' }} transition-[transform,filter] duration-300 ease-out {{ $shouldBlur ? 'blur-xl scale-[1.08]' : 'group-hover:scale-[1.04]' }}"
itemprop="thumbnailUrl"
/>
</picture>
@if($shouldBlur && $isMatureArtwork)
<div class="pointer-events-none absolute inset-0 z-10 bg-black/25"></div>
<div class="pointer-events-none absolute inset-x-0 bottom-0 z-20 bg-gradient-to-t from-black/85 via-black/45 to-transparent px-3 py-3 text-xs font-semibold uppercase tracking-[0.18em] text-white/90">
Mature content blurred
</div>
@endif
<div class="pointer-events-none absolute inset-x-0 bottom-0 z-20 bg-gradient-to-t from-black/80 via-black/40 to-transparent p-3 backdrop-blur-[2px] opacity-100 transition-opacity duration-200 md:opacity-0 md:group-hover:opacity-100 md:group-focus-visible:opacity-100">
<div class="truncate text-sm font-semibold text-white">{{ $title }}</div>
<div class="mt-1 flex items-start justify-between gap-3 text-xs text-white/80">
<span class="flex min-w-0 items-start gap-3">
<img src="{{ $avatarUrl }}" alt="Avatar of {{ e($author) }}" class="w-9 h-9 rounded-full object-cover">
<span class="min-w-0 flex-1">
<span class="block truncate">{{ $author }}@if($username !== '') <span class="text-white/60">{{ '@' . $username }}</span>@endif</span>
@if(!empty($metaParts))
<span class="mt-0.5 block truncate text-[11px] text-white/70">{{ implode(' • ', $metaParts) }}</span>
@endif
</span>
</span>
<span class="shrink-0"> {{ $likes }} · 💬 {{ $comments }}</span>
</div>
</div>
</div>
<span class="sr-only">{{ $title }} by {{ $author }}</span>
</a>
</article>
@endunless