optimizations

This commit is contained in:
2026-03-28 19:15:39 +01:00
parent 0b25d9570a
commit cab4fbd83e
509 changed files with 1016804 additions and 1605 deletions

View File

@@ -24,6 +24,49 @@
</x-slot>
</x-nova-page-header>
@php
$cacheTone = match ($feed_meta['cache_status'] ?? null) {
'hit' => 'text-emerald-200 ring-emerald-400/30 bg-emerald-500/12',
'stale' => 'text-amber-200 ring-amber-400/30 bg-amber-500/12',
default => 'text-sky-100 ring-sky-300/30 bg-sky-500/12',
};
$generatedAt = !empty($feed_meta['generated_at']) ? \Illuminate\Support\Carbon::parse($feed_meta['generated_at'])->diffForHumans() : null;
@endphp
<section class="px-6 md:px-10">
<div class="grid gap-3 rounded-[1.6rem] border border-white/8 bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.14),_transparent_42%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.88))] p-5 shadow-[0_18px_60px_rgba(2,6,23,0.38)] md:grid-cols-[minmax(0,1fr)_auto] md:items-center">
<div class="space-y-2">
<p class="text-[0.7rem] font-semibold uppercase tracking-[0.28em] text-sky-200/70">Personalized discovery</p>
<p class="max-w-3xl text-sm leading-6 text-slate-300">
This feed now runs on the same recommendation engine as the API, so your views and clicks on this page can refine what shows up next.
</p>
</div>
<div class="flex flex-wrap items-center gap-2 text-xs text-slate-200/85">
<span class="inline-flex items-center gap-2 rounded-full ring-1 ring-white/12 bg-white/6 px-3 py-1.5">
<span class="text-slate-400">Model</span>
<span>{{ $feed_meta['algo_version'] ?? 'n/a' }}</span>
</span>
<span class="inline-flex items-center gap-2 rounded-full ring-1 px-3 py-1.5 {{ $cacheTone }}">
<span class="text-slate-300/70">Cache</span>
<span>{{ str_replace(['-', '_'], ' ', $feed_meta['cache_status'] ?? 'unknown') }}</span>
</span>
@if (!empty($feed_meta['total_candidates']))
<span class="inline-flex items-center gap-2 rounded-full ring-1 ring-white/12 bg-white/6 px-3 py-1.5">
<span class="text-slate-400">Candidates</span>
<span>{{ number_format((int) $feed_meta['total_candidates']) }}</span>
</span>
@endif
@if ($generatedAt)
<span class="inline-flex items-center gap-2 rounded-full ring-1 ring-white/12 bg-white/6 px-3 py-1.5">
<span class="text-slate-400">Refreshed</span>
<span>{{ $generatedAt }}</span>
</span>
@endif
</div>
</div>
</section>
{{-- ── Artwork grid (React MasonryGallery) ── --}}
@php
$galleryArtworks = $artworks->map(fn ($art) => [
@@ -32,13 +75,20 @@
'thumb' => $art->thumb_url ?? null,
'thumb_srcset' => $art->thumb_srcset ?? null,
'uname' => $art->uname ?? '',
'username' => $art->uname ?? '',
'username' => $art->username ?? '',
'avatar_url' => $art->avatar_url ?? null,
'published_at' => $art->published_at ?? null,
'content_type_name' => $art->content_type_name ?? '',
'category_name' => $art->category_name ?? '',
'category_slug' => $art->category_slug ?? '',
'slug' => $art->slug ?? '',
'url' => $art->url ?? null,
'width' => $art->width ?? null,
'height' => $art->height ?? null,
'recommendation_source' => $art->recommendation_source ?? 'mixed',
'recommendation_reason' => $art->recommendation_reason ?? 'Picked for you',
'recommendation_score' => $art->recommendation_score,
'recommendation_algo_version' => $art->recommendation_algo_version ?? ($feed_meta['algo_version'] ?? null),
])->values();
@endphp
<section class="px-6 pt-8 md:px-10">
@@ -47,6 +97,8 @@
data-artworks="{{ json_encode($galleryArtworks) }}"
data-gallery-type="for-you"
data-cursor-endpoint="{{ route('discover.for-you') }}"
data-discovery-endpoint="{{ route('api.discovery.events.store') }}"
data-algo-version="{{ $feed_meta['algo_version'] ?? '' }}"
@if (!empty($next_cursor)) data-next-cursor="{{ $next_cursor }}" @endif
data-limit="40"
class="min-h-32"

View File

@@ -7,6 +7,13 @@
]);
@endphp
@php
$followingActivity = collect($following_activity ?? []);
$networkTrending = collect($network_trending ?? []);
$suggestedUsers = collect($suggested_users ?? $fallback_creators ?? []);
$fallbackTrending = collect($fallback_trending ?? []);
@endphp
@section('content')
<x-nova-page-header
@@ -23,9 +30,128 @@
</x-slot>
</x-nova-page-header>
@if (($section ?? null) === 'following')
<section class="px-6 pt-2 md:px-10">
@if (!empty($empty))
<div class="rounded-[32px] border border-white/10 bg-[linear-gradient(135deg,rgba(56,189,248,0.08),rgba(255,255,255,0.03),rgba(249,115,22,0.06))] p-6 shadow-[0_20px_60px_rgba(2,6,23,0.24)]">
<div class="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
<div>
<p class="text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200/80">Personalized following feed</p>
<h2 class="mt-2 text-3xl font-semibold tracking-[-0.03em] text-white">Your network starts here</h2>
<p class="mt-3 max-w-2xl text-sm leading-relaxed text-slate-300">Follow a few creators to unlock a feed made of their newest art, social activity, and rising work from around your network.</p>
</div>
<div class="flex flex-wrap gap-2">
<a href="/discover/trending" class="inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.04] px-4 py-2.5 text-sm font-medium text-slate-200 transition-colors hover:bg-white/[0.08]">
<i class="fa-solid fa-fire fa-fw"></i>
Explore trending
</a>
<a href="/feed/following" class="inline-flex items-center gap-2 rounded-2xl border border-sky-400/20 bg-sky-500/10 px-4 py-2.5 text-sm font-medium text-sky-200 transition-colors hover:bg-sky-500/15">
<i class="fa-solid fa-newspaper fa-fw"></i>
Open post feed
</a>
</div>
</div>
</div>
@endif
<div class="mt-6 grid gap-6 xl:grid-cols-[minmax(0,1.6fr)_minmax(0,1fr)]">
<div class="rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]">
<div class="mb-4 flex items-center justify-between gap-4">
<div>
<p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-500">Activity from people you follow</p>
<h3 class="mt-1 text-xl font-semibold text-white">Network activity</h3>
</div>
<a href="/community/activity?filter=following" class="text-sm text-sky-300/80 transition-colors hover:text-sky-200">View all</a>
</div>
<div class="space-y-3">
@forelse ($followingActivity as $activity)
<div class="rounded-2xl border border-white/[0.06] bg-white/[0.02] px-4 py-3">
<div class="flex items-start gap-3">
<img src="{{ data_get($activity, 'user.avatar_url') ?: '/images/avatar_default.webp' }}" alt="{{ data_get($activity, 'user.username') }}" class="h-10 w-10 rounded-full object-cover ring-1 ring-white/10" loading="lazy" />
<div class="min-w-0 flex-1">
<p class="text-sm font-semibold text-white">{{ data_get($activity, 'user.username') ? '@' . data_get($activity, 'user.username') : data_get($activity, 'user.name', 'Creator') }}</p>
<p class="mt-1 text-sm text-slate-300">
@if (data_get($activity, 'type') === 'follow')
started following {{ data_get($activity, 'target_user.username') ? '@' . data_get($activity, 'target_user.username') : 'another creator' }}
@elseif (data_get($activity, 'type') === 'upload')
published <a href="{{ data_get($activity, 'artwork.url') }}" class="text-sky-300 hover:text-sky-200">{{ data_get($activity, 'artwork.title', 'a new artwork') }}</a>
@elseif (in_array(data_get($activity, 'type'), ['comment', 'reply'], true))
{{ data_get($activity, 'type') === 'reply' ? 'replied on' : 'commented on' }} <a href="{{ data_get($activity, 'artwork.url') }}" class="text-sky-300 hover:text-sky-200">{{ data_get($activity, 'artwork.title', 'an artwork') }}</a>
@else
{{ ucfirst(str_replace('_', ' ', (string) data_get($activity, 'type', 'activity'))) }}
@endif
</p>
<p class="mt-1 text-xs text-slate-500">{{ data_get($activity, 'time_ago') ?: data_get($activity, 'created_at') }}</p>
</div>
</div>
</div>
@empty
<div class="rounded-2xl border border-dashed border-white/10 bg-white/[0.02] px-4 py-8 text-center text-sm text-slate-400">Follow activity will appear here as your network starts moving.</div>
@endforelse
</div>
</div>
<div class="space-y-6">
<div class="rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]">
<div class="mb-4">
<p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-500">Trending in your network</p>
<h3 class="mt-1 text-xl font-semibold text-white">Network highlights</h3>
</div>
<div class="space-y-3">
@foreach (($empty ?? false) ? $fallbackTrending->take(4) : $networkTrending->take(4) as $item)
@php
$itemId = (int) data_get($item, 'id', 0);
$itemSlug = (string) data_get($item, 'slug', '');
$itemUrl = $itemSlug !== '' && $itemId > 0
? route('art.show', ['id' => $itemId, 'slug' => $itemSlug])
: data_get($item, 'url', '#');
$itemThumb = data_get($item, 'thumb_url') ?: data_get($item, 'thumb') ?: data_get($item, 'thumbnail_url') ?: '/images/placeholder.jpg';
$itemTitle = data_get($item, 'title') ?: data_get($item, 'name', 'Artwork');
@endphp
<a href="{{ $itemUrl }}" class="flex items-center gap-3 rounded-2xl border border-white/[0.06] bg-white/[0.02] p-3 transition-colors hover:bg-white/[0.04]">
<img src="{{ $itemThumb }}" alt="{{ $itemTitle }}" class="h-16 w-16 rounded-xl object-cover" loading="lazy" />
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-semibold text-white">{{ $itemTitle ?: 'Untitled artwork' }}</p>
<p class="truncate text-xs text-slate-400">{{ data_get($item, 'author.username') ? '@' . data_get($item, 'author.username') : data_get($item, 'username', data_get($item, 'uname')) }}</p>
@if (is_array($item) && isset($item['stats']))
<p class="mt-1 text-[11px] text-slate-500">{{ number_format((int) data_get($item, 'stats.favorites', 0)) }} favourites · {{ number_format((int) data_get($item, 'stats.views', 0)) }} views</p>
@endif
</div>
</a>
@endforeach
</div>
</div>
<div class="rounded-[28px] border border-white/10 bg-white/[0.03] p-5 shadow-[0_20px_50px_rgba(2,6,23,0.18)]">
<div class="mb-4">
<p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-500">Suggested creators</p>
<h3 class="mt-1 text-xl font-semibold text-white">Who to follow next</h3>
</div>
<div class="space-y-3">
@foreach ($suggestedUsers->take(4) as $userCard)
<a href="{{ $userCard['profile_url'] ?? ('/@' . strtolower((string) ($userCard['username'] ?? ''))) }}" class="flex items-start gap-3 rounded-2xl border border-white/[0.06] bg-white/[0.02] p-3 transition-colors hover:bg-white/[0.04]">
<img src="{{ $userCard['avatar_url'] ?? '/images/avatar_default.webp' }}" alt="{{ $userCard['username'] ?? 'creator' }}" class="h-10 w-10 rounded-full object-cover ring-1 ring-white/10" loading="lazy" />
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-semibold text-white">{{ $userCard['name'] ?? $userCard['username'] ?? 'Creator' }}</p>
<p class="truncate text-xs text-slate-500">@{{ $userCard['username'] ?? 'creator' }}</p>
<p class="mt-1 text-xs text-slate-400">{{ data_get($userCard, 'context.follower_overlap.label') ?: data_get($userCard, 'context.shared_following.label') ?: ($userCard['reason'] ?? 'Recommended for you') }}</p>
</div>
</a>
@endforeach
</div>
</div>
</div>
</div>
</section>
@endif
{{-- ── Artwork grid (React MasonryGallery) ── --}}
@php
$galleryArtworks = collect($artworks->items())->map(fn ($art) => [
$galleryItems = method_exists($artworks, 'items') ? $artworks->items() : (is_iterable($artworks) ? $artworks : []);
$galleryArtworks = collect($galleryItems)->map(fn ($art) => [
'id' => $art->id,
'name' => $art->name ?? null,
'thumb' => $art->thumb_url ?? $art->thumb ?? null,