Files
SkinbaseNova/routes/web.php
2026-03-28 19:15:39 +01:00

682 lines
55 KiB
PHP

<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\User\ProfileController;
use App\Models\ContentType;
use App\Http\Controllers\User\AvatarController;
use App\Http\Controllers\Dashboard\ArtworkController as DashboardArtworkController;
use App\Http\Controllers\Web\ArtworkPageController;
use App\Http\Controllers\ArtworkDownloadController;
use App\Http\Controllers\Web\BrowseGalleryController;
use App\Http\Controllers\Web\FeaturedArtworksController;
use App\Http\Controllers\Web\DailyUploadsController;
use App\Http\Controllers\Web\DiscoverController;
use App\Http\Controllers\Web\ExploreController;
use App\Http\Controllers\Web\BlogController;
use App\Http\Controllers\Web\PageController;
use App\Http\Controllers\StoryController;
use App\Http\Controllers\Web\HomeController;
use App\Http\Controllers\Web\FooterController;
use App\Http\Controllers\Web\BugReportController;
use App\Http\Controllers\Web\StaffController;
use App\Http\Controllers\Web\RssFeedController;
use App\Http\Controllers\Web\ApplicationController;
use App\Http\Controllers\Web\CategoryController;
use App\Http\Controllers\News\NewsController as FrontendNewsController;
use App\Http\Controllers\News\NewsRssController;
use App\Http\Controllers\RSS\GlobalFeedController;
use App\Http\Controllers\RSS\DiscoverFeedController;
use App\Http\Controllers\RSS\ExploreFeedController;
use App\Http\Controllers\RSS\TagFeedController;
use App\Http\Controllers\RSS\CreatorFeedController;
use App\Http\Controllers\RSS\BlogFeedController;
use App\Http\Controllers\Studio\StudioController;
use App\Http\Controllers\DashboardController;
use App\Http\Controllers\Community\LatestController;
use App\Http\Controllers\User\MembersController;
use App\Http\Controllers\User\TodayDownloadsController;
use App\Http\Controllers\User\MonthlyCommentatorsController;
use App\Http\Controllers\User\ProfileCollectionController;
use App\Http\Controllers\User\SavedCollectionController;
use App\Http\Controllers\User\CollectionSavedLibraryController;
use App\Models\Artwork;
use App\Http\Controllers\Settings\CollectionAiController;
use App\Http\Controllers\Settings\CollectionInsightsController;
use App\Http\Controllers\Settings\CollectionManageController;
use App\Http\Controllers\Settings\CollectionProgrammingController;
use App\Http\Controllers\Settings\CollectionSurfaceController;
use Inertia\Inertia;
Route::get('/', [HomeController::class, 'index'])->name('index');
Route::get('/home', [HomeController::class, 'index']);
// ── PUBLIC GALLERIES / LISTS ─────────────────────────────────────────────────
Route::get('/featured', [FeaturedArtworksController::class, 'index'])->name('featured');
Route::get('/uploads/latest', [LatestController::class, 'index'])->name('uploads.latest');
Route::get('/uploads/daily', [DailyUploadsController::class, 'index'])->name('uploads.daily');
Route::get('/members/photos', [MembersController::class, 'photos'])->name('members.photos');
Route::get('/downloads/today', [TodayDownloadsController::class, 'index'])->name('downloads.today');
Route::get('/comments/monthly', [MonthlyCommentatorsController::class, 'index'])->name('comments.monthly');
// ── DISCOVER (/discover/*) ────────────────────────────────────────────────────
Route::prefix('discover')->name('discover.')->group(function () {
Route::get('/', fn () => redirect('/discover/trending', 301));
Route::get('/trending', [DiscoverController::class, 'trending'])->name('trending');
Route::get('/rising', [DiscoverController::class, 'rising'])->name('rising');
Route::get('/fresh', [DiscoverController::class, 'fresh'])->name('fresh');
Route::get('/top-rated', [DiscoverController::class, 'topRated'])->name('top-rated');
Route::get('/most-downloaded', [DiscoverController::class, 'mostDownloaded'])->name('most-downloaded');
Route::get('/on-this-day', [DiscoverController::class, 'onThisDay'])->name('on-this-day');
Route::middleware('auth')->get('/following', [DiscoverController::class, 'following'])->name('following');
Route::middleware('auth')->get('/for-you', [DiscoverController::class, 'forYou'])->name('for-you');
});
// ── EXPLORE (/explore/*) ──────────────────────────────────────────────────────
Route::prefix('explore')->name('explore.')->group(function () {
Route::get('/', [ExploreController::class, 'index'])->name('index');
Route::get('/members', fn () => redirect()->route('creators.top', request()->query(), 301))->name('members.redirect');
Route::get('/memebers', fn () => redirect()->route('creators.top', request()->query(), 301))->name('memebers.redirect');
Route::get('/{type}', [ExploreController::class, 'byType'])
->where('type', 'artworks|wallpapers|skins|photography|other')
->name('type');
Route::get('/{type}/{mode}', [ExploreController::class, 'byTypeMode'])
->where('type', 'artworks|wallpapers|skins|photography|other')
->where('mode', 'trending|new-hot|best|latest')
->name('type.mode');
});
// ── BLOG (/blog/*) ────────────────────────────────────────────────────────────
Route::prefix('blog')->name('blog.')->group(function () {
Route::get('/', [BlogController::class, 'index'])->name('index');
Route::get('/{slug}', [BlogController::class, 'show'])->where('slug', '[a-z0-9\-]+')->name('show');
});
// ── PAGES (DB-driven static pages) ───────────────────────────────────────────
Route::get('/pages/{slug}', [PageController::class, 'show'])
->where('slug', '[a-z0-9\-]+')
->name('pages.show');
Route::get('/about', [PageController::class, 'marketing'])->defaults('slug', 'about')->name('about');
Route::get('/help', [PageController::class, 'marketing'])->defaults('slug', 'help')->name('help');
Route::get('/contact', [PageController::class, 'marketing'])->defaults('slug', 'contact')->name('contact');
Route::get('/legal/{section}', [PageController::class, 'legal'])
->where('section', 'terms|privacy|cookies')
->name('legal');
// ── FOOTER ────────────────────────────────────────────────────────────────────
Route::get('/rss-feeds', [RssFeedController::class, 'index'])->name('rss-feeds');
Route::get('/faq', [FooterController::class, 'faq'])->name('faq');
Route::get('/rules-and-guidelines', [FooterController::class, 'rules'])->name('rules');
Route::get('/privacy-policy', [FooterController::class, 'privacyPolicy'])->name('privacy-policy');
Route::get('/terms-of-service', [FooterController::class, 'termsOfService'])->name('terms-of-service');
Route::get('/staff', [StaffController::class, 'index'])->name('staff');
Route::get('/bug-report', [BugReportController::class, 'show'])->name('bug-report');
Route::post('/bug-report', [BugReportController::class, 'submit'])->middleware('auth')->name('bug-report.submit');
Route::get('/contact', [ApplicationController::class, 'show'])->name('contact.show');
Route::post('/contact', [ApplicationController::class, 'submit'])->middleware('throttle:6,1')->name('contact.submit');
$cpPrefix = trim((string) config('cpad.webroot', config('cp.webroot', 'cp')), '/');
// ── LEGACY RSS (.xml feeds — old site compatibility) ──────────────────────────
Route::get('/rss/latest-uploads.xml', [RssFeedController::class, 'latestUploads'])->name('rss.uploads');
Route::get('/rss/latest-skins.xml', [RssFeedController::class, 'latestSkins'])->name('rss.skins');
Route::get('/rss/latest-wallpapers.xml', [RssFeedController::class, 'latestWallpapers'])->name('rss.wallpapers');
Route::get('/rss/latest-photos.xml', [RssFeedController::class, 'latestPhotos'])->name('rss.photos');
// ── RSS 2.0 feeds (/rss/*) ────────────────────────────────────────────────────
Route::middleware('throttle:60,1')->group(function () {
Route::get('/rss', GlobalFeedController::class)->name('rss.global');
Route::prefix('rss/discover')->name('rss.discover.')->group(function () {
Route::get('/', [DiscoverFeedController::class, 'index'])->name('index');
Route::get('/trending', [DiscoverFeedController::class, 'trending'])->name('trending');
Route::get('/fresh', [DiscoverFeedController::class, 'fresh'])->name('fresh');
Route::get('/rising', [DiscoverFeedController::class, 'rising'])->name('rising');
});
Route::prefix('rss/explore')->name('rss.explore.')->group(function () {
Route::get('/{type}', [ExploreFeedController::class, 'byType'])
->where('type', 'artworks|wallpapers|skins|photography|other')
->name('type');
Route::get('/{type}/{mode}', [ExploreFeedController::class, 'byTypeMode'])
->where('type', 'artworks|wallpapers|skins|photography|other')
->where('mode', 'trending|latest|best')
->name('type.mode');
});
Route::get('/rss/tag/{slug}', TagFeedController::class)
->where('slug', '[a-z0-9\-]+')
->name('rss.tag');
Route::get('/rss/creator/{username}', CreatorFeedController::class)
->where('username', '[A-Za-z0-9_\-]{3,20}')
->name('rss.creator');
Route::get('/rss/blog', BlogFeedController::class)->name('rss.blog');
});
// ── CREATORS (/creators/*) ────────────────────────────────────────────────────
Route::prefix('creators')->name('creators.')->group(function () {
Route::get('/top', [\App\Http\Controllers\User\TopAuthorsController::class, 'index'])->name('top');
Route::get('/rising', [DiscoverController::class, 'risingCreators'])->name('rising');
});
Route::get('/leaderboard', \App\Http\Controllers\Web\LeaderboardPageController::class)
->name('leaderboard');
// ── STORIES (/stories/*) ──────────────────────────────────────────────────────
Route::prefix('stories')->name('stories.')->group(function () {
Route::get('/', [StoryController::class, 'index'])->name('index');
Route::get('/tag/{tag}', [StoryController::class, 'tag'])
->where('tag', '[a-z0-9\-]+')
->name('tag');
Route::get('/creator/{username}', [StoryController::class, 'creator'])
->where('username', '[A-Za-z0-9_\-]{1,50}')
->name('creator');
Route::get('/author/{username}', fn (string $username) => redirect()->route('stories.creator', ['username' => $username], 301))
->where('username', '[A-Za-z0-9_\-]{1,50}')
->name('author');
Route::get('/category/{category}', [StoryController::class, 'category'])
->where('category', 'creator_story|tutorial|interview|project_breakdown|announcement|resource')
->name('category');
Route::get('/{slug}', [StoryController::class, 'show'])
->where('slug', '[a-z0-9\-]+')
->name('show');
});
Route::middleware(['auth', 'ensure.onboarding.complete', 'creator.access'])->prefix('creator/stories')->name('creator.stories.')->group(function () {
Route::get('/', [StoryController::class, 'dashboard'])->name('index');
Route::get('/create', [StoryController::class, 'create'])->name('create');
Route::post('/', [StoryController::class, 'store'])->name('store');
Route::get('/artworks/search', [StoryController::class, 'searchArtworks'])->name('artworks.search');
Route::post('/upload-image', [StoryController::class, 'uploadImage'])->name('upload-image');
Route::get('/{story}/edit', [StoryController::class, 'edit'])->name('edit');
Route::put('/{story}', [StoryController::class, 'update'])->name('update');
Route::delete('/{story}', [StoryController::class, 'destroy'])->name('destroy');
Route::post('/{story}/autosave', [StoryController::class, 'autosave'])->name('autosave');
Route::post('/{story}/submit-review', [StoryController::class, 'submitForReview'])->name('submit-review');
Route::post('/{story}/publish', [StoryController::class, 'publishNow'])->name('publish-now');
Route::get('/{story}/preview', [StoryController::class, 'preview'])->name('preview');
Route::get('/{story}/analytics', [StoryController::class, 'analytics'])->name('analytics');
});
// ── TAGS ──────────────────────────────────────────────────────────────────────
Route::get('/tags', [\App\Http\Controllers\Web\TagController::class, 'index'])->name('tags.index');
Route::get('/tag/{tag:slug}', [\App\Http\Controllers\Web\TagController::class, 'show'])
->where('tag', '[a-z0-9\-]+')
->name('tags.show');
Route::get('/tags/{tag}', [\App\Http\Controllers\Web\Posts\HashtagFeedController::class, 'index'])
->where('tag', '[A-Za-z][A-Za-z0-9_]{1,63}')
->name('feed.hashtag');
// ── CATEGORIES DIRECTORY ─────────────────────────────────────────────────────
Route::get('/categories', [CategoryController::class, 'index'])->name('categories.index');
// ── FOLLOWING (shortcut) ──────────────────────────────────────────────────────
Route::middleware('auth')->get('/following', function () {
return redirect()->route('dashboard.following');
})->name('following.redirect');
// ── ART / ARTWORKS ────────────────────────────────────────────────────────────
Route::get('/art/{id}/{slug?}', [ArtworkPageController::class, 'show'])
->where('id', '\d+')
->name('art.show');
Route::get('/download/artwork/{id}', ArtworkDownloadController::class)
->whereNumber('id')
->middleware('throttle:downloads')
->name('art.download');
// ── NEWS (/news/*) ────────────────────────────────────────────────────────────
Route::prefix('news')->name('news.')->group(function () {
Route::get('/', [FrontendNewsController::class, 'index'])->name('index');
Route::get('category/{slug}', [FrontendNewsController::class, 'category'])->name('category');
Route::get('tag/{slug}', [FrontendNewsController::class, 'tag'])->name('tag');
Route::get('{slug}', [FrontendNewsController::class, 'show'])
->where('slug', '[a-z0-9\-]+')
->name('show');
});
Route::get('/rss/news', [NewsRssController::class, 'feed'])->name('news.rss');
Route::get('/collections/featured', [\App\Http\Controllers\Web\CollectionDiscoveryController::class, 'featured'])
->name('collections.featured');
Route::get('/collections/trending', [\App\Http\Controllers\Web\CollectionDiscoveryController::class, 'trending'])
->name('collections.trending');
Route::get('/collections/editorial', [\App\Http\Controllers\Web\CollectionDiscoveryController::class, 'editorial'])
->name('collections.editorial');
Route::get('/collections/community', [\App\Http\Controllers\Web\CollectionDiscoveryController::class, 'community'])
->name('collections.community');
Route::get('/collections/seasonal', [\App\Http\Controllers\Web\CollectionDiscoveryController::class, 'seasonal'])
->name('collections.seasonal');
Route::get('/collections/campaigns/{campaignKey}', [\App\Http\Controllers\Web\CollectionDiscoveryController::class, 'campaign'])
->where('campaignKey', '[A-Za-z0-9_-]{1,80}')
->name('collections.campaign.show');
Route::get('/collections/program/{programKey}', [\App\Http\Controllers\Web\CollectionDiscoveryController::class, 'program'])
->where('programKey', '[A-Za-z0-9_-]{1,80}')
->name('collections.program.show');
Route::get('/collections/recommended', [\App\Http\Controllers\Web\CollectionDiscoveryController::class, 'recommended'])
->name('collections.recommended');
Route::get('/collections/search', [\App\Http\Controllers\Web\CollectionDiscoveryController::class, 'search'])
->name('collections.search');
Route::middleware('auth')->group(function () {
Route::post('/me/saved/collections/lists', [CollectionSavedLibraryController::class, 'storeList'])
->name('me.saved.collections.lists.store');
Route::get('/me/saved/collections/lists/{listSlug}', [\App\Http\Controllers\User\SavedCollectionController::class, 'showList'])
->name('me.saved.collections.lists.show');
Route::post('/me/saved/collections/{collection}/lists', [CollectionSavedLibraryController::class, 'storeItem'])
->name('me.saved.collections.lists.items.store');
Route::patch('/me/saved/collections/{collection}/note', [CollectionSavedLibraryController::class, 'updateNote'])
->name('me.saved.collections.notes.update');
Route::post('/me/saved/collections/lists/{list}/items/reorder', [CollectionSavedLibraryController::class, 'reorderItems'])
->name('me.saved.collections.lists.items.reorder');
Route::delete('/me/saved/collections/lists/{list}/items/{collection}', [CollectionSavedLibraryController::class, 'destroyItem'])
->name('me.saved.collections.lists.items.destroy');
Route::post('/collections/{collection}/follow', [\App\Http\Controllers\CollectionEngagementController::class, 'follow'])->name('collections.follow');
Route::delete('/collections/{collection}/follow', [\App\Http\Controllers\CollectionEngagementController::class, 'unfollow'])->name('collections.unfollow');
Route::post('/collections/{collection}/like', [\App\Http\Controllers\CollectionEngagementController::class, 'like'])->name('collections.like');
Route::delete('/collections/{collection}/like', [\App\Http\Controllers\CollectionEngagementController::class, 'unlike'])->name('collections.unlike');
Route::post('/collections/{collection}/save', [\App\Http\Controllers\CollectionEngagementController::class, 'save'])->name('collections.save');
Route::delete('/collections/{collection}/save', [\App\Http\Controllers\CollectionEngagementController::class, 'unsave'])->name('collections.unsave');
Route::post('/collections/{collection}/submissions', [\App\Http\Controllers\CollectionSubmissionController::class, 'store'])->name('collections.submissions.store');
Route::delete('/collections/submissions/{submission}', [\App\Http\Controllers\CollectionSubmissionController::class, 'destroy'])->name('collections.submissions.destroy');
Route::post('/collections/submissions/{submission}/approve', [\App\Http\Controllers\CollectionSubmissionController::class, 'approve'])->name('collections.submissions.approve');
Route::post('/collections/submissions/{submission}/reject', [\App\Http\Controllers\CollectionSubmissionController::class, 'reject'])->name('collections.submissions.reject');
Route::post('/collections/{collection}/comments', [\App\Http\Controllers\CollectionCommentController::class, 'store'])->name('collections.comments.store');
Route::delete('/collections/{collection}/comments/{comment}', [\App\Http\Controllers\CollectionCommentController::class, 'destroy'])->name('collections.comments.destroy');
});
Route::post('/collections/{collection}/share', [\App\Http\Controllers\CollectionEngagementController::class, 'share'])
->name('collections.share');
Route::get('/collections/{collection}/comments', [\App\Http\Controllers\CollectionCommentController::class, 'index'])
->name('collections.comments.index');
Route::get('/collections/series/{seriesKey}', [ProfileCollectionController::class, 'showSeries'])
->where('seriesKey', '[A-Za-z0-9_-]{1,80}')
->name('collections.series.show');
// ── PROFILES (@username) ──────────────────────────────────────────────────────
Route::get('/@{username}/gallery', [ProfileController::class, 'showGalleryByUsername'])
->where('username', '[A-Za-z0-9_-]{3,20}')
->name('profile.gallery');
Route::get('/@{username}/collections/{slug}', [ProfileCollectionController::class, 'show'])
->where('username', '[A-Za-z0-9_-]{3,20}')
->where('slug', '[a-z0-9\-]{2,140}')
->name('profile.collections.show');
Route::get('/@{username}/{tab}', [ProfileController::class, 'showTabByUsername'])
->where('username', '[A-Za-z0-9_-]{3,20}')
->where('tab', 'posts|artworks|stories|achievements|collections|about|stats|favourites|activity')
->name('profile.tab');
Route::get('/@{username}', [ProfileController::class, 'showByUsername'])
->where('username', '[A-Za-z0-9_-]{3,20}')
->name('profile.show');
Route::middleware('auth')->post('/@{username}/follow', [ProfileController::class, 'toggleFollow'])
->where('username', '[A-Za-z0-9_-]{3,20}')
->name('profile.follow');
Route::middleware('auth')->post('/@{username}/comment', [ProfileController::class, 'storeComment'])
->where('username', '[A-Za-z0-9_-]{3,20}')
->name('profile.comment');
Route::middleware('auth')->get('/me/saved/collections', [SavedCollectionController::class, 'index'])
->name('me.saved.collections');
// ── DASHBOARD ─────────────────────────────────────────────────────────────────
Route::get('/dashboard', [DashboardController::class, 'index'])
->middleware(['auth', 'verified'])
->name('dashboard');
Route::middleware(['auth', 'creator.access'])->prefix('creator')->name('creator.')->group(function () {
Route::get('/artworks', fn () => redirect()->route('studio.artworks'))->name('artworks');
Route::get('/analytics', fn () => redirect()->route('studio.analytics'))->name('analytics');
});
Route::middleware(['auth', \App\Http\Middleware\NoIndexDashboard::class])
->get('/manage', [\App\Http\Controllers\Dashboard\ManageController::class, 'index'])
->name('manage');
Route::middleware(['auth', \App\Http\Middleware\NoIndexDashboard::class])->prefix('dashboard')->name('dashboard.')->group(function () {
Route::get('/artworks', [DashboardArtworkController::class, 'index'])->name('artworks.index');
Route::get('/artworks/{id}/edit', [DashboardArtworkController::class, 'edit'])->whereNumber('id')->name('artworks.edit');
Route::put('/artworks/{id}', [DashboardArtworkController::class, 'update'])->whereNumber('id')->name('artworks.update');
Route::delete('/artworks/{id}', [DashboardArtworkController::class, 'destroy'])->whereNumber('id')->name('artworks.destroy');
Route::get('/favorites', [\App\Http\Controllers\Dashboard\FavoriteController::class, 'index'])->name('favorites');
Route::delete('/favorites/{artwork}', [\App\Http\Controllers\Dashboard\FavoriteController::class, 'destroy'])->name('favorites.destroy');
Route::get('/followers', [\App\Http\Controllers\Dashboard\FollowerController::class, 'index'])->name('followers');
Route::get('/following', [\App\Http\Controllers\Dashboard\FollowingController::class, 'index'])->name('following');
Route::get('/comments', fn () => redirect()->route('dashboard.comments.received', request()->query(), 302))->name('comments');
Route::get('/comments/received', [\App\Http\Controllers\Dashboard\CommentController::class, 'received'])->name('comments.received');
Route::get('/notifications', [\App\Http\Controllers\Dashboard\NotificationController::class, 'index'])->name('notifications');
Route::get('/gallery', [\App\Http\Controllers\Dashboard\DashboardGalleryController::class, 'index'])->name('gallery');
Route::get('/awards', [\App\Http\Controllers\Dashboard\DashboardAwardsController::class, 'index'])->name('awards');
});
// Canonical dashboard profile / settings
Route::middleware(['auth'])->get('/dashboard/profile', [ProfileController::class, 'editSettings'])->name('dashboard.profile');
Route::middleware(['auth'])->get('/settings/profile', [ProfileController::class, 'editSettings'])->name('settings.profile');
// ── STUDIO Pro (/studio/*) ────────────────────────────────────────────────────
Route::middleware(['auth', 'ensure.onboarding.complete'])->prefix('studio')->name('studio.')->group(function () {
Route::get('/', [StudioController::class, 'index'])->name('index');
Route::get('/artworks', [StudioController::class, 'artworks'])->name('artworks');
Route::get('/artworks/drafts', [StudioController::class, 'drafts'])->name('drafts');
Route::get('/artworks/archived', [StudioController::class, 'archived'])->name('archived');
Route::get('/artworks/{id}/edit', [StudioController::class, 'edit'])->whereNumber('id')->name('artworks.edit');
Route::get('/artworks/{id}/analytics', [StudioController::class, 'analytics'])->whereNumber('id')->name('artworks.analytics');
Route::get('/analytics', [StudioController::class, 'analyticsOverview'])->name('analytics');
Route::get('/cards', [\App\Http\Controllers\Studio\StudioNovaCardsController::class, 'index'])->name('cards.index');
Route::get('/cards/create', [\App\Http\Controllers\Studio\StudioNovaCardsController::class, 'create'])->name('cards.create');
Route::post('/cards/remix/{id}', [\App\Http\Controllers\Studio\StudioNovaCardsController::class, 'remix'])->whereNumber('id')->name('cards.remix');
Route::get('/cards/{id}/edit', [\App\Http\Controllers\Studio\StudioNovaCardsController::class, 'edit'])->whereNumber('id')->name('cards.edit');
Route::get('/cards/{id}/preview', [\App\Http\Controllers\Studio\StudioNovaCardsController::class, 'preview'])->whereNumber('id')->name('cards.preview');
Route::get('/cards/{id}/analytics', [\App\Http\Controllers\Studio\StudioNovaCardsController::class, 'analytics'])->whereNumber('id')->name('cards.analytics');
});
Route::get('/cards', [\App\Http\Controllers\Web\NovaCardsController::class, 'index'])->name('cards.index');
Route::get('/cards/popular', [\App\Http\Controllers\Web\NovaCardsController::class, 'popular'])->name('cards.popular');
Route::get('/cards/rising', [\App\Http\Controllers\Web\NovaCardsController::class, 'rising'])->name('cards.rising');
Route::get('/cards/remixed', [\App\Http\Controllers\Web\NovaCardsController::class, 'remixed'])->name('cards.remixed');
Route::get('/cards/remix-highlights', [\App\Http\Controllers\Web\NovaCardsController::class, 'remixHighlights'])->name('cards.remix-highlights');
Route::get('/cards/editorial', [\App\Http\Controllers\Web\NovaCardsController::class, 'editorial'])->name('cards.editorial');
Route::get('/cards/seasonal', [\App\Http\Controllers\Web\NovaCardsController::class, 'seasonal'])->name('cards.seasonal');
Route::get('/cards/collections/{slug}-{id}', [\App\Http\Controllers\Web\NovaCardsController::class, 'collection'])
->where('slug', '[a-z0-9\-]+')
->whereNumber('id')
->name('cards.collections.show');
Route::get('/cards/{slug}-{id}/lineage', [\App\Http\Controllers\Web\NovaCardsController::class, 'lineage'])
->where('slug', '[a-z0-9\-]+')
->whereNumber('id')
->name('cards.lineage');
Route::get('/cards/challenges', [\App\Http\Controllers\Web\NovaCardsController::class, 'challenges'])->name('cards.challenges');
Route::get('/cards/challenges/{slug}', [\App\Http\Controllers\Web\NovaCardsController::class, 'challenge'])->name('cards.challenges.show');
Route::get('/cards/templates', [\App\Http\Controllers\Web\NovaCardsController::class, 'templates'])->name('cards.templates');
Route::get('/cards/assets', [\App\Http\Controllers\Web\NovaCardsController::class, 'assets'])->name('cards.assets');
Route::get('/cards/category/{categorySlug}', [\App\Http\Controllers\Web\NovaCardsController::class, 'category'])->name('cards.category');
Route::get('/cards/mood/{moodSlug}', [\App\Http\Controllers\Web\NovaCardsController::class, 'mood'])->name('cards.mood');
Route::get('/cards/style/{styleSlug}', [\App\Http\Controllers\Web\NovaCardsController::class, 'style'])->name('cards.style');
Route::get('/cards/palette/{paletteSlug}', [\App\Http\Controllers\Web\NovaCardsController::class, 'palette'])->name('cards.palette');
Route::get('/cards/tag/{tagSlug}', [\App\Http\Controllers\Web\NovaCardsController::class, 'tag'])->name('cards.tag');
Route::get('/cards/creator/{username}/portfolio', [\App\Http\Controllers\Web\NovaCardsController::class, 'creatorPortfolio'])
->where('username', '[A-Za-z0-9_-]{3,20}')
->name('cards.creator.portfolio');
Route::get('/cards/creator/{username}', [\App\Http\Controllers\Web\NovaCardsController::class, 'creator'])
->where('username', '[A-Za-z0-9_-]{3,20}')
->name('cards.creator');
Route::get('/cards/{slug}-{id}', [\App\Http\Controllers\Web\NovaCardsController::class, 'show'])
->where('slug', '[a-z0-9\-]+')
->whereNumber('id')
->name('cards.show');
Route::middleware('auth')->group(function () {
Route::post('/cards/{card}/comments', [\App\Http\Controllers\NovaCardCommentController::class, 'store'])
->whereNumber('card')
->name('cards.comments.store');
Route::delete('/cards/{card}/comments/{comment}', [\App\Http\Controllers\NovaCardCommentController::class, 'destroy'])
->whereNumber('card')
->whereNumber('comment')
->name('cards.comments.destroy');
});
Route::middleware(['auth', 'admin.moderation'])->prefix('cp/cards')->name('cp.cards.')->group(function () {
Route::get('/', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'index'])->name('index');
Route::patch('/{card}', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'updateCard'])->whereNumber('card')->name('update');
Route::patch('/creators/{user}', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'updateCreator'])->whereNumber('user')->name('creators.update');
Route::get('/templates', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'templates'])->name('templates.index');
Route::post('/templates', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'storeTemplate'])->name('templates.store');
Route::patch('/templates/{template}', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'updateTemplate'])->whereNumber('template')->name('templates.update');
Route::get('/asset-packs', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'assetPacks'])->name('asset-packs.index');
Route::post('/asset-packs', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'storeAssetPack'])->name('asset-packs.store');
Route::patch('/asset-packs/{assetPack}', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'updateAssetPack'])->whereNumber('assetPack')->name('asset-packs.update');
Route::get('/challenges', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'challenges'])->name('challenges.index');
Route::post('/challenges', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'storeChallenge'])->name('challenges.store');
Route::patch('/challenges/{challenge}', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'updateChallenge'])->whereNumber('challenge')->name('challenges.update');
Route::get('/collections', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'collections'])->name('collections.index');
Route::post('/collections', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'storeCollection'])->name('collections.store');
Route::patch('/collections/{collection}', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'updateCollection'])->whereNumber('collection')->name('collections.update');
Route::post('/collections/{collection}/cards', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'storeCollectionCard'])->whereNumber('collection')->name('collections.cards.store');
Route::delete('/collections/{collection}/cards/{card}', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'destroyCollectionCard'])->whereNumber('collection')->whereNumber('card')->name('collections.cards.destroy');
Route::post('/categories', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'storeCategory'])->name('categories.store');
Route::patch('/categories/{category}', [\App\Http\Controllers\Settings\NovaCardAdminController::class, 'updateCategory'])->whereNumber('category')->name('categories.update');
});
// ── SETTINGS / PROFILE EDIT ───────────────────────────────────────────────────
Route::middleware(['auth', 'normalize.username', 'ensure.onboarding.complete'])->group(function () {
Route::get('/profile', fn () => redirect()->route('dashboard.profile', [], 301))->name('legacy.profile.redirect');
Route::get('/settings', fn () => redirect()->route('dashboard.profile', [], 302))->name('settings');
Route::get('/profile/edit', fn () => redirect()->route('dashboard.profile', [], 302))->name('profile.edit');
Route::match(['post','put','patch'], '/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
Route::match(['post', 'put'], '/profile/password', [ProfileController::class, 'password'])->name('profile.password');
Route::post('/avatar/upload', [AvatarController::class, 'upload'])->middleware('throttle:20,1')->name('avatar.upload');
Route::post('/settings/profile/update', [ProfileController::class, 'updateProfileSection'])->middleware('forum.bot.protection:profile_update')->name('settings.profile.update');
Route::post('/settings/account/username', [ProfileController::class, 'updateUsername'])->middleware('forum.bot.protection:profile_update')->name('settings.account.username');
Route::post('/settings/account/update', [ProfileController::class, 'updateAccountSection'])->middleware('forum.bot.protection:profile_update')->name('settings.account.update');
Route::post('/settings/email/request', [ProfileController::class, 'requestEmailChange'])
->middleware('throttle:email-change-request')
->name('settings.email.request');
Route::post('/settings/email/verify', [ProfileController::class, 'verifyEmailChange'])
->middleware('throttle:10,1')
->name('settings.email.verify');
Route::post('/settings/personal/update', [ProfileController::class, 'updatePersonalSection'])->middleware('forum.bot.protection:profile_update')->name('settings.personal.update');
Route::post('/settings/notifications/update', [ProfileController::class, 'updateNotificationsSection'])->middleware('forum.bot.protection:profile_update')->name('settings.notifications.update');
Route::post('/settings/security/password', [ProfileController::class, 'updateSecurityPassword'])->middleware('forum.bot.protection:profile_update')->name('settings.security.password');
Route::get('/settings/collections/create', [CollectionManageController::class, 'create'])->name('settings.collections.create');
Route::post('/settings/collections', [CollectionManageController::class, 'store'])->name('settings.collections.store');
Route::post('/settings/collections/smart-preview', [CollectionManageController::class, 'smartPreview'])->name('settings.collections.smart.preview');
Route::post('/settings/collections/reorder-profile', [CollectionManageController::class, 'reorderProfile'])->name('settings.collections.reorder-profile');
Route::get('/settings/collections/dashboard', [CollectionInsightsController::class, 'dashboard'])->name('settings.collections.dashboard');
Route::get('/settings/collections/search', [CollectionInsightsController::class, 'search'])->name('settings.collections.search');
Route::post('/settings/collections/bulk-actions', [CollectionInsightsController::class, 'bulkActions'])->name('settings.collections.bulk-actions');
Route::get('/settings/collections/{collection}', [CollectionManageController::class, 'show'])->name('settings.collections.show');
Route::get('/settings/collections/{collection}/edit', [CollectionManageController::class, 'edit'])->name('settings.collections.edit');
Route::get('/settings/collections/{collection}/analytics', [CollectionInsightsController::class, 'analytics'])->name('settings.collections.analytics');
Route::get('/settings/collections/{collection}/health', [CollectionInsightsController::class, 'health'])->name('settings.collections.health');
Route::get('/settings/collections/{collection}/history', [CollectionInsightsController::class, 'history'])->name('settings.collections.history');
Route::post('/settings/collections/{collection}/history/{history}/restore', [CollectionInsightsController::class, 'restoreHistory'])->name('settings.collections.history.restore');
Route::patch('/settings/collections/{collection}', [CollectionManageController::class, 'update'])->name('settings.collections.update');
Route::post('/settings/collections/{collection}/presentation', [CollectionManageController::class, 'updatePresentation'])->name('settings.collections.presentation');
Route::post('/settings/collections/{collection}/campaign', [CollectionManageController::class, 'updateCampaign'])->name('settings.collections.campaign');
Route::post('/settings/collections/{collection}/series', [CollectionManageController::class, 'updateSeries'])->name('settings.collections.series');
Route::post('/settings/collections/{collection}/lifecycle', [CollectionManageController::class, 'updateLifecycle'])->name('settings.collections.lifecycle');
Route::post('/settings/collections/{collection}/linked-collections', [CollectionManageController::class, 'syncLinkedCollections'])->name('settings.collections.linked.sync');
Route::post('/settings/collections/{collection}/entity-links', [CollectionManageController::class, 'syncEntityLinks'])->name('settings.collections.entity-links.sync');
Route::delete('/settings/collections/{collection}', [CollectionManageController::class, 'destroy'])->name('settings.collections.destroy');
Route::post('/settings/collections/{collection}/feature', [CollectionManageController::class, 'feature'])->name('settings.collections.feature');
Route::delete('/settings/collections/{collection}/feature', [CollectionManageController::class, 'unfeature'])->name('settings.collections.unfeature');
Route::patch('/settings/collections/{collection}/smart-rules', [CollectionManageController::class, 'updateSmartRules'])->name('settings.collections.smart.rules');
Route::post('/settings/collections/{collection}/ai/suggest-title', [CollectionAiController::class, 'suggestTitle'])->name('settings.collections.ai.suggest-title');
Route::post('/settings/collections/{collection}/ai/suggest-summary', [CollectionAiController::class, 'suggestSummary'])->name('settings.collections.ai.suggest-summary');
Route::post('/settings/collections/{collection}/ai/suggest-cover', [CollectionAiController::class, 'suggestCover'])->name('settings.collections.ai.suggest-cover');
Route::post('/settings/collections/{collection}/ai/suggest-grouping', [CollectionAiController::class, 'suggestGrouping'])->name('settings.collections.ai.suggest-grouping');
Route::post('/settings/collections/{collection}/ai/suggest-related-artworks', [CollectionAiController::class, 'suggestRelatedArtworks'])->name('settings.collections.ai.suggest-related-artworks');
Route::post('/settings/collections/{collection}/ai/suggest-tags', [CollectionAiController::class, 'suggestTags'])->name('settings.collections.ai.suggest-tags');
Route::post('/settings/collections/{collection}/ai/suggest-seo-description', [CollectionAiController::class, 'suggestSeoDescription'])->name('settings.collections.ai.suggest-seo-description');
Route::post('/settings/collections/{collection}/ai/explain-smart-rules', [CollectionAiController::class, 'explainSmartRules'])->name('settings.collections.ai.explain-smart-rules');
Route::post('/settings/collections/{collection}/ai/suggest-split-themes', [CollectionAiController::class, 'suggestSplitThemes'])->name('settings.collections.ai.suggest-split-themes');
Route::post('/settings/collections/{collection}/ai/suggest-merge-idea', [CollectionAiController::class, 'suggestMergeIdea'])->name('settings.collections.ai.suggest-merge-idea');
Route::post('/settings/collections/{collection}/ai/detect-weak-metadata', [CollectionAiController::class, 'detectWeakMetadata'])->name('settings.collections.ai.detect-weak-metadata');
Route::post('/settings/collections/{collection}/ai/suggest-stale-refresh', [CollectionAiController::class, 'suggestStaleRefresh'])->name('settings.collections.ai.suggest-stale-refresh');
Route::post('/settings/collections/{collection}/ai/suggest-campaign-fit', [CollectionAiController::class, 'suggestCampaignFit'])->name('settings.collections.ai.suggest-campaign-fit');
Route::post('/settings/collections/{collection}/ai/suggest-related-collections-to-link', [CollectionAiController::class, 'suggestRelatedCollectionsToLink'])->name('settings.collections.ai.suggest-related-collections-to-link');
Route::post('/settings/collections/{collection}/ai/quality-review', [CollectionInsightsController::class, 'qualityReview'])->name('settings.collections.ai.quality-review');
Route::post('/settings/collections/{collection}/workflow', [CollectionInsightsController::class, 'workflowUpdate'])->name('settings.collections.workflow');
Route::post('/settings/collections/{collection}/quality-refresh', [CollectionInsightsController::class, 'qualityRefresh'])->name('settings.collections.quality-refresh');
Route::post('/settings/collections/{collection}/canonicalize', [CollectionInsightsController::class, 'canonicalize'])->name('settings.collections.canonicalize');
Route::post('/settings/collections/{collection}/merge', [CollectionInsightsController::class, 'merge'])->name('settings.collections.merge');
Route::post('/settings/collections/{collection}/merge/reject', [CollectionInsightsController::class, 'rejectDuplicate'])->name('settings.collections.merge.reject');
Route::post('/settings/collections/{collection}/artworks', [CollectionManageController::class, 'attachArtworks'])->name('settings.collections.artworks.attach');
Route::get('/settings/collections/artworks/{artwork}/options', [CollectionManageController::class, 'artworkCollectionOptions'])->name('settings.collections.artworks.options');
Route::get('/settings/collections/{collection}/artworks/available', [CollectionManageController::class, 'availableArtworks'])->name('settings.collections.artworks.available');
Route::delete('/settings/collections/{collection}/artworks/{artwork}', [CollectionManageController::class, 'removeArtwork'])->name('settings.collections.artworks.remove');
Route::post('/settings/collections/{collection}/reorder', [CollectionManageController::class, 'reorderArtworks'])->name('settings.collections.artworks.reorder');
Route::post('/settings/collections/{collection}/members', [\App\Http\Controllers\CollectionCollaborationController::class, 'store'])->name('settings.collections.members.store');
Route::patch('/settings/collections/{collection}/members/{member}', [\App\Http\Controllers\CollectionCollaborationController::class, 'update'])->name('settings.collections.members.update');
Route::post('/settings/collections/{collection}/members/{member}/transfer', [\App\Http\Controllers\CollectionCollaborationController::class, 'transfer'])->name('settings.collections.members.transfer');
Route::delete('/settings/collections/{collection}/members/{member}', [\App\Http\Controllers\CollectionCollaborationController::class, 'destroy'])->name('settings.collections.members.destroy');
Route::post('/settings/collections/members/{member}/accept', [\App\Http\Controllers\CollectionCollaborationController::class, 'accept'])->name('settings.collections.members.accept');
Route::post('/settings/collections/members/{member}/decline', [\App\Http\Controllers\CollectionCollaborationController::class, 'decline'])->name('settings.collections.members.decline');
Route::get('/settings/collections/surfaces', [CollectionSurfaceController::class, 'index'])->name('settings.collections.surfaces.index');
Route::post('/settings/collections/surfaces/definitions', [CollectionSurfaceController::class, 'storeDefinition'])->name('settings.collections.surfaces.definitions.store');
Route::patch('/settings/collections/surfaces/definitions/{definition}', [CollectionSurfaceController::class, 'updateDefinition'])->name('settings.collections.surfaces.definitions.update');
Route::delete('/settings/collections/surfaces/definitions/{definition}', [CollectionSurfaceController::class, 'destroyDefinition'])->name('settings.collections.surfaces.definitions.destroy');
Route::post('/settings/collections/surfaces/placements', [CollectionSurfaceController::class, 'storePlacement'])->name('settings.collections.surfaces.placements.store');
Route::patch('/settings/collections/surfaces/placements/{placement}', [CollectionSurfaceController::class, 'updatePlacement'])->name('settings.collections.surfaces.placements.update');
Route::delete('/settings/collections/surfaces/placements/{placement}', [CollectionSurfaceController::class, 'destroyPlacement'])->name('settings.collections.surfaces.placements.destroy');
Route::get('/settings/collections/surfaces/definitions/{definition}/preview', [CollectionSurfaceController::class, 'preview'])->name('settings.collections.surfaces.preview');
Route::post('/settings/collections/surfaces/batch-editorial', [CollectionSurfaceController::class, 'batchEditorial'])->name('settings.collections.surfaces.batch-editorial');
Route::get('/staff/collections/programming', [CollectionProgrammingController::class, 'index'])->name('staff.collections.programming');
Route::post('/staff/collections/programs', [CollectionProgrammingController::class, 'storeProgram'])->name('staff.collections.programs.store');
Route::patch('/staff/collections/programs/{program}', [CollectionProgrammingController::class, 'updateProgram'])->name('staff.collections.programs.update');
Route::post('/staff/collections/surfaces/preview', [CollectionProgrammingController::class, 'preview'])->name('staff.collections.surfaces.preview');
Route::post('/staff/collections/eligibility/refresh', [CollectionProgrammingController::class, 'refreshEligibility'])->name('staff.collections.eligibility.refresh');
Route::post('/staff/collections/duplicate-scan', [CollectionProgrammingController::class, 'duplicateScan'])->name('staff.collections.duplicate-scan');
Route::post('/staff/collections/recommendation-refresh', [CollectionProgrammingController::class, 'refreshRecommendations'])->name('staff.collections.recommendation-refresh');
Route::post('/staff/collections/metadata', [CollectionProgrammingController::class, 'updateMetadata'])->name('staff.collections.metadata.update');
Route::post('/staff/collections/merge-queue/canonicalize', [CollectionProgrammingController::class, 'canonicalizeCandidate'])->name('staff.collections.merge-queue.canonicalize');
Route::post('/staff/collections/merge-queue/merge', [CollectionProgrammingController::class, 'mergeCandidate'])->name('staff.collections.merge-queue.merge');
Route::post('/staff/collections/merge-queue/reject', [CollectionProgrammingController::class, 'rejectCandidate'])->name('staff.collections.merge-queue.reject');
});
// ── UPLOAD ────────────────────────────────────────────────────────────────────
Route::middleware(['auth', 'ensure.onboarding.complete'])->group(function () {
Route::get('/upload', function () {
$contentTypes = ContentType::with(['rootCategories.children'])->get()->map(function ($ct) {
return [
'id' => $ct->id,
'name' => $ct->name,
'categories' => $ct->rootCategories->map(function ($c) {
return [
'id' => $c->id,
'name' => $c->name,
'children' => $c->children->map(function ($ch) {
return ['id' => $ch->id, 'name' => $ch->name];
})->values()->all(),
];
})->values()->all(),
];
})->values()->all();
return Inertia::render('Upload/Index', [
'draftId' => null,
'content_types' => $contentTypes,
'suggested_tags' => [],
'filesCdnUrl' => config('cdn.files_url'),
'chunkSize' => (int) config('uploads.chunk.max_bytes', 5242880),
'feature_flags' => [
'uploads_v2' => (bool) config('features.uploads_v2', false),
],
]);
})->name('upload');
Route::get('/upload/draft/{id}', function (string $id) {
$contentTypes = ContentType::with(['rootCategories.children'])->get()->map(function ($ct) {
return [
'id' => $ct->id,
'name' => $ct->name,
'categories' => $ct->rootCategories->map(function ($c) {
return [
'id' => $c->id,
'name' => $c->name,
'children' => $c->children->map(function ($ch) {
return ['id' => $ch->id, 'name' => $ch->name];
})->values()->all(),
];
})->values()->all(),
];
})->values()->all();
return Inertia::render('Upload/Index', [
'draftId' => $id,
'content_types' => $contentTypes,
'suggested_tags' => [],
'filesCdnUrl' => config('cdn.files_url'),
'chunkSize' => (int) config('uploads.chunk.max_bytes', 5242880),
'feature_flags' => [
'uploads_v2' => (bool) config('features.uploads_v2', false),
],
]);
})->whereUuid('id')->name('upload.draft');
});
// ── AUTH ──────────────────────────────────────────────────────────────────────
require __DIR__.'/auth.php';
// ── LEGACY ROUTES ─────────────────────────────────────────────────────────────
require __DIR__.'/legacy.php';
// ── SEARCH ────────────────────────────────────────────────────────────────────
Route::get('/search', [\App\Http\Controllers\Web\SearchController::class, 'index'])->name('search');
// ── MISC ──────────────────────────────────────────────────────────────────────
Route::view('/data-deletion', 'privacy.data-deletion')->name('privacy.data_deletion');
Route::view('/blank', 'blank')->name('blank');
// ── MESSAGES ──────────────────────────────────────────────────────────────────
Route::middleware(['auth', 'ensure.onboarding.complete'])
->get('/messages/attachments/{id}', [\App\Http\Controllers\Api\Messaging\AttachmentController::class, 'show'])
->whereNumber('id')
->name('messages.attachments.show');
Route::middleware(['auth', 'ensure.onboarding.complete'])->prefix('messages')->name('messages.')->group(function () {
Route::get('/', [\App\Http\Controllers\Messaging\MessagesPageController::class, 'index'])->name('index');
Route::get('/{id}', [\App\Http\Controllers\Messaging\MessagesPageController::class, 'show'])->whereNumber('id')->name('show');
});
Route::middleware(['auth', 'admin.moderation'])->get('/admin/usernames/moderation', function () {
return Inertia::render('Admin/UsernameQueue');
})->name('admin.usernames.moderation');
// ── COMMUNITY ACTIVITY ────────────────────────────────────────────────────────
Route::get('/community/activity', [\App\Http\Controllers\Web\CommunityActivityController::class, 'index'])
->name('community.activity');
// ── FEEDS ─────────────────────────────────────────────────────────────────────
Route::middleware(['auth', 'ensure.onboarding.complete'])
->get('/feed/following', [\App\Http\Controllers\Web\Posts\FollowingFeedController::class, 'index'])
->name('feed.following');
Route::get('/feed/trending', [\App\Http\Controllers\Web\Posts\TrendingFeedController::class, 'index'])
->name('feed.trending');
Route::middleware(['auth'])
->get('/feed/saved', [\App\Http\Controllers\Web\Posts\SavedFeedController::class, 'index'])
->name('feed.saved');
Route::get('/feed/search', [\App\Http\Controllers\Web\Posts\SearchFeedController::class, 'index'])
->name('feed.search');
// ── CONTENT BROWSER (artwork / category universal router) ─────────────────────
// Bind the artwork route parameter by slug when possible, but don't hard-fail.
// Some URLs that match this shape are actually nested category paths such as
// /skins/audio/blazemedia-pro, which should fall through to category handling.
Route::bind('artwork', function ($value) {
return Artwork::where('slug', $value)->first();
});
Route::get('/{contentTypeSlug}/{categoryPath}/{artwork}', [BrowseGalleryController::class, 'showArtwork'])
->where('contentTypeSlug', 'photography|wallpapers|skins|other')
->where('categoryPath', '[^/]+(?:/[^/]+)*')
->name('artworks.show');
Route::get('/{contentTypeSlug}/{path?}', [BrowseGalleryController::class, 'content'])
->where('contentTypeSlug', 'photography|wallpapers|skins|other')
->where('path', '.*')
->name('content.route');
// ── FALLBACK 404 — must be last ───────────────────────────────────────────────
Route::fallback(function (\Illuminate\Http\Request $request) {
return app(\App\Http\Controllers\Web\ErrorController::class)->handleNotFound($request);
})->name('404.fallback');