Save workspace changes

This commit is contained in:
2026-04-18 17:02:56 +02:00
parent f02ea9a711
commit 87d60af5a9
4220 changed files with 1388603 additions and 1554 deletions

View File

@@ -0,0 +1,247 @@
@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