Refine SEO, uploads, and deploy handling

This commit is contained in:
2026-05-02 10:48:08 +02:00
parent b6be6ed2ac
commit a9dfa6ea11
97 changed files with 373 additions and 327 deletions

View File

@@ -30,15 +30,14 @@ final class UploadVisionSuggestController extends Controller
public function __invoke(int $id, Request $request): JsonResponse
{
if (! $this->vision->isEnabled()) {
return response()->json(['tags' => [], 'vision_enabled' => false]);
}
$artwork = Artwork::query()->findOrFail($id);
$this->authorizeOrNotFound($request->user(), $artwork);
$limit = (int) $request->query('limit', 10);
return response()->json($this->vision->suggestTags($artwork, $this->normalizer, $limit));
return response()->json([
'tags' => [],
'vision_enabled' => false,
'reason' => 'disabled',
]);
}
private function authorizeOrNotFound(mixed $user, Artwork $artwork): void

View File

@@ -108,7 +108,7 @@ class CollectionInsightsController extends Controller
'bulkActions' => route('settings.collections.bulk-actions'),
],
'seo' => [
'title' => 'Collections Dashboard — Skinbase Nova',
'title' => 'Collections Dashboard — Skinbase',
'description' => 'Overview of collection lifecycle, quality, activity, and upcoming collection campaigns.',
'canonical' => route('settings.collections.dashboard'),
'robots' => 'noindex,follow',
@@ -127,7 +127,7 @@ class CollectionInsightsController extends Controller
'historyUrl' => route('settings.collections.history', ['collection' => $collection->id]),
'dashboardUrl' => route('settings.collections.dashboard'),
'seo' => [
'title' => sprintf('%s Analytics — Skinbase Nova', $collection->title),
'title' => sprintf('%s Analytics — Skinbase', $collection->title),
'description' => sprintf('Analytics and performance history for the %s collection.', $collection->title),
'canonical' => route('settings.collections.analytics', ['collection' => $collection->id]),
'robots' => 'noindex,follow',
@@ -150,7 +150,7 @@ class CollectionInsightsController extends Controller
'analyticsUrl' => route('settings.collections.analytics', ['collection' => $collection->id]),
'restorePattern' => route('settings.collections.history.restore', ['collection' => $collection->id, 'history' => '__HISTORY__']),
'seo' => [
'title' => sprintf('%s History — Skinbase Nova', $collection->title),
'title' => sprintf('%s History — Skinbase', $collection->title),
'description' => sprintf('Audit history and lifecycle changes for the %s collection.', $collection->title),
'canonical' => route('settings.collections.history', ['collection' => $collection->id]),
'robots' => 'noindex,follow',

View File

@@ -92,7 +92,7 @@ class CollectionProgrammingController extends Controller
'surfaces' => route('settings.collections.surfaces.index'),
],
'seo' => [
'title' => 'Collection Programming — Skinbase Nova',
'title' => 'Collection Programming — Skinbase',
'description' => 'Staff programming tools for assignments, previews, eligibility diagnostics, and recommendation refreshes.',
'canonical' => route('staff.collections.programming'),
'robots' => 'noindex,follow',

View File

@@ -66,7 +66,7 @@ class CollectionSurfaceController extends Controller
'batchEditorial' => route('settings.collections.surfaces.batch-editorial'),
],
'seo' => [
'title' => 'Collection Surfaces - Skinbase Nova',
'title' => 'Collection Surfaces - Skinbase',
'description' => 'Staff tools for homepage, discovery, and campaign collection surfaces.',
'canonical' => route('settings.collections.surfaces.index'),
'robots' => 'noindex,follow',

View File

@@ -43,7 +43,7 @@ class FeaturedArtworkAdminController extends Controller
'forceHeroEnabled' => $this->hasForceHeroColumn(),
],
'seo' => [
'title' => 'Featured Artworks — Skinbase Nova',
'title' => 'Featured Artworks — Skinbase',
'description' => 'Editorial controls for homepage featured artworks and the current hero winner.',
'canonical' => route($routePrefix . 'main'),
'robots' => 'noindex,follow',

View File

@@ -40,7 +40,7 @@ final class StudioWorldController extends Controller
return Inertia::render('Studio/StudioWorldsIndex', [
'title' => 'Worlds',
'description' => 'Create and manage seasonal, event, and campaign destinations across Skinbase Nova.',
'description' => 'Create and manage seasonal, event, and campaign destinations across Skinbase.',
'listing' => $this->worlds->studioListing($request->only(['q', 'status', 'type', 'per_page'])),
'analytics' => $this->analytics->portfolioReport(),
'statusOptions' => [
@@ -435,7 +435,7 @@ final class StudioWorldController extends Controller
$payload = $this->worlds->publicShowPayload($world, $request->user(), true);
$seo = app(SeoFactory::class)->collectionPage(
$world->seo_title ?: ($world->title . ' — Skinbase Nova Preview'),
$world->seo_title ?: ($world->title . ' — Skinbase Preview'),
$world->seo_description ?: ($world->summary ?: $world->description ?: 'Preview world page'),
route('studio.worlds.preview', ['world' => $world]),
$world->ogImageUrl(),

View File

@@ -116,9 +116,9 @@ class ProfileCollectionController extends Controller
$seo = app(SeoFactory::class)->collectionPage(
$collection->is_featured
? sprintf('Featured: %s by %s — Skinbase Nova', $collection->title, $collection->displayOwnerName())
: sprintf('%s by %s — Skinbase Nova', $collection->title, $collection->displayOwnerName()),
$collection->summary ?: $collection->description ?: sprintf('Explore the %s collection by %s on Skinbase Nova.', $collection->title, $collection->displayOwnerName()),
? sprintf('Featured: %s by %s — Skinbase', $collection->title, $collection->displayOwnerName())
: sprintf('%s by %s — Skinbase', $collection->title, $collection->displayOwnerName()),
$collection->summary ?: $collection->description ?: sprintf('Explore the %s collection by %s on Skinbase.', $collection->title, $collection->displayOwnerName()),
$collectionPayload['public_url'],
$collectionPayload['cover_image'],
$collection->visibility === Collection::VISIBILITY_PUBLIC,
@@ -202,8 +202,8 @@ class ProfileCollectionController extends Controller
$seriesDescription = $seriesMeta['description'];
$seo = app(SeoFactory::class)->collectionListing(
sprintf('Series: %s — Skinbase Nova', $seriesKey),
sprintf('Explore the %s collection series on Skinbase Nova.', $seriesKey),
sprintf('Series: %s — Skinbase', $seriesKey),
sprintf('Explore the %s collection series on Skinbase.', $seriesKey),
route('collections.series.show', ['seriesKey' => $seriesKey])
)->toArray();

View File

@@ -155,8 +155,8 @@ class SavedCollectionController extends Controller
'libraryUrl' => route('me.saved.collections'),
'browseUrl' => route('collections.featured'),
'seo' => [
'title' => $activeList ? sprintf('%s — Saved Collections — Skinbase Nova', $activeList->title) : 'Saved Collections — Skinbase Nova',
'description' => $activeList ? sprintf('Saved collections in the %s list on Skinbase Nova.', $activeList->title) : 'Your saved collections on Skinbase Nova.',
'title' => $activeList ? sprintf('%s — Saved Collections — Skinbase', $activeList->title) : 'Saved Collections — Skinbase',
'description' => $activeList ? sprintf('Saved collections in the %s list on Skinbase.', $activeList->title) : 'Your saved collections on Skinbase.',
'canonical' => $activeList ? route('me.saved.collections.lists.show', ['listSlug' => $activeList->slug]) : route('me.saved.collections'),
'robots' => 'noindex,follow',
],

View File

@@ -18,7 +18,7 @@ final class AccountHelpPageController extends Controller
$seo = app(SeoFactory::class)
->collectionPage(
'Account Settings Help — Skinbase',
'Learn how account settings, profile settings, email changes, password care, and creator preferences work on Skinbase Nova.',
'Learn how account settings, profile settings, email changes, password care, and creator preferences work on Skinbase.',
$canonical,
)
->toArray();

View File

@@ -18,7 +18,7 @@ final class AuthHelpPageController extends Controller
$seo = app(SeoFactory::class)
->collectionPage(
'Signup and Login Help — Skinbase',
'Learn how signup, login, password recovery, verification, and account access work on Skinbase Nova, with clear guidance for common access problems and practical next steps.',
'Learn how signup, login, password recovery, verification, and account access work on Skinbase, with clear guidance for common access problems and practical next steps.',
$canonical,
)
->toArray();
@@ -27,7 +27,7 @@ final class AuthHelpPageController extends Controller
return Inertia::render('Help/AuthHelpPage', [
'title' => 'Signup & Login Help',
'description' => 'Get clear help for account creation, sign-in, password recovery, verification basics, and common access problems on Skinbase Nova.',
'description' => 'Get clear help for account creation, sign-in, password recovery, verification basics, and common access problems on Skinbase.',
'seo' => $seo,
'links' => [
'help_home' => route('help'),

View File

@@ -184,7 +184,7 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller
(object) ['name' => 'Explore', 'url' => '/browse'],
(object) ['name' => $contentType->name, 'url' => '/' . $contentSlug],
]),
'page_title' => $contentType->name . ' Skinbase Nova',
'page_title' => $contentType->name . ' Skinbase',
'page_meta_description' => $contentType->description ?? ('Discover the best ' . $contentType->name . ' artworks on Skinbase'),
'page_meta_keywords' => strtolower($contentType->slug) . ', skinbase, artworks, wallpapers, skins, photography',
'page_canonical' => $seo['canonical'],
@@ -264,7 +264,7 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller
'hero_title' => $category->name,
'hero_description' => $category->description ?? ($contentType->name . ' artworks on Skinbase.'),
'breadcrumbs' => $breadcrumbs,
'page_title' => $category->name . ' Skinbase Nova',
'page_title' => $category->name . ' Skinbase',
'page_meta_description' => $category->description ?? ('Discover the best ' . $category->name . ' ' . $contentType->name . ' artworks on Skinbase'),
'page_meta_keywords' => strtolower($contentType->slug) . ', skinbase, artworks, wallpapers, skins, photography',
'page_canonical' => $seo['canonical'],

View File

@@ -18,7 +18,7 @@ final class CardsHelpPageController extends Controller
$seo = app(SeoFactory::class)
->collectionPage(
'Cards Help — Skinbase',
'Learn what Cards are on Skinbase Nova, how they differ from artworks, posts, and collections, and how to create, publish, and use them effectively in personal and Group workflows.',
'Learn what Cards are on Skinbase, how they differ from artworks, posts, and collections, and how to create, publish, and use them effectively in personal and Group workflows.',
$canonical,
)
->toArray();
@@ -27,7 +27,7 @@ final class CardsHelpPageController extends Controller
return Inertia::render('Help/CardsHelpPage', [
'title' => 'Cards Help',
'description' => 'Understand Cards as a distinct creative format on Skinbase Nova, with guidance for creation, publishing, ownership, design quality, and real-world use cases.',
'description' => 'Understand Cards as a distinct creative format on Skinbase, with guidance for creation, publishing, ownership, design quality, and real-world use cases.',
'seo' => $seo,
'links' => [
'help_home' => route('help'),

View File

@@ -52,9 +52,9 @@ class CollectionDiscoveryController extends Controller
$results = $this->search->publicSearch($filters, (int) config('collections.v5.search.public_per_page', 18));
$seo = app(SeoFactory::class)->collectionListing(
'Search Collections — Skinbase Nova',
'Search Collections — Skinbase',
filled($filters['q'] ?? null)
? sprintf('Search results for "%s" across public Skinbase Nova collections.', $filters['q'])
? sprintf('Search results for "%s" across public Skinbase collections.', $filters['q'])
: 'Browse public collections using filters for category, style, theme, color, quality tier, freshness, and programming metadata.',
$request->fullUrl(),
null,
@@ -65,7 +65,7 @@ class CollectionDiscoveryController extends Controller
'eyebrow' => 'Search',
'title' => 'Search collections',
'description' => filled($filters['q'] ?? null)
? sprintf('Search results for "%s" across public Skinbase Nova collections.', $filters['q'])
? sprintf('Search results for "%s" across public Skinbase collections.', $filters['q'])
: 'Browse public collections using filters for category, style, theme, color, quality tier, freshness, and programming metadata.',
'seo' => $seo,
'collections' => $this->collections->mapCollectionCardPayloads($results->items(), false, $request->user()),
@@ -100,7 +100,7 @@ class CollectionDiscoveryController extends Controller
viewer: $request->user(),
eyebrow: 'Discovery',
title: 'Featured collections',
description: 'A rotating set of standout galleries from across Skinbase Nova. Some are meticulously hand-sequenced. Others are smart collections that stay fresh as the creator publishes new work.',
description: 'A rotating set of standout galleries from across Skinbase. Some are meticulously hand-sequenced. Others are smart collections that stay fresh as the creator publishes new work.',
collections: $featuredCollections->isNotEmpty() ? $featuredCollections : $this->discovery->publicFeaturedCollections((int) config('collections.discovery.featured_limit', 18)),
communityCollections: $this->discovery->publicCollectionsByType(Collection::TYPE_COMMUNITY, 6),
editorialCollections: $this->discovery->publicCollectionsByType(Collection::TYPE_EDITORIAL, 6),
@@ -204,7 +204,7 @@ class CollectionDiscoveryController extends Controller
abort_if(! $program || collect($landing['collections'])->isEmpty(), 404);
$seo = app(SeoFactory::class)->collectionListing(
sprintf('%s — Skinbase Nova', $program['label']),
sprintf('%s — Skinbase', $program['label']),
$program['description'],
route('collections.program.show', ['programKey' => $program['key']]),
)->toArray();
@@ -239,7 +239,7 @@ class CollectionDiscoveryController extends Controller
$campaign = null,
) {
$seo = app(SeoFactory::class)->collectionListing(
sprintf('%s — Skinbase Nova', $title),
sprintf('%s — Skinbase', $title),
$description,
url()->current(),
)->toArray();

View File

@@ -18,7 +18,7 @@ final class GroupFaqPageController extends Controller
$seo = app(SeoFactory::class)
->collectionPage(
'Groups FAQ — Skinbase',
'Fast answers to the most common Groups questions on Skinbase Nova, including roles, permissions, publishing, contributor credit, invites, workflows, and troubleshooting.',
'Fast answers to the most common Groups questions on Skinbase, including roles, permissions, publishing, contributor credit, invites, workflows, and troubleshooting.',
$canonical,
)
->toArray();
@@ -27,7 +27,7 @@ final class GroupFaqPageController extends Controller
return Inertia::render('Group/GroupFaqPage', [
'title' => 'Groups FAQ',
'description' => 'Quick answers about Groups, roles, permissions, publishing, contributor credit, invites, workflows, and troubleshooting on Skinbase Nova.',
'description' => 'Quick answers about Groups, roles, permissions, publishing, contributor credit, invites, workflows, and troubleshooting on Skinbase.',
'seo' => $seo,
'links' => [
'groups_directory' => route('groups.index'),

View File

@@ -18,7 +18,7 @@ final class GroupHelpPageController extends Controller
$seo = app(SeoFactory::class)
->collectionPage(
'Groups Guide, Help, and Best Practices — Skinbase',
'Learn how Groups work on Skinbase Nova, how shared publishing preserves contributor credit, and how to manage roles, releases, reviews, projects, and team workflows with confidence.',
'Learn how Groups work on Skinbase, how shared publishing preserves contributor credit, and how to manage roles, releases, reviews, projects, and team workflows with confidence.',
$canonical,
)
->toArray();
@@ -27,7 +27,7 @@ final class GroupHelpPageController extends Controller
return Inertia::render('Group/GroupHelpPage', [
'title' => 'Groups Help & Guide',
'description' => 'Everything creators need to understand Groups, publish collaboratively, preserve contributor credit, and build a healthy shared identity on Skinbase Nova.',
'description' => 'Everything creators need to understand Groups, publish collaboratively, preserve contributor credit, and build a healthy shared identity on Skinbase.',
'seo' => $seo,
'links' => [
'groups_directory' => route('groups.index'),

View File

@@ -18,7 +18,7 @@ final class GroupQuickstartPageController extends Controller
$seo = app(SeoFactory::class)
->collectionPage(
'Groups Quickstart — Skinbase',
'A fast, creator-friendly Groups quickstart for Skinbase Nova. Learn when to use a Group, create one, invite members, and publish your first Group artwork with correct contributor credit.',
'A fast, creator-friendly Groups quickstart for Skinbase. Learn when to use a Group, create one, invite members, and publish your first Group artwork with correct contributor credit.',
$canonical,
)
->toArray();

View File

@@ -18,7 +18,7 @@ final class HelpCenterPageController extends Controller
$seo = app(SeoFactory::class)
->collectionPage(
'Help Center — Skinbase',
'Find help, guides, quickstarts, FAQs, and troubleshooting for Skinbase Nova, including Groups, Studio, Upload, Cards, Profile, and account access.',
'Find help, guides, quickstarts, FAQs, and troubleshooting for Skinbase, including Groups, Studio, Upload, Cards, Profile, and account access.',
$canonical,
)
->toArray();
@@ -27,7 +27,7 @@ final class HelpCenterPageController extends Controller
return Inertia::render('Help/HelpCenterPage', [
'title' => 'Help Center',
'description' => 'Find guides, quickstarts, FAQs, and troubleshooting for Skinbase Nova in one structured help hub.',
'description' => 'Find guides, quickstarts, FAQs, and troubleshooting for Skinbase in one structured help hub.',
'seo' => $seo,
'links' => [
'studio_help' => route('help.studio'),

View File

@@ -60,12 +60,12 @@ class NovaCardsController extends Controller
return view('cards.index', [
'meta' => [
'title' => 'Nova Cards - Skinbase Nova',
'description' => 'Browse featured, trending, and latest Nova Cards. Discover beautiful quote cards, mood cards, and visual text art by the Skinbase Nova community.',
'title' => 'Cards - Skinbase',
'description' => 'Browse featured, trending, and latest Cards. Discover beautiful quote cards, mood cards, and visual text art by the Skinbase community.',
'canonical' => route('cards.index'),
'robots' => 'index,follow',
],
'heading' => 'Nova Cards',
'heading' => 'Cards',
'subheading' => (string) config('nova_cards.brand.subtitle'),
'cards' => $this->presenter->cards($latest->items()),
'pagination' => $latest,
@@ -90,13 +90,13 @@ class NovaCardsController extends Controller
return view('cards.index', [
'meta' => [
'title' => $category->name . ' Cards - Skinbase Nova',
'description' => $category->description ?: ('Browse ' . strtolower($category->name) . ' Nova Cards on Skinbase Nova.'),
'title' => $category->name . ' Cards - Skinbase',
'description' => $category->description ?: ('Browse ' . strtolower($category->name) . ' Cards on Skinbase.'),
'canonical' => route('cards.category', ['categorySlug' => $category->slug]),
'robots' => 'index,follow',
],
'heading' => $category->name,
'subheading' => $category->description ?: 'Explore this Nova Cards category.',
'subheading' => $category->description ?: 'Explore this Cards category.',
'cards' => $this->presenter->cards($cards->items()),
'pagination' => $cards,
'featuredCards' => [],
@@ -119,8 +119,8 @@ class NovaCardsController extends Controller
return view('cards.index', [
'meta' => [
'title' => 'Popular Cards - Skinbase Nova',
'description' => 'Browse the most liked, saved, and viewed Nova Cards on Skinbase Nova.',
'title' => 'Popular Cards - Skinbase',
'description' => 'Browse the most liked, saved, and viewed Cards on Skinbase.',
'canonical' => route('cards.popular'),
'robots' => 'index,follow',
],
@@ -153,13 +153,13 @@ class NovaCardsController extends Controller
return view('cards.index', [
'meta' => [
'title' => 'Rising Cards - Skinbase Nova',
'description' => 'Discover Nova Cards that are gaining traction right now — fresh creators and fast-rising saves and remixes.',
'title' => 'Rising Cards - Skinbase',
'description' => 'Discover Cards that are gaining traction right now — fresh creators and fast-rising saves and remixes.',
'canonical' => route('cards.rising'),
'robots' => 'index,follow',
],
'heading' => 'Rising',
'subheading' => 'Fresh Nova Cards gaining momentum right now.',
'subheading' => 'Fresh Cards gaining momentum right now.',
'cards' => $this->presenter->cards($paginated->items(), false, $request->user()),
'pagination' => $paginated,
'featuredCards' => [],
@@ -182,13 +182,13 @@ class NovaCardsController extends Controller
return view('cards.index', [
'meta' => [
'title' => 'Remixed Cards - Skinbase Nova',
'description' => 'Discover Nova Cards remixed from community originals with attribution and lineage.',
'title' => 'Remixed Cards - Skinbase',
'description' => 'Discover Cards remixed from community originals with attribution and lineage.',
'canonical' => route('cards.remixed'),
'robots' => 'index,follow',
],
'heading' => 'Remixed cards',
'subheading' => 'Community reinterpretations linked back to their original Nova Cards.',
'subheading' => 'Community reinterpretations linked back to their original Cards.',
'cards' => $this->presenter->cards($cards->items(), false, $request->user()),
'pagination' => $cards,
'featuredCards' => [],
@@ -214,8 +214,8 @@ class NovaCardsController extends Controller
return view('cards.index', [
'meta' => [
'title' => 'Best Remixes - Skinbase Nova',
'description' => 'Browse standout Nova Card remixes ranked by remix traction, saves, and likes.',
'title' => 'Best Remixes - Skinbase',
'description' => 'Browse standout Card remixes ranked by remix traction, saves, and likes.',
'canonical' => route('cards.remix-highlights'),
'robots' => 'index,follow',
],
@@ -295,13 +295,13 @@ class NovaCardsController extends Controller
return view('cards.index', [
'meta' => [
'title' => 'Editorial Picks - Nova Cards - Skinbase Nova',
'description' => 'Browse editorial Nova Cards picks, featured collections, and highlighted challenges.',
'title' => 'Editorial Picks - Cards - Skinbase',
'description' => 'Browse editorial Cards picks, featured collections, and highlighted challenges.',
'canonical' => route('cards.editorial'),
'robots' => 'index,follow',
],
'heading' => 'Editorial picks',
'subheading' => 'Curated Nova Cards, featured collections, and standout challenge surfaces chosen for quality and cohesion.',
'subheading' => 'Curated Cards, featured collections, and standout challenge surfaces chosen for quality and cohesion.',
'cards' => $this->presenter->cards($cards->items(), false, $request->user()),
'pagination' => $cards,
'featuredCards' => [],
@@ -329,13 +329,13 @@ class NovaCardsController extends Controller
return view('cards.index', [
'meta' => [
'title' => 'Seasonal Cards - Nova Cards - Skinbase Nova',
'description' => 'Browse seasonal and event-aware Nova Cards grouped by recurring moods, holidays, and time-of-year themes.',
'title' => 'Seasonal Cards - Cards - Skinbase',
'description' => 'Browse seasonal and event-aware Cards grouped by recurring moods, holidays, and time-of-year themes.',
'canonical' => route('cards.seasonal'),
'robots' => 'index,follow',
],
'heading' => 'Seasonal cards',
'subheading' => 'Discover Nova Cards grouped by recurring seasonal and campaign-style themes.',
'subheading' => 'Discover Cards grouped by recurring seasonal and campaign-style themes.',
'cards' => $this->presenter->cards($cards->items(), false, $request->user()),
'pagination' => $cards,
'featuredCards' => [],
@@ -363,13 +363,13 @@ class NovaCardsController extends Controller
return view('cards.challenges', [
'meta' => [
'title' => 'Card Challenges - Skinbase Nova',
'description' => 'Browse active and completed Nova Cards challenges, prompts, and winners.',
'title' => 'Card Challenges - Skinbase',
'description' => 'Browse active and completed Cards challenges, prompts, and winners.',
'canonical' => route('cards.challenges'),
'robots' => 'index,follow',
],
'heading' => 'Card challenges',
'subheading' => 'Official prompts and community challenge runs for Nova Cards creators.',
'subheading' => 'Official prompts and community challenge runs for Cards creators.',
'challenges' => $challenges,
]);
}
@@ -388,8 +388,8 @@ class NovaCardsController extends Controller
return view('cards.challenges', [
'meta' => [
'title' => $challenge->title . ' - Skinbase Nova',
'description' => $challenge->description ?: 'Browse entries for this Nova Cards challenge.',
'title' => $challenge->title . ' - Skinbase',
'description' => $challenge->description ?: 'Browse entries for this Cards challenge.',
'canonical' => route('cards.challenges.show', ['slug' => $challenge->slug]),
'robots' => 'index,follow',
],
@@ -410,8 +410,8 @@ class NovaCardsController extends Controller
{
return view('cards.resources', [
'meta' => [
'title' => 'Template Packs - Skinbase Nova',
'description' => 'Browse official Nova Cards template packs and starting points.',
'title' => 'Template Packs - Skinbase',
'description' => 'Browse official Cards template packs and starting points.',
'canonical' => route('cards.templates'),
'robots' => 'index,follow',
],
@@ -427,13 +427,13 @@ class NovaCardsController extends Controller
{
return view('cards.resources', [
'meta' => [
'title' => 'Asset Packs - Skinbase Nova',
'description' => 'Browse official Nova Cards asset packs for decorative and editorial layouts.',
'title' => 'Asset Packs - Skinbase',
'description' => 'Browse official Cards asset packs for decorative and editorial layouts.',
'canonical' => route('cards.assets'),
'robots' => 'index,follow',
],
'heading' => 'Asset packs',
'subheading' => 'Official decorative and editorial pack sets for the Nova Cards v2 editor.',
'subheading' => 'Official decorative and editorial pack sets for the Cards v2 editor.',
'packs' => collect($this->presenter->options()['asset_packs'] ?? []),
'templates' => collect(),
'resourceType' => 'asset',
@@ -447,8 +447,8 @@ class NovaCardsController extends Controller
return view('cards.index', [
'meta' => [
'title' => '#' . $tag->name . ' Cards - Skinbase Nova',
'description' => 'Browse Nova Cards tagged with #' . $tag->name . ' on Skinbase Nova.',
'title' => '#' . $tag->name . ' Cards - Skinbase',
'description' => 'Browse Cards tagged with #' . $tag->name . ' on Skinbase.',
'canonical' => route('cards.tag', ['tagSlug' => $tag->slug]),
'robots' => 'index,follow',
],
@@ -480,13 +480,13 @@ class NovaCardsController extends Controller
return view('cards.index', [
'meta' => [
'title' => $mood['label'] . ' Mood Cards - Skinbase Nova',
'description' => 'Browse Nova Cards grouped into the ' . strtolower((string) $mood['label']) . ' mood family on Skinbase Nova.',
'title' => $mood['label'] . ' Mood Cards - Skinbase',
'description' => 'Browse Cards grouped into the ' . strtolower((string) $mood['label']) . ' mood family on Skinbase.',
'canonical' => route('cards.mood', ['moodSlug' => $mood['key']]),
'robots' => 'index,follow',
],
'heading' => $mood['label'],
'subheading' => 'Discover Nova Cards grouped by a curated mood family using durable tag mappings.',
'subheading' => 'Discover Cards grouped by a curated mood family using durable tag mappings.',
'cards' => $this->presenter->cards($cards->items(), false, $request->user()),
'pagination' => $cards,
'featuredCards' => [],
@@ -514,13 +514,13 @@ class NovaCardsController extends Controller
return view('cards.index', [
'meta' => [
'title' => $style['label'] . ' Style Cards - Skinbase Nova',
'description' => 'Browse Nova Cards using the ' . strtolower((string) $style['label']) . ' style family on Skinbase Nova.',
'title' => $style['label'] . ' Style Cards - Skinbase',
'description' => 'Browse Cards using the ' . strtolower((string) $style['label']) . ' style family on Skinbase.',
'canonical' => route('cards.style', ['styleSlug' => $style['key']]),
'robots' => 'index,follow',
],
'heading' => $style['label'],
'subheading' => 'Discover Nova Cards grouped by a shared visual style family.',
'subheading' => 'Discover Cards grouped by a shared visual style family.',
'cards' => $this->presenter->cards($cards->items(), false, $request->user()),
'pagination' => $cards,
'featuredCards' => [],
@@ -548,13 +548,13 @@ class NovaCardsController extends Controller
return view('cards.index', [
'meta' => [
'title' => $palette['label'] . ' Palette Cards - Skinbase Nova',
'description' => 'Browse Nova Cards using the ' . strtolower((string) $palette['label']) . ' palette family on Skinbase Nova.',
'title' => $palette['label'] . ' Palette Cards - Skinbase',
'description' => 'Browse Cards using the ' . strtolower((string) $palette['label']) . ' palette family on Skinbase.',
'canonical' => route('cards.palette', ['paletteSlug' => $palette['key']]),
'robots' => 'index,follow',
],
'heading' => $palette['label'],
'subheading' => 'Discover Nova Cards grouped by shared palette families and color direction.',
'subheading' => 'Discover Cards grouped by shared palette families and color direction.',
'cards' => $this->presenter->cards($cards->items(), false, $request->user()),
'pagination' => $cards,
'featuredCards' => [],
@@ -580,8 +580,8 @@ class NovaCardsController extends Controller
return view('cards.index', array_merge($this->creatorPagePayload($request, $user), [
'meta' => [
'title' => '@' . $user->username . ' Cards - Skinbase Nova',
'description' => 'Browse Nova Cards created by @' . $user->username . ' on Skinbase Nova.',
'title' => '@' . $user->username . ' Cards - Skinbase',
'description' => 'Browse Cards created by @' . $user->username . ' on Skinbase.',
'canonical' => route('cards.creator', ['username' => strtolower((string) $user->username)]),
'robots' => 'index,follow',
],
@@ -602,13 +602,13 @@ class NovaCardsController extends Controller
return view('cards.index', array_merge($this->creatorPagePayload($request, $user), [
'meta' => [
'title' => '@' . $user->username . ' Portfolio - Skinbase Nova',
'description' => 'Browse the dedicated Nova Cards portfolio page for @' . $user->username . ' on Skinbase Nova.',
'title' => '@' . $user->username . ' Portfolio - Skinbase',
'description' => 'Browse the dedicated Cards portfolio page for @' . $user->username . ' on Skinbase.',
'canonical' => route('cards.creator.portfolio', ['username' => strtolower((string) $user->username)]),
'robots' => 'index,follow',
],
'heading' => '@' . $user->username . ' Portfolio',
'subheading' => 'A dedicated Nova Cards portfolio view for ' . ($user->name ?: '@' . $user->username) . '.',
'subheading' => 'A dedicated Cards portfolio view for ' . ($user->name ?: '@' . $user->username) . '.',
'context' => 'creator-portfolio',
]));
}
@@ -695,8 +695,8 @@ class NovaCardsController extends Controller
return view('cards.collection', [
'meta' => [
'title' => $collection->name . ' - Nova Cards Collection - Skinbase Nova',
'description' => $collection->description ?: 'Browse this curated Nova Cards collection.',
'title' => $collection->name . ' - Cards Collection - Skinbase',
'description' => $collection->description ?: 'Browse this curated Cards collection.',
'canonical' => route('cards.collections.show', ['slug' => $collection->slug, 'id' => $collection->id]),
'robots' => 'index,follow',
],
@@ -721,7 +721,7 @@ class NovaCardsController extends Controller
return view('cards.lineage', [
'meta' => [
'title' => $card->title . ' Lineage - Nova Cards - Skinbase Nova',
'title' => $card->title . ' Lineage - Cards - Skinbase',
'description' => 'Browse the remix lineage and related variants for this Nova Card.',
'canonical' => route('cards.lineage', ['slug' => $card->slug, 'id' => $card->id]),
'robots' => 'index,follow',
@@ -767,7 +767,7 @@ class NovaCardsController extends Controller
return view('cards.show', [
'card' => $this->presenter->card($card, true, $request->user()),
'meta' => [
'title' => $card->title . ' - Nova Cards - Skinbase Nova',
'title' => $card->title . ' - Cards - Skinbase',
'description' => $card->description ?: $card->quote_text,
'canonical' => route('cards.show', ['slug' => $card->slug, 'id' => $card->id]),
'robots' => $card->visibility === NovaCard::VISIBILITY_PUBLIC ? 'index,follow' : 'noindex,follow',

View File

@@ -18,7 +18,7 @@ final class ProfileHelpPageController extends Controller
$seo = app(SeoFactory::class)
->collectionPage(
'Profile Help — Skinbase',
'Learn how profiles work on Skinbase Nova, how they differ from Groups, and how to build a stronger personal identity with better setup, presentation, and creator-facing profile habits.',
'Learn how profiles work on Skinbase, how they differ from Groups, and how to build a stronger personal identity with better setup, presentation, and creator-facing profile habits.',
$canonical,
)
->toArray();

View File

@@ -18,7 +18,7 @@ final class StudioHelpPageController extends Controller
$seo = app(SeoFactory::class)
->collectionPage(
'Studio Help — Skinbase',
'Learn how Studio works on Skinbase Nova, including drafts, publishing, personal versus Group context, artworks, cards, collections, and collaboration workflows.',
'Learn how Studio works on Skinbase, including drafts, publishing, personal versus Group context, artworks, cards, collections, and collaboration workflows.',
$canonical,
)
->toArray();
@@ -27,7 +27,7 @@ final class StudioHelpPageController extends Controller
return Inertia::render('Help/StudioHelpPage', [
'title' => 'Studio Help',
'description' => 'Understand Studio as the creative control center of Skinbase Nova, with guidance for drafts, publishing, artworks, cards, collections, and Group workflows.',
'description' => 'Understand Studio as the creative control center of Skinbase, with guidance for drafts, publishing, artworks, cards, collections, and Group workflows.',
'seo' => $seo,
'links' => [
'help_home' => route('help'),

View File

@@ -18,7 +18,7 @@ final class TroubleshootingHelpPageController extends Controller
$seo = app(SeoFactory::class)
->collectionPage(
'Troubleshooting Help — Skinbase',
'Use fast, support-oriented troubleshooting guidance for login issues, permissions confusion, publishing blockers, profile setup problems, and bug-report escalation on Skinbase Nova.',
'Use fast, support-oriented troubleshooting guidance for login issues, permissions confusion, publishing blockers, profile setup problems, and bug-report escalation on Skinbase.',
$canonical,
)
->toArray();

View File

@@ -18,7 +18,7 @@ final class UploadHelpPageController extends Controller
$seo = app(SeoFactory::class)
->collectionPage(
'Upload Help — Skinbase',
'Learn how uploading works on Skinbase Nova, including draft creation, metadata review, previews, personal versus Group context, contributor credit, publishing, and troubleshooting.',
'Learn how uploading works on Skinbase, including draft creation, metadata review, previews, personal versus Group context, contributor credit, publishing, and troubleshooting.',
$canonical,
)
->toArray();
@@ -27,7 +27,7 @@ final class UploadHelpPageController extends Controller
return Inertia::render('Help/UploadHelpPage', [
'title' => 'Upload Help',
'description' => 'Understand the full upload workflow on Skinbase Nova, from file submission and draft creation to metadata review, contributor credit, and final publish.',
'description' => 'Understand the full upload workflow on Skinbase, from file submission and draft creation to metadata review, contributor credit, and final publish.',
'seo' => $seo,
'links' => [
'help_home' => route('help'),

View File

@@ -22,7 +22,7 @@ final class WorldController extends Controller
{
$payload = $this->worlds->publicIndexPayload($request->user());
$seo = app(SeoFactory::class)->collectionListing(
'Worlds — Skinbase Nova',
'Worlds — Skinbase',
$payload['description'],
route('worlds.index'),
)->toArray();
@@ -45,8 +45,8 @@ final class WorldController extends Controller
$payload = $this->worlds->publicShowPayload($resolvedWorld, $request->user());
$seo = app(SeoFactory::class)->collectionPage(
$resolvedWorld->seo_title ?: ($resolvedWorld->title . ' — Skinbase Nova'),
$resolvedWorld->seo_description ?: ($resolvedWorld->summary ?: $resolvedWorld->description ?: 'Seasonal and editorial discovery world on Skinbase Nova.'),
$resolvedWorld->seo_title ?: ($resolvedWorld->title . ' — Skinbase'),
$resolvedWorld->seo_description ?: ($resolvedWorld->summary ?: $resolvedWorld->description ?: 'Seasonal and editorial discovery world on Skinbase.'),
$this->worlds->canonicalPublicUrl($resolvedWorld),
$resolvedWorld->ogImageUrl(),
)->toArray();
@@ -69,8 +69,8 @@ final class WorldController extends Controller
$payload = $this->worlds->publicShowPayload($resolvedWorld, $request->user());
$seo = app(SeoFactory::class)->collectionPage(
$resolvedWorld->seo_title ?: ($resolvedWorld->title . ' — Skinbase Nova'),
$resolvedWorld->seo_description ?: ($resolvedWorld->summary ?: $resolvedWorld->description ?: 'Seasonal and editorial discovery world on Skinbase Nova.'),
$resolvedWorld->seo_title ?: ($resolvedWorld->title . ' — Skinbase'),
$resolvedWorld->seo_description ?: ($resolvedWorld->summary ?: $resolvedWorld->description ?: 'Seasonal and editorial discovery world on Skinbase.'),
$this->worlds->canonicalPublicUrl($resolvedWorld),
$resolvedWorld->ogImageUrl(),
)->toArray();

View File

@@ -18,7 +18,7 @@ final class WorldsHelpPageController extends Controller
$seo = app(SeoFactory::class)
->collectionPage(
'Worlds Help — Skinbase',
'Learn how Worlds work on Skinbase Nova, including editorial purpose, attached content, section control, preview, publishing, recurrence, and homepage promotion.',
'Learn how Worlds work on Skinbase, including editorial purpose, attached content, section control, preview, publishing, recurrence, and homepage promotion.',
$canonical,
)
->toArray();
@@ -27,7 +27,7 @@ final class WorldsHelpPageController extends Controller
return Inertia::render('Help/WorldsHelpPage', [
'title' => 'Worlds Help',
'description' => 'A complete guide to creating, attaching content to, previewing, and publishing Worlds on Skinbase Nova.',
'description' => 'A complete guide to creating, attaching content to, previewing, and publishing Worlds on Skinbase.',
'seo' => $seo,
'links' => [
'help_home' => route('help'),

View File

@@ -48,7 +48,7 @@ final class GenerateArtworkEmbeddingJob implements ShouldQueue
public function handle(
ArtworkEmbeddingClient $client,
ArtworkVisionImageUrl $imageUrlBuilder,
VectorService|ArtworkVectorIndexService $vectors,
ArtworkVectorIndexService $vectors,
): void
{
if (! (bool) config('recommendations.embedding.enabled', true)) {
@@ -128,7 +128,7 @@ final class GenerateArtworkEmbeddingJob implements ShouldQueue
}
private function upsertVectorIndex(
VectorService|ArtworkVectorIndexService $vectors,
ArtworkVectorIndexService $vectors,
Artwork $artwork
): void
{

View File

@@ -25,7 +25,7 @@ final class AiBiographyPromptBuilder
private const MIN_WORDS = 30;
private const SYSTEM_PROMPT = <<<'PROMPT'
You are a concise writing assistant for Skinbase Nova, a digital art platform.
You are a concise writing assistant for Skinbase, a digital art platform.
Write short creator biographies using only the facts provided. Use a polished, factual, and slightly editorial tone.
@@ -44,7 +44,7 @@ Rules:
PROMPT;
private const SYSTEM_PROMPT_STRICT = <<<'PROMPT'
You are a cautious writing assistant for Skinbase Nova, a digital art platform.
You are a cautious writing assistant for Skinbase, a digital art platform.
Write a short, safe creator biography using only the facts provided. Be conservative.
@@ -59,7 +59,7 @@ Rules:
PROMPT;
private const SYSTEM_PROMPT_SPARSE = <<<'PROMPT'
You are a cautious writing assistant for Skinbase Nova, a digital art platform.
You are a cautious writing assistant for Skinbase, a digital art platform.
Write a short, modest creator introduction using only the facts provided.
@@ -75,7 +75,7 @@ Rules:
PROMPT;
private const SYSTEM_PROMPT_SPARSE_STRICT = <<<'PROMPT'
You are a cautious writing assistant for Skinbase Nova, a digital art platform.
You are a cautious writing assistant for Skinbase, a digital art platform.
Write a short, modest creator introduction using only the facts provided. Be conservative and precise.

View File

@@ -75,7 +75,7 @@ class CollectionAiCurationService
);
$seo = sprintf(
'%s on Skinbase Nova: %d curated artworks%s.',
'%s on Skinbase: %d curated artworks%s.',
$this->draftString($collection, $draft, 'title') ?: $collection->title,
$context['artworks_count'],
$context['theme_sentence'] !== '' ? ' exploring ' . $context['theme_sentence'] : ''

View File

@@ -238,7 +238,7 @@ final class ArtworkSquareThumbnailBackfillService
'timeout' => 30,
'ignore_errors' => true,
'header' => implode("\r\n", [
'User-Agent: Skinbase Nova square-thumb backfill',
'User-Agent: Skinbase square-thumb backfill',
'Accept: image/*,*/*;q=0.8',
'Accept-Encoding: identity',
'Connection: close',

View File

@@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
/**
* ArtworkRankingService Skinbase Nova Ranking Engine V2
* ArtworkRankingService Skinbase Ranking Engine V2
*
* Intelligent scoring system combining:
* 1. Base engagement (views, downloads, favourites, comments, shares)

View File

@@ -11,7 +11,7 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/**
* RankingService Skinbase Nova rank_v2
* RankingService Skinbase rank_v2
*
* Responsibilities:
* 1. Score computation turn raw artwork signals into three float scores.

View File

@@ -33,7 +33,7 @@ final class GoogleNewsSitemapBuilder extends AbstractSitemapBuilder
route('news.show', ['slug' => $article->slug]),
trim((string) $article->title),
$article->published_at,
(string) \config('sitemaps.news.google_publication_name', 'Skinbase Nova'),
(string) \config('sitemaps.news.google_publication_name', 'Skinbase'),
(string) \config('sitemaps.news.google_language', 'en'),
);
})