feat: add tag discovery analytics and reporting
This commit is contained in:
@@ -69,6 +69,8 @@
|
||||
$rankApiEndpoint = '/api/rank/global';
|
||||
}
|
||||
}
|
||||
|
||||
$tagContext = ($gallery_type ?? null) === 'tag' ? ($tag_context ?? null) : null;
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
@@ -76,13 +78,15 @@
|
||||
@php Banner::ShowResponsiveAd(); @endphp
|
||||
|
||||
@php
|
||||
$browseSection = isset($contentType) && $contentType ? strtolower((string) $contentType->slug) : 'artworks';
|
||||
$browseSection = $gallery_nav_section
|
||||
?? (isset($contentType) && $contentType ? strtolower((string) $contentType->slug) : (($gallery_type ?? null) === 'tag' ? 'tags' : 'artworks'));
|
||||
$browseIconMap = [
|
||||
'artworks' => 'fa-border-all',
|
||||
'photography' => 'fa-camera',
|
||||
'wallpapers' => 'fa-desktop',
|
||||
'skins' => 'fa-layer-group',
|
||||
'other' => 'fa-folder-open',
|
||||
'tags' => 'fa-tags',
|
||||
];
|
||||
$browseIcon = $browseIconMap[$browseSection] ?? 'fa-border-all';
|
||||
@endphp
|
||||
@@ -94,39 +98,141 @@
|
||||
<main class="w-full">
|
||||
|
||||
{{-- ── Hero header (discover-style) ── --}}
|
||||
<header class="px-6 pt-10 pb-7 md:px-10 border-b border-white/[0.06] bg-gradient-to-b from-sky-500/[0.04] to-transparent">
|
||||
@php
|
||||
$headerBreadcrumbs = collect();
|
||||
|
||||
if (($gallery_type ?? null) === 'browse') {
|
||||
$headerBreadcrumbs = collect([
|
||||
(object) ['name' => 'Browse', 'url' => '/browse'],
|
||||
]);
|
||||
} elseif (isset($contentType) && $contentType) {
|
||||
$headerBreadcrumbs = collect([
|
||||
(object) ['name' => 'Browse', 'url' => '/browse'],
|
||||
(object) ['name' => $contentType->name, 'url' => '/' . strtolower($contentType->slug)],
|
||||
]);
|
||||
|
||||
if (($gallery_type ?? null) === 'category' && isset($breadcrumbs) && $breadcrumbs->isNotEmpty()) {
|
||||
$headerBreadcrumbs = $breadcrumbs;
|
||||
}
|
||||
} elseif (isset($breadcrumbs) && $breadcrumbs->isNotEmpty()) {
|
||||
$headerBreadcrumbs = $breadcrumbs;
|
||||
}
|
||||
@endphp
|
||||
|
||||
<x-nova-page-header
|
||||
section="{{ ($gallery_type ?? null) === 'tag' ? 'Tags' : 'Browse' }}"
|
||||
:title="$hero_title ?? 'Browse Artworks'"
|
||||
:icon="$browseIcon"
|
||||
:breadcrumbs="$headerBreadcrumbs"
|
||||
:description="$hero_description ?? null"
|
||||
actionsClass="lg:pt-8"
|
||||
>
|
||||
<x-slot name="actions">
|
||||
@include('gallery._browse_nav', ['section' => $browseSection, 'includeTags' => ($gallery_type ?? null) === 'tag'])
|
||||
</x-slot>
|
||||
</x-nova-page-header>
|
||||
|
||||
@if($tagContext)
|
||||
<section class="px-6 pt-8 md:px-10">
|
||||
@php
|
||||
$headerBreadcrumbs = collect(array_filter([
|
||||
isset($contentType) && $contentType ? (object) ['name' => 'Explore', 'url' => '/explore'] : null,
|
||||
isset($contentType) && $contentType ? (object) ['name' => $contentType->name, 'url' => '/explore/' . strtolower($contentType->slug)] : (object) ['name' => 'Explore', 'url' => '/explore'],
|
||||
...(($gallery_type ?? null) === 'category' && isset($breadcrumbs) ? $breadcrumbs->all() : []),
|
||||
]));
|
||||
$topCompanionTag = collect($tagContext['related_tags'] ?? [])->first();
|
||||
@endphp
|
||||
<div class="overflow-hidden rounded-[1.75rem] border border-white/[0.08] bg-[radial-gradient(circle_at_top_left,_rgba(56,189,248,0.16),_transparent_34%),linear-gradient(135deg,rgba(11,17,27,0.96),rgba(10,16,24,0.88))] shadow-[0_20px_70px_rgba(3,7,18,0.24)]">
|
||||
<div class="grid gap-6 p-6 md:p-7 xl:grid-cols-[minmax(0,1.35fr)_minmax(300px,0.85fr)] xl:items-start">
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-wrap items-center gap-3 text-sm text-white/56">
|
||||
<span class="inline-flex items-center gap-2 rounded-full border border-sky-400/20 bg-sky-400/10 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-sky-200">
|
||||
<span class="h-2 w-2 rounded-full bg-sky-300"></span>
|
||||
Tag feed
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.05] px-3 py-1 text-xs font-medium text-white/60">
|
||||
Sorted by {{ $tagContext['current_sort_label'] ?? 'Most viewed' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||
<div class="max-w-3xl">
|
||||
<p class="text-xs font-semibold uppercase tracking-widest text-white/30 mb-1">Browse</p>
|
||||
<h1 class="text-3xl font-bold text-white leading-tight flex items-center gap-3">
|
||||
<i class="fa-solid {{ $browseIcon }} text-sky-400 text-2xl"></i>
|
||||
{{ $hero_title ?? 'Browse Artworks' }}
|
||||
</h1>
|
||||
@if(!empty($hero_description))
|
||||
<p class="mt-1 text-sm text-white/50">{!! $hero_description !!}</p>
|
||||
@endif
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<h2 class="text-2xl font-semibold tracking-tight text-white md:text-3xl">
|
||||
#{{ $tagContext['slug'] ?? $tagContext['name'] }}
|
||||
</h2>
|
||||
<p class="max-w-3xl text-sm leading-6 text-white/62 md:text-base">
|
||||
A focused feed for artwork tied to this theme. Use the ranking tabs to switch between momentum, recency, and quality without leaving the tag context.
|
||||
</p>
|
||||
@if($topCompanionTag && isset($topCompanionTag->shared_artworks_count))
|
||||
<a href="{{ route('tags.show', $topCompanionTag->slug) }}" data-tag-analytics-link data-tag-analytics-surface="top_companion" data-tag-analytics-tag="{{ $topCompanionTag->slug }}" data-tag-analytics-source-tag="{{ $tagContext['slug'] ?? '' }}" data-tag-analytics-position="1" class="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.05] px-3.5 py-2 text-sm text-white/68 transition hover:border-sky-400/28 hover:bg-sky-400/[0.08] hover:text-white">
|
||||
<span class="text-sky-200">Top companion</span>
|
||||
<span class="font-medium text-white">#{{ $topCompanionTag->name }}</span>
|
||||
<span class="text-white/34">{{ number_format($topCompanionTag->shared_artworks_count) }} shared artworks</span>
|
||||
@if(isset($topCompanionTag->transition_clicks) && (int) $topCompanionTag->transition_clicks > 0)
|
||||
<span class="text-emerald-200">{{ number_format((int) $topCompanionTag->transition_clicks) }} recent clicks</span>
|
||||
@endif
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-start gap-3 lg:items-end">
|
||||
<div class="hidden lg:flex lg:justify-end">
|
||||
@include('components.breadcrumbs', ['breadcrumbs' => $headerBreadcrumbs])
|
||||
@if(collect($tagContext['related_tags'] ?? [])->isNotEmpty())
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-white/38">Related tags</p>
|
||||
<div class="mt-3 flex flex-wrap gap-2.5">
|
||||
@foreach($tagContext['related_tags'] as $relatedIndex => $relatedTag)
|
||||
<a href="{{ route('tags.show', $relatedTag->slug) }}" data-tag-analytics-link data-tag-analytics-surface="related_chip" data-tag-analytics-tag="{{ $relatedTag->slug }}" data-tag-analytics-source-tag="{{ $tagContext['slug'] ?? '' }}" data-tag-analytics-position="{{ $relatedIndex + 1 }}" class="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.06] px-3.5 py-2 text-sm text-white/72 transition hover:border-sky-400/28 hover:bg-sky-400/[0.08] hover:text-white">
|
||||
<span>#{{ $relatedTag->name }}</span>
|
||||
<span class="text-xs text-white/36">
|
||||
{{ number_format($relatedTag->shared_artworks_count ?? $relatedTag->usage_count) }}
|
||||
{{ isset($relatedTag->shared_artworks_count) ? 'shared' : 'uses' }}
|
||||
</span>
|
||||
@if(isset($relatedTag->transition_clicks) && (int) $relatedTag->transition_clicks > 0)
|
||||
<span class="text-xs text-emerald-200">{{ number_format((int) $relatedTag->transition_clicks) }} recent</span>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid gap-3 md:grid-cols-3">
|
||||
@foreach(collect($tagContext['related_tags'])->take(3)->values() as $clusterIndex => $clusterTag)
|
||||
<a href="{{ route('tags.show', $clusterTag->slug) }}" data-tag-analytics-link data-tag-analytics-surface="related_cluster" data-tag-analytics-tag="{{ $clusterTag->slug }}" data-tag-analytics-source-tag="{{ $tagContext['slug'] ?? '' }}" data-tag-analytics-position="{{ $clusterIndex + 1 }}" class="rounded-2xl border border-white/10 bg-white/[0.04] p-4 transition hover:border-sky-400/28 hover:bg-sky-400/[0.08]">
|
||||
<p class="text-[11px] font-semibold uppercase tracking-[0.2em] text-white/34">Related cluster</p>
|
||||
<h3 class="mt-2 text-base font-semibold text-white">#{{ $clusterTag->name }}</h3>
|
||||
<p class="mt-2 text-sm text-white/52">
|
||||
{{ number_format($clusterTag->shared_artworks_count ?? 0) }} shared artworks with this tag feed.
|
||||
</p>
|
||||
@if(isset($clusterTag->transition_clicks) && (int) $clusterTag->transition_clicks > 0)
|
||||
<p class="mt-2 text-xs font-medium uppercase tracking-[0.18em] text-emerald-200">{{ number_format((int) $clusterTag->transition_clicks) }} recent clicks</p>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@include('gallery._browse_nav', ['section' => $browseSection])
|
||||
|
||||
<aside class="grid gap-3 sm:grid-cols-3 xl:grid-cols-1">
|
||||
<div class="rounded-2xl border border-white/10 bg-white/[0.05] p-4 backdrop-blur-sm">
|
||||
<p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-white/40">Artworks</p>
|
||||
<p class="mt-3 text-3xl font-semibold text-white">{{ number_format((int) ($tagContext['artworks_total'] ?? 0)) }}</p>
|
||||
<p class="mt-2 text-sm text-white/45">Public approved artworks currently in this feed.</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-white/10 bg-white/[0.05] p-4 backdrop-blur-sm">
|
||||
<p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-white/40">Total uses</p>
|
||||
<p class="mt-3 text-3xl font-semibold text-white">{{ number_format((int) ($tagContext['usage_count'] ?? 0)) }}</p>
|
||||
<p class="mt-2 text-sm text-white/45">How often this tag is attached across the catalog.</p>
|
||||
</div>
|
||||
<div class="rounded-2xl border border-white/10 bg-white/[0.05] p-4 backdrop-blur-sm">
|
||||
<p class="text-[11px] font-semibold uppercase tracking-[0.22em] text-white/40">Feed tools</p>
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
<a href="{{ route('tags.index') }}" class="inline-flex items-center gap-2 rounded-xl border border-white/10 bg-black/20 px-3 py-2 text-sm font-medium text-white/72 transition hover:bg-white/[0.08] hover:text-white">
|
||||
All tags
|
||||
</a>
|
||||
<a href="{{ $tagContext['rss_url'] ?? '#' }}" class="inline-flex items-center gap-2 rounded-xl border border-white/10 bg-black/20 px-3 py-2 text-sm font-medium text-white/72 transition hover:bg-white/[0.08] hover:text-white">
|
||||
RSS
|
||||
</a>
|
||||
</div>
|
||||
<p class="mt-3 text-sm text-white/45">Jump back to discovery or subscribe to this tag feed.</p>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 lg:hidden">
|
||||
@include('components.breadcrumbs', ['breadcrumbs' => $headerBreadcrumbs])
|
||||
</div>
|
||||
</header>
|
||||
</section>
|
||||
@endif
|
||||
|
||||
{{-- ═══════════════════════════════════════════════════════════════ --}}
|
||||
{{-- RANKING TABS --}}
|
||||
@@ -188,9 +294,11 @@
|
||||
@php
|
||||
$filterItems = $subcategories ?? collect();
|
||||
$activeFilterId = isset($category) ? ($category->id ?? null) : null;
|
||||
$categoryAllHref = isset($contentType) && $contentType
|
||||
? url('/' . $contentType->slug)
|
||||
: url('/browse');
|
||||
$categoryAllHref = isset($subcategory_parent) && $subcategory_parent && ($subcategory_parent->url ?? null)
|
||||
? url($subcategory_parent->url)
|
||||
: (isset($contentType) && $contentType
|
||||
? url('/' . $contentType->slug)
|
||||
: url('/browse'));
|
||||
$activeSortSlug = $activeTab !== 'trending' ? $activeTab : null;
|
||||
@endphp
|
||||
|
||||
@@ -382,7 +490,6 @@
|
||||
@push('scripts')
|
||||
@vite('resources/js/entry-masonry-gallery.jsx')
|
||||
@vite('resources/js/entry-pill-carousel.jsx')
|
||||
<script src="/js/legacy-gallery-init.js" defer></script>
|
||||
<script>
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
Reference in New Issue
Block a user