validate([ 'q' => ['nullable', 'string', 'max:120'], 'type' => ['nullable', 'string', 'max:40'], 'category' => ['nullable', 'string', 'max:80'], 'style' => ['nullable', 'string', 'max:80'], 'theme' => ['nullable', 'string', 'max:80'], 'color' => ['nullable', 'string', 'max:80'], 'quality_tier' => ['nullable', 'string', 'max:40'], 'lifecycle_state' => ['nullable', 'string', 'max:40'], 'mode' => ['nullable', 'string', 'max:40'], 'campaign_key' => ['nullable', 'string', 'max:80'], 'program_key' => ['nullable', 'string', 'max:80'], 'workflow_state' => ['nullable', 'string', 'max:40'], 'health_state' => ['nullable', 'string', 'max:40'], 'sort' => ['nullable', 'in:trending,recent,quality,evergreen'], ]); $results = $this->search->publicSearch($filters, (int) config('collections.v5.search.public_per_page', 18)); return Inertia::render('Collection/CollectionFeaturedIndex', [ 'eyebrow' => 'Search', 'title' => 'Search collections', 'description' => filled($filters['q'] ?? null) ? sprintf('Search results for "%s" across public Skinbase Nova collections.', $filters['q']) : 'Browse public collections using filters for category, style, theme, color, quality tier, freshness, and programming metadata.', 'seo' => [ 'title' => 'Search Collections — Skinbase Nova', 'description' => 'Search public collections by category, theme, quality tier, and curator context.', 'canonical' => route('collections.search'), 'robots' => 'index,follow', ], 'collections' => $this->collections->mapCollectionCardPayloads($results->items(), false, $request->user()), 'communityCollections' => [], 'editorialCollections' => [], 'recentCollections' => [], 'trendingCollections' => [], 'seasonalCollections' => [], 'campaign' => null, 'search' => [ 'filters' => $filters, 'options' => $this->search->publicFilterOptions(), 'meta' => [ 'current_page' => $results->currentPage(), 'last_page' => $results->lastPage(), 'per_page' => $results->perPage(), 'total' => $results->total(), ], 'links' => [ 'next' => $results->nextPageUrl(), 'prev' => $results->previousPageUrl(), ], ], ])->rootView('collections'); } public function featured(Request $request) { $featuredCollections = $this->surfaces->resolveSurfaceItems('discover.featured_collections', (int) config('collections.discovery.featured_limit', 18)); return $this->renderIndex( 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.', 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), recentCollections: $this->discovery->publicRecentCollections(6), trendingCollections: $this->discovery->publicTrendingCollections(6), seasonalCollections: $this->discovery->publicSeasonalCollections(6), ); } public function recommended(Request $request) { $collections = $this->recommendations->recommendedForUser($request->user(), (int) config('collections.discovery.featured_limit', 18)); return $this->renderIndex( viewer: $request->user(), eyebrow: $request->user() ? 'For You' : 'Discovery', title: $request->user() ? 'Recommended collections' : 'Collections worth exploring', description: $request->user() ? 'A safe, public-only recommendation feed based on the collections you save, follow, and engage with. Private, restricted, and unlisted sets are excluded.' : 'A safe public collection mix built from current momentum, editorial quality, and community interest.', collections: $collections, recentCollections: $this->discovery->publicRecentCollections(6), trendingCollections: $this->discovery->publicTrendingCollections(6), editorialCollections: $this->discovery->publicCollectionsByType(Collection::TYPE_EDITORIAL, 6), ); } public function trending(Request $request) { return $this->renderIndex( viewer: $request->user(), eyebrow: 'Trending', title: 'Trending collections', description: 'The collections drawing the strongest blend of follows, likes, saves, comments, and recent activity right now.', collections: $this->discovery->publicTrendingCollections((int) config('collections.discovery.featured_limit', 18)), recentCollections: $this->discovery->publicRecentlyActiveCollections(6), ); } public function editorial(Request $request) { return $this->renderIndex( viewer: $request->user(), eyebrow: 'Editorial', title: 'Editorial collections', description: 'Staff picks, campaign showcases, and premium curated sets with stronger scheduling and presentation rules.', collections: $this->discovery->publicCollectionsByType(Collection::TYPE_EDITORIAL, (int) config('collections.discovery.featured_limit', 18)), ); } public function community(Request $request) { return $this->renderIndex( viewer: $request->user(), eyebrow: 'Community', title: 'Community collections', description: 'Collaborative and submission-friendly showcases that celebrate both the curator and the creators featured inside them.', collections: $this->discovery->publicCollectionsByType(Collection::TYPE_COMMUNITY, (int) config('collections.discovery.featured_limit', 18)), ); } public function seasonal(Request $request) { return $this->renderIndex( viewer: $request->user(), eyebrow: 'Seasonal', title: 'Seasonal and event collections', description: 'Collections prepared for campaigns, seasonal spotlights, and event-driven showcases with dedicated metadata.', collections: $this->discovery->publicSeasonalCollections((int) config('collections.discovery.featured_limit', 18)), editorialCollections: $this->discovery->publicCollectionsByType(Collection::TYPE_EDITORIAL, 6), communityCollections: $this->discovery->publicCollectionsByType(Collection::TYPE_COMMUNITY, 6), ); } public function campaign(Request $request, string $campaignKey) { $landing = $this->campaigns->publicLanding($campaignKey, (int) config('collections.discovery.featured_limit', 18)); $campaign = $landing['campaign']; abort_if(collect($landing['collections'])->isEmpty(), 404); return $this->renderIndex( viewer: $request->user(), eyebrow: 'Campaign', title: $campaign['label'], description: $campaign['description'], collections: $landing['collections'], communityCollections: $landing['community_collections'], editorialCollections: $landing['editorial_collections'], recentCollections: $landing['recent_collections'], trendingCollections: $landing['trending_collections'], campaign: $campaign, ); } public function program(Request $request, string $programKey) { $landing = $this->partnerPrograms->publicLanding($programKey, (int) config('collections.discovery.featured_limit', 18)); $program = $landing['program']; abort_if(! $program || collect($landing['collections'])->isEmpty(), 404); return Inertia::render('Collection/CollectionFeaturedIndex', [ 'eyebrow' => 'Program', 'title' => $program['label'], 'description' => $program['description'], 'seo' => [ 'title' => sprintf('%s — Skinbase Nova', $program['label']), 'description' => $program['description'], 'canonical' => route('collections.program.show', ['programKey' => $program['key']]), 'robots' => 'index,follow', ], 'collections' => $this->collections->mapCollectionCardPayloads($landing['collections'], false, $request->user()), 'communityCollections' => $this->collections->mapCollectionCardPayloads($landing['community_collections'] ?? collect(), false, $request->user()), 'editorialCollections' => $this->collections->mapCollectionCardPayloads($landing['editorial_collections'] ?? collect(), false, $request->user()), 'recentCollections' => $this->collections->mapCollectionCardPayloads($landing['recent_collections'] ?? collect(), false, $request->user()), 'trendingCollections' => [], 'seasonalCollections' => [], 'campaign' => null, 'program' => $program, ])->rootView('collections'); } private function renderIndex( $viewer, string $eyebrow, string $title, string $description, $collections, $communityCollections = null, $editorialCollections = null, $recentCollections = null, $trendingCollections = null, $seasonalCollections = null, $campaign = null, ) { return Inertia::render('Collection/CollectionFeaturedIndex', [ 'eyebrow' => $eyebrow, 'title' => $title, 'description' => $description, 'seo' => [ 'title' => sprintf('%s — Skinbase Nova', $title), 'description' => $description, 'canonical' => url()->current(), 'robots' => 'index,follow', ], 'collections' => $this->collections->mapCollectionCardPayloads($collections, false, $viewer), 'communityCollections' => $this->collections->mapCollectionCardPayloads($communityCollections ?? collect(), false, $viewer), 'editorialCollections' => $this->collections->mapCollectionCardPayloads($editorialCollections ?? collect(), false, $viewer), 'recentCollections' => $this->collections->mapCollectionCardPayloads($recentCollections ?? collect(), false, $viewer), 'trendingCollections' => $this->collections->mapCollectionCardPayloads($trendingCollections ?? collect(), false, $viewer), 'seasonalCollections' => $this->collections->mapCollectionCardPayloads($seasonalCollections ?? collect(), false, $viewer), 'campaign' => $campaign, ])->rootView('collections'); } }